feat: add option to specify the default viewport (#3005)

Fixes #1183.
This commit is contained in:
Joel Einbinder 2018-08-01 16:23:03 -07:00 committed by Andrey Lushnikov
parent 0c49bf5245
commit 25632133e2
8 changed files with 94 additions and 72 deletions

View File

@ -359,6 +359,13 @@ puppeteer.launch().then(async browser => {
- `options` <[Object]> - `options` <[Object]>
- `browserWSEndpoint` <[string]> a [browser websocket endpoint](#browserwsendpoint) to connect to. - `browserWSEndpoint` <[string]> a [browser websocket endpoint](#browserwsendpoint) to connect to.
- `ignoreHTTPSErrors` <[boolean]> Whether to ignore HTTPS errors during navigation. Defaults to `false`. - `ignoreHTTPSErrors` <[boolean]> Whether to ignore HTTPS errors during navigation. Defaults to `false`.
- `defaultViewport` <?[Object]> Sets a consistent viewport for each page. Defaults to an 800x600 viewport. `null` disables the default viewport.
- `width` <[number]> page width in pixels.
- `height` <[number]> page height in pixels.
- `deviceScaleFactor` <[number]> Specify device scale factor (can be thought of as dpr). Defaults to `1`.
- `isMobile` <[boolean]> Whether the `meta viewport` tag is taken into account. Defaults to `false`.
- `hasTouch`<[boolean]> Specifies if viewport supports touch events. Defaults to `false`
- `isLandscape` <[boolean]> Specifies if viewport is in landscape mode. Defaults to `false`.
- `slowMo` <[number]> Slows down Puppeteer operations by the specified amount of milliseconds. Useful so that you can see what is going on. - `slowMo` <[number]> Slows down Puppeteer operations by the specified amount of milliseconds. Useful so that you can see what is going on.
- returns: <[Promise]<[Browser]>> - returns: <[Promise]<[Browser]>>
@ -383,6 +390,13 @@ This methods attaches Puppeteer to an existing Chromium instance.
- `headless` <[boolean]> Whether to run browser in [headless mode](https://developers.google.com/web/updates/2017/04/headless-chrome). Defaults to `true` unless the `devtools` option is `true`. - `headless` <[boolean]> Whether to run browser in [headless mode](https://developers.google.com/web/updates/2017/04/headless-chrome). Defaults to `true` unless the `devtools` option is `true`.
- `executablePath` <[string]> Path to a Chromium or Chrome executable to run instead of the bundled Chromium. If `executablePath` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). - `executablePath` <[string]> Path to a Chromium or Chrome executable to run instead of the bundled Chromium. If `executablePath` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd).
- `slowMo` <[number]> Slows down Puppeteer operations by the specified amount of milliseconds. Useful so that you can see what is going on. - `slowMo` <[number]> Slows down Puppeteer operations by the specified amount of milliseconds. Useful so that you can see what is going on.
- `defaultViewport` <?[Object]> Sets a consistent viewport for each page. Defaults to an 800x600 viewport. `null` disables the default viewport.
- `width` <[number]> page width in pixels.
- `height` <[number]> page height in pixels.
- `deviceScaleFactor` <[number]> Specify device scale factor (can be thought of as dpr). Defaults to `1`.
- `isMobile` <[boolean]> Whether the `meta viewport` tag is taken into account. Defaults to `false`.
- `hasTouch`<[boolean]> Specifies if viewport supports touch events. Defaults to `false`
- `isLandscape` <[boolean]> Specifies if viewport is in landscape mode. Defaults to `false`.
- `args` <[Array]<[string]>> Additional arguments to pass to the browser instance. The list of Chromium flags can be found [here](http://peter.sh/experiments/chromium-command-line-switches/). - `args` <[Array]<[string]>> Additional arguments to pass to the browser instance. The list of Chromium flags can be found [here](http://peter.sh/experiments/chromium-command-line-switches/).
- `ignoreDefaultArgs` <[boolean]> Do not use [`puppeteer.defaultArgs()`](#puppeteerdefaultargs). Dangerous option; use with care. Defaults to `false`. - `ignoreDefaultArgs` <[boolean]> Do not use [`puppeteer.defaultArgs()`](#puppeteerdefaultargs). Dangerous option; use with care. Defaults to `false`.
- `handleSIGINT` <[boolean]> Close the browser process on Ctrl-C. Defaults to `true`. - `handleSIGINT` <[boolean]> Close the browser process on Ctrl-C. Defaults to `true`.
@ -1546,7 +1560,7 @@ Shortcut for [page.mainFrame().type(selector, text[, options])](#frametypeselect
This is a shortcut for [page.mainFrame().url()](#frameurl) This is a shortcut for [page.mainFrame().url()](#frameurl)
#### page.viewport() #### page.viewport()
- returns: <[Object]> - returns: <?[Object]>
- `width` <[number]> page width in pixels. - `width` <[number]> page width in pixels.
- `height` <[number]> page height in pixels. - `height` <[number]> page height in pixels.
- `deviceScaleFactor` <[number]> Specify device scale factor (can be though of as dpr). Defaults to `1`. - `deviceScaleFactor` <[number]> Specify device scale factor (can be though of as dpr). Defaults to `1`.

View File

@ -24,14 +24,14 @@ class Browser extends EventEmitter {
* @param {!Puppeteer.Connection} connection * @param {!Puppeteer.Connection} connection
* @param {!Array<string>} contextIds * @param {!Array<string>} contextIds
* @param {boolean} ignoreHTTPSErrors * @param {boolean} ignoreHTTPSErrors
* @param {boolean} setDefaultViewport * @param {?Puppeteer.Viewport} defaultViewport
* @param {?Puppeteer.ChildProcess} process * @param {?Puppeteer.ChildProcess} process
* @param {(function():Promise)=} closeCallback * @param {(function():Promise)=} closeCallback
*/ */
constructor(connection, contextIds, ignoreHTTPSErrors, setDefaultViewport, process, closeCallback) { constructor(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback) {
super(); super();
this._ignoreHTTPSErrors = ignoreHTTPSErrors; this._ignoreHTTPSErrors = ignoreHTTPSErrors;
this._setDefaultViewport = setDefaultViewport; this._defaultViewport = defaultViewport;
this._process = process; this._process = process;
this._screenshotTaskQueue = new TaskQueue(); this._screenshotTaskQueue = new TaskQueue();
this._connection = connection; this._connection = connection;
@ -89,12 +89,12 @@ class Browser extends EventEmitter {
* @param {!Puppeteer.Connection} connection * @param {!Puppeteer.Connection} connection
* @param {!Array<string>} contextIds * @param {!Array<string>} contextIds
* @param {boolean} ignoreHTTPSErrors * @param {boolean} ignoreHTTPSErrors
* @param {boolean} appMode * @param {?Puppeteer.Viewport} defaultViewport
* @param {?Puppeteer.ChildProcess} process * @param {?Puppeteer.ChildProcess} process
* @param {function()=} closeCallback * @param {function()=} closeCallback
*/ */
static async create(connection, contextIds, ignoreHTTPSErrors, appMode, process, closeCallback) { static async create(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback) {
const browser = new Browser(connection, contextIds, ignoreHTTPSErrors, appMode, process, closeCallback); const browser = new Browser(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback);
await connection.send('Target.setDiscoverTargets', {discover: true}); await connection.send('Target.setDiscoverTargets', {discover: true});
return browser; return browser;
} }
@ -107,7 +107,7 @@ class Browser extends EventEmitter {
const {browserContextId} = targetInfo; const {browserContextId} = targetInfo;
const context = (browserContextId && this._contexts.has(browserContextId)) ? this._contexts.get(browserContextId) : this._defaultContext; const context = (browserContextId && this._contexts.has(browserContextId)) ? this._contexts.get(browserContextId) : this._defaultContext;
const target = new Target(targetInfo, context, () => this._connection.createSession(targetInfo), this._ignoreHTTPSErrors, this._setDefaultViewport, this._screenshotTaskQueue); const target = new Target(targetInfo, context, () => this._connection.createSession(targetInfo), this._ignoreHTTPSErrors, this._defaultViewport, this._screenshotTaskQueue);
assert(!this._targets.has(event.targetInfo.targetId), 'Target should not exist before targetCreated'); assert(!this._targets.has(event.targetInfo.targetId), 'Target should not exist before targetCreated');
this._targets.set(event.targetInfo.targetId, target); this._targets.set(event.targetInfo.targetId, target);
@ -300,6 +300,6 @@ module.exports = {Browser, BrowserContext};
/** /**
* @typedef {Object} BrowserOptions * @typedef {Object} BrowserOptions
* @property {boolean=} appMode * @property {boolean=} setDefaultViewport
* @property {boolean=} ignoreHTTPSErrors * @property {boolean=} ignoreHTTPSErrors
*/ */

View File

@ -25,7 +25,7 @@ class EmulationManager {
} }
/** /**
* @param {!EmulationManager.Viewport} viewport * @param {!Puppeteer.Viewport} viewport
* @return {Promise<boolean>} * @return {Promise<boolean>}
*/ */
async emulateViewport(viewport) { async emulateViewport(viewport) {
@ -51,14 +51,4 @@ class EmulationManager {
} }
} }
/**
* @typedef {Object} EmulationManager.Viewport
* @property {number} width
* @property {number} height
* @property {number=} deviceScaleFactor
* @property {boolean=} isMobile
* @property {boolean=} isLandscape
* @property {boolean=} hasTouch
*/
module.exports = EmulationManager; module.exports = EmulationManager;

View File

@ -58,22 +58,17 @@ const AUTOMATION_ARGS = [
class Launcher { class Launcher {
/** /**
* @param {!LaunchOptions=} options * @param {!(LaunchOptions & BrowserOptions)=} options
* @return {!Promise<!Browser>} * @return {!Promise<!Browser>}
*/ */
static async launch(options) { static async launch(options) {
options = Object.assign({}, options || {}); options = Object.assign({}, options || {});
assert(!options.ignoreDefaultArgs || !options.appMode, '`appMode` flag cannot be used together with `ignoreDefaultArgs`');
let temporaryUserDataDir = null; let temporaryUserDataDir = null;
const chromeArguments = []; const chromeArguments = [];
if (!options.ignoreDefaultArgs) if (!options.ignoreDefaultArgs)
chromeArguments.push(...DEFAULT_ARGS); chromeArguments.push(...DEFAULT_ARGS);
if (options.appMode) { if (!options.ignoreDefaultArgs)
options.headless = false;
options.pipe = true;
} else if (!options.ignoreDefaultArgs) {
chromeArguments.push(...AUTOMATION_ARGS); chromeArguments.push(...AUTOMATION_ARGS);
}
if (!options.ignoreDefaultArgs || !chromeArguments.some(argument => argument.startsWith('--remote-debugging-'))) if (!options.ignoreDefaultArgs || !chromeArguments.some(argument => argument.startsWith('--remote-debugging-')))
chromeArguments.push(options.pipe ? '--remote-debugging-pipe' : '--remote-debugging-port=0'); chromeArguments.push(options.pipe ? '--remote-debugging-pipe' : '--remote-debugging-port=0');
@ -155,17 +150,19 @@ class Launcher {
/** @type {?Connection} */ /** @type {?Connection} */
let connection = null; let connection = null;
try { try {
const connectionDelay = options.slowMo || 0; const {
ignoreHTTPSErrors = false,
defaultViewport = {width: 800, height: 600},
slowMo = 0
} = options;
if (!usePipe) { if (!usePipe) {
const timeout = helper.isNumber(options.timeout) ? options.timeout : 30000; const timeout = helper.isNumber(options.timeout) ? options.timeout : 30000;
const browserWSEndpoint = await waitForWSEndpoint(chromeProcess, timeout); const browserWSEndpoint = await waitForWSEndpoint(chromeProcess, timeout);
connection = await Connection.createForWebSocket(browserWSEndpoint, connectionDelay); connection = await Connection.createForWebSocket(browserWSEndpoint, slowMo);
} else { } else {
connection = Connection.createForPipe(/** @type {!NodeJS.WritableStream} */(chromeProcess.stdio[3]), /** @type {!NodeJS.ReadableStream} */ (chromeProcess.stdio[4]), connectionDelay); connection = Connection.createForPipe(/** @type {!NodeJS.WritableStream} */(chromeProcess.stdio[3]), /** @type {!NodeJS.ReadableStream} */ (chromeProcess.stdio[4]), slowMo);
} }
const ignoreHTTPSErrors = !!options.ignoreHTTPSErrors; const browser = await Browser.create(connection, [], ignoreHTTPSErrors, defaultViewport, chromeProcess, gracefullyCloseChrome);
const setDefaultViewport = !options.appMode;
const browser = await Browser.create(connection, [], ignoreHTTPSErrors, setDefaultViewport, chromeProcess, gracefullyCloseChrome);
await ensureInitialPage(browser); await ensureInitialPage(browser);
return browser; return browser;
} catch (e) { } catch (e) {
@ -247,15 +244,19 @@ class Launcher {
} }
/** /**
* @param {!Object=} options * @param {!(BrowserOptions & {browserWSEndpoint: string})=} options
* @return {!Promise<!Browser>} * @return {!Promise<!Browser>}
*/ */
static async connect(options = {}) { static async connect(options) {
const connectionDelay = options.slowMo || 0; const {
const connection = await Connection.createForWebSocket(options.browserWSEndpoint, connectionDelay); browserWSEndpoint,
ignoreHTTPSErrors = false,
defaultViewport = {width: 800, height: 600},
slowMo = 0,
} = options;
const connection = await Connection.createForWebSocket(browserWSEndpoint, slowMo);
const {browserContextIds} = await connection.send('Target.getBrowserContexts'); const {browserContextIds} = await connection.send('Target.getBrowserContexts');
const ignoreHTTPSErrors = !!options.ignoreHTTPSErrors; return Browser.create(connection, browserContextIds, ignoreHTTPSErrors, defaultViewport, null, () => connection.send('Browser.close').catch(debugError));
return Browser.create(connection, browserContextIds, ignoreHTTPSErrors, true /* setDefaultViewport */, null, () => connection.send('Browser.close').catch(debugError));
} }
} }
@ -317,10 +318,8 @@ function waitForWSEndpoint(chromeProcess, timeout) {
/** /**
* @typedef {Object} LaunchOptions * @typedef {Object} LaunchOptions
* @property {boolean=} ignoreHTTPSErrors
* @property {boolean=} headless * @property {boolean=} headless
* @property {string=} executablePath * @property {string=} executablePath
* @property {number=} slowMo
* @property {!Array<string>=} args * @property {!Array<string>=} args
* @property {boolean=} ignoreDefaultArgs * @property {boolean=} ignoreDefaultArgs
* @property {boolean=} handleSIGINT * @property {boolean=} handleSIGINT
@ -332,7 +331,13 @@ function waitForWSEndpoint(chromeProcess, timeout) {
* @property {!Object<string, string | undefined>=} env * @property {!Object<string, string | undefined>=} env
* @property {boolean=} devtools * @property {boolean=} devtools
* @property {boolean=} pipe * @property {boolean=} pipe
* @property {boolean=} appMode */
/**
* @typedef {Object} BrowserOptions
* @property {boolean=} ignoreHTTPSErrors
* @property {(?Puppeteer.Viewport)=} defaultViewport
* @property {number=} slowMo
*/ */

View File

@ -35,11 +35,11 @@ class Page extends EventEmitter {
* @param {!Puppeteer.CDPSession} client * @param {!Puppeteer.CDPSession} client
* @param {!Puppeteer.Target} target * @param {!Puppeteer.Target} target
* @param {boolean} ignoreHTTPSErrors * @param {boolean} ignoreHTTPSErrors
* @param {boolean} setDefaultViewport * @param {?Puppeteer.Viewport} defaultViewport
* @param {!Puppeteer.TaskQueue} screenshotTaskQueue * @param {!Puppeteer.TaskQueue} screenshotTaskQueue
* @return {!Promise<!Page>} * @return {!Promise<!Page>}
*/ */
static async create(client, target, ignoreHTTPSErrors, setDefaultViewport, screenshotTaskQueue) { static async create(client, target, ignoreHTTPSErrors, defaultViewport, screenshotTaskQueue) {
await client.send('Page.enable'); await client.send('Page.enable');
const {frameTree} = await client.send('Page.getFrameTree'); const {frameTree} = await client.send('Page.getFrameTree');
@ -57,8 +57,8 @@ class Page extends EventEmitter {
if (ignoreHTTPSErrors) if (ignoreHTTPSErrors)
await client.send('Security.setOverrideCertificateErrors', {override: true}); await client.send('Security.setOverrideCertificateErrors', {override: true});
// Initialize default page size. // Initialize default page size.
if (setDefaultViewport) if (defaultViewport)
await page.setViewport({width: 800, height: 600}); await page.setViewport(defaultViewport);
return page; return page;
} }
@ -89,6 +89,8 @@ class Page extends EventEmitter {
this._coverage = new Coverage(client); this._coverage = new Coverage(client);
this._defaultNavigationTimeout = 30000; this._defaultNavigationTimeout = 30000;
this._javascriptEnabled = true; this._javascriptEnabled = true;
/** @type {?Puppeteer.Viewport} */
this._viewport = null;
this._screenshotTaskQueue = screenshotTaskQueue; this._screenshotTaskQueue = screenshotTaskQueue;
@ -738,7 +740,7 @@ class Page extends EventEmitter {
} }
/** /**
* @param {!Page.Viewport} viewport * @param {!Puppeteer.Viewport} viewport
*/ */
async setViewport(viewport) { async setViewport(viewport) {
const needsReload = await this._emulationManager.emulateViewport(viewport); const needsReload = await this._emulationManager.emulateViewport(viewport);
@ -748,7 +750,7 @@ class Page extends EventEmitter {
} }
/** /**
* @return {!Page.Viewport} * @return {?Puppeteer.Viewport}
*/ */
viewport() { viewport() {
return this._viewport; return this._viewport;
@ -1128,15 +1130,6 @@ Page.Events = {
WorkerDestroyed: 'workerdestroyed', WorkerDestroyed: 'workerdestroyed',
}; };
/**
* @typedef {Object} Page.Viewport
* @property {number} width
* @property {number} height
* @property {number=} deviceScaleFactor
* @property {boolean=} isMobile
* @property {boolean=} isLandscape
* @property {boolean=} hasTouch
*/
/** /**
* @typedef {Object} Network.Cookie * @typedef {Object} Network.Cookie

View File

@ -7,16 +7,16 @@ class Target {
* @param {!Puppeteer.BrowserContext} browserContext * @param {!Puppeteer.BrowserContext} browserContext
* @param {!function():!Promise<!Puppeteer.CDPSession>} sessionFactory * @param {!function():!Promise<!Puppeteer.CDPSession>} sessionFactory
* @param {boolean} ignoreHTTPSErrors * @param {boolean} ignoreHTTPSErrors
* @param {boolean} setDefaultViewport * @param {?Puppeteer.Viewport} defaultViewport
* @param {!Puppeteer.TaskQueue} screenshotTaskQueue * @param {!Puppeteer.TaskQueue} screenshotTaskQueue
*/ */
constructor(targetInfo, browserContext, sessionFactory, ignoreHTTPSErrors, setDefaultViewport, screenshotTaskQueue) { constructor(targetInfo, browserContext, sessionFactory, ignoreHTTPSErrors, defaultViewport, screenshotTaskQueue) {
this._targetInfo = targetInfo; this._targetInfo = targetInfo;
this._browserContext = browserContext; this._browserContext = browserContext;
this._targetId = targetInfo.targetId; this._targetId = targetInfo.targetId;
this._sessionFactory = sessionFactory; this._sessionFactory = sessionFactory;
this._ignoreHTTPSErrors = ignoreHTTPSErrors; this._ignoreHTTPSErrors = ignoreHTTPSErrors;
this._setDefaultViewport = setDefaultViewport; this._defaultViewport = defaultViewport;
this._screenshotTaskQueue = screenshotTaskQueue; this._screenshotTaskQueue = screenshotTaskQueue;
/** @type {?Promise<!Puppeteer.Page>} */ /** @type {?Promise<!Puppeteer.Page>} */
this._pagePromise = null; this._pagePromise = null;
@ -40,7 +40,7 @@ class Target {
async page() { async page() {
if ((this._targetInfo.type === 'page' || this._targetInfo.type === 'background_page') && !this._pagePromise) { if ((this._targetInfo.type === 'page' || this._targetInfo.type === 'background_page') && !this._pagePromise) {
this._pagePromise = this._sessionFactory() this._pagePromise = this._sessionFactory()
.then(client => Page.create(client, this, this._ignoreHTTPSErrors, this._setDefaultViewport, this._screenshotTaskQueue)); .then(client => Page.create(client, this, this._ignoreHTTPSErrors, this._defaultViewport, this._screenshotTaskQueue));
} }
return this._pagePromise; return this._pagePromise;
} }

10
lib/externs.d.ts vendored
View File

@ -38,7 +38,15 @@ declare global {
close(); close();
} }
export interface ChildProcess extends child_process.ChildProcess {} export interface ChildProcess extends child_process.ChildProcess { }
export type Viewport = {
width: number;
height: number;
deviceScaleFactor?: number;
isMobile?: boolean;
isLandscape?: boolean;
hasTouch?: boolean;
}
} }
} }

View File

@ -60,16 +60,6 @@ module.exports.addTests = function({testRunner, expect, PROJECT_ROOT, defaultBro
await rmAsync(downloadsFolder); await rmAsync(downloadsFolder);
}); });
}); });
describe('AppMode', function() {
it('should work', async() => {
const options = Object.assign({appMode: true}, defaultBrowserOptions);
const browser = await puppeteer.launch(options);
const page = await browser.newPage();
expect(await page.evaluate('11 * 11')).toBe(121);
await page.close();
await browser.close();
});
});
describe('Puppeteer.launch', function() { describe('Puppeteer.launch', function() {
it('should reject all promises when browser is closed', async() => { it('should reject all promises when browser is closed', async() => {
@ -229,6 +219,28 @@ module.exports.addTests = function({testRunner, expect, PROJECT_ROOT, defaultBro
expect(pages[0].url()).toBe(customUrl); expect(pages[0].url()).toBe(customUrl);
await browser.close(); await browser.close();
}); });
it('should set the default viewport', async() => {
const options = Object.assign({}, defaultBrowserOptions, {
defaultViewport: {
width: 456,
height: 789
}
});
const browser = await puppeteer.launch(options);
const page = await browser.newPage();
expect(await page.evaluate('window.innerWidth')).toBe(456);
expect(await page.evaluate('window.innerHeight')).toBe(789);
await browser.close();
});
it('should disable the default viewport', async() => {
const options = Object.assign({}, defaultBrowserOptions, {
defaultViewport: null
});
const browser = await puppeteer.launch(options);
const page = await browser.newPage();
expect(page.viewport()).toBe(null);
await browser.close();
});
}); });
describe('Puppeteer.connect', function() { describe('Puppeteer.connect', function() {
it('should be able to connect multiple times to the same browser', async({server}) => { it('should be able to connect multiple times to the same browser', async({server}) => {