src/core/gatherer.ts (113 lines of code) (raw):

/** * MIT License * * Copyright (c) 2020-present, Elastic NV * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ import { chromium, ChromiumBrowser, BrowserContext, request as apiRequest, selectors, } from 'playwright-core'; import { PluginManager } from '../plugins'; import { log } from './logger'; import { Driver, NetworkConditions, RunOptions } from '../common_types'; // Default timeout for Playwright actions and Navigations const DEFAULT_TIMEOUT = 50000; /** * Purpose of the Gatherer is to set up the necessary browser driver * related capabilities for the runner to run all journeys */ export class Gatherer { static browser: ChromiumBrowser; static pluginManager: PluginManager; static async launchBrowser(options: RunOptions) { if (Gatherer.browser != null) { return; } log('Gatherer: setup browser'); const { wsEndpoint, playwrightOptions } = options; if (wsEndpoint) { log(`Gatherer: connecting to WS endpoint: ${wsEndpoint}`); Gatherer.browser = await chromium.connect(wsEndpoint); } else { Gatherer.browser = await chromium.launch({ ...playwrightOptions, args: [ ...(playwrightOptions?.headless ? ['--disable-gpu'] : []), ...(playwrightOptions?.args ?? []), ], }); } // Register sig int handler to close the browser process.on('SIGINT', async () => { await Gatherer.stop(); process.exit(130); }); } static async setupDriver(options: RunOptions): Promise<Driver> { await Gatherer.launchBrowser(options); const { playwrightOptions } = options; const context = await Gatherer.browser.newContext({ ...playwrightOptions, userAgent: await Gatherer.getUserAgent(playwrightOptions?.userAgent), }); // Set timeouts for actions and navigations context.setDefaultTimeout( playwrightOptions?.actionTimeout ?? DEFAULT_TIMEOUT ); context.setDefaultNavigationTimeout( playwrightOptions?.navigationTimeout ?? DEFAULT_TIMEOUT ); // TODO: Network throttling via chrome devtools emulation is disabled for now. // See docs/throttling.md for more details. // Gatherer.setNetworkConditions(context, networkConditions); if (playwrightOptions?.testIdAttribute) { selectors.setTestIdAttribute(playwrightOptions.testIdAttribute); } const page = await context.newPage(); const client = await context.newCDPSession(page); const request = await apiRequest.newContext({ ...playwrightOptions }); return { browser: Gatherer.browser, context, page, client, request }; } static async getUserAgent(userAgent?: string) { if (!userAgent) { const session = await Gatherer.browser.newBrowserCDPSession(); ({ userAgent } = await session.send('Browser.getVersion')); return userAgent + ' Elastic/Synthetics'; } return userAgent; } static setNetworkConditions( context: BrowserContext, networkConditions: NetworkConditions ) { if (networkConditions) { context.on('page', page => { const context = page.context(); const emulatePromise = context .newCDPSession(page) .then(client => client.send('Network.emulateNetworkConditions', networkConditions) ); /** * Guard against pages that gets closed before the emulation kicks to capture * unhandled rejections from accessing the CDP session of closed page */ Promise.race([ new Promise<void>(resolve => page.on('close', () => resolve())), emulatePromise, ]); }); } } /** * Starts recording all events related to the v8 devtools protocol * https://chromedevtools.github.io/devtools-protocol/v8/ */ static async beginRecording(driver: Driver, options: RunOptions) { log('Gatherer: started recording'); const { network, metrics } = options; Gatherer.pluginManager = new PluginManager(driver); Gatherer.pluginManager.registerAll(options); const plugins = [await Gatherer.pluginManager.start('browserconsole')]; network && plugins.push(await Gatherer.pluginManager.start('network')); metrics && plugins.push(await Gatherer.pluginManager.start('performance')); await Promise.all(plugins); return Gatherer.pluginManager; } static async endRecording() { log('Gatherer: ended recording'); await Gatherer.pluginManager.unregisterAll(); } static async dispose(driver: Driver) { log(`Gatherer: closing all contexts`); await driver.request.dispose(); await driver.context.close(); } static async stop() { log(`Gatherer: closing browser`); if (Gatherer.browser) { await Gatherer.browser.close(); Gatherer.browser = null; } } }