- web/ com Vite + Svelte 5 + TypeScript; servidor entrega web/dist em "/" (telas antigas seguem em /dashboard, /painel, /braco...) - design system em tokens CSS, tema escuro e claro, fontes locais - estado unico: foto de /estado + um EventSource em /eventos, sem polling - shell com navegacao lateral/inferior, selos de status reais e E-Stop fixo - command palette (Ctrl+K), gestos favoritos com atalhos 1-9, g+letra navega - Ao vivo: camera WebRTC e rastreamento portados para TS, rosto, aparencia, sensores do celular, IA local e feed de acontecimentos - Ajustes: campos do celular agrupados com busca, salvamento otimista e restaurar padrao (GET /app/padrao); motor da conversa, Gemini e token - carinha.html ganha modo ?painel=1 dirigido por postMessage (sem polling) - teste Playwright de ponta a ponta do painel novo Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
137 lines
5.7 KiB
Python
137 lines
5.7 KiB
Python
"""Painel novo (web/dist) contra o servidor real: token, SSE, gestos, paleta, E-Stop, ajustes e celular.
|
|
|
|
Requer `npm run build` em web/. Uso: python tests/test_painel_web_browser.py [pasta-para-capturas]
|
|
"""
|
|
from playwright.sync_api import sync_playwright, expect
|
|
from pathlib import Path
|
|
import importlib.util
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import threading
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT))
|
|
os.chdir(ROOT)
|
|
spec = importlib.util.spec_from_file_location('kiosk_web', ROOT / 'kiosk-server.py')
|
|
kiosk = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(kiosk)
|
|
from braco import Braco
|
|
from servos import ControleServos
|
|
|
|
TOKEN = 'token-de-teste-123'
|
|
|
|
|
|
class HW:
|
|
def __init__(self): self.calls = []
|
|
def pulse(self, ch, us): self.calls.append((ch, us))
|
|
def off(self, ch): self.calls.append((ch, None))
|
|
|
|
|
|
def main(capturas=None):
|
|
assert (ROOT / 'web/dist/index.html').is_file(), 'Rode npm run build em web/ primeiro'
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
tmp = Path(tmp)
|
|
kiosk.ARQUIVO_APARENCIA = str(tmp / 'aparencia.json')
|
|
server = kiosk.Servidor(('127.0.0.1', 0), kiosk.Handler)
|
|
server.token = TOKEN
|
|
server.controle = kiosk.ControlePC(tmp / 'app.json')
|
|
server.servos = ControleServos(tmp / 'servos.json', HW())
|
|
server.braco = Braco(tmp / 'braco.json', server.servos)
|
|
kiosk.ligar_eventos(server)
|
|
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
thread.start()
|
|
try:
|
|
testar('http://127.0.0.1:%d' % server.server_port, server, capturas)
|
|
finally:
|
|
kiosk.EVENTOS.fechar()
|
|
server.shutdown()
|
|
server.server_close()
|
|
server.servos.close()
|
|
|
|
|
|
def testar(base, server, capturas):
|
|
auth = {'Authorization': 'Bearer ' + TOKEN}
|
|
with sync_playwright() as p:
|
|
browser = p.chromium.launch()
|
|
page = browser.new_page(viewport={'width': 1440, 'height': 960})
|
|
erros = []
|
|
page.on('pageerror', lambda e: erros.append(str(e)))
|
|
page.goto(base + '/')
|
|
|
|
# 1. token
|
|
expect(page.get_by_role('heading', name='Token do Carinha')).to_be_visible()
|
|
page.get_by_label('Token').fill(TOKEN)
|
|
page.get_by_role('button', name='Entrar').click()
|
|
expect(page.get_by_text('Servidor', exact=True)).to_be_visible(timeout=5000)
|
|
|
|
# 2. telemetria do celular chega por SSE
|
|
page.request.post(base + '/app/status', headers=auth, data={'dormindo': False, 'bateria': 0.8,
|
|
'microfone_ativo': True, 'microfone': 0.3})
|
|
expect(page.get_by_text('Celular online · 80%')).to_be_visible(timeout=5000)
|
|
|
|
# 3. gesto pelo dock
|
|
kiosk.FILA.limpar()
|
|
page.get_by_role('button', name='Feliz').first.click()
|
|
expect(page.get_by_text('Expressão: feliz')).to_be_visible(timeout=5000)
|
|
assert kiosk.FILA.ordens() == ['feliz'], kiosk.FILA.ordens()
|
|
|
|
# 4. atalho numérico e paleta
|
|
page.locator('body').click(position={'x': 5, 'y': 900})
|
|
page.keyboard.press('2')
|
|
page.wait_for_timeout(300)
|
|
assert kiosk.FILA.ordens()[-1] == 'coracao', kiosk.FILA.ordens()
|
|
page.keyboard.press('Control+k')
|
|
page.get_by_label('Buscar comando').fill('surpreso')
|
|
page.keyboard.press('Enter')
|
|
page.wait_for_timeout(300)
|
|
assert kiosk.FILA.ordens()[-1] == 'surpreso', kiosk.FILA.ordens()
|
|
if capturas:
|
|
page.screenshot(path=str(Path(capturas) / 'ao-vivo.png'), full_page=True)
|
|
|
|
# 5. E-Stop real
|
|
page.get_by_role('button', name='Parar tudo').click()
|
|
expect(page.get_by_role('button', name='PARADO · Rearmar')).to_be_visible(timeout=5000)
|
|
assert kiosk.ESTOP['ativo'] and server.servos.travado
|
|
page.get_by_role('button', name='PARADO · Rearmar').click()
|
|
expect(page.get_by_role('button', name='Parar tudo')).to_be_visible(timeout=5000)
|
|
assert not server.servos.travado
|
|
|
|
# 6. ajustes salvam sem travar
|
|
page.get_by_role('link', name='Ajustes').first.click()
|
|
page.get_by_label('Buscar ajuste').fill('boca')
|
|
boca = page.get_by_role('switch', name='Mostrar boca')
|
|
assert boca.is_checked()
|
|
boca.locator('xpath=..').click()
|
|
page.wait_for_timeout(500)
|
|
assert server.controle.config['boca'] is False
|
|
if capturas:
|
|
page.get_by_label('Buscar ajuste').fill('')
|
|
page.screenshot(path=str(Path(capturas) / 'ajustes.png'), full_page=True)
|
|
|
|
# 7. celular: barra inferior e nada de rolagem horizontal
|
|
page.set_viewport_size({'width': 400, 'height': 860})
|
|
page.get_by_role('link', name='Ao vivo').click()
|
|
page.wait_for_timeout(300)
|
|
largura = page.evaluate('document.scrollingElement.scrollWidth')
|
|
assert largura <= 400, 'rolagem horizontal: %s' % largura
|
|
if capturas:
|
|
page.screenshot(path=str(Path(capturas) / 'celular.png'), full_page=True)
|
|
|
|
# 8. tema claro
|
|
page.set_viewport_size({'width': 1280, 'height': 900})
|
|
inicial = page.evaluate("document.documentElement.dataset.theme")
|
|
outro = 'light' if inicial == 'dark' else 'dark'
|
|
page.get_by_role('button', name='Usar tema ' + ('claro' if outro == 'light' else 'escuro')).click()
|
|
assert page.evaluate("document.documentElement.dataset.theme") == outro
|
|
if capturas:
|
|
page.screenshot(path=str(Path(capturas) / ('tema-' + outro + '.png')))
|
|
|
|
assert not erros, erros
|
|
browser.close()
|
|
print('OK: token, SSE, dock 1-9, paleta, E-Stop, ajustes otimistas, celular 400px, tema claro')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main(sys.argv[1] if len(sys.argv) > 1 else None)
|