Files

70 lines
4.7 KiB
Python
Raw Permalink Normal View History

"""Optional actual Chromium smoke on disposable site. Never contacts production."""
import json, os, socket, subprocess, sys, tempfile, time
from pathlib import Path
from urllib.request import urlopen
import httpx
from playwright.sync_api import sync_playwright
ROOT=Path(__file__).resolve().parent.parent
OUT=ROOT/'reports'/'studio-ux'
OUT.mkdir(parents=True,exist_ok=True)
def main():
with tempfile.TemporaryDirectory(prefix='sf-studio-ui-') as folder:
with socket.socket() as s:s.bind(('127.0.0.1',0));port=s.getsockname()[1]
base=f'http://127.0.0.1:{port}'
env={**os.environ,'SITEFORGE_ADMIN_PASSWORD':'studio-smoke-only-password','SITEFORGE_SESSION_SECRET':'studio-smoke-only-secret-very-long','SITEFORGE_DATA_DIR':folder,'SITEFORGE_DB_FILE':str(Path(folder)/'siteforge.sqlite3'),'SITEFORGE_DATABASE_URL':'','SITEFORGE_REDIS_URL':'','SITEFORGE_COOKIE_SECURE':'false','NO_PROXY':'127.0.0.1,localhost','no_proxy':'127.0.0.1,localhost'}
proc=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:
for i in range(120):
if proc.poll() is not None:raise RuntimeError('server exited: '+proc.stderr.read().decode()[-1200:])
try:
with urlopen(base+'/api/ready',timeout=0.4) as response:
if response.status==200:break
except Exception:time.sleep(.1)
else:raise RuntimeError('readiness timed out')
with httpx.Client(base_url=base,trust_env=False) as client:
login=client.post('/api/auth/login',json={'username':'admin','password':env['SITEFORGE_ADMIN_PASSWORD']});login.raise_for_status()
csrf=client.get('/api/auth/me').json()['csrf']
site=client.post('/api/sites',json={'name':'V8.21 Studio UI 验收站点','theme':'corporate'},headers={'X-SiteForge-CSRF':csrf});site.raise_for_status();sid=site.json()['id']
entries=client.get(f'/api/sites/{sid}/entries').json();page_id=next(x['id'] for x in entries if x['route']=='/')
cookie_names={c.name:c.value for c in client.cookies.jar}
with sync_playwright() as play:
browser=play.chromium.launch(executable_path='/usr/bin/chromium',headless=True,args=['--no-sandbox','--disable-dev-shm-usage','--no-proxy-server','--disable-gpu'])
context=browser.new_context(viewport={'width':1600,'height':950},device_scale_factor=1)
context.add_cookies([{'name':k,'value':v,'url':base} for k,v in cookie_names.items()])
page=context.new_page()
errors=[];page.on('pageerror',lambda e:errors.append(str(e)))
page.goto(base+f'/editor/{sid}/{page_id}',wait_until='domcontentloaded',timeout=20000)
page.locator('#blockList .block-row').first.wait_for(timeout=12000)
page.screenshot(path=str(OUT/'editor-desktop.png'),full_page=True)
page.locator('#tabResponsive').click()
assert page.locator('#tabResponsive').get_attribute('aria-selected')=='true'
assert not page.locator('[data-inspector-section="responsive"]').is_hidden()
page.locator('#tabContent').click()
total=page.locator('.block-row').count()
page.locator('#blockSearch').fill('not-find-anything')
assert page.locator('.block-row').count()==0
page.locator('#blockSearch').fill('')
assert page.locator('.block-row').count()==total
page.locator('.block-row').first.locator('button[title="隐藏"]').click()
assert page.locator('#saveStatus').inner_text().startswith('未保存')
page.locator('#saveDraftButton').click()
page.wait_for_function("document.querySelector('#saveStatus').textContent.startsWith('草稿')",timeout=12000)
page.locator('#devicePhone').click()
page.screenshot(path=str(OUT/'editor-phone-preview.png'),full_page=True)
assert not errors,errors
mobile=browser.new_context(viewport={'width':390,'height':844},device_scale_factor=1)
mobile.add_cookies([{'name':k,'value':v,'url':base} for k,v in cookie_names.items()])
mp=mobile.new_page();mp.goto(base+f'/editor/{sid}/{page_id}',wait_until='domcontentloaded',timeout=20000)
mp.locator('#blockCount').wait_for(timeout=12000)
mp.locator('.pane-switcher button').first.click()
assert 'show-structure' in mp.locator('body').get_attribute('class')
mp.screenshot(path=str(OUT/'editor-mobile-ui.png'),full_page=True)
mobile.close();browser.close()
print(json.dumps({'result':'PASS','desktop_blocks':total,'browser_errors':errors,'screenshots':[str(x) for x in OUT.glob('*.png')]},ensure_ascii=False))
finally:
proc.terminate()
try:proc.wait(timeout=5)
except subprocess.TimeoutExpired:proc.kill();proc.wait()
if __name__=='__main__':main()