Files
agents/tests/venue/design_system.py
T
2026-10-14 23:39:28 +07:00

108 lines
7.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Run with Vite on port 5198 and Firefox installed; no API required."""
import json, pathlib, socket, subprocess, tempfile, time
profile=pathlib.Path(tempfile.mkdtemp(prefix='home-dossier-'))
with socket.socket() as s:
s.bind(('127.0.0.1',0));port=s.getsockname()[1]
(profile/'user.js').write_text(f'user_pref("marionette.port", {port});\nuser_pref("browser.shell.checkDefaultBrowser", false);')
log=(profile/'firefox.log').open('w')
firefox=subprocess.Popen(['firefox','--headless','--no-remote','--profile',str(profile),'--marionette','--remote-allow-system-access','about:blank'],stdout=log,stderr=log)
sock=None
try:
for _ in range(100):
try: sock=socket.create_connection(('127.0.0.1',port),timeout=1);break
except OSError: time.sleep(.1)
assert sock is not None, 'Firefox did not start'
sock.settimeout(20)
def read():
size=b''
while not size.endswith(b':'): size+=sock.recv(1)
data=b'';length=int(size[:-1])
while len(data)<length: data+=sock.recv(length-len(data))
return json.loads(data)
read();counter=0
def command(name,args=None):
global counter
counter+=1;payload=json.dumps([0,counter,name,args or {}]).encode()
sock.sendall(str(len(payload)).encode()+b':'+payload)
result=read();assert not result[2], result[2];return result[3]
def js(script):
result=command('WebDriver:ExecuteScript',{'script':script,'args':[],'newSandbox':False,'sandbox':None})
return result.get('value') if isinstance(result,dict) and 'value' in result else result
def wait(script):
for _ in range(100):
if js('return '+script):return
time.sleep(.1)
raise AssertionError(script)
command('WebDriver:NewSession',{'capabilities':{'alwaysMatch':{}}})
command('Marionette:SetContext', {'value':'chrome'})
js('document.documentElement.style.minWidth="0";document.documentElement.setAttribute("minwidth","0");return true')
command('Marionette:SetContext', {'value':'content'})
for width in [375,768,1280]:
command('WebDriver:SetWindowRect',{'width':width,'height':900})
command('WebDriver:Navigate',{'url':'http://127.0.0.1:5198/design-system'})
wait('!!document.querySelector(".site-preview")')
assert js('return document.querySelectorAll("[role=tab]").length')==3
assert js('return document.documentElement.scrollWidth <= innerWidth')
# Third tab, circular arrows and Home/End use real keyboard actions.
def key(value):
command('WebDriver:PerformActions', {'actions':[{'type':'key','id':'keyboard','actions':[{'type':'keyDown','value':value},{'type':'keyUp','value':value}]}]})
js('document.querySelector("#tab-site").focus();return true')
key('\ue010')
wait('!!document.querySelector(".catalog")')
assert js('return document.activeElement.id')=='tab-components'
assert js('return document.documentElement.scrollWidth <= innerWidth && document.querySelector(".design-scroll").scrollWidth <= innerWidth'), 'Catalog overflow'
assert js('const entries=[...document.querySelectorAll(".catalog-entry")]; return entries.length===20 && entries.every(e=>e.children[0].tagName==="H2" && e.children[1].matches(".component-sample, .n-config-provider") && e.children[3].matches("pre") && e.children[3].querySelector("code"))'), 'Each component needs a heading, sample and usage code'
assert js('return [...document.querySelectorAll("[id]")].map(e=>e.id).length===new Set([...document.querySelectorAll("[id]")].map(e=>e.id)).size'), 'Duplicate DOM IDs'
assert js('return document.querySelectorAll(".catalog .status-stamp").length===2'), 'Optional folder status'
assert js('return !document.querySelectorAll(".catalog .folder-action")[0].disabled && document.querySelectorAll(".catalog .folder-action")[1].disabled'), 'Status controls availability'
ids=js('return [...document.querySelectorAll(".catalog .clue-toggle")].map(e=>e.getAttribute("aria-controls"))')
assert len(set(ids))==2
js('document.querySelectorAll(".catalog .clue-toggle")[0].click();return true')
wait('document.querySelectorAll(".catalog .clue-toggle")[0].getAttribute("aria-expanded")==="true"')
assert js('return document.querySelectorAll(".catalog .clue-toggle")[1].getAttribute("aria-expanded")')=='false'
js('document.querySelectorAll(".catalog .clue-toggle")[1].click();return true')
wait('document.querySelectorAll(".catalog .clue-toggle")[1].getAttribute("aria-expanded")==="true"')
js('document.querySelectorAll(".catalog .clue-toggle")[0].click();return true')
wait('document.querySelectorAll(".catalog .clue-toggle")[0].getAttribute("aria-expanded")==="false"')
assert js('return document.querySelectorAll(".catalog .clue-toggle")[1].getAttribute("aria-expanded")')=='true'
assert js('return document.querySelector(".catalog .case-board").textContent.includes("Найден у двери")')
assert js('return document.querySelector(".catalog [data-component=ObjectCover] .object-cover").textContent.includes("Чердак")')
js('document.querySelector(".catalog form").dispatchEvent(new Event("submit",{bubbles:true,cancelable:true}));return true')
wait('document.querySelector(".catalog").textContent.includes("Укажите название команды")')
js('const e=document.querySelector("#catalog-team");e.value="Сыщики";e.dispatchEvent(new Event("input",{bubbles:true}));return true')
js('document.querySelector(".catalog form").dispatchEvent(new Event("submit",{bubbles:true,cancelable:true}));return true')
wait('document.querySelector(".catalog").textContent.includes("Сыщики» готова")')
js('document.querySelector(".catalog .folder-action").focus();document.querySelector(".catalog .folder-action").click();return true')
wait('!!document.querySelector("[role=dialog]")')
assert js('return document.querySelector("[role=dialog]").getAttribute("aria-label")')=='Повторное дело'
key('\ue00c')
wait('!document.querySelector("[role=dialog]")')
wait('document.activeElement===document.querySelector(".catalog .folder-action")')
assert js('return document.querySelector(".catalog .buttons button[disabled]")!==null && document.querySelector(".catalog .buttons .n-button--loading")!==null')
assert js('return getComputedStyle(document.querySelector(".catalog .buttons button")).backgroundColor')=='rgb(255, 212, 59)'
js('const e=document.querySelector(".game-input-run");e.value="Сад";e.dispatchEvent(new Event("input",{bubbles:true}));return true')
wait('!document.querySelector(".game-button-run").disabled')
js('document.querySelector(".game-button-run").click();return true')
wait('!!document.querySelector(".action-result")')
assert js('return getComputedStyle(document.querySelector(".action-result")).getPropertyValue("--n-icon-color").trim()')!='#ffd43b'
js('document.querySelector("#tab-components").focus();return true')
key('\ue014'); wait('!!document.querySelector(".site-preview")')
key('\ue012'); wait('!!document.querySelector(".catalog")')
key('\ue011'); wait('!!document.querySelector(".site-preview")')
key('\ue014'); wait('!!document.querySelector(".game-preview")')
key('\ue014'); wait('!!document.querySelector(".catalog")')
key('\ue011'); wait('!!document.querySelector(".site-preview")')
print(f'PASS {width}px: three tabs, quotes, IDs, data, states, form, open, modal, themes', flush=True)
command('Marionette:SetContext', {'value':'chrome'})
js('Services.prefs.setIntPref("ui.prefersReducedMotion",1);return true')
command('Marionette:SetContext', {'value':'content'})
wait('matchMedia("(prefers-reduced-motion: reduce)").matches')
js('document.querySelector(".clue-toggle").focus();return true')
assert js('return getComputedStyle(document.querySelector(".case-folder")).transform')=='none'
assert js('return getComputedStyle(document.querySelector(".case-folder")).transitionDuration')=='0s'
assert js('return getComputedStyle(document.querySelector(".clues"),"::before").transitionDuration')=='0s'
print('PASS reduced motion', flush=True)
finally:
if sock:sock.close()
firefox.terminate();firefox.wait(timeout=10);log.close()