upgrade: adopt V8.31.1 (theme 1.3/1.4 redesigns, editor deep-canvas, guided launch, component library)
Overlay 190 upstream items (V8.22→8.31) preserving trade-pro theme, registry tests, and design docs. Re-applied full patch set on identical anchors (discovery/env_for/blocks.css/type-mapping/locale-override/ theme_color/frontend entries/test_theme_pro_v3 mods) plus compose pins. Verified: theme suites 35/35; dual-baseline full run 415 passed vs pristine 386+29 ours, FAILED-set diff EMPTY (same 10 Windows env fails). Live: container 8.31.1-dev.1 healthy, hesupipe 192 entries intact, trade-pro build 203 pages 0 errors. Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
"""Real isolated Chromium E2E for V8.22 visual authoring; never touches production."""
|
||||
import os,sys,socket,subprocess,tempfile,time,urllib.request,json
|
||||
from pathlib import Path
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
ROOT=Path(__file__).resolve().parents[1]
|
||||
def run():
|
||||
with tempfile.TemporaryDirectory(prefix='sf-v822-canvas-') as td:
|
||||
with socket.socket() as sock:sock.bind(('127.0.0.1',0));port=sock.getsockname()[1]
|
||||
base=f'http://127.0.0.1:{port}'
|
||||
env=dict(os.environ,SITEFORGE_ADMIN_PASSWORD='canvas-browser-strong-password-123',SITEFORGE_SESSION_SECRET='studio-e2e-session-long-test-secret',SITEFORGE_DATA_DIR=td,SITEFORGE_DB_FILE=str(Path(td)/'site.sqlite'),SITEFORGE_DATABASE_URL='',SITEFORGE_REDIS_URL='',SITEFORGE_COOKIE_SECURE='false')
|
||||
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 _ in range(100):
|
||||
if proc.poll() is not None:raise RuntimeError(proc.stderr.read().decode()[-1500:])
|
||||
try:
|
||||
if urllib.request.urlopen(base+'/api/ready',timeout=.4).status==200:break
|
||||
except Exception:time.sleep(.12)
|
||||
else:raise RuntimeError('readiness timeout')
|
||||
with sync_playwright() as playwright:
|
||||
browser=playwright.chromium.launch(executable_path=os.environ.get('SITEFORGE_CHROMIUM','/usr/bin/chromium'),headless=True,args=['--no-sandbox','--no-proxy-server','--proxy-server=direct://','--proxy-bypass-list=*'])
|
||||
ctx=browser.new_context(viewport={'width':1600,'height':1000})
|
||||
page=ctx.new_page()
|
||||
errors=[];page.on('pageerror',lambda error:errors.append(str(error)))
|
||||
r=ctx.request.post(base+'/api/auth/login',data={'username':'admin','password':env['SITEFORGE_ADMIN_PASSWORD']});assert r.ok,r.text()
|
||||
token=ctx.request.get(base+'/api/auth/me').json()['csrf']
|
||||
head={'X-SiteForge-CSRF':token}
|
||||
r=ctx.request.post(base+'/api/sites',data={'name':'V8.22 Visual Studio Test','theme':'corporate'},headers=head);assert r.status==201,r.text()
|
||||
sid=r.json()['id']
|
||||
entries=ctx.request.get(base+f'/api/sites/{sid}/entries').json()
|
||||
home=next(x for x in entries if x['route']=='/')
|
||||
pid=home['id']
|
||||
page.goto(base+f'/editor/{sid}/{pid}',wait_until='domcontentloaded')
|
||||
page.locator('#blockList .block-row').first.wait_for(timeout=18000)
|
||||
frame=page.frame_locator('#sitePreview')
|
||||
frame.locator('.sf-section[data-block]').first.wait_for(timeout=18000)
|
||||
first=page.locator('#blockList .block-row').first
|
||||
original_rows=page.locator('#blockList .block-row').count()
|
||||
assert original_rows>=2
|
||||
# Click on canvas: inspector and block list become selected.
|
||||
frame.locator('.sf-section[data-block]').first.click(position={'x':40,'y':80})
|
||||
assert page.locator('#blockList .block-row.selected').count()==1
|
||||
# Canvas toolbar duplicate must persist in draft, not published content.
|
||||
top=frame.locator('.sf-section[data-block]').first
|
||||
top.hover()
|
||||
top.locator('> .sf-canvas-toolbar [data-action="duplicate"]').click()
|
||||
assert page.locator('#blockList .block-row').count()==original_rows+1
|
||||
page.locator('#saveDraftButton').click()
|
||||
page.get_by_text('草稿 v1').first.wait_for(timeout=10000)
|
||||
saved=ctx.request.get(base+f'/api/sites/{sid}/pages/{pid}/studio-draft').json()
|
||||
assert saved['revision']==1 and len(saved['blocks'])==original_rows+1
|
||||
# Reorder from canvas: it should reflect in draft and undo history.
|
||||
top=frame.locator('.sf-section[data-block]').first
|
||||
top.hover()
|
||||
top.locator('> .sf-canvas-toolbar [data-action="down"]').click()
|
||||
assert page.locator('#saveStatus').inner_text()=='未保存更改'
|
||||
# Insert a real multi-column layout through existing UI.
|
||||
page.locator('#sfAddBlock').click()
|
||||
page.locator('#palette button').filter(has_text='多列自由布局').click()
|
||||
frame.locator('.sf-layout-grid[data-layout]').last.wait_for(timeout=12000)
|
||||
assert frame.locator('.sf-canvas-resize').count()>=1
|
||||
# Explicit mobile setting is a view change, never a data mutation.
|
||||
page.locator('#devicePhone').click()
|
||||
assert 'phone' in page.locator('#deviceFrame').get_attribute('class')
|
||||
page.locator('#deviceDesktop').click()
|
||||
assert 'desktop' not in page.locator('#deviceFrame').get_attribute('class')
|
||||
assert not errors,errors
|
||||
if os.environ.get('SITEFORGE_V822_SCREENSHOT_DIR'):
|
||||
dest=Path(os.environ['SITEFORGE_V822_SCREENSHOT_DIR']);dest.mkdir(parents=True,exist_ok=True)
|
||||
page.screenshot(path=str(dest/'studio-browser-verified.png'),full_page=False)
|
||||
print('PASS: Chromium canvas click/select -> toolbar duplicate -> save draft -> reorder -> add layout -> responsive preview; no JS errors')
|
||||
browser.close()
|
||||
finally:
|
||||
proc.terminate()
|
||||
try:proc.communicate(timeout=5)
|
||||
except subprocess.TimeoutExpired:proc.kill();proc.communicate()
|
||||
|
||||
if __name__=='__main__':run()
|
||||
Reference in New Issue
Block a user