Release v0.1.5: unify image loading and mobile layouts on port 6005 [skip ci]
This commit is contained in:
@@ -10,7 +10,7 @@ from urllib.request import Request, urlopen
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("mode", choices=["record", "check"])
|
||||
args = parser.parse_args()
|
||||
base = os.getenv("FRAME_FLOW_URL", "http://127.0.0.1:18787").rstrip("/")
|
||||
base = os.getenv("FRAME_FLOW_URL", "http://127.0.0.1:6005").rstrip("/")
|
||||
token = os.getenv("FRAME_FLOW_TOKEN", "")
|
||||
if not token:
|
||||
for line in Path(".env").read_text().splitlines():
|
||||
|
||||
@@ -40,5 +40,8 @@ os.environ["BUILD_COMMIT"] = (
|
||||
|
||||
os.environ["BUILD_TIME"] = datetime.now(UTC).isoformat()
|
||||
uvicorn.run(
|
||||
"pigway_frame_flow.main:app", host="127.0.0.1", port=18787, access_log=False
|
||||
"pigway_frame_flow.main:app",
|
||||
host="127.0.0.1",
|
||||
port=int(os.getenv("WEB_PORT", "6005")),
|
||||
access_log=False,
|
||||
)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
/* Mock vendor expiration must preserve first-party login and show reconnect. */
|
||||
const assert=require('assert'),{chromium}=require('playwright');
|
||||
(async()=>{const browser=await chromium.launch({headless:true,executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'});const page=await browser.newPage();const errors=[];page.on('pageerror',e=>errors.push(e.message));await page.goto('http://127.0.0.1:18787');await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();assert(await page.evaluate(()=>state.settings.mock));await page.route('**/api/v1/devices/*/widgets',r=>r.fulfill({status:401,json:{error:{code:'OFFICIAL_AUTH_REQUIRED',message:'Official account session expired',request_id:'fixture',details:{}}}}));await page.route('**/api/v1/devices/*/official/connection',r=>r.fulfill({json:{authorized:false,username:'fixture',robot_id:null,mock:true,devices:[]}}));await page.locator('[data-page="templates"]').click();await page.locator('.official-content-info summary').click();await page.locator('#official-login-form').waitFor();assert(await page.evaluate(()=>Boolean(state.token&&state.csrf)));assert(await page.evaluate(async()=>Boolean((await api('/api/v1/auth/me')).username)));await page.route('**/api/v1/test-first-party-expired',r=>r.fulfill({status:401,json:{error:{code:'UNAUTHORIZED',message:'Expired',request_id:'fixture',details:{}}}}));await page.evaluate(async()=>{try{await api('/api/v1/test-first-party-expired');}catch{}});assert(await page.evaluate(()=>state.token===''));assert(errors.length===0);await browser.close();console.log('Audit UI: official expiry preserves login, reconnect visible, own expiry clears login; PASS');})().catch(e=>{console.error(e.message);process.exit(1)});
|
||||
(async()=>{const browser=await chromium.launch({headless:true,executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'});const page=await browser.newPage();const errors=[];page.on('pageerror',e=>errors.push(e.message));await page.goto(process.env.FRAME_FLOW_URL||'http://127.0.0.1:6005');await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();assert(await page.evaluate(()=>state.settings.mock));await page.route('**/api/v1/devices/*/widgets',r=>r.fulfill({status:401,json:{error:{code:'OFFICIAL_AUTH_REQUIRED',message:'Official account session expired',request_id:'fixture',details:{}}}}));await page.route('**/api/v1/devices/*/official/connection',r=>r.fulfill({json:{authorized:false,username:'fixture',robot_id:null,mock:true,devices:[]}}));await page.locator('[data-page="templates"]').click();await page.locator('.official-content-info summary').click();await page.locator('#official-login-form').waitFor();assert(await page.evaluate(()=>Boolean(state.token&&state.csrf)));assert(await page.evaluate(async()=>Boolean((await api('/api/v1/auth/me')).username)));await page.route('**/api/v1/test-first-party-expired',r=>r.fulfill({status:401,json:{error:{code:'UNAUTHORIZED',message:'Expired',request_id:'fixture',details:{}}}}));await page.evaluate(async()=>{try{await api('/api/v1/test-first-party-expired');}catch{}});assert(await page.evaluate(()=>state.token===''));assert(errors.length===0);await browser.close();console.log('Audit UI: official expiry preserves login, reconnect visible, own expiry clears login; PASS');})().catch(e=>{console.error(e.message);process.exit(1)});
|
||||
|
||||
@@ -5,7 +5,7 @@ const assert=require('assert'),{chromium}=require('playwright');
|
||||
await page.route('**/api/v1/devices/*/images',r=>r.fulfill({json:[{image_id:1,name:'Current fixture',current:true,url:'/mock-image.jpg',created_at:null},{image_id:2,name:'Other fixture',current:false,url:'/mock-image.jpg',created_at:null}]}));
|
||||
page.on('request',r=>{if(r.method()==='POST'&&r.url().endsWith('/display'))writes++;});
|
||||
try{
|
||||
await page.goto('http://127.0.0.1:18787');await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();assert(await page.evaluate(()=>state.settings.mock));
|
||||
await page.goto(process.env.FRAME_FLOW_URL||'http://127.0.0.1:6005');await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();assert(await page.evaluate(()=>state.settings.mock));
|
||||
for(const lang of ['zh','en'])for(const width of [1440,390])for(const theme of ['dark','light']){
|
||||
await page.setViewportSize({width,height:1000});await page.evaluate(({lang,theme})=>{state.lang=lang;document.documentElement.dataset.theme=theme;shell();},{lang,theme});await page.locator('[data-page="gallery"]').click();await page.locator('[data-show="0"]').waitFor();assert(await page.locator('[data-show="0"]').isDisabled());assert(await page.locator('[data-show="0"]').textContent()===(lang==='zh'?'当前图片':'Current image'));assert(await page.locator('[data-show="1"]').isEnabled());await page.locator('[data-show="0"]').evaluate(b=>b.onclick());assert(!await page.locator('#modal').evaluate(d=>d.open));assert(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));checks++;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ const assert=require('assert'),{chromium}=require('playwright');
|
||||
await page.route('**/api/v1/assets/local-1/star',r=>{starred=r.request().postDataJSON().starred;return r.fulfill({json:{starred,expires_at:null}});});
|
||||
page.on('request',r=>{if(r.method()==='POST'&&r.url().endsWith('/display'))writes++;});
|
||||
try{
|
||||
await page.goto('http://127.0.0.1:18787');await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();assert(await page.evaluate(()=>state.settings.mock));
|
||||
await page.goto(process.env.FRAME_FLOW_URL||'http://127.0.0.1:6005');await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();assert(await page.evaluate(()=>state.settings.mock));
|
||||
for(const lang of ['zh','en'])for(const width of [1440,390])for(const theme of ['dark','light']){
|
||||
await page.setViewportSize({width,height:1000});await page.evaluate(({lang,theme})=>{state.lang=lang;document.documentElement.dataset.theme=theme;shell();},{lang,theme});await page.locator('[data-page="gallery"]').click();await page.locator('#local-tab').click();await page.locator('[data-show="0"]').waitFor();const star=page.locator('.image-card').first().locator('button[aria-pressed]');assert(await star.count()===1);const original=await star.getAttribute('aria-pressed');await star.click();await page.waitForFunction(expected=>document.querySelector('.image-card button[aria-pressed]')?.getAttribute('aria-pressed')===expected,original==='true'?'false':'true');assert(await page.locator('[data-show="0"]').isDisabled());assert(await page.locator('[data-show="0"]').textContent()===(lang==='zh'?'当前图片':'Current image'));assert(await page.locator('[data-show="1"]').isEnabled());await page.locator('[data-show="0"]').evaluate(b=>b.onclick());assert(!await page.locator('#modal').evaluate(d=>d.open));assert(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));checks++;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
/* Mock history fixtures only; verifies long identifiers and detail actions. */
|
||||
const assert=require('assert'),{chromium}=require('playwright');
|
||||
(async()=>{const browser=await chromium.launch({headless:true,executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'});const page=await browser.newPage();const op={operation_id:'a'.repeat(64),request_id:'b'.repeat(64),action:'render',device_id:'living-room',client_id:'user:way',created_at:'2026-10-05T00:28:05Z',status:'COMPLETED',result:{asset_id:'c'.repeat(64)}};await page.route('**/api/v1/operations',r=>r.fulfill({json:[op,op]}));await page.route('**/api/v1/operations/'+op.operation_id,r=>r.fulfill({json:op}));await page.goto('http://127.0.0.1:18787');await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();let checks=0;for(const lang of ['zh','en'])for(const width of [1440,768,390])for(const theme of ['dark','light']){await page.setViewportSize({width,height:1000});await page.evaluate(({lang,theme})=>{state.lang=lang;document.documentElement.dataset.theme=theme;shell();},{lang,theme});await page.locator('[data-page="history"]').click();await page.locator('.history-actions button').first().waitFor();assert(await page.evaluate(()=>{const table=document.querySelector('.history-table');return table.querySelectorAll('th').length===7&&[...table.querySelectorAll('tbody tr')].every(row=>row.children.length===7&&row.lastElementChild.classList.contains('history-actions')&&row.lastElementChild.previousElementSibling.querySelector('button')===null)&&document.documentElement.scrollWidth<=innerWidth;}));await page.locator('.history-actions button').first().click();await page.locator('#modal[open]').waitFor();await page.locator('#modal-cancel').click();checks++;}await browser.close();console.log('History layout and detail dialog: '+checks+' passed');})().catch(e=>{console.error(e);process.exit(1)});
|
||||
(async()=>{const browser=await chromium.launch({headless:true,executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'});const page=await browser.newPage();const op={operation_id:'a'.repeat(64),request_id:'b'.repeat(64),action:'render',device_id:'living-room',client_id:'user:way',created_at:'2026-10-05T00:28:05Z',status:'COMPLETED',result:{asset_id:'c'.repeat(64)}};await page.route('**/api/v1/operations',r=>r.fulfill({json:[op,op]}));await page.route('**/api/v1/operations/'+op.operation_id,r=>r.fulfill({json:op}));await page.goto(process.env.FRAME_FLOW_URL||'http://127.0.0.1:6005');await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();let checks=0;for(const lang of ['zh','en'])for(const width of [1440,768,390])for(const theme of ['dark','light']){await page.setViewportSize({width,height:1000});await page.evaluate(({lang,theme})=>{state.lang=lang;document.documentElement.dataset.theme=theme;shell();},{lang,theme});await page.locator('[data-page="history"]').click();await page.locator('.history-actions button').first().waitFor();assert(await page.evaluate(()=>{const table=document.querySelector('.history-table');return table.querySelectorAll('th').length===7&&[...table.querySelectorAll('tbody tr')].every(row=>row.children.length===7&&row.lastElementChild.classList.contains('history-actions')&&row.lastElementChild.previousElementSibling.querySelector('button')===null)&&document.documentElement.scrollWidth<=innerWidth;}));await page.locator('.history-actions button').first().click();await page.locator('#modal[open]').waitFor();await page.locator('#modal-cancel').click();checks++;}await browser.close();console.log('History layout and detail dialog: '+checks+' passed');})().catch(e=>{console.error(e);process.exit(1)});
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/* Isolated Mock UI audit: delayed images stay inside their own placeholders. */
|
||||
const assert=require('assert');
|
||||
const fs=require('fs');
|
||||
const {chromium}=require('playwright');
|
||||
(async()=>{
|
||||
const base=process.env.FRAME_FLOW_URL;assert(base);
|
||||
const token=fs.readFileSync(process.env.FRAME_FLOW_ENV_FILE,'utf8').split('\n').find(x=>x.startsWith('FRAME_FLOW_TOKEN=')).slice(17);
|
||||
assert((await(await fetch(base+'/api/v1/settings',{headers:{Authorization:'Bearer '+token}})).json()).mock===true);
|
||||
const browser=await chromium.launch({headless:true,executablePath:process.env.CHROME_EXECUTABLE||'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'});
|
||||
try{
|
||||
const page=await browser.newPage();const errors=[];page.on('pageerror',e=>errors.push(e.message));
|
||||
await page.route('**/api/v1/devices/*/images',r=>r.fulfill({json:[{image_id:1,name:'Loading fixture',url:'/ui-audit-image.svg',current:false}]}));
|
||||
let release;let requested;
|
||||
await page.route('**/ui-audit-image.svg*',async r=>{requested();await new Promise(resolve=>release=resolve);await r.fulfill({contentType:'image/svg+xml',body:'<svg xmlns="http://www.w3.org/2000/svg" width="300" height="400"><rect width="300" height="400" fill="#167253"/></svg>'});});
|
||||
await page.goto(base);await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();
|
||||
let checks=0;
|
||||
async function centered(){
|
||||
const result=await page.locator('.image-loading').evaluate(box=>{
|
||||
const spinner=box.querySelector('.spinner'),img=box.querySelector('img'),a=box.getBoundingClientRect(),b=spinner.getBoundingClientRect();
|
||||
return {centered:Math.abs(a.x+a.width/2-b.x-b.width/2)<1&&Math.abs(a.y+a.height/2-b.y-b.height/2)<1,hidden:getComputedStyle(img).visibility==='hidden',contained:box.querySelector('.image-loading-status').offsetParent===box,overflow:document.documentElement.scrollWidth>innerWidth};
|
||||
});assert.deepStrictEqual(result,{centered:true,hidden:true,contained:true,overflow:false});checks++;
|
||||
assert.equal(await page.locator('.spinner:not(.image-loading .spinner)').count(),0);checks++;
|
||||
}
|
||||
for(const width of [1440,768,390])for(const theme of ['light','dark']){
|
||||
await page.setViewportSize({width,height:900});await page.evaluate(theme=>document.documentElement.dataset.theme=theme,theme);
|
||||
const request=new Promise(resolve=>requested=resolve);await page.evaluate(()=>navigate('gallery'));await request;await centered();
|
||||
const before=await page.locator('.image-loading').boundingBox();release();await page.waitForFunction(()=>document.querySelector('.thumb')?.naturalWidth===300&&!document.querySelector('.image-loading'));
|
||||
const after=await page.locator('.thumb').boundingBox();assert(Math.abs(before.height-after.height)<1,'Thumbnail placeholder changes height');checks++;
|
||||
const originalRequest=new Promise(resolve=>requested=resolve);await page.locator('[data-original]').click();await originalRequest;await centered();release();await page.waitForFunction(()=>document.querySelector('#original-image')?.naturalWidth===300&&!document.querySelector('.image-loading'));await page.locator('#modal-cancel').click();checks++;
|
||||
await page.evaluate(async()=>{const img=document.querySelector('.thumb');await blobImage(img,'/ui-audit-missing-image');});
|
||||
assert.equal(await page.locator('.image-load-error').count(),1);assert.equal(await page.locator('.image-load-error .spinner').count(),0);assert(await page.locator('.image-load-error').innerText());checks++;
|
||||
}
|
||||
await page.evaluate(()=>navigate('connections'));
|
||||
assert.deepStrictEqual(await page.locator('#main input,#main select,#main textarea').evaluateAll(nodes=>nodes.filter(e=>e.checkVisibility()&&!e.labels.length&&!e.getAttribute('aria-label')&&!e.getAttribute('aria-labelledby')).map(e=>e.id)),[]);checks++;
|
||||
assert.equal(await page.locator('nav [aria-current="page"]').count(),1);checks++;
|
||||
const nav=await page.locator('nav button').evaluateAll(nodes=>nodes.map(e=>({height:e.getBoundingClientRect().height,nowrap:getComputedStyle(e).whiteSpace})));assert(nav.every(e=>e.height>=44&&e.nowrap==='nowrap'));checks++;
|
||||
assert.deepStrictEqual(errors,[]);console.log('Image/UI consistency audit:',checks,'passed; desktop/tablet/mobile, light/dark; no page errors');
|
||||
}finally{await browser.close();}
|
||||
})().catch(e=>{console.error(e);process.exit(1)});
|
||||
@@ -9,16 +9,20 @@ const assert=require('assert');const fs=require('fs');const {chromium}=require('
|
||||
await page.route('**/api/v1/integrations/synology-photos',async route=>{if(route.request().method()==='POST'){assert.deepStrictEqual(route.request().postDataJSON(),{username:'fixture-nas',password:'fixture-only'});connected=true;logins++;}await route.fulfill({json:{configured:true,connected}});});
|
||||
await page.route('**/api/v1/photo-sources',async route=>{assert(route.request().method()==='GET');await route.fulfill({json:[{id:'fixture-source',name:'NAS photos / NAS 照片',kind:'local',location:'.',enabled:false,people_filter:true,orientation:'auto'}]});});
|
||||
await page.goto(base);await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();
|
||||
const viewports=process.env.FRAME_FLOW_VIEWPORTS?JSON.parse(process.env.FRAME_FLOW_VIEWPORTS):[1440,768,390].map(width=>({width,height:900}));
|
||||
let checks=0;const failures=[];const out=process.env.FRAME_FLOW_SCREENSHOT_DIR;if(out)fs.mkdirSync(out,{recursive:true});
|
||||
async function inspect(label){
|
||||
const result=await page.evaluate(()=>{
|
||||
const root=document.querySelector('dialog[open]')||document.querySelector('#main');const controls=[...root.querySelectorAll('button,select,input:not([type=checkbox]):not([type=radio]):not([type=file]),textarea')].filter(e=>e.checkVisibility({checkOpacity:true,checkVisibilityCSS:true})&&!e.closest('[hidden]')).map(e=>({id:e.id||e.textContent.trim().slice(0,24),r:e.getBoundingClientRect()}));const close=[];
|
||||
for(let i=0;i<controls.length;i++)for(let j=i+1;j<controls.length;j++){const a=controls[i].r,b=controls[j].r;const x=Math.min(a.right,b.right)-Math.max(a.left,b.left),y=Math.min(a.bottom,b.bottom)-Math.max(a.top,b.top);const dx=Math.max(a.left,b.left)-Math.min(a.right,b.right),dy=Math.max(a.top,b.top)-Math.min(a.bottom,b.bottom);if((x>1&&dy<7.5)||(y>1&&dx<7.5))close.push([controls[i].id,controls[j].id]);}
|
||||
return {overflow:document.documentElement.scrollWidth>innerWidth,close,small:innerWidth<=720?controls.filter(e=>e.r.height<43.5).map(e=>e.id):[]};
|
||||
});checks++;if(result.overflow||result.close.length||result.small.length)failures.push({label,...result});
|
||||
const dialog=document.querySelector('dialog[open]');const rect=dialog?.getBoundingClientRect();
|
||||
const clippedDialog=!!rect&&(rect.left<0||rect.right>innerWidth||rect.top<0||rect.bottom>innerHeight);
|
||||
const clippedNav=innerWidth<=720&&[...document.querySelectorAll('nav button')].some(button=>{const r=button.getBoundingClientRect();return button.scrollHeight>button.clientHeight||r.height<44;});
|
||||
return {clippedDialog,clippedNav,overflow:document.documentElement.scrollWidth>innerWidth,close,small:innerWidth<=720?controls.filter(e=>e.r.height<43.5).map(e=>e.id):[]};
|
||||
});checks++;if(result.clippedDialog||result.clippedNav||result.overflow||result.close.length||result.small.length)failures.push({label,...result});
|
||||
}
|
||||
for(const width of [1440,768,390])for(const lang of ['zh','en'])for(const theme of ['light','dark']){
|
||||
await page.setViewportSize({width,height:900});await page.evaluate(({lang,theme})=>{state.lang=lang;document.documentElement.dataset.theme=theme;shell();},{lang,theme});
|
||||
for(const {width,height} of viewports)for(const lang of ['zh','en'])for(const theme of ['light','dark']){
|
||||
await page.setViewportSize({width,height});await page.evaluate(({lang,theme})=>{state.lang=lang;document.documentElement.dataset.theme=theme;shell();},{lang,theme});
|
||||
for(const view of ['dashboard','gallery','photo-sources','upload','templates','automation','playlists','history','settings','studio','connections']){await page.evaluate(view=>navigate(view),view);await inspect(`${view}/${width}/${lang}/${theme}`);}
|
||||
await page.evaluate(()=>navigate('studio'));await page.locator('.manual-collage summary').click();await inspect(`Manual collage/${width}/${lang}/${theme}`);await page.evaluate(()=>navigate('photo-sources'));await page.locator('#photos-login').click();await inspect(`Photos login/${width}/${lang}/${theme}`);await page.locator('#modal-cancel').click();
|
||||
await page.locator('#add-photo-source').click();assert(await page.locator('#source-credential').isHidden());assert(await page.locator('#source-oauth').isHidden());await inspect(`NAS source/${width}/${lang}/${theme}`);await page.locator('#source-kind').selectOption('google-drive');assert(await page.locator('#source-oauth').isVisible());assert(await page.locator('#source-local-quality').isHidden());await inspect(`Drive source/${width}/${lang}/${theme}`);await page.locator('#modal-cancel').click();
|
||||
@@ -29,6 +33,6 @@ const assert=require('assert');const fs=require('fs');const {chromium}=require('
|
||||
if(out&&lang==='zh'&&theme==='dark'){await page.evaluate(()=>navigate('photo-sources'));await page.screenshot({path:out+`/photos-${width}.png`,fullPage:true});}
|
||||
}
|
||||
await page.evaluate(()=>navigate('photo-sources'));await page.locator('#photos-login').click();await page.locator('#photos-login-user').fill('fixture-nas');await page.locator('#photos-login-password').fill('fixture-only');await page.locator('#photos-login-form button[type=submit]').click();await page.locator('#photos-login').filter({hasText:'Sign in again'}).waitFor();assert(logins===1);checks++;
|
||||
assert.deepStrictEqual(errors,[]);assert.deepStrictEqual(failures,[],JSON.stringify(failures));console.log('Layout audit:',checks,'passed; 11 pages plus local gallery, 6 modal states, 12 viewport/language/theme combinations; console errors:',errors.length);
|
||||
assert.deepStrictEqual(errors,[]);assert.deepStrictEqual(failures,[],JSON.stringify(failures));console.log('Layout audit:',checks,'passed; 11 pages plus local gallery, 6 modal states, '+(viewports.length*4)+' viewport/language/theme combinations; console errors:',errors.length);
|
||||
}finally{await browser.close();}
|
||||
})().catch(e=>{console.error(e.message);process.exit(1)});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* All-page layout and daily-poetry checks; Mock only, no real device writes. */
|
||||
const assert=require('assert'),{chromium}=require('playwright');
|
||||
(async()=>{const browser=await chromium.launch({headless:true,executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'});const page=await browser.newPage();let unintended=0;page.on('request',r=>{if(r.method()==='POST'&&/\/(jobs|playlists|display)$/.test(r.url()))unintended++;});const base='http://127.0.0.1:18787';await page.goto(base);await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();assert(await page.evaluate(()=>state.settings.mock));let checks=0;for(const lang of ['zh','en'])for(const writable of [false,true]){const clean=await page.evaluate(({lang,writable})=>{const previous={lang:state.lang,settings:state.settings};state.lang=lang;state.settings={...state.settings,mock:false,write_enabled:writable};shell();const clean=notice()===''&&!document.querySelector('#connection')&&!document.querySelector('.sidebar-bottom .live-dot');state.lang=previous.lang;state.settings=previous.settings;shell();return clean;},{lang,writable});assert(clean,'Live-device status must not appear in either language');checks++;}for(const width of [1440,390])for(const theme of ['light','dark']){await page.setViewportSize({width,height:1000});await page.evaluate(theme=>{localStorage.setItem('ff-theme',theme);document.documentElement.dataset.theme=theme;},theme);for(const name of ['dashboard','gallery','photo-sources','upload','templates','automation','playlists','history','settings']){await page.locator(`[data-page="${name}"]`).click();await page.waitForTimeout(350);if(name==='settings')await page.locator('#image-host-form').waitFor();const result=await page.evaluate(()=>{const children=[...document.querySelector('#main').children].filter(x=>x.getBoundingClientRect().height>0);return {overflow:document.documentElement.scrollWidth>innerWidth,buttonGaps:[...document.querySelectorAll('#main button')].flatMap(b=>{const next=b.nextElementSibling;if(next?.tagName!=='BUTTON')return [];const a=b.getBoundingClientRect(),c=next.getBoundingClientRect();if(!a.width||!c.width)return [];return [Math.abs(a.top-c.top)<4?c.left-a.right:c.top-a.bottom];}),gaps:children.slice(1).map((x,i)=>x.getBoundingClientRect().top-children[i].getBoundingClientRect().bottom)};});const controls=await page.evaluate(()=>{const nodes=[...document.querySelectorAll('#main input:not([type="checkbox"]):not([type="radio"]),#main select,#main textarea,#main button')].filter(x=>{const r=x.getBoundingClientRect();return r.width&&r.height&&getComputedStyle(x).visibility!=='hidden';});const issues=[];for(let i=0;i<nodes.length;i++)for(let j=i+1;j<nodes.length;j++){const a=nodes[i].getBoundingClientRect(),b=nodes[j].getBoundingClientRect();const ox=Math.min(a.right,b.right)-Math.max(a.left,b.left),oy=Math.min(a.bottom,b.bottom)-Math.max(a.top,b.top);const dy=Math.max(a.top,b.top)-Math.min(a.bottom,b.bottom),dx=Math.max(a.left,b.left)-Math.min(a.right,b.right);if((ox>Math.min(a.width,b.width)/2&&dy>=0&&dy<8)||(oy>Math.min(a.height,b.height)/2&&dx>=0&&dx<8))issues.push({a:nodes[i].id,b:nodes[j].id,gap:ox>0?dy:dx});}return issues;});assert(controls.length===0,`${name} ${width} ${theme} controls ${JSON.stringify(controls)}`);checks++;
|
||||
(async()=>{const browser=await chromium.launch({headless:true,executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'});const page=await browser.newPage();let unintended=0;page.on('request',r=>{if(r.method()==='POST'&&/\/(jobs|playlists|display)$/.test(r.url()))unintended++;});const base=process.env.FRAME_FLOW_URL||'http://127.0.0.1:6005';await page.goto(base);await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();assert(await page.evaluate(()=>state.settings.mock));let checks=0;for(const lang of ['zh','en'])for(const writable of [false,true]){const clean=await page.evaluate(({lang,writable})=>{const previous={lang:state.lang,settings:state.settings};state.lang=lang;state.settings={...state.settings,mock:false,write_enabled:writable};shell();const clean=notice()===''&&!document.querySelector('#connection')&&!document.querySelector('.sidebar-bottom .live-dot');state.lang=previous.lang;state.settings=previous.settings;shell();return clean;},{lang,writable});assert(clean,'Live-device status must not appear in either language');checks++;}for(const width of [1440,390])for(const theme of ['light','dark']){await page.setViewportSize({width,height:1000});await page.evaluate(theme=>{localStorage.setItem('ff-theme',theme);document.documentElement.dataset.theme=theme;},theme);for(const name of ['dashboard','gallery','photo-sources','upload','templates','automation','playlists','history','settings']){await page.locator(`[data-page="${name}"]`).click();await page.waitForTimeout(350);if(name==='settings')await page.locator('#image-host-form').waitFor();const result=await page.evaluate(()=>{const children=[...document.querySelector('#main').children].filter(x=>x.getBoundingClientRect().height>0);return {overflow:document.documentElement.scrollWidth>innerWidth,buttonGaps:[...document.querySelectorAll('#main button')].flatMap(b=>{const next=b.nextElementSibling;if(next?.tagName!=='BUTTON')return [];const a=b.getBoundingClientRect(),c=next.getBoundingClientRect();if(!a.width||!c.width)return [];return [Math.abs(a.top-c.top)<4?c.left-a.right:c.top-a.bottom];}),gaps:children.slice(1).map((x,i)=>x.getBoundingClientRect().top-children[i].getBoundingClientRect().bottom)};});const controls=await page.evaluate(()=>{const nodes=[...document.querySelectorAll('#main input:not([type="checkbox"]):not([type="radio"]),#main select,#main textarea,#main button')].filter(x=>{const r=x.getBoundingClientRect();return r.width&&r.height&&getComputedStyle(x).visibility!=='hidden';});const issues=[];for(let i=0;i<nodes.length;i++)for(let j=i+1;j<nodes.length;j++){const a=nodes[i].getBoundingClientRect(),b=nodes[j].getBoundingClientRect();const ox=Math.min(a.right,b.right)-Math.max(a.left,b.left),oy=Math.min(a.bottom,b.bottom)-Math.max(a.top,b.top);const dy=Math.max(a.top,b.top)-Math.min(a.bottom,b.bottom),dx=Math.max(a.left,b.left)-Math.min(a.right,b.right);if((ox>Math.min(a.width,b.width)/2&&dy>=0&&dy<8)||(oy>Math.min(a.height,b.height)/2&&dx>=0&&dx<8))issues.push({a:nodes[i].id,b:nodes[j].id,gap:ox>0?dy:dx});}return issues;});assert(controls.length===0,`${name} ${width} ${theme} controls ${JSON.stringify(controls)}`);checks++;
|
||||
assert(result.buttonGaps.every(x=>x>=8),`${name} ${width} button gaps ${result.buttonGaps}`);assert(!result.overflow,`${name} ${width} overflow`);assert(result.gaps.every(x=>x>=16),`${name} ${width} gaps ${result.gaps}`);checks++;if(name==='templates'||name==='settings')await page.screenshot({path:`docs/screenshots/${name}-${width}-${theme}.png`,fullPage:true});}}
|
||||
await page.locator('[data-page="templates"]').click();await page.locator('[data-template="daily-poetry"]').click();await page.locator('#poem-choice').waitFor();assert(await page.locator('#poem-choice option').count()===21);checks++;await page.locator('#poem-choice').selectOption('jing-ye-si');await page.locator('#render-template').click();await page.locator('#template-image').waitFor();await page.waitForFunction(()=>document.querySelector('#template-image')?.naturalWidth===1200);checks++;await page.locator('#daily-poetry-job').click();assert(!await page.locator('#poetry-job-enabled').isChecked());checks++;const gap=await page.evaluate(()=>{const a=document.querySelector('#daily-poetry-job').getBoundingClientRect(),b=document.querySelector('#template-fields').getBoundingClientRect();return b.top-a.bottom;});assert(gap>=20,'Poetry action touches fields');checks++;await page.locator('#modal-cancel').click();await page.screenshot({path:'docs/screenshots/daily-poetry-controls-mobile-dark.png',fullPage:true});for(const width of [1440,390])for(const theme of ['light','dark']){await page.setViewportSize({width,height:1000});await page.evaluate(theme=>document.documentElement.dataset.theme=theme,theme);await page.locator('[data-page="templates"]').click();for(const id of ['calendar','daily-dashboard','daily-poetry','photo-text','quote','system-alert','weather']){await page.locator(`[data-template="${id}"]`).click();await page.waitForTimeout(250);await page.locator('#download-template').waitFor();if(id==='photo-text'){const photoGap=await page.evaluate(()=>document.querySelector('#template-photo-upload').getBoundingClientRect().top-document.querySelector('#field-photo_id').getBoundingClientRect().bottom);assert(photoGap>=12,`${width} ${theme} photo controls touch`);checks++;}const gap=await page.evaluate(()=>{const a=document.querySelector('#display-template').getBoundingClientRect(),b=document.querySelector('#download-template').getBoundingClientRect();return Math.abs(a.top-b.top)<4?b.left-a.right:b.top-a.bottom;});assert(gap>=8,`${id} ${width} preview buttons touch`);checks++;}for(const [name,button] of [['automation','new-job'],['playlists','new-playlist'],['templates','daily-poetry-job']]){await page.locator(`[data-page="${name}"]`).click();if(name==='templates'){await page.locator('[data-template="daily-poetry"]').click();await page.locator('#poem-choice').waitFor();}await page.locator('#'+button).click();const gap=await page.evaluate(()=>{const [a,b]=[...document.querySelectorAll('#modal .modal-actions button')].map(x=>x.getBoundingClientRect());return Math.abs(a.top-b.top)<4?b.left-a.right:b.top-a.bottom;});assert(gap>=8,`${name} ${width} modal buttons touch`);checks++;await page.locator('#modal-cancel').click();}}assert(unintended===0,`Cancel submitted ${unintended} requests`);checks++;await browser.close();console.log(`Layout/poetry browser: ${checks} passed (32 page/profile combinations)`);})().catch(e=>{console.error(e.message);process.exit(1)});
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
/* Local Mock only. No real official account or frame writes. */
|
||||
const assert=require('assert'),{chromium}=require('playwright');
|
||||
(async()=>{const browser=await chromium.launch({headless:true,executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'});const page=await browser.newPage();const errors=[];let frameWrites=0;page.on('pageerror',e=>errors.push(e.message));await page.route('**/api/**',route=>{const r=route.request();if(r.method()!=='GET'&&!r.url().includes('/auth/')&&!r.url().endsWith('/official/connection')){frameWrites++;return route.abort();}return route.continue();});await page.goto('http://127.0.0.1:18787');await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();assert(await page.evaluate(()=>state.settings.mock));await page.locator('[data-page="templates"]').click();await page.locator('.official-content-info summary').click();await page.waitForTimeout(300);if(await page.locator('#official-disconnect').count())await page.locator('#official-disconnect').click();await page.locator('#official-username').fill('mock-official-user');await page.locator('#official-password').fill('mock-password-not-saved');await page.locator('#official-login-form button').click();await page.locator('#official-device').waitFor();await page.locator('#official-select-form button').click();await page.locator('.official-account-panel').waitFor();await page.reload();await page.locator('.official-content-info summary').click();await page.locator('#official-disconnect').waitFor();assert((await page.locator('.official-account-panel').textContent()).includes('已关联画框'));for(const width of [1440,390]){await page.setViewportSize({width,height:1000});assert(!await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth));}await page.locator('#official-disconnect').click();await page.locator('#official-login-form').waitFor();assert(await page.locator('#official-password').inputValue()==='');assert(!await page.evaluate(()=>JSON.stringify(localStorage).includes('mock-password-not-saved')));assert(errors.length===0&&frameWrites===0);await browser.close();console.log('Official connection UI: login, discover, select, reload, disconnect, 2 sizes PASS; 0 page errors, 0 frame writes');})().catch(e=>{console.error(e.message);process.exit(1)});
|
||||
(async()=>{const browser=await chromium.launch({headless:true,executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'});const page=await browser.newPage();const errors=[];let frameWrites=0;page.on('pageerror',e=>errors.push(e.message));await page.route('**/api/**',route=>{const r=route.request();if(r.method()!=='GET'&&!r.url().includes('/auth/')&&!r.url().endsWith('/official/connection')){frameWrites++;return route.abort();}return route.continue();});await page.goto(process.env.FRAME_FLOW_URL||'http://127.0.0.1:6005');await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();assert(await page.evaluate(()=>state.settings.mock));await page.locator('[data-page="templates"]').click();await page.locator('.official-content-info summary').click();await page.waitForTimeout(300);if(await page.locator('#official-disconnect').count())await page.locator('#official-disconnect').click();await page.locator('#official-username').fill('mock-official-user');await page.locator('#official-password').fill('mock-password-not-saved');await page.locator('#official-login-form button').click();await page.locator('#official-device').waitFor();await page.locator('#official-select-form button').click();await page.locator('.official-account-panel').waitFor();await page.reload();await page.locator('.official-content-info summary').click();await page.locator('#official-disconnect').waitFor();assert((await page.locator('.official-account-panel').textContent()).includes('已关联画框'));for(const width of [1440,390]){await page.setViewportSize({width,height:1000});assert(!await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth));}await page.locator('#official-disconnect').click();await page.locator('#official-login-form').waitFor();assert(await page.locator('#official-password').inputValue()==='');assert(!await page.evaluate(()=>JSON.stringify(localStorage).includes('mock-password-not-saved')));assert(errors.length===0&&frameWrites===0);await browser.close();console.log('Official connection UI: login, discover, select, reload, disconnect, 2 sizes PASS; 0 page errors, 0 frame writes');})().catch(e=>{console.error(e.message);process.exit(1)});
|
||||
|
||||
@@ -7,7 +7,7 @@ const assert=require('assert'),{chromium}=require('playwright');
|
||||
await page.route('**/api/v1/integrations/synology-photos',route=>route.fulfill({json:{configured:true,connected:true}}));
|
||||
await page.route('**/api/v1/integrations/synology-photos/people',route=>route.fulfill({json:[{id:1,name:'Family A',count:20,named:true},{id:2,name:'Family B',count:15,named:true},{id:3,name:'3',count:2,named:false}]}));
|
||||
try{
|
||||
await page.goto('http://127.0.0.1:18787');await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();assert(await page.evaluate(()=>state.settings.mock));
|
||||
await page.goto(process.env.FRAME_FLOW_URL||'http://127.0.0.1:6005');await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();assert(await page.evaluate(()=>state.settings.mock));
|
||||
for(const lang of ['zh','en'])for(const width of [1440,390])for(const theme of ['dark','light']){
|
||||
await page.setViewportSize({width,height:1000});await page.evaluate(({lang,theme})=>{state.lang=lang;document.documentElement.dataset.theme=theme;shell();},{lang,theme});await page.locator('[data-page="photo-sources"]').click();await page.locator('#add-photo-source').click();await page.locator('#source-id').fill(id);await page.locator('#source-name').fill('People fixture');await page.locator('#source-people-load').click();await page.locator('[data-person="1"]').waitFor();assert(await page.locator('[data-person]').count()===2);await page.locator('#people-unnamed').check();assert(await page.locator('[data-person]').count()===3);await page.locator('#people-search').fill('Family B');assert(await page.locator('[data-person]').count()===1);await page.locator('[data-person="2"]').check();await page.locator('#source-people-filter').check();await page.locator('#source-person-match').selectOption('all');assert(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));
|
||||
if(checks===0){await page.locator('#photo-source-form button[type="submit"]').click();await page.locator(`[data-source-edit="${id}"]`).waitFor();const saved=await page.evaluate(async id=>(await api('/api/v1/photo-sources')).find(s=>s.id===id),id);assert(saved.people_filter&&saved.person_match==='all'&&saved.person_ids.join(',')==='2');await page.locator(`[data-source-edit="${id}"]`).click();assert(await page.locator('#source-people-filter').isChecked());assert(await page.locator('#source-person-match').inputValue()==='all');}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
/* Local mock server only; no vendor/cloud writes. PHOTO_ROOT must contain a fixture image. */
|
||||
const assert=require('assert'),{chromium}=require('playwright');
|
||||
(async()=>{const browser=await chromium.launch({headless:true,executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'});const page=await browser.newPage();await page.goto('http://127.0.0.1:18787');await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();assert(await page.evaluate(()=>state.settings.mock));const id='ui-photo-'+Date.now();let jobId;let checks=0;try{await page.locator('[data-page="photo-sources"]').click();await page.locator('#add-photo-source').click();await page.locator('#source-id').fill(id);await page.locator('#source-name').fill('UI photo fixture');await page.locator('#source-location').fill('.');await page.locator('#source-orientation').selectOption('landscape');await page.locator('#source-organized').check();await page.locator('#source-caption').check();await page.locator('#source-synology-previews').check();await page.locator('#photo-source-form button[type="submit"]').click();await page.locator(`[data-source-browse="${id}"]`).waitFor();for(const lang of ['zh','en'])for(const width of [1440,390])for(const theme of ['dark','light']){await page.setViewportSize({width,height:1000});await page.evaluate(({lang,theme})=>{state.lang=lang;document.documentElement.dataset.theme=theme;shell();},{lang,theme});await page.locator('[data-page="photo-sources"]').click();await page.locator(`[data-source-browse="${id}"]`).click();await page.locator('[data-source-item]').first().waitFor();assert(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));await page.locator(`[data-source-edit="${id}"]`).click();assert(await page.locator('#source-id').inputValue()===id);assert(await page.locator('#source-orientation').inputValue()==='landscape');assert(await page.locator('#source-organized').isChecked());assert(await page.locator('#source-caption').isChecked());assert(await page.locator('#source-synology-previews').isChecked());await page.locator('#modal-cancel').click();checks++;}await page.locator(`[data-source-random="${id}"]`).click();await page.locator('#source-preview').waitFor();await page.waitForFunction(()=>document.querySelector('#source-preview')?.naturalWidth>0);assert(await page.locator('#source-preview').evaluate(img=>img.naturalWidth===1600&&img.naturalHeight===1200));const before=await page.evaluate(()=>api('/api/v1/devices/'+state.device));await page.locator('#modal-cancel').click();await page.locator(`[data-source-job="${id}"]`).click();assert(!await page.locator('#source-job-enabled').isChecked());await page.locator('#source-job-name').fill(id);await page.locator('#source-job-form button[type="submit"]').click();await page.locator('#new-job').waitFor();jobId=await page.evaluate(async id=>(await api('/api/v1/jobs')).find(j=>j.name===id).id,id);await page.evaluate(async jobId=>{const job=(await api('/api/v1/jobs')).find(j=>j.id===jobId);await jobEditor(job);},jobId);await page.locator('#job-photo-source').waitFor();assert(await page.locator('#job-form').textContent().then(x=>x.includes('最新成功图')||x.includes('latest confirmed')));assert(await page.locator('#job-content-type').inputValue()==='photo_source');assert(await page.locator('#job-photo-source').inputValue()===id);await page.locator('#modal-cancel').click();const after=await page.evaluate(()=>api('/api/v1/devices/'+state.device));assert(before.current_content.id===after.current_content.id);checks+=3;console.log('Photo sources browser: '+checks+' passed (8 language/size/theme profiles, preview and scheduler editing)');}finally{await page.evaluate(async({id,jobId})=>{if(jobId)await api('/api/v1/jobs/'+jobId,{method:'DELETE'});await api('/api/v1/photo-sources/'+id,{method:'DELETE'});},{id,jobId});await browser.close();}})().catch(e=>{console.error(e);process.exit(1)});
|
||||
(async()=>{const browser=await chromium.launch({headless:true,executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'});const page=await browser.newPage();await page.goto(process.env.FRAME_FLOW_URL||'http://127.0.0.1:6005');await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();assert(await page.evaluate(()=>state.settings.mock));const id='ui-photo-'+Date.now();let jobId;let checks=0;try{await page.locator('[data-page="photo-sources"]').click();await page.locator('#add-photo-source').click();await page.locator('#source-id').fill(id);await page.locator('#source-name').fill('UI photo fixture');await page.locator('#source-location').fill('.');await page.locator('#source-orientation').selectOption('landscape');await page.locator('#source-organized').check();await page.locator('#source-caption').check();await page.locator('#source-synology-previews').check();await page.locator('#photo-source-form button[type="submit"]').click();await page.locator(`[data-source-browse="${id}"]`).waitFor();for(const lang of ['zh','en'])for(const width of [1440,390])for(const theme of ['dark','light']){await page.setViewportSize({width,height:1000});await page.evaluate(({lang,theme})=>{state.lang=lang;document.documentElement.dataset.theme=theme;shell();},{lang,theme});await page.locator('[data-page="photo-sources"]').click();await page.locator(`[data-source-browse="${id}"]`).click();await page.locator('[data-source-item]').first().waitFor();assert(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));await page.locator(`[data-source-edit="${id}"]`).click();assert(await page.locator('#source-id').inputValue()===id);assert(await page.locator('#source-orientation').inputValue()==='landscape');assert(await page.locator('#source-organized').isChecked());assert(await page.locator('#source-caption').isChecked());assert(await page.locator('#source-synology-previews').isChecked());await page.locator('#modal-cancel').click();checks++;}await page.locator(`[data-source-random="${id}"]`).click();await page.locator('#source-preview').waitFor();await page.waitForFunction(()=>document.querySelector('#source-preview')?.naturalWidth>0);assert(await page.locator('#source-preview').evaluate(img=>img.naturalWidth===1600&&img.naturalHeight===1200));const before=await page.evaluate(()=>api('/api/v1/devices/'+state.device));await page.locator('#modal-cancel').click();await page.locator(`[data-source-job="${id}"]`).click();assert(!await page.locator('#source-job-enabled').isChecked());await page.locator('#source-job-name').fill(id);await page.locator('#source-job-form button[type="submit"]').click();await page.locator('#new-job').waitFor();jobId=await page.evaluate(async id=>(await api('/api/v1/jobs')).find(j=>j.name===id).id,id);await page.evaluate(async jobId=>{const job=(await api('/api/v1/jobs')).find(j=>j.id===jobId);await jobEditor(job);},jobId);await page.locator('#job-photo-source').waitFor();assert(await page.locator('#job-form').textContent().then(x=>x.includes('最新成功图')||x.includes('latest confirmed')));assert(await page.locator('#job-content-type').inputValue()==='photo_source');assert(await page.locator('#job-photo-source').inputValue()===id);await page.locator('#modal-cancel').click();const after=await page.evaluate(()=>api('/api/v1/devices/'+state.device));assert(before.current_content.id===after.current_content.id);checks+=3;console.log('Photo sources browser: '+checks+' passed (8 language/size/theme profiles, preview and scheduler editing)');}finally{await page.evaluate(async({id,jobId})=>{if(jobId)await api('/api/v1/jobs/'+jobId,{method:'DELETE'});await api('/api/v1/photo-sources/'+id,{method:'DELETE'});},{id,jobId});await browser.close();}})().catch(e=>{console.error(e);process.exit(1)});
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
/* Local mock server only; no vendor/cloud writes. PHOTO_ROOT must contain a fixture image. */
|
||||
const assert=require('assert'),{chromium}=require('playwright');
|
||||
(async()=>{const browser=await chromium.launch({headless:true,executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'});const page=await browser.newPage();await page.goto('http://127.0.0.1:18787');await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();assert(await page.evaluate(()=>state.settings.mock));const id='ui-photo-'+Date.now();let jobId;let checks=0;try{await page.locator('[data-page="photo-sources"]').click();await page.locator('#add-photo-source').click();await page.locator('#source-id').fill(id);await page.locator('#source-name').fill('UI photo fixture');await page.locator('#source-location').fill('.');await page.locator('#source-orientation').selectOption('landscape');await page.locator('#source-organized').check();await page.locator('#source-caption').check();await page.locator('#source-synology-previews').check();await page.locator('#photo-source-form button[type="submit"]').click();await page.locator(`[data-source-browse="${id}"]`).waitFor();for(const lang of ['zh','en'])for(const width of [1440,390])for(const theme of ['dark','light']){await page.setViewportSize({width,height:1000});await page.evaluate(({lang,theme})=>{state.lang=lang;document.documentElement.dataset.theme=theme;shell();},{lang,theme});await page.locator('[data-page="photo-sources"]').click();await page.locator(`[data-source-browse="${id}"]`).click();await page.locator('[data-source-item]').first().waitFor();assert(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));await page.locator(`[data-source-edit="${id}"]`).click();assert(await page.locator('#source-id').inputValue()===id);assert(await page.locator('#source-orientation').inputValue()==='landscape');assert(await page.locator('#source-organized').isChecked());assert(await page.locator('#source-caption').isChecked());assert(await page.locator('#source-synology-previews').isChecked());await page.locator('#modal-cancel').click();checks++;}await page.locator(`[data-source-random="${id}"]`).click();await page.locator('#source-preview').waitFor();await page.waitForFunction(()=>document.querySelector('#source-preview')?.naturalWidth>0);assert(await page.locator('#source-preview').evaluate(img=>img.naturalWidth===1600&&img.naturalHeight===1200));const before=await page.evaluate(()=>api('/api/v1/devices/'+state.device));await page.locator('#modal-cancel').click();await page.locator(`[data-source-job="${id}"]`).click();assert(!await page.locator('#source-job-enabled').isChecked());await page.locator('#source-job-name').fill(id);await page.locator('#source-job-form button[type="submit"]').click();await page.locator('#new-job').waitFor();jobId=await page.evaluate(async id=>(await api('/api/v1/jobs')).find(j=>j.name===id).id,id);await page.evaluate(async jobId=>{const job=(await api('/api/v1/jobs')).find(j=>j.id===jobId);await jobEditor(job);},jobId);await page.locator('#job-photo-source').waitFor();assert(await page.locator('#job-content-type').inputValue()==='photo_source');assert(await page.locator('#job-photo-source').inputValue()===id);await page.locator('#modal-cancel').click();const after=await page.evaluate(()=>api('/api/v1/devices/'+state.device));assert(before.current_content.id===after.current_content.id);checks+=3;console.log('Photo sources browser: '+checks+' passed (8 language/size/theme profiles, preview and scheduler editing)');}finally{await page.evaluate(async({id,jobId})=>{if(jobId)await api('/api/v1/jobs/'+jobId,{method:'DELETE'});await api('/api/v1/photo-sources/'+id,{method:'DELETE'});},{id,jobId});await browser.close();}})().catch(e=>{console.error(e);process.exit(1)});
|
||||
(async()=>{const browser=await chromium.launch({headless:true,executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'});const page=await browser.newPage();await page.goto(process.env.FRAME_FLOW_URL||'http://127.0.0.1:6005');await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();assert(await page.evaluate(()=>state.settings.mock));const id='ui-photo-'+Date.now();let jobId;let checks=0;try{await page.locator('[data-page="photo-sources"]').click();await page.locator('#add-photo-source').click();await page.locator('#source-id').fill(id);await page.locator('#source-name').fill('UI photo fixture');await page.locator('#source-location').fill('.');await page.locator('#source-orientation').selectOption('landscape');await page.locator('#source-organized').check();await page.locator('#source-caption').check();await page.locator('#source-synology-previews').check();await page.locator('#photo-source-form button[type="submit"]').click();await page.locator(`[data-source-browse="${id}"]`).waitFor();for(const lang of ['zh','en'])for(const width of [1440,390])for(const theme of ['dark','light']){await page.setViewportSize({width,height:1000});await page.evaluate(({lang,theme})=>{state.lang=lang;document.documentElement.dataset.theme=theme;shell();},{lang,theme});await page.locator('[data-page="photo-sources"]').click();await page.locator(`[data-source-browse="${id}"]`).click();await page.locator('[data-source-item]').first().waitFor();assert(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));await page.locator(`[data-source-edit="${id}"]`).click();assert(await page.locator('#source-id').inputValue()===id);assert(await page.locator('#source-orientation').inputValue()==='landscape');assert(await page.locator('#source-organized').isChecked());assert(await page.locator('#source-caption').isChecked());assert(await page.locator('#source-synology-previews').isChecked());await page.locator('#modal-cancel').click();checks++;}await page.locator(`[data-source-random="${id}"]`).click();await page.locator('#source-preview').waitFor();await page.waitForFunction(()=>document.querySelector('#source-preview')?.naturalWidth>0);assert(await page.locator('#source-preview').evaluate(img=>img.naturalWidth===1600&&img.naturalHeight===1200));const before=await page.evaluate(()=>api('/api/v1/devices/'+state.device));await page.locator('#modal-cancel').click();await page.locator(`[data-source-job="${id}"]`).click();assert(!await page.locator('#source-job-enabled').isChecked());await page.locator('#source-job-name').fill(id);await page.locator('#source-job-form button[type="submit"]').click();await page.locator('#new-job').waitFor();jobId=await page.evaluate(async id=>(await api('/api/v1/jobs')).find(j=>j.name===id).id,id);await page.evaluate(async jobId=>{const job=(await api('/api/v1/jobs')).find(j=>j.id===jobId);await jobEditor(job);},jobId);await page.locator('#job-photo-source').waitFor();assert(await page.locator('#job-content-type').inputValue()==='photo_source');assert(await page.locator('#job-photo-source').inputValue()===id);await page.locator('#modal-cancel').click();const after=await page.evaluate(()=>api('/api/v1/devices/'+state.device));assert(before.current_content.id===after.current_content.id);checks+=3;console.log('Photo sources browser: '+checks+' passed (8 language/size/theme profiles, preview and scheduler editing)');}finally{await page.evaluate(async({id,jobId})=>{if(jobId)await api('/api/v1/jobs/'+jobId,{method:'DELETE'});await api('/api/v1/photo-sources/'+id,{method:'DELETE'});},{id,jobId});await browser.close();}})().catch(e=>{console.error(e);process.exit(1)});
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/* Mock-only: known image metadata renders real slots; no synthetic skeletons. */
|
||||
const assert=require('assert'),fs=require('fs'),{chromium}=require('playwright');
|
||||
(async()=>{
|
||||
const base=process.env.FRAME_FLOW_URL;assert(base);
|
||||
const token=fs.readFileSync(process.env.FRAME_FLOW_ENV_FILE,'utf8').split('\n').find(x=>x.startsWith('FRAME_FLOW_TOKEN=')).slice(17);
|
||||
assert((await(await fetch(base+'/api/v1/settings',{headers:{Authorization:'Bearer '+token}})).json()).mock===true);
|
||||
const browser=await chromium.launch({headless:true,executablePath:process.env.CHROME_EXECUTABLE||'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'});
|
||||
try{
|
||||
const page=await browser.newPage();const errors=[];page.on('pageerror',e=>errors.push(e.message));
|
||||
let releaseAccount;await page.route('**/api/v1/auth/me',async r=>{await new Promise(resolve=>releaseAccount=resolve);await r.continue();});
|
||||
await page.goto(base);await page.locator('#nav button').first().waitFor();assert.equal(await page.locator('.image-pending,.placeholder-line,.page-placeholder').count(),0);assert.equal(await page.locator('.page-loading').count(),0);releaseAccount();
|
||||
await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();
|
||||
let checks=1;
|
||||
for(const width of [1440,390]){
|
||||
await page.setViewportSize({width,height:844});await page.evaluate(()=>state.galleryCache?.clear());let releaseMetadata,metadataRequested,releaseImage,imageRequested;
|
||||
await page.route('**/api/v1/devices/*/images',async r=>{metadataRequested();await new Promise(resolve=>releaseMetadata=resolve);await r.fulfill({json:[{image_id:1,name:'Progressive fixture',url:'/progressive-fixture.svg',current:false}]});});
|
||||
await page.route('**/progressive-fixture.svg*',async r=>{assert.equal(await page.locator('#gallery-search').count(),1,'Search must precede image requests');assert(await page.locator('#gallery-search').evaluate(e=>typeof e.oninput==='function'));imageRequested();await new Promise(resolve=>releaseImage=resolve);await r.fulfill({contentType:'image/svg+xml',body:'<svg xmlns="http://www.w3.org/2000/svg" width="300" height="400"/>'});});
|
||||
const metadata=new Promise(resolve=>metadataRequested=resolve);const image=new Promise(resolve=>imageRequested=resolve);
|
||||
await page.evaluate(()=>{window.navigation=navigate('gallery');});await metadata;
|
||||
assert.equal(await page.locator('.image-pending,.placeholder-line,.page-placeholder').count(),0);assert.equal(await page.locator('.image-card').count(),0);assert.equal(await page.locator('.page-loading').count(),0);checks++;
|
||||
releaseMetadata();await image;await page.evaluate(()=>window.navigation);
|
||||
assert(await page.locator('#gallery-upload').isEnabled());assert.equal(await page.locator('.image-loading .spinner').count(),1);assert.equal(await page.locator('.image-pending').count(),0);checks++;
|
||||
assert(await page.locator('.image-loading').evaluate(box=>{const a=box.getBoundingClientRect(),b=box.querySelector('.spinner').getBoundingClientRect();return Math.abs(a.x+a.width/2-b.x-b.width/2)<1&&Math.abs(a.y+a.height/2-b.y-b.height/2)<1;}));checks++;
|
||||
releaseImage();await page.waitForFunction(()=>document.querySelector('.thumb')?.naturalWidth===300&&!document.querySelector('.image-loading'));checks++;
|
||||
await page.evaluate(()=>navigate('upload'));
|
||||
const cachedMetadata=new Promise(resolve=>metadataRequested=resolve);const cachedImage=new Promise(resolve=>imageRequested=resolve);
|
||||
await page.evaluate(()=>{window.navigation=navigate('gallery');});await cachedMetadata;await cachedImage;
|
||||
assert.equal(await page.locator('#gallery-search').count(),1,'Search must exist while cached metadata refresh is pending');assert(await page.locator('#gallery-search').evaluate(e=>typeof e.oninput==='function'),'Search must be interactive');assert.equal(await page.locator('.image-card').count(),1);assert.equal(await page.locator('.image-loading .spinner').count(),1);assert.equal(await page.locator('.image-pending,.placeholder-line,.page-placeholder').count(),0);checks++;
|
||||
releaseMetadata();await page.evaluate(()=>window.navigation);releaseImage();await page.waitForFunction(()=>document.querySelector('.thumb')?.naturalWidth===300&&!document.querySelector('.image-loading'));
|
||||
|
||||
await page.unroute('**/api/v1/devices/*/images');await page.unroute('**/progressive-fixture.svg*');
|
||||
let releaseDevice,deviceRequested;await page.route('**/api/v1/devices/living-room',async r=>{deviceRequested();await new Promise(resolve=>releaseDevice=resolve);await r.continue();});
|
||||
const device=new Promise(resolve=>deviceRequested=resolve);await page.evaluate(()=>{window.navigation=navigate('dashboard');});await device;
|
||||
assert.equal(await page.locator('.image-pending,.placeholder-line,.page-placeholder').count(),0);assert.equal(await page.locator('.page-loading').count(),0);checks++;
|
||||
releaseDevice();await page.evaluate(()=>window.navigation);await page.unroute('**/api/v1/devices/living-room');
|
||||
}
|
||||
let releaseJobs,jobsRequested;await page.route('**/api/v1/jobs',async r=>{jobsRequested();await new Promise(resolve=>releaseJobs=resolve);await r.fulfill({json:[]});});
|
||||
await page.route('**/api/v1/assets?*',r=>r.fulfill({json:[{asset_id:'local-fixture',name:'Local fixture',url:'/local-fixture.svg',origin:'manual',starred:false}]}));
|
||||
await page.route('**/local-fixture.svg',r=>r.fulfill({contentType:'image/svg+xml',body:'<svg xmlns="http://www.w3.org/2000/svg" width="300" height="400"/>'}));
|
||||
const jobs=new Promise(resolve=>jobsRequested=resolve);await page.evaluate(()=>{state.tab='local';return navigate('gallery');});await jobs;
|
||||
assert.equal(await page.locator('.asset-select').count(),1);assert(await page.locator('#gallery-search').evaluate(e=>typeof e.oninput==='function'));await page.locator('#gallery-search').fill('no-match');assert(await page.locator('.image-card').evaluate(e=>e.hidden));releaseJobs();checks++;
|
||||
assert.deepEqual(errors,[]);console.log('Known image positions without skeletons:',checks,'checks PASS; delayed bootstrap, metadata and images; no page errors');
|
||||
}finally{await browser.close();}
|
||||
})().catch(e=>{console.error(e.message);process.exit(1)});
|
||||
@@ -2,7 +2,7 @@
|
||||
const fs=require('fs');const path=require('path');const assert=require('assert');const {chromium}=require('playwright');
|
||||
(async()=>{
|
||||
const uploadName='browser-upload-'+Date.now();const root=path.resolve(__dirname,'..');const env=fs.readFileSync(process.env.FRAME_FLOW_ENV_FILE||path.join(root,'.env'),'utf8');const token=env.split('\n').find(x=>x.startsWith('FRAME_FLOW_TOKEN=')).slice('FRAME_FLOW_TOKEN='.length);
|
||||
const base=process.env.FRAME_FLOW_URL||'http://127.0.0.1:18787';const response=await fetch(base+'/api/v1/settings',{headers:{Authorization:'Bearer '+token}});const settings=await response.json();assert(settings.mock===true,'UI smoke requires Mock mode');
|
||||
const base=process.env.FRAME_FLOW_URL||'http://127.0.0.1:6005';const response=await fetch(base+'/api/v1/settings',{headers:{Authorization:'Bearer '+token}});const settings=await response.json();assert(settings.mock===true,'UI smoke requires Mock mode');
|
||||
const browser=await chromium.launch({headless:true,executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'});
|
||||
const context=await browser.newContext({viewport:{width:1440,height:1000}});const page=await context.newPage();const errors=[];page.on('pageerror',e=>errors.push(e.message.replaceAll(token,'[REDACTED]')));let checks=0;
|
||||
await page.addInitScript(()=>Object.defineProperty(window.crypto,'randomUUID',{value:undefined,configurable:true}));await page.goto(base);await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();assert(await page.locator('h2').first().innerText()==='客厅相框');checks++;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/* Mock-only editor regression: parallel image requests, stable regions and live controls. */
|
||||
const assert=require('assert'),fs=require('fs'),{chromium}=require('playwright');
|
||||
(async()=>{
|
||||
const base=process.env.FRAME_FLOW_URL;assert(base);const token=fs.readFileSync(process.env.FRAME_FLOW_ENV_FILE,'utf8').split('\n').find(x=>x.startsWith('FRAME_FLOW_TOKEN=')).slice(17);
|
||||
assert((await(await fetch(base+'/api/v1/settings',{headers:{Authorization:'Bearer '+token}})).json()).mock===true);
|
||||
const browser=await chromium.launch({headless:true,executablePath:process.env.CHROME_EXECUTABLE||'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'});
|
||||
try{
|
||||
const page=await browser.newPage();const errors=[];page.on('pageerror',e=>errors.push(e.message));
|
||||
await page.route('**/api/v1/assets?*',r=>r.fulfill({json:[{asset_id:'studio-fixture-a',name:'A'},{asset_id:'studio-fixture-b',name:'B'}]}));
|
||||
await page.route('**/api/v1/layout/plan',r=>r.fulfill({json:{width:1200,height:1600,regions:[{asset_id:'studio-fixture-a',x:20,y:200,width:550,height:900},{asset_id:'studio-fixture-b',x:620,y:200,width:550,height:900}]}}));
|
||||
let requests,releases,failSecond=false;
|
||||
await page.route('**/api/v1/assets/studio-fixture-*',async r=>{const key=r.request().url().split('-').pop();requests.add(key);await new Promise(resolve=>releases[key]=resolve);await r.fulfill({contentType:'image/svg+xml',body:failSecond&&key==='b'?'invalid image':'<svg xmlns="http://www.w3.org/2000/svg" width="300" height="400"><rect width="300" height="400" fill="#167253"/></svg>'});});
|
||||
await page.goto(base);await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();let checks=0;
|
||||
for(const width of [1440,390])for(const theme of ['light','dark']){
|
||||
requests=new Set();releases={};await page.setViewportSize({width,height:900});await page.evaluate(theme=>{document.documentElement.dataset.theme=theme;return navigate('studio');},theme);await page.locator('.manual-collage summary').click();
|
||||
await page.locator('#studio-assets input').nth(0).check();await page.locator('#studio-assets input').nth(1).check();await page.locator('#studio-plan').click();
|
||||
await page.waitForFunction(()=>document.querySelectorAll('.studio-region .spinner').length===2);for(let n=0;n<100&&requests.size<2;n++)await page.waitForTimeout(20);assert.equal(requests.size,2,'Both requests must start before either finishes');assert(await page.locator('#studio-render').isEnabled());checks++;
|
||||
assert(await page.locator('.studio-region').evaluateAll(regions=>regions.every(region=>{const a=region.getBoundingClientRect(),b=region.querySelector('.spinner').getBoundingClientRect();return Math.abs(a.x+a.width/2-b.x-b.width/2)<1&&Math.abs(a.y+a.height/2-b.y-b.height/2)<1;})));checks++;
|
||||
const field=page.locator('[data-region="0"][data-key="x"]');await field.fill('40');await field.dispatchEvent('input');assert(await page.locator('.studio-region').first().evaluate(e=>Math.abs(parseFloat(e.style.left)-40/1200*100)<0.01));checks++;
|
||||
const before=await page.locator('.studio-region').nth(1).boundingBox();releases.b();await page.waitForFunction(()=>document.querySelectorAll('.studio-region')[1].querySelector('img').naturalWidth===300&&!document.querySelectorAll('.studio-region')[1].querySelector('.image-loading'));
|
||||
assert.equal(await page.locator('.studio-region').first().locator('.spinner').count(),1);assert.deepEqual(await page.locator('.studio-region').nth(1).boundingBox(),before);checks++;
|
||||
const region=page.locator('.studio-region').nth(1);await region.scrollIntoViewIfNeeded();const point=await region.boundingBox();const old=await page.locator('[data-region="1"][data-key="x"]').inputValue();await page.mouse.move(point.x+point.width/2,point.y+Math.min(point.height/2,80));await page.mouse.down();await page.mouse.move(point.x+point.width/2-10,point.y+Math.min(point.height/2,80)+5,{steps:3});await page.mouse.up();assert.notEqual(await page.locator('[data-region="1"][data-key="x"]').inputValue(),old);checks++;
|
||||
releases.a();await page.waitForFunction(()=>document.querySelectorAll('.studio-region img').length===2&&[...document.querySelectorAll('.studio-region img')].every(img=>img.naturalWidth===300)&&!document.querySelector('.studio-region .image-loading'));checks++;
|
||||
assert(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));checks++;
|
||||
}
|
||||
requests=new Set();releases={};failSecond=true;await page.locator('#studio-plan').click();for(let n=0;n<100&&requests.size<2;n++)await page.waitForTimeout(20);assert.equal(requests.size,2);
|
||||
releases.b();await page.locator('.studio-region .image-load-error').waitFor();assert.equal(await page.locator('.studio-region').first().locator('.spinner').count(),1);assert(await page.locator('#studio-render').isEnabled());checks++;
|
||||
const errorField=page.locator('[data-region="1"][data-key="x"]');await errorField.fill('600');await errorField.dispatchEvent('input');assert.equal(await page.locator('.studio-region').nth(1).evaluate(e=>e.style.left),'50%');checks++;
|
||||
releases.a();await page.waitForFunction(()=>document.querySelector('.studio-region img')?.naturalWidth===300);assert.equal(await page.locator('.studio-region .spinner').count(),0);checks++;
|
||||
assert.deepEqual(errors,[]);console.log('Studio image loading:',checks,'checks PASS; parallel requests, independent completion, local spinners, coordinate editing and dragging; no page errors');
|
||||
}finally{await browser.close();}
|
||||
})().catch(e=>{console.error(e.message);process.exit(1)});
|
||||
@@ -4,7 +4,7 @@ const assert=require('assert'),{chromium}=require('playwright');
|
||||
const browser=await chromium.launch({headless:true,executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'});
|
||||
const page=await browser.newPage();const errors=[];page.on('pageerror',e=>errors.push(e.message));let status='AUTH_REQUIRED',checks=0;
|
||||
await page.route('**/api/v1/devices/*/widgets',route=>route.fulfill({json:{source:status==='AUTH_REQUIRED'?'snapshot':'live',configured:status!=='AUTH_REQUIRED',status,observed_at:'2026-10-02',catalog:[],current:null,current_verified:status==='NO_WIDGET',writable:false}}));
|
||||
await page.goto('http://127.0.0.1:18787');await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();
|
||||
await page.goto(process.env.FRAME_FLOW_URL||'http://127.0.0.1:6005');await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();
|
||||
for(const lang of ['zh','en'])for(const value of ['AUTH_REQUIRED','NO_WIDGET','CURRENT_UNVERIFIED']){
|
||||
status=value;await page.evaluate(lang=>{state.lang=lang;return navigate('templates');},lang);
|
||||
await page.locator('.widget-auth-notice').waitFor({state:'attached'});
|
||||
|
||||
Reference in New Issue
Block a user