ice/tests/utils/browser.ts

140 lines
4.2 KiB
TypeScript

import * as http from 'http';
import * as url from 'url';
import * as path from 'path';
import * as fse from 'fs-extra';
import type {
Page as PuppeteerPage,
Browser as PuppeteerBrowser,
WaitForOptions,
HTTPResponse,
} from 'puppeteer';
import puppeteer from 'puppeteer';
export interface Page extends PuppeteerPage {
html: () => Promise<string>;
$text: (selector: string, trim?: boolean) => Promise<string | null>;
$$text: (selector: string, trim?: boolean) => Promise<(string | null)[]>;
$attr: (selector: string, attr: string) => Promise<string | null>;
$$attr: (selector: string, attr: string) => Promise<(string | null)[]>;
push: (url: string, options?: WaitForOptions & {
referer?: string;
referrerPolicy?: string;
}) => Promise<HTTPResponse | null>;
}
interface BrowserOptions {
cwd?: string;
port?: number;
server?: http.Server;
}
export default class Browser {
private server: http.Server;
private browser: PuppeteerBrowser;
private baseUrl: string;
constructor(options: BrowserOptions) {
const { server } = options;
if (server) {
this.server = server;
} else {
const { cwd, port } = options;
this.server = this.createServer(cwd!, port!);
}
}
createServer(cwd: string, port: number) {
return http.createServer((req, res) => {
const reqUrl: string = req.url || '';
const pathname = `${cwd}${url.parse(reqUrl).pathname}`.split(path.sep).join('/');
if (fse.existsSync(pathname)) {
switch (path.extname(pathname)) { // set HTTP HEAD
case '.html':
res.writeHead(200, { 'Content-Type': 'text/html' });
break;
case '.js':
res.writeHead(200, { 'Content-Type': 'text/javascript' });
break;
case '.css':
res.writeHead(200, { 'Content-Type': 'text/css' });
break;
case '.gif':
res.writeHead(200, { 'Content-Type': 'image/gif' });
break;
case '.jpg':
res.writeHead(200, { 'Content-Type': 'image/jpeg' });
break;
case '.png':
res.writeHead(200, { 'Content-Type': 'image/png' });
break;
default:
res.writeHead(200, {
'Content-Type': 'application/octet-stream',
});
}
fse.readFile(pathname, (_err, data) => {
res.end(data);
});
} else {
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end('<h1>404 Not Found</h1>');
console.log(`${pathname} Not Found.`);
}
}).listen(port, '127.0.0.1');
}
async start() {
this.browser = await puppeteer.launch(
{
args: ['--no-sandbox', '--disable-setuid-sandbox'],
},
);
}
async close() {
if (!this.browser) { return; }
await this.browser.close();
// @ts-ignore
if (this.server.stop) {
// @ts-ignore
this.server.stop();
} else {
this.server.close();
}
}
async page(baseUrl: string, path = '/', disableJS?: boolean): Promise<Page> {
this.baseUrl = baseUrl;
if (!this.browser) { throw new Error('Please call start() before page(url)'); }
const page = (await this.browser.newPage()) as Page;
if (disableJS) {
await page.setJavaScriptEnabled(false);
}
await page.goto(`${this.baseUrl}${path}`);
page.push = (url, options) => page.goto(`${this.baseUrl}${url}`, options);
page.html = () =>
page.evaluate(() => window.document.documentElement.outerHTML);
page.$text = (selector, trim) => page.$eval(selector, (el, trim) => {
return trim ? (el.textContent || '').replace(/^\s+|\s+$/g, '') : el.textContent;
}, trim);
page.$$text = (selector, trim) =>
page.$$eval(selector, (els, trim) => els.map((el) => {
return trim ? (el.textContent || '').replace(/^\s+|\s+$/g, '') : el.textContent;
}), trim);
page.$attr = (selector, attr) =>
page.$eval(selector, (el, attr) => el.getAttribute(attr as string), attr);
page.$$attr = (selector, attr) =>
page.$$eval(
selector,
(els, attr) => els.map(el => el.getAttribute(attr as string)),
attr,
);
return page;
}
}