chore: auto release commit (2026-06-15 17:32:47)
All checks were successful
技能自动化发布 / release (push) Successful in 7s
All checks were successful
技能自动化发布 / release (push) Successful in 7s
This commit is contained in:
0
examples/simulator_browser_rpa/tests/.gitkeep
Normal file
0
examples/simulator_browser_rpa/tests/.gitkeep
Normal file
10
examples/simulator_browser_rpa/tests/conftest.py
Normal file
10
examples/simulator_browser_rpa/tests/conftest.py
Normal file
@@ -0,0 +1,10 @@
|
||||
"""pytest 路径:将 examples/simulator_browser_rpa/scripts 加入 import 路径。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
SCRIPTS = Path(__file__).resolve().parents[1] / "scripts"
|
||||
if str(SCRIPTS) not in sys.path:
|
||||
sys.path.insert(0, str(SCRIPTS))
|
||||
@@ -0,0 +1,64 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""adapter 工厂 dispatch 单元测试。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from service.adapter import (
|
||||
BatchItem,
|
||||
MockBatchAdapter,
|
||||
SimulatorBrowserRpaAdapter,
|
||||
select_adapter,
|
||||
)
|
||||
|
||||
|
||||
class TestAdapterDispatch(unittest.TestCase):
|
||||
def test_default_returns_simulator_rpa(self) -> None:
|
||||
with patch.dict(os.environ, {}, clear=False):
|
||||
os.environ.pop("OPENCLAW_TEST_TARGET", None)
|
||||
adapter = select_adapter()
|
||||
self.assertIsInstance(adapter, SimulatorBrowserRpaAdapter)
|
||||
|
||||
def test_unit_returns_mock(self) -> None:
|
||||
with patch.dict(os.environ, {"OPENCLAW_TEST_TARGET": "unit"}):
|
||||
self.assertIsInstance(select_adapter(), MockBatchAdapter)
|
||||
|
||||
def test_mock_returns_mock(self) -> None:
|
||||
with patch.dict(os.environ, {"OPENCLAW_TEST_TARGET": "mock"}):
|
||||
self.assertIsInstance(select_adapter(), MockBatchAdapter)
|
||||
|
||||
def test_simulator_rpa_returns_simulator(self) -> None:
|
||||
with patch.dict(os.environ, {"OPENCLAW_TEST_TARGET": "simulator_rpa"}):
|
||||
self.assertIsInstance(select_adapter(), SimulatorBrowserRpaAdapter)
|
||||
|
||||
def test_real_rpa_falls_back_to_mock(self) -> None:
|
||||
with patch.dict(os.environ, {"OPENCLAW_TEST_TARGET": "real_rpa"}):
|
||||
self.assertIsInstance(select_adapter(), MockBatchAdapter)
|
||||
|
||||
def test_unknown_target_falls_back_simulator_rpa(self) -> None:
|
||||
with patch.dict(os.environ, {"OPENCLAW_TEST_TARGET": "simulator_api"}):
|
||||
self.assertIsInstance(select_adapter(), SimulatorBrowserRpaAdapter)
|
||||
|
||||
|
||||
class TestMockAdapter(unittest.TestCase):
|
||||
def test_mock_success(self) -> None:
|
||||
items = [
|
||||
BatchItem(row_index=1, name="Alice", account="1001", amount=10.5),
|
||||
BatchItem(row_index=2, name="Bob", account="1002", amount=20.0),
|
||||
]
|
||||
result = MockBatchAdapter().submit_batch("acct-demo-001", items)
|
||||
self.assertTrue(result.ok)
|
||||
self.assertEqual(result.submitted_count, 2)
|
||||
self.assertEqual(result.submitted_amount, 30.5)
|
||||
self.assertTrue((result.batch_id or "").startswith("MOCK-"))
|
||||
|
||||
def test_mock_empty_items(self) -> None:
|
||||
result = MockBatchAdapter().submit_batch("acct-demo-001", [])
|
||||
self.assertFalse(result.ok)
|
||||
self.assertEqual(result.error_msg, "items 为空")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,76 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""SimulatorBrowserRpaAdapter 单元测试(不启动真实浏览器)。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from service.adapter import BatchItem, SimulatorBrowserRpaAdapter
|
||||
from service.browser_session import CHROME_LAUNCH_ARGS
|
||||
|
||||
|
||||
class TestSimulatorRpaUnit(unittest.TestCase):
|
||||
def test_set_credentials_and_empty_profile_dir(self) -> None:
|
||||
adapter = SimulatorBrowserRpaAdapter()
|
||||
adapter.set_credentials("demo001", "pwd", "pin", " ")
|
||||
self.assertEqual(adapter._login_id, "demo001")
|
||||
self.assertIsNone(adapter._profile_dir)
|
||||
|
||||
def test_submit_batch_empty_items(self) -> None:
|
||||
adapter = SimulatorBrowserRpaAdapter()
|
||||
result = adapter.submit_batch("acct-demo-001", [])
|
||||
self.assertFalse(result.ok)
|
||||
self.assertIn("items", result.error_msg or "")
|
||||
|
||||
@patch("service.adapter.simulator_rpa.find_chrome_executable", return_value=None)
|
||||
def test_no_chrome_returns_error_without_launch(self, _chrome: MagicMock) -> None:
|
||||
adapter = SimulatorBrowserRpaAdapter()
|
||||
items = [BatchItem(row_index=1, name="A", account="1", amount=1.0)]
|
||||
result = adapter.submit_batch("acct-demo-001", items)
|
||||
self.assertFalse(result.ok)
|
||||
msg = result.error_msg or ""
|
||||
self.assertTrue("Chrome" in msg or "Edge" in msg or "浏览器" in msg)
|
||||
|
||||
def test_chrome_launch_args_must_not_contain_url(self) -> None:
|
||||
for arg in CHROME_LAUNCH_ARGS:
|
||||
lowered = arg.lower()
|
||||
self.assertFalse(lowered.startswith("http://"))
|
||||
self.assertFalse(lowered.startswith("https://"))
|
||||
self.assertFalse(lowered.startswith("file://"))
|
||||
|
||||
@patch("service.adapter.simulator_rpa.browser_session")
|
||||
@patch("service.adapter.simulator_rpa.find_chrome_executable", return_value="C:/Chrome/chrome.exe")
|
||||
def test_start_url_opened_via_goto_not_launch_args(
|
||||
self,
|
||||
_chrome: MagicMock,
|
||||
mock_session: MagicMock,
|
||||
) -> None:
|
||||
from service.adapter.simulator_rpa import RpaError
|
||||
|
||||
adapter = SimulatorBrowserRpaAdapter(base_url="file:///demo/demo_app.html")
|
||||
items = [BatchItem(row_index=1, name="A", account="1", amount=1.0)]
|
||||
|
||||
page = MagicMock()
|
||||
mock_cm = MagicMock()
|
||||
mock_cm.__enter__.return_value = page
|
||||
mock_cm.__exit__.return_value = False
|
||||
mock_session.return_value = mock_cm
|
||||
|
||||
with patch.object(adapter, "_login", side_effect=RpaError("login fail")):
|
||||
result = adapter.submit_batch("acct-demo-001", items)
|
||||
|
||||
self.assertFalse(result.ok)
|
||||
mock_session.assert_called_once()
|
||||
called_start_url = mock_session.call_args[0][0]
|
||||
self.assertEqual(called_start_url, "file:///demo/demo_app.html")
|
||||
for arg in CHROME_LAUNCH_ARGS:
|
||||
self.assertNotIn(arg, called_start_url)
|
||||
|
||||
def test_safe_screenshot_without_artifacts_dir(self) -> None:
|
||||
adapter = SimulatorBrowserRpaAdapter(artifacts_dir=None)
|
||||
page = MagicMock()
|
||||
self.assertIsNone(adapter._safe_screenshot(page, "tag"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
55
examples/simulator_browser_rpa/tests/test_stop_reason.py
Normal file
55
examples/simulator_browser_rpa/tests/test_stop_reason.py
Normal file
@@ -0,0 +1,55 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""RPA 失败结果与 artifacts 单元测试。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from service.adapter import BatchItem, SimulatorBrowserRpaAdapter
|
||||
from service.adapter.simulator_rpa import RpaError
|
||||
|
||||
|
||||
class TestBatchSubmitFailureResult(unittest.TestCase):
|
||||
@patch("service.adapter.simulator_rpa.find_chrome_executable", return_value="C:/Chrome/chrome.exe")
|
||||
@patch("service.adapter.simulator_rpa.browser_session")
|
||||
def test_rpa_error_converts_to_failed_result(self, mock_session: MagicMock, _chrome: MagicMock) -> None:
|
||||
page = MagicMock()
|
||||
mock_cm = MagicMock()
|
||||
mock_cm.__enter__.return_value = page
|
||||
mock_cm.__exit__.return_value = False
|
||||
mock_session.return_value = mock_cm
|
||||
|
||||
adapter = SimulatorBrowserRpaAdapter(artifacts_dir="/tmp/art")
|
||||
items = [BatchItem(row_index=1, name="A", account="1", amount=1.0)]
|
||||
|
||||
with patch.object(adapter, "_login", side_effect=RpaError("登录第一步失败:timeout", screenshot_path="/tmp/art/x.png")):
|
||||
result = adapter.submit_batch("acct-demo-001", items)
|
||||
|
||||
self.assertFalse(result.ok)
|
||||
self.assertIn("登录第一步失败", result.error_msg or "")
|
||||
self.assertEqual(result.artifacts.get("adapter"), "simulator_browser_rpa")
|
||||
self.assertEqual(result.artifacts.get("screenshot"), "/tmp/art/x.png")
|
||||
|
||||
@patch("service.adapter.simulator_rpa.find_chrome_executable", return_value="C:/Chrome/chrome.exe")
|
||||
@patch("service.adapter.simulator_rpa.browser_session")
|
||||
def test_unexpected_error_includes_adapter_name(self, mock_session: MagicMock, _chrome: MagicMock) -> None:
|
||||
page = MagicMock()
|
||||
mock_cm = MagicMock()
|
||||
mock_cm.__enter__.return_value = page
|
||||
mock_cm.__exit__.return_value = False
|
||||
mock_session.return_value = mock_cm
|
||||
|
||||
adapter = SimulatorBrowserRpaAdapter(artifacts_dir=None)
|
||||
items = [BatchItem(row_index=1, name="A", account="1", amount=1.0)]
|
||||
|
||||
with patch.object(adapter, "_login", side_effect=RuntimeError("boom")):
|
||||
result = adapter.submit_batch("acct-demo-001", items)
|
||||
|
||||
self.assertFalse(result.ok)
|
||||
self.assertIn("未预期异常", result.error_msg or "")
|
||||
self.assertEqual(result.artifacts.get("adapter"), "simulator_browser_rpa")
|
||||
self.assertNotIn("screenshot", result.artifacts)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user