2026-10-24 00:38:00 +08:00
/* Current gallery terminology and upload flow; isolated Mock only. */
2026-10-16 21:34:48 +08:00
const assert = require ( 'assert' ), fs = require ( 'fs' ), vm = require ( 'vm' ),{ chromium } = require ( 'playwright' );
2026-10-24 00:38:00 +08:00
const translations = {}; translations . window = translations ; vm . createContext ( translations ); vm . runInContext ( fs . readFileSync ( 'pigway_frame_flow/web/i18n.js' , 'utf8' ), translations );
for ( const file of [ 'app.js' , 'features.js' , 'extensions.js' , 'libraries.js' , 'source-browser.js' ]) for ( const match of fs . readFileSync ( 'pigway_frame_flow/web/' + file , 'utf8' ). matchAll ( /\bt\('([^']+)'\)/g )) for ( const lang of [ 'zh' , 'en' ]) assert ( translations . MESSAGES [ lang ][ match [ 1 ]], ` ${ file } : missing ${ lang } . ${ match [ 1 ] } ` );
( async ()=>{
const base = process . env . FRAME_FLOW_URL ; assert ( base );
const token = process . env . FRAME_FLOW_TOKEN || fs . readFileSync ( process . env . FRAME_FLOW_ENV_FILE , 'utf8' ). split ( '\n' ). find ( x => x . startsWith ( 'FRAME_FLOW_TOKEN=' )). slice ( 17 );
const headers = { Authorization : 'Bearer ' + token }; const request = async ( path , method = 'GET' , body )=>{ const response = await fetch ( base + path ,{ method , headers : {... headers ,...( body ? { 'Content-Type' : 'application/json' } : {})}, body : body ? JSON . stringify ( body ) : undefined }); assert ( response . ok , ` ${ path } : ${ response . status } ` ); return response . status === 204 ? null : response . json ();};
assert (( await request ( '/api/v1/settings' )). 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 ({ extraHTTPHeaders : headers }), errors = [];
page . on ( 'pageerror' , e => errors . push ( e . message )); await page . route ( '**/api/v1/auth/me' , r => r . fulfill ({ json : { username : 'fixture' , csrf_token : '' }}));
await page . goto ( base + '/#upload' ); await page . locator ( '#upload-source' ). waitFor ();
const png = Buffer . from ( process . env . FRAME_FLOW_FIXTURE_BASE64 , 'base64' ); assert ( png . length );
await page . locator ( '#upload-source' ). selectOption ( 'manual-uploads' );
await page . locator ( '#file' ). setInputFiles ({ name : 'Audit fixture.png' , mimeType : 'image/png' , buffer : png });
assert ( await page . locator ( '#save-local' ). isEnabled ()); await page . locator ( '#save-local' ). click (); await page . locator ( '#view-upload-library' ). waitFor ();
await page . locator ( '#view-upload-library' ). click (); await page . locator ( '[data-library-preview="0"]' ). first (). waitFor ();
assert . equal ( await page . locator ( '#library-source' ). inputValue (), 'manual-uploads' ); assert ( await page . locator ( '#library-import' ). textContent (). then ( x => x . includes ( '上传' ))); assert ( await page . locator ( '#device' ). isHidden ());
const pictures = await request ( '/api/v1/source-libraries/manual-uploads/images' ); assert . equal ( pictures . total , 1 );
const plan = await request ( '/api/v1/generation-plans' , 'POST' ,{ source_id : 'manual-uploads' , name : 'UI fixture watch' , width : 410 , height : 502 });
await request ( '/api/v1/source-images/' + pictures . items [ 0 ]. id + '/generate' , 'POST' ,{ plan_id : plan . id });
await request ( '/api/v1/library-terminals/watch/binding' , 'PUT' ,{ source_id : 'manual-uploads' , plan_id : plan . id });
for ( const lang of [ 'zh' , 'en' ]) for ( const width of [ 1440 , 390 ]){
await page . setViewportSize ({ width , height : 1000 }); await page . evaluate ( async lang =>{ state . lang = lang ; await navigate ( 'gallery' );}, lang ); await page . locator ( '#library-source' ). waitFor ();
const tabs = page . locator ( '[data-library-plan]' ); assert ( await tabs . count () >= 2 ); await page . locator ( '[data-library-plan="' + plan . id + '"]' ). click (); await page . locator ( '[data-library-settings="0"]' ). waitFor ();
assert ( ! ( await page . locator ( '#main' ). innerText ()). includes ( '本地图库' )); assert ( await page . locator ( '#device' ). isHidden ()); assert (( await page . locator ( '#footer' ). innerText ()). includes ( lang === 'zh' ? '独立智能' : 'Independent smart' ));
await page . evaluate (()=> navigate ( 'terminal-gallery' )); await page . locator ( '[data-library-terminal="watch"]' ). click (); await page . locator ( '[data-terminal-send="0"]' ). waitFor (); assert ( await page . locator ( '[data-terminal-send="0"]' ). isDisabled ());
await page . evaluate (()=> navigate ( 'upload' )); await page . locator ( '#upload-source' ). waitFor (); assert . equal ( await page . locator ( '#save-local' ). innerText (), lang === 'zh' ? '保存源画面到图库' : 'Save source pixels to library' );
await page . evaluate (()=> navigate ( 'settings' )); await page . locator ( '#storage-policy' ). waitFor (); assert ( await page . locator ( '#main' ). innerText (). then ( x => x . includes ( lang === 'zh' ? '磁盘可用' : 'Disk available' )));
assert ( await page . evaluate (()=> document . documentElement . scrollWidth <= innerWidth ), ` ${ lang } ${ width } : overflow` );
}
await page . evaluate (()=> navigate ( 'connections' )); await page . locator ( '#client-form' ). waitFor (); assert . equal ( await page . locator ( 'input[name="client-scope"][value="images:write"]' ). count (), 1 );
const sources = await request ( '/api/v1/source-libraries' ), nas = sources . find ( s => s . kind === 'synology-photos' ); assert ( nas );
await page . route ( '**/api/v1/source-libraries/' + nas . id + '/browse?*' , r => r . fulfill ({ json : { items : [], next_offset : new URL ( r . request (). url ()). searchParams . get ( 'offset' ) === '0' ? 40 : null , scanned : 40 , paged_provider : true }}));
await page . evaluate ( id => openSourceBrowser ( id ), nas . id ); await page . locator ( '#browse-next' ). waitFor ({ state : 'visible' }); await page . waitForFunction (()=> ! document . querySelector ( '#browse-next' ). disabled ); assert (( await page . locator ( '#browse-gallery' ). innerText ()). includes ( 'does not mean' )); await page . locator ( '#browse-next' ). click (); await page . waitForFunction (()=> document . querySelector ( '#browse-gallery' ). textContent . includes ( 'End of source' )); assert ( await page . locator ( '#browse-next' ). isDisabled ());
if ( process . env . FRAME_FLOW_UI_SCREENSHOT_DIR ){ fs . mkdirSync ( process . env . FRAME_FLOW_UI_SCREENSHOT_DIR ,{ recursive : true }); await page . screenshot ({ path : process . env . FRAME_FLOW_UI_SCREENSHOT_DIR + '/browser-empty-mobile.png' , fullPage : true }); await page . evaluate (()=> navigate ( 'gallery' )); await page . locator ( '#library-source' ). waitFor (); await page . screenshot ({ path : process . env . FRAME_FLOW_UI_SCREENSHOT_DIR + '/source-upload-mobile.png' , fullPage : true });}
assert . deepEqual ( errors ,[]); console . log ( 'PASS: upload → source library → plan generation → terminal, bilingual desktop/mobile copy, disk capacity, client scopes and sparse-search continuation' );
} finally { await browser . close ();}
})(). catch ( e =>{ console . error ( e . message ); process . exit ( 1 )});