Introduce Puppeteer.connect method (#264)
This patch: - refactors Connection to use a single remote debugging URL instead of a pair of port and browserTargetId - introduces Puppeteer.connect() method to attach to already running browser instance. Fixes #238.
This commit is contained in:
parent
96309a207c
commit
a424f5613a
14
docs/api.md
14
docs/api.md
@ -6,10 +6,12 @@
|
|||||||
|
|
||||||
- [Puppeteer](#puppeteer)
|
- [Puppeteer](#puppeteer)
|
||||||
* [class: Puppeteer](#class-puppeteer)
|
* [class: Puppeteer](#class-puppeteer)
|
||||||
|
+ [puppeteer.connect(options)](#puppeteerconnectoptions)
|
||||||
+ [puppeteer.launch([options])](#puppeteerlaunchoptions)
|
+ [puppeteer.launch([options])](#puppeteerlaunchoptions)
|
||||||
* [class: Browser](#class-browser)
|
* [class: Browser](#class-browser)
|
||||||
+ [browser.close()](#browserclose)
|
+ [browser.close()](#browserclose)
|
||||||
+ [browser.newPage()](#browsernewpage)
|
+ [browser.newPage()](#browsernewpage)
|
||||||
|
+ [browser.remoteDebuggingURL()](#browserremotedebuggingurl)
|
||||||
+ [browser.version()](#browserversion)
|
+ [browser.version()](#browserversion)
|
||||||
* [class: Page](#class-page)
|
* [class: Page](#class-page)
|
||||||
+ [event: 'console'](#event-console)
|
+ [event: 'console'](#event-console)
|
||||||
@ -135,6 +137,14 @@ puppeteer.launch().then(async browser => {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### puppeteer.connect(options)
|
||||||
|
- `options` <[Object]> Set of options to connect to the browser. Can have the following fields:
|
||||||
|
- `remoteDebuggingURL` <[string]> a remote debugging URL to connect to.
|
||||||
|
- `ignoreHTTPSErrors` <[boolean]> Whether to ignore HTTPS errors during navigation. Defaults to `false`.
|
||||||
|
- returns: <[Promise]<[Browser]>> Promise which resolves to browser instance.
|
||||||
|
|
||||||
|
This method could be used to connect to already running browser instance.
|
||||||
|
|
||||||
#### puppeteer.launch([options])
|
#### puppeteer.launch([options])
|
||||||
- `options` <[Object]> Set of configurable options to set on the browser. Can have the following fields:
|
- `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`.
|
- `ignoreHTTPSErrors` <[boolean]> Whether to ignore HTTPS errors during navigation. Defaults to `false`.
|
||||||
@ -174,6 +184,10 @@ Closes browser with all the pages (if any were opened). The browser object itsel
|
|||||||
#### browser.newPage()
|
#### browser.newPage()
|
||||||
- returns: <[Promise]<[Page]>> Promise which resolves to a new [Page] object.
|
- returns: <[Promise]<[Page]>> Promise which resolves to a new [Page] object.
|
||||||
|
|
||||||
|
#### browser.remoteDebuggingURL()
|
||||||
|
- returns: <[string]> A URL that could be used to start debugging this browser instance.
|
||||||
|
|
||||||
|
Remote debugging url could be used as an argument to the [puppeteer.connect](#puppeteerconnect).
|
||||||
|
|
||||||
#### browser.version()
|
#### 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`.
|
- 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`.
|
||||||
|
@ -30,6 +30,13 @@ class Browser {
|
|||||||
this._closeCallback = closeCallback || new Function();
|
this._closeCallback = closeCallback || new Function();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
remoteDebuggingURL() {
|
||||||
|
return this._connection.url();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {!Promise<!Page>}
|
* @return {!Promise<!Page>}
|
||||||
*/
|
*/
|
||||||
|
@ -21,16 +21,30 @@ const WebSocket = require('ws');
|
|||||||
|
|
||||||
class Connection extends EventEmitter {
|
class Connection extends EventEmitter {
|
||||||
/**
|
/**
|
||||||
* @param {number} port
|
* @param {string} url
|
||||||
|
* @param {number=} delay
|
||||||
|
* @return {!Promise<!Connection>}
|
||||||
|
*/
|
||||||
|
static async create(url, delay = 0) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let ws = new WebSocket(url, { perMessageDeflate: false });
|
||||||
|
ws.on('open', () => resolve(new Connection(url, ws, delay)));
|
||||||
|
ws.on('error', reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} url
|
||||||
* @param {!WebSocket} ws
|
* @param {!WebSocket} ws
|
||||||
* @param {number=} delay
|
* @param {number=} delay
|
||||||
*/
|
*/
|
||||||
constructor(ws, delay) {
|
constructor(url, ws, delay = 0) {
|
||||||
super();
|
super();
|
||||||
|
this._url = url;
|
||||||
this._lastId = 0;
|
this._lastId = 0;
|
||||||
/** @type {!Map<number, {resolve: function, reject: function, method: string}>}*/
|
/** @type {!Map<number, {resolve: function, reject: function, method: string}>}*/
|
||||||
this._callbacks = new Map();
|
this._callbacks = new Map();
|
||||||
this._delay = delay || 0;
|
this._delay = delay;
|
||||||
|
|
||||||
this._ws = ws;
|
this._ws = ws;
|
||||||
this._ws.on('message', this._onMessage.bind(this));
|
this._ws.on('message', this._onMessage.bind(this));
|
||||||
@ -39,6 +53,13 @@ class Connection extends EventEmitter {
|
|||||||
this._sessions = new Map();
|
this._sessions = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
url() {
|
||||||
|
return this._url;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} method
|
* @param {string} method
|
||||||
* @param {!Object=} params
|
* @param {!Object=} params
|
||||||
@ -114,20 +135,6 @@ class Connection extends EventEmitter {
|
|||||||
this._sessions.set(sessionId, session);
|
this._sessions.set(sessionId, session);
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} port
|
|
||||||
* @param {number=} delay
|
|
||||||
* @return {!Promise<!Connection>}
|
|
||||||
*/
|
|
||||||
static async create(port, targetId, delay) {
|
|
||||||
const url = `ws://localhost:${port}${targetId}`;
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let ws = new WebSocket(url, { perMessageDeflate: false });
|
|
||||||
ws.on('open', () => resolve(new Connection(ws, delay)));
|
|
||||||
ws.on('error', reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Session extends EventEmitter {
|
class Session extends EventEmitter {
|
||||||
|
@ -87,40 +87,49 @@ class Launcher {
|
|||||||
removeRecursive(userDataDir);
|
removeRecursive(userDataDir);
|
||||||
});
|
});
|
||||||
|
|
||||||
let {port, browserTargetId} = await waitForRemoteDebuggingPort(chromeProcess);
|
let remoteDebuggingURL = await waitForRemoteDebuggingURL(chromeProcess);
|
||||||
if (terminated)
|
if (terminated)
|
||||||
throw new Error('Failed to launch chrome! ' + stderr);
|
throw new Error('Failed to launch chrome! ' + stderr);
|
||||||
// Failed to connect to browser.
|
// Failed to connect to browser.
|
||||||
if (port === -1) {
|
if (!remoteDebuggingURL) {
|
||||||
chromeProcess.kill();
|
chromeProcess.kill();
|
||||||
throw new Error('Failed to connect to chrome!');
|
throw new Error('Failed to connect to chrome!');
|
||||||
}
|
}
|
||||||
|
|
||||||
let connectionDelay = options.slowMo || 0;
|
let connectionDelay = options.slowMo || 0;
|
||||||
let connection = await Connection.create(port, browserTargetId, connectionDelay);
|
let connection = await Connection.create(remoteDebuggingURL, connectionDelay);
|
||||||
return new Browser(connection, !!options.ignoreHTTPSErrors, () => chromeProcess.kill());
|
return new Browser(connection, !!options.ignoreHTTPSErrors, () => chromeProcess.kill());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} options
|
||||||
|
* @return {!Promise<!Browser>}
|
||||||
|
*/
|
||||||
|
static async connect({remoteDebuggingURL, ignoreHTTPSErrors = false}) {
|
||||||
|
let connection = await Connection.create(remoteDebuggingURL);
|
||||||
|
return new Browser(connection, !!ignoreHTTPSErrors);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {!ChildProcess} chromeProcess
|
* @param {!ChildProcess} chromeProcess
|
||||||
* @return {!Promise<number>}
|
* @return {!Promise<string>}
|
||||||
*/
|
*/
|
||||||
function waitForRemoteDebuggingPort(chromeProcess) {
|
function waitForRemoteDebuggingURL(chromeProcess) {
|
||||||
return new Promise(fulfill => {
|
return new Promise(fulfill => {
|
||||||
const rl = readline.createInterface({ input: chromeProcess.stderr });
|
const rl = readline.createInterface({ input: chromeProcess.stderr });
|
||||||
rl.on('line', onLine);
|
rl.on('line', onLine);
|
||||||
rl.once('close', () => fulfill(-1));
|
rl.once('close', () => fulfill(''));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} line
|
* @param {string} line
|
||||||
*/
|
*/
|
||||||
function onLine(line) {
|
function onLine(line) {
|
||||||
const match = line.match(/^DevTools listening on .*:(\d+)(\/.*)$/);
|
const match = line.match(/^DevTools listening on (ws:\/\/.*)$/);
|
||||||
if (!match)
|
if (!match)
|
||||||
return;
|
return;
|
||||||
rl.removeListener('line', onLine);
|
rl.removeListener('line', onLine);
|
||||||
fulfill({port: Number.parseInt(match[1], 10), browserTargetId: match[2]});
|
fulfill(match[1]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -21,9 +21,17 @@ class Puppeteer {
|
|||||||
* @param {!Object=} options
|
* @param {!Object=} options
|
||||||
* @return {!Promise<!Browser>}
|
* @return {!Promise<!Browser>}
|
||||||
*/
|
*/
|
||||||
static async launch(options) {
|
static launch(options) {
|
||||||
return Launcher.launch(options);
|
return Launcher.launch(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} options
|
||||||
|
* @return {!Promise<!Browser>}
|
||||||
|
*/
|
||||||
|
static connect(options) {
|
||||||
|
return Launcher.connect(options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Puppeteer;
|
module.exports = Puppeteer;
|
||||||
|
@ -104,6 +104,15 @@ describe('Browser', function() {
|
|||||||
await neverResolves;
|
await neverResolves;
|
||||||
expect(error.message).toContain('Protocol error');
|
expect(error.message).toContain('Protocol error');
|
||||||
}));
|
}));
|
||||||
|
it('Puppeteer.connect', SX(async function() {
|
||||||
|
let originalBrowser = await puppeteer.launch(defaultBrowserOptions);
|
||||||
|
let browser = await puppeteer.connect({
|
||||||
|
remoteDebuggingURL: originalBrowser.remoteDebuggingURL()
|
||||||
|
});
|
||||||
|
let page = await browser.newPage();
|
||||||
|
expect(await page.evaluate(() => 7 * 8)).toBe(56);
|
||||||
|
originalBrowser.close();
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Page', function() {
|
describe('Page', function() {
|
||||||
|
@ -87,6 +87,8 @@ class JSOutline {
|
|||||||
args.push(new Documentation.Argument('...' + param.argument.name));
|
args.push(new Documentation.Argument('...' + param.argument.name));
|
||||||
else if (param.type === 'Identifier')
|
else if (param.type === 'Identifier')
|
||||||
args.push(new Documentation.Argument(param.name));
|
args.push(new Documentation.Argument(param.name));
|
||||||
|
else if (param.type === 'ObjectPattern')
|
||||||
|
args.push(new Documentation.Argument('options'));
|
||||||
else
|
else
|
||||||
this.errors.push(`JS Parsing issue: unsupported syntax to define parameter in ${this._currentClassName}.${methodName}(): ${this._extractText(param)}`);
|
this.errors.push(`JS Parsing issue: unsupported syntax to define parameter in ${this._currentClassName}.${methodName}(): ${this._extractText(param)}`);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
[JavaScript] JS Parsing issue: unsupported syntax to define parameter in Foo.bar(): {visibility}
|
|
||||||
[MarkDown] Heading arguments for "foo.test(...files)" do not match described ones, i.e. "...files" != "...filePaths"
|
[MarkDown] Heading arguments for "foo.test(...files)" do not match described ones, i.e. "...files" != "...filePaths"
|
||||||
[MarkDown] Method Foo.bar() fails to describe its parameters:
|
|
||||||
- Non-existing argument found: options
|
|
||||||
[MarkDown] Method Foo.constructor() fails to describe its parameters:
|
[MarkDown] Method Foo.constructor() fails to describe its parameters:
|
||||||
- Argument not found: arg3
|
- Argument not found: arg3
|
||||||
- Non-existing argument found: arg2
|
- Non-existing argument found: arg2
|
Loading…
Reference in New Issue
Block a user