88 lines
4.8 KiB
Python
88 lines
4.8 KiB
Python
|
|
"""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)
|