Skip to content

Commit

Permalink
Puppeteer: staging commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
aslushnikov committed May 11, 2017
1 parent ebac211 commit 2cda8c1
Show file tree
Hide file tree
Showing 180 changed files with 19,038 additions and 2 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/node_modules/
/.local-chromium/
/.dev_profile*
.DS_Store
*.swp
*.pyc
23 changes: 23 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# How to Contribute

We'd love to accept your patches and contributions to this project. There are
just a few small guidelines you need to follow.

## Contributor License Agreement

Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution,
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to <https://cla.developers.google.com/> to see
your current agreements on file or to sign a new one.

You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again.

## Code reviews

All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
information on using pull requests.
38 changes: 36 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,36 @@
# puppeteer
Headless Chrome Node API
### Status

Test results on Mac OS X in headless mode:
```
111 passed
20 failed as expected
1 skipped
49 unsupported
```

### Installing

```bash
npm i
npm link # this adds puppeteer to $PATH
```

### Run

```bash
# run phantomjs script
puppeteer third_party/phantomjs/examples/colorwheel.js

# run 'headful'
puppeteer --no-headless third_party/phantomjs/examples/colorwheel.js

# run puppeteer example
node examples/screenshot.js
```

### Tests

Run phantom.js tests using puppeteer:
```
./third_party/phantomjs/test/run-tests.py
```
41 changes: 41 additions & 0 deletions examples/custom-chromium-revision.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* 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.
*/

var Browser = require('../lib/Browser');
var Downloader = require('../lib/Downloader');

var revision = "464642";
console.log('Downloading custom chromium revision - ' + revision);
Downloader.downloadChromium(revision).then(async () => {
console.log('Done.');
var executablePath = Downloader.executablePath(revision);
var browser1 = new Browser({
remoteDebuggingPort: 9228,
executablePath,
});
var browser2 = new Browser({
remoteDebuggingPort: 9229,
});
var [version1, version2] = await Promise.all([
browser1.version(),
browser2.version()
]);
console.log('browser1: ' + version1);
console.log('browser2: ' + version2);
browser1.close();
browser2.close();
});

27 changes: 27 additions & 0 deletions examples/screenshot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* 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.
*/

var Browser = require('./lib/Browser');
var Page = require('./lib/Page');
var fs = require('fs');
var browser = new Browser();

browser.newPage().then(async page => {
await page.navigate('http://example.com');
var screenshotBuffer = await page.screenshot(Page.ScreenshotTypes.PNG);
fs.writeFileSync('example.png', screenshotBuffer);
browser.close();
});
60 changes: 60 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env node
/**
* 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.
*/

var vm = require('vm');
var fs = require('fs');
var path = require('path');
var Browser = require('./lib/Browser');
var argv = require('minimist')(process.argv.slice(2), {
alias: { v: 'version' },
boolean: ['headless'],
default: {'headless': true },
});

if (argv.version) {
console.log('Puppeteer v' + require('./package.json').version);
return;
}

if (argv['ssl-certificates-path']) {
console.error('Flag --ssl-certificates-path is not currently supported.\nMore information at https://github.com/aslushnikov/puppeteer/issues/1');
process.exit(1);
return;
}

var scriptArguments = argv._;
if (!scriptArguments.length) {
console.log('puppeteer [scriptfile]');
return;
}

var scriptPath = path.resolve(process.cwd(), scriptArguments[0]);
if (!fs.existsSync(scriptPath)) {
console.error(`script not found: ${scriptPath}`);
process.exit(1);
return;
}

var browser = new Browser({
remoteDebuggingPort: 9229,
headless: argv.headless,
});

var PhatomJs = require('./phantomjs');
var context = PhatomJs.createContext(browser, scriptPath, argv);
var scriptContent = fs.readFileSync(scriptPath, 'utf8');
vm.runInContext(scriptContent, context);
32 changes: 32 additions & 0 deletions install.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
var Downloader = require('./lib/Downloader');
var revision = require('./package').puppeteer.chromium_revision;
var fs = require('fs');
var ProgressBar = require('progress');

var executable = Downloader.executablePath(revision);
if (fs.existsSync(executable))
return;

Downloader.downloadChromium(revision, onProgress)
.catch(error => {
console.error('Download failed: ' + error.message);
});

