Skip to content

Commit

Permalink
[7.17] Puppeteer v18.1 (elastic#143485) (elastic#143710)
Browse files Browse the repository at this point in the history
* Puppeteer v18.1

* fix types & remove any

* remove referencing puppeteer types

* fix unwanted diff

* fix unused translations

* fix pretty

* fix wait_for_elements implementation

* remove more puppeteer references
  • Loading branch information
tsullivan authored Oct 27, 2022
1 parent 420c83a commit 1d0ab38
Show file tree
Hide file tree
Showing 16 changed files with 190 additions and 199 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@
"prop-types": "^15.7.2",
"proxy-from-env": "1.0.0",
"puid": "1.0.7",
"puppeteer": "^10.2.0",
"puppeteer": "18.1.0",
"query-string": "^6.13.2",
"random-word-slugs": "^0.0.5",
"raw-loader": "^3.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import { i18n } from '@kbn/i18n';
import { map, truncate } from 'lodash';
import open from 'opn';
import puppeteer, { ElementHandle, EvaluateFn, SerializableOrJSHandle } from 'puppeteer';
import { parse as parseUrl } from 'url';
import type { LocatorParams } from '../../../../common/types';
import { REPORTING_REDIRECT_LOCATOR_STORE_KEY } from '../../../../common/constants';
Expand All @@ -30,9 +29,9 @@ interface WaitForSelectorOpts {
timeout: number;
}

interface EvaluateOpts {
fn: EvaluateFn;
args: SerializableOrJSHandle[];
interface EvaluateOpts<A extends unknown[]> {
fn: any;
args: unknown[];
}

interface EvaluateMetaOpts {
Expand All @@ -57,7 +56,7 @@ interface InterceptedRequest {
const WAIT_FOR_DELAY_MS: number = 100;

export class HeadlessChromiumDriver {
private readonly page: puppeteer.Page;
private readonly page: any;
private readonly inspect: boolean;
private readonly networkPolicy: NetworkPolicy;

Expand All @@ -67,7 +66,7 @@ export class HeadlessChromiumDriver {

constructor(
core: ReportingCore,
page: puppeteer.Page,
page: unknown,
{ inspect, networkPolicy }: ChromiumDriverOptions
) {
this.core = core;
Expand Down Expand Up @@ -119,7 +118,10 @@ export class HeadlessChromiumDriver {
await this.page.evaluateOnNewDocument(this.core.getEnableScreenshotMode());

if (layout) {
await this.page.evaluateOnNewDocument(this.core.getSetScreenshotLayout(), layout.id);
await this.page.evaluateOnNewDocument(
this.core.getSetScreenshotLayout() as (arg0: string) => void,
layout.id
);
}

if (locator) {
Expand Down Expand Up @@ -189,22 +191,21 @@ export class HeadlessChromiumDriver {
return undefined;
}

public async evaluate(
{ fn, args = [] }: EvaluateOpts,
public async evaluate<A extends unknown[], T = void>(
{ fn, args = [] }: EvaluateOpts<A>,
meta: EvaluateMetaOpts,
logger: LevelLogger
) {
): Promise<T> {
logger.debug(`evaluate ${meta.context}`);
const result = await this.page.evaluate(fn, ...args);
return result;
return this.page.evaluate(fn, ...args) as Promise<T>;
}

public async waitForSelector(
selector: string,
opts: WaitForSelectorOpts,
context: EvaluateMetaOpts,
logger: LevelLogger
): Promise<ElementHandle<Element>> {
): Promise<unknown> {
const { timeout } = opts;
logger.debug(`waitForSelector ${selector}`);
const resp = await this.page.waitForSelector(selector, { timeout }); // override default 30000ms
Expand All @@ -217,36 +218,16 @@ export class HeadlessChromiumDriver {
return resp;
}

public async waitFor(
{
fn,
args,
toEqual,
timeout,
}: {
fn: EvaluateFn;
args: SerializableOrJSHandle[];
toEqual: number;
timeout: number;
},
context: EvaluateMetaOpts,
logger: LevelLogger
): Promise<void> {
const startTime = Date.now();

while (true) {
const result = await this.evaluate({ fn, args }, context, logger);
if (result === toEqual) {
return;
}

if (Date.now() - startTime > timeout) {
throw new Error(
`Timed out waiting for the items selected to equal ${toEqual}. Found: ${result}. Context: ${context.context}`
);
}
await new Promise((r) => setTimeout(r, WAIT_FOR_DELAY_MS));
}
public async waitFor<T extends unknown[] = unknown[]>({
fn,
args,
timeout,
}: {
fn: unknown;
args: unknown[];
timeout: number;
}): Promise<void> {
await this.page.waitForFunction(fn, { timeout, polling: WAIT_FOR_DELAY_MS }, ...args);
}

public async setViewport(
Expand All @@ -271,9 +252,8 @@ export class HeadlessChromiumDriver {
return;
}

// @ts-ignore
// FIXME: retrieve the client in open() and pass in the client
const client = this.page._client;
// FIXME: retrieve the client in open() and pass in the client?
const client = this.page._client();

// We have to reach into the Chrome Devtools Protocol to apply headers as using
// puppeteer's API will cause map tile requests to hang indefinitely:
Expand Down Expand Up @@ -348,7 +328,7 @@ export class HeadlessChromiumDriver {
// Even though 3xx redirects go through our request
// handler, we should probably inspect responses just to
// avoid being bamboozled by some malicious request
this.page.on('response', (interceptedResponse: puppeteer.HTTPResponse) => {
this.page.on('response', (interceptedResponse: any) => {
const interceptedUrl = interceptedResponse.url();
const allowed = !interceptedUrl.startsWith('file://');

Expand All @@ -374,12 +354,12 @@ export class HeadlessChromiumDriver {
// In order to get the inspector running, we have to know the page's internal ID (again, private)
// in order to construct the final debugging URL.

const client = this.page._client();
const target = this.page.target();
const client = await target.createCDPSession();
const targetId = target._targetId;

await client.send('Debugger.enable');
await client.send('Debugger.pause');
const targetId = target._targetId;
const wsEndpoint = this.page.browser().wsEndpoint();
const { port } = parseUrl(wsEndpoint);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import del from 'del';
import apm from 'elastic-apm-node';
import fs from 'fs';
import path from 'path';
import puppeteer from 'puppeteer';
import * as Rx from 'rxjs';
import { InnerSubscriber } from 'rxjs/internal/InnerSubscriber';
import { ignoreElements, map, mergeMap, tap } from 'rxjs/operators';
Expand All @@ -25,6 +24,8 @@ import { safeChildProcess } from '../../safe_child_process';
import { HeadlessChromiumDriver } from '../driver';
import { args } from './args';
import { getMetrics, Metrics } from './metrics';
// @ts-ignore
import { launch } from './puppeteer_launcher';

interface CreatePageOptions {
browserTimezone?: string;
Expand Down Expand Up @@ -89,24 +90,20 @@ export class HeadlessChromiumDriverFactory {
const chromiumArgs = this.getChromiumArgs();
logger.debug(`Chromium launch args set to: ${chromiumArgs}`);

let browser: puppeteer.Browser;
let page: puppeteer.Page;
let devTools: puppeteer.CDPSession | undefined;
let browser: any;
let page: any;
let devTools: any;
let startMetrics: Metrics | undefined;

try {
browser = await puppeteer.launch({
pipe: !this.browserConfig.inspect,
userDataDir: this.userDataDir,
executablePath: this.binaryPath,
ignoreHTTPSErrors: true,
handleSIGHUP: false,
args: chromiumArgs,
defaultViewport: viewport,
env: {
TZ: browserTimezone,
},
});
browser = await launch(
this.browserConfig,
this.userDataDir,
this.binaryPath,
chromiumArgs,
viewport,
browserTimezone
);

page = await browser.newPage();
devTools = await page.target().createCDPSession();
Expand Down Expand Up @@ -209,8 +206,8 @@ export class HeadlessChromiumDriverFactory {
});
}

getBrowserLogger(page: puppeteer.Page, logger: LevelLogger): Rx.Observable<void> {
const consoleMessages$ = Rx.fromEvent<puppeteer.ConsoleMessage>(page, 'console').pipe(
getBrowserLogger(page: any, logger: LevelLogger): Rx.Observable<void> {
const consoleMessages$ = Rx.fromEvent<any>(page, 'console').pipe(
map((line) => {
const formatLine = () => `{ text: "${line.text()?.trim()}", url: ${line.location()?.url} }`;

Expand All @@ -235,7 +232,7 @@ export class HeadlessChromiumDriverFactory {
})
);

const pageRequestFailed$ = Rx.fromEvent<puppeteer.HTTPRequest>(page, 'requestfailed').pipe(
const pageRequestFailed$ = Rx.fromEvent<any>(page, 'requestfailed').pipe(
map((req) => {
const failure = req.failure && req.failure();
if (failure) {
Expand All @@ -249,7 +246,7 @@ export class HeadlessChromiumDriverFactory {
return Rx.merge(consoleMessages$, uncaughtExceptionPageError$, pageRequestFailed$);
}

getProcessLogger(browser: puppeteer.Browser, logger: LevelLogger): Rx.Observable<void> {
getProcessLogger(browser: any, logger: LevelLogger): Rx.Observable<void> {
const childProcess = browser.process();
// NOTE: The browser driver can not observe stdout and stderr of the child process
// Puppeteer doesn't give a handle to the original ChildProcess object
Expand All @@ -269,7 +266,7 @@ export class HeadlessChromiumDriverFactory {
return processClose$; // ideally, this would also merge with observers for stdout and stderr
}

getPageExit(browser: puppeteer.Browser, page: puppeteer.Page) {
getPageExit(browser: any, page: any) {
const pageError$ = Rx.fromEvent<Error>(page, 'error').pipe(
mergeMap((err) => {
return Rx.throwError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,8 @@
* 2.0.
*/

import type { Metrics as PuppeteerMetrics } from 'puppeteer';
import { cpus } from 'os';

declare module 'puppeteer' {
interface CDPSession {
send(command: 'Performance.getMetrics'): Promise<RawMetrics>;
}
}

type RawMetrics = Metrics;

export interface Metrics {
metrics: Metric[];
}
Expand All @@ -25,8 +16,10 @@ interface Metric {
value: unknown;
}

interface NormalizedMetrics extends Required<PuppeteerMetrics> {
interface NormalizedMetrics {
Timestamp: number;
ProcessTime: number;
JSHeapTotalSize: number;
}

interface PerformanceMetrics {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import puppeteer from 'puppeteer';

/**
* This function exists as a JS file in order to prevent the v4.1.3 TypeScript compiler from interpreting types
* in the Puppeteer node module.
*/

export async function launch(
browserConfig,
userDataDir,
binaryPath,
chromiumArgs,
viewport,
browserTimezone
) {
return await puppeteer.launch({
pipe: !browserConfig.inspect,
userDataDir: userDataDir,
executablePath: binaryPath,
ignoreHTTPSErrors: true,
handleSIGHUP: false,
args: chromiumArgs,
defaultViewport: viewport,
env: {
TZ: browserTimezone,
},
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,16 @@ export const getElementPositionAndAttributes = async (
): Promise<ElementsPositionAndAttribute[] | null> => {
const endTrace = startTrace('get_element_position_data', 'read');
const { screenshot: screenshotSelector } = layout.selectors; // data-shared-items-container
const screenshotAttributes = { title: 'data-title', description: 'data-description' };

let elementsPositionAndAttributes: ElementsPositionAndAttribute[] | null;
try {
elementsPositionAndAttributes = await browser.evaluate(
elementsPositionAndAttributes = await browser.evaluate<
[typeof screenshotSelector, typeof screenshotAttributes],
ElementsPositionAndAttribute[] | null
>(
{
fn: (selector, attributes) => {
fn: (selector: string, attributes: typeof screenshotAttributes) => {
const elements = Array.from(document.querySelectorAll<Element>(selector));
const results: ElementsPositionAndAttribute[] = [];

Expand All @@ -42,16 +47,19 @@ export const getElementPositionAndAttributes = async (
y: window.scrollY,
},
},
attributes: Object.keys(attributes).reduce((result: AttributesMap, key) => {
const attribute = attributes[key];
result[key] = element.getAttribute(attribute);
return result;
}, {} as AttributesMap),
attributes: Object.keys(attributes).reduce<AttributesMap>(
(result: AttributesMap, key) => {
const attribute = attributes[key as keyof typeof attributes];
result[key] = element.getAttribute(attribute);
return result;
},
{}
),
});
}
return results;
},
args: [screenshotSelector, { title: 'data-title', description: 'data-description' }],
args: [screenshotSelector, screenshotAttributes],
},
{ context: CONTEXT_ELEMENTATTRIBUTES },
logger
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ export const getNumberOfItems = async (

// returns the value of the `itemsCountAttribute` if it's there, otherwise
// we just count the number of `itemSelector`: the number of items already rendered
itemsCount = await browser.evaluate(
itemsCount = await browser.evaluate<string[], number>(
{
fn: (selector, countAttribute) => {
fn: (selector: string, countAttribute: string) => {
const elementWithCount = document.querySelector(`[${countAttribute}]`);
if (elementWithCount && elementWithCount != null) {
const count = elementWithCount.getAttribute(countAttribute);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ export const getRenderErrors = async (
): Promise<undefined | string[]> => {
const endTrace = startTrace('get_render_errors', 'read');
logger.debug('reading render errors');
const errorsFound: undefined | string[] = await browser.evaluate(
const errorsFound: undefined | string[] = await browser.evaluate<string[], string[] | undefined>(
{
fn: (errorSelector, errorAttribute) => {
fn: (errorSelector: string, errorAttribute: string) => {
const visualizations: Element[] = Array.from(document.querySelectorAll(errorSelector));
const errors: string[] = [];

Expand Down
Loading

0 comments on commit 1d0ab38

Please sign in to comment.