chore: add PDF print for BiDi (#9914)

This commit is contained in:
Nikolay Vitkov 2023-03-27 11:39:40 +02:00 committed by GitHub
parent 94f680a046
commit 95c99e84b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 266 additions and 161 deletions

View File

@ -26,8 +26,6 @@ Promise<Readable>
## Remarks ## 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()`. 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. 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.

View File

@ -122,7 +122,7 @@ page.off('request', logRequest);
| [off(eventName, handler)](./puppeteer.page.off.md) | | | | [off(eventName, handler)](./puppeteer.page.off.md) | | |
| [on(eventName, handler)](./puppeteer.page.on.md) | | <p>Listen to page events.</p><p>:::note</p><p>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).</p><p>:::</p> | | [on(eventName, handler)](./puppeteer.page.on.md) | | <p>Listen to page events.</p><p>:::note</p><p>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).</p><p>:::</p> |
| [once(eventName, handler)](./puppeteer.page.once.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 <code>print</code> CSS media type. |
| [queryObjects(prototypeHandle)](./puppeteer.page.queryobjects.md) | | This method iterates the JavaScript heap and finds all objects with the given prototype. | | [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) | | | | [reload(options)](./puppeteer.page.reload.md) | | |
| [screenshot(options)](./puppeteer.page.screenshot.md) | | | | [screenshot(options)](./puppeteer.page.screenshot.md) | | |

View File

@ -4,6 +4,8 @@ sidebar_label: Page.pdf
# Page.pdf() method # Page.pdf() method
Generates a PDF of the page with the `print` CSS media type.
#### Signature: #### Signature:
```typescript ```typescript
@ -14,10 +16,16 @@ class Page {
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --------- | --------------------------------------- | ------------ | | --------- | --------------------------------------- | -------------------------------------------- |
| options | [PDFOptions](./puppeteer.pdfoptions.md) | _(Optional)_ | | options | [PDFOptions](./puppeteer.pdfoptions.md) | _(Optional)_ options for generating the PDF. |
**Returns:** **Returns:**
Promise&lt;Buffer&gt; Promise&lt;Buffer&gt;
## 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.

16
package-lock.json generated
View File

@ -2587,9 +2587,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/chromium-bidi": { "node_modules/chromium-bidi": {
"version": "0.4.5", "version": "0.4.6",
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.5.tgz", "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.6.tgz",
"integrity": "sha512-rkav9YzRfAshSTG3wNXF7P7yNiI29QAo1xBXElPoCoSQR5n20q3cOyVhDv6S7+GlF/CJ/emUxlQiR0xOPurkGg==", "integrity": "sha512-TQOkWRaLI/IWvoP8XC+7jO4uHTIiAUiklXU1T0qszlUFEai9LgKXIBXy3pOS3EnQZ3bQtMbKUPkug0fTAEHCSw==",
"dependencies": { "dependencies": {
"mitt": "3.0.0" "mitt": "3.0.0"
}, },
@ -9481,7 +9481,7 @@
"version": "19.8.0", "version": "19.8.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"chromium-bidi": "0.4.5", "chromium-bidi": "0.4.6",
"cross-fetch": "3.1.5", "cross-fetch": "3.1.5",
"debug": "4.3.4", "debug": "4.3.4",
"devtools-protocol": "0.0.1107588", "devtools-protocol": "0.0.1107588",
@ -11371,9 +11371,9 @@
"version": "1.1.4" "version": "1.1.4"
}, },
"chromium-bidi": { "chromium-bidi": {
"version": "0.4.5", "version": "0.4.6",
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.5.tgz", "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.6.tgz",
"integrity": "sha512-rkav9YzRfAshSTG3wNXF7P7yNiI29QAo1xBXElPoCoSQR5n20q3cOyVhDv6S7+GlF/CJ/emUxlQiR0xOPurkGg==", "integrity": "sha512-TQOkWRaLI/IWvoP8XC+7jO4uHTIiAUiklXU1T0qszlUFEai9LgKXIBXy3pOS3EnQZ3bQtMbKUPkug0fTAEHCSw==",
"requires": { "requires": {
"mitt": "3.0.0" "mitt": "3.0.0"
} }
@ -14462,7 +14462,7 @@
"puppeteer-core": { "puppeteer-core": {
"version": "file:packages/puppeteer-core", "version": "file:packages/puppeteer-core",
"requires": { "requires": {
"chromium-bidi": "0.4.5", "chromium-bidi": "0.4.6",
"cross-fetch": "3.1.5", "cross-fetch": "3.1.5",
"debug": "4.3.4", "debug": "4.3.4",
"devtools-protocol": "0.0.1107588", "devtools-protocol": "0.0.1107588",

View File

@ -131,7 +131,7 @@
"author": "The Chromium Authors", "author": "The Chromium Authors",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"chromium-bidi": "0.4.5", "chromium-bidi": "0.4.6",
"cross-fetch": "3.1.5", "cross-fetch": "3.1.5",
"debug": "4.3.4", "debug": "4.3.4",
"devtools-protocol": "0.0.1107588", "devtools-protocol": "0.0.1107588",

View File

@ -43,7 +43,12 @@ import type {
import type {WaitForSelectorOptions} from '../common/IsolatedWorld.js'; import type {WaitForSelectorOptions} from '../common/IsolatedWorld.js';
import type {PuppeteerLifeCycleEvent} from '../common/LifecycleWatcher.js'; import type {PuppeteerLifeCycleEvent} from '../common/LifecycleWatcher.js';
import type {Credentials, NetworkConditions} from '../common/NetworkManager.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 {Viewport} from '../common/PuppeteerViewport.js';
import type {Target} from '../common/Target.js'; import type {Target} from '../common/Target.js';
import type {Tracing} from '../common/Tracing.js'; import type {Tracing} from '../common/Tracing.js';
@ -53,7 +58,9 @@ import type {
HandleFor, HandleFor,
NodeFor, NodeFor,
} from '../common/types.js'; } from '../common/types.js';
import {isNumber, isString} from '../common/util.js';
import type {WebWorker} from '../common/WebWorker.js'; import type {WebWorker} from '../common/WebWorker.js';
import {assert} from '../util/assert.js';
import type {Browser} from './Browser.js'; import type {Browser} from './Browser.js';
import type {BrowserContext} from './BrowserContext.js'; import type {BrowserContext} from './BrowserContext.js';
@ -2136,12 +2143,58 @@ export class Page extends EventEmitter {
throw new Error('Not implemented'); 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. * Generates a PDF of the page with the `print` CSS media type.
* @remarks * @remarks
* *
* NOTE: PDF generation is only supported in Chrome headless mode.
*
* To generate a PDF with the `screen` media type, call * To generate a PDF with the `screen` media type, call
* {@link Page.emulateMediaType | `page.emulateMediaType('screen')`} before * {@link Page.emulateMediaType | `page.emulateMediaType('screen')`} before
* calling `page.pdf()`. * calling `page.pdf()`.
@ -2159,8 +2212,7 @@ export class Page extends EventEmitter {
} }
/** /**
* @param options - * {@inheritDoc Page.createPDFStream}
* @returns
*/ */
async pdf(options?: PDFOptions): Promise<Buffer>; async pdf(options?: PDFOptions): Promise<Buffer>;
async pdf(): Promise<Buffer> { async pdf(): Promise<Buffer> {
@ -2619,3 +2671,36 @@ export const unitToPixels = {
cm: 37.8, cm: 37.8,
mm: 3.78, 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;
}

View File

@ -40,7 +40,7 @@ import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
import {LazyArg} from './LazyArg.js'; import {LazyArg} from './LazyArg.js';
import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js'; import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
import {EvaluateFunc, EvaluateFuncWith, HandleFor, NodeFor} from './types.js'; import {EvaluateFunc, EvaluateFuncWith, HandleFor, NodeFor} from './types.js';
import {importFS} from './util.js'; import {importFSPromises} from './util.js';
/** /**
* @public * @public
@ -883,9 +883,9 @@ export class Frame {
} }
if (path) { if (path) {
let fs: typeof import('fs').promises; let fs: typeof import('fs/promises');
try { try {
fs = (await importFS()).promises; fs = await importFSPromises();
} catch (error) { } catch (error) {
if (error instanceof TypeError) { if (error instanceof TypeError) {
throw new Error( throw new Error(

View File

@ -184,19 +184,38 @@ export interface PaperFormatDimensions {
/** /**
* @internal * @internal
*/ */
export const _paperFormats: Record< export interface ParsedPDFOptionsInterface {
LowerCasePaperFormat, width: number;
PaperFormatDimensions height: number;
> = { margin: {
letter: {width: 8.5, height: 11}, top: number;
legal: {width: 8.5, height: 14}, bottom: number;
tabloid: {width: 11, height: 17}, left: number;
ledger: {width: 17, height: 11}, right: number;
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}, * @internal
a5: {width: 5.83, height: 8.27}, */
a6: {width: 4.13, height: 5.83}, export type ParsedPDFOptions = Required<
} as const; Omit<PDFOptions, 'path' | 'format'> & ParsedPDFOptionsInterface
>;
/**
* @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;

View File

@ -70,7 +70,7 @@ import {
NetworkConditions, NetworkConditions,
NetworkManagerEmittedEvents, NetworkManagerEmittedEvents,
} from './NetworkManager.js'; } from './NetworkManager.js';
import {LowerCasePaperFormat, PDFOptions, _paperFormats} from './PDFOptions.js'; import {PDFOptions} from './PDFOptions.js';
import {Viewport} from './PuppeteerViewport.js'; import {Viewport} from './PuppeteerViewport.js';
import {Target} from './Target.js'; import {Target} from './Target.js';
import {TargetManagerEmittedEvents} from './TargetManager.js'; import {TargetManagerEmittedEvents} from './TargetManager.js';
@ -91,8 +91,7 @@ import {
getExceptionMessage, getExceptionMessage,
getReadableAsBuffer, getReadableAsBuffer,
getReadableFromProtocolStream, getReadableFromProtocolStream,
importFS, importFSPromises,
isNumber,
isString, isString,
pageBindingInitString, pageBindingInitString,
releaseObject, releaseObject,
@ -1448,7 +1447,7 @@ export class CDPPage extends Page {
if (options.path) { if (options.path) {
try { try {
const fs = (await importFS()).promises; const fs = await importFSPromises();
await fs.writeFile(options.path, buffer); await fs.writeFile(options.path, buffer);
} catch (error) { } catch (error) {
if (error instanceof TypeError) { if (error instanceof TypeError) {
@ -1471,68 +1470,37 @@ export class CDPPage extends Page {
} }
override async createPDFStream(options: PDFOptions = {}): Promise<Readable> { override async createPDFStream(options: PDFOptions = {}): Promise<Readable> {
const { const params = this._getPDFOptions(options);
scale = 1,
displayHeaderFooter = false,
headerTemplate = '',
footerTemplate = '',
printBackground = false,
landscape = false,
pageRanges = '',
preferCSSPageSize = false,
margin = {},
omitBackground = false,
timeout = 30000,
} = options;
let paperWidth = 8.5; if (params.omitBackground) {
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) {
await this.#setTransparentBackgroundColor(); await this.#setTransparentBackgroundColor();
} }
const printCommandPromise = this.#client.send('Page.printToPDF', { const printCommandPromise = this.#client.send('Page.printToPDF', {
transferMode: 'ReturnAsStream', transferMode: 'ReturnAsStream',
landscape, landscape: params.landscape,
displayHeaderFooter, displayHeaderFooter: params.displayHeaderFooter,
headerTemplate, headerTemplate: params.headerTemplate,
footerTemplate, footerTemplate: params.footerTemplate,
printBackground, printBackground: params.printBackground,
scale, scale: params.scale,
paperWidth, paperWidth: params.width,
paperHeight, paperHeight: params.height,
marginTop, marginTop: params.margin.top,
marginBottom, marginBottom: params.margin.bottom,
marginLeft, marginLeft: params.margin.left,
marginRight, marginRight: params.margin.right,
pageRanges, pageRanges: params.pageRanges,
preferCSSPageSize, preferCSSPageSize: params.preferCSSPageSize,
}); });
const result = await waitWithTimeout( const result = await waitWithTimeout(
printCommandPromise, printCommandPromise,
'Page.printToPDF', 'Page.printToPDF',
timeout params.timeout
); );
if (omitBackground) { if (params.omitBackground) {
await this.#resetDefaultBackgroundColor(); await this.#resetDefaultBackgroundColor();
} }
@ -1688,43 +1656,3 @@ const supportedMetrics = new Set<string>([
'JSHeapUsedSize', 'JSHeapUsedSize',
'JSHeapTotalSize', '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;
}

View File

@ -55,6 +55,10 @@ interface Commands {
params: Bidi.BrowsingContext.NavigateParameters; params: Bidi.BrowsingContext.NavigateParameters;
returnType: Bidi.BrowsingContext.NavigateResult; returnType: Bidi.BrowsingContext.NavigateResult;
}; };
'browsingContext.print': {
params: Bidi.BrowsingContext.PrintParameters;
returnType: Bidi.BrowsingContext.PrintResult;
};
'session.new': { 'session.new': {
params: {capabilities?: Record<any, unknown>}; // TODO: Update Types in chromium bidi params: {capabilities?: Record<any, unknown>}; // TODO: Update Types in chromium bidi
@ -152,10 +156,10 @@ export class Connection extends EventEmitter {
#maybeEmitOnContext(event: Bidi.Message.EventMessage) { #maybeEmitOnContext(event: Bidi.Message.EventMessage) {
let context: Context | undefined; let context: Context | undefined;
// Context specific events // Context specific events
if ('context' in event.params) { if ('context' in event.params && event.params.context) {
context = this.#contexts.get(event.params.context); context = this.#contexts.get(event.params.context);
// `log.entryAdded` specific 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 = this.#contexts.get(event.params.source.context);
} }
context?.emit(event.method, event.params); context?.emit(event.method, event.params);

View File

@ -14,6 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
import type {Readable} from 'stream';
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js'; import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
import {HTTPResponse} from '../../api/HTTPResponse.js'; import {HTTPResponse} from '../../api/HTTPResponse.js';
@ -25,8 +27,9 @@ import {
import {isErrorLike} from '../../util/ErrorLike.js'; import {isErrorLike} from '../../util/ErrorLike.js';
import {ConsoleMessage, ConsoleMessageLocation} from '../ConsoleMessage.js'; import {ConsoleMessage, ConsoleMessageLocation} from '../ConsoleMessage.js';
import {Handler} from '../EventEmitter.js'; import {Handler} from '../EventEmitter.js';
import {PDFOptions} from '../PDFOptions.js';
import {EvaluateFunc, HandleFor} from '../types.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 {Context, getBidiHandle} from './Context.js';
import {BidiSerializer} from './Serializer.js'; import {BidiSerializer} from './Serializer.js';
@ -199,6 +202,64 @@ export class Page extends PageBase {
return retVal; return retVal;
}); });
} }
override async pdf(options: PDFOptions = {}): Promise<Buffer> {
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<Readable> {
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( function isConsoleLogEntry(

View File

@ -361,13 +361,15 @@ export async function waitWithTimeout<T>(
/** /**
* @internal * @internal
*/ */
let fs: typeof import('fs') | null = null; let fs: typeof import('fs/promises') | null = null;
/** /**
* @internal * @internal
*/ */
export async function importFS(): Promise<typeof import('fs')> { export async function importFSPromises(): Promise<
typeof import('fs/promises')
> {
if (!fs) { if (!fs) {
fs = await import('fs'); fs = await import('fs/promises');
} }
return fs; return fs;
} }
@ -381,9 +383,9 @@ export async function getReadableAsBuffer(
): Promise<Buffer | null> { ): Promise<Buffer | null> {
const buffers = []; const buffers = [];
if (path) { if (path) {
let fs: typeof import('fs').promises; let fs: typeof import('fs/promises');
try { try {
fs = (await importFS()).promises; fs = await importFSPromises();
} catch (error) { } catch (error) {
if (error instanceof TypeError) { if (error instanceof TypeError) {
throw new Error( throw new Error(

View File

@ -59,6 +59,12 @@
"parameters": ["webDriverBiDi"], "parameters": ["webDriverBiDi"],
"expectations": ["PASS", "TIMEOUT"] "expectations": ["PASS", "TIMEOUT"]
}, },
{
"testIdPattern": "[page.spec] Page Page.pdf *",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{ {
"testIdPattern": "[page.spec] Page Page.setContent *", "testIdPattern": "[page.spec] Page Page.setContent *",
"platforms": ["darwin", "linux", "win32"], "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", "testIdPattern": "[navigation.spec] navigation Page.goto should navigate to URL with hash and fire requests without hash",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"], "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", "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", "testIdPattern": "[navigation.spec] navigation Page.goto should return last response in redirect chain",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"], "parameters": ["webDriverBiDi"],
"expectations": ["FAIL"] "expectations": ["FAIL", "TIMEOUT"]
}, },
{ {
"testIdPattern": "[navigation.spec] navigation Page.goto should return response when page changes its URL after load", "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", "testIdPattern": "[navigation.spec] navigation Page.goto should work when navigating to valid url",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"], "parameters": ["webDriverBiDi"],
"expectations": ["FAIL"] "expectations": ["FAIL", "TIMEOUT"]
}, },
{ {
"testIdPattern": "[navigation.spec] navigation Page.goto should work when page calls history API in beforeunload", "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", "testIdPattern": "[navigation.spec] navigation Page.reload should work",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"], "parameters": ["webDriverBiDi"],
"expectations": ["FAIL"] "expectations": ["FAIL", "TIMEOUT"]
}, },
{ {
"testIdPattern": "[oopif.spec] *", "testIdPattern": "[oopif.spec] *",

View File

@ -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 () => { it('can print to PDF and save to file', async () => {
// Printing to pdf is currently only supported in headless const {page, server} = getTestState();
const {isHeadless, page} = getTestState();
if (!isHeadless) {
return;
}
const outputFile = __dirname + '/../assets/output.pdf'; const outputFile = __dirname + '/../assets/output.pdf';
await page.goto(server.PREFIX + '/pdf.html');
await page.pdf({path: outputFile}); await page.pdf({path: outputFile});
expect(fs.readFileSync(outputFile).byteLength).toBeGreaterThan(0); expect(fs.readFileSync(outputFile).byteLength).toBeGreaterThan(0);
fs.unlinkSync(outputFile); fs.unlinkSync(outputFile);
}); });
it('can print to PDF and stream the result', async () => { it('can print to PDF and stream the result', async () => {
// Printing to pdf is currently only supported in headless const {page} = getTestState();
const {isHeadless, page} = getTestState();
if (!isHeadless) {
return;
}
const stream = await page.createPDFStream(); const stream = await page.createPDFStream();
let size = 0; let size = 0;
@ -2083,10 +2074,7 @@ describe('Page', function () {
}); });
it('should respect timeout', async () => { it('should respect timeout', async () => {
const {isHeadless, page, server} = getTestState(); const {page, server} = getTestState();
if (!isHeadless) {
return;
}
await page.goto(server.PREFIX + '/pdf.html'); await page.goto(server.PREFIX + '/pdf.html');