112 lines
7.3 KiB
Python
112 lines
7.3 KiB
Python
|
|
"""Browser smoke test for the real editor template, with deterministic mocked APIs.
|
||
|
|
|
||
|
|
No real user session, database or network service is accessed.
|
||
|
|
"""
|
||
|
|
from __future__ import annotations
|
||
|
|
import json
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
ROOT=Path(__file__).resolve().parents[1]
|
||
|
|
REPORT=ROOT/'reports'/'v829-browser'
|
||
|
|
|
||
|
|
def run():
|
||
|
|
from playwright.sync_api import sync_playwright
|
||
|
|
REPORT.mkdir(parents=True,exist_ok=True)
|
||
|
|
editor=(ROOT/'app/templates/editor.html').read_bytes()
|
||
|
|
schema={'blocks':{
|
||
|
|
'hero':{'name':'横幅','icon':'◆','description':'顶部展示','fields':['title'],'defaults':{'title':'全球业务'}},
|
||
|
|
'richtext':{'name':'文字介绍','icon':'☷','fields':['title'],'defaults':{'title':'介绍'}},
|
||
|
|
'collection':{'name':'产品列表','icon':'▦','fields':['title'],'defaults':{'title':'我们的产品'}},
|
||
|
|
'layout':{'name':'多列布局','icon':'◫','fields':['title'],'defaults':{'title':'多列布局'}},
|
||
|
|
},'page_templates':{'standard':{'name':'标准页面'}}}
|
||
|
|
calls=[]
|
||
|
|
def mock(route):
|
||
|
|
req=route.request
|
||
|
|
path=req.url.split('example.test',1)[-1].split('?',1)[0]
|
||
|
|
if path=='/studio/s1':return route.fulfill(status=200,body=editor,content_type='text/html')
|
||
|
|
if path.startswith('/ui/'):
|
||
|
|
file=ROOT/'app/static'/path.removeprefix('/ui/')
|
||
|
|
if file.is_file():
|
||
|
|
typ='text/css' if file.suffix=='.css' else 'application/javascript'
|
||
|
|
return route.fulfill(status=200,body=file.read_bytes(),content_type=typ)
|
||
|
|
if path=='/api/auth/me':data={'csrf':'test-token'}
|
||
|
|
elif path=='/api/sites/s1':data={'id':'s1','name':'测试网站','theme':'automotive'}
|
||
|
|
elif path=='/api/sites/s1/entries':data=[{'id':'p1','title':'首页','route':'/'}]
|
||
|
|
elif path=='/api/editor/blocks':data=schema
|
||
|
|
elif path=='/api/sites/s1/components':data=[]
|
||
|
|
elif path=='/api/sites/s1/pages/p1/studio-draft':data={'revision':0,'blocks':[{'id':'b1','type':'hero','title':'全球业务','visible':True,'style':{}}],'page_template':'standard','layout_mode':'append','entry':{'title':'首页','route':'/'}}
|
||
|
|
elif path=='/api/sites/s1/pages/p1/editor-preview':
|
||
|
|
return route.fulfill(status=200,body='<html><body><section data-block="b1"><h1>全球业务</h1></section></body></html>',content_type='text/html')
|
||
|
|
elif path.startswith('/api/factory/themes/') and path.endswith('/preview'):
|
||
|
|
return route.fulfill(status=200,body='<html><body><header>主题示例</header><main><h1>网页主题样例</h1></main></body></html>',content_type='text/html')
|
||
|
|
else:return route.fulfill(status=404,body='not mocked')
|
||
|
|
calls.append(path)
|
||
|
|
return route.fulfill(status=200,body=json.dumps(data,ensure_ascii=False),content_type='application/json')
|
||
|
|
with sync_playwright() as p:
|
||
|
|
browser=p.chromium.launch(executable_path='/usr/bin/chromium',headless=True,args=['--no-sandbox','--disable-dev-shm-usage'])
|
||
|
|
page=browser.new_page(viewport={'width':1440,'height':900},device_scale_factor=1)
|
||
|
|
failures=[]
|
||
|
|
page.on('pageerror',lambda e:failures.append(str(e)))
|
||
|
|
# Navigation to local HTTP is blocked by this runner's browser policy.
|
||
|
|
# Exercise the exact editor DOM, CSS, and JS in isolated set_content instead.
|
||
|
|
import re
|
||
|
|
html_source=editor.decode('utf-8')
|
||
|
|
html_source=re.sub(r'<script[^>]+src="[^"]+"[^>]*></script>', '',html_source)
|
||
|
|
html_source=re.sub(r'<link[^>]+href="/ui/[^"]+"[^>]*>', '',html_source)
|
||
|
|
page.set_content(html_source)
|
||
|
|
page.add_style_tag(content=(ROOT/'app/static/editor.css').read_text())
|
||
|
|
page.add_script_tag(content=(ROOT/'app/static/editor-helpers.js').read_text())
|
||
|
|
page.add_script_tag(content=(ROOT/'app/static/studio-experience-v829.js').read_text())
|
||
|
|
page.evaluate("""({schema}) => {
|
||
|
|
const payloads={
|
||
|
|
'/api/auth/me':{csrf:'test-token'},
|
||
|
|
'/api/sites/s1':{id:'s1',name:'测试网站',theme:'automotive'},
|
||
|
|
'/api/sites/s1/entries':[{id:'p1',title:'首页',route:'/'}],
|
||
|
|
'/api/editor/blocks':schema,
|
||
|
|
'/api/sites/s1/components':[],
|
||
|
|
'/api/sites/s1/pages/p1/studio-draft':{revision:0,blocks:[{id:'b1',type:'hero',title:'全球业务',visible:true,style:{}}],page_template:'standard',layout_mode:'append',entry:{title:'首页',route:'/'}},
|
||
|
|
};
|
||
|
|
window.fetch=async function(url){
|
||
|
|
const path=String(url).split('?')[0];
|
||
|
|
if(path.endsWith('/editor-preview'))return {ok:true,status:200,headers:{get:()=> 'text/html'},text:async()=>'<html><body><h1>全球业务</h1></body></html>'};
|
||
|
|
const data=payloads[path];
|
||
|
|
return {ok:!!data,status:data?200:404,headers:{get:()=> 'application/json'},json:async()=>data||{detail:'not mocked'}};
|
||
|
|
};
|
||
|
|
}""",{'schema':schema})
|
||
|
|
script=(ROOT/'app/static/editor.js').read_text().replace("const siteId=pathParts[1],initialPage=pathParts[2];","const siteId='s1',initialPage='p1';")
|
||
|
|
page.add_script_tag(content=script)
|
||
|
|
page.evaluate('Studio.init()')
|
||
|
|
page.locator('#pageName').get_by_text('首页').wait_for(timeout=10000)
|
||
|
|
page.locator('#sfAddBlock').click()
|
||
|
|
assert page.locator('.sf-palette-group').count()==4
|
||
|
|
page.locator('#paletteSearch').fill('产品')
|
||
|
|
assert page.locator('.sf-palette-grid button').count()==1
|
||
|
|
assert '产品列表' in page.locator('.sf-palette-grid button').inner_text()
|
||
|
|
page.locator('#paletteSearch').fill('')
|
||
|
|
page.locator('#studioThemePreviewButton').click()
|
||
|
|
assert page.locator('#themePreviewModal').is_visible()
|
||
|
|
page.locator('#themePreviewTheme').select_option('industrial')
|
||
|
|
page.locator('#themePreviewPage').select_option('product')
|
||
|
|
page.get_by_role('button',name='手机',exact=True).last.click()
|
||
|
|
assert 'width: 390px' in page.locator('#themePreviewFrameShell').get_attribute('style')
|
||
|
|
assert '/industrial/preview?page_type=product' in page.locator('#themePreviewFrame').get_attribute('src')
|
||
|
|
page.locator('#themePreviewModal').locator('button[aria-label="关闭主题预览"]').click()
|
||
|
|
page.locator('#tabStyle').click()
|
||
|
|
page.get_by_role('button',name='柔和浅底').click()
|
||
|
|
assert page.locator('#saveStatus').inner_text()=='未保存更改'
|
||
|
|
assert not page.locator('#quickUndo').is_disabled()
|
||
|
|
page.locator('#quickUndo').click()
|
||
|
|
assert not failures,failures
|
||
|
|
page.screenshot(path=str(REPORT/'v829-studio-desktop.png'),full_page=False)
|
||
|
|
page.set_viewport_size({'width':390,'height':844})
|
||
|
|
page.locator('details.studio-actions summary').click()
|
||
|
|
page.locator('.sf-more-theme').click()
|
||
|
|
assert page.locator('#themePreviewModal').is_visible()
|
||
|
|
assert page.evaluate('document.documentElement.scrollWidth <= innerWidth + 2')
|
||
|
|
page.screenshot(path=str(REPORT/'v829-studio-phone.png'),full_page=False)
|
||
|
|
browser.close()
|
||
|
|
(REPORT/'result.json').write_text(json.dumps({'status':'passed','browser':'Chromium, route-mocked real editor assets','desktop':'palette/theme/style/undo','mobile':'390px theme dialog, no horizontal overflow','page_errors':failures,'api_calls':calls},ensure_ascii=False,indent=2))
|
||
|
|
print('PASS: browser studio V8.29 desktop + 390px mobile. No JS errors.')
|
||
|
|
|
||
|
|
if __name__=='__main__':run()
|