Files
meguri/tests/browser-navigation.cjs
T
ab a47c4a0398
Checks / test (push) Failing after 12m20s
Init
2026-09-24 14:35:05 +03:00

26 lines
6.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const {chromium}=require('playwright'),assert=require('node:assert/strict'),fs=require('node:fs');
const base=process.env.MEGURI_BROWSER_URL||'http://127.0.0.1:3000';assert(['127.0.0.1','localhost'].includes(new URL(base).hostname));
(async()=>{const browser=await chromium.launch({channel:process.env.MEGURI_BROWSER_CHANNEL||'chrome',headless:true});const errors=[];let page;try{
const context=await browser.newContext({locale:'en-US',viewport:{width:1440,height:1000}});page=await context.newPage();page.setDefaultTimeout(12000);page.on('pageerror',e=>errors.push(e.message));
await page.goto(base+'/?view=policies');await page.getByLabel('Username',{exact:true}).fill('alice');await page.getByLabel('Password',{exact:true}).fill('meguri-demo-only');await page.getByRole('button',{name:'Sign in',exact:true}).click();await page.waitForFunction(()=>Alpine.$data(document.body).workspace);await page.getByRole('heading',{name:'Schedule setup',exact:true}).waitFor();
const matrix=await page.evaluate(()=>Alpine.$data(document.body).selected);const support=await page.evaluate(()=>Alpine.$data(document.body).directoryState.workspaces.find(t=>t.id!==Alpine.$data(document.body).selected).id);
assert.equal(new URL(page.url()).searchParams.get('view'),'configuration');await page.reload();await page.getByRole('heading',{name:'Schedule setup',exact:true}).waitFor();
await page.getByRole('link',{name:'Requests',exact:true}).click();await page.waitForURL(/view=requests/);await page.getByLabel('Workspace',{exact:true}).selectOption(support);await page.waitForFunction(id=>Alpine.$data(document.body).workspace.id===id,support);assert.equal(new URL(page.url()).searchParams.get('workspace'),support);
await page.goBack();await page.waitForFunction(id=>Alpine.$data(document.body).workspace.id===id,matrix);assert.equal(await page.evaluate(()=>Alpine.$data(document.body).view),'requests');await page.goBack();await page.getByRole('heading',{name:'Schedule setup',exact:true}).waitFor();await page.goForward();await page.getByRole('heading',{name:'Requests',exact:true}).waitFor();
const href=await page.getByRole('link',{name:'Activity',exact:true}).getAttribute('href');const tab=await context.newPage();await tab.goto(base+href);await tab.getByRole('heading',{name:'Activity',exact:true}).waitFor();assert.equal(await tab.evaluate(()=>Alpine.$data(document.body).selected),matrix);await tab.close();
// Locale is a saved display preference; use the real account form, then a fresh browser locale.
await page.locator('.account-button').click();await page.getByLabel('Language',{exact:true}).selectOption('ru');await page.getByRole('dialog').getByRole('button',{name:'Save changes',exact:true}).click();await page.waitForFunction(()=>document.documentElement.lang==='ru');await page.reload();await page.waitForFunction(()=>document.documentElement.lang==='ru' && Alpine.$data(document.body).workspace);await page.screenshot({path:'test-results/localization-ru.png',fullPage:true});
const snapshot=await page.evaluate(async()=>{const a=Alpine.$data(document.body);return {revision:a.workspace.revision,schedules:JSON.stringify(a.doc.schedules),user:a.user};});
const fr=await browser.newContext({locale:'fr-CA',storageState:await context.storageState(),viewport:{width:390,height:844}});const fp=await fr.newPage();fp.on('pageerror',e=>errors.push(e.message));await fp.goto(base+'/?view=configuration&workspace='+matrix);await fp.waitForFunction(()=>document.documentElement.lang==='ru' && Alpine.$data(document.body).workspace);
await fp.evaluate(async()=>{const a=Alpine.$data(document.body);await a.api('/api/account','PUT',{...a.user,language:null});});await fp.reload();await fp.waitForFunction(()=>document.documentElement.lang==='fr' && Alpine.$data(document.body).workspace);await fp.getByRole('heading',{name:'Configuration des plannings',exact:true}).waitFor();assert.equal(await fp.evaluate(()=>Alpine.$data(document.body).workspace.revision),snapshot.revision);assert.equal(await fp.evaluate(()=>JSON.stringify(Alpine.$data(document.body).doc.schedules)),snapshot.schedules);assert(await fp.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));await fp.screenshot({path:'test-results/localization-fr-mobile.png',fullPage:true});
assert.equal(await fp.evaluate(()=>I18n.diagnostic('Choose an existing workspace variable')),'Choisissez une variable existante de l’espace');assert.equal(await fp.evaluate(()=>I18n.translate('coverage_to_arrange',{count:'<b>{person}</b>'},'fr')),'Intervalles sans remplacement : <b>{person}</b>');
// Invalid preference cannot be persisted.
const status=await fp.evaluate(async()=>{const a=Alpine.$data(document.body);return (await fetch('/api/account',{method:'PUT',headers:{'Content-Type':'application/json','X-CSRF-Token':a.csrf},body:JSON.stringify({...a.user,language:'xx'})})).status;});assert.equal(status,422);
await fr.close();
for(const [locale,expected]of [['ru-RU','ru'],['de-DE','en'],['fr-CA','fr']]){const cx=await browser.newContext({locale});const p=await cx.newPage();await p.goto(base);await p.waitForFunction(()=>Alpine.$data(document.body).ready);assert.equal(await p.locator('html').getAttribute('lang'),expected);await cx.close();}
// Parse templates rather than rendered text: built-in copy must be catalog-backed.
for(const file of fs.readdirSync('static').filter(f=>f.endsWith('.html'))){const source=fs.readFileSync('static/'+file,'utf8');const literals=await page.evaluate(source=>{const doc=new DOMParser().parseFromString(source,'text/html'),out=[];const visit=node=>{for(const n of node.childNodes){if(n.nodeType===3 && n.textContent.trim())out.push(n.textContent.trim());if(n.nodeType===1){if(n.tagName==='TEMPLATE')visit(n.content);else if(!['SCRIPT','STYLE'].includes(n.tagName))visit(n);}}};visit(doc);return out;},source);assert.deepEqual(literals,[],file+' contains inline text');}
const catalog=JSON.parse(fs.readFileSync('static/locales/messages.json','utf8'));const placeholders=s=>[...s.matchAll(/\{(\w+)\}/g)].map(m=>m[1]).sort();for(const [key,entry]of Object.entries(catalog))for(const lang of ['en','ru','fr']){assert(entry[lang],key+': '+lang);assert.deepEqual(placeholders(entry[lang]),placeholders(entry.en),key);}
assert.deepEqual(errors,[]);console.log('PASS: menu links, reload, cross-team Back/Forward, new tab, language persistence and browser fallback, translated validation, unchanged schedules, mobile, complete catalog, no inline template copy');
}finally{await browser.close();}})().catch(e=>{console.error(e);process.exit(1)});