From b413026b3efc25ab6fb06347e47029934b53dc1e Mon Sep 17 00:00:00 2001 From: Yufeng Wang Date: Thu, 23 Oct 2025 20:16:11 -0700 Subject: [PATCH] [WEBRTCR] Send command to DUT via websocket in CI (#41486) * [WEBRTCR] Send command to DUT via websocket in CI * Update src/python_testing/TC_WEBRTCR_2_2.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/python_testing/TC_WEBRTCR_2_6.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/python_testing/TC_WEBRTCR_2_7.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Restyled by autopep8 * Restyled by isort * Address review comments --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Restyled.io --- src/python_testing/TC_WEBRTCRTestBase.py | 80 ++++++++++++++++++++++++ src/python_testing/TC_WEBRTCR_2_1.py | 22 +++++-- src/python_testing/TC_WEBRTCR_2_2.py | 22 +++++-- src/python_testing/TC_WEBRTCR_2_3.py | 20 +----- src/python_testing/TC_WEBRTCR_2_4.py | 20 +----- src/python_testing/TC_WEBRTCR_2_5.py | 22 +++++-- src/python_testing/TC_WEBRTCR_2_6.py | 22 +++++-- src/python_testing/TC_WEBRTCR_2_7.py | 22 +++++-- src/python_testing/test_metadata.yaml | 3 + 9 files changed, 174 insertions(+), 59 deletions(-) create mode 100644 src/python_testing/TC_WEBRTCRTestBase.py diff --git a/src/python_testing/TC_WEBRTCRTestBase.py b/src/python_testing/TC_WEBRTCRTestBase.py new file mode 100644 index 00000000000000..8f859f46ccc1af --- /dev/null +++ b/src/python_testing/TC_WEBRTCRTestBase.py @@ -0,0 +1,80 @@ +# +# Copyright (c) 2025 Project CHIP Authors +# All rights reserved. +# +# Licensed 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. +# + +import logging + +import websockets + +from matter.testing.matter_testing import MatterBaseTest + +# WebSocket server URI for sending commands to the DUT +SERVER_URI = "ws://localhost:9002" + + +class WEBRTCRTestBase(MatterBaseTest): + """ + Base class for WebRTC Transport Requestor test cases. + + Provides common functionality including: + - WebSocket-based command sending to the DUT + - Shared SERVER_URI constant + """ + + async def send_command(self, command): + """ + Send a command to the DUT via WebSocket and wait for response. + + Args: + command: The command string to send to the DUT + + This method: + 1. Connects to the WebSocket server at SERVER_URI + 2. Sends the command + 3. Waits for and receives the response + 4. Logs the connection, command, and response status + + Raises: + Exception: Re-raises any exceptions after logging them clearly + """ + try: + async with websockets.connect(SERVER_URI) as websocket: + logging.info(f"Connected to {SERVER_URI}") + + # Send command + logging.info(f"Sending command: {command}") + await websocket.send(command) + + # Receive response + await websocket.recv() + logging.info("Received command response") + + except ConnectionRefusedError as e: + logging.error(f"Failed to connect to WebSocket server at {SERVER_URI}: Connection refused. " + f"Is the DUT WebSocket server running? Error: {e}") + raise + + except websockets.exceptions.WebSocketException as e: + logging.error(f"WebSocket error while communicating with {SERVER_URI}: {e}") + raise + + except OSError as e: + logging.error(f"Network error while connecting to {SERVER_URI}: {e}") + raise + + except Exception as e: + logging.error(f"Unexpected error while sending command '{command}' to {SERVER_URI}: {e}") + raise diff --git a/src/python_testing/TC_WEBRTCR_2_1.py b/src/python_testing/TC_WEBRTCR_2_1.py index d37f163a02bebf..f02c0f6023ad62 100644 --- a/src/python_testing/TC_WEBRTCR_2_1.py +++ b/src/python_testing/TC_WEBRTCR_2_1.py @@ -21,6 +21,8 @@ # === BEGIN CI TEST ARGUMENTS === # test-runner-runs: # run1: +# app: ${CAMERA_CONTROLLER_APP} +# app-args: interactive server # script-args: > # --PICS src/app/tests/suites/certification/ci-pics-values # --storage-path admin_storage.json @@ -38,14 +40,15 @@ from time import sleep from mobly import asserts +from TC_WEBRTCRTestBase import WEBRTCRTestBase import matter.clusters as Clusters from matter import ChipDeviceCtrl from matter.testing.apps import AppServerSubprocess -from matter.testing.matter_testing import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from matter.testing.matter_testing import TestStep, async_test_body, default_matter_test_main -class TC_WebRTCR_2_1(MatterBaseTest): +class TC_WebRTCR_2_1(WEBRTCRTestBase): def setup_class(self): super().setup_class() @@ -152,7 +155,7 @@ async def test_TC_WebRTCR_2_1(self): ) if self.is_pics_sdk_ci_only: - # TODO: send command to DUT via websocket + await self.send_command(f"pairing onnetwork 1 {passcode}") resp = 'Y' else: resp = self.wait_for_user_input(prompt_msg) @@ -201,8 +204,17 @@ async def test_TC_WebRTCR_2_1(self): ) if self.is_pics_sdk_ci_only: - # TODO: send command to DUT via websocket - resp = 'Y' + self.th_server.set_output_match("NOT_FOUND") + self.th_server.event.clear() + + try: + await self.send_command("webrtc establish-session 1 --offer-type 1") + # Wait up to 90s until the provider logs that the data‑channel opened + if not self.th_server.event.wait(90): + raise TimeoutError("PeerConnection is not connected within 90s") + resp = 'Y' + except TimeoutError: + resp = 'N' else: resp = self.wait_for_user_input(prompt_msg) diff --git a/src/python_testing/TC_WEBRTCR_2_2.py b/src/python_testing/TC_WEBRTCR_2_2.py index 5a0c93e1fce2ef..13d403cfcc745b 100644 --- a/src/python_testing/TC_WEBRTCR_2_2.py +++ b/src/python_testing/TC_WEBRTCR_2_2.py @@ -21,6 +21,8 @@ # === BEGIN CI TEST ARGUMENTS === # test-runner-runs: # run1: +# app: ${CAMERA_CONTROLLER_APP} +# app-args: interactive server # script-args: > # --PICS src/app/tests/suites/certification/ci-pics-values # --storage-path admin_storage.json @@ -38,14 +40,15 @@ from time import sleep from mobly import asserts +from TC_WEBRTCRTestBase import WEBRTCRTestBase import matter.clusters as Clusters from matter import ChipDeviceCtrl from matter.testing.apps import AppServerSubprocess -from matter.testing.matter_testing import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from matter.testing.matter_testing import TestStep, async_test_body, default_matter_test_main -class TC_WebRTCR_2_2(MatterBaseTest): +class TC_WebRTCR_2_2(WEBRTCRTestBase): def setup_class(self): super().setup_class() @@ -152,7 +155,7 @@ async def test_TC_WebRTCR_2_2(self): ) if self.is_pics_sdk_ci_only: - # TODO: send command to DUT via websocket + await self.send_command(f"pairing onnetwork 1 {passcode}") resp = 'Y' else: resp = self.wait_for_user_input(prompt_msg) @@ -201,8 +204,17 @@ async def test_TC_WebRTCR_2_2(self): ) if self.is_pics_sdk_ci_only: - # TODO: send command to DUT via websocket - resp = 'Y' + self.th_server.set_output_match("NOT_FOUND") + self.th_server.event.clear() + + try: + await self.send_command("webrtc establish-session 1") + # Wait up to 90s until the provider logs that the data‑channel opened + if not self.th_server.event.wait(90): + raise TimeoutError("PeerConnection is not connected within 90s") + resp = 'Y' + except TimeoutError: + resp = 'N' else: resp = self.wait_for_user_input(prompt_msg) diff --git a/src/python_testing/TC_WEBRTCR_2_3.py b/src/python_testing/TC_WEBRTCR_2_3.py index f6a8f0ede99f34..7e7596538a1b70 100644 --- a/src/python_testing/TC_WEBRTCR_2_3.py +++ b/src/python_testing/TC_WEBRTCR_2_3.py @@ -38,16 +38,14 @@ import os import tempfile -import websockets from mobly import asserts +from TC_WEBRTCRTestBase import WEBRTCRTestBase from matter.testing.apps import AppServerSubprocess -from matter.testing.matter_testing import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from matter.testing.matter_testing import TestStep, async_test_body, default_matter_test_main -SERVER_URI = "ws://localhost:9002" - -class TC_WebRTCR_2_3(MatterBaseTest): +class TC_WebRTCR_2_3(WEBRTCRTestBase): def setup_class(self): super().setup_class() @@ -119,18 +117,6 @@ def pics_TC_WebRTCR_2_3(self) -> list[str]: def default_timeout(self) -> int: return 3 * 60 - async def send_command(self, command): - async with websockets.connect(SERVER_URI) as websocket: - logging.info(f"Connected to {SERVER_URI}") - - # Send command - logging.info(f"Sending command: {command}") - await websocket.send(command) - - # Receive response - await websocket.recv() - logging.info("Received command response") - @async_test_body async def test_TC_WebRTCR_2_3(self): """ diff --git a/src/python_testing/TC_WEBRTCR_2_4.py b/src/python_testing/TC_WEBRTCR_2_4.py index 54b9b4cd69edd1..922edffacbb57f 100644 --- a/src/python_testing/TC_WEBRTCR_2_4.py +++ b/src/python_testing/TC_WEBRTCR_2_4.py @@ -38,16 +38,14 @@ import os import tempfile -import websockets from mobly import asserts +from TC_WEBRTCRTestBase import WEBRTCRTestBase from matter.testing.apps import AppServerSubprocess -from matter.testing.matter_testing import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from matter.testing.matter_testing import TestStep, async_test_body, default_matter_test_main -SERVER_URI = "ws://localhost:9002" - -class TC_WebRTCR_2_4(MatterBaseTest): +class TC_WebRTCR_2_4(WEBRTCRTestBase): def setup_class(self): super().setup_class() @@ -119,18 +117,6 @@ def pics_TC_WebRTCR_2_4(self) -> list[str]: def default_timeout(self) -> int: return 3 * 60 - async def send_command(self, command): - async with websockets.connect(SERVER_URI) as websocket: - logging.info(f"Connected to {SERVER_URI}") - - # Send command - logging.info(f"Sending command: {command}") - await websocket.send(command) - - # Receive response - await websocket.recv() - logging.info("Received command response") - @async_test_body async def test_TC_WebRTCR_2_4(self): """ diff --git a/src/python_testing/TC_WEBRTCR_2_5.py b/src/python_testing/TC_WEBRTCR_2_5.py index b529d8522b78d1..59bc42475514a3 100644 --- a/src/python_testing/TC_WEBRTCR_2_5.py +++ b/src/python_testing/TC_WEBRTCR_2_5.py @@ -21,6 +21,8 @@ # === BEGIN CI TEST ARGUMENTS === # test-runner-runs: # run1: +# app: ${CAMERA_CONTROLLER_APP} +# app-args: interactive server # script-args: > # --PICS src/app/tests/suites/certification/ci-pics-values # --storage-path admin_storage.json @@ -38,13 +40,14 @@ from time import sleep from mobly import asserts +from TC_WEBRTCRTestBase import WEBRTCRTestBase from matter import ChipDeviceCtrl from matter.testing.apps import AppServerSubprocess -from matter.testing.matter_testing import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from matter.testing.matter_testing import TestStep, async_test_body, default_matter_test_main -class TC_WebRTCR_2_5(MatterBaseTest): +class TC_WebRTCR_2_5(WEBRTCRTestBase): def setup_class(self): super().setup_class() @@ -156,7 +159,7 @@ async def test_TC_WebRTCR_2_5(self): ) if self.is_pics_sdk_ci_only: - # TODO: send command to DUT via websocket + await self.send_command(f"pairing onnetwork 1 {passcode}") resp = 'Y' else: resp = self.wait_for_user_input(prompt_msg) @@ -204,8 +207,17 @@ async def test_TC_WebRTCR_2_5(self): ) if self.is_pics_sdk_ci_only: - # TODO: establish session via websocket - resp = 'Y' + self.th_server.set_output_match("PeerConnection State: Connected") + self.th_server.event.clear() + + try: + await self.send_command("webrtc establish-session 1") + # Wait up to 90s until the provider logs that the data‑channel opened + if not self.th_server.event.wait(90): + raise TimeoutError("PeerConnection is not connected within 90s") + resp = 'Y' + except TimeoutError: + resp = 'N' else: resp = self.wait_for_user_input(prompt_msg) diff --git a/src/python_testing/TC_WEBRTCR_2_6.py b/src/python_testing/TC_WEBRTCR_2_6.py index 8dbcdd6904129e..4255aa21aab02f 100644 --- a/src/python_testing/TC_WEBRTCR_2_6.py +++ b/src/python_testing/TC_WEBRTCR_2_6.py @@ -21,6 +21,8 @@ # === BEGIN CI TEST ARGUMENTS === # test-runner-runs: # run1: +# app: ${CAMERA_CONTROLLER_APP} +# app-args: interactive server # script-args: > # --PICS src/app/tests/suites/certification/ci-pics-values # --storage-path admin_storage.json @@ -38,14 +40,15 @@ from time import sleep from mobly import asserts +from TC_WEBRTCRTestBase import WEBRTCRTestBase import matter.clusters as Clusters from matter import ChipDeviceCtrl from matter.testing.apps import AppServerSubprocess -from matter.testing.matter_testing import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from matter.testing.matter_testing import TestStep, async_test_body, default_matter_test_main -class TC_WebRTCR_2_6(MatterBaseTest): +class TC_WebRTCR_2_6(WEBRTCRTestBase): def setup_class(self): super().setup_class() @@ -153,7 +156,7 @@ async def test_TC_WebRTCR_2_6(self): ) if self.is_pics_sdk_ci_only: - # TODO: send command to DUT via websocket + await self.send_command(f"pairing onnetwork 1 {passcode}") resp = 'Y' else: resp = self.wait_for_user_input(prompt_msg) @@ -203,8 +206,17 @@ async def test_TC_WebRTCR_2_6(self): ) if self.is_pics_sdk_ci_only: - # TODO: send command to DUT via websocket - resp = 'Y' + self.th_server.set_output_match("NOT_FOUND") + self.th_server.event.clear() + + try: + await self.send_command("webrtc establish-session 1") + # Wait up to 90s until the provider logs that the data‑channel opened + if not self.th_server.event.wait(90): + raise TimeoutError("PeerConnection is not connected within 90s") + resp = 'Y' + except TimeoutError: + resp = 'N' else: resp = self.wait_for_user_input(prompt_msg) diff --git a/src/python_testing/TC_WEBRTCR_2_7.py b/src/python_testing/TC_WEBRTCR_2_7.py index 7f89c29c8f4379..2a4a5be2b48278 100644 --- a/src/python_testing/TC_WEBRTCR_2_7.py +++ b/src/python_testing/TC_WEBRTCR_2_7.py @@ -21,6 +21,8 @@ # === BEGIN CI TEST ARGUMENTS === # test-runner-runs: # run1: +# app: ${CAMERA_CONTROLLER_APP} +# app-args: interactive server # script-args: > # --PICS src/app/tests/suites/certification/ci-pics-values # --storage-path admin_storage.json @@ -38,14 +40,15 @@ from time import sleep from mobly import asserts +from TC_WEBRTCRTestBase import WEBRTCRTestBase import matter.clusters as Clusters from matter import ChipDeviceCtrl from matter.testing.apps import AppServerSubprocess -from matter.testing.matter_testing import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from matter.testing.matter_testing import TestStep, async_test_body, default_matter_test_main -class TC_WebRTCR_2_7(MatterBaseTest): +class TC_WebRTCR_2_7(WEBRTCRTestBase): def setup_class(self): super().setup_class() @@ -153,7 +156,7 @@ async def test_TC_WebRTCR_2_7(self): ) if self.is_pics_sdk_ci_only: - # TODO: send command to DUT via websocket + await self.send_command(f"pairing onnetwork 1 {passcode}") resp = 'Y' else: resp = self.wait_for_user_input(prompt_msg) @@ -203,8 +206,17 @@ async def test_TC_WebRTCR_2_7(self): ) if self.is_pics_sdk_ci_only: - # TODO: send command to DUT via websocket - resp = 'Y' + self.th_server.set_output_match("CONSTRAINT_ERROR") + self.th_server.event.clear() + + try: + await self.send_command("webrtc establish-session 1") + # Wait up to 90s until the provider logs that the data‑channel opened + if not self.th_server.event.wait(90): + raise TimeoutError("PeerConnection is not connected within 90s") + resp = 'Y' + except TimeoutError: + resp = 'N' else: resp = self.wait_for_user_input(prompt_msg) diff --git a/src/python_testing/test_metadata.yaml b/src/python_testing/test_metadata.yaml index ca797d50fb2a06..7df76cecb20922 100644 --- a/src/python_testing/test_metadata.yaml +++ b/src/python_testing/test_metadata.yaml @@ -130,6 +130,9 @@ not_automated: reason: Depends on a browser peer for webrtc. CI is not supported. - name: TC_CHIMETestBase.py reason: Shared code for the Chime Cluster, not a standalone test. + - name: TC_WEBRTCRTestBase.py + reason: + Shared code for the WebRTC Requestor Cluster, not a standalone test. - name: TC_WEBRTCPTestBase.py reason: Shared code for the WebRTC Provider Cluster, not a standalone test.