"""Verify ui.py against both native Glyphix emulator profiles (not a physical S2). Build deviceprobe QuickApps first. Supply a compatible vendor global/launcher image using --seed (or initialize the probe image). Never attaches to an existing Inspector or terminates another user's emulator. """ import argparse import hashlib import json from pathlib import Path import shutil import socket import subprocess import sys import time PROJECT = Path(__file__).resolve().parents[1] ROOT = PROJECT.parent sys.path.insert(0, str(PROJECT)) from ui import Inspector def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--output', required=True, type=Path) parser.add_argument('--seed', type=Path, default=ROOT / 'com.sydxky.rtos.deviceprobe/.glyphix-work/image/generic-watch-466x466', help='Initialized vendor image containing global/, global.pkg and apps/emulator.launcher.pkg') args = parser.parse_args() output = args.output.resolve() output.mkdir(parents=True, exist_ok=True) results = [] for shape, device in [('round', '466x466'), ('rect', '390x450')]: with socket.socket() as sock: if sock.connect_ex(('127.0.0.1', 14201)) == 0: raise RuntimeError('Inspector port in use; will not attach to another runtime') folder = output / shape image = folder / 'image' image.mkdir(parents=True, exist_ok=True) seed = args.seed.resolve() shutil.copytree(seed / 'global', image / 'global', dirs_exist_ok=True) shutil.copy2(seed / 'global.pkg', image / 'global.pkg') (image / 'apps').mkdir(exist_ok=True) shutil.copy2(seed / 'apps/emulator.launcher.pkg', image / 'apps/emulator.launcher.pkg') name = 'com.sydxky.rtos.deviceprobe' package = ROOT / name / '.glyphix-work/dist' / ('generic-watch-' + device + '-quickapp') / name / 'bundle.pkg' shutil.copy2(package, image / 'apps' / (name + '.pkg')) (image / 'pkgs.db').write_text(json.dumps({'apps': {p: {'uri': 'pkg://' + p} for p in (name, 'emulator.launcher')}, 'dials': {}}), encoding='utf-8') command = [str(ROOT / 'temp/glyphix-v0.7.2-windows-x64/bin/glyphix-emu.exe'), '--config', 'pkgs.db', '--target', name, '--inspector'] command += ['--width', '624', '--height', '624', '--dpi', '326', '--font-size', '32', '--round', '--surface', 'surface/surface-466x466.json'] if shape == 'round' else ['--width', '390', '--height', '450', '--dpi', '321', '--font-size', '29'] command += ['--memory-profile', '8192', '--font-map', 'font-faces', '--font-family', 'sans-serif'] client = Inspector('http://127.0.0.1:14201') item = {'profile': shape, 'evidence_scope': 'native_emulator_not_S2', 'package_sha256': hashlib.sha256(package.read_bytes()).hexdigest(), 'passed': False} results.append(item) with open(folder / 'stdout.log', 'w', encoding='utf-8') as out, open(folder / 'stderr.log', 'w', encoding='utf-8') as err: proc = subprocess.Popen(command, cwd=image, stdin=subprocess.PIPE, stdout=out, stderr=err, creationflags=subprocess.CREATE_NO_WINDOW) try: for _ in range(70): if proc.poll() is not None: raise RuntimeError('Emulator exited before Inspector startup') try: client.request('/logs') break except OSError: time.sleep(.3) else: raise TimeoutError('Inspector did not start') time.sleep(1) item['before'] = client.screenshot(folder / 'before.png') item['initial_visible'] = client.exists(('text', '点击次数:0')) client.click(('text', '点击验证')) item['after_visible'] = client.exists(('text', '点击次数:1')) item['after'] = client.screenshot(folder / 'after.png') plan = json.loads((PROJECT / 'examples/probe-inspector.json').read_text(encoding='utf-8')) plan['steps'][0]['value'] = '点击次数:1' plan['steps'][2]['value'] = '点击次数:2' planfile = folder / 'plan.json' planfile.write_text(json.dumps(plan, ensure_ascii=False), encoding='utf-8') run = subprocess.run([sys.executable, str(PROJECT / 'ui.py'), '--target', 'inspector', '--inspector', client.base, 'run', str(planfile), '--output', str(folder / 'plan-results')], capture_output=True, timeout=90) item['plan_exit'] = run.returncode item['passed'] = item['initial_visible'] and item['after_visible'] and run.returncode == 0 if run.returncode: raise RuntimeError(run.stderr.decode('utf-8', 'replace') or 'UI plan failed') except Exception as error: item['error'] = type(error).__name__ raise finally: if proc.poll() is None: proc.terminate() proc.wait(timeout=10) proc.stdin.close() (output / 'results.json').write_text(json.dumps(results, ensure_ascii=False, indent=2), encoding='utf-8') print(json.dumps(item, ensure_ascii=True), flush=True) time.sleep(2) return 0 if all(item['passed'] for item in results) else 1 if __name__ == '__main__': sys.exit(main())