81 lines
5.6 KiB
Python
81 lines
5.6 KiB
Python
|
|
"""Mock-backed real workspace Chromium test; NEVER touches production or localhost HTTP."""
|
||
|
|
from pathlib import Path
|
||
|
|
from playwright.sync_api import sync_playwright
|
||
|
|
ROOT=Path(__file__).resolve().parents[1]
|
||
|
|
|
||
|
|
def run():
|
||
|
|
errors=[]
|
||
|
|
with sync_playwright() as p:
|
||
|
|
browser=p.chromium.launch(headless=True,executable_path='/usr/bin/chromium',args=['--no-sandbox'])
|
||
|
|
page=browser.new_page(viewport={'width':1366,'height':900})
|
||
|
|
page.on('pageerror',lambda e:errors.append(str(e)))
|
||
|
|
markup=(ROOT/'app/templates/workspace.html').read_text()
|
||
|
|
for tag in ('<link rel="stylesheet" href="/ui/workspace.css">','<script defer src="/ui/workspace-guide.js"></script>','<script defer src="/ui/workspace-launch.js"></script>','<script defer src="/ui/workspace.js"></script>'):
|
||
|
|
markup=markup.replace(tag,'')
|
||
|
|
page.set_content(markup)
|
||
|
|
page.add_style_tag(content=(ROOT/'app/static/workspace.css').read_text())
|
||
|
|
page.evaluate(r'''() => {
|
||
|
|
window.history.replaceState=()=>{};window.history.pushState=()=>{};Object.defineProperty(window.crypto,'randomUUID',{value:()=> 'mock-idempotency-uuid'});
|
||
|
|
window.confirm=()=>true;
|
||
|
|
window.mock={approved:false,submitCount:0,requests:[],deployCount:0,site:{id:'s1',name:'测试网站',theme:'corporate',mode:'new',domain:'https://www.example.com',updated_at:'2026-01-01T00:00:00Z'},
|
||
|
|
builds:[{id:'b1',status:'complete',created_at:'2026-01-03T00:00:00Z',total_pages:12}]};
|
||
|
|
window.fetch=async (uri,opts={})=>{
|
||
|
|
let key=String(uri).split('?')[0],method=opts.method||'GET',result=null,status=200;
|
||
|
|
const m=window.mock;
|
||
|
|
if(key==='/api/auth/me')result={csrf:'test'};
|
||
|
|
else if(key==='/api/health')result={version:'8.28.0-dev.1'};
|
||
|
|
else if(key==='/api/sites'&&method==='GET')result=[m.site];
|
||
|
|
else if(key==='/api/sites/s1')result=m.site;
|
||
|
|
else if(key==='/api/sites/s1/entries')result=[{id:'h1',source:'manual',status:'published',route:'/',type:'page',title:'网站首页'}];
|
||
|
|
else if(key==='/api/sites/s1/builds')result=m.builds;
|
||
|
|
else if(key==='/api/enterprise/sites/s1/jobs')result=[];
|
||
|
|
else if(key==='/api/production/sites/s1/delivery-status')result={jobs:[],history:[],configured:{production:true,staging:true}};
|
||
|
|
else if(key==='/api/factory/sites/s1/release-requests'&&method==='GET')result=m.requests;
|
||
|
|
else if(key==='/api/factory/sites/s1/release-requests'&&method==='POST') {result={id:'r1',state:'pending'};m.requests=[{id:'r1',build_id:'b1',state:'pending'}]}
|
||
|
|
else if(key==='/api/production/sites/s1/preflight/b1')result={ready:m.approved,build_id:'b1',message:m.approved?'已批准':'尚未批准'};
|
||
|
|
else if(key==='/api/production/sites/s1/deploy'&&method==='POST') {m.deployCount++; result={id:'job-test-1'};}
|
||
|
|
else if(key==='/api/sites/s1/builds/b1/report')result={pages:12,warnings:[],redirects:0};
|
||
|
|
else {status=404;result={detail:'模拟接口没有实现 '+key};}
|
||
|
|
return {ok:status<400,status,json:async()=>result};
|
||
|
|
};
|
||
|
|
}''')
|
||
|
|
for name in ('workspace-guide.js','workspace-launch.js','workspace.js'):
|
||
|
|
page.add_script_tag(content=(ROOT/'app/static'/name).read_text())
|
||
|
|
page.evaluate("document.dispatchEvent(new Event('DOMContentLoaded'))")
|
||
|
|
page.wait_for_selector('.site-card',timeout=10000)
|
||
|
|
page.locator('.site-card').first.click()
|
||
|
|
page.locator('[data-view="launch"].nav-item').click()
|
||
|
|
page.wait_for_timeout(1100)
|
||
|
|
page.wait_for_selector('.sf-launch-step',timeout=3000)
|
||
|
|
assert page.locator('.sf-launch-step').count()==6
|
||
|
|
assert page.locator('[data-action="launch-deploy"]').is_disabled()
|
||
|
|
assert page.locator('[data-action="launch-request-approval"]').is_disabled()
|
||
|
|
page.locator('#launchReviewCheck').check()
|
||
|
|
assert page.locator('[data-action="launch-request-approval"]').is_enabled()
|
||
|
|
page.locator('[data-action="launch-request-approval"]').click()
|
||
|
|
page.wait_for_timeout(400)
|
||
|
|
page.wait_for_function('window.mock.requests.length===1',timeout=3500)
|
||
|
|
assert page.locator('[data-action="launch-preflight"]').is_disabled()
|
||
|
|
page.evaluate("window.mock.requests[0].state='approved';window.mock.approved=true")
|
||
|
|
page.locator('[data-action="launch-refresh"]').first.click()
|
||
|
|
page.wait_for_timeout(600)
|
||
|
|
page.wait_for_function("document.querySelector('[data-action=launch-preflight]')?.disabled===false",timeout=4000)
|
||
|
|
page.locator('[data-action="launch-preflight"]').click()
|
||
|
|
page.wait_for_timeout(450)
|
||
|
|
page.wait_for_function("document.querySelector('[data-action=launch-deploy]')?.disabled===false",timeout=4000)
|
||
|
|
page.locator('[data-action="launch-deploy"]').click()
|
||
|
|
page.wait_for_function('window.mock.deployCount===1')
|
||
|
|
assert '已进入后台队列' in page.locator('#launchNotice').inner_text()
|
||
|
|
report=ROOT/'reports/v828';report.mkdir(parents=True,exist_ok=True)
|
||
|
|
page.evaluate('window.scrollTo(0,0)')
|
||
|
|
page.wait_for_timeout(150)
|
||
|
|
page.screenshot(path=str(report/'launch-desktop.png'),full_page=True)
|
||
|
|
page.set_viewport_size({'width':390,'height':844})
|
||
|
|
page.wait_for_timeout(250)
|
||
|
|
page.screenshot(path=str(report/'launch-mobile.png'),full_page=True)
|
||
|
|
assert page.evaluate('document.documentElement.scrollWidth<=innerWidth+1'),'mobile overflow'
|
||
|
|
assert not errors,errors
|
||
|
|
browser.close()
|
||
|
|
print('PASS: original workspace JS, approval flow, preview checkbox, preflight, submit mock, mobile width, 0 runtime errors')
|
||
|
|
if __name__=='__main__':run()
|