116 lines
4.4 KiB
Python
116 lines
4.4 KiB
Python
"""Opt-in cache regression on a selected Android device with Chrome and the helper.
|
|
|
|
Set ARTEMIS_TEST_DEVICE_SERIAL and run this file with ``pytest -m android``.
|
|
Opens a disposable local page via ADB reverse; no model call or system-setting edit.
|
|
"""
|
|
|
|
import base64
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
from io import BytesIO
|
|
import os
|
|
import subprocess
|
|
import threading
|
|
import time
|
|
from uuid import uuid4
|
|
|
|
from PIL import Image
|
|
import pytest
|
|
|
|
from artemis.clients.accessibility_client import AccessibilityClient
|
|
from artemis.runtime.adb_endpoint import adb_command
|
|
from artemis.runtime.device_lock import DeviceExecutionLock
|
|
|
|
pytestmark = [pytest.mark.android, pytest.mark.integration]
|
|
|
|
|
|
def test_helper_refreshes_dynamic_web_text_and_pixels(tmp_path):
|
|
serial = os.getenv("ARTEMIS_TEST_DEVICE_SERIAL")
|
|
if not serial:
|
|
pytest.skip("Select ARTEMIS_TEST_DEVICE_SERIAL explicitly")
|
|
marker = "updated-" + uuid4().hex[:8]
|
|
state = {"value": "baseline"}
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
def log_message(self, *_args):
|
|
pass
|
|
|
|
def do_GET(self):
|
|
if self.path == "/state":
|
|
body = state["value"].encode()
|
|
else:
|
|
body = b"""<!doctype html><meta name="viewport" content="width=device-width">
|
|
<title>ARTEMIS dynamic observation fixture</title>
|
|
<body style="background:#cc2222;font:24px sans-serif"><p id="result">loading</p>
|
|
<script>setInterval(async()=>{const v=await(await fetch('/state')).text();
|
|
document.getElementById('result').textContent=v;
|
|
document.body.style.background=v==='baseline'?'#cc2222':'#22cc22';},500)</script>"""
|
|
self.send_response(200)
|
|
self.send_header("Content-Type", "text/html; charset=utf-8")
|
|
self.send_header("Cache-Control", "no-store")
|
|
self.end_headers()
|
|
self.wfile.write(body)
|
|
|
|
def adb(*arguments, check=True):
|
|
return subprocess.run(
|
|
adb_command(["-s", serial, *arguments]),
|
|
capture_output=True,
|
|
check=check,
|
|
timeout=15,
|
|
)
|
|
|
|
lease = DeviceExecutionLock(serial, description="Dynamic helper observation regression")
|
|
lease.acquire(blocking=False)
|
|
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
|
|
worker = threading.Thread(target=server.serve_forever, daemon=True)
|
|
worker.start()
|
|
client = AccessibilityClient(serial, provision_on_connect=False)
|
|
device_port = None
|
|
try:
|
|
client.connect()
|
|
# Let ADB allocate an unused reverse port; never replace a user's mapping.
|
|
device_port = adb("reverse", "tcp:0", f"tcp:{server.server_port}").stdout.decode().strip()
|
|
assert device_port.isdigit(), "ADB did not allocate a reverse port"
|
|
adb(
|
|
"shell",
|
|
"am",
|
|
"start",
|
|
"-a",
|
|
"android.intent.action.VIEW",
|
|
"-d",
|
|
f"http://127.0.0.1:{device_port}/",
|
|
"-p",
|
|
"com.android.chrome",
|
|
)
|
|
|
|
def await_observation(expected, green):
|
|
deadline = time.monotonic() + 15
|
|
observed_text = []
|
|
while time.monotonic() < deadline:
|
|
observation = client.get_screen_data()
|
|
observed_text = [element.get("text") for element in observation.elements]
|
|
raw = base64.b64decode(observation.base64)
|
|
with Image.open(BytesIO(raw)) as picture:
|
|
pixels = list(picture.convert("RGB").resize((32, 32)).getdata())
|
|
count = sum(
|
|
g > 150 and r < 100 if green else r > 150 and g < 100 for r, g, _ in pixels
|
|
)
|
|
if expected in observed_text and count > len(pixels) // 4:
|
|
(tmp_path / f"{expected}.jpg").write_bytes(raw)
|
|
return
|
|
time.sleep(0.5)
|
|
pytest.fail(f"Pixels and UI hierarchy did not converge to {expected}: {observed_text}")
|
|
|
|
await_observation("baseline", False)
|
|
state["value"] = marker
|
|
await_observation(marker, True)
|
|
state["value"] = "baseline"
|
|
await_observation("baseline", False)
|
|
finally:
|
|
client.disconnect()
|
|
if device_port and device_port.isdigit():
|
|
adb("reverse", "--remove", f"tcp:{device_port}", check=False)
|
|
server.shutdown()
|
|
server.server_close()
|
|
worker.join(timeout=5)
|
|
lease.release()
|