From 95c99e84b8550e2e37fa57be989858f5c86d37c0 Mon Sep 17 00:00:00 2001 From: Nikolay Vitkov <34244704+Lightning00Blade@users.noreply.github.com> Date: Mon, 27 Mar 2023 11:39:40 +0200 Subject: [PATCH] chore: add PDF print for BiDi (#9914) --- docs/api/puppeteer.page.createpdfstream.md | 2 - docs/api/puppeteer.page.md | 2 +- docs/api/puppeteer.page.pdf.md | 14 ++- package-lock.json | 16 +-- packages/puppeteer-core/package.json | 2 +- packages/puppeteer-core/src/api/Page.ts | 95 ++++++++++++++- packages/puppeteer-core/src/common/Frame.ts | 6 +- .../puppeteer-core/src/common/PDFOptions.ts | 51 +++++--- packages/puppeteer-core/src/common/Page.ts | 114 ++++-------------- .../src/common/bidi/Connection.ts | 8 +- .../puppeteer-core/src/common/bidi/Page.ts | 63 +++++++++- packages/puppeteer-core/src/common/util.ts | 12 +- test/TestExpectations.json | 20 ++- test/src/page.spec.ts | 22 +--- 14 files changed, 266 insertions(+), 161 deletions(-) diff --git a/docs/api/puppeteer.page.createpdfstream.md b/docs/api/puppeteer.page.createpdfstream.md index a4a5a650c86..e84764bd54d 100644 --- a/docs/api/puppeteer.page.createpdfstream.md +++ b/docs/api/puppeteer.page.createpdfstream.md @@ -26,8 +26,6 @@ Promise<Readable> ## Remarks -NOTE: PDF generation is only supported in Chrome headless mode. - To generate a PDF with the `screen` media type, call [\`page.emulateMediaType('screen')\`](./puppeteer.page.emulatemediatype.md) before calling `page.pdf()`. By default, `page.pdf()` generates a pdf with modified colors for printing. Use the [\`-webkit-print-color-adjust\`](https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-print-color-adjust) property to force rendering of exact colors. diff --git a/docs/api/puppeteer.page.md b/docs/api/puppeteer.page.md index a8bcd6278aa..e73ba4f149f 100644 --- a/docs/api/puppeteer.page.md +++ b/docs/api/puppeteer.page.md @@ -122,7 +122,7 @@ page.off('request', logRequest); | [off(eventName, handler)](./puppeteer.page.off.md) | | | | [on(eventName, handler)](./puppeteer.page.on.md) | |

Listen to page events.

:::note

This method exists to define event typings and handle proper wireup of cooperative request interception. Actual event listening and dispatching is delegated to [EventEmitter](./puppeteer.eventemitter.md).

:::

| | [once(eventName, handler)](./puppeteer.page.once.md) | | | -| [pdf(options)](./puppeteer.page.pdf.md) | | | +| [pdf(options)](./puppeteer.page.pdf.md) | | Generates a PDF of the page with the print CSS media type. | | [queryObjects(prototypeHandle)](./puppeteer.page.queryobjects.md) | | This method iterates the JavaScript heap and finds all objects with the given prototype. | | [reload(options)](./puppeteer.page.reload.md) | | | | [screenshot(options)](./puppeteer.page.screenshot.md) | | | diff --git a/docs/api/puppeteer.page.pdf.md b/docs/api/puppeteer.page.pdf.md index 0a7b357b271..e96e341e9e3 100644 --- a/docs/api/puppeteer.page.pdf.md +++ b/docs/api/puppeteer.page.pdf.md @@ -4,6 +4,8 @@ sidebar_label: Page.pdf # Page.pdf() method +Generates a PDF of the page with the `print` CSS media type. + #### Signature: ```typescript @@ -14,10 +16,16 @@ class Page { ## Parameters -| Parameter | Type | Description | -| --------- | --------------------------------------- | ------------ | -| options | [PDFOptions](./puppeteer.pdfoptions.md) | _(Optional)_ | +| Parameter | Type | Description | +| --------- | --------------------------------------- | -------------------------------------------- | +| options | [PDFOptions](./puppeteer.pdfoptions.md) | _(Optional)_ options for generating the PDF. | **Returns:** Promise<Buffer> + +## Remarks + +To generate a PDF with the `screen` media type, call [\`page.emulateMediaType('screen')\`](./puppeteer.page.emulatemediatype.md) before calling `page.pdf()`. + +By default, `page.pdf()` generates a pdf with modified colors for printing. Use the [\`-webkit-print-color-adjust\`](https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-print-color-adjust) property to force rendering of exact colors. diff --git a/package-lock.json b/package-lock.json index 70d2fd85f6b..48a5f3c0680 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2587,9 +2587,9 @@ "license": "ISC" }, "node_modules/chromium-bidi": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.5.tgz", - "integrity": "sha512-rkav9YzRfAshSTG3wNXF7P7yNiI29QAo1xBXElPoCoSQR5n20q3cOyVhDv6S7+GlF/CJ/emUxlQiR0xOPurkGg==", + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.6.tgz", + "integrity": "sha512-TQOkWRaLI/IWvoP8XC+7jO4uHTIiAUiklXU1T0qszlUFEai9LgKXIBXy3pOS3EnQZ3bQtMbKUPkug0fTAEHCSw==", "dependencies": { "mitt": "3.0.0" }, @@ -9481,7 +9481,7 @@ "version": "19.8.0", "license": "Apache-2.0", "dependencies": { - "chromium-bidi": "0.4.5", + "chromium-bidi": "0.4.6", "cross-fetch": "3.1.5", "debug": "4.3.4", "devtools-protocol": "0.0.1107588", @@ -11371,9 +11371,9 @@ "version": "1.1.4" }, "chromium-bidi": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.5.tgz", - "integrity": "sha512-rkav9YzRfAshSTG3wNXF7P7yNiI29QAo1xBXElPoCoSQR5n20q3cOyVhDv6S7+GlF/CJ/emUxlQiR0xOPurkGg==", + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.6.tgz", + "integrity": "sha512-TQOkWRaLI/IWvoP8XC+7jO4uHTIiAUiklXU1T0qszlUFEai9LgKXIBXy3pOS3EnQZ3bQtMbKUPkug0fTAEHCSw==", "requires": { "mitt": "3.0.0" } @@ -14462,7 +14462,7 @@ "puppeteer-core": { "version": "file:packages/puppeteer-core", "requires": { - "chromium-bidi": "0.4.5", + "chromium-bidi": "0.4.6", "cross-fetch": "3.1.5", "debug": "4.3.4", "devtools-protocol": "0.0.1107588", diff --git a/packages/puppeteer-core/package.json b/packages/puppeteer-core/package.json index 2ed554a4e90..66bbb73ffe6 100644 --- a/packages/puppeteer-core/package.json +++ b/packages/puppeteer-core/package.json @@ -131,7 +131,7 @@ "author": "The Chromium Authors", "license": "Apache-2.0", "dependencies": { - "chromium-bidi": "0.4.5", + "chromium-bidi": "0.4.6", "cross-fetch": "3.1.5", "debug": "4.3.4", "devtools-protocol": "0.0.1107588", diff --git a/packages/puppeteer-core/src/api/Page.ts b/packages/puppeteer-core/src/api/Page.ts index d092a6c1c35..912442b1863 100644 --- a/packages/puppeteer-core/src/api/Page.ts +++ b/packages/puppeteer-core/src/api/Page.ts @@ -43,7 +43,12 @@ import type { import type {WaitForSelectorOptions} from '../common/IsolatedWorld.js'; import type {PuppeteerLifeCycleEvent} from '../common/LifecycleWatcher.js'; import type {Credentials, NetworkConditions} from '../common/NetworkManager.js'; -import type {PDFOptions} from '../common/PDFOptions.js'; +import { + LowerCasePaperFormat, + ParsedPDFOptions, + PDFOptions, + paperFormats, +} from '../common/PDFOptions.js'; import type {Viewport} from '../common/PuppeteerViewport.js'; import type {Target} from '../common/Target.js'; import type {Tracing} from '../common/Tracing.js'; @@ -53,7 +58,9 @@ import type { HandleFor, NodeFor, } from '../common/types.js'; +import {isNumber, isString} from '../common/util.js'; import type {WebWorker} from '../common/WebWorker.js'; +import {assert} from '../util/assert.js'; import type {Browser} from './Browser.js'; import type {BrowserContext} from './BrowserContext.js'; @@ -2136,12 +2143,58 @@ export class Page extends EventEmitter { throw new Error('Not implemented'); } + /** + * @internal + */ + _getPDFOptions(options: PDFOptions = {}): ParsedPDFOptions { + const defaults = { + scale: 1, + displayHeaderFooter: false, + headerTemplate: '', + footerTemplate: '', + printBackground: false, + landscape: false, + pageRanges: '', + preferCSSPageSize: false, + omitBackground: false, + timeout: 30000, + }; + + let width = 8.5; + let height = 11; + if (options.format) { + const format = + paperFormats[options.format.toLowerCase() as LowerCasePaperFormat]; + assert(format, 'Unknown paper format: ' + options.format); + width = format.width; + height = format.height; + } else { + width = convertPrintParameterToInches(options.width) ?? width; + height = convertPrintParameterToInches(options.height) ?? height; + } + + const margin = { + top: convertPrintParameterToInches(options.margin?.top) || 0, + left: convertPrintParameterToInches(options.margin?.left) || 0, + bottom: convertPrintParameterToInches(options.margin?.bottom) || 0, + right: convertPrintParameterToInches(options.margin?.right) || 0, + }; + + const output = { + ...defaults, + ...options, + width, + height, + margin, + }; + + return output; + } + /** * Generates a PDF of the page with the `print` CSS media type. * @remarks * - * NOTE: PDF generation is only supported in Chrome headless mode. - * * To generate a PDF with the `screen` media type, call * {@link Page.emulateMediaType | `page.emulateMediaType('screen')`} before * calling `page.pdf()`. @@ -2159,8 +2212,7 @@ export class Page extends EventEmitter { } /** - * @param options - - * @returns + * {@inheritDoc Page.createPDFStream} */ async pdf(options?: PDFOptions): Promise; async pdf(): Promise { @@ -2619,3 +2671,36 @@ export const unitToPixels = { cm: 37.8, mm: 3.78, }; + +function convertPrintParameterToInches( + parameter?: string | number +): number | undefined { + if (typeof parameter === 'undefined') { + return undefined; + } + let pixels; + if (isNumber(parameter)) { + // Treat numbers as pixel values to be aligned with phantom's paperSize. + pixels = parameter; + } else if (isString(parameter)) { + const text = parameter; + let unit = text.substring(text.length - 2).toLowerCase(); + let valueText = ''; + if (unit in unitToPixels) { + valueText = text.substring(0, text.length - 2); + } else { + // In case of unknown unit try to parse the whole parameter as number of pixels. + // This is consistent with phantom's paperSize behavior. + unit = 'px'; + valueText = text; + } + const value = Number(valueText); + assert(!isNaN(value), 'Failed to parse parameter value: ' + text); + pixels = value * unitToPixels[unit as keyof typeof unitToPixels]; + } else { + throw new Error( + 'page.pdf() Cannot handle parameter type: ' + typeof parameter + ); + } + return pixels / 96; +} diff --git a/packages/puppeteer-core/src/common/Frame.ts b/packages/puppeteer-core/src/common/Frame.ts index 07a86c6be6a..158977d5cdf 100644 --- a/packages/puppeteer-core/src/common/Frame.ts +++ b/packages/puppeteer-core/src/common/Frame.ts @@ -40,7 +40,7 @@ import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js'; import {LazyArg} from './LazyArg.js'; import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js'; import {EvaluateFunc, EvaluateFuncWith, HandleFor, NodeFor} from './types.js'; -import {importFS} from './util.js'; +import {importFSPromises} from './util.js'; /** * @public @@ -883,9 +883,9 @@ export class Frame { } if (path) { - let fs: typeof import('fs').promises; + let fs: typeof import('fs/promises'); try { - fs = (await importFS()).promises; + fs = await importFSPromises(); } catch (error) { if (error instanceof TypeError) { throw new Error( diff --git a/packages/puppeteer-core/src/common/PDFOptions.ts b/packages/puppeteer-core/src/common/PDFOptions.ts index c11c6c6814f..bca6d9eb159 100644 --- a/packages/puppeteer-core/src/common/PDFOptions.ts +++ b/packages/puppeteer-core/src/common/PDFOptions.ts @@ -184,19 +184,38 @@ export interface PaperFormatDimensions { /** * @internal */ -export const _paperFormats: Record< - LowerCasePaperFormat, - PaperFormatDimensions -> = { - letter: {width: 8.5, height: 11}, - legal: {width: 8.5, height: 14}, - tabloid: {width: 11, height: 17}, - ledger: {width: 17, height: 11}, - a0: {width: 33.1, height: 46.8}, - a1: {width: 23.4, height: 33.1}, - a2: {width: 16.54, height: 23.4}, - a3: {width: 11.7, height: 16.54}, - a4: {width: 8.27, height: 11.7}, - a5: {width: 5.83, height: 8.27}, - a6: {width: 4.13, height: 5.83}, -} as const; +export interface ParsedPDFOptionsInterface { + width: number; + height: number; + margin: { + top: number; + bottom: number; + left: number; + right: number; + }; +} + +/** + * @internal + */ +export type ParsedPDFOptions = Required< + Omit & ParsedPDFOptionsInterface +>; + +/** + * @internal + */ +export const paperFormats: Record = + { + letter: {width: 8.5, height: 11}, + legal: {width: 8.5, height: 14}, + tabloid: {width: 11, height: 17}, + ledger: {width: 17, height: 11}, + a0: {width: 33.1, height: 46.8}, + a1: {width: 23.4, height: 33.1}, + a2: {width: 16.54, height: 23.4}, + a3: {width: 11.7, height: 16.54}, + a4: {width: 8.27, height: 11.7}, + a5: {width: 5.83, height: 8.27}, + a6: {width: 4.13, height: 5.83}, + } as const; diff --git a/packages/puppeteer-core/src/common/Page.ts b/packages/puppeteer-core/src/common/Page.ts index 21c13514d71..3e93dbf7f3d 100644 --- a/packages/puppeteer-core/src/common/Page.ts +++ b/packages/puppeteer-core/src/common/Page.ts @@ -70,7 +70,7 @@ import { NetworkConditions, NetworkManagerEmittedEvents, } from './NetworkManager.js'; -import {LowerCasePaperFormat, PDFOptions, _paperFormats} from './PDFOptions.js'; +import {PDFOptions} from './PDFOptions.js'; import {Viewport} from './PuppeteerViewport.js'; import {Target} from './Target.js'; import {TargetManagerEmittedEvents} from './TargetManager.js'; @@ -91,8 +91,7 @@ import { getExceptionMessage, getReadableAsBuffer, getReadableFromProtocolStream, - importFS, - isNumber, + importFSPromises, isString, pageBindingInitString, releaseObject, @@ -1448,7 +1447,7 @@ export class CDPPage extends Page { if (options.path) { try { - const fs = (await importFS()).promises; + const fs = await importFSPromises(); await fs.writeFile(options.path, buffer); } catch (error) { if (error instanceof TypeError) { @@ -1471,68 +1470,37 @@ export class CDPPage extends Page { } override async createPDFStream(options: PDFOptions = {}): Promise { - const { - scale = 1, - displayHeaderFooter = false, - headerTemplate = '', - footerTemplate = '', - printBackground = false, - landscape = false, - pageRanges = '', - preferCSSPageSize = false, - margin = {}, - omitBackground = false, - timeout = 30000, - } = options; + const params = this._getPDFOptions(options); - let paperWidth = 8.5; - let paperHeight = 11; - if (options.format) { - const format = - _paperFormats[options.format.toLowerCase() as LowerCasePaperFormat]; - assert(format, 'Unknown paper format: ' + options.format); - paperWidth = format.width; - paperHeight = format.height; - } else { - paperWidth = convertPrintParameterToInches(options.width) || paperWidth; - paperHeight = - convertPrintParameterToInches(options.height) || paperHeight; - } - - const marginTop = convertPrintParameterToInches(margin.top) || 0; - const marginLeft = convertPrintParameterToInches(margin.left) || 0; - const marginBottom = convertPrintParameterToInches(margin.bottom) || 0; - const marginRight = convertPrintParameterToInches(margin.right) || 0; - - if (omitBackground) { + if (params.omitBackground) { await this.#setTransparentBackgroundColor(); } const printCommandPromise = this.#client.send('Page.printToPDF', { transferMode: 'ReturnAsStream', - landscape, - displayHeaderFooter, - headerTemplate, - footerTemplate, - printBackground, - scale, - paperWidth, - paperHeight, - marginTop, - marginBottom, - marginLeft, - marginRight, - pageRanges, - preferCSSPageSize, + landscape: params.landscape, + displayHeaderFooter: params.displayHeaderFooter, + headerTemplate: params.headerTemplate, + footerTemplate: params.footerTemplate, + printBackground: params.printBackground, + scale: params.scale, + paperWidth: params.width, + paperHeight: params.height, + marginTop: params.margin.top, + marginBottom: params.margin.bottom, + marginLeft: params.margin.left, + marginRight: params.margin.right, + pageRanges: params.pageRanges, + preferCSSPageSize: params.preferCSSPageSize, }); const result = await waitWithTimeout( printCommandPromise, 'Page.printToPDF', - timeout + params.timeout ); - if (omitBackground) { + if (params.omitBackground) { await this.#resetDefaultBackgroundColor(); } @@ -1688,43 +1656,3 @@ const supportedMetrics = new Set([ 'JSHeapUsedSize', 'JSHeapTotalSize', ]); - -const unitToPixels = { - px: 1, - in: 96, - cm: 37.8, - mm: 3.78, -}; - -function convertPrintParameterToInches( - parameter?: string | number -): number | undefined { - if (typeof parameter === 'undefined') { - return undefined; - } - let pixels; - if (isNumber(parameter)) { - // Treat numbers as pixel values to be aligned with phantom's paperSize. - pixels = parameter; - } else if (isString(parameter)) { - const text = parameter; - let unit = text.substring(text.length - 2).toLowerCase(); - let valueText = ''; - if (unit in unitToPixels) { - valueText = text.substring(0, text.length - 2); - } else { - // In case of unknown unit try to parse the whole parameter as number of pixels. - // This is consistent with phantom's paperSize behavior. - unit = 'px'; - valueText = text; - } - const value = Number(valueText); - assert(!isNaN(value), 'Failed to parse parameter value: ' + text); - pixels = value * unitToPixels[unit as keyof typeof unitToPixels]; - } else { - throw new Error( - 'page.pdf() Cannot handle parameter type: ' + typeof parameter - ); - } - return pixels / 96; -} diff --git a/packages/puppeteer-core/src/common/bidi/Connection.ts b/packages/puppeteer-core/src/common/bidi/Connection.ts index 05de314acf0..ff90c2902d1 100644 --- a/packages/puppeteer-core/src/common/bidi/Connection.ts +++ b/packages/puppeteer-core/src/common/bidi/Connection.ts @@ -55,6 +55,10 @@ interface Commands { params: Bidi.BrowsingContext.NavigateParameters; returnType: Bidi.BrowsingContext.NavigateResult; }; + 'browsingContext.print': { + params: Bidi.BrowsingContext.PrintParameters; + returnType: Bidi.BrowsingContext.PrintResult; + }; 'session.new': { params: {capabilities?: Record}; // TODO: Update Types in chromium bidi @@ -152,10 +156,10 @@ export class Connection extends EventEmitter { #maybeEmitOnContext(event: Bidi.Message.EventMessage) { let context: Context | undefined; // Context specific events - if ('context' in event.params) { + if ('context' in event.params && event.params.context) { context = this.#contexts.get(event.params.context); // `log.entryAdded` specific context - } else if ('source' in event.params && !!event.params.source.context) { + } else if ('source' in event.params && event.params.source.context) { context = this.#contexts.get(event.params.source.context); } context?.emit(event.method, event.params); diff --git a/packages/puppeteer-core/src/common/bidi/Page.ts b/packages/puppeteer-core/src/common/bidi/Page.ts index 4fbafd86459..9da38978117 100644 --- a/packages/puppeteer-core/src/common/bidi/Page.ts +++ b/packages/puppeteer-core/src/common/bidi/Page.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import type {Readable} from 'stream'; + import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js'; import {HTTPResponse} from '../../api/HTTPResponse.js'; @@ -25,8 +27,9 @@ import { import {isErrorLike} from '../../util/ErrorLike.js'; import {ConsoleMessage, ConsoleMessageLocation} from '../ConsoleMessage.js'; import {Handler} from '../EventEmitter.js'; +import {PDFOptions} from '../PDFOptions.js'; import {EvaluateFunc, HandleFor} from '../types.js'; -import {debugError} from '../util.js'; +import {debugError, importFSPromises, waitWithTimeout} from '../util.js'; import {Context, getBidiHandle} from './Context.js'; import {BidiSerializer} from './Serializer.js'; @@ -199,6 +202,64 @@ export class Page extends PageBase { return retVal; }); } + + override async pdf(options: PDFOptions = {}): Promise { + const {path = undefined} = options; + const params = this._getPDFOptions(options); + const {result} = await waitWithTimeout( + this.#context.connection.send('browsingContext.print', { + context: this.#context._contextId, + background: params.printBackground, + margin: params.margin, + orientation: params.landscape ? 'landscape' : 'portrait', + page: { + width: params.width, + height: params.height, + }, + pageRanges: params.pageRanges.split(', '), + scale: params.scale, + shrinkToFit: !params.preferCSSPageSize, + }), + 'browsingContext.print', + params.timeout + ); + + const buffer = Buffer.from(result.data, 'base64'); + + try { + if (path) { + const fs = await importFSPromises(); + + await fs.writeFile(path, buffer); + } + } catch (error) { + if (error instanceof TypeError) { + throw new Error( + 'Can only pass a file path in a Node-like environment.' + ); + } + throw error; + } + + return buffer; + } + + override async createPDFStream( + options?: PDFOptions | undefined + ): Promise { + const buffer = await this.pdf(options); + try { + const {Readable} = await import('stream'); + return Readable.from(buffer); + } catch (error) { + if (error instanceof TypeError) { + throw new Error( + 'Can only pass a file path in a Node-like environment.' + ); + } + throw error; + } + } } function isConsoleLogEntry( diff --git a/packages/puppeteer-core/src/common/util.ts b/packages/puppeteer-core/src/common/util.ts index 77f05497590..200b559f787 100644 --- a/packages/puppeteer-core/src/common/util.ts +++ b/packages/puppeteer-core/src/common/util.ts @@ -361,13 +361,15 @@ export async function waitWithTimeout( /** * @internal */ -let fs: typeof import('fs') | null = null; +let fs: typeof import('fs/promises') | null = null; /** * @internal */ -export async function importFS(): Promise { +export async function importFSPromises(): Promise< + typeof import('fs/promises') +> { if (!fs) { - fs = await import('fs'); + fs = await import('fs/promises'); } return fs; } @@ -381,9 +383,9 @@ export async function getReadableAsBuffer( ): Promise { const buffers = []; if (path) { - let fs: typeof import('fs').promises; + let fs: typeof import('fs/promises'); try { - fs = (await importFS()).promises; + fs = await importFSPromises(); } catch (error) { if (error instanceof TypeError) { throw new Error( diff --git a/test/TestExpectations.json b/test/TestExpectations.json index ca642c01db6..e163144a9dc 100644 --- a/test/TestExpectations.json +++ b/test/TestExpectations.json @@ -59,6 +59,12 @@ "parameters": ["webDriverBiDi"], "expectations": ["PASS", "TIMEOUT"] }, + { + "testIdPattern": "[page.spec] Page Page.pdf *", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[page.spec] Page Page.setContent *", "platforms": ["darwin", "linux", "win32"], @@ -297,7 +303,13 @@ "testIdPattern": "[navigation.spec] navigation Page.goto should navigate to URL with hash and fire requests without hash", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] + "expectations": ["FAIL", "TIMEOUT"] + }, + { + "testIdPattern": "[navigation.spec] navigation Page.goto should not leak listeners during navigation of 11 pages", + "platforms": ["darwin"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["PASS", "TIMEOUT"] }, { "testIdPattern": "[navigation.spec] navigation Page.goto should not throw an error for a 404 response with an empty body", @@ -315,7 +327,7 @@ "testIdPattern": "[navigation.spec] navigation Page.goto should return last response in redirect chain", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] + "expectations": ["FAIL", "TIMEOUT"] }, { "testIdPattern": "[navigation.spec] navigation Page.goto should return response when page changes its URL after load", @@ -351,7 +363,7 @@ "testIdPattern": "[navigation.spec] navigation Page.goto should work when navigating to valid url", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] + "expectations": ["FAIL", "TIMEOUT"] }, { "testIdPattern": "[navigation.spec] navigation Page.goto should work when page calls history API in beforeunload", @@ -369,7 +381,7 @@ "testIdPattern": "[navigation.spec] navigation Page.reload should work", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] + "expectations": ["FAIL", "TIMEOUT"] }, { "testIdPattern": "[oopif.spec] *", diff --git a/test/src/page.spec.ts b/test/src/page.spec.ts index 31722363809..fb35fa98034 100644 --- a/test/src/page.spec.ts +++ b/test/src/page.spec.ts @@ -2051,28 +2051,19 @@ describe('Page', function () { }); }); - describe('printing to PDF', function () { + describe('Page.pdf', function () { it('can print to PDF and save to file', async () => { - // Printing to pdf is currently only supported in headless - const {isHeadless, page} = getTestState(); - - if (!isHeadless) { - return; - } + const {page, server} = getTestState(); const outputFile = __dirname + '/../assets/output.pdf'; + await page.goto(server.PREFIX + '/pdf.html'); await page.pdf({path: outputFile}); expect(fs.readFileSync(outputFile).byteLength).toBeGreaterThan(0); fs.unlinkSync(outputFile); }); it('can print to PDF and stream the result', async () => { - // Printing to pdf is currently only supported in headless - const {isHeadless, page} = getTestState(); - - if (!isHeadless) { - return; - } + const {page} = getTestState(); const stream = await page.createPDFStream(); let size = 0; @@ -2083,10 +2074,7 @@ describe('Page', function () { }); it('should respect timeout', async () => { - const {isHeadless, page, server} = getTestState(); - if (!isHeadless) { - return; - } + const {page, server} = getTestState(); await page.goto(server.PREFIX + '/pdf.html');