blob: 1e5986b3c9bc27668446e218f4ba48eef4b60ae6 [file]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/* eslint-disable no-console */
// use require here due to
// https://github.com/evanw/esbuild/issues/587#issuecomment-901397213
import puppeteer = require('puppeteer');
import {type CoverageMapData} from 'istanbul-lib-coverage';
import {
clearPuppeteerState,
getBrowserAndPages,
registerHandlers,
setBrowserAndPages,
setTestServerPort,
} from './puppeteer-state.js';
import {getTestRunnerConfigSetting} from './test_runner_config.js';
import {
loadEmptyPageAndWaitForContent,
DevToolsFrontendTab,
type DevToolsFrontendReloadOptions,
} from './frontend_tab.js';
import {dumpCollectedErrors, installPageErrorHandlers, setupBrowserProcessIO} from './events.js';
import {TargetTab} from './target_tab.js';
// Workaround for mismatching versions of puppeteer types and puppeteer library.
declare module 'puppeteer' {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface ConsoleMessage {
stackTrace(): ConsoleMessageLocation[];
}
}
const viewportWidth = 1280;
const viewportHeight = 720;
// Adding some offset to the window size used in the headful mode
// so to account for the size of the browser UI.
// Values are choosen by trial and error to make sure that the window
// size is not much bigger than the viewport but so that the entire
// viewport is visible.
const windowWidth = viewportWidth + 50;
const windowHeight = viewportHeight + 200;
const headless = !process.env['DEBUG_TEST'];
const envSlowMo = process.env['STRESS'] ? 50 : undefined;
const envThrottleRate = process.env['STRESS'] ? 3 : 1;
const TEST_SERVER_TYPE = getTestRunnerConfigSetting<string>('test-server-type', 'hosted-mode');
let browser: puppeteer.Browser;
let frontendTab: DevToolsFrontendTab;
let targetTab: TargetTab;
const envChromeBinary = getTestRunnerConfigSetting<string>('chrome-binary-path', process.env['CHROME_BIN'] || '');
const envChromeFeatures = getTestRunnerConfigSetting<string>('chrome-features', process.env['CHROME_FEATURES'] || '');
function launchChrome() {
// Use port 0 to request any free port.
const enabledFeatures = ['Portals', 'PortalsCrossOrigin', 'PartitionedCookies'];
const launchArgs = [
'--remote-debugging-port=0', '--enable-experimental-web-platform-features',
// This fingerprint may be generated from the certificate using
// openssl x509 -noout -pubkey -in scripts/hosted_mode/cert.pem | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64
'--ignore-certificate-errors-spki-list=KLy6vv6synForXwI6lDIl+D3ZrMV6Y1EMTY6YpOcAos=',
'--site-per-process', // Default on Desktop anyway, but ensure that we always use out-of-process frames when we intend to.
'--host-resolver-rules=MAP *.test 127.0.0.1', '--disable-gpu',
'--enable-blink-features=CSSContainerQueries,HighlightInheritance', // TODO(crbug.com/1218390) Remove globally enabled flags and conditionally enable them
];
const opts: puppeteer.LaunchOptions&puppeteer.BrowserLaunchArgumentOptions&puppeteer.BrowserConnectOptions = {
headless,
executablePath: envChromeBinary,
dumpio: !headless,
slowMo: envSlowMo,
};
// Always set the default viewport because setting only the window size for
// headful mode would result in much smaller actual viewport.
opts.defaultViewport = {width: viewportWidth, height: viewportHeight};
// Toggle either viewport or window size depending on headless vs not.
if (!headless) {
launchArgs.push(`--window-size=${windowWidth},${windowHeight}`);
}
if (envChromeFeatures) {
enabledFeatures.push(envChromeFeatures);
}
launchArgs.push(`--enable-features=${enabledFeatures.join(',')}`);
opts.args = launchArgs;
return puppeteer.launch(opts);
}
async function loadTargetPageAndFrontend(testServerPort: number) {
browser = await launchChrome();
setupBrowserProcessIO(browser);
// Load the target page.
targetTab = await TargetTab.create(browser);
// Create the frontend - the page that will be under test. This will be either
// DevTools Frontend in hosted mode, or the component docs in docs test mode.
let frontend: puppeteer.Page;
if (TEST_SERVER_TYPE === 'hosted-mode') {
/**
* In hosted mode we run the DevTools and test against it.
*/
frontendTab = await DevToolsFrontendTab.create({browser, testServerPort, targetId: targetTab.targetId()});
frontend = frontendTab.page;
} else if (TEST_SERVER_TYPE === 'component-docs') {
/**
* In the component docs mode it points to the page where we load component
* doc examples, so let's just set it to an empty page for now.
*/
frontend = await browser.newPage();
installPageErrorHandlers(frontend);
await loadEmptyPageAndWaitForContent(frontend);
} else {
throw new Error(`Unknown TEST_SERVER_TYPE "${TEST_SERVER_TYPE}"`);
}
setBrowserAndPages({target: targetTab.page, frontend, browser});
}
export async function resetPages() {
await targetTab.reset();
// Under stress conditions throttle the CPU down.
await throttleCPUIfRequired();
if (TEST_SERVER_TYPE === 'hosted-mode') {
await frontendTab.reset();
} else if (TEST_SERVER_TYPE === 'component-docs') {
// Reset the frontend back to an empty page for the component docs server.
const {frontend} = getBrowserAndPages();
await loadEmptyPageAndWaitForContent(frontend);
}
}
async function throttleCPUIfRequired(): Promise<void> {
const {frontend} = getBrowserAndPages();
// Under stress conditions throttle the CPU down.
if (envThrottleRate !== 1) {
console.log(`Throttling CPU: ${envThrottleRate}x slowdown`);
const client = await frontend.target().createCDPSession();
await client.send('Emulation.setCPUThrottlingRate', {rate: envThrottleRate});
}
}
export async function reloadDevTools(options?: DevToolsFrontendReloadOptions) {
await frontendTab.reload(options);
}
// Can be run multiple times in the same process.
export async function preFileSetup(serverPort: number) {
setTestServerPort(serverPort);
registerHandlers();
await loadTargetPageAndFrontend(serverPort);
}
// Can be run multiple times in the same process.
export async function postFileTeardown() {
// We need to kill the browser before we stop the hosted mode server.
// That's because the browser could continue to make network requests,
// even after we would have closed the server. If we did so, the requests
// would fail and the test would crash on closedown. This only happens
// for the very last test that runs.
await browser.close();
clearPuppeteerState();
dumpCollectedErrors();
}
export function collectCoverageFromPage(): Promise<CoverageMapData|undefined> {
const {frontend} = getBrowserAndPages();
return frontend.evaluate('window.__coverage__');
}
export function getDevToolsFrontendHostname(): string {
return frontendTab.hostname();
}