211 lines
13 KiB
JavaScript
211 lines
13 KiB
JavaScript
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 root = fileURLToPath(new URL('../', import.meta.url));
|
|
const moduleName = process.env.PLAYWRIGHT_MODULE ? pathToFileURL(path.resolve(process.env.PLAYWRIGHT_MODULE)).href : 'playwright';
|
|
const expectedMode = process.env.FORM_A_BROWSER_MODE === 'off' ? 'off' : 'mock';
|
|
const campaignRoutes = ['/lp/aba-therapy', '/lp/aba-insurance', '/lp/aba-ahcccs', '/lp/aba-private-pay'];
|
|
const earlyInterventionRoute = '/lp/early-intervention';
|
|
const routes = expectedMode === 'off' ? [...campaignRoutes, earlyInterventionRoute] : ['/contact', '/es/contact', ...campaignRoutes, earlyInterventionRoute];
|
|
const output = await fs.mkdtemp('/tmp/aia-form-a-preview-');
|
|
const evidence = { mode: expectedMode, routes, widths: [390, 1440], checks: 0, failures: [], blocked: [], productionAttempts: [], screenshots: [], 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}`;
|
|
const isProduction = (url) => /(?:jotform|api\.azinstitute4autism\.com|google-analytics|googletagmanager|doubleclick)/i.test(url.hostname + url.pathname);
|
|
let browser;
|
|
let preview;
|
|
try {
|
|
const { chromium } = await import(moduleName);
|
|
browser = await chromium.launch(process.env.CHROMIUM_EXECUTABLE ? { headless: true, executablePath: process.env.CHROMIUM_EXECUTABLE } : { headless: true });
|
|
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);
|
|
|
|
for (const width of evidence.widths) {
|
|
const context = await browser.newContext({ viewport: { width, height: width < 500 ? 844 : 900 }, serviceWorkers: 'block' });
|
|
await context.routeWebSocket('**/*', (socket) => socket.close());
|
|
await context.route('**/*', (route) => {
|
|
const request = route.request();
|
|
const url = new URL(request.url());
|
|
if (url.origin !== origin || !['GET', 'HEAD'].includes(request.method())) {
|
|
const item = { origin: url.origin, path: url.pathname, method: request.method() };
|
|
evidence.blocked.push(item);
|
|
if (isProduction(url)) evidence.productionAttempts.push(item);
|
|
return route.abort();
|
|
}
|
|
return route.continue();
|
|
});
|
|
const page = await context.newPage();
|
|
for (const route of routes) {
|
|
evidence.checks++;
|
|
try {
|
|
const response = await page.goto(origin + route, { waitUntil: 'domcontentloaded', timeout: 15000 });
|
|
assert.equal(response?.status(), 200);
|
|
if (route === earlyInterventionRoute) {
|
|
assert.equal(await page.locator('[data-form-a-preview]').count(), 0);
|
|
assert.equal(await page.locator('#consultation[data-forms-mode="off"]').count(), 1);
|
|
assert.equal(await page.locator('#consultation button[type="button"][disabled]').count(), 1);
|
|
assert.equal(await page.locator('[data-form-status-notice], #consultation-unavailable').count(), 0);
|
|
assert.doesNotMatch(await page.locator('.qr-landing').textContent(), /(?:online )?(?:forms?|requests?|submissions?) (?:are |is )?(?:unavailable|not enabled)|detailed medical|diagnostic|insurance-member/i);
|
|
assert.ok(await page.locator('a[href="tel:+14806877099"]').count() >= 1);
|
|
assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1));
|
|
continue;
|
|
}
|
|
if (expectedMode === 'off') {
|
|
assert.equal(await page.locator('[data-form-a-preview]').count(), 0);
|
|
assert.equal(await page.locator('#lead-form[data-forms-mode="off"]').count(), 1);
|
|
assert.equal(await page.locator('[data-form-status-notice], #campaign-form-status').count(), 0);
|
|
assert.equal(await page.locator('#lead-form a[href^="tel:"]').count(), 1);
|
|
assert.equal(await page.locator('#lead-form a[href="/privacy-policy"]').count(), 1);
|
|
assert.doesNotMatch(await page.locator('#lead-form').textContent(), /(?:online )?(?:forms?|requests?|submissions?) (?:are |is )?(?:unavailable|not enabled)|detailed medical|diagnostic|insurance-member/i);
|
|
assert.equal(await page.locator('#lead-form button[type="button"][disabled]').count(), 1);
|
|
assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1));
|
|
continue;
|
|
}
|
|
const compact = route.startsWith('/lp/');
|
|
const fields = compact ? ['name', 'phone', 'email', 'preferredContact'] : ['name', 'phone', 'email', 'preferredContact', 'message'];
|
|
const result = await page.evaluate(({ fields: expected, compact: isCompact }) => ({
|
|
enabled: document.querySelector('[data-form-a-preview]')?.dataset.formAPreviewEnabled,
|
|
names: [...document.querySelectorAll('[data-form-a-preview] [name]')].map((el) => el.getAttribute('name')),
|
|
forms: document.querySelectorAll('form').length,
|
|
iframe: document.querySelectorAll('iframe').length,
|
|
h1: document.querySelectorAll('main h1').length,
|
|
width: document.documentElement.scrollWidth,
|
|
expectedCount: expected.length,
|
|
isCompact
|
|
}), { fields, compact });
|
|
assert.equal(result.enabled, 'true');
|
|
assert.deepEqual(result.names, fields);
|
|
assert.equal(result.forms, 0);
|
|
assert.equal(result.iframe, 0);
|
|
assert.equal(result.h1, 1);
|
|
assert.ok(result.width <= width + 1, `overflow ${result.width}`);
|
|
|
|
const preview = page.locator('[data-form-a-preview]');
|
|
if (route === '/es/contact') {
|
|
assert.match(await preview.textContent(), /Su nombre|Método de contacto preferido/);
|
|
assert.doesNotMatch(await preview.textContent(), /Your name|Preferred contact method/);
|
|
}
|
|
const expectedButtonText = route === '/contact' ? 'Submit' : route === '/es/contact' ? 'Enviar' : {
|
|
'/lp/aba-therapy': 'Request a Free Consultation',
|
|
'/lp/aba-insurance': 'Request Insurance Verification',
|
|
'/lp/aba-ahcccs': 'Request a Benefits Check',
|
|
'/lp/aba-private-pay': 'Discuss Private-Pay Options'
|
|
}[route];
|
|
assert.equal(await preview.locator('[data-preview-status]').count(), 1);
|
|
assert.equal(await preview.locator('.form-a-preview__caution').count(), compact ? 0 : 1);
|
|
if (compact) {
|
|
assert.equal(await page.locator('[data-form-status-notice]').count(), 1);
|
|
assert.equal((await page.locator('[data-form-status-notice]').textContent()).match(/Preview only — nothing is submitted\./g)?.length, 1);
|
|
assert.equal(await page.locator('[data-form-status-notice] a[href="tel:+14806877099"]').count(), 1);
|
|
assert.equal(await page.locator('[data-form-status-notice] a[href="/privacy-policy"]').count(), 1);
|
|
assert.doesNotMatch(await page.locator('#consultation').textContent(), /(?:online )?(?:forms?|requests?|submissions?) (?:are |is )?(?:unavailable|not enabled)|detailed medical|diagnostic|insurance-member/i);
|
|
}
|
|
const button = preview.locator('[data-preview-submit]');
|
|
assert.equal((await button.textContent()).trim(), expectedButtonText);
|
|
const filledStyle = await button.evaluate((element) => {
|
|
const style = getComputedStyle(element);
|
|
return { background: style.backgroundColor, border: style.borderColor, borderWidth: style.borderWidth, color: style.color };
|
|
});
|
|
assert.equal(filledStyle.background, 'rgb(255, 189, 89)');
|
|
assert.equal(filledStyle.border, 'rgb(255, 189, 89)');
|
|
assert.equal(filledStyle.borderWidth, '2px');
|
|
assert.equal(filledStyle.color, 'rgb(37, 64, 128)');
|
|
await button.hover();
|
|
await delay(350);
|
|
const hoverStyle = await button.evaluate((element) => {
|
|
const style = getComputedStyle(element);
|
|
return { background: style.backgroundColor, border: style.borderColor };
|
|
});
|
|
assert.equal(hoverStyle.background, 'rgba(0, 0, 0, 0)');
|
|
assert.equal(hoverStyle.border, 'rgb(255, 145, 77)');
|
|
await button.focus();
|
|
assert.notEqual(await button.evaluate((element) => getComputedStyle(element).outlineWidth), '0px');
|
|
await button.click();
|
|
assert.doesNotMatch(await preview.locator('[data-preview-status]').textContent(), /completed locally|terminó localmente/);
|
|
await preview.locator('input[name="name"]').fill('Demo Reviewer');
|
|
await preview.locator('input[name="phone"]').fill('480-555-0100');
|
|
await preview.locator('select[name="preferredContact"]').selectOption('phone');
|
|
await button.click();
|
|
if (compact) assert.match(await preview.locator('[data-preview-status]').textContent(), /Preview only — nothing is submitted/);
|
|
else assert.match(await preview.locator('[data-preview-status]').textContent(), /completed locally|terminó localmente/);
|
|
await preview.locator('select[name="preferredContact"]').selectOption('email');
|
|
if (route === '/es/contact') {
|
|
assert.equal(
|
|
(await preview.locator('[id$="-email-requirement"]').textContent())?.trim(),
|
|
'(obligatorio para contacto por correo)'
|
|
);
|
|
}
|
|
await button.click();
|
|
await assert.match(await preview.locator('[data-preview-status]').textContent(), /email|correo/i);
|
|
await preview.locator('input[name="email"]').fill('demo@example.test');
|
|
await button.click();
|
|
if (compact) assert.match(await preview.locator('[data-preview-status]').textContent(), /Preview only — nothing is submitted/);
|
|
else assert.match(await preview.locator('[data-preview-status]').textContent(), /completed locally|terminó localmente/);
|
|
if ((route === '/contact' || route === '/lp/aba-therapy') && (width === 390 || width === 1440)) {
|
|
const file = `${route === '/contact' ? 'contact' : 'campaign'}-${width}.png`;
|
|
await page.screenshot({ path: path.join(output, file), fullPage: true });
|
|
evidence.screenshots.push({ route, width, file });
|
|
}
|
|
} catch (error) {
|
|
evidence.failures.push({ route, width, error: String(error.message) });
|
|
}
|
|
}
|
|
await context.close();
|
|
}
|
|
if (expectedMode === 'mock') {
|
|
const noJsContext = await browser.newContext({ viewport: { width: 390, height: 844 }, javaScriptEnabled: false, serviceWorkers: 'block' });
|
|
await noJsContext.route('**/*', (route) => {
|
|
const request = route.request();
|
|
const url = new URL(request.url());
|
|
if (url.origin !== origin || !['GET', 'HEAD'].includes(request.method())) {
|
|
const item = { origin: url.origin, path: url.pathname, method: request.method(), noJs: true };
|
|
evidence.blocked.push(item);
|
|
if (isProduction(url)) evidence.productionAttempts.push(item);
|
|
return route.abort();
|
|
}
|
|
return route.continue();
|
|
});
|
|
for (const route of ['/contact', '/lp/aba-therapy']) {
|
|
evidence.checks++;
|
|
const page = await noJsContext.newPage();
|
|
const response = await page.goto(origin + route, { waitUntil: 'domcontentloaded', timeout: 15000 });
|
|
assert.equal(response?.status(), 200);
|
|
assert.equal(await page.locator('[data-form-a-preview]').count(), 1);
|
|
assert.equal(await page.locator('form').count(), 0);
|
|
const bodyText = await page.locator('body').textContent();
|
|
if (route === '/contact') assert.match(bodyText, /JavaScript|JavaScript is required|Se requiere JavaScript/);
|
|
else assert.match(bodyText, /Preview only — nothing is submitted/);
|
|
}
|
|
await noJsContext.close();
|
|
}
|
|
evidence.status = evidence.failures.length || evidence.productionAttempts.length ? 'failed' : 'passed';
|
|
} catch (error) {
|
|
evidence.error = String(error.message);
|
|
} finally {
|
|
await browser?.close();
|
|
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');
|
|
}
|
|
evidence.finishedAt = new Date().toISOString();
|
|
await fs.writeFile(path.join(output, 'results.json'), JSON.stringify(evidence, null, 2) + '\n');
|
|
console.log(JSON.stringify({ ...evidence, output }, null, 2));
|
|
if (evidence.status !== 'passed') process.exitCode = 1;
|
|
}
|