var progressBar = null;
function onProgress(bytesTotal, delta) {
if (!progressBar) {
progressBar = new ProgressBar(`Downloading Chromium - ${toMegabytes(bytesTotal)} [:bar] :percent :etas `, {
complete: '=',
incomplete: ' ',
width: 20,
total: bytesTotal,
});
}
progressBar.tick(delta);
}

function toMegabytes(bytes) {
var mb = bytes / 1024 / 1024;
return (Math.round(mb * 10) / 10) + ' Mb';
}

142 changes: 142 additions & 0 deletions lib/Browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/**
* 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.
*/

var CDP = require('chrome-remote-interface');
var http = require('http');
var path = require('path');
var removeRecursive = require('rimraf').sync;
var Page = require('./Page');
var childProcess = require('child_process');
var Downloader = require('./Downloader');

var CHROME_PROFILE_PATH = path.resolve(__dirname, '..', '.dev_profile');
var browserId = 0;

var DEFAULT_ARGS = [
'--disable-background-timer-throttling',
'--no-first-run',
];

class Browser {
/**
* @param {(!Object|undefined)} options
*/
constructor(options) {
options = options || {};
++browserId;
this._userDataDir = CHROME_PROFILE_PATH + browserId;
this._remoteDebuggingPort = 9229;
if (typeof options.remoteDebuggingPort === 'number')
this._remoteDebuggingPort = options.remoteDebuggingPort;
this._chromeArguments = DEFAULT_ARGS.concat([
`--user-data-dir=${this._userDataDir}`,
`--remote-debugging-port=${this._remoteDebuggingPort}`,
]);
if (typeof options.headless !== 'boolean' || options.headless) {
this._chromeArguments.push(...[
`--headless`,
`--disable-gpu`,
]);
}
if (typeof options.executablePath === 'string') {
this._chromeExecutable = options.executablePath;
} else {
var chromiumRevision = require('../package.json').puppeteer.chromium_revision;
this._chromeExecutable = Downloader.executablePath(chromiumRevision);
}
if (Array.isArray(options.args))
this._chromeArguments.push(...options.args);
this._chromeProcess = null;
this._tabSymbol = Symbol('Browser.TabSymbol');
}

/**
* @return {!Promise<!Page>}
*/
async newPage() {
await this._ensureChromeIsRunning();
if (!this._chromeProcess || this._chromeProcess.killed)
throw new Error('ERROR: this chrome instance is not alive any more!');
var tab = await CDP.New({port: this._remoteDebuggingPort});
var client = await CDP({tab: tab, port: this._remoteDebuggingPort});
var page = await Page.create(this, client);
page[this._tabSymbol] = tab;
return page;
}

/**
* @param {!Page} page
*/
async closePage(page) {
if (!this._chromeProcess || this._chromeProcess.killed)
throw new Error('ERROR: this chrome instance is not running');
var tab = page[this._tabSymbol];
if (!tab)
throw new Error('ERROR: page does not belong to this chrome instance');
await CDP.Close({id: tab.id, port: this._remoteDebuggingPort});
}

/**
* @return {string}
*/
async version() {
await this._ensureChromeIsRunning();
var version = await CDP.Version({port: this._remoteDebuggingPort});
return version.Browser;
}

async _ensureChromeIsRunning() {
if (this._chromeProcess)
return;
this._chromeProcess = childProcess.spawn(this._chromeExecutable, this._chromeArguments, {});
// Cleanup as processes exit.
process.on('exit', () => this._chromeProcess.kill());
this._chromeProcess.on('exit', () => removeRecursive(this._userDataDir));

await waitForChromeResponsive(this._remoteDebuggingPort);
}

close() {
if (!this._chromeProcess)
return;
this._chromeProcess.kill();
}
}

module.exports = Browser;

function waitForChromeResponsive(remoteDebuggingPort) {
var fulfill;
var promise = new Promise(x => fulfill = x);
var options = {
method: 'GET',
host: 'localhost',
port: remoteDebuggingPort,
path: '/json/list'
};
var probeTimeout = 100;
var probeAttempt = 1;
sendRequest();
return promise;

function sendRequest() {
var req = http.request(options, res => {
fulfill()
});
req.on('error', e => setTimeout(sendRequest, probeTimeout));
req.end();
}
}
Loading

0 comments on commit 2cda8c1

Please sign in to comment.