Files
aia-website/www/tools/service-portal-browser.mjs

464 lines
32 KiB
JavaScript
Raw Permalink 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.
/**
* Guarded consultation-only Form A browser regression.
* Run after a normal live build. Every Jotform request is
* fulfilled or aborted locally; no Jotform request is transmitted.
*/
import { randomBytes } from 'node:crypto';
import { encryptPortalStream, portalAlgorithm } from '../src/utils/portal-crypto.mjs';
import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import http from 'node:http';
import net from 'node:net';
import path from 'node:path';
import { spawn } from 'node:child_process';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { setTimeout as delay } from 'node:timers/promises';
const forms = process.argv.find(a => a.startsWith('--forms='))?.split('=')[1] ?? 'live';
assert.ok(['live', 'off', 'mock'].includes(forms));
const browserName = process.argv.find(a => a.startsWith('--browser='))?.split('=')[1] ?? 'chromium';
assert.ok(['chromium', 'firefox'].includes(browserName));
const root = fileURLToPath(new URL('../', import.meta.url));
const moduleName = process.env.PLAYWRIGHT_MODULE
? pathToFileURL(path.resolve(process.env.PLAYWRIGHT_MODULE)).href
: 'playwright';
const output = await fs.mkdtemp('/tmp/aia-service-portal-');
const routes = [
{ route: '/client-consultation', locale: 'en', drawer: false, screenshot: 'client-consultation' },
{ route: '/es/client-consultation', locale: 'es', drawer: false, screenshot: 'es-client-consultation' },
{ route: '/schedule-consultation', locale: 'en', drawer: false, screenshot: 'schedule-consultation' },
{ route: '/aba-therapy', locale: 'en', drawer: true, screenshot: 'aba-therapy' },
{ route: '/autism-evaluations', locale: 'en', drawer: true, screenshot: 'autism-evaluations' },
{ route: '/learner-social-club', locale: 'en', drawer: true, screenshot: 'learner-social-club' },
{ route: '/es/aba-therapy', locale: 'es', drawer: true, screenshot: 'es-aba-therapy' },
{ route: '/es/autism-evaluations', locale: 'es', drawer: true, screenshot: 'es-autism-evaluations' },
{ route: '/es/learner-social-club', locale: 'es', drawer: true, screenshot: 'es-learner-social-club' },
];
const widths = [320, 390, 768, 1440];
const evidence = { forms, browserName, routes, widths, checks: 0, blocked: [], jotformAttempts: [], resourceRequests: [], screenshots: [], fallbackChecks: [], status: 'failed' };
const reserve = net.createServer();
await new Promise((resolve, reject) => { reserve.once('error', reject); reserve.listen(0, '127.0.0.1', resolve); });
const port = reserve.address().port;
await new Promise((resolve) => reserve.close(resolve));
const origin = 'http://127.0.0.1:' + port;
let browser;
let preview;
function expectedUrl(locale) {
return 'https://form.jotform.com/262946078314059?formContext=consultation&language=' + locale;
}
try {
const drivers = await import(moduleName);
const driver = drivers[browserName];
const executablePath = browserName === 'chromium' ? process.env.CHROMIUM_EXECUTABLE : process.env.FIREFOX_EXECUTABLE;
browser = await driver.launch(executablePath
? { headless: true, executablePath }
: { headless: true });
evidence.browser = browser.version();
preview = spawn(process.execPath, ['node_modules/astro/bin/astro.mjs', 'preview', '--host', '127.0.0.1', '--port', String(port)], {
cwd: root,
env: { ...process.env, ASTRO_TELEMETRY_DISABLED: '1' },
stdio: 'ignore',
});
const reachable = () => new Promise((resolve) => {
const request = http.get(origin + '/', (response) => { response.resume(); resolve(response.statusCode === 200); });
request.setTimeout(500, () => request.destroy());
request.on('error', () => resolve(false));
});
for (let i = 0; i < 60 && !(await reachable()); i++) await delay(250);
assert.equal(await reachable(), true, 'Loopback preview did not become reachable');
const key = 'aia:service-inquiry:auto-suppressed-until';
const duration = 30 * 86400000;
const staffCode = randomBytes(24).toString('hex');
const managementCode = randomBytes(24).toString('hex');
const staffResourceName = 'a'.repeat(64) + '.pdf';
const managementResourceName = 'b'.repeat(64) + '.docx';
const staffResourcePath = `/staff-resources/${staffResourceName}`;
const managementResourcePath = `/management-resources/${managementResourceName}`;
const styledFixture = '<section><h2>Synthetic Directory</h2><h3>Operational reports</h3><p><a class="hs-button arbitrary-legacy-class" style="position:fixed" onclick="window.portalUnsafe=true" href="https://example.invalid/report">Synthetic report</a></p><ul><li>Synthetic list item</li></ul><table><caption>Fixture directory</caption><thead><tr><th scope="col">Role</th><th scope="col">Contact</th></tr></thead><tbody><tr><td>Fixture role</td><td><a href="mailto:fixture@example.invalid">Fixture email</a></td></tr></tbody></table><p><a style="background-color:#ffbd59" href="https://example.invalid/second">Second fixture report</a></p><p><a class="unrecognized-report-legacy" href="https://example.invalid/third">Operations Monthly Lineup Report - PM</a></p><a href="javascript:alert(1)">Unsafe destination</a><iframe src="https://example.invalid/frame"></iframe><form action="https://example.invalid/post"><input></form></section>';
const staffTemplates = '<section><h2>Management Templates</h2><ul><li><div><a href="https://example.invalid/check-in">Client Check-In &amp; Check-Out</a></div></li><li><div><a href="https://example.invalid/employee-performance">Employee Performance Template</a></div></li><li><div><a href="https://example.invalid/employee-availability">Employee Availability Template</a></div></li></ul></section><section><h2>Other Staff Resources</h2><p><a href="https://example.invalid/resource">General staff resource</a></p><p><a href="mailto:staff@example.invalid">Staff email</a></p></section>';
const payload = { alg: portalAlgorithm, streams: [
await encryptPortalStream('b', `<h2>Synthetic Staff</h2><p>Staff fixture only</p><a href="${staffResourcePath}">Staff's Safety &amp; Training: Résumé (2026)!</a><script>window.portalUnsafe=true</script><img src="https://example.invalid/image">` + styledFixture + staffTemplates, staffCode),
await encryptPortalStream('a', `<h2>Synthetic Management</h2><p>Management fixture only</p><a class="hs-button" href="${managementResourcePath}">Management – Policies / Q&amp;A #1</a>` + styledFixture, managementCode),
] };
const expectedAuthorization = {
staff: 'Basic ' + Buffer.from('staff:' + staffCode, 'utf8').toString('base64'),
management: 'Basic ' + Buffer.from('management:' + managementCode, 'utf8').toString('base64'),
};
const guard = async (context, inject = false) => {
await context.routeWebSocket('**/*', (socket) => { evidence.blocked.push({ method: 'WEBSOCKET' }); socket.close(); });
await context.route('**/*', async (route) => {
const req = route.request(), url = new URL(req.url());
if (url.origin === origin && ['GET', 'HEAD'].includes(req.method())) {
const resource = /^\/(staff|management)-resources\/([a-f0-9]{64})[.](pdf|docx)$/.exec(url.pathname);
if (resource) {
const role = resource[1];
const authorized = req.headers().authorization === expectedAuthorization[role];
evidence.resourceRequests.push({ role, authorized });
if (!authorized) return route.fulfill({ status: 401, headers: { 'www-authenticate': `Basic realm="AIA ${role} resources"`, 'cache-control': 'private, no-store' } });
return route.fulfill({
status: 200,
contentType: resource[3] === 'pdf' ? 'application/pdf' : 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
headers: { 'cache-control': 'private, no-store' },
body: resource[3] === 'pdf' ? `%PDF-1.7\nSynthetic ${role} document\n` : `Synthetic ${role} document\n`,
});
}
if (inject && url.pathname === '/employee-portal') {
const response = await route.fetch();
const html = (await response.text()).replace(/(<script[^>]*data-portal-payload[^>]*>)[\s\S]*?(<\/script>)/, '$1' + JSON.stringify(payload) + '$2');
return route.fulfill({ response, body: html });
}
return route.continue();
}
evidence.blocked.push({ method: req.method(), origin: url.origin, path: url.pathname });
if (url.origin === 'https://form.jotform.com') {
evidence.jotformAttempts.push({ method: req.method(), url: url.href });
return route.fulfill({ contentType: 'text/html', body: '<!doctype html><title>Local fixture</title><label>Fixture contact <input></label><button type="button">Fixture Submit</button>' });
}
return route.abort();
});
};
if (forms === 'live' && !process.argv.includes('--portal-only') && !process.argv.includes('--correction-only')) {
for (const width of widths) {
for (const target of routes.filter((r) => r.drawer)) {
const context = await browser.newContext({ viewport: { width, height: 844 }, serviceWorkers: 'block', reducedMotion: 'reduce' });
await guard(context);
const page = await context.newPage();
await page.clock.install({ time: new Date('2026-10-29T12:00:00Z') }); await page.clock.pauseAt(new Date('2026-10-29T12:00:01Z'));
await page.goto(origin + target.route, { waitUntil: 'domcontentloaded' });
const dialog = page.locator('[data-service-inquiry-drawer]');
const frame = dialog.locator('iframe');
const before = evidence.jotformAttempts.length;
assert.equal(await frame.getAttribute('src'), null);
await page.clock.runFor(3999);
assert.equal(await dialog.evaluate((d) => d.open), false);
await page.clock.runFor(100);
assert.equal(await dialog.evaluate((d) => d.open), true);
assert.equal(await frame.getAttribute('src'), expectedUrl(target.locale));
await page.waitForFunction(() => document.querySelector('iframe[data-jotform-iframe-frame]')?.src.includes('consultation'));
assert.equal(await page.locator('[data-service-inquiry-trigger]').count(), 1);
const heading = await dialog.locator('h2').textContent();
assert.ok(target.locale === 'es' ? /Conozca|preguntas/.test(heading) : /Explore|Questions/.test(heading));
assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1));
await page.screenshot({ path: path.join(output, target.screenshot + '-' + width + '.png'), fullPage: true });
evidence.screenshots.push(target.screenshot + '-' + width + '.png');
await page.keyboard.press('Escape');
await page.clock.runFor(100);
assert.equal(await dialog.evaluate((d) => d.open), false);
const expiry = await page.evaluate((k) => Number(localStorage.getItem(k)), key);
const now = await page.evaluate(() => Date.now());
assert.ok(expiry > now && expiry <= now + duration);
await page.clock.runFor(10000);
await page.locator('[data-service-inquiry-trigger]').focus();
await page.keyboard.press('Enter');
await page.clock.runFor(100);
assert.equal(await dialog.evaluate((d) => d.open), true);
assert.equal(await frame.count(), 1);
await page.locator('[data-service-inquiry-close]').click();
await page.clock.runFor(100);
assert.equal(await page.evaluate(() => document.activeElement?.hasAttribute('data-service-inquiry-trigger')), true);
const renewed = await page.evaluate((k) => Number(localStorage.getItem(k)), key);
assert.ok(renewed > expiry);
assert.ok(evidence.jotformAttempts.length <= before + 1);
evidence.checks++;
await context.close();
}
}
// Scroll wins, suppression crosses locale/service navigation, manual opening bypasses it.
const context = await browser.newContext({ serviceWorkers: 'block', reducedMotion: 'reduce' });
await guard(context);
const page = await context.newPage(); await page.clock.install({ time: new Date('2026-10-29T12:00:00Z') }); await page.clock.pauseAt(new Date('2026-10-29T12:00:01Z'));
await page.goto(origin + '/aba-therapy', { waitUntil: 'domcontentloaded' });
await page.evaluate(() => scrollTo({ top: (document.documentElement.scrollHeight - innerHeight) * .6, behavior: 'instant' }));
await page.clock.runFor(100);
assert.equal(await page.locator('dialog').evaluate((d) => d.open), true);
await page.keyboard.press('Escape'); await page.clock.runFor(100);
await page.goto(origin + '/es/learner-social-club', { waitUntil: 'domcontentloaded' });
await page.clock.runFor(5000);
await page.evaluate(() => scrollTo({ top: document.documentElement.scrollHeight, behavior: 'instant' })); await page.clock.runFor(100);
assert.equal(await page.locator('dialog').evaluate((d) => d.open), false);
assert.equal(await page.locator('dialog iframe').getAttribute('src'), null);
await page.locator('[data-service-inquiry-trigger]').click(); await page.clock.runFor(100);
assert.equal(await page.locator('dialog').evaluate((d) => d.open), true);
await page.keyboard.press('Escape'); await page.clock.runFor(100);
// A second page-load initialization disposes previous listeners without duplicating activation.
await page.evaluate(() => document.dispatchEvent(new Event('astro:page-load')));
await page.locator('[data-service-inquiry-trigger]').click(); await page.clock.runFor(100);
assert.equal(await page.locator('dialog iframe').count(), 1);
evidence.checks += 2;
await context.close();
for (const preference of ['expired', 'invalid', 'denied', 'editing', 'short', 'other-modal']) {
const ctx = await browser.newContext({ serviceWorkers: 'block', reducedMotion: 'reduce' }); await guard(ctx);
await ctx.addInitScript(({ preference, key }) => {
if (preference === 'expired') localStorage.setItem(key, '1');
if (preference === 'invalid') localStorage.setItem(key, 'broken');
if (preference === 'denied') Object.defineProperty(window, 'localStorage', { get() { throw new Error('Denied'); } });
}, { preference, key });
const p = await ctx.newPage(); await p.clock.install({ time: new Date('2026-10-29T12:00:00Z') }); await p.clock.pauseAt(new Date('2026-10-29T12:00:01Z')); await p.goto(origin + '/aba-therapy', { waitUntil: 'domcontentloaded' });
if (preference === 'editing') await p.evaluate(() => { const field = document.createElement('input'); document.body.prepend(field); field.focus(); });
if (preference === 'other-modal') await p.evaluate(() => { const d = document.createElement('dialog'); document.body.append(d); d.showModal(); });
if (preference === 'short') {
await p.evaluate(() => { document.querySelectorAll('main > :not([data-service-inquiry-drawer]), header, footer').forEach((e) => e instanceof HTMLElement && (e.style.display = 'none')); });
await p.clock.runFor(100);
assert.equal(await p.locator('[data-service-inquiry-drawer]').evaluate((d) => d.open), false);
}
await p.clock.runFor(4100);
assert.equal(await p.locator('[data-service-inquiry-drawer]').evaluate((d) => d.open), !['editing', 'other-modal'].includes(preference), preference);
evidence.checks++; await ctx.close();
}
const backContext = await browser.newContext({ serviceWorkers: 'block', reducedMotion: 'reduce' });
await guard(backContext);
const backPage = await backContext.newPage();
await backPage.goto(origin + '/aba-therapy', { waitUntil: 'domcontentloaded' });
await backPage.evaluate(() => {
window.dispatchEvent(new PageTransitionEvent('pagehide', { persisted: true }));
window.dispatchEvent(new PageTransitionEvent('pageshow', { persisted: true }));
});
assert.equal(await backPage.evaluate((storageKey) => localStorage.getItem(storageKey), key), null);
await backPage.locator('[data-service-inquiry-trigger]').click();
assert.equal(await backPage.locator('dialog iframe').getAttribute('src'), expectedUrl('en'));
evidence.checks++;
await backContext.close();
}
if (forms === 'live' && !process.argv.includes('--portal-only')) {
// Completion messages originate from a locally fulfilled cross-origin frame.
// No fixture submits anything; malformed browser envelopes are injected only in tests.
for (const locale of ['en', 'es']) {
const ctx = await browser.newContext({ serviceWorkers: 'block', viewport: { width: locale === 'es' ? 390 : 1440, height: 900 } });
await guard(ctx);
await ctx.addInitScript((key) => localStorage.setItem(key, String(Date.now() + 86400000)), key);
const p = await ctx.newPage();
await p.clock.install({ time: new Date('2026-10-30T12:00:00Z') });
await p.clock.pauseAt(new Date('2026-10-30T12:00:01Z'));
await p.goto(origin + (locale === 'es' ? '/es/aba-therapy' : '/aba-therapy'));
const d = p.locator('[data-service-inquiry-drawer]');
const ack = d.locator('[data-service-inquiry-completed]');
const trigger = p.locator('[data-service-inquiry-trigger]');
const send = async (changes = {}) => p.evaluate((changes) => {
const frame = document.querySelector('dialog iframe');
const source = changes.wrongSource ? window : frame.contentWindow;
dispatchEvent(new MessageEvent('message', { origin: changes.origin ?? 'https://form.jotform.com', source,
data: { action: 'submission-completed', formID: '262946078314059', ...changes.data } }));
}, changes);
await send(); // Closed and unactivated.
assert.equal(await d.getAttribute('data-submission-completed'), null);
await trigger.click(); await p.clock.runFor(600);
// CSS transition rendering uses browser time, independently of mocked JS timers.
await delay(550);
await p.waitForFunction(() => [...document.querySelectorAll('iframe')].some(f => f.src.includes('consultation')));
for (const wrong of [{ origin: 'https://form.jotform.com.evil.invalid' }, { wrongSource: true },
{ data: { formID: 'other' } }, { data: { action: 'setHeight' } }]) await send(wrong);
await p.clock.runFor(6000);
assert.equal(await d.evaluate(d => d.open), true);
assert.equal(await ack.isVisible(), false);
evidence.checks++;
const child = p.frames().find(f => f.url() === expectedUrl(locale));
assert.ok(child, 'authorized fixture frame loaded');
await child.evaluate(() => parent.postMessage({ action: 'submission-completed', formID: '262946078314059' }, '*'));
await p.waitForFunction(() => document.querySelector('dialog')?.dataset.submissionCompleted === 'true');
assert.equal(await ack.isVisible(), true);
assert.match(await ack.textContent(), locale === 'es' ? /¡Gracias!/ : /Thank you!/);
const expiry = await p.evaluate(key => Number(localStorage.getItem(key)), key);
const now = await p.evaluate(() => Date.now());
assert.equal(expiry, now + duration);
await p.clock.runFor(3000); await send(); // Duplicate must not restart countdown.
await p.clock.runFor(1999);
assert.equal(await d.evaluate(d => d.open && d.classList.contains('is-open')), true);
const acknowledgmentBox = await ack.boundingBox();
assert.ok(acknowledgmentBox && acknowledgmentBox.x >= 0 && acknowledgmentBox.y >= 0 &&
acknowledgmentBox.y + acknowledgmentBox.height <= 900, 'acknowledgment is inside the visible viewport');
await p.screenshot({ path: path.join(output, `completion-${locale}.png`) });
evidence.screenshots.push(`completion-${locale}.png`);
await p.clock.runFor(1);
assert.equal(await d.evaluate(d => d.open && !d.classList.contains('is-open')), true, 'slide-out starts after full 5 seconds');
await p.clock.runFor(500);
assert.equal(await d.evaluate(d => d.open), false);
assert.equal(await trigger.evaluate(e => e === document.activeElement), true);
assert.equal(await p.evaluate(key => Number(localStorage.getItem(key)), key), expiry);
evidence.checks++;
await p.evaluate(() => window.oldFixtureWindow = document.querySelector('dialog iframe').contentWindow);
const count = evidence.jotformAttempts.length;
await trigger.click(); await p.clock.runFor(600);
assert.equal(await ack.isVisible(), false);
assert.equal(await d.locator('iframe').count(), 1);
assert.equal(await p.evaluate(() => window.oldFixtureWindow === document.querySelector('dialog iframe').contentWindow), false);
await p.waitForFunction(() => document.querySelector('[data-jotform-form-a]')?.dataset.jotformDocumentLoaded === 'true');
assert.equal(evidence.jotformAttempts.length, count + 1);
await p.evaluate(() => dispatchEvent(new MessageEvent('message', { origin: 'https://form.jotform.com', source: window.oldFixtureWindow,
data: { action: 'submission-completed', formID: '262946078314059' } })));
assert.equal(await ack.isVisible(), false);
await send(); await p.clock.runFor(1000);
await p.keyboard.press('Escape'); await p.clock.runFor(500);
assert.equal(await d.evaluate(d => d.open), false);
await trigger.click(); await p.clock.runFor(5000);
assert.equal(await d.evaluate(d => d.open), true, 'manual close canceled prior countdown');
assert.equal(await ack.isVisible(), false);
evidence.checks++;
await send();
await p.evaluate(() => document.dispatchEvent(new Event('astro:before-swap')));
await p.clock.runFor(6000);
assert.equal(await d.evaluate(d => d.open), false);
await p.evaluate(() => document.dispatchEvent(new Event('astro:page-load')));
await trigger.click(); await p.clock.runFor(6000);
assert.equal(await ack.isVisible(), false);
assert.equal(await d.evaluate(d => d.open), true);
evidence.checks++;
await ctx.close();
}
}
if (forms !== 'live') {
for (const width of [390, 1440]) for (const target of routes.filter(r => r.drawer)) {
const ctx = await browser.newContext({ viewport: { width, height: 900 }, serviceWorkers: 'block', reducedMotion: 'reduce' });
await guard(ctx);
const p = await ctx.newPage();
await p.clock.install({ time: new Date('2026-10-30T12:00:00Z') });
await p.clock.pauseAt(new Date('2026-10-30T12:00:01Z'));
await p.goto(origin + target.route, { waitUntil: 'domcontentloaded' });
assert.equal(await p.locator('iframe[data-jotform-iframe-frame]').count(), 0);
await p.evaluate(() => scrollTo({ top: document.documentElement.scrollHeight, behavior: 'instant' }));
await p.clock.runFor(11000);
const dialog = p.locator('[data-service-inquiry-drawer]');
if (forms === 'off') {
assert.equal(await dialog.evaluate(d => d.open), false, 'off never auto-opens');
const destination = target.locale === 'es' ? '/es/client-consultation' : '/client-consultation';
const cta = p.locator('main a[href="' + destination + '"]').first();
assert.ok(await cta.count());
await cta.click();
await p.waitForURL(origin + destination);
} else {
assert.equal(await dialog.evaluate(d => d.open), true, 'existing synthetic drawer remains available');
assert.ok(await dialog.locator('[data-form-mode="mock"]').count());
assert.equal(await dialog.locator('form').count(), 0);
}
assert.equal(evidence.jotformAttempts.length, 0);
evidence.checks++;
await ctx.close();
}
}
// Actual portal UI with synthetic encrypted response fixtures, never real private source.
for (const width of widths) {
const ctx = await browser.newContext({ viewport: { width, height: 844 }, serviceWorkers: 'block' }); await guard(ctx, true);
const p = await ctx.newPage(); await p.goto(origin + '/employee-portal');
assert.equal(await p.locator('meta[name="robots"]').getAttribute('content'), 'noindex,nofollow');
assert.equal(await p.locator('[data-portal-content]').isVisible(), false);
const input = p.locator('[data-portal-code]');
await input.fill('synthetic-incorrect'); await input.press('Enter');
await p.waitForFunction(() => document.querySelector('[data-portal-feedback]')?.textContent === 'Invalid Code');
assert.equal(await p.locator('[data-portal-content]').textContent(), '');
await input.fill(staffCode); await input.press('Enter');
await p.locator('[data-portal-content]').waitFor({ state: 'visible' });
assert.match(await p.locator('[data-portal-content]').textContent(), /Synthetic Staff/);
assert.doesNotMatch(await p.locator('[data-portal-content]').textContent(), /Synthetic Management|Management fixture only/);
assert.equal(await p.evaluate(() => window.portalUnsafe), undefined);
assert.equal(await p.locator('[data-portal-content] img, [data-portal-content] script').count(), 0);
assert.equal(await p.evaluate(() => document.activeElement?.hasAttribute('data-portal-content')), true);
assert.equal(await input.inputValue(), '');
const checkPresentation = async (staff = false) => {
const content = p.locator('[data-portal-content]');
assert.equal(await content.locator('.portal-resource-button').count(), staff ? 8 : 4);
assert.equal(await content.locator('[style], [onclick], .arbitrary-legacy-class, .unrecognized-report-legacy, iframe, form, input').count(), 0);
assert.equal(await content.locator('a[href^="javascript:"]').count(), 0);
assert.equal(await content.locator('th[scope="col"]').count(), 2);
const button = content.locator('.portal-resource-button').first();
assert.equal(await button.evaluate(e => getComputedStyle(e).backgroundColor), 'rgb(255, 189, 89)');
const legacyReport = content.getByRole('link', { name: 'Operations Monthly Lineup Report - PM' });
assert.equal(await legacyReport.getAttribute('class'), 'portal-resource-button');
assert.equal(await legacyReport.evaluate(e => getComputedStyle(e).backgroundColor), 'rgb(255, 189, 89)');
assert.equal(await content.locator('li').first().evaluate(e => getComputedStyle(e, '::marker').color), 'rgb(255, 189, 89)');
assert.equal(await content.locator('h2').first().evaluate(e => getComputedStyle(e).color), 'rgb(37, 64, 128)');
assert.ok(await content.locator('h2').first().evaluate(e => parseFloat(getComputedStyle(e).fontSize) >= 28));
assert.equal(await content.locator('.portal-contact-link').first().evaluate(e => getComputedStyle(e).color), 'rgb(169, 70, 0)');
await button.focus();
assert.equal(await button.evaluate(e => getComputedStyle(e).outlineStyle), 'solid');
assert.equal(await button.evaluate(e => getComputedStyle(e).outlineWidth), '3px');
};
await checkPresentation(true);
const staffContent = p.locator('[data-portal-content]');
const grid = staffContent.locator('ul.portal-template-grid');
assert.equal(await grid.count(), 1);
assert.equal(await grid.locator('li').count(), 3);
assert.equal(await grid.locator('a.portal-resource-button').count(), 3);
const columns = await grid.evaluate((element) => getComputedStyle(element).gridTemplateColumns.split(/\s+/).length);
assert.equal(columns, width >= 768 ? 2 : 1, 'staff templates follow responsive column count');
assert.equal(await staffContent.getByRole('link', { name: 'General staff resource' }).getAttribute('class'), 'portal-resource-button');
assert.equal(await staffContent.getByRole('link', { name: 'Staff email' }).getAttribute('class'), 'portal-contact-link');
assert.equal(await p.evaluate(async (url) => (await fetch(url)).status, origin + staffResourcePath), 401);
await p.evaluate(() => {
window.portalObjectUrlRevocations = [];
const revoke = URL.revokeObjectURL.bind(URL);
URL.revokeObjectURL = (url) => { window.portalObjectUrlRevocations.push(url); revoke(url); };
});
const staffLink = staffContent.getByRole('link', { name: "Staff's Safety & Training: Résumé (2026)!" });
const staffDownload = p.waitForEvent('download');
await staffLink.click();
assert.equal((await staffDownload).suggestedFilename(), 'Staffs-Safety-and-Training-Resume-2026.pdf');
await p.waitForFunction(() => document.querySelector('[data-portal-download-feedback]')?.textContent === 'Downloaded Staffs-Safety-and-Training-Resume-2026.pdf.');
assert.equal(await p.evaluate(() => window.portalObjectUrlRevocations.length), 0, 'Blob URL remains valid while the browser begins the download');
assert.equal(p.url(), origin + '/employee-portal');
const beforeCrossRole = evidence.resourceRequests.length;
await staffLink.evaluate((link, href) => { link.href = href; }, managementResourcePath);
await staffLink.click();
await p.waitForFunction(() => document.querySelector('[data-portal-download-feedback]')?.textContent?.includes('not available from this portal'));
assert.equal(evidence.resourceRequests.length, beforeCrossRole, 'staff credential is not sent to management resources');
assert.deepEqual(await p.evaluate(() => ({ local: { ...localStorage }, session: { ...sessionStorage } })), { local: {}, session: {} });
assert.equal(p.url().includes(staffCode), false);
assert.ok(await p.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1));
await p.screenshot({ path: path.join(output, 'portal-synthetic-' + width + '.png'), fullPage: true });
evidence.screenshots.push('portal-synthetic-' + width + '.png');
await p.reload(); assert.equal(await p.locator('[data-portal-content]').isVisible(), false);
await input.fill(managementCode); await input.press('Enter');
await p.locator('[data-portal-content]').waitFor({ state: 'visible' });
assert.match(await p.locator('[data-portal-content]').textContent(), /Synthetic Management/);
assert.doesNotMatch(await p.locator('[data-portal-content]').textContent(), /Staff fixture/);
await checkPresentation();
assert.equal(await p.evaluate(async (url) => (await fetch(url)).status, origin + managementResourcePath), 401);
await p.evaluate(() => {
window.portalObjectUrlRevocations = [];
const revoke = URL.revokeObjectURL.bind(URL);
URL.revokeObjectURL = (url) => { window.portalObjectUrlRevocations.push(url); revoke(url); };
});
const managementLink = p.getByRole('link', { name: 'Management – Policies / Q&A #1' });
const managementDownload = p.waitForEvent('download');
await managementLink.click();
assert.equal((await managementDownload).suggestedFilename(), 'Management-Policies-Q-and-A-1.docx');
await p.waitForFunction(() => document.querySelector('[data-portal-download-feedback]')?.textContent === 'Downloaded Management-Policies-Q-and-A-1.docx.');
assert.equal(await p.evaluate(() => window.portalObjectUrlRevocations.length), 0, 'Blob URL remains valid while the browser begins the download');
const beforeManagementCrossRole = evidence.resourceRequests.length;
await managementLink.evaluate((link, href) => { link.href = href; }, staffResourcePath);
await managementLink.click();
await p.waitForFunction(() => document.querySelector('[data-portal-download-feedback]')?.textContent?.includes('not available from this portal'));
assert.equal(evidence.resourceRequests.length, beforeManagementCrossRole, 'management credential is not sent to staff resources');
assert.equal(p.url().includes(managementCode), false);
assert.equal(await p.locator('[data-portal-content] ul.portal-template-grid').count(), 0, 'management view retains original layout');
await p.screenshot({ path: path.join(output, 'portal-management-synthetic-' + width + '.png'), fullPage: true });
evidence.screenshots.push('portal-management-synthetic-' + width + '.png');
evidence.checks++; await ctx.close();
}
for (const js of [false, true]) {
const ctx = await browser.newContext({ javaScriptEnabled: js, serviceWorkers: 'block' }); await guard(ctx);
const p = await ctx.newPage(); await p.goto(origin + '/employee-portal');
assert.equal(await p.locator('[data-portal-code]').isDisabled(), true);
assert.equal(await p.locator('[data-portal-content]').isVisible(), false);
if (!js) assert.ok(await p.locator('noscript').last().isVisible());
evidence.checks++; await ctx.close();
}
assert.equal(evidence.blocked.filter((r) => !['GET','HEAD'].includes(r.method)).length, 0);
evidence.status = 'passed';
} catch (error) {
evidence.error = error instanceof Error ? error.message : String(error);
throw error;
} finally {
await fs.writeFile(path.join(output, 'evidence.json'), JSON.stringify(evidence, null, 2));
if (preview && preview.exitCode === null) {
preview.kill('SIGTERM');
await Promise.race([new Promise((resolve) => preview.once('exit', resolve)), delay(3000)]);
if (preview.exitCode === null) preview.kill('SIGKILL');
}
await browser?.close();
console.log(JSON.stringify({ ...evidence, artifactDirectory: output }, null, 2));
}