This commit is contained in:
2026-10-14 23:39:28 +07:00
parent 734025a765
commit 2ed1bf377b
4 changed files with 127 additions and 14 deletions
+7
View File
@@ -210,3 +210,10 @@ TR3000 V1 2.5.27-20260804-183625 без изменения настроек ро
Flow также проверяет переход заполненной БД с числовых ID команд на UUID v7,
сохранность данных и ограничений, независимое создание команд в трёх схемах
и полную Go-проверку на временном PostgreSQL. `team_uuid.py` вызывается из flow.
## Дизайн-система (без сервера и БД)
Запустить фронтенд командой `npm run dev -- --port 5198`, затем из корня
`python3 tests/venue/design_system.py`. Требуется установленный Firefox.
Smoke проверяет три ширины, клавиатурные вкладки, повторные карточки и ID,
локальную форму и окно, состояния, темы и reduced motion.
+107
View File
@@ -0,0 +1,107 @@
"""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()