feat: release 2.2.0 with PC-only tests and execution reliability fixes
Unify task scheduling and queue state, fix Android input and observation recovery, preserve screenshot paths, and match final review criteria accurately. Add collapsible copyable results and publish the complete 2.1.0 upgrade notes. Validation: 3189 backend tests, 326 frontend tests, 24 client tests, production build and quality gates.
This commit is contained in:
1 parent
fac4ccb760
commit
be6e8c2abd
79 files changed
+2263
-277
No files matched your search
@@ -0,0 +1,99 @@
|
||||
"""Whole-field clearing must not depend on text length or keyboard shortcuts."""
|
||||
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from artemis.controllers.unified_controller import UnifiedMobileController
|
||||
from artemis.drivers.android.adb_driver import AndroidAdbDriver
|
||||
from artemis.drivers.android.input_ime import AndroidInputIME
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"value",
|
||||
["ARTEMIS_PRO_R3_20261026", "长文本🙂\n" * 80, ""],
|
||||
ids=["23-chars", "long-unicode-multiline", "empty"],
|
||||
)
|
||||
async def test_full_clear_replaces_the_whole_field(value):
|
||||
field = {"text": value}
|
||||
device, ui = Mock(), Mock()
|
||||
|
||||
def shell(command, **kwargs):
|
||||
# A field that ignores Ctrl+A: the previous implementation leaves AR.
|
||||
if "67" in command:
|
||||
field["text"] = field["text"][: -command.count("67")]
|
||||
return ""
|
||||
|
||||
def clear():
|
||||
field["text"] = ""
|
||||
return True
|
||||
|
||||
device.shell.side_effect = shell
|
||||
ui.clear_text.side_effect = clear
|
||||
driver = AndroidAdbDriver("test-device", Mock(), ui_adb_client=ui)
|
||||
driver._device = device
|
||||
controller = object.__new__(UnifiedMobileController)
|
||||
controller._driver = driver
|
||||
assert await controller.erase_text()
|
||||
assert field["text"] == ""
|
||||
ui.clear_text.assert_called_once_with()
|
||||
device.shell.assert_not_called()
|
||||
ui.set_clipboard.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("implementation", ["driver", "ime"])
|
||||
@pytest.mark.parametrize("failure", [False, RuntimeError("write reply lost")])
|
||||
async def test_failed_clear_never_types_or_retries(implementation, failure):
|
||||
device, ui = Mock(), Mock()
|
||||
if isinstance(failure, Exception):
|
||||
ui.clear_text.side_effect = failure
|
||||
else:
|
||||
ui.clear_text.return_value = failure
|
||||
if implementation == "driver":
|
||||
driver = AndroidAdbDriver("test-device", Mock(), ui_adb_client=ui)
|
||||
driver._device = device
|
||||
result = await driver.input_text("replacement", clear_existing=True)
|
||||
else:
|
||||
result = await AndroidInputIME(device, ui).type_text("replacement", clear_existing=True)
|
||||
assert result is False
|
||||
ui.clear_text.assert_called_once_with()
|
||||
device.shell.assert_not_called()
|
||||
ui.set_clipboard.assert_not_called()
|
||||
ui.send_text.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_missing_clear_backend_is_not_reported_as_success():
|
||||
driver = AndroidAdbDriver("test-device", Mock())
|
||||
driver._device = Mock()
|
||||
assert not await driver.input_text("replacement", clear_existing=True)
|
||||
driver._device.shell.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("implementation", ["driver", "ime"])
|
||||
async def test_empty_append_does_not_paste_or_send_keys(implementation):
|
||||
device, ui = Mock(), Mock()
|
||||
if implementation == "driver":
|
||||
driver = AndroidAdbDriver("test-device", Mock(), ui_adb_client=ui)
|
||||
driver._device = device
|
||||
result = await driver.input_text("", clear_existing=False)
|
||||
else:
|
||||
result = await AndroidInputIME(device, ui).type_text("", clear_existing=False)
|
||||
assert result is True
|
||||
device.shell.assert_not_called()
|
||||
ui.clear_text.assert_not_called()
|
||||
ui.set_clipboard.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_partial_delete_stops_at_first_failed_key():
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
controller = object.__new__(UnifiedMobileController)
|
||||
controller._driver = Mock()
|
||||
controller._driver.press_key = AsyncMock(return_value=False)
|
||||
assert not await controller.erase_text(nb_chars=3)
|
||||
controller._driver.press_key.assert_awaited_once_with("delete")
|
||||
Reference in new issue
Block a user