"""Real Chromium browser smoke test for the unified management console. Requires optional `playwright` and Chromium. Uses an isolated SQLite directory; does not touch live sites, data, or deployment targets. """ from __future__ import annotations import os import socket import subprocess import sys import tempfile import time import urllib.request from pathlib import Path from playwright.sync_api import sync_playwright ROOT=Path(__file__).resolve().parent.parent def run(screenshot_dir=None): with tempfile.TemporaryDirectory(prefix='siteforge-browser-') as data: with socket.socket() as sock: sock.bind(('127.0.0.1',0)) port=sock.getsockname()[1] root_url=f'http://127.0.0.1:{port}' env=os.environ.copy() env.update(SITEFORGE_ADMIN_PASSWORD='browser-smoke-only-strong-password', SITEFORGE_SESSION_SECRET='browser-smoke-secret-key-strong-long-enough', SITEFORGE_DATA_DIR=data, SITEFORGE_DB_FILE=str(Path(data)/'browser.sqlite3'), SITEFORGE_DATABASE_URL='',SITEFORGE_REDIS_URL='') server=subprocess.Popen([sys.executable,'-m','uvicorn','app.main:app','--host','127.0.0.1','--port',str(port)], cwd=ROOT,env=env,stdout=subprocess.DEVNULL,stderr=subprocess.PIPE) try: ready=False for _ in range(100): if server.poll() is not None: raise RuntimeError(f'Web app stopped during startup: {server.stderr.read().decode(errors="replace")[-3000:]}') try: with urllib.request.urlopen(root_url+'/api/ready',timeout=0.5) as res: ready=res.status==200 if ready:break except Exception:time.sleep(0.1) if not ready:raise RuntimeError('Web readiness timeout') with sync_playwright() as pw: launch_settings={'executable_path':os.environ['SITEFORGE_CHROMIUM']} if os.environ.get('SITEFORGE_CHROMIUM') else {} browser=pw.chromium.launch(**launch_settings,headless=True,args=['--no-sandbox','--no-proxy-server','--proxy-server=direct://','--proxy-bypass-list=*','--disable-features=ProxyPacHttpsUrlStripping']) context=browser.new_context(viewport={'width':1440,'height':900},device_scale_factor=1) page=context.new_page() errors=[] page.on('pageerror',lambda error:errors.append(str(error))) page.goto(root_url+'/login',wait_until='domcontentloaded') page.locator('#pass').fill(env['SITEFORGE_ADMIN_PASSWORD']) page.locator('#submit').click() page.wait_for_url(root_url+'/',timeout=12000) page.get_by_text('我的网站',exact=True).first.wait_for(timeout=10000) page.locator('#newSiteTop').click() assert page.locator('#siteModal').evaluate('(element)=>element.open') page.locator('#siteForm [name=name]').fill('浏览器验收站') page.locator('#siteForm button[type=submit]').click() page.locator('#siteModal').wait_for(state='hidden',timeout=10000) page.get_by_text('浏览器验收站',exact=True).first.wait_for(timeout=10000) page.locator('button[data-view="content"]').click() page.locator('#breadcrumbTitle').get_by_text('内容与页面').wait_for() page.locator('button[data-view="design"]').click() page.locator('#breadcrumbTitle').get_by_text('外观与设计').wait_for() if screenshot_dir: d=Path(screenshot_dir);d.mkdir(parents=True,exist_ok=True) page.screenshot(path=str(d/'desktop.png'),full_page=True) mobile=context.new_page() mobile.set_viewport_size({'width':390,'height':844}) mobile.goto(root_url+'/',wait_until='domcontentloaded') assert mobile.url.rstrip('/') == root_url, 'Mobile session unexpectedly lost authentication' mobile.locator('#mainContent').wait_for(timeout=10000) mobile.locator('#menuToggle').click() assert 'nav-open' in mobile.locator('body').get_attribute('class') or mobile.locator('#sideMenu').is_visible() if screenshot_dir: mobile.screenshot(path=str(d/'mobile.png'),full_page=True) assert not errors,errors print('PASS: Chromium login, create site, navigate content/design, mobile menu, no page exceptions') browser.close() finally: server.terminate() try:server.communicate(timeout=5) except subprocess.TimeoutExpired:server.kill();server.communicate() if __name__=='__main__': run(sys.argv[1] if len(sys.argv)>1 else None)