Use puppeteer.launch
instead of browser constructor (#255)
This patch: - split browser launching logic from Browser into `lib/Launcher.js` - introduce `puppeteer` namespace which currently has a single `launch` method to start a browser With this patch, the browser is no longer created with the `new Browser(..)` command. Instead, it should be "launched" via the `puppeteer.launch` method: ```js const puppeteer = require('puppeteer'); puppeteer.launch().then(async browser => { ... }); ``` With this approach browser instance lifetime matches the lifetime of actual browser process. This helps us: - remove proxy streams, e.g. browser.stderr and browser.stdout - cleanup browser class and make it possible to connect to remote browser - introduce events on the browser instance, e.g. 'page' event. In case of lazy-launching browser, we should've launch browser when an event listener is added, which is unneded comlpexity.
This commit is contained in:
parent
0a3dd4e727
commit
13e8580a34
@ -133,11 +133,11 @@ npm run coverage
|
||||
Puppeteer uses [DEBUG](https://github.com/visionmedia/debug) module to expose some of it's inner guts under the `puppeteer` namespace. Try putting the following in a file called `script.js` and running it via `DEBUG=* node script.js`:
|
||||
|
||||
```js
|
||||
const {Browser} = require('puppeteer');
|
||||
const browser = new Browser();
|
||||
const puppeteer = require('puppeteer');
|
||||
|
||||
(async() => {
|
||||
|
||||
const browser = await puppeteer.launch();
|
||||
const page = await browser.newPage();
|
||||
await page.goto('https://example.com');
|
||||
browser.close();
|
||||
|
29
README.md
29
README.md
@ -39,10 +39,11 @@ of `Browser`, open pages, and then manipulate them with [Puppeteer's API](https:
|
||||
**Example** - navigating to https://example.com and saving a screenshot as *example.png*:
|
||||
|
||||
```js
|
||||
const {Browser} = require('puppeteer');
|
||||
const browser = new Browser();
|
||||
const puppeteer = require('puppeteer');
|
||||
|
||||
(async() => {
|
||||
|
||||
const browser = await puppeteer.launch();
|
||||
const page = await browser.newPage();
|
||||
await page.goto('https://example.com');
|
||||
await page.screenshot({path: 'example.png'});
|
||||
@ -54,16 +55,17 @@ browser.close();
|
||||
or, without `async`/`await`:
|
||||
|
||||
```js
|
||||
const {Browser} = require('puppeteer');
|
||||
const browser = new Browser();
|
||||
const puppeteer = require('puppeteer');
|
||||
|
||||
browser.newPage().then(page => {
|
||||
page.goto('https://example.com').then(response => {
|
||||
page.screenshot({path: 'example.png'}).then(buffer => {
|
||||
browser.close();
|
||||
});
|
||||
puppeteer.launch()
|
||||
.then(browser => browser.newPage())
|
||||
.then(page => {
|
||||
page.goto('https://example.com').then(response => {
|
||||
page.screenshot({path: 'example.png'}).then(buffer => {
|
||||
browser.close();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Puppeteer sets an initial page size to 800px x 600px, which defines the screenshot size. The page size can be customized with [`Page.setViewport()`](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagesetviewportviewport).
|
||||
@ -71,10 +73,11 @@ Puppeteer sets an initial page size to 800px x 600px, which defines the screensh
|
||||
**Example** - create a PDF.
|
||||
|
||||
```js
|
||||
const {Browser} = require('puppeteer');
|
||||
const browser = new Browser();
|
||||
const puppeteer = require('puppeteer');
|
||||
|
||||
(async() => {
|
||||
|
||||
const browser = await puppeteer.launch();
|
||||
const page = await browser.newPage();
|
||||
await page.goto('https://news.ycombinator.com', {waitUntil: 'networkidle'});
|
||||
await page.pdf({path: 'hn.pdf', format: 'A4'});
|
||||
@ -92,7 +95,7 @@ See [`Page.pdf()`](https://github.com/GoogleChrome/puppeteer/blob/master/docs/ap
|
||||
Puppeteer launches Chromium in [headless mode](https://developers.google.com/web/updates/2017/04/headless-chrome). To launch a full version of Chromium, set the ['headless' option](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#new-browseroptions) when creating a browser:
|
||||
|
||||
```js
|
||||
const browser = new Browser({headless: false});
|
||||
const browser = await puppeteer.launch({headless: false});
|
||||
```
|
||||
|
||||
**2. Runs a bundled version of Chromium**
|
||||
|
161
docs/api.md
161
docs/api.md
@ -5,12 +5,11 @@
|
||||
<!-- toc -->
|
||||
|
||||
- [Puppeteer](#puppeteer)
|
||||
* [class: Puppeteer](#class-puppeteer)
|
||||
+ [puppeteer.launch([options])](#puppeteerlaunchoptions)
|
||||
* [class: Browser](#class-browser)
|
||||
+ [new Browser([options])](#new-browseroptions)
|
||||
+ [browser.close()](#browserclose)
|
||||
+ [browser.newPage()](#browsernewpage)
|
||||
+ [browser.stderr](#browserstderr)
|
||||
+ [browser.stdout](#browserstdout)
|
||||
+ [browser.version()](#browserversion)
|
||||
* [class: Page](#class-page)
|
||||
+ [event: 'console'](#event-console)
|
||||
@ -120,18 +119,33 @@
|
||||
|
||||
Puppeteer is a Node library which provides a high-level API to control Chromium over the DevTools Protocol.
|
||||
|
||||
Puppeteer provides a top-level require which has a [Browser](#class-browser) class.
|
||||
The following is a typical example of using a Browser class to drive automation:
|
||||
|
||||
### class: Puppeteer
|
||||
|
||||
Puppeteer module provides a method to launch a chromium instance.
|
||||
The following is a typical example of using a Puppeteer to drive automation:
|
||||
```js
|
||||
const {Browser} = require('puppeteer');
|
||||
const browser = new Browser();
|
||||
browser.newPage().then(async page => {
|
||||
const puppeteer = require('puppeteer');
|
||||
puppeteer.launch().then(async browser => {
|
||||
let page = await browser.newPage();
|
||||
await page.goto('https://google.com');
|
||||
// other actions...
|
||||
browser.close();
|
||||
});
|
||||
```
|
||||
|
||||
#### puppeteer.launch([options])
|
||||
- `options` <[Object]> Set of configurable options to set on the browser. Can have the following fields:
|
||||
- `ignoreHTTPSErrors` <[boolean]> Whether to ignore HTTPS errors during navigation. Defaults to `false`.
|
||||
- `headless` <[boolean]> Whether to run chromium in [headless mode](https://developers.google.com/web/updates/2017/04/headless-chrome). Defaults to `true`.
|
||||
- `executablePath` <[string]> Path to a chromium executable to run instead of 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.
|
||||
- `args` <[Array]<[string]>> Additional arguments to pass to the chromium instance. List of chromium flags can be found [here](http://peter.sh/experiments/chromium-command-line-switches/).
|
||||
- `dumpio` <[boolean]> Whether to pipe browser process stdout and stderr into `process.stdout` and `process.stderr`. Defaults to `false`.
|
||||
- returns: <[Promise]<[Browser]>> Promise which resolves to browser instance.
|
||||
|
||||
The method launches a browser instance with given arguments. The browser will be closed when the parent node.js process gets closed.
|
||||
|
||||
### class: Browser
|
||||
|
||||
Browser manages a browser instance, creating it with a predefined
|
||||
@ -140,23 +154,15 @@ not necessarily result in launching browser; the instance will be launched when
|
||||
|
||||
A typical scenario of using [Browser] is opening a new page and navigating it to a desired URL:
|
||||
```js
|
||||
const {Browser} = require('puppeteer');
|
||||
const browser = new Browser();
|
||||
browser.newPage().then(async page => {
|
||||
const puppeteer = require('puppeteer');
|
||||
|
||||
puppeteer.launch().then(async browser => {
|
||||
let page = await browser.newPage();
|
||||
await page.goto('https://example.com');
|
||||
browser.close();
|
||||
});
|
||||
```
|
||||
|
||||
#### new Browser([options])
|
||||
- `options` <[Object]> Set of configurable options to set on the browser. Can have the following fields:
|
||||
- `ignoreHTTPSErrors` <[boolean]> Whether to ignore HTTPS errors during navigation. Defaults to `false`.
|
||||
- `headless` <[boolean]> Whether to run chromium in [headless mode](https://developers.google.com/web/updates/2017/04/headless-chrome). Defaults to `true`.
|
||||
- `executablePath` <[string]> Path to a chromium executable to run instead of 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.
|
||||
- `args` <[Array]<[string]>> Additional arguments to pass to the chromium instance. List of chromium flags can be found [here](http://peter.sh/experiments/chromium-command-line-switches/).
|
||||
|
||||
|
||||
#### browser.close()
|
||||
|
||||
@ -166,36 +172,6 @@ Closes browser with all the pages (if any were opened). The browser object itsel
|
||||
- returns: <[Promise]<[Page]>> Promise which resolves to a new [Page] object.
|
||||
|
||||
|
||||
#### browser.stderr
|
||||
- <[stream.Readable]>
|
||||
|
||||
A Readable Stream that represents the browser process's stderr.
|
||||
For example, `stderr` could be piped into `process.stderr`:
|
||||
```js
|
||||
const {Browser} = require('puppeteer');
|
||||
const browser = new Browser();
|
||||
browser.stderr.pipe(process.stderr);
|
||||
browser.version().then(version => {
|
||||
console.log(version);
|
||||
browser.close();
|
||||
});
|
||||
```
|
||||
|
||||
#### browser.stdout
|
||||
- <[stream.Readable]>
|
||||
|
||||
A Readable Stream that represents the browser process's stdout.
|
||||
For example, `stdout` could be piped into `process.stdout`:
|
||||
```js
|
||||
const {Browser} = require('puppeteer');
|
||||
const browser = new Browser();
|
||||
browser.stdout.pipe(process.stdout);
|
||||
browser.version().then(version => {
|
||||
console.log(version);
|
||||
browser.close();
|
||||
});
|
||||
```
|
||||
|
||||
#### browser.version()
|
||||
- returns: <[Promise]<[string]>> String describing browser version. For headless chromium, this is similar to `HeadlessChrome/61.0.3153.0`. For non-headless, this is `Chrome/61.0.3153.0`.
|
||||
|
||||
@ -207,9 +183,10 @@ Page provides methods to interact with browser page. Page could be thought about
|
||||
|
||||
An example of creating a page, navigating it to a URL and saving screenshot as `screenshot.png`:
|
||||
```js
|
||||
const {Browser} = require('puppeteer');
|
||||
const browser = new Browser();
|
||||
browser.newPage().then(async page =>
|
||||
const puppeteer = require('puppeteer');
|
||||
|
||||
puppeteer.launch().then(async browser => {
|
||||
let page = await browser.newPage();
|
||||
await page.goto('https://example.com');
|
||||
await page.screenshot({path: 'screenshot.png'});
|
||||
browser.close();
|
||||
@ -298,11 +275,11 @@ If the `puppeteerFunction` returns a promise, it would be awaited.
|
||||
|
||||
An example of adding `window.md5` binding to the page:
|
||||
```js
|
||||
const {Browser} = require('puppeteer');
|
||||
const browser = new Browser();
|
||||
const puppeteer = require('puppeteer');
|
||||
const crypto = require('crypto');
|
||||
|
||||
browser.newPage().then(async page => {
|
||||
puppeteer.launch().then(async browser => {
|
||||
let page = await browser.newPage();
|
||||
page.on('console', console.log);
|
||||
await page.setInPageCallback('md5', text => crypto.createHash('md5').update(text).digest('hex'));
|
||||
await page.evaluate(async () => {
|
||||
@ -318,11 +295,11 @@ browser.newPage().then(async page => {
|
||||
An example of adding `window.readfile` binding to the page:
|
||||
|
||||
```js
|
||||
const {Browser} = require('puppeteer');
|
||||
const browser = new Browser();
|
||||
const puppeteer = require('puppeteer');
|
||||
const fs = require('fs');
|
||||
|
||||
browser.newPage().then(async page => {
|
||||
puppeteer.launch().then(async browser => {
|
||||
let page = await browser.newPage();
|
||||
page.on('console', console.log);
|
||||
await page.setInPageCallback('readfile', async filePath => {
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -380,11 +357,12 @@ Emulates given device metrics and user agent. This method is a shortcut for call
|
||||
To aid emulation, puppeteer provides a list of device descriptors which could be obtained via the `require('puppeteer/DeviceDescriptors')` command.
|
||||
Below is an example of emulating iPhone 6 in puppeteer:
|
||||
```js
|
||||
const {Browser} = require('puppeteer');
|
||||
const puppeteer = require('puppeteer');
|
||||
const devices = require('puppeteer/DeviceDescriptors');
|
||||
const iPhone = devices['iPhone 6'];
|
||||
const browser = new Browser();
|
||||
browser.newPage().then(async page => {
|
||||
|
||||
puppeteer.launch().then(async browser => {
|
||||
let page = await browser.newPage();
|
||||
await page.emulate(iPhone);
|
||||
await page.goto('https://google.com');
|
||||
// other actions...
|
||||
@ -402,9 +380,9 @@ List of all available devices is available in the source code: [DeviceDescriptor
|
||||
If the function, passed to the `page.evaluate`, returns a [Promise], then `page.evaluate` would wait for the promise to resolve and return it's value.
|
||||
|
||||
```js
|
||||
const {Browser} = require('puppeteer');
|
||||
const browser = new Browser();
|
||||
browser.newPage().then(async page =>
|
||||
const puppeteer = require('puppeteer');
|
||||
puppeteer.launch().then(async browser => {
|
||||
let page = await browser.newPage();
|
||||
const result = await page.evaluate(() => {
|
||||
return Promise.resolve(8 * 7);
|
||||
});
|
||||
@ -613,10 +591,9 @@ Activating request interception enables `request.abort` and `request.continue`.
|
||||
|
||||
An example of a naïve request interceptor which aborts all image requests:
|
||||
```js
|
||||
const {Browser} = require('puppeteer');
|
||||
const browser = new Browser();
|
||||
|
||||
browser.newPage().then(async page =>
|
||||
const puppeteer = require('puppeteer');
|
||||
puppeteer.launch().then(async browser => {
|
||||
let page = await browser.newPage();
|
||||
await page.setRequestInterceptionEnabled(true);
|
||||
page.on('request', request => {
|
||||
if (interceptedRequest.url.endsWith('.png') || interceptedRequest.url.endsWith('.jpg'))
|
||||
@ -713,10 +690,9 @@ Shortcut for [page.mainFrame().waitFor(selectorOrFunctionOrTimeout[, options])](
|
||||
|
||||
The `waitForFunction` could be used to observe viewport size change:
|
||||
```js
|
||||
const {Browser} = require('.');
|
||||
const browser = new Browser();
|
||||
|
||||
browser.newPage().then(async page => {
|
||||
const puppeteer = require('puppeteer');
|
||||
puppeteer.launch().then(async browser => {
|
||||
let page = await browser.newPage();
|
||||
const watchDog = page.waitForFunction('window.innerWidth < 100');
|
||||
page.setViewport({width: 50, height: 50});
|
||||
await watchDog;
|
||||
@ -748,10 +724,9 @@ immediately. If the selector doesn't appear after the `timeout` milliseconds of
|
||||
|
||||
This method works across navigations:
|
||||
```js
|
||||
const {Browser} = new require('puppeteer');
|
||||
const browser = new Browser();
|
||||
|
||||
browser.newPage().then(async page => {
|
||||
const puppeteer = require('puppeteer');
|
||||
puppeteer.launch().then(async browser => {
|
||||
let page = await browser.newPage();
|
||||
let currentURL;
|
||||
page.waitForSelector('img').then(() => console.log('First URL with image: ' + currentURL));
|
||||
for (currentURL of ['https://example.com', 'https://google.com', 'https://bbc.com'])
|
||||
@ -874,9 +849,10 @@ Only one trace can be active at a time per browser.
|
||||
|
||||
An example of using `Dialog` class:
|
||||
```js
|
||||
const {Browser} = require('puppeteer');
|
||||
const browser = new Browser({headless: false});
|
||||
browser.newPage().then(async page => {
|
||||
const puppeteer = require('puppeteer');
|
||||
|
||||
puppeteer.launch().then(async browser => {
|
||||
let page = await browser.newPage();
|
||||
page.on('dialog', dialog => {
|
||||
console.log(dialog.message());
|
||||
dialog.dismiss();
|
||||
@ -916,10 +892,10 @@ At every point of time, page exposes its current frame tree via the [page.mainFr
|
||||
An example of dumping frame tree:
|
||||
|
||||
```js
|
||||
const {Browser} = new require('.');
|
||||
const browser = new Browser({headless: true});
|
||||
const puppeteer = require('puppeteer');
|
||||
|
||||
browser.newPage().then(async page => {
|
||||
puppeteer.launch().then(async browser => {
|
||||
let page = await browser.newPage();
|
||||
await page.goto('https://www.google.com/chrome/browser/canary.html');
|
||||
dumpFrameTree(page.mainFrame(), '');
|
||||
browser.close();
|
||||
@ -958,9 +934,10 @@ Adds a `<script>` tag to the frame with the desired url. Alternatively, JavaScri
|
||||
If the function, passed to the `page.evaluate`, returns a [Promise], then `page.evaluate` would wait for the promise to resolve and return it's value.
|
||||
|
||||
```js
|
||||
const {Browser} = require('puppeteer');
|
||||
const browser = new Browser();
|
||||
browser.newPage().then(async page =>
|
||||
const puppeteer = require('puppeteer');
|
||||
|
||||
puppeteer.launch().then(async browser => {
|
||||
let page = await browser.newPage();
|
||||
const result = await page.evaluate(() => {
|
||||
return Promise.resolve(8 * 7);
|
||||
});
|
||||
@ -1042,10 +1019,10 @@ This method behaves differently with respect to the type of the first parameter:
|
||||
|
||||
The `waitForFunction` could be used to observe viewport size change:
|
||||
```js
|
||||
const {Browser} = require('.');
|
||||
const browser = new Browser();
|
||||
const puppeteer = require('puppeteer');
|
||||
|
||||
browser.newPage().then(async page => {
|
||||
puppeteer.launch().then(async browser => {
|
||||
let page = await browser.newPage();
|
||||
const watchDog = page.waitForFunction('window.innerWidth < 100');
|
||||
page.setViewport({width: 50, height: 50});
|
||||
await watchDog;
|
||||
@ -1066,10 +1043,10 @@ immediately. If the selector doesn't appear after the `timeout` milliseconds of
|
||||
|
||||
This method works across navigations:
|
||||
```js
|
||||
const {Browser} = new require('puppeteer');
|
||||
const browser = new Browser();
|
||||
const puppeteer = require('puppeteer');
|
||||
|
||||
browser.newPage().then(async page => {
|
||||
puppeteer.launch().then(async browser => {
|
||||
let page = await browser.newPage();
|
||||
let currentURL;
|
||||
page.waitForSelector('img').then(() => console.log('First URL with image: ' + currentURL));
|
||||
for (currentURL of ['https://example.com', 'https://google.com', 'https://bbc.com'])
|
||||
|
@ -14,13 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const {Browser} = require('puppeteer');
|
||||
const browser = new Browser();
|
||||
const puppeteer = require('puppeteer');
|
||||
|
||||
(async() => {
|
||||
|
||||
const browser = await puppeteer.launch();
|
||||
const page = await browser.newPage();
|
||||
await page.setRequestInterceptor(request => {
|
||||
await page.setRequestInterceptionEnabled(true);
|
||||
page.on('request', request => {
|
||||
if (/\.(png|jpg|jpeg$)/.test(request.url))
|
||||
request.abort();
|
||||
else
|
||||
|
@ -14,8 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const {Browser} = require('puppeteer');
|
||||
const browser = new Browser();
|
||||
const puppeteer = require('puppeteer');
|
||||
|
||||
function sniffDetector() {
|
||||
let userAgent = window.navigator.userAgent;
|
||||
@ -34,6 +33,7 @@ function sniffDetector() {
|
||||
|
||||
(async() => {
|
||||
|
||||
const browser = await puppeteer.launch();
|
||||
const page = await browser.newPage();
|
||||
await page.evaluateOnNewDocument(sniffDetector);
|
||||
await page.goto('https://www.google.com', {waitUntil: 'networkidle'});
|
||||
|
@ -14,11 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const {Browser} = require('puppeteer');
|
||||
const browser = new Browser();
|
||||
const puppeteer = require('puppeteer');
|
||||
|
||||
(async() => {
|
||||
|
||||
const browser = await puppeteer.launch();
|
||||
const page = await browser.newPage();
|
||||
await page.goto('https://news.ycombinator.com', {waitUntil: 'networkidle'});
|
||||
// page.pdf() is currently supported only in headless mode.
|
||||
|
@ -14,12 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const {Browser} = require('puppeteer');
|
||||
const puppeteer = require('puppeteer');
|
||||
const devices = require('puppeteer/DeviceDescriptors');
|
||||
const browser = new Browser();
|
||||
|
||||
(async() => {
|
||||
|
||||
const browser = await puppeteer.launch();
|
||||
let page = await browser.newPage();
|
||||
await page.emulate(devices['iPhone 6']);
|
||||
await page.goto('https://www.nytimes.com/');
|
||||
|
@ -14,11 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const {Browser} = require('puppeteer');
|
||||
const browser = new Browser();
|
||||
const puppeteer = require('puppeteer');
|
||||
|
||||
(async() => {
|
||||
|
||||
const browser = await puppeteer.launch();
|
||||
const page = await browser.newPage();
|
||||
await page.goto('http://example.com');
|
||||
await page.screenshot({path: 'example.png'});
|
||||
|
@ -14,11 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const puppeteer = require('puppeteer');
|
||||
|
||||
(async() => {
|
||||
|
||||
const {Browser} = require('puppeteer');
|
||||
const browser = new Browser();
|
||||
|
||||
const browser = await puppeteer.launch();
|
||||
const page = await browser.newPage();
|
||||
await page.goto('https://google.com', {waitUntil: 'networkidle'});
|
||||
// Type our query into the search bar
|
||||
|
4
index.js
4
index.js
@ -14,6 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
Browser: require('./lib/Browser')
|
||||
};
|
||||
module.exports = require('./lib/Puppeteer');
|
||||
|
156
lib/Browser.js
156
lib/Browser.js
@ -14,85 +14,26 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const {Duplex} = require('stream');
|
||||
const path = require('path');
|
||||
const helper = require('./helper');
|
||||
const removeRecursive = require('rimraf').sync;
|
||||
const Page = require('./Page');
|
||||
const childProcess = require('child_process');
|
||||
const Downloader = require('../utils/ChromiumDownloader');
|
||||
const Connection = require('./Connection');
|
||||
const readline = require('readline');
|
||||
|
||||
const CHROME_PROFILE_PATH = path.resolve(__dirname, '..', '.dev_profile');
|
||||
let browserId = 0;
|
||||
|
||||
const DEFAULT_ARGS = [
|
||||
'--disable-background-networking',
|
||||
'--disable-background-timer-throttling',
|
||||
'--disable-client-side-phishing-detection',
|
||||
'--disable-default-apps',
|
||||
'--disable-hang-monitor',
|
||||
'--disable-popup-blocking',
|
||||
'--disable-prompt-on-repost',
|
||||
'--disable-sync',
|
||||
'--enable-automation',
|
||||
'--metrics-recording-only',
|
||||
'--no-first-run',
|
||||
'--password-store=basic',
|
||||
'--remote-debugging-port=0',
|
||||
'--safebrowsing-disable-auto-update',
|
||||
'--use-mock-keychain',
|
||||
];
|
||||
|
||||
class Browser {
|
||||
/**
|
||||
* @param {!Object=} options
|
||||
* @param {!Connection} connection
|
||||
* @param {boolean} ignoreHTTPSErrors
|
||||
* @param {function()=} closeCallback
|
||||
*/
|
||||
constructor(options) {
|
||||
options = options || {};
|
||||
++browserId;
|
||||
this._userDataDir = CHROME_PROFILE_PATH + browserId;
|
||||
this._remoteDebuggingPort = 0;
|
||||
this._chromeArguments = DEFAULT_ARGS.concat([
|
||||
`--user-data-dir=${this._userDataDir}`,
|
||||
]);
|
||||
if (typeof options.headless !== 'boolean' || options.headless) {
|
||||
this._chromeArguments.push(
|
||||
`--headless`,
|
||||
`--disable-gpu`,
|
||||
`--hide-scrollbars`
|
||||
);
|
||||
}
|
||||
if (typeof options.executablePath === 'string') {
|
||||
this._chromeExecutable = options.executablePath;
|
||||
} else {
|
||||
let chromiumRevision = require('../package.json').puppeteer.chromium_revision;
|
||||
let revisionInfo = Downloader.revisionInfo(Downloader.currentPlatform(), chromiumRevision);
|
||||
console.assert(revisionInfo, 'Chromium revision is not downloaded. Run npm install');
|
||||
this._chromeExecutable = revisionInfo.executablePath;
|
||||
}
|
||||
this._ignoreHTTPSErrors = !!options.ignoreHTTPSErrors;
|
||||
if (Array.isArray(options.args))
|
||||
this._chromeArguments.push(...options.args);
|
||||
this._connectionDelay = options.slowMo || 0;
|
||||
this._terminated = false;
|
||||
this._chromeProcess = null;
|
||||
this._launchPromise = null;
|
||||
constructor(connection, ignoreHTTPSErrors, closeCallback) {
|
||||
this._ignoreHTTPSErrors = ignoreHTTPSErrors;
|
||||
this._screenshotTaskQueue = new TaskQueue();
|
||||
|
||||
this.stderr = new ProxyStream();
|
||||
this.stdout = new ProxyStream();
|
||||
this._connection = connection;
|
||||
this._closeCallback = closeCallback || new Function();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Promise<!Page>}
|
||||
*/
|
||||
async newPage() {
|
||||
await this._ensureChromeIsRunning();
|
||||
if (!this._chromeProcess || this._terminated)
|
||||
throw new Error('ERROR: this chrome instance is not alive any more!');
|
||||
|
||||
const {targetId} = await this._connection.send('Target.createTarget', {url: 'about:blank'});
|
||||
const client = await this._connection.createSession(targetId);
|
||||
return await Page.create(client, this._ignoreHTTPSErrors, this._screenshotTaskQueue);
|
||||
@ -102,84 +43,19 @@ class Browser {
|
||||
* @return {!Promise<string>}
|
||||
*/
|
||||
async version() {
|
||||
await this._ensureChromeIsRunning();
|
||||
let version = await this._connection.send('Browser.getVersion');
|
||||
return version.product;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Promise}
|
||||
*/
|
||||
async _ensureChromeIsRunning() {
|
||||
if (!this._launchPromise)
|
||||
this._launchPromise = this._launchChrome();
|
||||
return this._launchPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Promise}
|
||||
*/
|
||||
async _launchChrome() {
|
||||
this._chromeProcess = childProcess.spawn(this._chromeExecutable, this._chromeArguments, {});
|
||||
let stderr = '';
|
||||
this._chromeProcess.stderr.on('data', data => stderr += data.toString('utf8'));
|
||||
// Cleanup as processes exit.
|
||||
const onProcessExit = () => this._chromeProcess.kill();
|
||||
process.on('exit', onProcessExit);
|
||||
this._chromeProcess.on('exit', () => {
|
||||
this._terminated = true;
|
||||
process.removeListener('exit', onProcessExit);
|
||||
removeRecursive(this._userDataDir);
|
||||
});
|
||||
this._chromeProcess.stderr.pipe(this.stderr);
|
||||
this._chromeProcess.stdout.pipe(this.stdout);
|
||||
|
||||
let {port, browserTargetId} = await waitForRemoteDebuggingPort(this._chromeProcess);
|
||||
// Failed to connect to browser.
|
||||
if (port === -1) {
|
||||
this._chromeProcess.kill();
|
||||
throw new Error('Failed to connect to chrome!');
|
||||
}
|
||||
|
||||
if (this._terminated)
|
||||
throw new Error('Failed to launch chrome! ' + stderr);
|
||||
this._remoteDebuggingPort = port;
|
||||
this._connection = await Connection.create(port, browserTargetId, this._connectionDelay);
|
||||
}
|
||||
|
||||
close() {
|
||||
if (!this._chromeProcess)
|
||||
return;
|
||||
this._chromeProcess.kill();
|
||||
this._connection.dispose();
|
||||
this._closeCallback.call(null);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Browser;
|
||||
helper.tracePublicAPI(Browser);
|
||||
|
||||
/**
|
||||
* @param {!ChildProcess} chromeProcess
|
||||
* @return {!Promise<number>}
|
||||
*/
|
||||
function waitForRemoteDebuggingPort(chromeProcess) {
|
||||
return new Promise(fulfill => {
|
||||
const rl = readline.createInterface({ input: chromeProcess.stderr });
|
||||
rl.on('line', onLine);
|
||||
rl.once('close', () => fulfill(-1));
|
||||
|
||||
/**
|
||||
* @param {string} line
|
||||
*/
|
||||
function onLine(line) {
|
||||
const match = line.match(/^DevTools listening on .*:(\d+)(\/.*)$/);
|
||||
if (!match)
|
||||
return;
|
||||
rl.removeListener('line', onLine);
|
||||
fulfill({port: Number.parseInt(match[1], 10), browserTargetId: match[2]});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class TaskQueue {
|
||||
constructor() {
|
||||
this._chain = Promise.resolve();
|
||||
@ -195,17 +71,3 @@ class TaskQueue {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class ProxyStream extends Duplex {
|
||||
_read() { }
|
||||
|
||||
/**
|
||||
* @param {?} chunk
|
||||
* @param {string} encoding
|
||||
* @param {function()} callback
|
||||
*/
|
||||
_write(chunk, encoding, callback) {
|
||||
this.push(chunk, encoding);
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +99,8 @@ class Connection extends EventEmitter {
|
||||
/**
|
||||
* @return {!Promise}
|
||||
*/
|
||||
async dispose() {
|
||||
dispose() {
|
||||
this._onClose();
|
||||
this._ws.close();
|
||||
}
|
||||
|
||||
|
128
lib/Launcher.js
Normal file
128
lib/Launcher.js
Normal file
@ -0,0 +1,128 @@
|
||||
/**
|
||||
* 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 path = require('path');
|
||||
const removeRecursive = require('rimraf').sync;
|
||||
const childProcess = require('child_process');
|
||||
const Downloader = require('../utils/ChromiumDownloader');
|
||||
const Connection = require('./Connection');
|
||||
const Browser = require('./Browser');
|
||||
const readline = require('readline');
|
||||
|
||||
const CHROME_PROFILE_PATH = path.resolve(__dirname, '..', '.dev_profile');
|
||||
let browserId = 0;
|
||||
|
||||
const DEFAULT_ARGS = [
|
||||
'--disable-background-networking',
|
||||
'--disable-background-timer-throttling',
|
||||
'--disable-client-side-phishing-detection',
|
||||
'--disable-default-apps',
|
||||
'--disable-hang-monitor',
|
||||
'--disable-popup-blocking',
|
||||
'--disable-prompt-on-repost',
|
||||
'--disable-sync',
|
||||
'--enable-automation',
|
||||
'--metrics-recording-only',
|
||||
'--no-first-run',
|
||||
'--password-store=basic',
|
||||
'--remote-debugging-port=0',
|
||||
'--safebrowsing-disable-auto-update',
|
||||
'--use-mock-keychain',
|
||||
];
|
||||
|
||||
class Launcher {
|
||||
/**
|
||||
* @param {!Object} options
|
||||
* @return {!Promise<!Browser>}
|
||||
*/
|
||||
static async launch(options) {
|
||||
options = options || {};
|
||||
++browserId;
|
||||
let userDataDir = CHROME_PROFILE_PATH + browserId;
|
||||
let chromeArguments = DEFAULT_ARGS.concat([
|
||||
`--user-data-dir=${userDataDir}`,
|
||||
]);
|
||||
if (typeof options.headless !== 'boolean' || options.headless) {
|
||||
chromeArguments.push(
|
||||
`--headless`,
|
||||
`--disable-gpu`,
|
||||
`--hide-scrollbars`
|
||||
);
|
||||
}
|
||||
let chromeExecutable = options.executablePath;
|
||||
if (typeof chromeExecutable !== 'string') {
|
||||
let chromiumRevision = require('../package.json').puppeteer.chromium_revision;
|
||||
let revisionInfo = Downloader.revisionInfo(Downloader.currentPlatform(), chromiumRevision);
|
||||
console.assert(revisionInfo, 'Chromium revision is not downloaded. Run npm install');
|
||||
chromeExecutable = revisionInfo.executablePath;
|
||||
}
|
||||
if (Array.isArray(options.args))
|
||||
chromeArguments.push(...options.args);
|
||||
let chromeProcess = childProcess.spawn(chromeExecutable, chromeArguments, {});
|
||||
if (options.dumpio) {
|
||||
chromeProcess.stdout.pipe(process.stdout);
|
||||
chromeProcess.stderr.pipe(process.stderr);
|
||||
}
|
||||
let stderr = '';
|
||||
chromeProcess.stderr.on('data', data => stderr += data.toString('utf8'));
|
||||
// Cleanup as processes exit.
|
||||
const onProcessExit = () => chromeProcess.kill();
|
||||
process.on('exit', onProcessExit);
|
||||
let terminated = false;
|
||||
chromeProcess.on('exit', () => {
|
||||
terminated = true;
|
||||
process.removeListener('exit', onProcessExit);
|
||||
removeRecursive(userDataDir);
|
||||
});
|
||||
|
||||
let {port, browserTargetId} = await waitForRemoteDebuggingPort(chromeProcess);
|
||||
if (terminated)
|
||||
throw new Error('Failed to launch chrome! ' + stderr);
|
||||
// Failed to connect to browser.
|
||||
if (port === -1) {
|
||||
chromeProcess.kill();
|
||||
throw new Error('Failed to connect to chrome!');
|
||||
}
|
||||
|
||||
let connectionDelay = options.slowMo || 0;
|
||||
let connection = await Connection.create(port, browserTargetId, connectionDelay);
|
||||
return new Browser(connection, !!options.ignoreHTTPSErrors, () => chromeProcess.kill());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!ChildProcess} chromeProcess
|
||||
* @return {!Promise<number>}
|
||||
*/
|
||||
function waitForRemoteDebuggingPort(chromeProcess) {
|
||||
return new Promise(fulfill => {
|
||||
const rl = readline.createInterface({ input: chromeProcess.stderr });
|
||||
rl.on('line', onLine);
|
||||
rl.once('close', () => fulfill(-1));
|
||||
|
||||
/**
|
||||
* @param {string} line
|
||||
*/
|
||||
function onLine(line) {
|
||||
const match = line.match(/^DevTools listening on .*:(\d+)(\/.*)$/);
|
||||
if (!match)
|
||||
return;
|
||||
rl.removeListener('line', onLine);
|
||||
fulfill({port: Number.parseInt(match[1], 10), browserTargetId: match[2]});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = Launcher;
|
30
lib/Puppeteer.js
Normal file
30
lib/Puppeteer.js
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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 helper = require('./helper');
|
||||
const Launcher = require('./Launcher');
|
||||
|
||||
class Puppeteer {
|
||||
/**
|
||||
* @param {!Object=} options
|
||||
* @return {!Promise<!Browser>}
|
||||
*/
|
||||
static async launch(options) {
|
||||
return Launcher.launch(options);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Puppeteer;
|
||||
helper.tracePublicAPI(Puppeteer);
|
@ -15,6 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const await = require('./utilities').await;
|
||||
const vm = require('vm');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
@ -24,7 +25,7 @@ const System = require('./System');
|
||||
const WebPage = require('./WebPage');
|
||||
const WebServer = require('./WebServer');
|
||||
const child_process = require('child_process');
|
||||
const Browser = require('..').Browser;
|
||||
const puppeteer = require('..');
|
||||
const argv = require('minimist')(process.argv.slice(2), {
|
||||
alias: { v: 'version' },
|
||||
boolean: ['headless'],
|
||||
@ -55,23 +56,19 @@ if (!fs.existsSync(scriptPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let browser = new Browser({
|
||||
headless: argv.headless,
|
||||
args: ['--no-sandbox']
|
||||
});
|
||||
|
||||
let context = createPhantomContext(browser, scriptPath, argv);
|
||||
let context = createPhantomContext(argv.headless, scriptPath, argv);
|
||||
let scriptContent = fs.readFileSync(scriptPath, 'utf8');
|
||||
vm.runInContext(scriptContent, context);
|
||||
|
||||
/**
|
||||
* @param {!Browser} browser
|
||||
* @param {boolean} headless
|
||||
* @param {string} scriptPath
|
||||
* @param {!Array<string>} argv
|
||||
* @return {!Object}
|
||||
*/
|
||||
function createPhantomContext(browser, scriptPath, argv) {
|
||||
function createPhantomContext(headless, scriptPath, argv) {
|
||||
let context = {};
|
||||
let browser = null;
|
||||
context.setInterval = setInterval;
|
||||
context.setTimeout = setTimeout;
|
||||
context.clearInterval = clearInterval;
|
||||
@ -80,7 +77,7 @@ function createPhantomContext(browser, scriptPath, argv) {
|
||||
context.phantom = Phantom.create(context, scriptPath);
|
||||
context.console = console;
|
||||
context.window = context;
|
||||
context.WebPage = options => new WebPage(browser, scriptPath, options);
|
||||
context.WebPage = options => new WebPage(ensureBrowser(), scriptPath, options);
|
||||
|
||||
vm.createContext(context);
|
||||
|
||||
@ -104,5 +101,15 @@ function createPhantomContext(browser, scriptPath, argv) {
|
||||
filename: 'bootstrap.js'
|
||||
})(nativeExports);
|
||||
return context;
|
||||
|
||||
function ensureBrowser() {
|
||||
if (!browser) {
|
||||
browser = await(puppeteer.launch({
|
||||
headless: argv.headless,
|
||||
args: ['--no-sandbox']
|
||||
}));
|
||||
}
|
||||
return browser;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ const path = require('path');
|
||||
const helper = require('../lib/helper');
|
||||
if (process.env.COVERAGE)
|
||||
helper.recordPublicAPICoverage();
|
||||
const Browser = require('../lib/Browser');
|
||||
const puppeteer = require('..');
|
||||
const SimpleServer = require('./server/SimpleServer');
|
||||
const GoldenUtils = require('./golden-utils');
|
||||
|
||||
@ -82,7 +82,7 @@ afterAll(SX(async function() {
|
||||
describe('Browser', function() {
|
||||
it('Browser.Options.ignoreHTTPSErrors', SX(async function() {
|
||||
let options = Object.assign({ignoreHTTPSErrors: true}, defaultBrowserOptions);
|
||||
let browser = new Browser(options);
|
||||
let browser = await puppeteer.launch(options);
|
||||
let page = await browser.newPage();
|
||||
let error = null;
|
||||
let response = null;
|
||||
@ -96,7 +96,7 @@ describe('Browser', function() {
|
||||
browser.close();
|
||||
}));
|
||||
it('should reject all promises when browser is closed', SX(async function() {
|
||||
let browser = new Browser(defaultBrowserOptions);
|
||||
let browser = await puppeteer.launch(defaultBrowserOptions);
|
||||
let page = await browser.newPage();
|
||||
let error = null;
|
||||
let neverResolves = page.evaluate(() => new Promise(r => {})).catch(e => error = e);
|
||||
@ -114,7 +114,7 @@ describe('Page', function() {
|
||||
let page;
|
||||
|
||||
beforeAll(SX(async function() {
|
||||
browser = new Browser(defaultBrowserOptions);
|
||||
browser = await puppeteer.launch(defaultBrowserOptions);
|
||||
}));
|
||||
|
||||
afterAll(SX(async function() {
|
||||
|
@ -24,6 +24,7 @@ const EXCLUDE_CLASSES = new Set([
|
||||
'EmulationManager',
|
||||
'FrameManager',
|
||||
'Helper',
|
||||
'Launcher',
|
||||
'Multimap',
|
||||
'NavigatorWatcher',
|
||||
'NetworkManager',
|
||||
@ -35,6 +36,7 @@ const EXCLUDE_CLASSES = new Set([
|
||||
|
||||
const EXCLUDE_METHODS = new Set([
|
||||
'Body.constructor',
|
||||
'Browser.constructor',
|
||||
'Dialog.constructor',
|
||||
'Frame.constructor',
|
||||
'Headers.constructor',
|
||||
|
@ -17,7 +17,7 @@
|
||||
const fs = require('fs');
|
||||
const rm = require('rimraf').sync;
|
||||
const path = require('path');
|
||||
const Browser = require('../../../../lib/Browser');
|
||||
const puppeteer = require('../../../..');
|
||||
const checkPublicAPI = require('..');
|
||||
const SourceFactory = require('../../SourceFactory');
|
||||
const GoldenUtils = require('../../../../test/golden-utils');
|
||||
@ -25,7 +25,7 @@ const GoldenUtils = require('../../../../test/golden-utils');
|
||||
const OUTPUT_DIR = path.join(__dirname, 'output');
|
||||
const GOLDEN_DIR = path.join(__dirname, 'golden');
|
||||
|
||||
const browser = new Browser({args: ['--no-sandbox']});
|
||||
let browser;
|
||||
let page;
|
||||
let specName;
|
||||
|
||||
@ -34,6 +34,7 @@ jasmine.getEnv().addReporter({
|
||||
});
|
||||
|
||||
beforeAll(SX(async function() {
|
||||
browser = await puppeteer.launch({args: ['--no-sandbox']});
|
||||
page = await browser.newPage();
|
||||
if (fs.existsSync(OUTPUT_DIR))
|
||||
rm(OUTPUT_DIR);
|
||||
|
@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const Browser = require('../../lib/Browser');
|
||||
const puppeteer = require('../..');
|
||||
const path = require('path');
|
||||
const SourceFactory = require('./SourceFactory');
|
||||
|
||||
@ -44,7 +44,7 @@ async function run() {
|
||||
const preprocessor = require('./preprocessor');
|
||||
messages.push(...await preprocessor(mdSources));
|
||||
|
||||
const browser = new Browser({args: ['--no-sandbox']});
|
||||
const browser = await puppeteer.launch({args: ['--no-sandbox']});
|
||||
const page = await browser.newPage();
|
||||
const checkPublicAPI = require('./check_public_api');
|
||||
const jsSources = await sourceFactory.readdir(path.join(PROJECT_DIR, 'lib'), '.js');
|
||||
|
Loading…
Reference in New Issue
Block a user