chore: move pdf, tracing, coverage, a11y into featuress/ (#28)

This commit is contained in:
Pavel Feldman 2019-11-19 16:29:28 -08:00 committed by Andrey Lushnikov
parent 60f332d01b
commit 85035fedea
12 changed files with 302 additions and 52 deletions

View file

@ -120,6 +120,7 @@
* [page.mainFrame()](#pagemainframe)
* [page.metrics()](#pagemetrics)
* [page.mouse](#pagemouse)
* [page.pdf](#pagepdf)
* [page.queryObjects(prototypeHandle)](#pagequeryobjectsprototypehandle)
* [page.reload([options])](#pagereloadoptions)
* [page.screenshot([options])](#pagescreenshotoptions)
@ -175,6 +176,8 @@
* [mouse.move(x, y[, options])](#mousemovex-y-options)
* [mouse.tripleclick(x, y[, options])](#mousetripleclickx-y-options)
* [mouse.up([options])](#mouseupoptions)
- [class: PDF](#class-pdf)
* [pdf.generate([options])](#pdfgenerateoptions)
- [class: Touchscreen](#class-touchscreen)
* [touchscreen.tap(x, y)](#touchscreentapx-y)
- [class: Tracing](#class-tracing)
@ -1656,6 +1659,9 @@ Page is guaranteed to have a main frame which persists during navigations.
- returns: <[Mouse]>
#### page.pdf
- returns: <[PDF]>
#### page.queryObjects(prototypeHandle)
- `prototypeHandle` <[JSHandle]> A handle to the object prototype.
- returns: <[Promise]<[JSHandle]>> Promise which resolves to a handle to an array of objects with this prototype.
@ -2459,6 +2465,77 @@ Shortcut for [`mouse.move`](#mousemovex-y-options), [`mouse.down`](#mousedownopt
Dispatches a `mouseup` event.
### class: PDF
#### pdf.generate([options])
- `options` <[Object]> Options object which might have the following properties:
- `path` <[string]> The file path to save the PDF to. If `path` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). If no path is provided, the PDF won't be saved to the disk.
- `scale` <[number]> Scale of the webpage rendering. Defaults to `1`. Scale amount must be between 0.1 and 2.
- `displayHeaderFooter` <[boolean]> Display header and footer. Defaults to `false`.
- `headerTemplate` <[string]> HTML template for the print header. Should be valid HTML markup with following classes used to inject printing values into them:
- `date` formatted print date
- `title` document title
- `url` document location
- `pageNumber` current page number
- `totalPages` total pages in the document
- `footerTemplate` <[string]> HTML template for the print footer. Should use the same format as the `headerTemplate`.
- `printBackground` <[boolean]> Print background graphics. Defaults to `false`.
- `landscape` <[boolean]> Paper orientation. Defaults to `false`.
- `pageRanges` <[string]> Paper ranges to print, e.g., '1-5, 8, 11-13'. Defaults to the empty string, which means print all pages.
- `format` <[string]> Paper format. If set, takes priority over `width` or `height` options. Defaults to 'Letter'.
- `width` <[string]|[number]> Paper width, accepts values labeled with units.
- `height` <[string]|[number]> Paper height, accepts values labeled with units.
- `margin` <[Object]> Paper margins, defaults to none.
- `top` <[string]|[number]> Top margin, accepts values labeled with units.
- `right` <[string]|[number]> Right margin, accepts values labeled with units.
- `bottom` <[string]|[number]> Bottom margin, accepts values labeled with units.
- `left` <[string]|[number]> Left margin, accepts values labeled with units.
- `preferCSSPageSize` <[boolean]> Give any CSS `@page` size declared in the page priority over what is declared in `width` and `height` or `format` options. Defaults to `false`, which will scale the content to fit the paper size.
- returns: <[Promise]<[Buffer]>> Promise which resolves with PDF buffer.
> **NOTE** Generating a pdf is currently only supported in Chrome headless.
`page.pdf()` generates a pdf of the page with `print` css media. To generate a pdf with `screen` media, call [page.emulateMedia('screen')](#pageemulatemediamediatype) before calling `page.pdf()`:
> **NOTE** 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.
```js
// Generates a PDF with 'screen' media type.
await page.emulateMedia('screen');
await page.pdf({path: 'page.pdf'});
```
The `width`, `height`, and `margin` options accept values labeled with units. Unlabeled values are treated as pixels.
A few examples:
- `page.pdf({width: 100})` - prints with width set to 100 pixels
- `page.pdf({width: '100px'})` - prints with width set to 100 pixels
- `page.pdf({width: '10cm'})` - prints with width set to 10 centimeters.
All possible units are:
- `px` - pixel
- `in` - inch
- `cm` - centimeter
- `mm` - millimeter
The `format` options are:
- `Letter`: 8.5in x 11in
- `Legal`: 8.5in x 14in
- `Tabloid`: 11in x 17in
- `Ledger`: 17in x 11in
- `A0`: 33.1in x 46.8in
- `A1`: 23.4in x 33.1in
- `A2`: 16.54in x 23.4in
- `A3`: 11.7in x 16.54in
- `A4`: 8.27in x 11.7in
- `A5`: 5.83in x 8.27in
- `A6`: 4.13in x 5.83in
> **NOTE** `headerTemplate` and `footerTemplate` markup have the following limitations:
> 1. Script tags inside templates are not evaluated.
> 2. Page styles are not visible inside templates.
### class: Touchscreen
#### touchscreen.tap(x, y)

View file

@ -16,13 +16,13 @@
*/
export = {
Chromium: {
Accessibility: require('./chromium/Accessibility').Accessibility,
Accessibility: require('./chromium/features/accessibility').Accessibility,
Browser: require('./chromium/Browser').Browser,
BrowserContext: require('./chromium/BrowserContext').BrowserContext,
BrowserFetcher: require('./chromium/BrowserFetcher').BrowserFetcher,
CDPSession: require('./chromium/Connection').CDPSession,
ConsoleMessage: require('./chromium/Page').ConsoleMessage,
Coverage: require('./chromium/Coverage').Coverage,
Coverage: require('./chromium/features/coverage').Coverage,
Dialog: require('./chromium/Dialog').Dialog,
ElementHandle: require('./chromium/JSHandle').ElementHandle,
ExecutionContext: require('./chromium/ExecutionContext').ExecutionContext,
@ -31,6 +31,7 @@ export = {
JSHandle: require('./chromium/JSHandle').JSHandle,
Keyboard: require('./chromium/Input').Keyboard,
Mouse: require('./chromium/Input').Mouse,
PDF: require('./chromium/features/pdf').PDF,
Page: require('./chromium/Page').Page,
Playwright: require('./chromium/Playwright').Playwright,
Request: require('./chromium/NetworkManager').Request,
@ -39,7 +40,7 @@ export = {
Target: require('./chromium/Target').Target,
TimeoutError: require('./Errors').TimeoutError,
Touchscreen: require('./chromium/Input').Touchscreen,
Tracing: require('./chromium/Tracing').Tracing,
Tracing: require('./chromium/features/tracing').Tracing,
Worker: require('./chromium/Worker').Worker,
},
Firefox: {

View file

@ -19,27 +19,28 @@ import { EventEmitter } from 'events';
import * as fs from 'fs';
import * as mime from 'mime';
import * as path from 'path';
import { Accessibility } from './Accessibility';
import { CDPSession, CDPSessionEvents, Connection } from './Connection';
import { Coverage } from './Coverage';
import { Dialog, DialogType } from './Dialog';
import { EmulationManager } from './EmulationManager';
import { Events } from '../Events';
import { Frame } from './Frame';
import { FrameManager, FrameManagerEvents } from './FrameManager';
import { assert, debugError, helper } from '../helper';
import { releaseObject, getExceptionMessage, valueFromRemoteObject } from './protocolHelper';
import { Keyboard, Mouse, Touchscreen } from './Input';
import { createJSHandle, ElementHandle, JSHandle, ClickOptions, PointerActionOptions, MultiClickOptions } from './JSHandle';
import { Response, NetworkManagerEvents } from './NetworkManager';
import { TaskQueue } from './TaskQueue';
import { TimeoutSettings } from '../TimeoutSettings';
import { Tracing } from './Tracing';
import { Worker } from './Worker';
import { Target } from './Target';
import { Accessibility } from './features/accessibility';
import { Browser } from './Browser';
import { BrowserContext } from './BrowserContext';
import { CDPSession, CDPSessionEvents, Connection } from './Connection';
import { Coverage } from './features/coverage';
import { Dialog, DialogType } from './Dialog';
import { EmulationManager } from './EmulationManager';
import { PDF } from './features/pdf';
import { Frame } from './Frame';
import { FrameManager, FrameManagerEvents } from './FrameManager';
import { Keyboard, Mouse, Touchscreen } from './Input';
import { ClickOptions, createJSHandle, ElementHandle, JSHandle, MultiClickOptions, PointerActionOptions } from './JSHandle';
import { NetworkManagerEvents, Response } from './NetworkManager';
import { Protocol } from './protocol';
import { getExceptionMessage, releaseObject, valueFromRemoteObject } from './protocolHelper';
import { Target } from './Target';
import { TaskQueue } from './TaskQueue';
import { Tracing } from './features/tracing';
import { Worker } from './Worker';
const writeFileAsync = helper.promisify(fs.writeFile);
@ -60,12 +61,13 @@ export class Page extends EventEmitter {
private _mouse: Mouse;
private _timeoutSettings: TimeoutSettings;
private _touchscreen: Touchscreen;
private _accessibility: Accessibility;
private _frameManager: FrameManager;
private _emulationManager: EmulationManager;
private _tracing: Tracing;
readonly accessibility: Accessibility;
readonly coverage: Coverage;
readonly pdf: PDF;
readonly tracing: Tracing;
private _pageBindings = new Map<string, Function>();
private _coverage: Coverage;
_javascriptEnabled = true;
private _viewport: Viewport | null = null;
private _screenshotTaskQueue: TaskQueue;
@ -90,11 +92,12 @@ export class Page extends EventEmitter {
this._mouse = new Mouse(client, this._keyboard);
this._timeoutSettings = new TimeoutSettings();
this._touchscreen = new Touchscreen(client, this._keyboard);
this._accessibility = new Accessibility(client);
this.accessibility = new Accessibility(client);
this._frameManager = new FrameManager(client, this, ignoreHTTPSErrors, this._timeoutSettings);
this._emulationManager = new EmulationManager(client);
this._tracing = new Tracing(client);
this._coverage = new Coverage(client);
this.tracing = new Tracing(client);
this.coverage = new Coverage(client);
this.pdf = new PDF(client);
this._screenshotTaskQueue = screenshotTaskQueue;
@ -231,18 +234,6 @@ export class Page extends EventEmitter {
return this._touchscreen;
}
get coverage(): Coverage {
return this._coverage;
}
get tracing(): Tracing {
return this._tracing;
}
get accessibility(): Accessibility {
return this._accessibility;
}
frames(): Frame[] {
return this._frameManager.frames();
}

View file

@ -15,9 +15,9 @@
* limitations under the License.
*/
import { CDPSession } from './Connection';
import { ElementHandle } from './JSHandle';
import { Protocol } from './protocol';
import { CDPSession } from '../Connection';
import { ElementHandle } from '../JSHandle';
import { Protocol } from '../protocol';
type SerializedAXNode = {
role: string,

View file

@ -15,11 +15,11 @@
* limitations under the License.
*/
import { CDPSession } from './Connection';
import { assert, debugError, helper, RegisteredListener } from '../helper';
import { Protocol } from './protocol';
import { CDPSession } from '../Connection';
import { assert, debugError, helper, RegisteredListener } from '../../helper';
import { Protocol } from '../protocol';
const {EVALUATION_SCRIPT_URL} = require('./ExecutionContext');
import { EVALUATION_SCRIPT_URL } from '../ExecutionContext';
type CoverageEntry = {
url: string,

View file

@ -0,0 +1,34 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const fs = require('fs');
const path = require('path');
module.exports.addTests = function({testRunner, expect, headless, ASSETS_DIR}) {
const {describe, xdescribe, fdescribe} = testRunner;
const {it, fit, xit} = testRunner;
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
// Printing to pdf is currently only supported in headless
describe.skip(!headless)('Page.pdf', function() {
it('should be able to save file', async({page, server}) => {
const outputFile = path.join(ASSETS_DIR, 'output.pdf');
await page.pdf.generate({path: outputFile});
expect(fs.readFileSync(outputFile).byteLength).toBeGreaterThan(0);
fs.unlinkSync(outputFile);
});
});
};

View file

@ -0,0 +1,144 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { assert, helper } from '../../helper';
import { CDPSession } from '../Connection';
import { readProtocolStream } from '../protocolHelper';
type PDFOptions = {
scale?: number,
displayHeaderFooter?: boolean,
headerTemplate?: string,
footerTemplate?: string,
printBackground?: boolean,
landscape?: boolean,
pageRanges?: string,
format?: string,
width?: string|number,
height?: string|number,
preferCSSPageSize?: boolean,
margin?: {top?: string|number, bottom?: string|number, left?: string|number, right?: string|number},
path?: string,
}
const PagePaperFormats = {
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 },
};
const unitToPixels = {
'px': 1,
'in': 96,
'cm': 37.8,
'mm': 3.78
};
function convertPrintParameterToInches(parameter: (string | number | undefined)): (number | undefined) {
if (typeof parameter === 'undefined')
return undefined;
let pixels: number;
if (helper.isNumber(parameter)) {
// Treat numbers as pixel values to be aligned with phantom's paperSize.
pixels = parameter as number;
} else if (helper.isString(parameter)) {
const text: string = parameter as string;
let unit = text.substring(text.length - 2).toLowerCase();
let valueText = '';
if (unitToPixels.hasOwnProperty(unit)) {
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];
} else {
throw new Error('page.pdf() Cannot handle parameter type: ' + (typeof parameter));
}
return pixels / 96;
}
export class PDF {
private _client: CDPSession;
constructor(client: CDPSession) {
this._client = client;
}
async generate(options: PDFOptions = {}): Promise<Buffer> {
const {
scale = 1,
displayHeaderFooter = false,
headerTemplate = '',
footerTemplate = '',
printBackground = false,
landscape = false,
pageRanges = '',
preferCSSPageSize = false,
margin = {},
path = null
} = options;
let paperWidth = 8.5;
let paperHeight = 11;
if (options.format) {
const format = PagePaperFormats[options.format.toLowerCase()];
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;
const result = await this._client.send('Page.printToPDF', {
transferMode: 'ReturnAsStream',
landscape,
displayHeaderFooter,
headerTemplate,
footerTemplate,
printBackground,
scale,
paperWidth,
paperHeight,
marginTop,
marginBottom,
marginLeft,
marginRight,
pageRanges,
preferCSSPageSize
});
return await readProtocolStream(this._client, result.stream, path);
}
}

View file

@ -17,14 +17,14 @@
const fs = require('fs');
const path = require('path');
module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, playwright, FFOX, CHROME, WEBKIT}) {
module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, playwright, ASSETS_DIR}) {
const {describe, xdescribe, fdescribe} = testRunner;
const {it, fit, xit} = testRunner;
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
describe('Tracing', function() {
beforeEach(async function(state) {
state.outputFile = path.join(__dirname, 'assets', `trace-${state.parallelIndex}.json`);
state.outputFile = path.join(ASSETS_DIR, `trace-${state.parallelIndex}.json`);
state.browser = await playwright.launch(defaultBrowserOptions);
state.page = await state.browser.newPage();
});
@ -47,7 +47,7 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
await page.tracing.start({path: outputFile, categories: ['disabled-by-default-v8.cpu_profiler.hires']});
await page.tracing.stop();
const traceJson = JSON.parse(fs.readFileSync(outputFile));
const traceJson = JSON.parse(fs.readFileSync(outputFile).toString());
expect(traceJson.metadata['trace-config']).toContain('disabled-by-default-v8.cpu_profiler.hires');
});
it('should throw if tracing on two pages', async({page, server, browser, outputFile}) => {

View file

@ -14,9 +14,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { CDPSession } from './Connection';
import { assert } from '../helper';
import { readProtocolStream } from './protocolHelper';
import { CDPSession } from '../Connection';
import { assert } from '../../helper';
import { readProtocolStream } from '../protocolHelper';
export class Tracing {
private _client: CDPSession;

View file

@ -59,6 +59,7 @@ module.exports.addTests = ({testRunner, product, playwrightPath}) => {
const GOLDEN_DIR = path.join(__dirname, 'golden-' + product.toLowerCase());
const OUTPUT_DIR = path.join(__dirname, 'output-' + product.toLowerCase());
const ASSETS_DIR = path.join(__dirname, 'assets');
if (fs.existsSync(OUTPUT_DIR))
rm(OUTPUT_DIR);
const {expect} = new Matchers({
@ -76,6 +77,7 @@ module.exports.addTests = ({testRunner, product, playwrightPath}) => {
defaultBrowserOptions,
playwrightPath,
headless: !!defaultBrowserOptions.headless,
ASSETS_DIR,
};
beforeAll(async() => {
@ -129,7 +131,7 @@ module.exports.addTests = ({testRunner, product, playwrightPath}) => {
// Page-level tests that are given a browser, a context and a page.
// Each test is launched in a new browser context.
require('./accessibility.spec.js').addTests(testOptions);
require('../src/chromium/features/accessibility.spec.js').addTests(testOptions);
require('./browser.spec.js').addTests(testOptions);
require('./click.spec.js').addTests(testOptions);
require('./cookies.spec.js').addTests(testOptions);
@ -155,9 +157,10 @@ module.exports.addTests = ({testRunner, product, playwrightPath}) => {
require('./worker.spec.js').addTests(testOptions);
if (CHROME) {
require('./CDPSession.spec.js').addTests(testOptions);
require('./coverage.spec.js').addTests(testOptions);
require('../src/chromium/features/coverage.spec.js').addTests(testOptions);
// Add page-level Chromium-specific tests.
require('./chromiumonly.spec.js').addPageTests(testOptions);
require('../src/chromium/features/pdf.spec.js').addTests(testOptions);
}
});
@ -173,7 +176,7 @@ module.exports.addTests = ({testRunner, product, playwrightPath}) => {
if (CHROME) {
require('./oopif.spec.js').addTests(testOptions);
require('./headful.spec.js').addTests(testOptions);
require('./tracing.spec.js').addTests(testOptions);
require('../src/chromium/features/tracing.spec.js').addTests(testOptions);
// Add top-level Chromium-specific tests.
require('./chromiumonly.spec.js').addLauncherTests(testOptions);
}