139 lines
4.2 KiB
TypeScript
139 lines
4.2 KiB
TypeScript
/**
|
|
* Puppeteer + from-memory devServer rollup plugin to open the result in a webpage en output the result
|
|
* (after an optional series of commands to the puppeteer Page)
|
|
*/
|
|
|
|
|
|
import puppeteer, {Page} from "puppeteer";
|
|
import {fileURLToPath, URL} from "node:url";
|
|
import {isInDebugMode} from "./debug-mode.ts";
|
|
|
|
|
|
export type PageTestCallback = (page: Page)=>Promise<void>;
|
|
|
|
export interface TestOptions {
|
|
path: string
|
|
cb: PageTestCallback
|
|
replaceHost: boolean
|
|
replaceHostWith?: string
|
|
}
|
|
const defaultOptions: Partial<TestOptions> = {
|
|
path: 'index.html',
|
|
cb: async (page: Page)=>{
|
|
await page.waitForNetworkIdle({});
|
|
},
|
|
replaceHost: true,
|
|
replaceHostWith: `http://localhost`,
|
|
}
|
|
export interface TestOutput{
|
|
html: string,
|
|
console: string[],
|
|
errors: string[],
|
|
responses: string[],
|
|
requestsFailed: string[],
|
|
}
|
|
|
|
/**
|
|
* Opens a page in a puppeteer browser and return the resulting HTML and logmessages produced.
|
|
* Optionally a callback can be provided to simulate user interactions on the page before returning the HTML
|
|
* When DEBUG mode is detected, puppeteer headless mode will be disabled allowing you to inspect the page if you set a breakpoint
|
|
*
|
|
* @param opts
|
|
* @param hostUrl
|
|
*/
|
|
export async function puppeteerRunTest(opts: Partial<TestOptions>, hostUrl: string){
|
|
const options : TestOptions = (<TestOptions>{
|
|
...defaultOptions,
|
|
...opts,
|
|
});
|
|
const {
|
|
path,
|
|
cb,
|
|
replaceHost,
|
|
replaceHostWith,
|
|
} = options;
|
|
|
|
const browser = await puppeteer.launch({
|
|
headless: isInDebugMode()? false : 'new',
|
|
args: [
|
|
...(process.env.PUPPETEER_CHROME_ARGS??'').split(' '),
|
|
]// --use-gl=egl
|
|
});
|
|
const page = await browser.newPage();
|
|
|
|
let output : TestOutput = {
|
|
html: '',
|
|
console: [],
|
|
errors: [],
|
|
responses: [],
|
|
requestsFailed: []
|
|
};
|
|
|
|
let errored = false;
|
|
|
|
try {
|
|
// Track requests, errors and console
|
|
page.on('console', message => {
|
|
let [type, text] = [message.type(), message.text()];
|
|
if (replaceHost) {
|
|
text = text.replaceAll(hostUrl, replaceHostWith!);
|
|
}
|
|
output.console?.push(`[${type}] ${text}`);
|
|
}).on('pageerror', ({message}) => {
|
|
let text = message;
|
|
if (replaceHost) {
|
|
text = text.replaceAll(hostUrl, replaceHostWith!);
|
|
}
|
|
output.errors?.push(text);
|
|
}).on('response', response => {
|
|
let [status, url] = [response.status(), response.url()]
|
|
if (replaceHost) {
|
|
url = url.replaceAll(hostUrl, replaceHostWith!);
|
|
}
|
|
output.responses?.push(`${status} ${url}`);
|
|
}).on('requestfailed', request => {
|
|
let [failure, url] = [request.failure()?.errorText, request.url()];
|
|
if (replaceHost) {
|
|
failure = failure?.replaceAll(hostUrl, replaceHostWith!);
|
|
url = url.replaceAll(hostUrl, replaceHostWith!);
|
|
}
|
|
output.requestsFailed?.push(`${failure} ${url}`);
|
|
});
|
|
|
|
const url = new URL(path??'', hostUrl);
|
|
await page.goto(url.href);
|
|
|
|
if (!cb) {
|
|
await page.waitForNetworkIdle({});
|
|
} else {
|
|
await cb(page);
|
|
}
|
|
const htmlHandle = await page.$('html');
|
|
const html = await page.evaluate( // potentially trips up Jest's code coverage, hence the istanbul ignore
|
|
/* istanbul ignore next */html => html?.outerHTML ?? html?.innerHTML, htmlHandle
|
|
);
|
|
|
|
// Add the final html
|
|
output.html = html || '';
|
|
|
|
return output;
|
|
}catch(err){
|
|
errored = true;
|
|
throw err;
|
|
}finally{
|
|
if(isInDebugMode() && !errored){
|
|
console.log(`DEBUG MODE ENABLED, Close the puppeteer browsertab to continue!\n${import.meta.url}:144`);
|
|
await new Promise((resolve)=>{
|
|
page.on('close', ()=>{
|
|
console.log("Page closed");
|
|
resolve(null);
|
|
})
|
|
});
|
|
}else{
|
|
await page.close();
|
|
}
|
|
await browser.close();
|
|
}
|
|
}
|
|
|