601451f22b
Overlay ~150 upstream items onto our V8.12 base, preserving trade-pro
theme, registry tests, and design docs. Re-applied our patch set
(discovery/env_for/blocks.css/type-mapping/theme_color/frontend entries,
test_theme_pro_v3 mods) — all anchors survived upstream evolution.
New platform enhancement (fixes upgrade-introduced regression):
multilingual.site_locale now honors settings_json.locale, plus
PUT /api/i18n/sites/{id}/locale — standalone English sites get
<html lang='en'> under the V8.15 locale-rewrite pipeline.
compose: adopt V8.21.3 (image 8.21.3-dev.1, /api/ready probes, MySQL/S3
env) with project name pinned to siteforge-v8-10 (data volumes).
Tests: 356 passed / 11 failed — all 11 confirmed pre-existing in
pristine V8.21.3 on this host (10 upstream Windows env + our build test
now green after locale fix). Patch registry updated in plan doc.
Co-Authored-By: Claude Code <noreply@anthropic.com>
70 lines
4.7 KiB
Python
70 lines
4.7 KiB
Python
"""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()
|