Files

108 lines
10 KiB
JavaScript
Raw Permalink Normal View History

2026-09-24 14:35:05 +03:00
const {openSetup}=require('./support/browser.cjs');
// End-to-end coverage for scheduling contacts, country imports, working hours, and display timezones.
2026-09-24 16:10:08 +03:00
2026-09-24 14:35:05 +03:00
const assert=require('node:assert/strict');
const fs=require('node:fs');
const base=process.env.MEGURI_BROWSER_URL || 'http://127.0.0.1:3000';
assert(['localhost','127.0.0.1'].includes(new URL(base).hostname));
fs.mkdirSync('test-results',{recursive:true});
(async()=>{
2026-09-24 16:10:08 +03:00
const browser=await require('./support/browser.cjs').launchBrowser();
2026-09-24 14:35:05 +03:00
const page=await browser.newPage({viewport:{width:1440,height:1100},timezoneId:'America/Los_Angeles'});
page.setDefaultTimeout(10000);const errors=[];page.on('pageerror',e=>errors.push(e.message));
const dialog=()=>page.getByRole('dialog');
const save=async()=>{await dialog().getByRole('button',{name:'Save changes',exact:true}).click();await dialog().waitFor({state:'hidden'});};
const state=()=>page.evaluate(()=>{const app=Alpine.$data(document.body);return JSON.parse(JSON.stringify({workspace:app.workspace,user:app.user,endpoint:app.endpoint(),displayZone:app.displayZone}));});
try {
const label='Calendars '+Date.now();
await page.goto(base);
await page.getByRole('button',{name:'New here? Create an account'}).click();
await page.getByLabel('Username',{exact:true}).fill('calendars-'+Date.now());
await page.getByLabel('Display name',{exact:true}).fill('Calendar tester');
await page.getByLabel('Password',{exact:true}).fill('local-browser-test-password');
2026-10-07 15:30:31 +01:00
await page.getByLabel('Email address',{exact:true}).fill('browser-'+Date.now()+'-'+Math.random().toString(16).slice(2)+'@example.com');await page.getByRole('button',{name:'Create account',exact:true}).click();
2026-09-24 14:35:05 +03:00
await page.getByRole('button',{name:'New workspace',exact:true}).waitFor();
await page.getByRole('button',{name:'New workspace',exact:true}).click();
await dialog().getByLabel('Name',{exact:true}).fill(label);await save();
await page.getByRole('button',{name:'New workspace',exact:true}).click();
await dialog().getByLabel('Name',{exact:true}).fill(label);await save();
await openSetup(page,'Workspace settings');
await page.getByRole('button',{name:'+ Add teammate'}).click();await dialog().getByLabel('Registered username').fill('bob');await save();
await openSetup(page,'Schedules');
await page.locator('.schedule-create > summary').click();
await page.getByRole('button',{name:'+ Rotation',exact:true}).click();
await dialog().getByLabel('Schedule name').fill('Distributed rotation');
await dialog().getByLabel('Handover time').fill('09:00');
await dialog().getByLabel('Remove participant').first().click();
await dialog().getByRole('button',{name:'+ Add participant · name or email'}).click();
const picker=()=>page.getByRole('region',{name:'Choose a person'});
await picker().getByLabel('Search workspace users or enter email').fill('Bob');
await picker().locator('.picker-option').filter({hasText:'Bob Chen'}).click();
await dialog().getByRole('button',{name:'+ Add participant · name or email'}).click();
await picker().getByLabel('Search workspace users or enter email').fill('never-logged-in@example.com');
assert.equal(await picker().getByLabel('Country',{exact:true}).inputValue(),'CY');
await picker().getByLabel('Name (optional)',{exact:true}).fill('Ani in Armenia');
await picker().getByLabel('Country',{exact:true}).selectOption('AM');
await picker().getByRole('button',{name:'Add never-logged-in@example.com',exact:true}).click();
await page.screenshot({path:'test-results/rotation-people.png',fullPage:true});await save();
let current=await state();assert.equal(await page.getByLabel('Workspace',{exact:true}).inputValue(),current.workspace.id);const contact=current.workspace.document.people.find(p=>p.email==='never-logged-in@example.com');
assert.equal(contact.user_id,null);assert.equal(contact.country,'AM');assert.equal(current.workspace.document.schedules[0].handover,'09:00:00');
await page.getByRole('button',{name:'+ Absence',exact:true}).click();
await dialog().locator('.person-select').click();
await picker().getByLabel('Search workspace users or enter email').fill('absence-only@example.com');
await picker().getByRole('button',{name:'Add absence-only@example.com',exact:true}).click();await save();
current=await state();assert(current.workspace.document.absences.some(a=>a.user_id===current.workspace.document.people.find(p=>p.email==='absence-only@example.com').id));
await page.locator('#public-holidays').evaluate(el=>el.open=true);await page.getByRole('button',{name:'Import CSV',exact:true}).click();
const csv='country,date,name\nCY,2027-01-01,New Year\nAM,2027-01-01,"New Year, holiday"';
const before=current.workspace.revision;
await dialog().getByLabel('Holiday CSV').fill(csv);
await dialog().getByText('New holidays: 2',{exact:true}).waitFor();
let remote=await page.evaluate(async endpoint=>fetch(endpoint).then(r=>r.json()),current.endpoint);
assert.equal(remote.workspace.revision,before,'Preview must not write');assert.equal(remote.workspace.document.holidays.length,0);
await dialog().getByLabel('Holiday CSV').fill(csv+'\nXX,2027-01-01,Invalid country');
await dialog().locator('.validation-error').waitFor();assert(await dialog().getByRole('button',{name:'Import new holidays'}).isDisabled());
await dialog().getByLabel('Holiday CSV').fill(csv);await dialog().getByText('New holidays: 2',{exact:true}).waitFor();
await page.screenshot({path:'test-results/holiday-import.png',fullPage:true});
await dialog().getByRole('button',{name:'Import new holidays'}).click();await dialog().waitFor({state:'hidden'});
await page.locator('.holiday-group summary').filter({hasText:'Cyprus'}).waitFor();await page.locator('.holiday-group summary').filter({hasText:'Armenia'}).waitFor();
await page.locator('#public-holidays').evaluate(el=>el.open=true);await page.getByRole('button',{name:'Import CSV',exact:true}).click();
await dialog().getByLabel('Holiday CSV').fill(csv);await dialog().getByText('2 already saved',{exact:true}).waitFor();assert(await dialog().getByRole('button',{name:'Import new holidays'}).isDisabled());
await dialog().getByRole('button',{name:'Cancel',exact:true}).click();
await openSetup(page,'Workspace settings');
await page.getByRole('button',{name:'Edit working interval'}).click();
await dialog().getByLabel('Workspace IANA timezone').fill('Asia/Yerevan');
await dialog().getByLabel('Working day starts').fill('10:00');await dialog().getByLabel('Working day ends').fill('18:00');await save();
current=await state();assert.equal(current.workspace.document.timezone,'Asia/Yerevan');assert.equal(current.workspace.document.working_interval.start,'10:00:00');
await openSetup(page,'Escalations & routing');await page.getByRole('button',{name:'+ Escalation policy',exact:true}).click();
await dialog().getByLabel('Policy name').fill('Primary then backup');
await dialog().getByRole('button',{name:'+ Escalation step'}).click();await save();
await page.getByRole('button',{name:'+ Routing rule',exact:true}).click();
await dialog().getByLabel('Rule name').fill('Production work hours');
await dialog().getByLabel('Escalation policy').selectOption({label:'Primary then backup'});
await dialog().getByRole('button',{name:'+ AND condition'}).click();await dialog().getByLabel('Value',{exact:true}).fill('env:prod');
await dialog().getByLabel('Only during working hours').check();
await dialog().getByText(/Mon, Tue, Wed, Thu, Fri · 10:00–18:00 · Asia\/Yerevan/).waitFor();
assert.equal(await dialog().locator('input[type=time]').count(),0,'Working interval is edited at workspace level');
await page.screenshot({path:'test-results/routing-editor.png',fullPage:true});await save();
await page.screenshot({path:'test-results/policy-flows.png',fullPage:true});
current=await state();const schedule=JSON.stringify(current.workspace.document.schedules),revision=current.workspace.revision;
assert.equal(current.displayZone,'America/Los_Angeles','Follow browser timezone by default');
await page.locator('.account-button').click();
assert.equal(await dialog().getByLabel('Country of work').inputValue(),'CY');
assert((await dialog().getByLabel('Country of work').locator('option').count())>=249);
await dialog().getByLabel('Display timezone',{exact:true}).fill('Asia/Tokyo');await save();
current=await state();assert.equal(current.displayZone,'Asia/Tokyo');assert.equal(current.workspace.revision,revision);assert.equal(JSON.stringify(current.workspace.document.schedules),schedule);
await page.getByRole('link', { name: 'Calendar',exact:true}).click();await page.locator('.day-clock').first().filter({hasText:'14:00 GMT+9'}).waitFor();
await page.locator('.account-button').click();await dialog().getByRole('button',{name:'Use browser timezone'}).click();await save();
current=await state();assert.equal(current.displayZone,'America/Los_Angeles');assert.equal(current.workspace.revision,revision);
await openSetup(page,'Schedules');await page.locator('#public-holidays').evaluate(el=>el.open=true);await page.getByRole('button',{name:'Import CSV',exact:true}).click();
await dialog().getByLabel('Holiday CSV').fill(csv+'\nCY,2027-12-25,Christmas');await dialog().getByText('New holidays: 1',{exact:true}).waitFor();
await page.setViewportSize({width:390,height:844});await page.screenshot({path:'test-results/holiday-import-mobile.png',fullPage:true});
assert(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth),'Mobile import should not overflow');
assert.deepEqual(errors,[]);
console.log('PASS: scoped user picker, email rotation/absence, country CSV preview/import/validation/dedup, workspace interval, policy flows, browser/account display timezone, mobile import');
} catch(error) {
console.error('Browser errors:',errors);console.error('Visible alert:',await page.locator('[role=alert]').textContent());await page.screenshot({path:'test-results/calendars-failure.png',fullPage:true});throw error;
} finally {await browser.close();}
})().catch(error=>{console.error(error);process.exitCode=1;});