├── test
├── assets
│ ├── sw.js
│ ├── empty.html
│ ├── simple.json
│ ├── file-to-upload.txt
│ ├── frames
│ │ ├── script.js
│ │ ├── style.css
│ │ ├── frame.html
│ │ ├── two-frames.html
│ │ └── nested-frames.html
│ ├── injectedstyle.css
│ ├── one-style.css
│ ├── tamperable.html
│ ├── injectedfile.js
│ ├── pptr.png
│ ├── mobile.html
│ ├── digits
│ │ ├── 0.png
│ │ ├── 1.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ ├── 4.png
│ │ ├── 5.png
│ │ ├── 6.png
│ │ ├── 7.png
│ │ ├── 8.png
│ │ └── 9.png
│ ├── one-style.html
│ ├── input
│ │ ├── fileupload.html
│ │ ├── button.html
│ │ ├── textarea.html
│ │ ├── scrollable.html
│ │ ├── touches.html
│ │ ├── keyboard.html
│ │ ├── mouse-helper.js
│ │ └── select.html
│ ├── error.html
│ ├── playground.html
│ ├── networkidle.html
│ ├── grid.html
│ └── detect-touch.html
├── golden
│ ├── grid-cell-0.png
│ ├── grid-cell-1.png
│ ├── grid-cell-2.png
│ ├── grid-cell-3.png
│ ├── transparent.png
│ ├── screenshot-sanity.png
│ ├── mock-binary-response.png
│ ├── screenshot-clip-rect.png
│ ├── screenshot-clip-odd-size.png
│ ├── screenshot-grid-fullpage.png
│ ├── screenshot-element-rotate.png
│ ├── screenshot-offscreen-clip.png
│ ├── screenshot-element-bounding-box.png
│ ├── screenshot-element-padding-border.png
│ ├── nested-frames.txt
│ └── reconnect-nested-frames.txt
├── diffstyle.css
├── server
│ ├── cert.pem
│ ├── key.pem
│ └── SimpleServer.js
├── frame-utils.js
└── golden-utils.js
├── utils
├── doclint
│ ├── .gitignore
│ ├── check_public_api
│ │ ├── test
│ │ │ ├── diff-classes
│ │ │ │ ├── foo.js
│ │ │ │ ├── other.js
│ │ │ │ ├── doc.md
│ │ │ │ └── result.txt
│ │ │ ├── .gitignore
│ │ │ ├── diff-properties
│ │ │ │ ├── doc.md
│ │ │ │ ├── result.txt
│ │ │ │ └── foo.js
│ │ │ ├── diff-events
│ │ │ │ ├── doc.md
│ │ │ │ ├── foo.js
│ │ │ │ └── result.txt
│ │ │ ├── check-duplicates
│ │ │ │ ├── foo.js
│ │ │ │ ├── result.txt
│ │ │ │ └── doc.md
│ │ │ ├── diff-methods
│ │ │ │ ├── result.txt
│ │ │ │ ├── doc.md
│ │ │ │ └── foo.js
│ │ │ ├── diff-arguments
│ │ │ │ ├── foo.js
│ │ │ │ ├── doc.md
│ │ │ │ └── result.txt
│ │ │ ├── check-returns
│ │ │ │ ├── doc.md
│ │ │ │ ├── foo.js
│ │ │ │ └── result.txt
│ │ │ ├── check-sorting
│ │ │ │ ├── doc.md
│ │ │ │ ├── foo.js
│ │ │ │ └── result.txt
│ │ │ ├── js-builder-common
│ │ │ │ ├── foo.js
│ │ │ │ └── result.txt
│ │ │ ├── js-builder-inheritance
│ │ │ │ ├── foo.js
│ │ │ │ └── result.txt
│ │ │ ├── md-builder-common
│ │ │ │ ├── doc.md
│ │ │ │ └── result.txt
│ │ │ └── test.js
│ │ ├── Documentation.js
│ │ ├── MDBuilder.js
│ │ └── JSBuilder.js
│ ├── README.md
│ ├── toc.js
│ ├── Message.js
│ ├── preprocessor
│ │ ├── test.js
│ │ └── index.js
│ ├── cli.js
│ └── SourceFactory.js
├── node6-transform
│ ├── index.js
│ ├── test
│ │ └── test.js
│ └── TransformAsyncFunctions.js
├── ESTreeWalker.js
├── check_availability.js
└── fetch_devices.js
├── .eslintignore
├── .editorconfig
├── tsconfig.json
├── .gitignore
├── .npmignore
├── .appveyor.yml
├── lib
├── .eslintrc.js
├── Puppeteer.js
├── externs.d.ts
├── Dialog.js
├── Multimap.js
├── Tracing.js
├── EmulationManager.js
├── NavigatorWatcher.js
├── ElementHandle.js
├── Browser.js
├── Connection.js
├── ExecutionContext.js
└── helper.js
├── examples
├── .eslintrc.js
├── README.md
├── screenshot.js
├── screenshot-fullpage.js
├── proxy.js
├── pdf.js
├── block-images.js
├── search.js
├── detect-sniff.js
└── custom-event.js
├── .travis.yml
├── index.js
├── docs
├── issue_template.md
└── troubleshooting.md
├── package.json
├── install.js
├── .eslintrc.js
└── CONTRIBUTING.md
/test/assets/sw.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/assets/empty.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/utils/doclint/.gitignore:
--------------------------------------------------------------------------------
1 | output/
2 |
--------------------------------------------------------------------------------
/test/assets/simple.json:
--------------------------------------------------------------------------------
1 | {"foo": "bar"}
2 |
--------------------------------------------------------------------------------
/test/assets/file-to-upload.txt:
--------------------------------------------------------------------------------
1 | contents of the file
--------------------------------------------------------------------------------
/test/assets/frames/script.js:
--------------------------------------------------------------------------------
1 | console.log('Cheers!');
2 |
--------------------------------------------------------------------------------
/test/assets/frames/style.css:
--------------------------------------------------------------------------------
1 | div {
2 | color: blue;
3 | }
4 |
--------------------------------------------------------------------------------
/test/assets/injectedstyle.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: red;
3 | }
4 |
--------------------------------------------------------------------------------
/test/assets/one-style.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: pink;
3 | }
4 |
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/diff-classes/foo.js:
--------------------------------------------------------------------------------
1 | class Foo {
2 | }
3 |
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/diff-classes/other.js:
--------------------------------------------------------------------------------
1 | class Other {
2 | }
3 |
--------------------------------------------------------------------------------
/test/assets/tamperable.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/.gitignore:
--------------------------------------------------------------------------------
1 | result-actual.txt
2 | result-diff.html
3 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | third_party/*
2 | utils/doclint/check_public_api/test/
3 | node6/*
4 | node6-test/*
--------------------------------------------------------------------------------
/test/assets/injectedfile.js:
--------------------------------------------------------------------------------
1 | window.__injected = 42;
2 | window.__injectedError = new Error('hi');
--------------------------------------------------------------------------------
/test/assets/pptr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/assets/pptr.png
--------------------------------------------------------------------------------
/test/assets/mobile.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/test/assets/digits/0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/assets/digits/0.png
--------------------------------------------------------------------------------
/test/assets/digits/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/assets/digits/1.png
--------------------------------------------------------------------------------
/test/assets/digits/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/assets/digits/2.png
--------------------------------------------------------------------------------
/test/assets/digits/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/assets/digits/3.png
--------------------------------------------------------------------------------
/test/assets/digits/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/assets/digits/4.png
--------------------------------------------------------------------------------
/test/assets/digits/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/assets/digits/5.png
--------------------------------------------------------------------------------
/test/assets/digits/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/assets/digits/6.png
--------------------------------------------------------------------------------
/test/assets/digits/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/assets/digits/7.png
--------------------------------------------------------------------------------
/test/assets/digits/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/assets/digits/8.png
--------------------------------------------------------------------------------
/test/assets/digits/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/assets/digits/9.png
--------------------------------------------------------------------------------
/test/assets/one-style.html:
--------------------------------------------------------------------------------
1 |
2 |
hello, world!
3 |
--------------------------------------------------------------------------------
/test/golden/grid-cell-0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/golden/grid-cell-0.png
--------------------------------------------------------------------------------
/test/golden/grid-cell-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/golden/grid-cell-1.png
--------------------------------------------------------------------------------
/test/golden/grid-cell-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/golden/grid-cell-2.png
--------------------------------------------------------------------------------
/test/golden/grid-cell-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/golden/grid-cell-3.png
--------------------------------------------------------------------------------
/test/golden/transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/golden/transparent.png
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/diff-properties/doc.md:
--------------------------------------------------------------------------------
1 | ### class: Foo
2 |
3 | #### foo.a
4 |
5 | #### foo.c
6 |
--------------------------------------------------------------------------------
/test/golden/screenshot-sanity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/golden/screenshot-sanity.png
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/diff-classes/doc.md:
--------------------------------------------------------------------------------
1 | ### class: Foo
2 |
3 | ### class: Bar
4 |
5 | ### class: Baz
6 |
--------------------------------------------------------------------------------
/test/golden/mock-binary-response.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/golden/mock-binary-response.png
--------------------------------------------------------------------------------
/test/golden/screenshot-clip-rect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/golden/screenshot-clip-rect.png
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/diff-events/doc.md:
--------------------------------------------------------------------------------
1 | ### class: Foo
2 |
3 | #### event: 'start'
4 |
5 | #### event: 'stop'
6 |
--------------------------------------------------------------------------------
/test/golden/screenshot-clip-odd-size.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/golden/screenshot-clip-odd-size.png
--------------------------------------------------------------------------------
/test/golden/screenshot-grid-fullpage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/golden/screenshot-grid-fullpage.png
--------------------------------------------------------------------------------
/test/golden/screenshot-element-rotate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/golden/screenshot-element-rotate.png
--------------------------------------------------------------------------------
/test/golden/screenshot-offscreen-clip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/golden/screenshot-offscreen-clip.png
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/diff-properties/result.txt:
--------------------------------------------------------------------------------
1 | [MarkDown] Non-existing property found: Foo.c
2 | [MarkDown] Property not found: Foo.b
--------------------------------------------------------------------------------
/test/golden/screenshot-element-bounding-box.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/golden/screenshot-element-bounding-box.png
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/diff-events/foo.js:
--------------------------------------------------------------------------------
1 | class Foo {
2 | }
3 |
4 | Foo.Events = {
5 | Start: 'start',
6 | Finish: 'finish',
7 | };
8 |
--------------------------------------------------------------------------------
/test/golden/screenshot-element-padding-border.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timleland/puppeteer/master/test/golden/screenshot-element-padding-border.png
--------------------------------------------------------------------------------
/test/assets/frames/frame.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Hi, I'm frame
4 |
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/diff-events/result.txt:
--------------------------------------------------------------------------------
1 | [MarkDown] Non-existing event found in class Foo: 'stop'
2 | [MarkDown] Event not found in class Foo: 'finish'
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/check-duplicates/foo.js:
--------------------------------------------------------------------------------
1 | class Foo {
2 | test() {
3 | }
4 |
5 | title(arg) {
6 | }
7 | }
8 |
9 | class Bar {
10 | }
11 |
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/diff-classes/result.txt:
--------------------------------------------------------------------------------
1 | [MarkDown] Non-existing class found: Bar
2 | [MarkDown] Non-existing class found: Baz
3 | [MarkDown] Class not found: Other
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/diff-properties/foo.js:
--------------------------------------------------------------------------------
1 | class Foo {
2 | constructor() {
3 | this.a = 42;
4 | this.b = 'hello';
5 | this.emit('done');
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/test/assets/input/fileupload.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | File upload test
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/diff-methods/result.txt:
--------------------------------------------------------------------------------
1 | [MarkDown] Non-existing method found: Foo.proceed()
2 | [MarkDown] Method not found: Foo.stop()
3 | [MarkDown] Property not found: Foo.zzz
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/diff-methods/doc.md:
--------------------------------------------------------------------------------
1 | ### class: Foo
2 |
3 | #### foo.$()
4 |
5 | #### foo.money$$money()
6 |
7 | #### foo.proceed()
8 |
9 | #### foo.start()
10 |
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/diff-arguments/foo.js:
--------------------------------------------------------------------------------
1 | class Foo {
2 | foo(arg1, arg3 = {}) {
3 | }
4 |
5 | test(...filePaths) {
6 | }
7 |
8 | bar({visibility}) {
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "noEmit": true,
4 | "allowJs": true,
5 | "checkJs": true,
6 | "target": "es2017"
7 | },
8 | "include": [
9 | "lib"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /test/output
3 | /test/test-user-data-dir*
4 | /.local-chromium/
5 | /.dev_profile*
6 | .DS_Store
7 | *.swp
8 | *.pyc
9 | .vscode
10 | package-lock.json
11 | /node6
12 | /node6-test
13 |
--------------------------------------------------------------------------------
/test/assets/error.html:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/check-duplicates/result.txt:
--------------------------------------------------------------------------------
1 | [MarkDown] Duplicate declaration of method Foo.test()
2 | [MarkDown] Duplicate declaration of argument Foo.title "arg"
3 | [MarkDown] Duplicate declaration of class Bar
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/diff-methods/foo.js:
--------------------------------------------------------------------------------
1 | class Foo {
2 | start() {
3 | }
4 |
5 | stop() {
6 | }
7 |
8 | get zzz() {
9 | }
10 |
11 | $() {
12 | }
13 |
14 | money$$money() {
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/test/diffstyle.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: monospace;
3 | white-space: pre;
4 | }
5 |
6 | ins {
7 | background-color: #9cffa0;
8 | text-decoration: none;
9 | }
10 |
11 | del {
12 | background-color: #ff9e9e;
13 | }
14 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # exclude all tests
2 | test
3 | utils/node6-transform
4 |
5 | # repeats from .gitignore
6 | node_modules
7 | .local-chromium
8 | .dev_profile*
9 | .DS_Store
10 | *.swp
11 | *.pyc
12 | .vscode
13 | package-lock.json
14 | /node6-test
15 |
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/check-returns/doc.md:
--------------------------------------------------------------------------------
1 | ### class: Foo
2 |
3 | #### foo.asyncFunction()
4 |
5 | #### foo.return42()
6 |
7 | #### foo.returnNothing()
8 | - returns: <[number]>
9 |
10 | #### foo.www()
11 | - returns <[string]>
12 |
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/check-sorting/doc.md:
--------------------------------------------------------------------------------
1 | ### class: Foo
2 |
3 | #### event: 'c'
4 |
5 | #### event: 'a'
6 |
7 | #### foo.aaa()
8 |
9 | #### event: 'b'
10 |
11 | #### foo.ddd
12 |
13 | #### foo.ccc()
14 |
15 | #### foo.bbb()
16 |
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/check-duplicates/doc.md:
--------------------------------------------------------------------------------
1 | ### class: Bar
2 |
3 | ### class: Foo
4 |
5 | #### foo.test()
6 |
7 | #### foo.test()
8 |
9 | #### foo.title(arg, arg)
10 | - `arg` <[number]>
11 | - `arg` <[number]>
12 |
13 | ### class: Bar
14 |
15 |
--------------------------------------------------------------------------------
/test/golden/nested-frames.txt:
--------------------------------------------------------------------------------
1 | http://localhost:8907/frames/nested-frames.html
2 | http://localhost:8907/frames/two-frames.html
3 | http://localhost:8907/frames/frame.html
4 | http://localhost:8907/frames/frame.html
5 | http://localhost:8907/frames/frame.html
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/check-sorting/foo.js:
--------------------------------------------------------------------------------
1 | class Foo {
2 | constructor() {
3 | this.ddd = 10;
4 | }
5 |
6 | aaa() {}
7 |
8 | bbb() {}
9 |
10 | ccc() {}
11 | }
12 |
13 | Foo.Events = {
14 | a: 'a',
15 | b: 'b',
16 | c: 'c'
17 | }
18 |
--------------------------------------------------------------------------------
/test/golden/reconnect-nested-frames.txt:
--------------------------------------------------------------------------------
1 | http://localhost:8907/frames/nested-frames.html
2 | http://localhost:8907/frames/two-frames.html
3 | http://localhost:8907/frames/frame.html
4 | http://localhost:8907/frames/frame.html
5 | http://localhost:8907/frames/frame.html
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/diff-arguments/doc.md:
--------------------------------------------------------------------------------
1 | ### class: Foo
2 | #### foo.bar(options)
3 | - `options` <[Object]>
4 |
5 | #### foo.foo(arg1, arg2)
6 | - `arg1` <[string]>
7 | - `arg2` <[string]>
8 |
9 | #### foo.test(...files)
10 | - `...filePaths` <[string]>
11 |
12 |
--------------------------------------------------------------------------------
/test/assets/frames/two-frames.html:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/diff-arguments/result.txt:
--------------------------------------------------------------------------------
1 | [MarkDown] Heading arguments for "foo.test(...files)" do not match described ones, i.e. "...files" != "...filePaths"
2 | [MarkDown] Method Foo.foo() fails to describe its parameters:
3 | - Argument not found: arg3
4 | - Non-existing argument found: arg2
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/js-builder-common/foo.js:
--------------------------------------------------------------------------------
1 | class A {
2 | constructor(delegate) {
3 | this.property1 = 1;
4 | this._property2 = 2;
5 | }
6 |
7 | get getter() {
8 | return null;
9 | }
10 |
11 | async method(foo, bar) {
12 | }
13 | }
14 |
15 | A.Events = {
16 | AnEvent: 'anevent'
17 | };
18 |
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/js-builder-inheritance/foo.js:
--------------------------------------------------------------------------------
1 | class A {
2 | constructor() {
3 | }
4 |
5 | foo(a) {
6 | }
7 |
8 | bar() {
9 | }
10 | }
11 |
12 | class B extends A {
13 | bar(override) {
14 | }
15 | }
16 |
17 | B.Events = {
18 | // Event with the same name as a super class method.
19 | foo: 'foo'
20 | };
21 |
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/check-returns/foo.js:
--------------------------------------------------------------------------------
1 | class Foo {
2 | return42() {
3 | return 42;
4 | }
5 |
6 | returnNothing() {
7 | let e = () => {
8 | return 10;
9 | }
10 | e();
11 | }
12 |
13 | www() {
14 | if (1 === 1)
15 | return 'df';
16 | }
17 |
18 | async asyncFunction() {
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/check-sorting/result.txt:
--------------------------------------------------------------------------------
1 | [MarkDown] Events should go first. Event 'b' in class Foo breaks order
2 | [MarkDown] Event 'c' in class Foo breaks alphabetic ordering of events
3 | [MarkDown] Bad alphabetic ordering of Foo members: Foo.ddd should go after Foo.ccc()
4 | [MarkDown] Bad alphabetic ordering of Foo members: Foo.ccc() should go after Foo.bbb()
--------------------------------------------------------------------------------
/test/assets/playground.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Playground
5 |
6 |
7 |
8 |
9 | First div
10 |
11 | Second div
12 | Inner span
13 |
14 |
15 |
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/check-returns/result.txt:
--------------------------------------------------------------------------------
1 | [MarkDown] foo.www() has mistyped 'return' type declaration: expected exactly 'returns: ', found 'returns '.
2 | [MarkDown] Async method Foo.asyncFunction should describe return type Promise
3 | [MarkDown] Method Foo.return42 is missing return type description
4 | [MarkDown] Method Foo.returnNothing has unneeded description of return type
--------------------------------------------------------------------------------
/test/assets/input/button.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Button test
5 |
6 |
7 |
8 |
9 |
15 |
16 |
--------------------------------------------------------------------------------
/test/assets/input/textarea.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Textarea test
5 |
6 |
7 |
8 |
9 |
14 |
15 |
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/md-builder-common/doc.md:
--------------------------------------------------------------------------------
1 | ### class: Foo
2 |
3 | This is a class.
4 |
5 | #### event: 'frame'
6 | - <[Frame]>
7 |
8 | This event is dispatched.
9 |
10 | #### foo.$(selector)
11 | - `selector` <[string]> A selector to query page for
12 | - returns: <[Promise]<[ElementHandle]>>
13 |
14 | The method runs document.querySelector.
15 |
16 | #### foo.url
17 | - <[string]>
18 |
19 | Contains the URL of the request.
20 |
--------------------------------------------------------------------------------
/test/assets/networkidle.html:
--------------------------------------------------------------------------------
1 |
20 |
--------------------------------------------------------------------------------
/.appveyor.yml:
--------------------------------------------------------------------------------
1 | environment:
2 | matrix:
3 | - nodejs_version: "6"
4 | - nodejs_version: "7"
5 |
6 | build: off
7 |
8 | install:
9 | - ps: Install-Product node $env:nodejs_version
10 | - yarn install
11 | - if "%nodejs_version%" == "7" (
12 | yarn run lint &
13 | yarn run coverage &
14 | yarn run test-doclint
15 | ) else (
16 | yarn run test-node6-transformer &
17 | yarn run build &
18 | yarn run unit-node6
19 | )
20 |
--------------------------------------------------------------------------------
/lib/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "extends": "../.eslintrc.js",
3 | /**
4 | * ESLint rules
5 | *
6 | * All available rules: http://eslint.org/docs/rules/
7 | *
8 | * Rules take the following form:
9 | * "rule-name", [severity, { opts }]
10 | * Severity: 2 == error, 1 == warning, 0 == off.
11 | */
12 | "rules": {
13 | "no-console": [2, { "allow": ["warn", "error", "assert", "timeStamp", "time", "timeEnd"] }],
14 | "no-debugger": 0,
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/examples/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "extends": "../.eslintrc.js",
3 | /**
4 | * ESLint rules
5 | *
6 | * All available rules: http://eslint.org/docs/rules/
7 | *
8 | * Rules take the following form:
9 | * "rule-name", [severity, { opts }]
10 | * Severity: 2 == error, 1 == warning, 0 == off.
11 | */
12 | "rules": {
13 | "indent": [2, 2, { "SwitchCase": 1, "CallExpression": {"arguments": 2}, "MemberExpression": 2, "outerIIFEBody": 0 }],
14 | "strict": [2, "global"],
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/test/assets/frames/nested-frames.html:
--------------------------------------------------------------------------------
1 |
11 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/md-builder-common/result.txt:
--------------------------------------------------------------------------------
1 | {
2 | "classes": [
3 | {
4 | "name": "Foo",
5 | "members": [
6 | {
7 | "name": "frame",
8 | "type": "event",
9 | "hasReturn": false,
10 | "async": false,
11 | "args": []
12 | },
13 | {
14 | "name": "$",
15 | "type": "method",
16 | "hasReturn": true,
17 | "async": false,
18 | "args": [
19 | "selector"
20 | ]
21 | },
22 | {
23 | "name": "url",
24 | "type": "property",
25 | "hasReturn": false,
26 | "async": false,
27 | "args": []
28 | }
29 | ]
30 | }
31 | ]
32 | }
--------------------------------------------------------------------------------
/utils/doclint/README.md:
--------------------------------------------------------------------------------
1 | # DocLint
2 |
3 | **Doclint** is a small program that lints Puppeteer's documentation against
4 | Puppeteer's source code.
5 |
6 | Doclint works in a few steps:
7 |
8 | 1. Read sources in `lib/` folder, parse AST trees and extract public API
9 | 2. Read sources in `docs/` folder, render markdown to HTML, use puppeteer to traverse the HTML
10 | and extract described API
11 | 3. Compare one API to another
12 |
13 | Doclint is also responsible for general markdown checks, most notably for the table of contents
14 | relevancy.
15 |
16 | ## Running
17 |
18 | ```bash
19 | npm run doc
20 | ```
21 |
22 | ## Tests
23 |
24 | Doclint has its own set of jasmine tests, located at `utils/doclint/test` folder.
25 |
26 | To execute tests, run:
27 |
28 | ```bash
29 | npm run test-doclint
30 | ```
31 |
--------------------------------------------------------------------------------
/test/assets/input/scrollable.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Scrollable test
5 |
6 |
7 |
8 |
22 |
23 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | dist: trusty
3 | addons:
4 | apt:
5 | packages:
6 | # This is required to run new chrome on old trusty
7 | - libnss3
8 | cache:
9 | yarn: true
10 | directories:
11 | - node_modules
12 | install:
13 | - yarn install
14 | # puppeteer's install script downloads Chrome
15 | script:
16 | - 'if [ "$NODE7" = "true" ]; then yarn run lint; fi'
17 | - 'if [ "$NODE7" = "true" ]; then yarn run coverage; fi'
18 | - 'if [ "$NODE7" = "true" ]; then yarn run test-doclint; fi'
19 | - 'if [ "$NODE6" = "true" ]; then yarn run test-node6-transformer; fi'
20 | - 'if [ "$NODE6" = "true" ]; then yarn run build; fi'
21 | - 'if [ "$NODE6" = "true" ]; then yarn run unit-node6; fi'
22 | jobs:
23 | include:
24 | - node_js: "7.6.0"
25 | env: NODE7=true
26 | - node_js: "6.4.0"
27 | env: NODE6=true
28 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # Other resources
2 |
3 | > Other useful tools, articles, and projects that use Puppeteer.
4 |
5 | ## Web scraping
6 | - [Puppetron](https://github.com/cheeaun/puppetron) - Demo site that shows how to use Puppeteer and Headless Chrome to render pages. Inspired by [GoogleChrome/rendertron](https://github.com/GoogleChrome/rendertron).
7 | - [Thal](https://medium.com/@e_mad_ehsan/getting-started-with-puppeteer-and-chrome-headless-for-web-scrapping-6bf5979dee3e "An article on medium") - Getting started with Puppeteer and Chrome Headless for Web Scraping.
8 |
9 | ## Testing
10 | - [angular-puppeteer-demo](https://github.com/Quramy/angular-puppeteer-demo) - Demo repository explaining how to use Puppeteer in Karma.
11 | - [mocha-headless-chrome](https://github.com/direct-adv-interfaces/mocha-headless-chrome) - Tool which runs client-side **mocha** tests in the command line through headless Chrome.
12 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // If node does not support async await, use the compiled version.
18 | let folder = 'lib';
19 | try {
20 | new Function('async function test(){await 1}');
21 | } catch (error) {
22 | folder = 'node6';
23 | }
24 |
25 | module.exports = require(`./${folder}/Puppeteer`);
26 |
--------------------------------------------------------------------------------
/examples/screenshot.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | const puppeteer = require('puppeteer');
20 |
21 | (async() => {
22 |
23 | const browser = await puppeteer.launch();
24 | const page = await browser.newPage();
25 | await page.goto('http://example.com');
26 | await page.screenshot({path: 'example.png'});
27 |
28 | await browser.close();
29 |
30 | })();
31 |
--------------------------------------------------------------------------------
/examples/screenshot-fullpage.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | const puppeteer = require('puppeteer');
20 | const devices = require('puppeteer/DeviceDescriptors');
21 |
22 | (async() => {
23 |
24 | const browser = await puppeteer.launch();
25 | const page = await browser.newPage();
26 | await page.emulate(devices['iPhone 6']);
27 | await page.goto('https://www.nytimes.com/');
28 | await page.screenshot({path: 'full.png', fullPage: true});
29 | await browser.close();
30 |
31 | })();
32 |
--------------------------------------------------------------------------------
/examples/proxy.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | const puppeteer = require('puppeteer');
20 |
21 | (async() => {
22 |
23 | const browser = await puppeteer.launch({
24 | // Launch chromium using a proxy server on port 9876.
25 | // More on proxying:
26 | // https://www.chromium.org/developers/design-documents/network-settings
27 | args: [ '--proxy-server=127.0.0.1:9876' ]
28 | });
29 | const page = await browser.newPage();
30 | await page.goto('https://google.com');
31 | await browser.close();
32 |
33 | })();
34 |
--------------------------------------------------------------------------------
/docs/issue_template.md:
--------------------------------------------------------------------------------
1 |
18 |
19 | ### Steps to reproduce
20 |
21 | **Tell us about your environment:**
22 |
23 | * Puppeteer version:
24 | * Platform / OS version:
25 | * URLs (if applicable):
26 |
27 | **What steps will reproduce the problem?**
28 |
29 | _Please include code that reproduces the issue._
30 |
31 | 1.
32 | 2.
33 | 3.
34 |
35 | **What is the expected result?**
36 |
37 |
38 | **What happens instead?**
39 |
40 |
--------------------------------------------------------------------------------
/examples/pdf.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | const puppeteer = require('puppeteer');
20 |
21 | (async() => {
22 |
23 | const browser = await puppeteer.launch();
24 | const page = await browser.newPage();
25 | await page.goto('https://news.ycombinator.com', {waitUntil: 'networkidle2'});
26 | // page.pdf() is currently supported only in headless mode.
27 | // @see https://bugs.chromium.org/p/chromium/issues/detail?id=753118
28 | await page.pdf({
29 | path: 'hn.pdf',
30 | format: 'letter'
31 | });
32 |
33 | await browser.close();
34 |
35 | })();
36 |
--------------------------------------------------------------------------------
/utils/doclint/toc.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | const markdownToc = require('markdown-toc');
18 | const Message = require('./Message');
19 |
20 | /**
21 | * @param {!Array} sources
22 | * @return {!Array}
23 | */
24 | module.exports = function(sources) {
25 | const warnings = [];
26 | for (const source of sources) {
27 | const newText = markdownToc.insert(source.text());
28 | if (source.setText(newText))
29 | warnings.push('Regenerated table-of-contexts: ' + source.projectPath());
30 | }
31 | return warnings.map(warning => Message.warning(warning));
32 | };
33 |
--------------------------------------------------------------------------------
/test/assets/input/touches.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Touch test
5 |
6 |
7 |
8 |
9 |
34 |
35 |
--------------------------------------------------------------------------------
/examples/block-images.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc., PhantomJS Authors All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | const puppeteer = require('puppeteer');
20 |
21 | (async() => {
22 |
23 | const browser = await puppeteer.launch();
24 | const page = await browser.newPage();
25 | await page.setRequestInterception(true);
26 | page.on('request', request => {
27 | if (request.resourceType === 'image')
28 | request.abort();
29 | else
30 | request.continue();
31 | });
32 | await page.goto('https://news.google.com/news/');
33 | await page.screenshot({path: 'news.png', fullPage: true});
34 |
35 | await browser.close();
36 |
37 | })();
38 |
39 |
--------------------------------------------------------------------------------
/utils/doclint/Message.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | class Message {
18 | /**
19 | * @param {string} type
20 | * @param {string} text
21 | */
22 | constructor(type, text) {
23 | this.type = type;
24 | this.text = text;
25 | }
26 |
27 | /**
28 | * @param {string} text
29 | * @return {!Message}
30 | */
31 | static error(text) {
32 | return new Message('error', text);
33 | }
34 |
35 | /**
36 | * @param {string} text
37 | * @return {!Message}
38 | */
39 | static warning(text) {
40 | return new Message('warning', text);
41 | }
42 | }
43 |
44 | module.exports = Message;
45 |
--------------------------------------------------------------------------------
/test/assets/grid.html:
--------------------------------------------------------------------------------
1 |
28 |
29 |
49 |
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/js-builder-common/result.txt:
--------------------------------------------------------------------------------
1 | {
2 | "classes": [
3 | {
4 | "name": "A",
5 | "members": [
6 | {
7 | "name": "property1",
8 | "type": "property",
9 | "hasReturn": false,
10 | "async": false,
11 | "args": []
12 | },
13 | {
14 | "name": "_property2",
15 | "type": "property",
16 | "hasReturn": false,
17 | "async": false,
18 | "args": []
19 | },
20 | {
21 | "name": "constructor",
22 | "type": "method",
23 | "hasReturn": false,
24 | "async": false,
25 | "args": [
26 | "delegate"
27 | ]
28 | },
29 | {
30 | "name": "getter",
31 | "type": "property",
32 | "hasReturn": false,
33 | "async": false,
34 | "args": []
35 | },
36 | {
37 | "name": "method",
38 | "type": "method",
39 | "hasReturn": true,
40 | "async": true,
41 | "args": [
42 | "foo",
43 | "bar"
44 | ]
45 | },
46 | {
47 | "name": "anevent",
48 | "type": "event",
49 | "hasReturn": false,
50 | "async": false,
51 | "args": []
52 | }
53 | ]
54 | }
55 | ]
56 | }
--------------------------------------------------------------------------------
/lib/Puppeteer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | const {helper} = require('./helper');
17 | const Launcher = require('./Launcher');
18 |
19 | class Puppeteer {
20 | /**
21 | * @param {!Object=} options
22 | * @return {!Promise}
23 | */
24 | static launch(options) {
25 | return Launcher.launch(options);
26 | }
27 |
28 | /**
29 | * @param {{browserWSEndpoint: string, ignoreHTTPSErrors: boolean}} options
30 | * @return {!Promise}
31 | */
32 | static connect(options) {
33 | return Launcher.connect(options);
34 | }
35 |
36 | /**
37 | * @return {string}
38 | */
39 | static executablePath() {
40 | return Launcher.executablePath();
41 | }
42 | }
43 |
44 | module.exports = Puppeteer;
45 | helper.tracePublicAPI(Puppeteer);
46 |
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/js-builder-inheritance/result.txt:
--------------------------------------------------------------------------------
1 | {
2 | "classes": [
3 | {
4 | "name": "A",
5 | "members": [
6 | {
7 | "name": "constructor",
8 | "type": "method",
9 | "hasReturn": false,
10 | "async": false,
11 | "args": []
12 | },
13 | {
14 | "name": "foo",
15 | "type": "method",
16 | "hasReturn": false,
17 | "async": false,
18 | "args": [
19 | "a"
20 | ]
21 | },
22 | {
23 | "name": "bar",
24 | "type": "method",
25 | "hasReturn": false,
26 | "async": false,
27 | "args": []
28 | }
29 | ]
30 | },
31 | {
32 | "name": "B",
33 | "members": [
34 | {
35 | "name": "bar",
36 | "type": "method",
37 | "hasReturn": false,
38 | "async": false,
39 | "args": [
40 | "override"
41 | ]
42 | },
43 | {
44 | "name": "foo",
45 | "type": "event",
46 | "hasReturn": false,
47 | "async": false,
48 | "args": []
49 | },
50 | {
51 | "name": "foo",
52 | "type": "method",
53 | "hasReturn": false,
54 | "async": false,
55 | "args": [
56 | "a"
57 | ]
58 | }
59 | ]
60 | }
61 | ]
62 | }
--------------------------------------------------------------------------------
/test/assets/input/keyboard.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Keyboard test
5 |
6 |
7 |
8 |
43 |
44 |
--------------------------------------------------------------------------------
/examples/search.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | const puppeteer = require('puppeteer');
20 |
21 | (async() => {
22 |
23 | const browser = await puppeteer.launch();
24 | const page = await browser.newPage();
25 | await page.goto('https://google.com', {waitUntil: 'networkidle2'});
26 |
27 | await page.waitFor('input[name=q]');
28 | // Type our query into the search bar
29 | await page.type('input[name=q]', 'puppeteer');
30 |
31 | await page.click('input[type="submit"]');
32 |
33 | // Wait for the results to show up
34 | await page.waitForSelector('h3 a');
35 |
36 | // Extract the results from the page
37 | const links = await page.evaluate(() => {
38 | const anchors = Array.from(document.querySelectorAll('h3 a'));
39 | return anchors.map(anchor => anchor.textContent);
40 | });
41 | console.log(links.join('\n'));
42 | await browser.close();
43 |
44 | })();
45 |
--------------------------------------------------------------------------------
/lib/externs.d.ts:
--------------------------------------------------------------------------------
1 | import { Connection as RealConnection, Session as RealSession } from './Connection.js';
2 | import {Browser as RealBrowser, TaskQueue as RealTaskQueue} from './Browser.js';
3 | import * as RealPage from './Page.js';
4 | import {Mouse as RealMouse, Keyboard as RealKeyboard, Touchscreen as RealTouchscreen} from './Input.js';
5 | import {Frame as RealFrame, FrameManager as RealFrameManager} from './FrameManager.js';
6 | import {JSHandle as RealJSHandle, ExecutionContext as RealExecutionContext} from './ExecutionContext.js';
7 | import * as RealElementHandle from './ElementHandle.js';
8 | import * as RealNetworkManager from './NetworkManager.js';
9 | import * as child_process from 'child_process';
10 | export as namespace Puppeteer;
11 |
12 | export class Connection extends RealConnection {}
13 | export class Session extends RealSession {}
14 | export class Mouse extends RealMouse {}
15 | export class Keyboard extends RealKeyboard {}
16 | export class Touchscreen extends RealTouchscreen {}
17 | export class TaskQueue extends RealTaskQueue {}
18 | export class Browser extends RealBrowser {}
19 | export class Frame extends RealFrame {}
20 | export class FrameManager extends RealFrameManager {}
21 | export class NetworkManager extends RealNetworkManager {}
22 | export class ElementHandle extends RealElementHandle {}
23 | export class JSHandle extends RealJSHandle {}
24 | export class ExecutionContext extends RealExecutionContext {}
25 | export class Page extends RealPage {}
26 |
27 |
28 | export interface ChildProcess extends child_process.ChildProcess {}
29 |
--------------------------------------------------------------------------------
/examples/detect-sniff.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc., PhantomJS Authors All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | const puppeteer = require('puppeteer');
20 |
21 | function sniffDetector() {
22 | const userAgent = window.navigator.userAgent;
23 | const platform = window.navigator.platform;
24 |
25 | window.navigator.__defineGetter__('userAgent', function() {
26 | window.navigator.sniffed = true;
27 | return userAgent;
28 | });
29 |
30 | window.navigator.__defineGetter__('platform', function() {
31 | window.navigator.sniffed = true;
32 | return platform;
33 | });
34 | }
35 |
36 | (async() => {
37 |
38 | const browser = await puppeteer.launch();
39 | const page = await browser.newPage();
40 | await page.evaluateOnNewDocument(sniffDetector);
41 | await page.goto('https://www.google.com', {waitUntil: 'networkidle2'});
42 | console.log('Sniffed: ' + (await page.evaluate(() => !!navigator.sniffed)));
43 |
44 | await browser.close();
45 |
46 | })();
47 |
--------------------------------------------------------------------------------
/examples/custom-event.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | const puppeteer = require('puppeteer');
20 |
21 | (async() => {
22 |
23 | const browser = await puppeteer.launch();
24 | const page = await browser.newPage();
25 |
26 | // Define a window.onCustomEvent function on the page.
27 | await page.exposeFunction('onCustomEvent', e => {
28 | console.log(`${e.type} fired`, e.detail || '');
29 | });
30 |
31 | /**
32 | * Attach an event listener to page to capture a custom event on page load/navigation.
33 | * @param {string} type Event name.
34 | * @return {!Promise}
35 | */
36 | function listenFor(type) {
37 | return page.evaluateOnNewDocument(type => {
38 | document.addEventListener(type, e => {
39 | window.onCustomEvent({type, detail: e.detail});
40 | });
41 | }, type);
42 | }
43 |
44 | await listenFor('app-ready'); // Listen for "app-ready" custom event on page load.
45 |
46 | await page.goto('https://www.chromestatus.com/features', {waitUntil: 'networkidle2'});
47 |
48 | await browser.close();
49 |
50 | })();
51 |
--------------------------------------------------------------------------------
/utils/node6-transform/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | const fs = require('fs');
18 | const path = require('path');
19 | const removeRecursive = require('rimraf').sync;
20 | const transformAsyncFunctions = require('./TransformAsyncFunctions');
21 |
22 | copyFolder(path.join(__dirname, '..', '..', 'lib'), path.join(__dirname, '..', '..', 'node6'));
23 | copyFolder(path.join(__dirname, '..', '..', 'test'), path.join(__dirname, '..', '..', 'node6-test'));
24 |
25 |
26 | function copyFolder(source, target) {
27 | if (fs.existsSync(target))
28 | removeRecursive(target);
29 | fs.mkdirSync(target);
30 |
31 | fs.readdirSync(source).forEach(file => {
32 | const from = path.join(source, file);
33 | const to = path.join(target, file);
34 | if (fs.lstatSync(from).isDirectory()) {
35 | copyFolder(from, to);
36 | } else {
37 | let text = fs.readFileSync(from);
38 | if (file.endsWith('.js'))
39 | text = transformAsyncFunctions(text.toString()).replace(/require\('\.\.\/lib\//g, `require('../node6/`);
40 | fs.writeFileSync(to, text);
41 | }
42 | });
43 | }
44 |
--------------------------------------------------------------------------------
/test/assets/input/mouse-helper.js:
--------------------------------------------------------------------------------
1 | // This injects a box into the page that moves with the mouse;
2 | // Useful for debugging
3 | (function(){
4 | const box = document.createElement('div');
5 | box.classList.add('mouse-helper');
6 | const styleElement = document.createElement('style');
7 | styleElement.innerHTML = `
8 | .mouse-helper {
9 | pointer-events: none;
10 | position: absolute;
11 | top: 0;
12 | left: 0;
13 | width: 20px;
14 | height: 20px;
15 | background: rgba(0,0,0,.4);
16 | border: 1px solid white;
17 | border-radius: 10px;
18 | margin-left: -10px;
19 | margin-top: -10px;
20 | transition: background .2s, border-radius .2s, border-color .2s;
21 | }
22 | .mouse-helper.button-1 {
23 | transition: none;
24 | background: rgba(0,0,0,0.9);
25 | }
26 | .mouse-helper.button-2 {
27 | transition: none;
28 | border-color: rgba(0,0,255,0.9);
29 | }
30 | .mouse-helper.button-3 {
31 | transition: none;
32 | border-radius: 4px;
33 | }
34 | .mouse-helper.button-4 {
35 | transition: none;
36 | border-color: rgba(255,0,0,0.9);
37 | }
38 | .mouse-helper.button-5 {
39 | transition: none;
40 | border-color: rgba(0,255,0,0.9);
41 | }
42 | `;
43 | document.head.appendChild(styleElement);
44 | document.body.appendChild(box);
45 | document.addEventListener('mousemove', event => {
46 | box.style.left = event.pageX + 'px';
47 | box.style.top = event.pageY + 'px';
48 | updateButtons(event.buttons);
49 | }, true);
50 | document.addEventListener('mousedown', event => {
51 | updateButtons(event.buttons);
52 | box.classList.add('button-' + event.which);
53 | }, true);
54 | document.addEventListener('mouseup', event => {
55 | updateButtons(event.buttons);
56 | box.classList.remove('button-' + event.which);
57 | }, true);
58 | function updateButtons(buttons) {
59 | for (let i = 0; i < 5; i++)
60 | box.classList.toggle('button-' + i, buttons & (1 << i));
61 | }
62 | })();
63 |
--------------------------------------------------------------------------------
/test/server/cert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIFXzCCA0egAwIBAgIJAM+8uXXn61zZMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
3 | BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
4 | aWRnaXRzIFB0eSBMdGQwIBcNMTcwNzEwMjI1MDA2WhgPMzAxNjExMTAyMjUwMDZa
5 | MEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJ
6 | bnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
7 | ggIKAoICAQCfsIbw1Q91wooUdSu5tDiyrSYB6ZQmY49Y141KQ0ll0ZXzf1sPTpPg
8 | OuBjJE8Fre2Wn3jJ0SfLFyQBMvE49hqWyY/U5Xc367ujqKFQVoItnoV5MM2TPu5J
9 | /zhtf26Vq0Pcrujt5LfRe1JSYKdJ21Tquqa0MAGI0HghaCdSdtyo2xuotnukirKb
10 | QrvP/YNa+ONZT6KW8MFAwfoCOJMo1idrkBA1Wve5xcCd7J9Oy5mWCBxTSR67W2vQ
11 | izoOTSkzD0xoXpFF5V/34zJGWU9t6Z5qytV/w5ROY3Tk9EaKs0NcQmXlCxSmkXil
12 | KSTlZ/VDeDliI92jGn4hT+apisglm3aaTnVVAP0EbZ/CF9Fwb601M7IcAP9ejaeB
13 | EEs+smXpuzhAfxPa5SpZCWeaXLcFq6Ewi2LXrMaChWvbu9AUi3QjuT3u9PW3B0w5
14 | C54FLfvcy9X9dQQ/jCgydF3eyhiO3SuLZqrhofHUn53z4UCEYgbC7uQSv08ep2UD
15 | kT2ARN6aetXVgiQBYS8hcGaFrdsUTSAmT0flt0g8ZoHn+NmuAWxbAx8UnPd0p/EP
16 | B4cZByDOUDGgDMSOEluheiCFlZBQEJnvOhim6rwSje87EzQazGkwRpOrBtzGIGcM
17 | xmotG9IrMbzKb4Z+yg5pOEW2WKEy3f2h8P2bisbbHwYa/tgTHVWbGwIDAQABo1Aw
18 | TjAdBgNVHQ4EFgQUZvIOJVkyQTAd0fpUkpqgcXVID+4wHwYDVR0jBBgwFoAUZvIO
19 | JVkyQTAd0fpUkpqgcXVID+4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC
20 | AgEASmqzfr4TOVFbNo3r85QlCQB8W4xaArRpY6k7ugxJm2/9iyyovMKMhCcRvmPV
21 | HZXDTV8WEbLDg3uNF56WU39YmtfjghnsYcGQ/UhnH2PoHQxGIKzOoalLew7kP0Uc
22 | HCmXi7OPjqeFQgjWVhLkL/RIzZ3vOVFQfqodTVIdYDe0O1cNRD0U0rtGa0MHzZlD
23 | y/ZaksiB0E3UL/GsgyJoCxCAF1E80PU9aw1K9COyTOChpUKnhSHC964KlhnJKyOe
24 | HKCYtb5RVrF7ryBcGneLTcQm/oNUp8aRyiwyQIDH7djFSp11MakXBF+EeGR523pp
25 | u+sleR7ZFBGyb3Z5K9NdRdUk0SWu7Hu4jQtJHn+PmIZ1qjfbPv5qDfVd1vmFwqsu
26 | 7NfsLoNm0dQNu5WOMLISQHmQiT86AH2wWQ3l5Sm+g8/BdNLQLklhtZcRhp2efyiL
27 | ciUmGugKqoX+nPIZ36kuoRTZy++AnTiid011vZFe1qrfug/ykWiqWmBSvD/cfRU4
28 | ydoK87cfjIixqmpRZ7j2q+/cDK2SbYN0t/Xrufw3L6TjDgUEL7ZCImcwqqWJz9I8
29 | ASnnL5PhX8bbsUrtE21Ugqk2zYnVnqRO5FjINtlTb07y9pGC/QpBkb1AasF5okhe
30 | 7lw/sMiryKKzS4A10nRV/+gErDBsIBj+cpGPM8brLfZuy2o=
31 | -----END CERTIFICATE-----
32 |
--------------------------------------------------------------------------------
/lib/Dialog.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | const {helper} = require('./helper');
18 |
19 | class Dialog {
20 | /**
21 | * @param {!Puppeteer.Session} client
22 | * @param {string} type
23 | * @param {string} message
24 | * @param {(string|undefined)} defaultValue
25 | */
26 | constructor(client, type, message, defaultValue = '') {
27 | this._client = client;
28 | this.type = type;
29 | this._message = message;
30 | this._handled = false;
31 | this._defaultValue = defaultValue;
32 | }
33 |
34 | /**
35 | * @return {string}
36 | */
37 | message() {
38 | return this._message;
39 | }
40 |
41 | /**
42 | * @return {string}
43 | */
44 | defaultValue() {
45 | return this._defaultValue;
46 | }
47 |
48 | /**
49 | * @param {string=} promptText
50 | */
51 | async accept(promptText) {
52 | console.assert(!this._handled, 'Cannot accept dialog which is already handled!');
53 | this._handled = true;
54 | await this._client.send('Page.handleJavaScriptDialog', {
55 | accept: true,
56 | promptText: promptText
57 | });
58 | }
59 |
60 | async dismiss() {
61 | console.assert(!this._handled, 'Cannot dismiss dialog which is already handled!');
62 | this._handled = true;
63 | await this._client.send('Page.handleJavaScriptDialog', {
64 | accept: false
65 | });
66 | }
67 | }
68 |
69 | Dialog.Type = {
70 | Alert: 'alert',
71 | BeforeUnload: 'beforeunload',
72 | Confirm: 'confirm',
73 | Prompt: 'prompt'
74 | };
75 |
76 | module.exports = Dialog;
77 | helper.tracePublicAPI(Dialog);
78 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "puppeteer",
3 | "version": "0.13.0-alpha",
4 | "description": "A high-level API to control headless Chrome over the DevTools Protocol",
5 | "main": "index.js",
6 | "repository": "github:GoogleChrome/puppeteer",
7 | "engines": {
8 | "node": ">=6.4.0"
9 | },
10 | "scripts": {
11 | "unit": "jasmine test/test.js",
12 | "debug-unit": "cross-env DEBUG_TEST=true node --inspect-brk ./node_modules/jasmine/bin/jasmine.js test/test.js",
13 | "test-doclint": "jasmine utils/doclint/check_public_api/test/test.js && jasmine utils/doclint/preprocessor/test.js",
14 | "test": "npm run lint --silent && npm run coverage && npm run test-doclint && npm run test-node6-transformer",
15 | "install": "node install.js",
16 | "lint": "([ \"$CI\" = true ] && eslint --quiet -f codeframe . || eslint .) && npm run tsc && npm run doc",
17 | "doc": "node utils/doclint/cli.js",
18 | "coverage": "cross-env COVERAGE=true npm run unit",
19 | "test-node6-transformer": "jasmine utils/node6-transform/test/test.js",
20 | "build": "node utils/node6-transform/index.js",
21 | "unit-node6": "jasmine node6-test/test.js",
22 | "tsc": "tsc -p ."
23 | },
24 | "author": "The Chromium Authors",
25 | "license": "Apache-2.0",
26 | "dependencies": {
27 | "debug": "^2.6.8",
28 | "extract-zip": "^1.6.5",
29 | "https-proxy-agent": "^2.1.0",
30 | "mime": "^1.3.4",
31 | "progress": "^2.0.0",
32 | "proxy-from-env": "^1.0.0",
33 | "rimraf": "^2.6.1",
34 | "ws": "^3.0.0"
35 | },
36 | "puppeteer": {
37 | "chromium_revision": "511134"
38 | },
39 | "devDependencies": {
40 | "@types/debug": "0.0.30",
41 | "@types/extract-zip": "^1.6.2",
42 | "@types/mime": "^1.3.1",
43 | "@types/node": "^8.0.26",
44 | "@types/rimraf": "^2.0.2",
45 | "@types/ws": "^3.0.2",
46 | "commonmark": "^0.27.0",
47 | "cross-env": "^5.0.5",
48 | "eslint": "^4.0.0",
49 | "esprima": "^4.0.0",
50 | "jasmine": "^2.6.0",
51 | "markdown-toc": "^1.1.0",
52 | "minimist": "^1.2.0",
53 | "ncp": "^2.0.0",
54 | "pdfjs-dist": "^1.8.595",
55 | "pixelmatch": "^4.0.2",
56 | "pngjs": "^3.2.0",
57 | "text-diff": "^1.0.1",
58 | "typescript": "^2.6.0-rc"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/lib/Multimap.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | class Multimap {
18 | constructor() {
19 | this._map = new Map();
20 | }
21 |
22 | set(key, value) {
23 | let set = this._map.get(key);
24 | if (!set) {
25 | set = new Set();
26 | this._map.set(key, set);
27 | }
28 | set.add(value);
29 | }
30 |
31 | get(key) {
32 | let result = this._map.get(key);
33 | if (!result)
34 | result = new Set();
35 | return result;
36 | }
37 |
38 | has(key) {
39 | return this._map.has(key);
40 | }
41 |
42 | hasValue(key, value) {
43 | const set = this._map.get(key);
44 | if (!set)
45 | return false;
46 | return set.has(value);
47 | }
48 |
49 | /**
50 | * @return {number}
51 | */
52 | get size() {
53 | return this._map.size;
54 | }
55 |
56 | delete(key, value) {
57 | const values = this.get(key);
58 | const result = values.delete(value);
59 | if (!values.size)
60 | this._map.delete(key);
61 | return result;
62 | }
63 |
64 | deleteAll(key) {
65 | this._map.delete(key);
66 | }
67 |
68 | firstValue(key) {
69 | const set = this._map.get(key);
70 | if (!set)
71 | return null;
72 | return set.values().next().value;
73 | }
74 |
75 | firstKey() {
76 | return this._map.keys().next().value;
77 | }
78 |
79 | valuesArray() {
80 | const result = [];
81 | for (const key of this._map.keys())
82 | result.push(...Array.from(this._map.get(key).values()));
83 | return result;
84 | }
85 |
86 | keysArray() {
87 | return Array.from(this._map.keys());
88 | }
89 |
90 | clear() {
91 | this._map.clear();
92 | }
93 | }
94 |
95 | module.exports = Multimap;
96 |
--------------------------------------------------------------------------------
/test/assets/input/select.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Selection Test
5 |
6 |
7 |
24 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/test/frame-utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | const utils = module.exports = {
18 | /**
19 | * @param {!Page} page
20 | * @param {string} frameId
21 | * @param {string} url
22 | */
23 | attachFrame: async function(page, frameId, url) {
24 | await page.evaluate(attachFrame, frameId, url);
25 |
26 | function attachFrame(frameId, url) {
27 | const frame = document.createElement('iframe');
28 | frame.src = url;
29 | frame.id = frameId;
30 | document.body.appendChild(frame);
31 | return new Promise(x => frame.onload = x);
32 | }
33 | },
34 |
35 | /**
36 | * @param {!Page} page
37 | * @param {string} frameId
38 | */
39 | detachFrame: async function(page, frameId) {
40 | await page.evaluate(detachFrame, frameId);
41 |
42 | function detachFrame(frameId) {
43 | const frame = document.getElementById(frameId);
44 | frame.remove();
45 | }
46 | },
47 |
48 | /**
49 | * @param {!Page} page
50 | * @param {string} frameId
51 | * @param {string} url
52 | */
53 | navigateFrame: async function(page, frameId, url) {
54 | await page.evaluate(navigateFrame, frameId, url);
55 |
56 | function navigateFrame(frameId, url) {
57 | const frame = document.getElementById(frameId);
58 | frame.src = url;
59 | return new Promise(x => frame.onload = x);
60 | }
61 | },
62 |
63 | /**
64 | * @param {!Frame} frame
65 | * @param {string=} indentation
66 | * @return {string}
67 | */
68 | dumpFrames: function(frame, indentation) {
69 | indentation = indentation || '';
70 | let result = indentation + frame.url();
71 | for (const child of frame.childFrames())
72 | result += '\n' + utils.dumpFrames(child, ' ' + indentation);
73 | return result;
74 | },
75 | };
76 |
--------------------------------------------------------------------------------
/utils/doclint/preprocessor/test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | const preprocessor = require('.');
18 | const SourceFactory = require('../SourceFactory');
19 | const factory = new SourceFactory();
20 | const VERSION = require('../../../package.json').version;
21 |
22 | describe('preprocessor', function() {
23 | it('should throw for unknown command', function() {
24 | const source = factory.createForTest('doc.md', getCommand('unknownCommand()'));
25 | const messages = preprocessor([source]);
26 | expect(source.hasUpdatedText()).toBe(false);
27 | expect(messages.length).toBe(1);
28 | expect(messages[0].type).toBe('error');
29 | expect(messages[0].text).toContain('Unknown command');
30 | });
31 | describe('gen:version', function() {
32 | it('should work', function() {
33 | const source = factory.createForTest('doc.md', `Puppeteer v${getCommand('version')}`);
34 | const messages = preprocessor([source]);
35 | expect(messages.length).toBe(1);
36 | expect(messages[0].type).toBe('warning');
37 | expect(messages[0].text).toContain('doc.md');
38 | expect(source.text()).toBe(`Puppeteer v${getCommand('version', VERSION)}`);
39 | });
40 | it('should tolerate different writing', function() {
41 | const source = factory.createForTest('doc.md', `Puppeteer vWHAT
42 | `);
43 | preprocessor([source]);
44 | expect(source.text()).toBe(`Puppeteer v${VERSION}`);
45 | });
46 | it('should not tolerate missing gen:stop', function() {
47 | const source = factory.createForTest('doc.md', ``);
48 | const messages = preprocessor([source]);
49 | expect(source.hasUpdatedText()).toBe(false);
50 | expect(messages.length).toBe(1);
51 | expect(messages[0].type).toBe('error');
52 | expect(messages[0].text).toContain(`Failed to find 'gen:stop'`);
53 | });
54 | });
55 | });
56 |
57 | function getCommand(name, body = '') {
58 | return `${body}`;
59 | }
60 |
--------------------------------------------------------------------------------
/test/assets/detect-touch.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Detect Touch Test
5 |
10 |
11 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/lib/Tracing.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | const {helper} = require('./helper');
17 | const fs = require('fs');
18 |
19 | const openAsync = helper.promisify(fs.open);
20 | const writeAsync = helper.promisify(fs.write);
21 | const closeAsync = helper.promisify(fs.close);
22 |
23 | class Tracing {
24 | /**
25 | * @param {!Puppeteer.Session} client
26 | */
27 | constructor(client) {
28 | this._client = client;
29 | this._recording = false;
30 | this._path = '';
31 | }
32 |
33 | /**
34 | * @param {!Object} options
35 | */
36 | async start(options) {
37 | console.assert(!this._recording, 'Cannot start recording trace while already recording trace.');
38 | console.assert(options.path, 'Must specify a path to write trace file to.');
39 |
40 | const categoriesArray = [
41 | '-*', 'devtools.timeline', 'v8.execute', 'disabled-by-default-devtools.timeline',
42 | 'disabled-by-default-devtools.timeline.frame', 'toplevel',
43 | 'blink.console', 'blink.user_timing', 'latencyInfo', 'disabled-by-default-devtools.timeline.stack',
44 | 'disabled-by-default-v8.cpu_profiler'
45 | ];
46 |
47 | if (options.screenshots)
48 | categoriesArray.push('disabled-by-default-devtools.screenshot');
49 |
50 | this._path = options.path;
51 | this._recording = true;
52 | await this._client.send('Tracing.start', {
53 | transferMode: 'ReturnAsStream',
54 | categories: categoriesArray.join(',')
55 | });
56 | }
57 |
58 | async stop() {
59 | let fulfill;
60 | const contentPromise = new Promise(x => fulfill = x);
61 | this._client.once('Tracing.tracingComplete', event => {
62 | this._readStream(event.stream, this._path).then(fulfill);
63 | });
64 | await this._client.send('Tracing.end');
65 | this._recording = false;
66 | return contentPromise;
67 | }
68 |
69 | /**
70 | * @param {string} handle
71 | * @param {string} path
72 | */
73 | async _readStream(handle, path) {
74 | let eof = false;
75 | const file = await openAsync(path, 'w');
76 | while (!eof) {
77 | const response = await this._client.send('IO.read', {handle});
78 | eof = response.eof;
79 | if (path)
80 | await writeAsync(file, response.data);
81 | }
82 | await closeAsync(file);
83 | await this._client.send('IO.close', {handle});
84 | }
85 | }
86 | helper.tracePublicAPI(Tracing);
87 |
88 | module.exports = Tracing;
89 |
--------------------------------------------------------------------------------
/utils/doclint/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | /**
3 | * Copyright 2017 Google Inc. All rights reserved.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | const puppeteer = require('../..');
19 | const path = require('path');
20 | const SourceFactory = require('./SourceFactory');
21 |
22 | const PROJECT_DIR = path.join(__dirname, '..', '..');
23 |
24 | const RED_COLOR = '\x1b[31m';
25 | const YELLOW_COLOR = '\x1b[33m';
26 | const RESET_COLOR = '\x1b[0m';
27 |
28 | run();
29 |
30 | async function run() {
31 | const startTime = Date.now();
32 |
33 | const sourceFactory = new SourceFactory();
34 | /** @type {!Array} */
35 | const messages = [];
36 |
37 | // Documentation checks.
38 | {
39 | const mdSources = await sourceFactory.readdir(path.join(PROJECT_DIR, 'docs'), '.md');
40 |
41 | const toc = require('./toc');
42 | messages.push(...await toc(mdSources));
43 |
44 | const preprocessor = require('./preprocessor');
45 | messages.push(...await preprocessor(mdSources));
46 |
47 | const browser = await puppeteer.launch({args: ['--no-sandbox']});
48 | const page = await browser.newPage();
49 | const checkPublicAPI = require('./check_public_api');
50 | const jsSources = await sourceFactory.readdir(path.join(PROJECT_DIR, 'lib'), '.js');
51 | messages.push(...await checkPublicAPI(page, mdSources, jsSources));
52 | await browser.close();
53 | }
54 |
55 | // Report results.
56 | const errors = messages.filter(message => message.type === 'error');
57 | if (errors.length) {
58 | console.log('DocLint Failures:');
59 | for (let i = 0; i < errors.length; ++i) {
60 | let error = errors[i].text;
61 | error = error.split('\n').join('\n ');
62 | console.log(` ${i + 1}) ${RED_COLOR}${error}${RESET_COLOR}`);
63 | }
64 | }
65 | const warnings = messages.filter(message => message.type === 'warning');
66 | if (warnings.length) {
67 | console.log('DocLint Warnings:');
68 | for (let i = 0; i < warnings.length; ++i) {
69 | let warning = warnings[i].text;
70 | warning = warning.split('\n').join('\n ');
71 | console.log(` ${i + 1}) ${YELLOW_COLOR}${warning}${RESET_COLOR}`);
72 | }
73 | }
74 | let clearExit = messages.length === 0;
75 | if (await sourceFactory.saveChangedSources()) {
76 | if (clearExit)
77 | console.log(`${YELLOW_COLOR}Some files were updated.${RESET_COLOR}`);
78 | clearExit = false;
79 | }
80 | console.log(`${errors.length} failures, ${warnings.length} warnings.`);
81 | const runningTime = Date.now() - startTime;
82 | console.log(`DocLint Finished in ${runningTime / 1000} seconds`);
83 | process.exit(clearExit ? 0 : 1);
84 | }
85 |
--------------------------------------------------------------------------------
/install.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | if (process.env.PUPPETEER_SKIP_CHROMIUM_DOWNLOAD) {
18 | console.log('**INFO** Skipping Chromium download. "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" environment variable was found.');
19 | return;
20 | }
21 | if (process.env.NPM_CONFIG_PUPPETEER_SKIP_CHROMIUM_DOWNLOAD || process.env.npm_config_puppeteer_skip_chromium_download) {
22 | console.log('**INFO** Skipping Chromium download. "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" was set in npm config.');
23 | return;
24 | }
25 |
26 | const Downloader = require('./utils/ChromiumDownloader');
27 | const platform = Downloader.currentPlatform();
28 | const revision = require('./package').puppeteer.chromium_revision;
29 | const ProgressBar = require('progress');
30 |
31 | const revisionInfo = Downloader.revisionInfo(platform, revision);
32 | // Do nothing if the revision is already downloaded.
33 | if (revisionInfo.downloaded)
34 | return;
35 |
36 | // Override current environment proxy settings with npm configuration, if any.
37 | const NPM_HTTPS_PROXY = process.env.npm_config_https_proxy || process.env.npm_config_proxy;
38 | const NPM_HTTP_PROXY = process.env.npm_config_http_proxy || process.env.npm_config_proxy;
39 | if (NPM_HTTPS_PROXY)
40 | process.env.HTTPS_PROXY = NPM_HTTPS_PROXY;
41 | if (NPM_HTTP_PROXY)
42 | process.env.HTTP_PROXY = NPM_HTTP_PROXY;
43 |
44 | const allRevisions = Downloader.downloadedRevisions();
45 | const DOWNLOAD_HOST = process.env.PUPPETEER_DOWNLOAD_HOST || process.env.npm_config_puppeteer_download_host;
46 | Downloader.downloadRevision(platform, revision, DOWNLOAD_HOST, onProgress)
47 | .then(onSuccess)
48 | .catch(onError);
49 |
50 | /**
51 | * @return {!Promise}
52 | */
53 | function onSuccess() {
54 | console.log('Chromium downloaded to ' + revisionInfo.folderPath);
55 | // Remove previous chromium revisions.
56 | const cleanupOldVersions = allRevisions.map(({platform, revision}) => Downloader.removeRevision(platform, revision));
57 | return Promise.all(cleanupOldVersions);
58 | }
59 |
60 | /**
61 | * @param {!Error} error
62 | */
63 | function onError(error) {
64 | console.error(`ERROR: Failed to download Chromium r${revision}! Set "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" env variable to skip download.`);
65 | console.error(error);
66 | process.exit(1);
67 | }
68 |
69 | let progressBar = null;
70 | function onProgress(bytesTotal, delta) {
71 | if (!progressBar) {
72 | progressBar = new ProgressBar(`Downloading Chromium r${revision} - ${toMegabytes(bytesTotal)} [:bar] :percent :etas `, {
73 | complete: '=',
74 | incomplete: ' ',
75 | width: 20,
76 | total: bytesTotal,
77 | });
78 | }
79 | progressBar.tick(delta);
80 | }
81 |
82 | function toMegabytes(bytes) {
83 | const mb = bytes / 1024 / 1024;
84 | return `${Math.round(mb * 10) / 10} Mb`;
85 | }
86 |
87 |
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/Documentation.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | class Documentation {
18 | /**
19 | * @param {!Array} clasesArray
20 | */
21 | constructor(classesArray) {
22 | this.classesArray = classesArray;
23 | this.classes = new Map();
24 | for (const cls of classesArray)
25 | this.classes.set(cls.name, cls);
26 | }
27 | }
28 |
29 | Documentation.Class = class {
30 | /**
31 | * @param {string} name
32 | * @param {!Array} membersArray
33 | */
34 | constructor(name, membersArray) {
35 | this.name = name;
36 | this.membersArray = membersArray;
37 | this.members = new Map();
38 | this.properties = new Map();
39 | this.methods = new Map();
40 | this.events = new Map();
41 | for (const member of membersArray) {
42 | this.members.set(member.name, member);
43 | if (member.type === 'method')
44 | this.methods.set(member.name, member);
45 | else if (member.type === 'property')
46 | this.properties.set(member.name, member);
47 | else if (member.type === 'event')
48 | this.events.set(member.name, member);
49 | }
50 | }
51 | };
52 |
53 | Documentation.Member = class {
54 | /**
55 | * @param {string} type
56 | * @param {string} name
57 | * @param {!Array} argsArray
58 | * @param {boolean} hasReturn
59 | * @param {boolean} async
60 | */
61 | constructor(type, name, argsArray, hasReturn, async) {
62 | this.type = type;
63 | this.name = name;
64 | this.argsArray = argsArray;
65 | this.args = new Map();
66 | this.hasReturn = hasReturn;
67 | this.async = async;
68 | for (const arg of argsArray)
69 | this.args.set(arg.name, arg);
70 | }
71 |
72 | /**
73 | * @param {string} name
74 | * @param {!Array} argsArray
75 | * @param {boolean} hasReturn
76 | * @return {!Documentation.Member}
77 | */
78 | static createMethod(name, argsArray, hasReturn, async) {
79 | return new Documentation.Member('method', name, argsArray, hasReturn, async);
80 | }
81 |
82 | /**
83 | * @param {string} name
84 | * @return {!Documentation.Member}
85 | */
86 | static createProperty(name) {
87 | return new Documentation.Member('property', name, [], false, false);
88 | }
89 |
90 | /**
91 | * @param {string} name
92 | * @return {!Documentation.Member}
93 | */
94 | static createEvent(name) {
95 | return new Documentation.Member('event', name, [], false, false);
96 | }
97 | };
98 |
99 | Documentation.Argument = class {
100 | /**
101 | * @param {string} name
102 | */
103 | constructor(name) {
104 | this.name = name;
105 | }
106 | };
107 |
108 | module.exports = Documentation;
109 |
110 |
--------------------------------------------------------------------------------
/utils/doclint/preprocessor/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | const Message = require('../Message');
18 |
19 | const PUPPETEER_VERSION = require('../../../package.json').version;
20 |
21 | module.exports = function(sources) {
22 | const messages = [];
23 | let commands = [];
24 | for (const source of sources) {
25 | const text = source.text();
26 | const commandStartRegex = //ig;
27 | const commandEndRegex = //ig;
28 | let start;
29 |
30 | while (start = commandStartRegex.exec(text)) { // eslint-disable-line no-cond-assign
31 | commandEndRegex.lastIndex = commandStartRegex.lastIndex;
32 | const end = commandEndRegex.exec(text);
33 | if (!end) {
34 | messages.push(Message.error(`Failed to find 'gen:stop' for comamnd ${start[0]}`));
35 | break;
36 | }
37 | const name = start[1];
38 | const arg = start[2];
39 | const from = commandStartRegex.lastIndex;
40 | const to = end.index;
41 | commandStartRegex.lastIndex = commandEndRegex.lastIndex;
42 | commands.push({name, arg, from, to, source});
43 | }
44 | }
45 |
46 | commands = validateCommands(commands, messages);
47 |
48 | const changedSources = new Set();
49 | // Iterate commands in reverse order so that edits don't conflict.
50 | commands.sort((a, b) => b.from - a.from);
51 | for (const command of commands) {
52 | let newText = command.source.text();
53 | if (command.name === 'version')
54 | newText = replaceInText(newText, command.from, command.to, PUPPETEER_VERSION);
55 | if (command.source.setText(newText))
56 | changedSources.add(command.source);
57 | }
58 | for (const source of changedSources)
59 | messages.push(Message.warning(`GEN: updated ${source.projectPath()}`));
60 | return messages;
61 | };
62 |
63 | /**
64 | * @param {!Array} commands
65 | * @param {!Array} outMessages
66 | * @return {!Array}
67 | */
68 | function validateCommands(commands, outMessages) {
69 | // Filter sane commands
70 | const goodCommands = commands.filter(command => {
71 | if (command.name === 'version')
72 | return check(command, !command.arg, `"gen:version" should not have argument`);
73 | check(command, false, `Unknown command: "gen:${command.name}"`);
74 | });
75 |
76 | return goodCommands;
77 |
78 | function check(command, condition, message) {
79 | if (condition)
80 | return true;
81 | outMessages.push(Message.error(`${command.source.projectPath()}: ${message}`));
82 | return false;
83 | }
84 | }
85 |
86 | /**
87 | * @param {string} text
88 | * @param {number} from
89 | * @param {number} to
90 | * @param {string} newText
91 | * @return {string}
92 | */
93 | function replaceInText(text, from, to, newText) {
94 | return text.substring(0, from) + newText + text.substring(to);
95 | }
96 |
--------------------------------------------------------------------------------
/lib/EmulationManager.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | class EmulationManager {
18 | /**
19 | * @param {!Puppeteer.Session} client
20 | */
21 | constructor(client) {
22 | this._client = client;
23 | this._emulatingMobile = false;
24 | this._injectedTouchScriptId = null;
25 | }
26 |
27 | /**
28 | * @param {!EmulationManager.Viewport} viewport
29 | * @return {Promise}
30 | */
31 | async emulateViewport(client, viewport) {
32 | const mobile = viewport.isMobile || false;
33 | const width = viewport.width;
34 | const height = viewport.height;
35 | const deviceScaleFactor = viewport.deviceScaleFactor || 1;
36 | const screenOrientation = viewport.isLandscape ? { angle: 90, type: 'landscapePrimary' } : { angle: 0, type: 'portraitPrimary' };
37 |
38 | await Promise.all([
39 | this._client.send('Emulation.setDeviceMetricsOverride', { mobile, width, height, deviceScaleFactor, screenOrientation }),
40 | this._client.send('Emulation.setTouchEmulationEnabled', {
41 | enabled: viewport.hasTouch || false,
42 | configuration: viewport.isMobile ? 'mobile' : 'desktop'
43 | })
44 | ]);
45 |
46 | let reloadNeeded = false;
47 | if (viewport.hasTouch && !this._injectedTouchScriptId) {
48 | const source = `(${injectedTouchEventsFunction})()`;
49 | this._injectedTouchScriptId = (await this._client.send('Page.addScriptToEvaluateOnNewDocument', { source })).identifier;
50 | reloadNeeded = true;
51 | } else if (!viewport.hasTouch && this._injectedTouchScriptId) {
52 | await this._client.send('Page.removeScriptToEvaluateOnNewDocument', {identifier: this._injectedTouchScriptId});
53 | this._injectedTouchScriptId = null;
54 | reloadNeeded = true;
55 | }
56 |
57 | if (this._emulatingMobile !== mobile)
58 | reloadNeeded = true;
59 | this._emulatingMobile = mobile;
60 | return reloadNeeded;
61 |
62 | function injectedTouchEventsFunction() {
63 | const touchEvents = ['ontouchstart', 'ontouchend', 'ontouchmove', 'ontouchcancel'];
64 | // @ts-ignore
65 | const recepients = [window.__proto__, document.__proto__];
66 | for (let i = 0; i < touchEvents.length; ++i) {
67 | for (let j = 0; j < recepients.length; ++j) {
68 | if (!(touchEvents[i] in recepients[j])) {
69 | Object.defineProperty(recepients[j], touchEvents[i], {
70 | value: null, writable: true, configurable: true, enumerable: true
71 | });
72 | }
73 | }
74 | }
75 | }
76 | }
77 | }
78 |
79 | /**
80 | * @typedef {Object} EmulationManager.Viewport
81 | * @property {number} width
82 | * @property {number} height
83 | * @property {number=} deviceScaleFactor
84 | * @property {boolean=} isMobile
85 | * @property {boolean=} isLandscape
86 | * @property {boolean=} hasTouch
87 | */
88 |
89 | module.exports = EmulationManager;
90 |
--------------------------------------------------------------------------------
/test/server/key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN ENCRYPTED PRIVATE KEY-----
2 | MIIJjjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIT2qHBljWwsICAggA
3 | MBQGCCqGSIb3DQMHBAh5lMoGsb1UgQSCCUg/FtxOBSUG05DteTzzkdp6FptfOy5p
4 | iirYKWUNck6EhPdZ1RPSuCvs84S+qP6Po5gFfGz9e7tdbNSueOQnMD+vbS8vXpZU
5 | 4KVafxUlKbAfSVj2ZY6SkbU8v/iXiTieUj3G046z5QYsfcvyEdS47Z+WbKAJKemv
6 | BVck4W+/gKtiukbK/94VaehC7kbbO2yp9/kYp9qqk33aB61C8xX/fILOIRdYkvut
7 | 55pHGviN5Eajabm/d83MPAzaqUdv9NiIKej2IFXs+Oz8OYRbVVHwyN93HquHmn+V
8 | WKOknJXzvS5Pa+ViGmB4o6+fpluuIyHXJUUG20z/IY5n0vaBo4jRlyYZ3J1vWTZ2
9 | 1bhLGPUPUlGt1vToY5Ejr8qxaYGxLEOOxvRL3Z1xnlCbv2tQTD3TyV+Yr7i/54s4
10 | 2h0ddnP22KDmKEA8PhxZMZG7i7fsO+yA6hbKeMnS8lGv5Yyw2H7vRm4JwgeuX7A7
11 | gPQ/zQ6Sf6fUCkOMgVD3jJZ4XK7l9GkEQMYJAoowYb+WJ+yHSUQWJLJzPnNqhAPT
12 | aqQGHAO+AjGDnSI6ANDlxUubfU4yjQPg2eVyFczS7G+BISgCrJyQP/Z3Mpzo5rdu
13 | iTVcH8Phvsdm3PBOTkbgqxeHos6tyIiH1aElwlG3hxM1QiM6D9p7b5RyjUqfP/qn
14 | r1CndzroAyvseQ3ET8mJQAqWdGd+qmw9t0Voi7sR9jcDLVw+oLSk+oD8dk6BrItI
15 | rb39DmQAJiP7sLtAZ/zUm3sRpGgytNjElzzDdmijCM1A5oJzLvRwwa4AHwicMBkW
16 | nRnvliockHhFHQnQ/QThNGTPoNOHHgWwUKIidSFAdPIK1tYJP/4te2t5Djm25jS7
17 | ITm/LR1gbUguOMlOqwrDyAQir5Jjjk2pqN30+W9AXgVk/Oq/bWC8eMRv8zsmBlUY
18 | poXBwV52ITCzpNOsywSpz1vWGs1I0WQcLbm1zrHCR3CYxPOB45XdvHtwnHn5zaLF
19 | NB42IS/zK4PYzSJrbwJMsILS51Qb88JNL4PvYMCz43dUd0W6hokX4yCnQjfeEvB1
20 | MIhYQBu/lFK3IsskgoYBeg4zbU950vKSL+oNb5/dzAoayyJ/jRg5a1xMx8tCHazW
21 | 5afYxyIE9FRThwsHQ7K8BGe/4qwMCbvDIh/Fk+tWqXiyJJdsixf6YZLXtQXPkzki
22 | azpN9plUSoWpTVY4i6wNF5IO5LExLWlemDnw1v99lnU5W3SfgEwV8DYAyScA7Qzd
23 | GJQEfZVPSSxMQSyfrAap80PVs5KZWYcJpzKl9vwwdzopA2oj2vvx40r43YFiVFHp
24 | IpG9biWgh6McKKVCT1QThtNktDS9NT4uQZofC6m+RzzbsfI2R9IelozSOrS3xRJd
25 | D1eTiIccuomOsrhnh+/VDc9iuP6LgGaKwjdNSfcryuOn3S2+vho7wGrul/GxRf5k
26 | GrT1C68y5e0C/qbxyKtJPEGcntgLADhVrPr3WiM2M4tZ/imMi9XeyKSZ3i6P0OB/
27 | QkhfNLrws03nXi3ASpk7/C9EWnnAYGwxQRht2LpEcTOpCUmmZ+6cBM4+dXMjvxqU
28 | SXR93Vm7Rwfs3MSIbtD2lGEXaIvG5mOh/9HXByBOh/UuSATeUIgEWgfW1zn0tvAw
29 | muqOyeS+ngYtoRK0NV979Kp7Pizq7ZHpoemm+C65EVvq2CU/ceRNh6DwCW5ZCHvU
30 | rJdOXqdO9Oef/2rHmLjnkwIjkXS3MJFd9wlCsSJn2SsuNmSLkwXuEkgdbjMKwjW+
31 | sKzozd6FDp80HBuNw6H+kBn8KhO9tdQmyZnS9EpwO+OLgCTuyDNBiA892aevx9zD
32 | 7WPzNlsppcMEcxudv0mvavMfUJhbQ0wo+9Rp3wRQXIquKt++5vK7EoPvUjhVO39p
33 | 1VJTfx/wnX39VF62hc+OH3rritHmsrCBwzPupDGWSLBwQcMJbmgjFojuMG02pkIM
34 | mR5HIp+Xl4fWmSa9aklVyssdSi1hK+6VRIafTPtCiyA2BfeeYQk1xEsjSai0TYqh
35 | Suw+3wKOORm0xPEV30qX08N3bCP7aqqA+dMXWjfDM7LUBzDV4+MX6WOMdunlBdIS
36 | 6q63DXon6CCLoXPun/IwUEhOZfh43UAF3qEfxJdkhH6AgPdCSYBB5AWHQDdqvrBY
37 | wkhPEXkY2viMF2hOknXQr1GAdx0EjnAaMMxDE7+q01ERdgnO0xRVOD/4DjngdU4K
38 | p935fERs5/1nWe4yq/z8lXEC2W3YdiPR3Ok+pekyzhokh5/b+eG8G0JEnnl4+M8y
39 | qqyZZTJJeDFhasOzxwc5hwq8yqOznF6dEF58FXO9pKqBXbz5NrwbdUPtp67WwNx7
40 | iTRNe9szns5U8/3S5LEg+xGvAUhwbdmWxzYHye2MAt4sObi4YFDb1RVkZzCj2eer
41 | 6hzCQi5VUB3pUqr8in1TJMXPzCrl4ZdK2KoHtNA1gKT7midLzsjP+rXsgKtzcGAO
42 | IcNOkbv8KqzYr2OtcEW+v9z6vgSpKImQ/n+6/WLRGWh9pLCZ+onD06OLcsm55H4y
43 | zNacau+nT+Mf9XQTErFjj2+orjF3p2u3tY9vpoLJN653mMTNzexVZi+k9NgZz7eL
44 | 4m8R3ai0ZeGQezo9rGHQ6ulqlueN0qXGKI07G4VKxM35OZeOyPPnrF95FQzMY5Ox
45 | GkQr1hR0rj+oQJTFWIfy9ffZEBzUqC8fYYvOPJO32I+fe2tvBUtpUe3w7dYHVs3a
46 | lqi/+Wwk0QLE4ItR5f0sZsoIeHkDeXoKYrCOOPl2bnIQA3UT/rszGl6YDY8pU/7W
47 | fziO/G5BzGe7LU7o26ykzkmDF5alUaNdfVnnQcm/iRy8YOsjfKQmqbFfFCKOlRMo
48 | RdTStOcsEzddttI57YlBHv5Gh1GfMjd5acgeQfStSMwHcahwY2Y36WcR4GE4Id/5
49 | ycaikCt+zYEVKUoO+JEZxvzFQIPVhT3oBOjSFj/EeMLmnGMjnohdgEmh2T7+0Ko5
50 | oAMzWW/XNeJlL1DAPPEpya66oSvwZvxUkGDLcIGYWgzSKz0JO8zn6wDTOU7O2YeF
51 | Gm8YymS0oqnK1wE6HJ1YSk2ers6kgZij3kVAnGlbI88YLeMJvteO2oL7GqfEQdSA
52 | HskQklLZUT2Y+m7oinVyCcdbKRGI9frnfm6j3TVkrNHjcc7aKhM+XT5ZNKxT3ftt
53 | 4Ig=
54 | -----END ENCRYPTED PRIVATE KEY-----
55 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "root": true,
3 |
4 | "env": {
5 | "node": true,
6 | "es6": true
7 | },
8 |
9 | "parserOptions": {
10 | "ecmaVersion": 8
11 | },
12 |
13 | /**
14 | * ESLint rules
15 | *
16 | * All available rules: http://eslint.org/docs/rules/
17 | *
18 | * Rules take the following form:
19 | * "rule-name", [severity, { opts }]
20 | * Severity: 2 == error, 1 == warning, 0 == off.
21 | */
22 | "rules": {
23 | /**
24 | * Enforced rules
25 | */
26 |
27 |
28 | // syntax preferences
29 | "quotes": [2, "single", {
30 | "avoidEscape": true,
31 | "allowTemplateLiterals": true
32 | }],
33 | "semi": 2,
34 | "no-extra-semi": 2,
35 | "comma-style": [2, "last"],
36 | "wrap-iife": [2, "inside"],
37 | "spaced-comment": [2, "always", {
38 | "markers": ["*"]
39 | }],
40 | "eqeqeq": [2],
41 | "arrow-body-style": [2, "as-needed"],
42 | "accessor-pairs": [2, {
43 | "getWithoutSet": false,
44 | "setWithoutGet": false
45 | }],
46 | "brace-style": [2, "1tbs", {"allowSingleLine": true}],
47 | "curly": [2, "multi-or-nest", "consistent"],
48 | "new-parens": 2,
49 | "func-call-spacing": 2,
50 | "arrow-parens": [2, "as-needed"],
51 | "prefer-const": 2,
52 | "quote-props": [2, "consistent"],
53 |
54 | // anti-patterns
55 | "no-var": 2,
56 | "no-with": 2,
57 | "no-multi-str": 2,
58 | "no-caller": 2,
59 | "no-implied-eval": 2,
60 | "no-labels": 2,
61 | "no-new-object": 2,
62 | "no-octal-escape": 2,
63 | "no-self-compare": 2,
64 | "no-shadow-restricted-names": 2,
65 | "no-cond-assign": 2,
66 | "no-debugger": 2,
67 | "no-dupe-keys": 2,
68 | "no-duplicate-case": 2,
69 | "no-empty-character-class": 2,
70 | "no-unreachable": 2,
71 | "no-unsafe-negation": 2,
72 | "radix": 2,
73 | "valid-typeof": 2,
74 | "no-unused-vars": [2, { "args": "none", "vars": "local" }],
75 | "no-implicit-globals": [2],
76 |
77 | // es2015 features
78 | "require-yield": 2,
79 | "template-curly-spacing": [2, "never"],
80 |
81 | // spacing details
82 | "space-infix-ops": 2,
83 | "space-in-parens": [2, "never"],
84 | "space-before-function-paren": [2, "never"],
85 | "no-whitespace-before-property": 2,
86 | "keyword-spacing": [2, {
87 | "overrides": {
88 | "if": {"after": true},
89 | "else": {"after": true},
90 | "for": {"after": true},
91 | "while": {"after": true},
92 | "do": {"after": true},
93 | "switch": {"after": true},
94 | "return": {"after": true}
95 | }
96 | }],
97 | "arrow-spacing": [2, {
98 | "after": true,
99 | "before": true
100 | }],
101 |
102 | // file whitespace
103 | "no-multiple-empty-lines": [2, {"max": 2}],
104 | "no-mixed-spaces-and-tabs": 2,
105 | "no-trailing-spaces": 2,
106 | "linebreak-style": [ process.platform === "win32" ? 0 : 2, "unix" ],
107 | "indent": [2, 2, { "SwitchCase": 1, "CallExpression": {"arguments": 2}, "MemberExpression": 2 }],
108 | "key-spacing": [2, {
109 | "beforeColon": false
110 | }]
111 | }
112 | };
113 |
--------------------------------------------------------------------------------
/utils/node6-transform/test/test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | const transformAsyncFunctions = require('../TransformAsyncFunctions');
17 |
18 | describe('TransformAsyncFunctions', function() {
19 | it('should convert a function expression', function(done) {
20 | const input = `(async function(){ return 123 })()`;
21 | const output = eval(transformAsyncFunctions(input));
22 | expect(output instanceof Promise).toBe(true);
23 | output.then(result => expect(result).toBe(123)).then(done);
24 | });
25 | it('should convert an arrow function', function(done) {
26 | const input = `(async () => 123)()`;
27 | const output = eval(transformAsyncFunctions(input));
28 | expect(output instanceof Promise).toBe(true);
29 | output.then(result => expect(result).toBe(123)).then(done);
30 | });
31 | it('should convert an arrow function with curly braces', function(done) {
32 | const input = `(async () => { return 123 })()`;
33 | const output = eval(transformAsyncFunctions(input));
34 | expect(output instanceof Promise).toBe(true);
35 | output.then(result => expect(result).toBe(123)).then(done);
36 | });
37 | it('should convert a function declaration', function(done) {
38 | const input = `async function f(){ return 123; } f();`;
39 | const output = eval(transformAsyncFunctions(input));
40 | expect(output instanceof Promise).toBe(true);
41 | output.then(result => expect(result).toBe(123)).then(done);
42 | });
43 | it('should convert await', function(done) {
44 | const input = `async function f(){ return 23 + await Promise.resolve(100); } f();`;
45 | const output = eval(transformAsyncFunctions(input));
46 | expect(output instanceof Promise).toBe(true);
47 | output.then(result => expect(result).toBe(123)).then(done);
48 | });
49 | it('should convert method', function(done) {
50 | const input = `class X{async f() { return 123 }} (new X()).f();`;
51 | const output = eval(transformAsyncFunctions(input));
52 | expect(output instanceof Promise).toBe(true);
53 | output.then(result => expect(result).toBe(123)).then(done);
54 | });
55 | it('should pass arguments', function(done) {
56 | const input = `(async function(a, b){ return await a + await b })(Promise.resolve(100), 23)`;
57 | const output = eval(transformAsyncFunctions(input));
58 | expect(output instanceof Promise).toBe(true);
59 | output.then(result => expect(result).toBe(123)).then(done);
60 | });
61 | it('should still work across eval', function(done) {
62 | const input = `var str = (async function(){ return 123; }).toString(); eval('(' + str + ')')();`;
63 | const output = eval(transformAsyncFunctions(input));
64 | expect(output instanceof Promise).toBe(true);
65 | output.then(result => expect(result).toBe(123)).then(done);
66 | });
67 | it('should work with double await', function(done) {
68 | const input = `async function f(){ return 23 + await Promise.resolve(50 + await Promise.resolve(50)); } f();`;
69 | const output = eval(transformAsyncFunctions(input));
70 | expect(output instanceof Promise).toBe(true);
71 | output.then(result => expect(result).toBe(123)).then(done);
72 | });
73 | it('should work paren around arrow function', function(done) {
74 | const input = `(async x => ( 123))()`;
75 | const output = eval(transformAsyncFunctions(input));
76 | expect(output instanceof Promise).toBe(true);
77 | output.then(result => expect(result).toBe(123)).then(done);
78 | });
79 | });
--------------------------------------------------------------------------------
/lib/NavigatorWatcher.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | const {helper} = require('./helper');
18 |
19 | class NavigatorWatcher {
20 | /**
21 | * @param {!Puppeteer.Session} client
22 | * @param {string} frameId
23 | * @param {boolean} ignoreHTTPSErrors
24 | * @param {!Object=} options
25 | */
26 | constructor(client, frameId, ignoreHTTPSErrors, options = {}) {
27 | console.assert(options.networkIdleTimeout === undefined, 'ERROR: networkIdleTimeout option is no longer supported.');
28 | console.assert(options.networkIdleInflight === undefined, 'ERROR: networkIdleInflight option is no longer supported.');
29 | console.assert(options.waitUntil !== 'networkidle', 'ERROR: "networkidle" option is no longer supported. Use "networkidle2" instead');
30 | this._client = client;
31 | this._frameId = frameId;
32 | this._ignoreHTTPSErrors = ignoreHTTPSErrors;
33 | this._timeout = typeof options.timeout === 'number' ? options.timeout : 30000;
34 | let waitUntil = ['load'];
35 | if (Array.isArray(options.waitUntil))
36 | waitUntil = options.waitUntil.slice();
37 | else if (typeof options.waitUntil === 'string')
38 | waitUntil = [options.waitUntil];
39 | for (const value of waitUntil) {
40 | const isAllowedValue = value === 'networkidle0' || value === 'networkidle2' || value === 'load' || value === 'domcontentloaded';
41 | console.assert(isAllowedValue, 'Unknown value for options.waitUntil: ' + value);
42 | }
43 | this._pendingEvents = new Set(waitUntil);
44 | }
45 |
46 | /**
47 | * @return {!Promise}
48 | */
49 | async waitForNavigation() {
50 | this._eventListeners = [];
51 |
52 | const navigationPromises = [];
53 | if (this._timeout) {
54 | const watchdog = new Promise(fulfill => this._maximumTimer = setTimeout(fulfill, this._timeout))
55 | .then(() => 'Navigation Timeout Exceeded: ' + this._timeout + 'ms exceeded');
56 | navigationPromises.push(watchdog);
57 | }
58 |
59 | if (!this._ignoreHTTPSErrors) {
60 | const certificateError = new Promise(fulfill => {
61 | this._eventListeners.push(helper.addEventListener(this._client, 'Security.certificateError', fulfill));
62 | }).then(error => 'SSL Certificate error: ' + error.errorType);
63 | navigationPromises.push(certificateError);
64 | }
65 |
66 | this._eventListeners.push(helper.addEventListener(this._client, 'Page.lifecycleEvent', this._onLifecycleEvent.bind(this)));
67 | const pendingEventsFired = new Promise(fulfill => this._pendingEventsCallback = fulfill);
68 | navigationPromises.push(pendingEventsFired);
69 |
70 | const error = await Promise.race(navigationPromises);
71 | this._cleanup();
72 | return error ? new Error(error) : null;
73 | }
74 |
75 | /**
76 | * @param {!{frameId: string, name: string}} event
77 | */
78 | _onLifecycleEvent(event) {
79 | if (event.frameId !== this._frameId)
80 | return;
81 | const pptrName = protocolLifecycleToPuppeteer[event.name];
82 | if (!pptrName)
83 | return;
84 | this._pendingEvents.delete(pptrName);
85 | if (this._pendingEvents.size === 0)
86 | this._pendingEventsCallback();
87 | }
88 |
89 | cancel() {
90 | this._cleanup();
91 | }
92 |
93 | _cleanup() {
94 | helper.removeEventListeners(this._eventListeners);
95 | clearTimeout(this._maximumTimer);
96 | }
97 | }
98 |
99 | const protocolLifecycleToPuppeteer = {
100 | 'load': 'load',
101 | 'DOMContentLoaded': 'domcontentloaded',
102 | 'networkIdle': 'networkidle0',
103 | 'networkAlmostIdle': 'networkidle2',
104 | };
105 |
106 | module.exports = NavigatorWatcher;
107 |
--------------------------------------------------------------------------------
/utils/doclint/check_public_api/test/test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | const fs = require('fs');
18 | const rm = require('rimraf').sync;
19 | const path = require('path');
20 | const puppeteer = require('../../../..');
21 | const checkPublicAPI = require('..');
22 | const SourceFactory = require('../../SourceFactory');
23 | const mdBuilder = require('../MDBuilder');
24 | const jsBuilder = require('../JSBuilder');
25 | const GoldenUtils = require('../../../../test/golden-utils');
26 |
27 | const OUTPUT_DIR = path.join(__dirname, 'output');
28 | const GOLDEN_DIR = path.join(__dirname, 'golden');
29 |
30 | let browser;
31 | let page;
32 | let specName;
33 |
34 | jasmine.getEnv().addReporter({
35 | specStarted: result => specName = result.description
36 | });
37 |
38 | beforeAll(SX(async function() {
39 | browser = await puppeteer.launch({args: ['--no-sandbox']});
40 | page = await browser.newPage();
41 | if (fs.existsSync(OUTPUT_DIR))
42 | rm(OUTPUT_DIR);
43 | }));
44 |
45 | afterAll(SX(async function() {
46 | await browser.close();
47 | }));
48 |
49 | describe('checkPublicAPI', function() {
50 | it('diff-classes', SX(testLint));
51 | it('diff-methods', SX(testLint));
52 | it('diff-properties', SX(testLint));
53 | it('diff-arguments', SX(testLint));
54 | it('diff-events', SX(testLint));
55 | it('check-duplicates', SX(testLint));
56 | it('check-sorting', SX(testLint));
57 | it('check-returns', SX(testLint));
58 | it('js-builder-common', SX(testJSBuilder));
59 | it('js-builder-inheritance', SX(testJSBuilder));
60 | it('md-builder-common', SX(testMDBuilder));
61 | });
62 |
63 | async function testLint() {
64 | const dirPath = path.join(__dirname, specName);
65 | GoldenUtils.addMatchers(jasmine, dirPath, dirPath);
66 | const factory = new SourceFactory();
67 | const mdSources = await factory.readdir(dirPath, '.md');
68 | const jsSources = await factory.readdir(dirPath, '.js');
69 | const messages = await checkPublicAPI(page, mdSources, jsSources);
70 | const errors = messages.map(message => message.text);
71 | expect(errors.join('\n')).toBeGolden('result.txt');
72 | }
73 |
74 | async function testMDBuilder() {
75 | const dirPath = path.join(__dirname, specName);
76 | GoldenUtils.addMatchers(jasmine, dirPath, dirPath);
77 | const factory = new SourceFactory();
78 | const sources = await factory.readdir(dirPath, '.md');
79 | const {documentation} = await mdBuilder(page, sources);
80 | expect(serialize(documentation)).toBeGolden('result.txt');
81 | }
82 |
83 | async function testJSBuilder() {
84 | const dirPath = path.join(__dirname, specName);
85 | GoldenUtils.addMatchers(jasmine, dirPath, dirPath);
86 | const factory = new SourceFactory();
87 | const sources = await factory.readdir(dirPath, '.js');
88 | const {documentation} = await jsBuilder(sources);
89 | expect(serialize(documentation)).toBeGolden('result.txt');
90 | }
91 |
92 | function serialize(doc) {
93 | const result = {classes: []};
94 | for (let cls of doc.classesArray) {
95 | const classJSON = {
96 | name: cls.name,
97 | members: []
98 | };
99 | result.classes.push(classJSON);
100 | for (let member of cls.membersArray) {
101 | classJSON.members.push({
102 | name: member.name,
103 | type: member.type,
104 | hasReturn: member.hasReturn,
105 | async: member.async,
106 | args: member.argsArray.map(arg => arg.name)
107 | });
108 | }
109 | }
110 | return JSON.stringify(result, null, 2);
111 | }
112 |
113 | // Since Jasmine doesn't like async functions, they should be wrapped
114 | // in a SX function.
115 | function SX(fun) {
116 | return done => Promise.resolve(fun()).then(done).catch(done.fail);
117 | }
118 |
--------------------------------------------------------------------------------
/utils/node6-transform/TransformAsyncFunctions.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | const esprima = require('esprima');
18 | const ESTreeWalker = require('../ESTreeWalker');
19 |
20 | // This is converted from Babel's "transform-async-to-generator"
21 | // https://babeljs.io/docs/plugins/transform-async-to-generator/
22 | const asyncToGenerator = fn => {
23 | const gen = fn.call(this);
24 | return new Promise((resolve, reject) => {
25 | function step(key, arg) {
26 | let info, value;
27 | try {
28 | info = gen[key](arg);
29 | value = info.value;
30 | } catch (error) {
31 | reject(error);
32 | return;
33 | }
34 | if (info.done) {
35 | resolve(value);
36 | } else {
37 | return Promise.resolve(value).then(
38 | value => {
39 | step('next', value);
40 | },
41 | err => {
42 | step('throw', err);
43 | });
44 | }
45 | }
46 | return step('next');
47 | });
48 | };
49 |
50 | /**
51 | * @param {string} text
52 | * @return {string}
53 | */
54 | function transformAsyncFunctions(text) {
55 | const edits = [];
56 |
57 | const ast = esprima.parseScript(text, {range: true, tolerant: true});
58 | const walker = new ESTreeWalker(node => {
59 | if (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' || node.type === 'ArrowFunctionExpression')
60 | onFunction(node);
61 | else if (node.type === 'AwaitExpression')
62 | onAwait(node);
63 | });
64 | walker.walk(ast);
65 |
66 | edits.sort((a, b) => b.from - a.from);
67 | for (const {replacement, from, to} of edits)
68 | text = text.substring(0, from) + replacement + text.substring(to);
69 |
70 | return text;
71 |
72 | /**
73 | * @param {ESTree.Node} node
74 | */
75 | function onFunction(node) {
76 | if (!node.async) return;
77 |
78 | let range;
79 | if (node.parent.type === 'MethodDefinition')
80 | range = node.parent.range;
81 | else
82 | range = node.range;
83 | const index = text.substring(range[0], range[1]).indexOf('async') + range[0];
84 | insertText(index, index + 'async'.length, '/* async */');
85 |
86 | let before = `{return (${asyncToGenerator.toString()})(function*()`;
87 | let after = `);}`;
88 | if (node.body.type !== 'BlockStatement') {
89 | before += `{ return `;
90 | after = `; }` + after;
91 |
92 | // Remove parentheses that might wrap an arrow function
93 | const beforeBody = text.substring(node.range[0], node.body.range[0]);
94 | if (/\(\s*$/.test(beforeBody)) {
95 | const afterBody = text.substring(node.body.range[1], node.range[1]);
96 | const openParen = node.range[0] + beforeBody.lastIndexOf('(');
97 | insertText(openParen, openParen + 1, ' ');
98 | const closeParen = node.body.range[1] + afterBody.indexOf(')');
99 | insertText(closeParen, closeParen + 1, ' ');
100 | }
101 | }
102 |
103 |
104 | insertText(node.body.range[0], node.body.range[0], before);
105 | insertText(node.body.range[1], node.body.range[1], after);
106 | }
107 |
108 | /**
109 | * @param {ESTree.Node} node
110 | */
111 | function onAwait(node) {
112 | const index = text.substring(node.range[0], node.range[1]).indexOf('await') + node.range[0];
113 | insertText(index, index + 'await'.length, '(yield');
114 | insertText(node.range[1], node.range[1], ')');
115 | }
116 |
117 | /**
118 | * @param {number} from
119 | * @param {number} to
120 | * @param {string} replacement
121 | */
122 | function insertText(from, to, replacement) {
123 | edits.push({from, to, replacement});
124 | }
125 | }
126 |
127 | module.exports = transformAsyncFunctions;
--------------------------------------------------------------------------------
/utils/doclint/SourceFactory.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | const path = require('path');
18 | const fs = require('fs');
19 |
20 | const readFileAsync = promisify(fs.readFile);
21 | const readdirAsync = promisify(fs.readdir);
22 | const writeFileAsync = promisify(fs.writeFile);
23 |
24 | const PROJECT_DIR = path.join(__dirname, '..', '..');
25 |
26 | class Source {
27 | /**
28 | * @param {string} filePath
29 | * @param {string} text
30 | */
31 | constructor(filePath, text) {
32 | this._filePath = filePath;
33 | this._projectPath = path.relative(PROJECT_DIR, filePath);
34 | this._name = path.basename(filePath);
35 | this._text = text;
36 | this._hasUpdatedText = false;
37 | }
38 |
39 | /**
40 | * @return {string}
41 | */
42 | filePath() {
43 | return this._filePath;
44 | }
45 |
46 | /**
47 | * @return {string}
48 | */
49 | projectPath() {
50 | return this._projectPath;
51 | }
52 |
53 | /**
54 | * @return {string}
55 | */
56 | name() {
57 | return this._name;
58 | }
59 |
60 | /**
61 | * @param {string} text
62 | * @return {boolean}
63 | */
64 | setText(text) {
65 | if (text === this._text)
66 | return false;
67 | this._hasUpdatedText = true;
68 | this._text = text;
69 | return true;
70 | }
71 |
72 | /**
73 | * @return {string}
74 | */
75 | text() {
76 | return this._text;
77 | }
78 |
79 | /**
80 | * @return {boolean}
81 | */
82 | hasUpdatedText() {
83 | return this._hasUpdatedText;
84 | }
85 | }
86 |
87 | class SourceFactory {
88 | constructor() {
89 | this._sources = new Map();
90 | }
91 |
92 | /**
93 | * @return {!Array}
94 | */
95 | sources() {
96 | return Array.from(this._sources.values());
97 | }
98 |
99 | /**
100 | * @return {!Promise}
101 | */
102 | async saveChangedSources() {
103 | const changedSources = Array.from(this._sources.values()).filter(source => source.hasUpdatedText());
104 | if (!changedSources.length)
105 | return false;
106 | await Promise.all(changedSources.map(source => writeFileAsync(source.filePath(), source.text())));
107 | return true;
108 | }
109 |
110 | /**
111 | * @param {string} filePath
112 | * @return {!Promise}
113 | */
114 | async readFile(filePath) {
115 | filePath = path.resolve(filePath);
116 | let source = this._sources.get(filePath);
117 | if (!source) {
118 | const text = await readFileAsync(filePath, {encoding: 'utf8'});
119 | source = new Source(filePath, text);
120 | this._sources.set(filePath, source);
121 | }
122 | return source;
123 | }
124 |
125 | /**
126 | * @param {string} dirPath
127 | * @param {string=} extension
128 | * @return {!Promise>}
129 | */
130 | async readdir(dirPath, extension = '') {
131 | const fileNames = await readdirAsync(dirPath);
132 | const filePaths = fileNames.filter(fileName => fileName.endsWith(extension)).map(fileName => path.join(dirPath, fileName));
133 | return Promise.all(filePaths.map(filePath => this.readFile(filePath)));
134 | }
135 |
136 | /**
137 | * @param {string} filePath
138 | * @param {string} text
139 | * @return {!Source}
140 | */
141 | createForTest(filePath, text) {
142 | return new Source(filePath, text);
143 | }
144 | }
145 |
146 | /**
147 | * @param {function(?)} nodeFunction
148 | * @return {function(?):!Promise>}
149 | */
150 | function promisify(nodeFunction) {
151 | /**
152 | * @param {!Array>} options
153 | * @return {!Promise>}
154 | */
155 | return function(...options) {
156 | return new Promise(function(fulfill, reject) {
157 | options.push(callback);
158 | nodeFunction.call(null, ...options);
159 | function callback(err, result) {
160 | if (err)
161 | reject(err);
162 | else
163 | fulfill(result);
164 | }
165 | });
166 | };
167 | }
168 |
169 | module.exports = SourceFactory;
170 |
--------------------------------------------------------------------------------
/utils/ESTreeWalker.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2014 The Chromium Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | /**
6 | * @unrestricted
7 | */
8 | class ESTreeWalker {
9 | /**
10 | * @param {function(!ESTree.Node):(!Object|undefined)} beforeVisit
11 | * @param {function(!ESTree.Node)=} afterVisit
12 | */
13 | constructor(beforeVisit, afterVisit) {
14 | this._beforeVisit = beforeVisit;
15 | this._afterVisit = afterVisit || new Function();
16 | }
17 |
18 | /**
19 | * @param {!ESTree.Node} ast
20 | */
21 | walk(ast) {
22 | this._innerWalk(ast, null);
23 | }
24 |
25 | /**
26 | * @param {!ESTree.Node} node
27 | * @param {?ESTree.Node} parent
28 | */
29 | _innerWalk(node, parent) {
30 | if (!node)
31 | return;
32 | node.parent = parent;
33 |
34 | if (this._beforeVisit.call(null, node) === ESTreeWalker.SkipSubtree) {
35 | this._afterVisit.call(null, node);
36 | return;
37 | }
38 |
39 | const walkOrder = ESTreeWalker._walkOrder[node.type];
40 | if (!walkOrder)
41 | return;
42 |
43 | if (node.type === 'TemplateLiteral') {
44 | const templateLiteral = /** @type {!ESTree.TemplateLiteralNode} */ (node);
45 | const expressionsLength = templateLiteral.expressions.length;
46 | for (let i = 0; i < expressionsLength; ++i) {
47 | this._innerWalk(templateLiteral.quasis[i], templateLiteral);
48 | this._innerWalk(templateLiteral.expressions[i], templateLiteral);
49 | }
50 | this._innerWalk(templateLiteral.quasis[expressionsLength], templateLiteral);
51 | } else {
52 | for (let i = 0; i < walkOrder.length; ++i) {
53 | const entity = node[walkOrder[i]];
54 | if (Array.isArray(entity))
55 | this._walkArray(entity, node);
56 | else
57 | this._innerWalk(entity, node);
58 | }
59 | }
60 |
61 | this._afterVisit.call(null, node);
62 | }
63 |
64 | /**
65 | * @param {!Array.} nodeArray
66 | * @param {?ESTree.Node} parentNode
67 | */
68 | _walkArray(nodeArray, parentNode) {
69 | for (let i = 0; i < nodeArray.length; ++i)
70 | this._innerWalk(nodeArray[i], parentNode);
71 | }
72 | }
73 |
74 | /** @typedef {!Object} ESTreeWalker.SkipSubtree */
75 | ESTreeWalker.SkipSubtree = {};
76 |
77 | /** @enum {!Array.} */
78 | ESTreeWalker._walkOrder = {
79 | 'AwaitExpression': ['argument'],
80 | 'ArrayExpression': ['elements'],
81 | 'ArrowFunctionExpression': ['params', 'body'],
82 | 'AssignmentExpression': ['left', 'right'],
83 | 'BinaryExpression': ['left', 'right'],
84 | 'BlockStatement': ['body'],
85 | 'BreakStatement': ['label'],
86 | 'CallExpression': ['callee', 'arguments'],
87 | 'CatchClause': ['param', 'body'],
88 | 'ClassBody': ['body'],
89 | 'ClassDeclaration': ['id', 'superClass', 'body'],
90 | 'ClassExpression': ['id', 'superClass', 'body'],
91 | 'ConditionalExpression': ['test', 'consequent', 'alternate'],
92 | 'ContinueStatement': ['label'],
93 | 'DebuggerStatement': [],
94 | 'DoWhileStatement': ['body', 'test'],
95 | 'EmptyStatement': [],
96 | 'ExpressionStatement': ['expression'],
97 | 'ForInStatement': ['left', 'right', 'body'],
98 | 'ForOfStatement': ['left', 'right', 'body'],
99 | 'ForStatement': ['init', 'test', 'update', 'body'],
100 | 'FunctionDeclaration': ['id', 'params', 'body'],
101 | 'FunctionExpression': ['id', 'params', 'body'],
102 | 'Identifier': [],
103 | 'IfStatement': ['test', 'consequent', 'alternate'],
104 | 'LabeledStatement': ['label', 'body'],
105 | 'Literal': [],
106 | 'LogicalExpression': ['left', 'right'],
107 | 'MemberExpression': ['object', 'property'],
108 | 'MethodDefinition': ['key', 'value'],
109 | 'NewExpression': ['callee', 'arguments'],
110 | 'ObjectExpression': ['properties'],
111 | 'ObjectPattern': ['properties'],
112 | 'ParenthesizedExpression': ['expression'],
113 | 'Program': ['body'],
114 | 'Property': ['key', 'value'],
115 | 'ReturnStatement': ['argument'],
116 | 'SequenceExpression': ['expressions'],
117 | 'Super': [],
118 | 'SwitchCase': ['test', 'consequent'],
119 | 'SwitchStatement': ['discriminant', 'cases'],
120 | 'TaggedTemplateExpression': ['tag', 'quasi'],
121 | 'TemplateElement': [],
122 | 'TemplateLiteral': ['quasis', 'expressions'],
123 | 'ThisExpression': [],
124 | 'ThrowStatement': ['argument'],
125 | 'TryStatement': ['block', 'handler', 'finalizer'],
126 | 'UnaryExpression': ['argument'],
127 | 'UpdateExpression': ['argument'],
128 | 'VariableDeclaration': ['declarations'],
129 | 'VariableDeclarator': ['id', 'init'],
130 | 'WhileStatement': ['test', 'body'],
131 | 'WithStatement': ['object', 'body'],
132 | 'YieldExpression': ['argument']
133 | };
134 |
135 | module.exports = ESTreeWalker;
136 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | First of all, thank you for your interest in Puppeteer!
4 | We'd love to accept your patches and contributions!
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution,
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Getting setup
19 |
20 | 1. Clone this repository
21 | ```bash
22 | git clone https://github.com/GoogleChrome/puppeteer
23 | cd puppeteer
24 | ```
25 | 2. Install dependencies
26 | ```bash
27 | yarn # or 'npm install'
28 | ```
29 |
30 | ## Code reviews
31 |
32 | All submissions, including submissions by project members, require review. We
33 | use GitHub pull requests for this purpose. Consult
34 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
35 | information on using pull requests.
36 |
37 | ## Code Style
38 |
39 | - coding style is fully defined in [.eslintrc](https://github.com/GoogleChrome/puppeteer/blob/master/.eslintrc.js)
40 | - code should be annotated with [closure annotations](https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler)
41 | - comments should be generally avoided. If the code would not be understood without comments, consider re-writing the code to make it self-explanatory
42 |
43 | To run code linter, use:
44 | ```
45 | npm run lint
46 | ```
47 |
48 | ## Writing Documentation
49 |
50 | All public API should have a descriptive entry in the [docs/api.md](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md). There's a [documentation linter](https://github.com/GoogleChrome/puppeteer/tree/master/utils/doclint) which makes sure documentation is aligned with the codebase.
51 |
52 | To run documentation linter, use
53 | ```
54 | npm run doc
55 | ```
56 |
57 | ## Adding New Dependencies
58 |
59 | For all dependencies (both installation and development):
60 | - **Do not add** a dependency if the desired functionality is easily implementable
61 | - if adding a dependency, it should be well-maintained and trustworthy
62 |
63 | A barrier for introducing new installation dependencies is especially high:
64 | - **do not add** installation dependency unless it's critical to project success
65 |
66 | ## Writing Tests
67 |
68 | - every feature should be accompanied by a test
69 | - every public api event/method should be accompanied by a test
70 | - tests should be *hermetic*. Tests should not depend on external services.
71 | - tests should work on all three platforms: Mac, Linux and Win. This is especially important for screenshot tests.
72 |
73 | Puppeteer tests are located in [test/test.js](https://github.com/GoogleChrome/puppeteer/blob/master/test/test.js)
74 | and are written using [Jasmine](https://jasmine.github.io/) testing framework. Despite being named 'unit', these are integration tests, making sure public API methods and events work as expected.
75 |
76 | - To run all tests:
77 | ```
78 | npm run unit
79 | ```
80 | - To filter tests by name:
81 | ```
82 | npm run unit -- --filter=waitFor
83 | ```
84 | - To run a specific test, substitute the `it` with `fit` (mnemonic rule: '*focus it*'):
85 | ```js
86 | ...
87 | // Using "fit" to run specific test
88 | fit('should work', SX(async function() {
89 | const response = await page.goto(EMPTY_PAGE);
90 | expect(response.ok).toBe(true);
91 | }))
92 | ```
93 | - To disable a specific test, substitute the `it` with `xit` (mnemonic rule: '*cross it*'):
94 | ```js
95 | ...
96 | // Using "xit" to skip specific test
97 | xit('should work', SX(async function() {
98 | const response = await page.goto(EMPTY_PAGE);
99 | expect(response.ok).toBe(true);
100 | }))
101 | ```
102 | - To run tests in non-headless mode:
103 | ```
104 | HEADLESS=false npm run unit
105 | ```
106 | - To run tests with custom Chromium executable:
107 | ```
108 | CHROME= npm run unit
109 | ```
110 | - To run tests in slow-mode:
111 | ```
112 | HEADLESS=false SLOW_MO=500 npm run unit
113 | ```
114 | - To debug a test, "focus" a test first and then run:
115 | ```
116 | npm run debug-unit
117 | ```
118 |
119 | ## Public API Coverage
120 |
121 | Every public API method or event should be called at least once in tests. To ensure this, there's a coverage command which tracks calls to public API and reports back if some methods/events were not called.
122 |
123 | Run coverage:
124 |
125 | ```
126 | npm run coverage
127 | ```
128 |
129 | ## Debugging Puppeteer
130 | See [Debugging Tips](README.md#debugging-tips) in the readme
131 |
132 |
--------------------------------------------------------------------------------
/docs/troubleshooting.md:
--------------------------------------------------------------------------------
1 | # Troubleshooting
2 |
3 | ## Chrome headless doesn't launch
4 |
5 | - Make sure all the necessary dependencies are installed:
6 |
7 |
8 | Debian (e.g. Ubuntu) Dependencies
9 |
10 | ```
11 | gconf-service
12 | libasound2
13 | libatk1.0-0
14 | libc6
15 | libcairo2
16 | libcups2
17 | libdbus-1-3
18 | libexpat1
19 | libfontconfig1
20 | libgcc1
21 | libgconf-2-4
22 | libgdk-pixbuf2.0-0
23 | libglib2.0-0
24 | libgtk-3-0
25 | libnspr4
26 | libpango-1.0-0
27 | libpangocairo-1.0-0
28 | libstdc++6
29 | libx11-6
30 | libx11-xcb1
31 | libxcb1
32 | libxcomposite1
33 | libxcursor1
34 | libxdamage1
35 | libxext6
36 | libxfixes3
37 | libxi6
38 | libxrandr2
39 | libxrender1
40 | libxss1
41 | libxtst6
42 | ca-certificates
43 | fonts-liberation
44 | libappindicator1
45 | libnss3
46 | lsb-release
47 | xdg-utils
48 | wget
49 | ```
50 |
51 |
52 |
53 | CentOS Dependencies
54 |
55 | ```
56 | pango.x86_64
57 | libXcomposite.x86_64
58 | libXcursor.x86_64
59 | libXdamage.x86_64
60 | libXext.x86_64
61 | libXi.x86_64
62 | libXtst.x86_64
63 | cups-libs.x86_64
64 | libXScrnSaver.x86_64
65 | libXrandr.x86_64
66 | GConf2.x86_64
67 | alsa-lib.x86_64
68 | atk.x86_64
69 | gtk3.x86_64
70 | ipa-gothic-fonts
71 | xorg-x11-fonts-100dpi
72 | xorg-x11-fonts-75dpi
73 | xorg-x11-utils
74 | xorg-x11-fonts-cyrillic
75 | xorg-x11-fonts-Type1
76 | xorg-x11-fonts-misc
77 | ```
78 |
79 |
80 | - Check out discussions:
81 | - [#290](https://github.com/GoogleChrome/puppeteer/issues/290) - Debian troubleshooting
82 | - [#391](https://github.com/GoogleChrome/puppeteer/issues/391) - CentOS troubleshooting
83 |
84 |
85 | ## Chrome Headless fails due to sandbox issues
86 |
87 | - make sure kernel version is up-to-date
88 | - read about linux sandbox here: https://chromium.googlesource.com/chromium/src/+/master/docs/linux_suid_sandbox_development.md
89 | - try running without the sandbox (**Note: running without the sandbox is not recommended due to security reasons!**)
90 | ```js
91 | const browser = await puppeteer.launch({args: ['--no-sandbox', '--disable-setuid-sandbox']});
92 | ```
93 |
94 | ## Running Puppeteer in Docker
95 |
96 | Using headless Chrome Linux to run Puppeteer in Docker container can be tricky.
97 | The bundled version Chromium that Puppeteer installs is missing the necessary
98 | shared library dependencies.
99 |
100 | To fix this, you'll need to install the latest version of Chrome dev in your
101 | Dockerfile:
102 |
103 | ```
104 | FROM node:8-slim
105 |
106 | # Install latest chrome dev package.
107 | # Note: this installs the necessary libs to make the bundled version of Chromium that Pupppeteer
108 | # installs, work.
109 | RUN apt-get update && apt-get install -y wget --no-install-recommends \
110 | && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
111 | && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
112 | && apt-get update \
113 | && apt-get install -y google-chrome-unstable \
114 | --no-install-recommends \
115 | && rm -rf /var/lib/apt/lists/* \
116 | && apt-get purge --auto-remove -y curl \
117 | && rm -rf /src/*.deb
118 |
119 | # Uncomment to skip the chromium download when installing puppeteer. If you do,
120 | # you'll need to launch puppeteer with:
121 | # browser.launch({executablePath: 'google-chrome-unstable'})
122 | # ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true
123 |
124 | # Install puppeteer so it's available in the container.
125 | RUN yarn add puppeteer
126 |
127 | # Add pptr user.
128 | RUN groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser \
129 | && mkdir -p /home/pptruser/Downloads \
130 | && chown -R pptruser:pptruser /home/pptruser \
131 | && chown -R pptruser:pptruser /node_modules
132 |
133 | # Run user as non privileged.
134 | USER pptruser
135 |
136 | CMD ["google-chrome-unstable"]
137 | ```
138 |
139 | Build the container:
140 |
141 | ```bash
142 | docker build -t puppeteer-chrome-linux .
143 | ```
144 |
145 | Run the container by passing `node -e "` as the command:
146 |
147 | ```bash
148 | docker run -i --rm --name puppeteer-chrome puppeteer-chrome-linux node -e "`cat yourscript.js`"
149 | ```
150 |
151 | There's a full example at https://github.com/ebidel/try-puppeteer that shows
152 | how to run this setup from a webserver running on App Engine Flex (Node).
153 |
154 | ## Running Puppeteer on Heroku
155 |
156 | Running Puppeteer on Heroku requires some additional dependencies that aren't included on the Linux box that Heroku spins up for you. To add the dependencies on deploy, add the Puppeteer Heroku buildpack to the list of buildpacks for your app under Settings > Buildpacks.
157 |
158 | The url for the buildpack is https://github.com/jontewks/puppeteer-heroku-buildpack
159 |
160 | When you click add buildpack, simply paste that url into the input, and click save. On the next deploy, your app will also install the dependencies that Puppeteer needs to run.
161 |
162 | If you need to render Chinese, Japanese, or Korean characters you may need to use a buildpack with additional font files like https://github.com/CoffeeAndCode/puppeteer-heroku-buildpack
163 |
--------------------------------------------------------------------------------
/test/golden-utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | const path = require('path');
17 | const fs = require('fs');
18 | const Diff = require('text-diff');
19 | const mime = require('mime');
20 | const PNG = require('pngjs').PNG;
21 | const pixelmatch = require('pixelmatch');
22 |
23 | module.exports = {
24 | addMatchers: function(jasmine, goldenPath, outputPath) {
25 | jasmine.addMatchers({
26 | toBeGolden: function(util, customEqualityTesters) {
27 | return { compare: compare.bind(null, goldenPath, outputPath) };
28 | }
29 | });
30 | },
31 | };
32 |
33 | const GoldenComparators = {
34 | 'image/png': compareImages,
35 | 'text/plain': compareText
36 | };
37 |
38 | /**
39 | * @param {?Object} actualBuffer
40 | * @param {!Buffer} expectedBuffer
41 | * @return {?{diff: (!Object:undefined), errorMessage: (string|undefined)}}
42 | */
43 | function compareImages(actualBuffer, expectedBuffer) {
44 | if (!actualBuffer || !(actualBuffer instanceof Buffer))
45 | return { errorMessage: 'Actual result should be Buffer.' };
46 |
47 | const actual = PNG.sync.read(actualBuffer);
48 | const expected = PNG.sync.read(expectedBuffer);
49 | if (expected.width !== actual.width || expected.height !== actual.height) {
50 | return {
51 | errorMessage: `Sizes differ: expected image ${expected.width}px X ${expected.height}px, but got ${actual.width}px X ${actual.height}px. `
52 | };
53 | }
54 | const diff = new PNG({width: expected.width, height: expected.height});
55 | const count = pixelmatch(expected.data, actual.data, diff.data, expected.width, expected.height, {threshold: 0.1});
56 | return count > 0 ? { diff: PNG.sync.write(diff) } : null;
57 | }
58 |
59 | /**
60 | * @param {?Object} actual
61 | * @param {!Buffer} expectedBuffer
62 | * @return {?{diff: (!Object:undefined), errorMessage: (string|undefined)}}
63 | */
64 | function compareText(actual, expectedBuffer) {
65 | if (typeof actual !== 'string')
66 | return { errorMessage: 'Actual result should be string' };
67 | const expected = expectedBuffer.toString('utf-8');
68 | if (expected === actual)
69 | return null;
70 | const diff = new Diff();
71 | const result = diff.main(expected, actual);
72 | diff.cleanupSemantic(result);
73 | let html = diff.prettyHtml(result);
74 | const diffStylePath = path.join(__dirname, 'diffstyle.css');
75 | html = `` + html;
76 | return {
77 | diff: html,
78 | diffExtension: '.html'
79 | };
80 | }
81 |
82 | /**
83 | * @param {?Object} actual
84 | * @param {string} goldenName
85 | * @return {!{pass: boolean, message: (undefined|string)}}
86 | */
87 | function compare(goldenPath, outputPath, actual, goldenName) {
88 | goldenPath = path.normalize(goldenPath);
89 | outputPath = path.normalize(outputPath);
90 | const expectedPath = path.join(goldenPath, goldenName);
91 | const actualPath = path.join(outputPath, goldenName);
92 |
93 | const messageSuffix = 'Output is saved in "' + path.basename(outputPath + '" directory');
94 |
95 | if (!fs.existsSync(expectedPath)) {
96 | ensureOutputDir();
97 | fs.writeFileSync(actualPath, actual);
98 | return {
99 | pass: false,
100 | message: goldenName + ' is missing in golden results. ' + messageSuffix
101 | };
102 | }
103 | const expected = fs.readFileSync(expectedPath);
104 | const comparator = GoldenComparators[mime.lookup(goldenName)];
105 | if (!comparator) {
106 | return {
107 | pass: false,
108 | message: 'Failed to find comparator with type ' + mime.lookup(goldenName) + ': ' + goldenName
109 | };
110 | }
111 | const result = comparator(actual, expected);
112 | if (!result)
113 | return { pass: true };
114 | ensureOutputDir();
115 | if (goldenPath === outputPath) {
116 | fs.writeFileSync(addSuffix(actualPath, '-actual'), actual);
117 | } else {
118 | fs.writeFileSync(actualPath, actual);
119 | // Copy expected to the output/ folder for convenience.
120 | fs.writeFileSync(addSuffix(actualPath, '-expected'), expected);
121 | }
122 | if (result.diff) {
123 | const diffPath = addSuffix(actualPath, '-diff', result.diffExtension);
124 | fs.writeFileSync(diffPath, result.diff);
125 | }
126 |
127 | let message = goldenName + ' mismatch!';
128 | if (result.errorMessage)
129 | message += ' ' + result.errorMessage;
130 | return {
131 | pass: false,
132 | message: message + ' ' + messageSuffix
133 | };
134 |
135 | function ensureOutputDir() {
136 | if (!fs.existsSync(outputPath))
137 | fs.mkdirSync(outputPath);
138 | }
139 | }
140 |
141 | /**
142 | * @param {string} filePath
143 | * @param {string} suffix
144 | * @param {string=} customExtension
145 | * @return {string}
146 | */
147 | function addSuffix(filePath, suffix, customExtension) {
148 | const dirname = path.dirname(filePath);
149 | const ext = path.extname(filePath);
150 | const name = path.basename(filePath, ext);
151 | return path.join(dirname, name + suffix + (customExtension || ext));
152 | }
153 |
--------------------------------------------------------------------------------
/lib/ElementHandle.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | const path = require('path');
17 | const {JSHandle} = require('./ExecutionContext');
18 | const {helper} = require('./helper');
19 |
20 | class ElementHandle extends JSHandle {
21 | /**
22 | * @param {!Puppeteer.ExecutionContext} context
23 | * @param {!Puppeteer.Session} client
24 | * @param {!Object} remoteObject
25 | * @param {!Puppeteer.Page} page
26 | */
27 | constructor(context, client, remoteObject, page) {
28 | super(context, client, remoteObject);
29 | this._client = client;
30 | this._remoteObject = remoteObject;
31 | this._page = page;
32 | this._disposed = false;
33 | }
34 |
35 | /**
36 | * @override
37 | * @return {?ElementHandle}
38 | */
39 | asElement() {
40 | return this;
41 | }
42 |
43 | async _scrollIntoViewIfNeeded() {
44 | const error = await this.executionContext().evaluate(element => {
45 | if (!element.ownerDocument.contains(element))
46 | return 'Node is detached from document';
47 | if (element.nodeType !== Node.ELEMENT_NODE)
48 | return 'Node is not of type HTMLElement';
49 | element.scrollIntoViewIfNeeded();
50 | return false;
51 | }, this);
52 | if (error)
53 | throw new Error(error);
54 | }
55 |
56 | /**
57 | * @return {!Promise<{x: number, y: number}>}
58 | */
59 | async _visibleCenter() {
60 | await this._scrollIntoViewIfNeeded();
61 | const box = await this.boundingBox();
62 | return {
63 | x: box.x + box.width / 2,
64 | y: box.y + box.height / 2
65 | };
66 | }
67 |
68 | async hover() {
69 | const {x, y} = await this._visibleCenter();
70 | await this._page.mouse.move(x, y);
71 | }
72 |
73 | /**
74 | * @param {!Object=} options
75 | */
76 | async click(options) {
77 | const {x, y} = await this._visibleCenter();
78 | await this._page.mouse.click(x, y, options);
79 | }
80 |
81 | /**
82 | * @param {!Array} filePaths
83 | * @return {!Promise}
84 | */
85 | async uploadFile(...filePaths) {
86 | const files = filePaths.map(filePath => path.resolve(filePath));
87 | const objectId = this._remoteObject.objectId;
88 | return this._client.send('DOM.setFileInputFiles', { objectId, files });
89 | }
90 |
91 | async tap() {
92 | const {x, y} = await this._visibleCenter();
93 | await this._page.touchscreen.tap(x, y);
94 | }
95 |
96 | async focus() {
97 | await this.executionContext().evaluate(element => element.focus(), this);
98 | }
99 |
100 | /**
101 | * @param {string} text
102 | * @param {{delay: (number|undefined)}=} options
103 | */
104 | async type(text, options) {
105 | await this.focus();
106 | await this._page.keyboard.type(text, options);
107 | }
108 |
109 | /**
110 | * @param {string} key
111 | * @param {!Object=} options
112 | */
113 | async press(key, options) {
114 | await this.focus();
115 | await this._page.keyboard.press(key, options);
116 | }
117 |
118 | /**
119 | * @return {!Promise<{x: number, y: number, width: number, height: number}>}
120 | */
121 | async boundingBox() {
122 | const {model} = await this._client.send('DOM.getBoxModel', {
123 | objectId: this._remoteObject.objectId
124 | });
125 | if (!model)
126 | throw new Error('Node is detached from document');
127 |
128 | const quad = model.border;
129 | const x = Math.min(quad[0], quad[2], quad[4], quad[6]);
130 | const y = Math.min(quad[1], quad[3], quad[5], quad[7]);
131 | const width = Math.max(quad[0], quad[2], quad[4], quad[6]) - x;
132 | const height = Math.max(quad[1], quad[3], quad[5], quad[7]) - y;
133 |
134 | return {x, y, width, height};
135 | }
136 |
137 | /**
138 | *
139 | * @param {!Object=} options
140 | * @returns {!Promise