Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

351
tools/test-feedback.py Normal file
View file

@ -0,0 +1,351 @@
#!/usr/bin/env python3
"""Regression checks for the September desktop feedback, in actual Gaia UI."""
import argparse
import json
import os
import subprocess
import time
from pathlib import Path
from marionette_client import Client
ROOT = Path(__file__).resolve().parents[1]
parser = argparse.ArgumentParser()
parser.add_argument("--restart-check", action="store_true")
args = parser.parse_args()
results = {}
with Client(2857) as client:
engine = client.execute("return Services.appinfo.platformVersion;")
assert (
client.execute("return PathUtils.profileDir;").split("/")[-1].startswith("host-")
), "Test profile required"
client.execute(
"try{ChromeUtils.registerWindowActor('VulpesProbe',{child:{esModuleURI:'resource://vulpes/tests/ProbeChild.sys.mjs'},allFrames:true,safeForUntrustedWebProcess:true});}catch(_){}"
)
def ev(app, code, path="/"):
prefix = "http://" + app + ".localhost:8765" + path
script = """const done=arguments[arguments.length-1];const w=Services.wm.getMostRecentWindow('vulpes:host');
if(!w?.document.getElementById('system')){done({missing:true});return;}
const bc=(function find(b){if(b.currentWindowGlobal?.documentURI.spec.startsWith(PREFIX))return b;for(const c of b.children){const f=find(c);if(f)return f;}return null;})(w.document.getElementById('system').browsingContext);
if(!bc){done({missing:true});return;}bc.currentWindowGlobal.getActor('VulpesProbe').sendQuery('Evaluate',CODE).then(result=>done({result}),error=>done({error:String(error)}));""".replace(
"PREFIX", json.dumps(prefix)
).replace(
"CODE", json.dumps(code)
)
r = client.call(
"WebDriver:ExecuteAsyncScript",
{
"script": script,
"args": [],
"newSandbox": True,
"sandbox": "system",
"scriptTimeout": 15000,
},
)["value"]
if "error" in r:
raise AssertionError(r)
return r.get("result")
def wait(app, code, path="/"):
for _ in range(70):
r = ev(app, code, path)
if r:
return r
time.sleep(0.15)
raise AssertionError("Timeout: " + app + " " + code)
def check(name, condition):
results[name] = condition
print(name + ": " + json.dumps(condition, ensure_ascii=False), flush=True)
assert condition is True, (name, condition)
def launch(app, entry=None):
ev(
"system",
'await VulpesCompat.call("apps.launch",'
+ json.dumps(
{"manifestURL": f"http://{app}.localhost:8765/manifest.webapp", "entryPoint": entry}
)
+ ");return true;",
)
wait(app, "return document.readyState==='complete';")
time.sleep(0.5)
def focus():
client.execute(
"const w=Services.wm.getMostRecentWindow('vulpes:host');w.focus();w.document.getElementById('system').focus();return true;"
)
subprocess.run(
["xdotool", "search", "--name", "^Vulpes", "windowraise", "windowfocus"],
check=True,
stdout=subprocess.DEVNULL,
)
def tap(app, selector, path="/"):
pos = ev(
app,
"const e="
+ selector
+ ';e.scrollIntoView({block:"nearest"});await new Promise(r=>setTimeout(r,150));const b=e.getBoundingClientRect();return {x:mozInnerScreenX+b.x+b.width/2,y:mozInnerScreenY+b.y+b.height/2};',
path,
)
focus()
subprocess.run(
["xdotool", "mousemove", str(round(pos["x"])), str(round(pos["y"])), "click", "1"],
check=True,
)
time.sleep(0.4)
def shot(name):
focus()
subprocess.run(
["import", "-window", "root", str(ROOT / "logs" / ("feedback-" + name + ".png"))],
check=True,
)
try:
wait(
"system",
"return document.body?.getAttribute('ready-state')==='fullyLoaded' && !!document.querySelector('#statusbar-time');",
)
ev("system", "ScreenManager.turnScreenOn(true);return true;")
time.sleep(.5)
ev("system", "lockScreen.unlock(true);return true;")
wait("system", "return !lockScreen.locked;")
time.sleep(.5)
if args.restart_check:
launch("communications", "contacts")
check(
"contacts-survive-restart",
ev(
"communications",
"const c=await navigator.mozContacts.find({filterBy:['name'],filterValue:'Vulpes Regression',filterOp:'equals'});return c.length===1 && c[0].tel[0].value==='0612345678' && Object.prototype.toString.call(c[0].updated)==='[object Date]' && Number.isFinite(c[0].updated.getTime());",
"/contacts/",
),
)
check(
"passcode-survives-restart",
ev(
"system",
"await LazyLoader.load('/shared/js/passcode_helper.js');return await PasscodeHelper.check('9876');",
),
)
raise SystemExit(0)
check(
"clock-at-right",
ev(
"system",
"const b=document.querySelector('#statusbar-time').getBoundingClientRect();return b.right>innerWidth-15 && b.left>innerWidth/2;",
),
)
check(
"status-tray-icons",
ev(
"system",
"UtilityTray.show(true);await new Promise(r=>setTimeout(r,300));return [...document.querySelectorAll('#quick-settings a')].filter(e=>e.getBoundingClientRect().width>0 && e.getBoundingClientRect().top>0).length>=4;",
),
)
ev("system", "UtilityTray.hide(true);return true;")
launch("communications", "dialer")
tap("communications", "document.querySelector('[data-value=\"1\"]')", "/dialer/")
tap("communications", "document.querySelector('[data-value=\"2\"]')", "/dialer/")
check(
"dialpad-physical-taps",
ev("communications", "return KeypadManager._phoneNumber.endsWith('12');", "/dialer/"),
)
tap("communications", "document.getElementById('option-recents')", "/dialer/")
check(
"dialer-recents-tab",
wait(
"communications",
"return document.body.innerText.includes('Journal d’appels');",
"/dialer/",
),
)
tap("communications", "document.getElementById('option-contacts')", "/dialer/")
check(
"dialer-contacts-tab",
wait(
"communications",
"return document.readyState==='complete' && document.body.innerText.includes('Contacts');",
"/contacts/",
),
)
shot("contacts")
check(
"contact-store-roundtrip",
ev(
"communications",
"""const old=await navigator.mozContacts.find({filterBy:['name'],filterValue:'Vulpes Regression',filterOp:'equals'});for(const c of old)await navigator.mozContacts.remove(c);const c=new mozContact({name:['Vulpes Regression'],givenName:['Vulpes'],familyName:['Regression'],tel:[{value:'0612345678',type:['mobile']}]});await navigator.mozContacts.save(c);window.__testContactId=c.id;const list=await navigator.mozContacts.find({filterBy:['id'],filterValue:c.id,filterOp:'equals'});return list.length===1 && list[0].name[0]==='Vulpes Regression';""",
"/contacts/",
),
)
check(
"contact-list-updates",
wait(
"communications",
"return document.body.innerText.includes('Regression');",
"/contacts/",
),
)
check(
"contacts-permission-isolation",
ev(
"homescreen",
"const r=await navigator.vulpesRequest({version:1,id:500,method:'contacts.request',params:{operation:'all'}});return r.error?.code==='PERMISSION_DENIED';",
),
)
launch("sms")
check(
"sms-empty-view",
wait("sms", "return document.body.innerText.includes('Aucun message');"),
)
shot("sms")
tap("sms", "document.querySelector('#threads-composer-link')")
check(
"sms-composer-ready",
wait(
"sms",
"return location.hash==='#/composer' && !!document.querySelector('#messages-input');",
),
)
tap("sms", "document.querySelector('#messages-input')")
subprocess.run(["xdotool", "type", "--clearmodifiers", "Brouillon Vulpes"], check=True)
check(
"sms-composer-typing",
wait(
"sms",
"return document.querySelector('#messages-input').textContent.includes('Brouillon Vulpes');",
),
)
check(
"sms-no-fake-send",
ev(
"sms",
"try{await navigator.mozMobileMessage.send('0612345678','test');return false;}catch(e){return e.name==='RadioDisabledError';}",
),
)
check(
"sms-segment-count",
ev(
"sms",
"const s=await navigator.mozMobileMessage.getSegmentInfoForText('a'.repeat(161));return s.segments===2 && s.charsPerSegment===153;",
),
)
launch("settings")
ev(
"settings",
"await new Promise(r=>require(['modules/settings_service'],s=>{s.navigate('root');r();}));return true;",
)
wait("settings", "return !!document.querySelector('#root.current');")
time.sleep(0.5)
tap("settings", "document.querySelector('a[href=\"#about\"]')")
wait("settings", "return !!document.querySelector('#about.current');")
check(
"back-arrow-visible",
ev(
"settings",
"const h=document.querySelector('#about gaia-header'),b=h.shadowRoot.querySelector('button');return getComputedStyle(h).display==='block' && getComputedStyle(b,'::before').content==='\"left\"' && b.getBoundingClientRect().width===50;",
),
)
tap("settings", "document.querySelector('a[href=\"#about-moreInfo\"]')")
check(
"actual-versions-in-panel",
wait(
"settings",
"return document.querySelector('#about-moreInfo.current') && document.body.innerText.includes("
+ json.dumps(engine)
+ ") && document.body.innerText.includes('2.7');",
),
)
shot("versions")
check(
"passcode-byte-roundtrip",
ev(
"system",
"await LazyLoader.load('/shared/js/passcode_helper.js');await PasscodeHelper.set('9876');const yes=await PasscodeHelper.check('9876'),no=await PasscodeHelper.check('1111');return yes && !no;",
),
)
check(
"iac-forged-route-denied",
ev(
"homescreen",
"try{await VulpesCompat.call('iac.connect',{keyword:'search'});return false;}catch(e){return e.name==='CONNECTION_DENIED';}",
),
)
launch("search")
check(
"firefox-newtab-ready",
wait(
"search",
"return document.readyState==='complete' && !!document.querySelector('#top-sites');",
"/newtab.html",
),
)
tap("system", "document.querySelector('.appWindow.active .urlbar-hit-area')")
wait("system", "return document.querySelector('#rocketbar').classList.contains('active');")
tap("system", "document.querySelector('#rocketbar-input')")
subprocess.run(
[
"xdotool",
"key",
"ctrl+a",
"type",
"--clearmodifiers",
"--delay",
"40",
"https://example.com",
],
check=True,
)
check(
"rocketbar-physical-typing",
ev(
"system",
"return document.querySelector('#rocketbar-input').value==='https://example.com';",
),
)
subprocess.run(["xdotool", "key", "Return"], check=True)
check(
"rocketbar-submit-creates-browser",
wait(
"system",
"return [...document.querySelectorAll('iframe')].some(f=>f.src==='https://example.com/');",
),
)
time.sleep(1)
shot("browser")
client.execute(
"Services.wm.getMostRecentWindow('vulpes:host').document.getElementById('home').click();return true;"
)
time.sleep(0.7)
check(
"browser-return-home",
client.execute(
"return [...Services.wm.getMostRecentWindow('vulpes:host').document.querySelectorAll('browser')].filter(b=>b.id!=='system').every(b=>b.style.visibility==='hidden');"
),
)
check(
"apps-fill-height",
ev(
"system",
"return !document.querySelector('#screen').classList.contains('software-button-enabled');",
),
)
print(
"APP DIAGNOSTICS",
json.dumps(
{
a: ev(a, "return window.__vulpesDiagnostics;")
for a in ["system", "communications", "sms", "settings", "search"]
},
ensure_ascii=False,
),
flush=True,
)
finally:
(
ROOT
/ "logs"
/ ("feedback-restart-results.json" if args.restart_check else "feedback-results.json")
).write_text(json.dumps(results, indent=2) + "\n")