Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
245 changes: 119 additions & 126 deletions runner/workers/serve-testing/puppeteer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,149 +28,142 @@ export async function runAppInPuppeteer(
let screenshotBase64Data: string | undefined;
let axeViolations: AxeResult[] | undefined;
let lighthouseResult: LighthouseResult | undefined;
let unexpectedErrorMessage: string | undefined;

try {
const browser = await puppeteer.launch({
headless: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu',
],
});
const page = await browser.newPage();
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu'],
});
const page = await browser.newPage();

page.on('console', async message => {
if (message.type() !== 'error') return;
page.on('console', async message => {
if (message.type() !== 'error') return;

if (!message.text().includes('JSHandle@error')) {
progressLog(
'error',
`Runtime Error: ${message.type().substring(0, 3).toUpperCase()} ${message.text()}`,
);
if (!message.text().includes('JSHandle@error')) {
progressLog(
'error',
`Runtime Error: ${message.type().substring(0, 3).toUpperCase()} ${message.text()}`,
);
return;
}
const messages = await Promise.all(
message.args().map(async arg => {
const [message, stack] = await Promise.all([
arg.getProperty('message'),
arg.getProperty('stack'),
]);

let result = '';
if (message) {
result += message;
}
if (stack) {
result += (result.length ? '\n\n' : '') + stack;
}
return result;
}),
);
runtimeErrors.push(messages.filter(Boolean).join('\n'));
});

page.on('pageerror', error => {
progressLog('error', 'Page error', error.message);
runtimeErrors.push(error.toString());
});

await page.setViewport({width: 1280, height: 720});

// Set up auto-CSP handling if enabled for the environment.
if (enableAutoCsp) {
const autoCsp = new AutoCsp();
await autoCsp.connectToDevTools(page);
await page.setRequestInterception(true);
page.on('request', async request => {
if (request.isInterceptResolutionHandled()) {
return;
}
const messages = await Promise.all(
message.args().map(async arg => {
const [message, stack] = await Promise.all([
arg.getProperty('message'),
arg.getProperty('stack'),
]);

let result = '';
if (message) {
result += message;
}
if (stack) {
result += (result.length ? '\n\n' : '') + stack;
}
return result;
}),
);
runtimeErrors.push(messages.filter(Boolean).join('\n'));
});

page.on('pageerror', error => {
progressLog('error', 'Page error', error.message);
runtimeErrors.push(error.toString());
// Delegate CSP-related requests to the AutoCsp class
const handled = await autoCsp.handleRequest(request);
if (!handled) {
// Other requests (CSS, JS, images) pass through
await request.continue();
}
});

await page.setViewport({width: 1280, height: 720});

// Set up auto-CSP handling if enabled for the environment.
if (enableAutoCsp) {
const autoCsp = new AutoCsp();
await autoCsp.connectToDevTools(page);
await page.setRequestInterception(true);
page.on('request', async request => {
if (request.isInterceptResolutionHandled()) {
return;
}
await page.goto(hostUrl, {
waitUntil: 'networkidle0',
timeout: 30000,
});

// Delegate CSP-related requests to the AutoCsp class
const handled = await autoCsp.handleRequest(request);
if (!handled) {
// Other requests (CSS, JS, images) pass through
await request.continue();
}
});

await page.goto(hostUrl, {
waitUntil: 'networkidle0',
timeout: 30000,
});

// Now that the page is loaded, process the collected CSP reports.
autoCsp.processViolations();
cspViolations = autoCsp.violations;
} else {
// If CSP is not enabled, just navigate to the page directly.
await page.goto(hostUrl, {
waitUntil: 'networkidle0',
timeout: 30000,
});
}
// Now that the page is loaded, process the collected CSP reports.
autoCsp.processViolations();
cspViolations = autoCsp.violations;
} else {
// If CSP is not enabled, just navigate to the page directly.
await page.goto(hostUrl, {
waitUntil: 'networkidle0',
timeout: 30000,
});
}

// Perform Axe Testing
if (includeAxeTesting) {
try {
progressLog('eval', `Running Axe accessibility test from ${hostUrl}`);
const axeResults = await new AxePuppeteer(page).analyze();
axeViolations = axeResults.violations;
progressLog('success', `Axe accessibility test completed.`);

if (axeViolations.length > 0) {
progressLog('error', `Found ${axeViolations.length} Axe violations.`);
} else {
progressLog('success', `No Axe violations found.`);
}
} catch (axeError: any) {
progressLog('error', 'Could not perform Axe accessibility test', axeError.message);
// Perform Axe Testing
if (includeAxeTesting) {
try {
progressLog('eval', `Running Axe accessibility test from ${hostUrl}`);
const axeResults = await new AxePuppeteer(page).analyze();
axeViolations = axeResults.violations;
progressLog('success', `Axe accessibility test completed.`);

if (axeViolations.length > 0) {
progressLog('error', `Found ${axeViolations.length} Axe violations.`);
} else {
progressLog('success', `No Axe violations found.`);
}
} catch (axeError: any) {
progressLog('error', 'Could not perform Axe accessibility test', axeError.message);
}
}

if (takeScreenshots) {
progressLog('eval', `Taking screenshot from ${hostUrl}`);

screenshotBase64Data = await callWithTimeout(
`Taking screenshot for ${appName}`,
() =>
page.screenshot({
type: 'png',
fullPage: true,
encoding: 'base64',
}),
1, // 1 minute
);
progressLog('success', 'Screenshot captured and encoded');
}

if (includeLighthouseData) {
try {
progressLog('eval', `Gathering Lighthouse data from ${hostUrl}`);
lighthouseResult = await getLighthouseData(hostUrl, page);
if (takeScreenshots) {
progressLog('eval', `Taking screenshot from ${hostUrl}`);

if (lighthouseResult) {
progressLog('success', 'Lighthouse data has been collected');
} else {
progressLog('error', 'Lighthouse did not produce usable data');
}
} catch (lighthouseError: any) {
progressLog('error', 'Could not gather Lighthouse data', lighthouseError.message);
}
}
screenshotBase64Data = await callWithTimeout(
`Taking screenshot for ${appName}`,
() =>
page.screenshot({
type: 'png',
fullPage: true,
encoding: 'base64',
}),
1, // 1 minute
);
progressLog('success', 'Screenshot captured and encoded');
}

await browser.close();
} catch (screenshotError: any) {
let details: string = screenshotError.message;
if (includeLighthouseData) {
try {
progressLog('eval', `Gathering Lighthouse data from ${hostUrl}`);
lighthouseResult = await getLighthouseData(hostUrl, page);

if (screenshotError.stack) {
details += '\n' + screenshotError.stack;
if (lighthouseResult) {
progressLog('success', 'Lighthouse data has been collected');
} else {
progressLog('error', 'Lighthouse did not produce usable data');
}
} catch (lighthouseError: any) {
progressLog('error', 'Could not gather Lighthouse data', lighthouseError.message);
}

progressLog('error', 'Could not take screenshot', details);
}

return {screenshotBase64Data, runtimeErrors, axeViolations, cspViolations, lighthouseResult};
await browser.close();

return {
screenshotBase64Data,
runtimeErrors,
axeViolations,
cspViolations,
lighthouseResult,
unexpectedErrorMessage,
};
}
Loading