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)
|
||||
* [class: Puppeteer](#class-puppeteer)
|
||||
+ [puppeteer.connect(options)](#puppeteerconnectoptions)
|
||||
+ [puppeteer.launch([options])](#puppeteerlaunchoptions)
|
||||
* [class: Browser](#class-browser)
|
||||
+ [browser.close()](#browserclose)
|
||||
+ [browser.newPage()](#browsernewpage)
|
||||
+ [browser.remoteDebuggingURL()](#browserremotedebuggingurl)
|
||||
+ [browser.version()](#browserversion)
|
||||
* [class: Page](#class-page)
|
||||
+ [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])
|
||||
- `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`.
|
||||
@ -174,6 +184,10 @@ Closes browser with all the pages (if any were opened). The browser object itsel
|
||||
#### browser.newPage()
|
||||
- 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()
|
||||
- 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
remoteDebuggingURL() {
|
||||
return this._connection.url();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Promise<!Page>}
|
||||
*/
|
||||
|
@ -21,16 +21,30 @@ const WebSocket = require('ws');
|
||||
|
||||
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 {number=} delay
|
||||
*/
|
||||
constructor(ws, delay) {
|
||||
constructor(url, ws, delay = 0) {
|
||||
super();
|
||||
this._url = url;
|
||||
this._lastId = 0;
|
||||
/** @type {!Map<number, {resolve: function, reject: function, method: string}>}*/
|
||||
this._callbacks = new Map();
|
||||
this._delay = delay || 0;
|
||||
this._delay = delay;
|
||||
|
||||
this._ws = ws;
|
||||
this._ws.on('message', this._onMessage.bind(this));
|
||||
@ -39,6 +53,13 @@ class Connection extends EventEmitter {
|
||||
this._sessions = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
url() {
|
||||
return this._url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} method
|
||||
* @param {!Object=} params
|
||||
@ -114,20 +135,6 @@ class Connection extends EventEmitter {
|
||||
this._sessions.set(sessionId, 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 {
|
||||
|
@ -87,40 +87,49 @@ class Launcher {
|
||||
removeRecursive(userDataDir);
|
||||
});
|
||||
|
||||
let {port, browserTargetId} = await waitForRemoteDebuggingPort(chromeProcess);
|
||||
let remoteDebuggingURL = await waitForRemoteDebuggingURL(chromeProcess);
|
||||
if (terminated)
|
||||
throw new Error('Failed to launch chrome! ' + stderr);
|
||||
// Failed to connect to browser.
|
||||
if (port === -1) {
|
||||
if (!remoteDebuggingURL) {
|
||||
chromeProcess.kill();
|
||||
throw new Error('Failed to connect to chrome!');
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
* @return {!Promise<number>}
|
||||
* @return {!Promise<string>}
|
||||
*/
|
||||
function waitForRemoteDebuggingPort(chromeProcess) {
|
||||
function waitForRemoteDebuggingURL(chromeProcess) {
|
||||
return new Promise(fulfill => {
|
||||
const rl = readline.createInterface({ input: chromeProcess.stderr });
|
||||
rl.on('line', onLine);
|
||||
rl.once('close', () => fulfill(-1));
|
||||
rl.once('close', () => fulfill(''));
|
||||
|
||||
/**
|
||||
* @param {string} line
|
||||
*/
|
||||
function onLine(line) {
|
||||
const match = line.match(/^DevTools listening on .*:(\d+)(\/.*)$/);
|
||||
const match = line.match(/^DevTools listening on (ws:\/\/.*)$/);
|
||||
if (!match)
|
||||
return;
|
||||
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
|
||||
* @return {!Promise<!Browser>}
|
||||
*/
|
||||
static async launch(options) {
|
||||
static launch(options) {
|
||||
return Launcher.launch(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} options
|
||||
* @return {!Promise<!Browser>}
|
||||
*/
|
||||
static connect(options) {
|
||||
return Launcher.connect(options);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Puppeteer;
|
||||
|
@ -104,6 +104,15 @@ describe('Browser', function() {
|
||||
await neverResolves;
|
||||
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() {
|
||||
|
@ -87,6 +87,8 @@ class JSOutline {
|
||||
args.push(new Documentation.Argument('...' + param.argument.name));
|
||||
else if (param.type === 'Identifier')
|
||||
args.push(new Documentation.Argument(param.name));
|
||||
else if (param.type === 'ObjectPattern')
|
||||
args.push(new Documentation.Argument('options'));
|
||||
else
|
||||
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] Method Foo.bar() fails to describe its parameters:
|
||||
- Non-existing argument found: options
|
||||
[MarkDown] Method Foo.constructor() fails to describe its parameters:
|
||||
- Argument not found: arg3
|
||||
- Non-existing argument found: arg2
|
Loading…
Reference in New Issue
Block a user