diff --git a/README.md b/README.md
index 74768d13..a3f4c39e 100644
--- a/README.md
+++ b/README.md
@@ -20,74 +20,77 @@ some Citrus test cases.
Each sample folder demonstrates a special aspect of how to use Citrus. Most of the samples use a simple todo-list application as
system under test. Please find following list of samples and their primary objective:
-| Samples | Description |
-|---------------------------------------|:-----------:|
-| [sample-reporting](sample-reporting)| Shows how to add a custom reporter |
-| [sample-docker](sample-docker)| Shows how to use Citrus within Docker infrastructure |
-| [sample-kubernetes](sample-kubernetes)| Shows how to use Citrus within Kubernetes infrastructure |
-| [sample-gradle](sample-gradle)| Uses Gradle build to execute tests |
-| [sample-annotation-config](sample-annotation-config)| Uses annotation based endpoint configuration |
-| [sample-javaconfig](sample-javaconfig)| Uses pure Java POJOs for configuration |
-| [sample-groovy](sample-groovy)| Uses Groovy scripts to define Citrus test cases |
-| [sample-behaviors](sample-behaviors)| Shows how to reuse test actions in test behaviors |
-| [sample-dictionaries](sample-dictionaries)| Shows how to incorporate message manipulation using data dictionaries |
-| [sample-message-store](sample-message-store)| Shows how to access internal message store |
-| [sample-binary](sample-binary)| Shows binary message content handling in Citrus |
-| [sample-hamcrest](sample-hamcrest)| Shows Hamcrest matcher support in validation and conditions |
-| [sample-mail](sample-mail)| Shows mail server activities in Citrus |
-| [sample-selenium](sample-selenium)| Perform UI testing with Selenium and Citrus |
-| [sample-dynamic-endpoints](sample-dynamic-endpoints)| Shows dynamic endpoint component usage |
-| [sample-jms](sample-jms)| Shows JMS message broker integration |
-| [sample-kafka](sample-kafka)| Shows Kafka integration |
-| [sample-rmi](sample-rmi)| Shows how to use RMI with Citrus as a client and server |
-| [sample-camel-context](sample-camel-context)| Interact with Apache Camel context and routes |
-| Samples DB | Description |
-| [sample-jdbc](samples-db/sample-jdbc)| Simulates database server with JDBC |
-| [sample-jdbc-callable-statements](samples-db/sample-jdbc-callable-statements)| Simulates database server communication using callable statements |
-| [sample-jdbc-transactions](samples-db/sample-jdbc-transactions)| Simulates database server with transactional JDBC |
-| [sample-sql](samples-db/sample-sql)| Validates stored data in relational database |
-| Samples JSON | Description |
-| [sample-json](samples-json/sample-json)| Shows Json payload validation feature with JsonPath validation |
-| [sample-databind](samples-json/sample-databind)| Shows JSON object mapping feature when sending and receiving messages |
-| Samples XML | Description |
-| [sample-xml](samples-xml/sample-xml)| Shows XML validation feature with schema and Xpath validation |
-| [sample-oxm](samples-xml/sample-oxm)| Shows XML object marshalling feature when sending and receiving messages |
-| [sample-xhtml](samples-xml/sample-xhtml)| Shows XHTML validation feature |
-| Samples FTP/SFTP | Description |
-| [sample-ftp](samples-ftp/sample-ftp)| Shows FTP client and server interaction in Citrus |
-| [sample-sftp](samples-ftp/sample-sftp)| Shows SFTP client and server interaction in Citrus |
-| [sample-scp](samples-ftp/sample-scp)| Shows SCP client and server interaction in Citrus |
-| Samples TestNG | Description |
-| [sample-testng](samples-testng/sample-testng)| Shows TestNG framework support |
-| [sample-dataprovider](samples-testng/sample-dataprovider)| Shows TestNG data provider usage in Citrus |
-| Samples JUnit | Description |
-| [sample-junit](samples-junit/sample-junit)| Shows JUnit4 framework support |
-| [sample-junit5](samples-junit/sample-junit5)| Shows JUnit5 framework support |
-| Samples Http | Description |
-| [sample-swagger](samples-http/sample-swagger)| Auto generate tests from Swagger Open API |
-| [sample-http](samples-http/sample-http)| Shows Http REST API calls as a client |
-| [sample-http-loadtest](samples-http/sample-http-loadtest)| Calls REST API on Http server with multiple threads for load testing |
-| [sample-http-static-response](samples-http/sample-http-static-response)| Shows how to setup a static response generating Http server component |
-| [sample-http-query-param](samples-http/sample-http-form-data)| How to use Http form data with `x-www-form-urlencoded` Http POST |
-| [sample-http-form-data](samples-http/sample-http-query-param)| Exchange form data via Http GET/POST |
-| [sample-http-basic-auth](samples-http/sample-http-basic-auth)| Shows how to use basic authentication on client and server components |
-| [sample-https](samples-http/sample-https)| Shows how to use SSL connectivity as a client and server |
-| Samples SOAP | Description |
-| [sample-wsdl](samples-soap/sample-wsdl)| Auto generate tests from WSDL |
-| [sample-soap](samples-soap/sample-soap)| Shows basic SOAP web service support |
-| [sample-soap-mtom](samples-soap/sample-soap-mtom)| Shows how to send and receive MTOM enabled SOAP attachments |
-| [sample-soap-attachment](samples-soap/sample-soap-attachment)| Shows how to send SOAP attachments to server |
-| [sample-soap-wssecurity](samples-soap/sample-soap-wssecurity)| Shows how to configure SOAP web service client and server with WSSecurity enabled |
-| [sample-soap-wsaddressing](samples-soap/sample-soap-wsaddressing)| Shows how to configure SOAP web service client and server with WSAddressing enabled |
-| [sample-soap-ssl](samples-soap/sample-soap-ssl)| Shows how to configure SOAP web service with SSL secure connectivity |
-| [sample-soap-static-response](samples-soap/sample-soap-static-response)| Shows how to setup a static response generating SOAP web service server component |
-| Samples Cucumber BDD | Description |
-| [sample-cucumber](samples-cucumber/sample-cucumber)| Shows BDD integration with Cucumber |
-| [sample-cucumber-spring](samples-cucumber/sample-cucumber-spring)| Shows BDD integration with Cucumber using Spring Framework injection |
-| [sample-cucumber-spring2](samples-cucumber/sample-cucumber-spring2)| Shows BDD integration with Cucumber Spring Framework support |
-| Samples - Remote | Description |
-| [sample-test-jar](samples-remote/sample-test-jar)| Creates an executable test JAR to run all integration tests |
-| [sample-test-war](samples-remote/sample-test-war)| Creates a deployable test WAR to run all integration tests as part of a web deployment |
+| Samples | Description |
+|-------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------:|
+| [sample-reporting](sample-reporting) | Shows how to add a custom reporter |
+| [sample-docker](sample-docker) | Shows how to use Citrus within Docker infrastructure |
+| [sample-kubernetes](sample-kubernetes) | Shows how to use Citrus within Kubernetes infrastructure |
+| [sample-gradle](sample-gradle) | Uses Gradle build to execute tests |
+| [sample-annotation-config](sample-annotation-config) | Uses annotation based endpoint configuration |
+| [sample-javaconfig](sample-javaconfig) | Uses pure Java POJOs for configuration |
+| [sample-groovy](sample-groovy) | Uses Groovy scripts to define Citrus test cases |
+| [sample-behaviors](sample-behaviors) | Shows how to reuse test actions in test behaviors |
+| [sample-dictionaries](sample-dictionaries) | Shows how to incorporate message manipulation using data dictionaries |
+| [sample-message-store](sample-message-store) | Shows how to access internal message store |
+| [sample-binary](sample-binary) | Shows binary message content handling in Citrus |
+| [sample-hamcrest](sample-hamcrest) | Shows Hamcrest matcher support in validation and conditions |
+| [sample-mail](sample-mail) | Shows mail server activities in Citrus |
+| [sample-selenium](sample-selenium) | Perform UI testing with Selenium and Citrus |
+| [sample-dynamic-endpoints](sample-dynamic-endpoints) | Shows dynamic endpoint component usage |
+| [sample-jms](sample-jms) | Shows JMS message broker integration |
+| [sample-kafka](sample-kafka) | Shows Kafka integration |
+| [sample-rmi](sample-rmi) | Shows how to use RMI with Citrus as a client and server |
+| [sample-camel-context](sample-camel-context) | Interact with Apache Camel context and routes |
+| Samples DB | Description |
+| [sample-jdbc](samples-db/sample-jdbc) | Simulates database server with JDBC |
+| [sample-jdbc-callable-statements](samples-db/sample-jdbc-callable-statements) | Simulates database server communication using callable statements |
+| [sample-jdbc-transactions](samples-db/sample-jdbc-transactions) | Simulates database server with transactional JDBC |
+| [sample-sql](samples-db/sample-sql) | Validates stored data in relational database |
+| Samples JSON | Description |
+| [sample-json](samples-json/sample-json) | Shows Json payload validation feature with JsonPath validation |
+| [sample-databind](samples-json/sample-databind) | Shows JSON object mapping feature when sending and receiving messages |
+| Samples XML | Description |
+| [sample-xml](samples-xml/sample-xml) | Shows XML validation feature with schema and Xpath validation |
+| [sample-oxm](samples-xml/sample-oxm) | Shows XML object marshalling feature when sending and receiving messages |
+| [sample-xhtml](samples-xml/sample-xhtml) | Shows XHTML validation feature |
+| Samples FTP/SFTP | Description |
+| [sample-ftp](samples-ftp/sample-ftp) | Shows FTP client and server interaction in Citrus |
+| [sample-sftp](samples-ftp/sample-sftp) | Shows SFTP client and server interaction in Citrus |
+| [sample-scp](samples-ftp/sample-scp) | Shows SCP client and server interaction in Citrus |
+| Samples TestNG | Description |
+| [sample-testng](samples-testng/sample-testng) | Shows TestNG framework support |
+| [sample-dataprovider](samples-testng/sample-dataprovider) | Shows TestNG data provider usage in Citrus |
+| Samples JUnit | Description |
+| [sample-junit](samples-junit/sample-junit) | Shows JUnit4 framework support |
+| [sample-junit5](samples-junit/sample-junit5) | Shows JUnit5 framework support |
+| Samples Http | Description |
+| [sample-swagger](samples-http/sample-swagger) | Auto generate tests from Swagger Open API |
+| [sample-http](samples-http/sample-http) | Shows Http REST API calls as a client |
+| [sample-http-loadtest](samples-http/sample-http-loadtest) | Calls REST API on Http server with multiple threads for load testing |
+| [sample-http-static-response](samples-http/sample-http-static-response) | Shows how to setup a static response generating Http server component |
+| [sample-http-query-param](samples-http/sample-http-form-data) | How to use Http form data with `x-www-form-urlencoded` Http POST |
+| [sample-http-form-data](samples-http/sample-http-query-param) | Exchange form data via Http GET/POST |
+| [sample-http-basic-auth](samples-http/sample-http-basic-auth) | Shows how to use basic authentication on client and server components |
+| [sample-https](samples-http/sample-https) | Shows how to use SSL connectivity as a client and server |
+| Samples Websockets | Description |
+| [sample-websocket-client](samples-websocket/sample-websocket-client) | Shows how to connect to a Websocket as a client during the test |
+| [sample-websocket-server](samples-websocket/sample-websocket-server) | Shows how to provide a Websocket as a server for clients to connect |
+| Samples SOAP | Description |
+| [sample-wsdl](samples-soap/sample-wsdl) | Auto generate tests from WSDL |
+| [sample-soap](samples-soap/sample-soap) | Shows basic SOAP web service support |
+| [sample-soap-mtom](samples-soap/sample-soap-mtom) | Shows how to send and receive MTOM enabled SOAP attachments |
+| [sample-soap-attachment](samples-soap/sample-soap-attachment) | Shows how to send SOAP attachments to server |
+| [sample-soap-wssecurity](samples-soap/sample-soap-wssecurity) | Shows how to configure SOAP web service client and server with WSSecurity enabled |
+| [sample-soap-wsaddressing](samples-soap/sample-soap-wsaddressing) | Shows how to configure SOAP web service client and server with WSAddressing enabled |
+| [sample-soap-ssl](samples-soap/sample-soap-ssl) | Shows how to configure SOAP web service with SSL secure connectivity |
+| [sample-soap-static-response](samples-soap/sample-soap-static-response) | Shows how to setup a static response generating SOAP web service server component |
+| Samples Cucumber BDD | Description |
+| [sample-cucumber](samples-cucumber/sample-cucumber) | Shows BDD integration with Cucumber |
+| [sample-cucumber-spring](samples-cucumber/sample-cucumber-spring) | Shows BDD integration with Cucumber using Spring Framework injection |
+| [sample-cucumber-spring2](samples-cucumber/sample-cucumber-spring2) | Shows BDD integration with Cucumber Spring Framework support |
+| Samples - Remote | Description |
+| [sample-test-jar](samples-remote/sample-test-jar) | Creates an executable test JAR to run all integration tests |
+| [sample-test-war](samples-remote/sample-test-war) | Creates a deployable test WAR to run all integration tests as part of a web deployment |
Following sample projects cover message transports and technologies. Each of these samples provides a separate system under test applicaiton
that demonstrates the messaging aspect.
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();
+ }
+ }
+}