diff --git a/pom.xml b/pom.xml
index 213a4f4a..e202f8fa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -38,5 +38,6 @@
sample-binarysamples-soapsamples-db
+ samples-websocket
diff --git a/samples-websocket/pom.xml b/samples-websocket/pom.xml
new file mode 100644
index 00000000..a5d4532f
--- /dev/null
+++ b/samples-websocket/pom.xml
@@ -0,0 +1,34 @@
+
+
+
+
+ 4.0.0
+
+ org.citrusframework.samples
+ citrus-samples-websocket
+ 4.0.0
+ Citrus Samples:: Websocket Samples
+ pom
+
+
+ sample-websocket-client
+ sample-websocket-server
+
+
diff --git a/samples-websocket/sample-websocket-client/README.md b/samples-websocket/sample-websocket-client/README.md
new file mode 100644
index 00000000..9847217c
--- /dev/null
+++ b/samples-websocket/sample-websocket-client/README.md
@@ -0,0 +1,120 @@
+Websockets sample ![Logo][1]
+==============
+
+This sample shows how to use the Citrus Websocket client to connect to a socket on the server and send/receive data.
+Citrus Websocket features are also described in detail in [reference guide][4]
+
+Objectives
+---------
+
+The sample uses a small Quarkus application that provides a server side websocket for clients to connect to.
+All messages sent to the socket get pushed to the connected clients.
+Citrus is able to connect to the socket as a client in order to send/receive all messages via this socket broadcast.
+
+In the test Citrus will connect to the socket and send some data to it.
+The same message is received in a next step to verify the message broadcast.
+
+We need a Websocket client component in the configuration:
+
+```java
+@BindToRegistry
+public WebSocketClient chatClient() {
+ return new WebSocketClientBuilder()
+ .requestUrl("ws://localhost:8081/chat/citrus")
+ .build();
+}
+```
+
+In the test cases we can reference this client component in order to send REST calls to the server.
+
+```java
+t.when(send("http://localhost:8081/chat/citrus-user")
+ .message()
+ .fork(true)
+ .body("Hello from Citrus!"));
+```
+
+**NOTE:** The send action uses `fork=true` option.
+This is because the send operation should not block the test to proceed and verify the server side socket communication.
+
+The Quarkus server socket should accept the connection and process the message sent by the Citrus client.
+As a result of this we are able to verify the same message on the client because of the server socket broadcast.
+This time the message has been adjusted by the Quarkus server with `>> {username}:` prefix.
+
+```java
+t.then(receive()
+ .endpoint(chatClient)
+ .message()
+ .body(">> citrus: Hello Quarkus chat!"));
+```
+
+Run
+---------
+
+The sample application uses QuarkusTest as a framework to run the tests with JUnit Jupiter.
+So you can compile, package and test the sample with Maven to run the test.
+
+```shell
+mvn clean verify
+```
+
+This executes the complete Maven build lifecycle.
+The Citrus test cases are part of the unit testing lifecycle and get executed automatically.
+The Quarkus application is also started automatically as part of the test.
+
+During the build you will see Citrus performing some integration tests.
+
+System under test
+---------
+
+The sample uses a small Quarkus application that provides the Websocket implementation.
+You can read more about Quarkus websocket support in [https://quarkus.io/guides/websockets](https://quarkus.io/guides/websockets).
+
+Up to now we have started the Quarkus sample application as part of the unit test during the Maven build lifecycle.
+This approach is fantastic when running automated tests in a continuous build.
+
+There may be times we want to test against a standalone application.
+
+You can start the sample Quarkus application in DevServices mode with this command.
+
+```shell
+mvn quarkus:dev
+```
+
+Now we are ready to execute some Citrus tests in a separate JVM.
+
+Citrus test
+---------
+
+Once the sample application is deployed and running you can execute the Citrus test cases.
+Open a separate command line terminal and navigate to the sample folder.
+
+Execute all Citrus tests by calling
+
+```shell
+mvn verify
+```
+
+You can also pick a single test by calling
+
+```shell
+mvn clean verify -Dtest=
+```
+
+You should see Citrus performing several tests with lots of debugging output in both terminals (sample application
+and Citrus test client).
+And of course green tests at the very end of the build.
+
+Of course, you can also start the Citrus tests from your favorite IDE.
+Just start the Citrus test using the JUnit Jupiter IDE integration in IntelliJ, Eclipse or Netbeans.
+
+Further information
+---------
+
+For more information on Citrus see [www.citrusframework.org][2], including
+a complete [reference manual][3].
+
+ [1]: https://citrusframework.org/img/brand-logo.png "Citrus"
+ [2]: https://citrusframework.org
+ [3]: https://citrusframework.org/reference/html/
+ [4]: https://citrusframework.org/reference/html#websocket
diff --git a/samples-websocket/sample-websocket-client/pom.xml b/samples-websocket/sample-websocket-client/pom.xml
new file mode 100644
index 00000000..ba0203f6
--- /dev/null
+++ b/samples-websocket/sample-websocket-client/pom.xml
@@ -0,0 +1,160 @@
+
+
+
+ 4.0.0
+
+ org.citrusframework.samples
+ citrus-sample-websocket-client
+ Citrus Samples:: Websocket Client
+ 4.0.0
+
+
+ UTF-8
+ UTF-8
+
+ 3.11.0
+ 3.1.2
+
+ 3.6.6
+ 4.0.2
+
+
+
+
+
+ io.quarkus.platform
+ quarkus-bom
+ ${quarkus.platform.version}
+ pom
+ import
+
+
+ org.citrusframework
+ citrus-bom
+ ${citrus.version}
+ pom
+ import
+
+
+
+
+
+
+
+ io.quarkus
+ quarkus-websockets
+
+
+
+
+ io.quarkus
+ quarkus-junit5
+ test
+
+
+
+
+ org.citrusframework
+ citrus-quarkus
+ test
+
+
+ org.citrusframework
+ citrus-websocket
+ test
+
+
+ org.citrusframework
+ citrus-validation-text
+ test
+
+
+
+
+
+
+ io.quarkus.platform
+ quarkus-maven-plugin
+ ${quarkus.platform.version}
+
+
+ 9092
+
+
+ true
+
+
+
+ build
+ generate-code
+ generate-code-tests
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${compiler-plugin.version}
+
+ ${project.build.sourceEncoding}
+
+ -parameters
+
+
+ 17
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${surefire-plugin.version}
+
+
+ org.jboss.logmanager.LogManager
+ ${maven.home}
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ ${surefire-plugin.version}
+
+
+
+ integration-test
+ verify
+
+
+
+ ${project.build.directory}/${project.build.finalName}-runner
+ org.jboss.logmanager.LogManager
+ ${maven.home}
+
+
+
+
+
+
+
+
+
diff --git a/samples-websocket/sample-websocket-client/src/main/java/org/citrusframework/samples/websocket/ChatSocket.java b/samples-websocket/sample-websocket-client/src/main/java/org/citrusframework/samples/websocket/ChatSocket.java
new file mode 100644
index 00000000..7dd9f283
--- /dev/null
+++ b/samples-websocket/sample-websocket-client/src/main/java/org/citrusframework/samples/websocket/ChatSocket.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.citrusframework.samples.websocket;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.websocket.OnClose;
+import jakarta.websocket.OnError;
+import jakarta.websocket.OnMessage;
+import jakarta.websocket.OnOpen;
+import jakarta.websocket.Session;
+import jakarta.websocket.server.PathParam;
+import jakarta.websocket.server.ServerEndpoint;
+
+import org.jboss.logging.Logger;
+
+@ServerEndpoint("/chat/{username}")
+@ApplicationScoped
+public class ChatSocket {
+
+ private static final Logger LOG = Logger.getLogger(ChatSocket.class);
+
+ Map sessions = new ConcurrentHashMap<>();
+
+ @OnOpen
+ public void onOpen(Session session, @PathParam("username") String username) {
+ sessions.put(username, session);
+ }
+
+ @OnClose
+ public void onClose(Session session, @PathParam("username") String username) {
+ sessions.remove(username);
+ broadcast("User " + username + " left");
+ }
+
+ @OnError
+ public void onError(Session session, @PathParam("username") String username, Throwable throwable) {
+ sessions.remove(username);
+ LOG.error("onError", throwable);
+ broadcast("User " + username + " left on error: " + throwable);
+ }
+
+ @OnMessage
+ public void onMessage(String message, @PathParam("username") String username) {
+ if (message.equalsIgnoreCase("_ready_")) {
+ broadcast("User " + username + " joined");
+ } else {
+ broadcast(">> " + username + ": " + message);
+ }
+ }
+
+ private void broadcast(String message) {
+ sessions.values().forEach(s -> {
+ s.getAsyncRemote().sendObject(message, result -> {
+ if (result.getException() != null) {
+ System.out.println("Unable to send message: " + result.getException());
+ }
+ });
+ });
+ }
+
+}
diff --git a/samples-websocket/sample-websocket-client/src/main/resources/application.properties b/samples-websocket/sample-websocket-client/src/main/resources/application.properties
new file mode 100644
index 00000000..c0824d2d
--- /dev/null
+++ b/samples-websocket/sample-websocket-client/src/main/resources/application.properties
@@ -0,0 +1,21 @@
+#
+# Copyright 2024 the original author or authors.
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+quarkus.log.level=INFO
+quarkus.arc.ignored-split-packages=org.citrusframework.*
diff --git a/samples-websocket/sample-websocket-client/src/test/java/org/citrusframework/samples/websocket/ChatSocketTest.java b/samples-websocket/sample-websocket-client/src/test/java/org/citrusframework/samples/websocket/ChatSocketTest.java
new file mode 100644
index 00000000..d0d4f495
--- /dev/null
+++ b/samples-websocket/sample-websocket-client/src/test/java/org/citrusframework/samples/websocket/ChatSocketTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.citrusframework.samples.websocket;
+
+import io.quarkus.test.junit.QuarkusTest;
+import org.citrusframework.TestCaseRunner;
+import org.citrusframework.annotations.CitrusConfiguration;
+import org.citrusframework.annotations.CitrusEndpoint;
+import org.citrusframework.annotations.CitrusResource;
+import org.citrusframework.quarkus.CitrusSupport;
+import org.citrusframework.spi.BindToRegistry;
+import org.citrusframework.websocket.client.WebSocketClient;
+import org.citrusframework.websocket.client.WebSocketClientBuilder;
+import org.junit.jupiter.api.Test;
+
+import static org.citrusframework.actions.ReceiveMessageAction.Builder.receive;
+import static org.citrusframework.actions.SendMessageAction.Builder.send;
+
+@QuarkusTest
+@CitrusSupport
+@CitrusConfiguration( classes = { ChatSocketTest.EndpointConfig.class } )
+class ChatSocketTest {
+
+ @CitrusResource
+ TestCaseRunner t;
+
+ @CitrusEndpoint
+ WebSocketClient chatClient;
+
+ @Test
+ void shouldConnectAndSendMessage() {
+ t.given(send()
+ .endpoint(chatClient)
+ .message()
+ .body("Hello Quarkus chat!"));
+
+ t.then(receive()
+ .endpoint(chatClient)
+ .message()
+ .body(">> citrus: Hello Quarkus chat!"));
+ }
+
+ public static class EndpointConfig {
+
+ @BindToRegistry
+ public WebSocketClient chatClient() {
+ return new WebSocketClientBuilder()
+ .requestUrl("ws://localhost:8081/chat/citrus")
+ .build();
+ }
+ }
+}
diff --git a/samples-websocket/sample-websocket-server/README.md b/samples-websocket/sample-websocket-server/README.md
new file mode 100644
index 00000000..8212f717
--- /dev/null
+++ b/samples-websocket/sample-websocket-server/README.md
@@ -0,0 +1,170 @@
+Websockets sample ![Logo][1]
+==============
+
+This sample shows how to use the Citrus Websocket server to provide a socket on the server so that clients can connect to and send/receive data.
+Citrus Websocket features are also described in detail in [reference guide][4]
+
+Objectives
+---------
+
+The sample uses a small Quarkus application that provides a websocket client to connect to the Citrus Websocket server.
+All messages sent to the socket get pushed to the connected clients.
+Citrus is able to start the socket as a server in order to accept client sessions and broadcast messages to all connected clients.
+
+In the test Citrus will verify client connections and broadcast some data to the clients.
+
+We need a Websocket server component in the configuration:
+
+```java
+public static class EndpointConfig {
+
+ private WebSocketEndpoint chatEndpoint;
+
+ @BindToRegistry
+ public WebSocketEndpoint chatEndpoint() {
+ if (chatEndpoint == null) {
+ WebSocketServerEndpointConfiguration chatEndpointConfig = new WebSocketServerEndpointConfiguration();
+ chatEndpointConfig.setEndpointUri("/chat");
+ chatEndpoint = new WebSocketEndpoint(chatEndpointConfig);
+ }
+
+ return chatEndpoint;
+ }
+
+ @BindToRegistry
+ public WebSocketServer chatServer() {
+ return new WebSocketServerBuilder()
+ .webSockets(Collections.singletonList(chatEndpoint()))
+ .port(8088)
+ .autoStart(true)
+ .build();
+ }
+}
+```
+
+The server listens on port `8088` and provides a websocket endpoint on `/chat`.
+So clients may connect to the socket opening sessions on `http://localhost:8088/chat`.
+
+In the test cases we can receive client sessions with a normal receive action that references the websocket endpoint.
+
+```java
+t.then(receive()
+ .endpoint(chatEndpoint)
+ .message()
+ .body("Quarkus wants to join ..."));
+```
+
+**NOTE:** The message `Quarkus wants to join ...` is automatically sent by the sample Quarkus application when the session is opened.
+We can respond with a server side message that is sent to all connected clients.
+
+```java
+t.then(send()
+ .endpoint(chatEndpoint)
+ .message()
+ .body("Welcome Quarkus!"));
+```
+
+You will see this response printed to the log output of the Quarkus sample application.
+
+The test is able to trigger some client messages on the Quarkus application by sending a Http POST request to the REST API of the Quarkus application.
+
+```java
+t.when(http().client("http://localhost:8081")
+ .send()
+ .post("chat/citrus-user")
+ .fork(true)
+ .message()
+ .body("Hello from Citrus!"));
+```
+
+**NOTE:** The test uses a dynamic Http endpoint URL to send the POST request.
+The username is given as a path parameter and the message body represents the message that is sent to the websocket.
+
+Now the test is able to verify the websocket message on the server.
+This time the message has been adjusted by the Quarkus client with `>> {username}:` prefix.
+
+```java
+t.then(receive()
+ .endpoint(chatEndpoint)
+ .message()
+ .body(">> citrus-user: Hello from Citrus!"));
+```
+
+At the very end of the test we can verify the response of the Http POST request.
+
+```java
+t.then(http().client("http://localhost:8081")
+ .receive()
+ .response(HttpStatus.CREATED));
+```
+
+Run
+---------
+
+The sample application uses QuarkusTest as a framework to run the tests with JUnit Jupiter.
+So you can compile, package and test the sample with Maven to run the test.
+
+```shell
+mvn clean verify
+```
+
+This executes the complete Maven build lifecycle.
+The Citrus test cases are part of the unit testing lifecycle and get executed automatically.
+The Quarkus application is also started automatically as part of the test.
+
+During the build you will see Citrus performing some integration tests.
+
+System under test
+---------
+
+The sample uses a small Quarkus application that provides the Websocket implementation.
+You can read more about Quarkus websocket support in [https://quarkus.io/guides/websockets](https://quarkus.io/guides/websockets).
+
+Up to now we have started the Quarkus sample application as part of the unit test during the Maven build lifecycle.
+This approach is fantastic when running automated tests in a continuous build.
+
+There may be times we want to test against a standalone application.
+
+You can start the sample Quarkus application in DevServices mode with this command.
+
+```shell
+mvn quarkus:dev
+```
+
+Now we are ready to execute some Citrus tests in a separate JVM.
+
+Citrus test
+---------
+
+Once the sample application is deployed and running you can execute the Citrus test cases.
+Open a separate command line terminal and navigate to the sample folder.
+
+Execute all Citrus tests by calling
+
+```shell
+mvn verify
+```
+
+You can also pick a single test by calling
+
+```shell
+mvn clean verify -Dtest=
+```
+
+You should see Citrus performing several tests with lots of debugging output in both terminals (sample application
+and Citrus test client).
+And of course green tests at the very end of the build.
+
+Of course, you can also start the Citrus tests from your favorite IDE.
+Just start the Citrus test using the JUnit Jupiter IDE integration in IntelliJ, Eclipse or Netbeans.
+
+Further information
+---------
+
+For more information on Citrus see [www.citrusframework.org][2], including
+a complete [reference manual][3].
+
+ [1]: https://citrusframework.org/img/brand-logo.png "Citrus"
+ [2]: https://citrusframework.org
+ [3]: https://citrusframework.org/reference/html/
+ [4]: https://citrusframework.org/reference/html#websocket
diff --git a/samples-websocket/sample-websocket-server/pom.xml b/samples-websocket/sample-websocket-server/pom.xml
new file mode 100644
index 00000000..2bdbf9fd
--- /dev/null
+++ b/samples-websocket/sample-websocket-server/pom.xml
@@ -0,0 +1,168 @@
+
+
+
+ 4.0.0
+
+ org.citrusframework.samples
+ citrus-sample-websocket-server
+ Citrus Samples:: Websocket Server
+ 4.0.0
+
+
+ UTF-8
+ UTF-8
+
+ 3.11.0
+ 3.1.2
+
+ 3.6.6
+ 4.0.2
+
+
+
+
+
+ io.quarkus.platform
+ quarkus-bom
+ ${quarkus.platform.version}
+ pom
+ import
+
+
+ org.citrusframework
+ citrus-bom
+ ${citrus.version}
+ pom
+ import
+
+
+
+
+
+
+
+ io.quarkus
+ quarkus-resteasy
+
+
+ io.quarkus
+ quarkus-arc
+
+
+ io.quarkus
+ quarkus-websockets
+
+
+
+
+ io.quarkus
+ quarkus-junit5
+ test
+
+
+
+
+ org.citrusframework
+ citrus-quarkus
+ test
+
+
+ org.citrusframework
+ citrus-websocket
+ test
+
+
+ org.citrusframework
+ citrus-validation-text
+ test
+
+
+
+
+
+
+ io.quarkus.platform
+ quarkus-maven-plugin
+ ${quarkus.platform.version}
+
+
+ 9092
+
+
+ true
+
+
+
+ build
+ generate-code
+ generate-code-tests
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${compiler-plugin.version}
+
+ ${project.build.sourceEncoding}
+
+ -parameters
+
+
+ 17
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${surefire-plugin.version}
+
+
+ org.jboss.logmanager.LogManager
+ ${maven.home}
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ ${surefire-plugin.version}
+
+
+
+ integration-test
+ verify
+
+
+
+ ${project.build.directory}/${project.build.finalName}-runner
+ org.jboss.logmanager.LogManager
+ ${maven.home}
+
+
+
+
+
+
+
+
+
diff --git a/samples-websocket/sample-websocket-server/src/main/java/org/citrusframework/samples/websocket/ChatResource.java b/samples-websocket/sample-websocket-server/src/main/java/org/citrusframework/samples/websocket/ChatResource.java
new file mode 100644
index 00000000..8bbd1c70
--- /dev/null
+++ b/samples-websocket/sample-websocket-server/src/main/java/org/citrusframework/samples/websocket/ChatResource.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.citrusframework.samples.websocket;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+
+@Path("chat/{username}")
+public class ChatResource {
+
+ @Inject
+ ChatService chatService;
+
+ @POST
+ @Produces("text/plain")
+ @Consumes(MediaType.TEXT_PLAIN)
+ public Response add(@PathParam("username") String user, String message) {
+ chatService.send(user, message);
+ return Response.status(Response.Status.CREATED).build();
+ }
+}
diff --git a/samples-websocket/sample-websocket-server/src/main/java/org/citrusframework/samples/websocket/ChatService.java b/samples-websocket/sample-websocket-server/src/main/java/org/citrusframework/samples/websocket/ChatService.java
new file mode 100644
index 00000000..f4cbfc66
--- /dev/null
+++ b/samples-websocket/sample-websocket-server/src/main/java/org/citrusframework/samples/websocket/ChatService.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.citrusframework.samples.websocket;
+
+import java.io.IOException;
+import java.net.URI;
+
+import jakarta.inject.Singleton;
+import jakarta.websocket.ClientEndpoint;
+import jakarta.websocket.ContainerProvider;
+import jakarta.websocket.DeploymentException;
+import jakarta.websocket.OnMessage;
+import jakarta.websocket.OnOpen;
+import jakarta.websocket.Session;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.jboss.logging.Logger;
+
+@Singleton
+public class ChatService {
+
+ private static final Logger LOG = Logger.getLogger(ChatService.class);
+
+ @ConfigProperty(name = "chat.websocket.uri", defaultValue = "http://localhost:8088/chat")
+ URI uri;
+
+ private Session session;
+
+ public void send(String user, String message) {
+ openSession().getAsyncRemote().sendText(">> %s: %s".formatted(user, message));
+ }
+
+ private Session openSession() {
+ if (session == null) {
+ try {
+ session = ContainerProvider.getWebSocketContainer().connectToServer(ChatClient.class, uri);
+ } catch (DeploymentException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ return session;
+ }
+
+ @ClientEndpoint
+ public static class ChatClient {
+
+ @OnOpen
+ public void open(Session session) {
+ LOG.info("CONNECTED!");
+ session.getAsyncRemote().sendText("Quarkus wants to join ...");
+ }
+
+ @OnMessage
+ void message(String msg) {
+ LOG.info(msg);
+ }
+
+ }
+}
diff --git a/samples-websocket/sample-websocket-server/src/main/resources/application.properties b/samples-websocket/sample-websocket-server/src/main/resources/application.properties
new file mode 100644
index 00000000..c0824d2d
--- /dev/null
+++ b/samples-websocket/sample-websocket-server/src/main/resources/application.properties
@@ -0,0 +1,21 @@
+#
+# Copyright 2024 the original author or authors.
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+quarkus.log.level=INFO
+quarkus.arc.ignored-split-packages=org.citrusframework.*
diff --git a/samples-websocket/sample-websocket-server/src/test/java/org/citrusframework/samples/websocket/ChatSocketTest.java b/samples-websocket/sample-websocket-server/src/test/java/org/citrusframework/samples/websocket/ChatSocketTest.java
new file mode 100644
index 00000000..0f6e397f
--- /dev/null
+++ b/samples-websocket/sample-websocket-server/src/test/java/org/citrusframework/samples/websocket/ChatSocketTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.citrusframework.samples.websocket;
+
+import java.util.Collections;
+
+import io.quarkus.test.junit.QuarkusTest;
+import org.citrusframework.TestCaseRunner;
+import org.citrusframework.annotations.CitrusConfiguration;
+import org.citrusframework.annotations.CitrusEndpoint;
+import org.citrusframework.annotations.CitrusResource;
+import org.citrusframework.quarkus.CitrusSupport;
+import org.citrusframework.spi.BindToRegistry;
+import org.citrusframework.websocket.endpoint.WebSocketEndpoint;
+import org.citrusframework.websocket.server.WebSocketServer;
+import org.citrusframework.websocket.server.WebSocketServerBuilder;
+import org.citrusframework.websocket.server.WebSocketServerEndpointConfiguration;
+import org.junit.jupiter.api.Test;
+import org.springframework.http.HttpStatus;
+
+import static org.citrusframework.actions.ReceiveMessageAction.Builder.receive;
+import static org.citrusframework.actions.SendMessageAction.Builder.send;
+import static org.citrusframework.http.actions.HttpActionBuilder.http;
+
+@QuarkusTest
+@CitrusSupport
+@CitrusConfiguration(classes = { ChatSocketTest.EndpointConfig.class })
+class ChatSocketTest {
+
+ @CitrusResource
+ TestCaseRunner t;
+
+ @CitrusEndpoint
+ WebSocketEndpoint chatEndpoint;
+
+ @Test
+ void shouldBroadcastMessages() {
+ t.when(http()
+ .client("http://localhost:8081")
+ .send()
+ .post("chat/citrus-user")
+ .fork(true)
+ .message()
+ .body("Hello from Citrus!"));
+
+ t.then(receive()
+ .endpoint(chatEndpoint)
+ .message()
+ .body("Quarkus wants to join ..."));
+
+ t.then(send()
+ .endpoint(chatEndpoint)
+ .message()
+ .body("Welcome Quarkus!"));
+
+ t.then(receive()
+ .endpoint(chatEndpoint)
+ .message()
+ .body(">> citrus-user: Hello from Citrus!"));
+
+ t.then(http().client("http://localhost:8081")
+ .receive()
+ .response(HttpStatus.CREATED));
+ }
+
+ public static class EndpointConfig {
+
+ private WebSocketEndpoint chatEndpoint;
+
+ @BindToRegistry
+ public WebSocketEndpoint chatEndpoint() {
+ if (chatEndpoint == null) {
+ WebSocketServerEndpointConfiguration chatEndpointConfig = new WebSocketServerEndpointConfiguration();
+ chatEndpointConfig.setEndpointUri("/chat");
+ chatEndpoint = new WebSocketEndpoint(chatEndpointConfig);
+ }
+
+ return chatEndpoint;
+ }
+
+ @BindToRegistry
+ public WebSocketServer chatServer() {
+ return new WebSocketServerBuilder()
+ .webSockets(Collections.singletonList(chatEndpoint()))
+ .port(8088)
+ .autoStart(true)
+ .build();
+ }
+ }
+}