VulpesOS/tools/test-keypad-workflows.py
2026-10-22 20:42:26 +02:00

74 lines
5.2 KiB
Python

#!/usr/bin/env python3
"""Keypad input in real Gaia forms, isolated profile; no calls or SMS sent."""
import importlib.util,json,os,subprocess,time
from pathlib import Path
from marionette_client import Client
spec=importlib.util.spec_from_file_location('probe',Path(__file__).with_name('probe-desktop.py'))
probe=importlib.util.module_from_spec(spec);spec.loader.exec_module(probe)
state=json.loads(Path('tundra/out/beatles/gecko-156.0.1/pc-test-pids.json').read_text())
env={**os.environ,'DISPLAY':':'+state['display']}
checks=[]
with Client(2857) as c:
assert Path(c.execute('return PathUtils.profileDir;')).name=='pc-keypad-profile'
c.execute("try{ChromeUtils.registerWindowActor('VulpesProbe',{child:{esModuleURI:'resource://vulpes/tests/ProbeChild.sys.mjs'},allFrames:true,safeForUntrustedWebProcess:true});}catch(_){}")
def ev(app,code):
host,path=('communications','/contacts/') if app=='contacts' else (app,'/')
r=probe.evaluate(c,host,code,path);assert 'error' not in r,r
return r.get('value')
def wait(app,code):
for _ in range(150):
r=ev(app,code)
if r:return r
time.sleep(.1)
raise AssertionError((app,code))
def check(name,value):
assert value,name
checks.append(name);print(name+': OK',flush=True)
def key(*keys):
subprocess.run(['xdotool','search','--name','Vulpes — port','windowraise','windowfocus','key','--clearmodifiers','--delay','130',*keys],env=env,check=True,stdout=subprocess.DEVNULL)
time.sleep(.15)
def focus(app,selector):
wait(app,'return !!document.querySelector('+json.dumps(selector)+');')
ev(app,'const e=document.querySelector('+json.dumps(selector)+');e.setAttribute("tabindex","0");e.scrollIntoView({block:"nearest"});e.focus();return true;')
def launch(app,entry=None):
params={'manifestURL':'http://'+app+'.localhost:8765/manifest.webapp'}
if entry:params['entryPoint']=entry
ev('system','await VulpesCompat.call("apps.launch",'+json.dumps(params)+');return true;')
wait('system','return typeof ScreenManager!=="undefined" && !!window.Service;')
ev('system','ScreenManager.turnScreenOn(true);if(Service.query("locked"))await Service.request("unlock",{forcibly:true});return true;')
launch('communications','contacts')
wait('contacts','return document.readyState==="complete" && !!document.getElementById("add-contact-button");')
focus('contacts','#add-contact-button');key('Return')
wait('contacts','return document.getElementById("givenName")?.getBoundingClientRect().height>0;')
focus('contacts','#givenName');key('2','2','6','6','6','2','2')
check('contact-name-multitap',wait('contacts','return document.getElementById("givenName").value==="bob";'))
key('BackSpace');check('delete-letter',wait('contacts','return document.getElementById("givenName").value==="bo";'))
key('numbersign','2');check('uppercase-mode',wait('contacts','return document.getElementById("givenName").value==="boA";'))
key('numbersign','2');check('number-mode',wait('contacts','return document.getElementById("givenName").value==="boA2";'))
key('numbersign')
focus('contacts','#number_0');key('5','5','5','0','1','0','0')
check('telephone-digits-remain-numeric',wait('contacts','return document.getElementById("number_0").value==="5550100";'))
check('physical-keypad-does-not-open-software-keyboard',ev('system','return !document.querySelector(".inputWindow.active");'))
# Leave the unsaved form; no personal data is written.
key('Escape')
launch('sms');wait('sms','return !!document.getElementById("threads-composer-link");')
focus('sms','#threads-composer-link');key('Return')
wait('sms','return typeof ConversationView!=="undefined" && Navigation.isCurrentPanel("composer") && document.activeElement.classList.contains("recipient");')
# Wait for the original Gaia panel transition, including its final focus.
ev('sms','await App.whenReady();await new Promise(r=>setTimeout(r,400));return true;')
focus('sms','#messages-recipients-list [contenteditable]');key('5','5','5','0','1','0','0','Return')
check('sms-recipient-accepted',wait('sms','return ConversationView.recipients.numbers.includes("5550100");'))
focus('sms','#messages-input');key('2','2','6','6','6','2','2')
check('sms-body-multitap',wait('sms','return document.getElementById("messages-input").textContent.trim()==="bob";'))
# Clear the unsent text through the same editor.
key('BackSpace','BackSpace','BackSpace')
ev('sms','ConversationView.cleanFields();return true;')
launch('settings');wait('settings','return typeof require==="function";')
ev('settings','const s=await new Promise(r=>require(["modules/settings_service"],r));await s.navigate("display");return true;')
focus('settings','#vulpes-layout');key('Return')
check('select-opens-gaia-dialog',wait('settings','return !!document.getElementById("vulpes-value-selector");'))
key('Down');check('selection-stays-in-modal',wait('settings','return !!document.activeElement.closest("#vulpes-value-selector");'))
key('Escape');check('back-cancels-selector',wait('settings','return !document.getElementById("vulpes-value-selector");'))
key('Escape');check('back-leaves-display-panel',wait('settings','return location.hash!=="#display";'))
Path('logs/keypad-workflows.json').write_text(json.dumps({'checks':checks,'input':'Native XTest on desktop, shared Gaia at 240x320'},indent=2))
print(json.dumps(checks,indent=2))