├── test ├── assets │ ├── sw.js │ ├── empty.html │ ├── simple.json │ ├── file-to-upload.txt │ ├── jscoverage │ │ ├── script1.js │ │ ├── script2.js │ │ ├── unused.html │ │ ├── ranges.html │ │ ├── multiple.html │ │ ├── sourceurl.html │ │ ├── simple.html │ │ └── involved.html │ ├── frames │ │ ├── script.js │ │ ├── style.css │ │ ├── frame.html │ │ ├── two-frames.html │ │ └── nested-frames.html │ ├── csscoverage │ │ ├── stylesheet1.css │ │ ├── stylesheet2.css │ │ ├── media.html │ │ ├── Dosis-Regular.ttf │ │ ├── simple.html │ │ ├── sourceurl.html │ │ ├── unused.html │ │ ├── multiple.html │ │ ├── involved.html │ │ └── OFL.txt │ ├── injectedstyle.css │ ├── one-style.css │ ├── pptr.png │ ├── tamperable.html │ ├── injectedfile.js │ ├── digits │ │ ├── 0.png │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ └── 9.png │ ├── mobile.html │ ├── one-style.html │ ├── self-request.html │ ├── input │ │ ├── fileupload.html │ │ ├── button.html │ │ ├── textarea.html │ │ ├── scrollable.html │ │ ├── checkbox.html │ │ ├── touches.html │ │ ├── keyboard.html │ │ ├── mouse-helper.js │ │ └── select.html │ ├── error.html │ ├── playground.html │ ├── networkidle.html │ ├── shadow.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 │ ├── screenshot-element-scrolled-into-view.png │ ├── nested-frames.txt │ ├── reconnect-nested-frames.txt │ ├── jscoverage-involved.txt │ └── csscoverage-involved.txt ├── diffstyle.css ├── server │ ├── run.js │ ├── 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 │ ├── README.md │ ├── toc.js │ ├── Message.js │ ├── preprocessor │ │ ├── test.js │ │ └── index.js │ ├── cli.js │ └── SourceFactory.js ├── apply_next_version.js ├── testrunner │ ├── index.js │ ├── examples │ │ ├── timeout.js │ │ ├── hookfail.js │ │ ├── hooktimeout.js │ │ ├── unhandledpromiserejection.js │ │ └── fail.js │ ├── README.md │ ├── Multimap.js │ ├── Matchers.js │ └── Reporter.js ├── node6-transform │ ├── index.js │ ├── test │ │ └── test.js │ └── TransformAsyncFunctions.js ├── ESTreeWalker.js └── check_availability.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 └── ExecutionContext.js ├── examples ├── screenshot.js ├── screenshot-fullpage.js ├── proxy.js ├── pdf.js ├── block-images.js ├── detect-sniff.js ├── custom-event.js ├── search.js └── README.md ├── index.js ├── docs └── issue_template.md ├── .travis.yml ├── package.json ├── .eslintrc.js ├── install.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/jscoverage/script1.js: -------------------------------------------------------------------------------- 1 | console.log(3); 2 | -------------------------------------------------------------------------------- /test/assets/jscoverage/script2.js: -------------------------------------------------------------------------------- 1 | console.log(3); 2 | -------------------------------------------------------------------------------- /test/assets/frames/script.js: -------------------------------------------------------------------------------- 1 | console.log('Cheers!'); 2 | -------------------------------------------------------------------------------- /test/assets/frames/style.css: -------------------------------------------------------------------------------- 1 | div { 2 | color: blue; 3 | } 4 | -------------------------------------------------------------------------------- /test/assets/csscoverage/stylesheet1.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /test/assets/injectedstyle.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: red; 3 | } 4 | -------------------------------------------------------------------------------- /test/assets/jscoverage/unused.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /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/csscoverage/stylesheet2.css: -------------------------------------------------------------------------------- 1 | html { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | -------------------------------------------------------------------------------- /test/assets/pptr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/assets/pptr.png -------------------------------------------------------------------------------- /test/assets/tamperable.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /utils/doclint/check_public_api/test/.gitignore: -------------------------------------------------------------------------------- 1 | result-actual.txt 2 | result-diff.html 3 | -------------------------------------------------------------------------------- /test/assets/injectedfile.js: -------------------------------------------------------------------------------- 1 | window.__injected = 42; 2 | window.__injectedError = new Error('hi'); -------------------------------------------------------------------------------- /test/assets/digits/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/assets/digits/0.png -------------------------------------------------------------------------------- /test/assets/digits/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/assets/digits/1.png -------------------------------------------------------------------------------- /test/assets/digits/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/assets/digits/2.png -------------------------------------------------------------------------------- /test/assets/digits/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/assets/digits/3.png -------------------------------------------------------------------------------- /test/assets/digits/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/assets/digits/4.png -------------------------------------------------------------------------------- /test/assets/digits/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/assets/digits/5.png -------------------------------------------------------------------------------- /test/assets/digits/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/assets/digits/6.png -------------------------------------------------------------------------------- /test/assets/digits/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/assets/digits/7.png -------------------------------------------------------------------------------- /test/assets/digits/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/assets/digits/8.png -------------------------------------------------------------------------------- /test/assets/digits/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/assets/digits/9.png -------------------------------------------------------------------------------- /test/assets/jscoverage/ranges.html: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /test/assets/mobile.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/assets/one-style.html: -------------------------------------------------------------------------------- 1 | 2 |
hello, world!
3 | -------------------------------------------------------------------------------- /test/golden/grid-cell-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/golden/grid-cell-0.png -------------------------------------------------------------------------------- /test/golden/grid-cell-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/golden/grid-cell-1.png -------------------------------------------------------------------------------- /test/golden/grid-cell-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/golden/grid-cell-2.png -------------------------------------------------------------------------------- /test/golden/grid-cell-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/golden/grid-cell-3.png -------------------------------------------------------------------------------- /test/golden/transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/golden/transparent.png -------------------------------------------------------------------------------- /test/assets/jscoverage/multiple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /test/assets/jscoverage/sourceurl.html: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /test/golden/screenshot-sanity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/golden/screenshot-sanity.png -------------------------------------------------------------------------------- /utils/doclint/check_public_api/test/diff-properties/doc.md: -------------------------------------------------------------------------------- 1 | ### class: Foo 2 | 3 | #### foo.a 4 | 5 | #### foo.c 6 | -------------------------------------------------------------------------------- /test/assets/jscoverage/simple.html: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /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/oyyd/puppeteer/master/test/golden/mock-binary-response.png -------------------------------------------------------------------------------- /test/golden/screenshot-clip-rect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/golden/screenshot-clip-rect.png -------------------------------------------------------------------------------- /test/assets/csscoverage/media.html: -------------------------------------------------------------------------------- 1 | 3 |
hello, world
4 | 5 | -------------------------------------------------------------------------------- /test/golden/screenshot-clip-odd-size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/golden/screenshot-clip-odd-size.png -------------------------------------------------------------------------------- /test/golden/screenshot-grid-fullpage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/golden/screenshot-grid-fullpage.png -------------------------------------------------------------------------------- /utils/doclint/check_public_api/test/diff-events/doc.md: -------------------------------------------------------------------------------- 1 | ### class: Foo 2 | 3 | #### event: 'start' 4 | 5 | #### event: 'stop' 6 | -------------------------------------------------------------------------------- /test/assets/csscoverage/Dosis-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/assets/csscoverage/Dosis-Regular.ttf -------------------------------------------------------------------------------- /test/golden/screenshot-element-rotate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/golden/screenshot-element-rotate.png -------------------------------------------------------------------------------- /test/golden/screenshot-offscreen-clip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/golden/screenshot-offscreen-clip.png -------------------------------------------------------------------------------- /test/assets/csscoverage/simple.html: -------------------------------------------------------------------------------- 1 | 5 |
hello, world
6 | 7 | -------------------------------------------------------------------------------- /test/assets/csscoverage/sourceurl.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | -------------------------------------------------------------------------------- /test/golden/screenshot-element-bounding-box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/golden/screenshot-element-bounding-box.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/assets/csscoverage/unused.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | -------------------------------------------------------------------------------- /test/golden/screenshot-element-padding-border.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/golden/screenshot-element-padding-border.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 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | third_party/* 2 | utils/doclint/check_public_api/test/ 3 | utils/testrunner/examples/ 4 | node6/* 5 | node6-test/* 6 | node6-testrunner/* 7 | -------------------------------------------------------------------------------- /test/assets/self-request.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /test/golden/screenshot-element-scrolled-into-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/puppeteer/master/test/golden/screenshot-element-scrolled-into-view.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 | yarn.lock 12 | /node6 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/nested-frames.txt: -------------------------------------------------------------------------------- 1 | http://localhost:/frames/nested-frames.html 2 | http://localhost:/frames/two-frames.html 3 | http://localhost:/frames/frame.html 4 | http://localhost:/frames/frame.html 5 | http://localhost:/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 | -------------------------------------------------------------------------------- /test/golden/reconnect-nested-frames.txt: -------------------------------------------------------------------------------- 1 | http://localhost:/frames/nested-frames.html 2 | http://localhost:/frames/two-frames.html 3 | http://localhost:/frames/frame.html 4 | http://localhost:/frames/frame.html 5 | http://localhost:/frames/frame.html -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /.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 | /node6/utils 16 | /test 17 | /utils 18 | /docs 19 | yarn.lock 20 | -------------------------------------------------------------------------------- /test/assets/csscoverage/multiple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | -------------------------------------------------------------------------------- /test/assets/jscoverage/involved.html: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /.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 | - npm install 11 | - if "%nodejs_version%" == "7" ( 12 | npm run lint && 13 | npm run coverage && 14 | npm run test-doclint 15 | ) else ( 16 | npm run unit-node6 17 | ) 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /utils/apply_next_version.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | 4 | const package = require('../package.json'); 5 | let version = package.version; 6 | const dashIndex = version.indexOf('-'); 7 | if (dashIndex !== -1) 8 | version = version.substring(0, dashIndex); 9 | version += '-next.' + Date.now(); 10 | console.log('Setting version to ' + version); 11 | package.version = version; 12 | fs.writeFileSync(path.join(__dirname, '..', 'package.json'), JSON.stringify(package, undefined, 2) + '\n'); -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/assets/shadow.html: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /test/assets/frames/nested-frames.html: -------------------------------------------------------------------------------- 1 | 11 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/assets/csscoverage/involved.html: -------------------------------------------------------------------------------- 1 | 24 |
woof!
25 | fancy text 26 | 27 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /test/golden/jscoverage-involved.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "url": "http://localhost:/jscoverage/involved.html", 4 | "ranges": [ 5 | { 6 | "start": 0, 7 | "end": 35 8 | }, 9 | { 10 | "start": 50, 11 | "end": 100 12 | }, 13 | { 14 | "start": 107, 15 | "end": 141 16 | }, 17 | { 18 | "start": 148, 19 | "end": 160 20 | }, 21 | { 22 | "start": 168, 23 | "end": 207 24 | } 25 | ], 26 | "text": "\nfunction foo() {\n if (1 > 2)\n console.log(1);\n if (1 < 2)\n console.log(2);\n let x = 1 > 2 ? 'foo' : 'bar';\n let y = 1 < 2 ? 'foo' : 'bar';\n let z = () => {};\n let q = () => {};\n q();\n}\n\nfoo();\n" 27 | } 28 | ] -------------------------------------------------------------------------------- /test/golden/csscoverage-involved.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "url": "http://localhost:/csscoverage/involved.html", 4 | "ranges": [ 5 | { 6 | "start": 149, 7 | "end": 297 8 | }, 9 | { 10 | "start": 327, 11 | "end": 433 12 | } 13 | ], 14 | "text": "\n@charset \"utf-8\";\n@namespace svg url(http://www.w3.org/2000/svg);\n@font-face {\n font-family: \"Example Font\";\n src: url(\"./Dosis-Regular.ttf\");\n}\n\n#fluffy {\n border: 1px solid black;\n z-index: 1;\n /* -webkit-disabled-property: rgb(1, 2, 3) */\n -lol-cats: \"dogs\" /* non-existing property */\n}\n\n@media (min-width: 1px) {\n span {\n -webkit-border-radius: 10px;\n font-family: \"Example Font\";\n animation: 1s identifier;\n }\n}\n" 15 | } 16 | ] -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /utils/testrunner/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 TestRunner = require('./TestRunner'); 18 | const Reporter = require('./Reporter'); 19 | const Matchers = require('./Matchers'); 20 | 21 | module.exports = { TestRunner, Reporter, Matchers }; 22 | -------------------------------------------------------------------------------- /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 | const browser = await puppeteer.launch(); 23 | const page = await browser.newPage(); 24 | await page.goto('http://example.com'); 25 | await page.screenshot({path: 'example.png'}); 26 | await browser.close(); 27 | })(); 28 | -------------------------------------------------------------------------------- /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 | let asyncawait = true; 18 | try { 19 | new Function('async function test(){await 1}'); 20 | } catch (error) { 21 | asyncawait = false; 22 | } 23 | 24 | // If node does not support async await, use the compiled version. 25 | if (asyncawait) 26 | module.exports = require('./lib/Puppeteer'); 27 | else 28 | module.exports = require('./node6/lib/Puppeteer'); 29 | -------------------------------------------------------------------------------- /test/assets/input/checkbox.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Selection Test 5 | 6 | 7 | 8 | 9 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /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 | const browser = await puppeteer.launch(); 24 | const page = await browser.newPage(); 25 | await page.emulate(devices['iPhone 6']); 26 | await page.goto('https://www.nytimes.com/'); 27 | await page.screenshot({path: 'full.png', fullPage: true}); 28 | await browser.close(); 29 | })(); 30 | -------------------------------------------------------------------------------- /utils/testrunner/examples/timeout.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 {TestRunner, Reporter} = require('..'); 18 | 19 | const runner = new TestRunner({ timeout: 100 }); 20 | const reporter = new Reporter(runner); 21 | 22 | const {describe, xdescribe, fdescribe} = runner; 23 | const {it, fit, xit} = runner; 24 | const {beforeAll, beforeEach, afterAll, afterEach} = runner; 25 | 26 | describe('testsuite', () => { 27 | it('timeout', async (state) => { 28 | await new Promise(() => {}); 29 | }); 30 | }); 31 | 32 | runner.run(); 33 | -------------------------------------------------------------------------------- /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 | const browser = await puppeteer.launch({ 23 | // Launch chromium using a proxy server on port 9876. 24 | // More on proxying: 25 | // https://www.chromium.org/developers/design-documents/network-settings 26 | args: [ '--proxy-server=127.0.0.1:9876' ] 27 | }); 28 | const page = await browser.newPage(); 29 | await page.goto('https://google.com'); 30 | await browser.close(); 31 | })(); 32 | -------------------------------------------------------------------------------- /test/server/run.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 SimpleServer = require('./SimpleServer'); 18 | 19 | const port = 8907; 20 | const httpsPort = 8908; 21 | const assetsPath = path.join(__dirname, '..', 'assets'); 22 | Promise.all([ 23 | SimpleServer.create(assetsPath, port), 24 | SimpleServer.createHTTPS(assetsPath, httpsPort) 25 | ]).then(([server, httpsServer]) => { 26 | console.log(`HTTP: server is running on http://localhost:${port}`); 27 | console.log(`HTTPS: server is running on https://localhost:${httpsPort}`); 28 | }); 29 | -------------------------------------------------------------------------------- /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 | * Node.js version: 27 | 28 | **What steps will reproduce the problem?** 29 | 30 | _Please include code that reproduces the issue._ 31 | 32 | 1. 33 | 2. 34 | 3. 35 | 36 | **What is the expected result?** 37 | 38 | 39 | **What happens instead?** 40 | 41 | -------------------------------------------------------------------------------- /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 | const browser = await puppeteer.launch(); 23 | const page = await browser.newPage(); 24 | await page.goto('https://news.ycombinator.com', {waitUntil: 'networkidle2'}); 25 | // page.pdf() is currently supported only in headless mode. 26 | // @see https://bugs.chromium.org/p/chromium/issues/detail?id=753118 27 | await page.pdf({ 28 | path: 'hn.pdf', 29 | format: 'letter' 30 | }); 31 | 32 | await browser.close(); 33 | })(); 34 | -------------------------------------------------------------------------------- /utils/testrunner/examples/hookfail.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 {TestRunner, Reporter, Matchers} = require('..'); 18 | 19 | const runner = new TestRunner(); 20 | const reporter = new Reporter(runner); 21 | const {expect} = new Matchers(); 22 | 23 | const {describe, xdescribe, fdescribe} = runner; 24 | const {it, fit, xit} = runner; 25 | const {beforeAll, beforeEach, afterAll, afterEach} = runner; 26 | 27 | describe('testsuite', () => { 28 | beforeAll(() => { 29 | expect(false).toBeTruthy(); 30 | }); 31 | it('test', async () => { 32 | }); 33 | }); 34 | 35 | runner.run(); 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /utils/testrunner/examples/hooktimeout.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 {TestRunner, Reporter, Matchers} = require('..'); 18 | 19 | const runner = new TestRunner({ timeout: 100 }); 20 | const reporter = new Reporter(runner); 21 | const {expect} = new Matchers(); 22 | 23 | const {describe, xdescribe, fdescribe} = runner; 24 | const {it, fit, xit} = runner; 25 | const {beforeAll, beforeEach, afterAll, afterEach} = runner; 26 | 27 | describe('testsuite', () => { 28 | beforeAll(async () => { 29 | await new Promise(() => {}); 30 | }); 31 | it('something', async (state) => { 32 | }); 33 | }); 34 | 35 | runner.run(); 36 | -------------------------------------------------------------------------------- /utils/testrunner/examples/unhandledpromiserejection.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 {TestRunner, Reporter} = require('..'); 18 | 19 | const runner = new TestRunner(); 20 | const reporter = new Reporter(runner); 21 | 22 | const {describe, xdescribe, fdescribe} = runner; 23 | const {it, fit, xit} = runner; 24 | const {beforeAll, beforeEach, afterAll, afterEach} = runner; 25 | 26 | describe('testsuite', () => { 27 | it('failure', async (state) => { 28 | Promise.reject(new Error('fail!')); 29 | }); 30 | it('slow', async () => { 31 | await new Promise(x => setTimeout(x, 1000)); 32 | }); 33 | }); 34 | 35 | runner.run(); 36 | -------------------------------------------------------------------------------- /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 | const browser = await puppeteer.launch(); 23 | const page = await browser.newPage(); 24 | await page.setRequestInterception(true); 25 | page.on('request', request => { 26 | if (request.resourceType() === 'image') 27 | request.abort(); 28 | else 29 | request.continue(); 30 | }); 31 | await page.goto('https://news.google.com/news/'); 32 | await page.screenshot({path: 'news.png', fullPage: true}); 33 | 34 | await browser.close(); 35 | })(); 36 | 37 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /utils/testrunner/README.md: -------------------------------------------------------------------------------- 1 | # TestRunner 2 | 3 | - testrunner is a library: no additional binary required; tests are `node.js` scripts 4 | - parallel wrt IO operations 5 | - supports async/await 6 | - modular 7 | - well-isolated state per execution thread 8 | 9 | Example 10 | 11 | ```js 12 | const {TestRunner, Reporter, Matchers} = require('../utils/testrunner'); 13 | 14 | // Runner holds and runs all the tests 15 | const runner = new TestRunner({ 16 | parallel: 2, // run 2 parallel threads 17 | timeout: 1000, // setup timeout of 1 second per test 18 | }); 19 | // Simple expect-like matchers 20 | const {expect} = new Matchers(); 21 | 22 | // Extract jasmine-like DSL into the global namespace 23 | const {describe, xdescribe, fdescribe} = runner; 24 | const {it, fit, xit} = runner; 25 | const {beforeAll, beforeEach, afterAll, afterEach} = runner; 26 | 27 | beforeAll(state => { 28 | state.parallelIndex; // either 0 or 1 in this example, depending on the executing thread 29 | state.foo = 'bar'; // set state for every test 30 | }); 31 | 32 | describe('math', () => { 33 | it('to be sane', async (state, test) => { 34 | state.parallel; // Very first test will always be ran by the 0's thread 35 | state.foo; // this will be 'bar' 36 | expect(2 + 2).toBe(4); 37 | }); 38 | }); 39 | 40 | // Reporter subscribes to TestRunner events and displays information in terminal 41 | const reporter = new Reporter(runner); 42 | 43 | // Run all tests. 44 | runner.run(); 45 | ``` 46 | -------------------------------------------------------------------------------- /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 | * @return {!Array} 45 | */ 46 | static defaultArgs() { 47 | return Launcher.defaultArgs(); 48 | } 49 | } 50 | 51 | module.exports = Puppeteer; 52 | helper.tracePublicAPI(Puppeteer); 53 | -------------------------------------------------------------------------------- /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 | const browser = await puppeteer.launch(); 38 | const page = await browser.newPage(); 39 | await page.evaluateOnNewDocument(sniffDetector); 40 | await page.goto('https://www.google.com', {waitUntil: 'networkidle2'}); 41 | console.log('Sniffed: ' + (await page.evaluate(() => !!navigator.sniffed))); 42 | 43 | await browser.close(); 44 | })(); 45 | -------------------------------------------------------------------------------- /lib/externs.d.ts: -------------------------------------------------------------------------------- 1 | import { Connection as RealConnection, CDPSession as RealCDPSession } from './Connection.js'; 2 | import {Browser as RealBrowser, TaskQueue as RealTaskQueue, Target as RealTarget} 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 CDPSession extends RealCDPSession {} 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 Target extends RealTarget {} 20 | export class Frame extends RealFrame {} 21 | export class FrameManager extends RealFrameManager {} 22 | export class NetworkManager extends RealNetworkManager {} 23 | export class ElementHandle extends RealElementHandle {} 24 | export class JSHandle extends RealJSHandle {} 25 | export class ExecutionContext extends RealExecutionContext {} 26 | export class Page extends RealPage {} 27 | 28 | 29 | export interface ChildProcess extends child_process.ChildProcess {} 30 | -------------------------------------------------------------------------------- /.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 | directories: 10 | - node_modules 11 | # allow headful tests 12 | before_install: 13 | - "export DISPLAY=:99.0" 14 | - "sh -e /etc/init.d/xvfb start" 15 | script: 16 | - 'if [ "$NODE7" = "true" ]; then npm run lint; fi' 17 | - 'if [ "$NODE7" = "true" ]; then npm run coverage; fi' 18 | - 'if [ "$NODE7" = "true" ]; then npm run test-doclint; fi' 19 | - 'if [ "$NODE6" = "true" ]; then npm run unit-node6; fi' 20 | jobs: 21 | include: 22 | - node_js: "7.6.0" 23 | env: NODE7=true 24 | - node_js: "6.4.0" 25 | env: NODE6=true 26 | before_deploy: "npm run apply-next-version" 27 | deploy: 28 | provider: npm 29 | email: aslushnikov@gmail.com 30 | api_key: 31 | secure: S/cwhamxKh0dsI46jewA6AkFXhDQWqfGkEe19WP3uoBg69jBqxs3DMcqGmd7og8RgqFUhhnBon8cDyJD25XHhDSSVANY9sFhwAPsty+Pjc9NHlG9My59tzpKSuhxOHxUaUd4Ug1gjYKTRNh4yYQKuGFm1mS3X34AbHg+eAFz6gVk3oVoc8Uu/rYH/dTP/ZGa29U7tn8DrEiyxoxv1pIEoX66AmAA649+nULwZw1MlhTaZZgYYIHlMC91dY6N6ql3ESj0zZSmQmrdZbWcGkTZDfBmdHMfFhnX9n0D7AgNvKNL+LDBcu7yiGO3hM2BrVOlGv24FFaHF5cLU4wuRuBvRG8cx3j1rG5w8c8jkEN1iL6qcmo76++YfILi3DIzD2n4RAsKeGILTcQOHhY4wqViuy0bb5zc5i/pqbtUERb3ngCZbNYTY8MQ5ertckmEw8daS/irREZfmThY90EPitsYw39f1+tdm9YUpWz/q2MpjqNByk6ycr17i2zFdqy4j/5CwrlmtewCw7Np8ubNITuqL7rst5GojlJvKSA3onuGyJLtSshUFn6lwwAHpkptZ5JWDwkxHiW+ofQ50Td6+NouJrSobOd1JzJ5om4eH2V1hkD5RP5saeDgojbRTMX+j8lQaQAnYQLYj0C2I1i6yl+VP0HUt7L//3zTRUetNTzGdA4= 32 | on: 33 | branch: master 34 | condition: "$NODE7 = true" 35 | skip_cleanup: true 36 | tag: next 37 | -------------------------------------------------------------------------------- /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 | const browser = await puppeteer.launch(); 23 | const page = await browser.newPage(); 24 | 25 | // Define a window.onCustomEvent function on the page. 26 | await page.exposeFunction('onCustomEvent', e => { 27 | console.log(`${e.type} fired`, e.detail || ''); 28 | }); 29 | 30 | /** 31 | * Attach an event listener to page to capture a custom event on page load/navigation. 32 | * @param {string} type Event name. 33 | * @return {!Promise} 34 | */ 35 | function listenFor(type) { 36 | return page.evaluateOnNewDocument(type => { 37 | document.addEventListener(type, e => { 38 | window.onCustomEvent({type, detail: e.detail}); 39 | }); 40 | }, type); 41 | } 42 | 43 | await listenFor('app-ready'); // Listen for "app-ready" custom event on page load. 44 | 45 | await page.goto('https://www.chromestatus.com/features', {waitUntil: 'networkidle2'}); 46 | 47 | await browser.close(); 48 | })(); 49 | -------------------------------------------------------------------------------- /utils/testrunner/examples/fail.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 {TestRunner, Reporter, Matchers} = require('..'); 18 | 19 | const runner = new TestRunner(); 20 | const reporter = new Reporter(runner); 21 | const {expect} = new Matchers(); 22 | 23 | const {describe, xdescribe, fdescribe} = runner; 24 | const {it, fit, xit} = runner; 25 | const {beforeAll, beforeEach, afterAll, afterEach} = runner; 26 | 27 | describe('testsuite', () => { 28 | it('toBe', async (state) => { 29 | expect(2 + 2).toBe(5); 30 | }); 31 | it('toBeFalsy', async (state) => { 32 | expect(true).toBeFalsy(); 33 | }); 34 | it('toBeTruthy', async (state) => { 35 | expect(false).toBeTruthy(); 36 | }); 37 | it('toBeGreaterThan', async (state) => { 38 | expect(2).toBeGreaterThan(3); 39 | }); 40 | it('toBeNull', async (state) => { 41 | expect(2).toBeNull(); 42 | }); 43 | it('toContain', async (state) => { 44 | expect('asdf').toContain('e'); 45 | }); 46 | it('toEqual', async (state) => { 47 | expect([1,2,3]).toEqual([1,2,3,4]); 48 | }); 49 | }); 50 | 51 | runner.run(); 52 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | /** 18 | * @fileoverview Search developers.google.com/web for articles tagged 19 | * "Headless Chrome" and scrape results from the results page. 20 | */ 21 | 22 | 'use strict'; 23 | 24 | const puppeteer = require('puppeteer'); 25 | 26 | (async() => { 27 | const browser = await puppeteer.launch(); 28 | const page = await browser.newPage(); 29 | 30 | await page.goto('https://developers.google.com/web/'); 31 | 32 | // Type into search box. 33 | await page.type('#searchbox input', 'Headless Chrome'); 34 | 35 | // Wait for suggest overlay to appear and click "show all results". 36 | const allResultsSelector = '.devsite-suggest-all-results'; 37 | await page.waitForSelector(allResultsSelector); 38 | await page.click(allResultsSelector); 39 | 40 | // Wait for the results page to load and display the results. 41 | const resultsSelector = '.gsc-results .gsc-thumbnail-inside a.gs-title'; 42 | await page.waitForSelector(resultsSelector); 43 | 44 | // Extract the results from the page. 45 | const links = await page.evaluate(resultsSelector => { 46 | const anchors = Array.from(document.querySelectorAll(resultsSelector)); 47 | return anchors.map(anchor => { 48 | const title = anchor.textContent.split('|')[0].trim(); 49 | return `${title} - ${anchor.href}`; 50 | }); 51 | }, resultsSelector); 52 | console.log(links.join('\n')); 53 | 54 | await browser.close(); 55 | })(); 56 | -------------------------------------------------------------------------------- /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 | const root = path.join(__dirname, '..', '..'); 23 | const dest = path.join(__dirname, '..', '..', 'node6'); 24 | 25 | if (fs.existsSync(dest)) 26 | removeRecursive(dest); 27 | fs.mkdirSync(dest); 28 | fs.mkdirSync(path.join(dest, 'utils')); 29 | 30 | copyFolder(path.join(root, 'lib'), path.join(dest, 'lib')); 31 | copyFolder(path.join(root, 'test'), path.join(dest, 'test')); 32 | copyFolder(path.join(root, 'utils'), path.join(dest, 'utils')); 33 | 34 | function copyFolder(source, target) { 35 | if (fs.existsSync(target)) 36 | removeRecursive(target); 37 | fs.mkdirSync(target); 38 | 39 | fs.readdirSync(source).forEach(file => { 40 | const from = path.join(source, file); 41 | const to = path.join(target, file); 42 | if (fs.lstatSync(from).isDirectory()) 43 | copyFolder(from, to); 44 | else 45 | copyFile(from, to); 46 | }); 47 | } 48 | 49 | function copyFile(from, to) { 50 | let text = fs.readFileSync(from); 51 | if (from.endsWith('.js')) { 52 | text = text.toString(); 53 | const prefix = text.startsWith('#!') ? text.substring(0, text.indexOf('\n')) : ''; 54 | text = prefix + transformAsyncFunctions(text.substring(prefix.length)); 55 | } 56 | fs.writeFileSync(to, text); 57 | } 58 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Running the examples 2 | 3 | Assuming you have a checkout of the Puppeteer repo and have run npm i (or yarn) to install the dependencies, the examples can be run from the root folder like so: 4 | 5 | ```sh 6 | NODE_PATH=../ node examples/search.js 7 | ``` 8 | 9 | # Tips & Tricks 10 | 11 | ### Load a Chrome extension 12 | 13 | By default, Puppeteer disables extensions when launching Chrome. You can load a specific 14 | extension using: 15 | 16 | ```js 17 | const browser = await puppeteer.launch({ 18 | headless: false, 19 | args: [ 20 | '--disable-extensions-except=/path/to/extension/', 21 | '--load-extension=/path/to/extension/', 22 | ] 23 | }); 24 | ``` 25 | 26 | # Other resources 27 | 28 | > Other useful tools, articles, and projects that use Puppeteer. 29 | 30 | ## Rendering and web scraping 31 | - [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). 32 | - [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. 33 | - [pupperender](https://github.com/LasaleFamine/pupperender) - Express middleware that checks the User-Agent header of incoming requests, and if it matches one of a configurable set of bots, render the page using Puppeteer. Useful for PWA rendering. 34 | - [headless-chrome-crawler](https://github.com/yujiosaka/headless-chrome-crawler) - Crawler that provides simple APIs to manipulate Headless Chrome and allows you to crawl dynamic websites. 35 | - [puppeteer-examples](https://github.com/checkly/puppeteer-examples) - Puppeteer Headless Chrome examples for real life use cases such as getting useful info from the web pages or common login scenarios. 36 | 37 | ## Testing 38 | - [angular-puppeteer-demo](https://github.com/Quramy/angular-puppeteer-demo) - Demo repository explaining how to use Puppeteer in Karma. 39 | - [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. 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puppeteer", 3 | "version": "1.0.0-post", 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": "node test/test.js", 12 | "debug-unit": "node --inspect-brk test/test.js", 13 | "test-doclint": "node utils/doclint/check_public_api/test/test.js && node 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": "node utils/node6-transform/test/test.js", 20 | "build": "node utils/node6-transform/index.js", 21 | "unit-node6": "node node6/test/test.js", 22 | "tsc": "tsc -p .", 23 | "prepublishOnly": "npm run build", 24 | "apply-next-version": "node utils/apply_next_version.js" 25 | }, 26 | "author": "The Chromium Authors", 27 | "license": "Apache-2.0", 28 | "dependencies": { 29 | "debug": "^2.6.8", 30 | "extract-zip": "^1.6.5", 31 | "https-proxy-agent": "^2.1.0", 32 | "mime": "^1.3.4", 33 | "progress": "^2.0.0", 34 | "proxy-from-env": "^1.0.0", 35 | "rimraf": "^2.6.1", 36 | "ws": "^3.0.0" 37 | }, 38 | "puppeteer": { 39 | "chromium_revision": "526987" 40 | }, 41 | "devDependencies": { 42 | "@types/debug": "0.0.30", 43 | "@types/extract-zip": "^1.6.2", 44 | "@types/mime": "^1.3.1", 45 | "@types/node": "^8.0.26", 46 | "@types/rimraf": "^2.0.2", 47 | "@types/ws": "^3.0.2", 48 | "commonmark": "^0.27.0", 49 | "cross-env": "^5.0.5", 50 | "eslint": "^4.0.0", 51 | "esprima": "^4.0.0", 52 | "markdown-toc": "^1.1.0", 53 | "minimist": "^1.2.0", 54 | "ncp": "^2.0.0", 55 | "pdfjs-dist": "^1.8.595", 56 | "pixelmatch": "^4.0.2", 57 | "pngjs": "^3.2.0", 58 | "text-diff": "^1.0.1", 59 | "typescript": "^2.6.0-rc" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /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.CDPSession} 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 | type() { 38 | return this._type; 39 | } 40 | 41 | /** 42 | * @return {string} 43 | */ 44 | message() { 45 | return this._message; 46 | } 47 | 48 | /** 49 | * @return {string} 50 | */ 51 | defaultValue() { 52 | return this._defaultValue; 53 | } 54 | 55 | /** 56 | * @param {string=} promptText 57 | */ 58 | async accept(promptText) { 59 | console.assert(!this._handled, 'Cannot accept dialog which is already handled!'); 60 | this._handled = true; 61 | await this._client.send('Page.handleJavaScriptDialog', { 62 | accept: true, 63 | promptText: promptText 64 | }); 65 | } 66 | 67 | async dismiss() { 68 | console.assert(!this._handled, 'Cannot dismiss dialog which is already handled!'); 69 | this._handled = true; 70 | await this._client.send('Page.handleJavaScriptDialog', { 71 | accept: false 72 | }); 73 | } 74 | } 75 | 76 | Dialog.Type = { 77 | Alert: 'alert', 78 | BeforeUnload: 'beforeunload', 79 | Confirm: 'confirm', 80 | Prompt: 'prompt' 81 | }; 82 | 83 | module.exports = Dialog; 84 | helper.tracePublicAPI(Dialog); 85 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /utils/testrunner/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().replace(/:\d{4}\//, ':/'); 71 | for (const child of frame.childFrames()) 72 | result += '\n' + utils.dumpFrames(child, ' ' + indentation); 73 | return result; 74 | }, 75 | }; 76 | -------------------------------------------------------------------------------- /test/assets/detect-touch.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Detect Touch Test 5 | 10 | 11 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /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 | const {TestRunner, Reporter, Matchers} = require('../../testrunner/'); 23 | const runner = new TestRunner(); 24 | new Reporter(runner); 25 | 26 | const {describe, xdescribe, fdescribe} = runner; 27 | const {it, fit, xit} = runner; 28 | const {beforeAll, beforeEach, afterAll, afterEach} = runner; 29 | const {expect} = new Matchers(); 30 | 31 | describe('preprocessor', function() { 32 | it('should throw for unknown command', function() { 33 | const source = factory.createForTest('doc.md', getCommand('unknownCommand()')); 34 | const messages = preprocessor([source]); 35 | expect(source.hasUpdatedText()).toBe(false); 36 | expect(messages.length).toBe(1); 37 | expect(messages[0].type).toBe('error'); 38 | expect(messages[0].text).toContain('Unknown command'); 39 | }); 40 | describe('gen:version', function() { 41 | it('should work', function() { 42 | const source = factory.createForTest('doc.md', `Puppeteer v${getCommand('version')}`); 43 | const messages = preprocessor([source]); 44 | expect(messages.length).toBe(1); 45 | expect(messages[0].type).toBe('warning'); 46 | expect(messages[0].text).toContain('doc.md'); 47 | expect(source.text()).toBe(`Puppeteer v${getCommand('version', VERSION)}`); 48 | }); 49 | it('should tolerate different writing', function() { 50 | const source = factory.createForTest('doc.md', `Puppeteer vWHAT 51 | `); 52 | preprocessor([source]); 53 | expect(source.text()).toBe(`Puppeteer v${VERSION}`); 54 | }); 55 | it('should not tolerate missing gen:stop', function() { 56 | const source = factory.createForTest('doc.md', ``); 57 | const messages = preprocessor([source]); 58 | expect(source.hasUpdatedText()).toBe(false); 59 | expect(messages.length).toBe(1); 60 | expect(messages[0].type).toBe('error'); 61 | expect(messages[0].text).toContain(`Failed to find 'gen:stop'`); 62 | }); 63 | }); 64 | }); 65 | 66 | runner.run(); 67 | 68 | function getCommand(name, body = '') { 69 | return `${body}`; 70 | } 71 | -------------------------------------------------------------------------------- /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.CDPSession} 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 defaultCategories = [ 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 | const categoriesArray = options.categories || defaultCategories; 47 | 48 | if (options.screenshots) 49 | categoriesArray.push('disabled-by-default-devtools.screenshot'); 50 | 51 | this._path = options.path; 52 | this._recording = true; 53 | await this._client.send('Tracing.start', { 54 | transferMode: 'ReturnAsStream', 55 | categories: categoriesArray.join(',') 56 | }); 57 | } 58 | 59 | async stop() { 60 | let fulfill; 61 | const contentPromise = new Promise(x => fulfill = x); 62 | this._client.once('Tracing.tracingComplete', event => { 63 | this._readStream(event.stream, this._path).then(fulfill); 64 | }); 65 | await this._client.send('Tracing.end'); 66 | this._recording = false; 67 | return contentPromise; 68 | } 69 | 70 | /** 71 | * @param {string} handle 72 | * @param {string} path 73 | */ 74 | async _readStream(handle, path) { 75 | let eof = false; 76 | const file = await openAsync(path, 'w'); 77 | while (!eof) { 78 | const response = await this._client.send('IO.read', {handle}); 79 | eof = response.eof; 80 | if (path) 81 | await writeAsync(file, response.data); 82 | } 83 | await closeAsync(file); 84 | await this._client.send('IO.close', {handle}); 85 | } 86 | } 87 | helper.tracePublicAPI(Tracing); 88 | 89 | module.exports = Tracing; 90 | -------------------------------------------------------------------------------- /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 apiSource = await sourceFactory.readFile(path.join(PROJECT_DIR, 'docs', 'api.md')); 40 | const mdSources = [apiSource]; 41 | 42 | const toc = require('./toc'); 43 | messages.push(...await toc(mdSources)); 44 | 45 | const preprocessor = require('./preprocessor'); 46 | messages.push(...await preprocessor(mdSources)); 47 | 48 | const browser = await puppeteer.launch({args: ['--no-sandbox']}); 49 | const page = await browser.newPage(); 50 | const checkPublicAPI = require('./check_public_api'); 51 | const jsSources = await sourceFactory.readdir(path.join(PROJECT_DIR, 'lib'), '.js'); 52 | messages.push(...await checkPublicAPI(page, mdSources, jsSources)); 53 | await browser.close(); 54 | } 55 | 56 | // Report results. 57 | const errors = messages.filter(message => message.type === 'error'); 58 | if (errors.length) { 59 | console.log('DocLint Failures:'); 60 | for (let i = 0; i < errors.length; ++i) { 61 | let error = errors[i].text; 62 | error = error.split('\n').join('\n '); 63 | console.log(` ${i + 1}) ${RED_COLOR}${error}${RESET_COLOR}`); 64 | } 65 | } 66 | const warnings = messages.filter(message => message.type === 'warning'); 67 | if (warnings.length) { 68 | console.log('DocLint Warnings:'); 69 | for (let i = 0; i < warnings.length; ++i) { 70 | let warning = warnings[i].text; 71 | warning = warning.split('\n').join('\n '); 72 | console.log(` ${i + 1}) ${YELLOW_COLOR}${warning}${RESET_COLOR}`); 73 | } 74 | } 75 | let clearExit = messages.length === 0; 76 | if (await sourceFactory.saveChangedSources()) { 77 | if (clearExit) 78 | console.log(`${YELLOW_COLOR}Some files were updated.${RESET_COLOR}`); 79 | clearExit = false; 80 | } 81 | console.log(`${errors.length} failures, ${warnings.length} warnings.`); 82 | const runningTime = Date.now() - startTime; 83 | console.log(`DocLint Finished in ${runningTime / 1000} seconds`); 84 | process.exit(clearExit ? 0 : 1); 85 | } 86 | -------------------------------------------------------------------------------- /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.CDPSession} 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", "varsIgnorePattern": "([fx]?describe|[fx]?it|beforeAll|beforeEach|afterAll|afterEach)" }], 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/testrunner/Matchers.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 | module.exports = class Matchers { 18 | constructor(customMatchers = {}) { 19 | this._matchers = {}; 20 | Object.assign(this._matchers, DefaultMatchers); 21 | Object.assign(this._matchers, customMatchers); 22 | this.expect = this.expect.bind(this); 23 | } 24 | 25 | addMatcher(name, matcher) { 26 | this._matchers[name] = matcher; 27 | } 28 | 29 | expect(value) { 30 | return new Expect(value, this._matchers); 31 | } 32 | }; 33 | 34 | class Expect { 35 | constructor(value, matchers) { 36 | this.not = {}; 37 | this.not.not = this; 38 | for (const matcherName of Object.keys(matchers)) { 39 | const matcher = matchers[matcherName]; 40 | this[matcherName] = applyMatcher.bind(null, matcherName, matcher, false, value); 41 | this.not[matcherName] = applyMatcher.bind(null, matcherName, matcher, true, value); 42 | } 43 | 44 | function applyMatcher(matcherName, matcher, inverse, value, ...args) { 45 | const result = matcher.call(null, value, ...args); 46 | const message = `expect.${matcherName} failed` + (result.message ? `: ${result.message}` : ''); 47 | console.assert(result.pass !== inverse, message); 48 | } 49 | } 50 | } 51 | 52 | const DefaultMatchers = { 53 | toBe: function(value, other, message) { 54 | message = message || `${value} == ${other}`; 55 | return { pass: value === other, message }; 56 | }, 57 | 58 | toBeFalsy: function(value, message) { 59 | message = message || `${value}`; 60 | return { pass: !value, message }; 61 | }, 62 | 63 | toBeTruthy: function(value, message) { 64 | message = message || `${value}`; 65 | return { pass: !!value, message }; 66 | }, 67 | 68 | toBeGreaterThan: function(value, other, message) { 69 | message = message || `${value} > ${other}`; 70 | return { pass: value > other, message }; 71 | }, 72 | 73 | toBeGreaterThanOrEqual: function(value, other, message) { 74 | message = message || `${value} >= ${other}`; 75 | return { pass: value >= other, message }; 76 | }, 77 | 78 | toBeLessThan: function(value, other, message) { 79 | message = message || `${value} < ${other}`; 80 | return { pass: value < other, message }; 81 | }, 82 | 83 | toBeLessThanOrEqual: function(value, other, message) { 84 | message = message || `${value} <= ${other}`; 85 | return { pass: value <= other, message }; 86 | }, 87 | 88 | toBeNull: function(value, message) { 89 | message = message || `${value} == null`; 90 | return { pass: value === null, message }; 91 | }, 92 | 93 | toContain: function(value, other, message) { 94 | message = message || `${value} ⊇ ${other}`; 95 | return { pass: value.includes(other), message }; 96 | }, 97 | 98 | toEqual: function(value, other, message) { 99 | message = message || `${JSON.stringify(value)} ≈ ${JSON.stringify(other)}`; 100 | return { pass: JSON.stringify(value) === JSON.stringify(other), message }; 101 | }, 102 | 103 | toBeCloseTo: function(value, other, precision, message) { 104 | return { 105 | pass: Math.abs(value - other) < Math.pow(10, -precision), 106 | message 107 | }; 108 | } 109 | }; 110 | -------------------------------------------------------------------------------- /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 path = require('path'); 18 | const puppeteer = require('../../../..'); 19 | const checkPublicAPI = require('..'); 20 | const SourceFactory = require('../../SourceFactory'); 21 | const mdBuilder = require('../MDBuilder'); 22 | const jsBuilder = require('../JSBuilder'); 23 | const GoldenUtils = require('../../../../test/golden-utils'); 24 | 25 | const {TestRunner, Reporter, Matchers} = require('../../../testrunner/'); 26 | const runner = new TestRunner(); 27 | const reporter = new Reporter(runner); 28 | 29 | const {describe, xdescribe, fdescribe} = runner; 30 | const {it, fit, xit} = runner; 31 | const {beforeAll, beforeEach, afterAll, afterEach} = runner; 32 | 33 | let browser; 34 | let page; 35 | 36 | beforeAll(async function() { 37 | browser = await puppeteer.launch({args: ['--no-sandbox']}); 38 | page = await browser.newPage(); 39 | }); 40 | 41 | afterAll(async function() { 42 | await browser.close(); 43 | }); 44 | 45 | describe('checkPublicAPI', function() { 46 | it('diff-classes', testLint); 47 | it('diff-methods', testLint); 48 | it('diff-properties', testLint); 49 | it('diff-arguments', testLint); 50 | it('diff-events', testLint); 51 | it('check-duplicates', testLint); 52 | it('check-sorting', testLint); 53 | it('check-returns', testLint); 54 | it('js-builder-common', testJSBuilder); 55 | it('js-builder-inheritance', testJSBuilder); 56 | it('md-builder-common', testMDBuilder); 57 | }); 58 | 59 | runner.run(); 60 | 61 | async function testLint(state, test) { 62 | const dirPath = path.join(__dirname, test.name); 63 | const {expect} = new Matchers({ 64 | toBeGolden: GoldenUtils.compare.bind(null, dirPath, dirPath) 65 | }); 66 | 67 | const factory = new SourceFactory(); 68 | const mdSources = await factory.readdir(dirPath, '.md'); 69 | const jsSources = await factory.readdir(dirPath, '.js'); 70 | const messages = await checkPublicAPI(page, mdSources, jsSources); 71 | const errors = messages.map(message => message.text); 72 | expect(errors.join('\n')).toBeGolden('result.txt'); 73 | } 74 | 75 | async function testMDBuilder(state, test) { 76 | const dirPath = path.join(__dirname, test.name); 77 | const {expect} = new Matchers({ 78 | toBeGolden: GoldenUtils.compare.bind(null, dirPath, dirPath) 79 | }); 80 | const factory = new SourceFactory(); 81 | const sources = await factory.readdir(dirPath, '.md'); 82 | const {documentation} = await mdBuilder(page, sources); 83 | expect(serialize(documentation)).toBeGolden('result.txt'); 84 | } 85 | 86 | async function testJSBuilder(state, test) { 87 | const dirPath = path.join(__dirname, test.name); 88 | const {expect} = new Matchers({ 89 | toBeGolden: GoldenUtils.compare.bind(null, dirPath, dirPath) 90 | }); 91 | const factory = new SourceFactory(); 92 | const sources = await factory.readdir(dirPath, '.js'); 93 | const {documentation} = await jsBuilder(sources); 94 | expect(serialize(documentation)).toBeGolden('result.txt'); 95 | } 96 | 97 | function serialize(doc) { 98 | const result = {classes: []}; 99 | for (let cls of doc.classesArray) { 100 | const classJSON = { 101 | name: cls.name, 102 | members: [] 103 | }; 104 | result.classes.push(classJSON); 105 | for (let member of cls.membersArray) { 106 | classJSON.members.push({ 107 | name: member.name, 108 | type: member.type, 109 | hasReturn: member.hasReturn, 110 | async: member.async, 111 | args: member.argsArray.map(arg => arg.name) 112 | }); 113 | } 114 | } 115 | return JSON.stringify(result, null, 2); 116 | } 117 | 118 | -------------------------------------------------------------------------------- /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 | const {TestRunner, Reporter, Matchers} = require('../../testrunner/'); 19 | const runner = new TestRunner(); 20 | new Reporter(runner); 21 | 22 | const {describe, xdescribe, fdescribe} = runner; 23 | const {it, fit, xit} = runner; 24 | const {beforeAll, beforeEach, afterAll, afterEach} = runner; 25 | 26 | const {expect} = new Matchers(); 27 | 28 | describe('TransformAsyncFunctions', function() { 29 | it('should convert a function expression', function(done) { 30 | const input = `(async function(){ return 123 })()`; 31 | const output = eval(transformAsyncFunctions(input)); 32 | expect(output instanceof Promise).toBe(true); 33 | output.then(result => expect(result).toBe(123)).then(done); 34 | }); 35 | it('should convert an arrow function', function(done) { 36 | const input = `(async () => 123)()`; 37 | const output = eval(transformAsyncFunctions(input)); 38 | expect(output instanceof Promise).toBe(true); 39 | output.then(result => expect(result).toBe(123)).then(done); 40 | }); 41 | it('should convert an arrow function with curly braces', function(done) { 42 | const input = `(async () => { return 123 })()`; 43 | const output = eval(transformAsyncFunctions(input)); 44 | expect(output instanceof Promise).toBe(true); 45 | output.then(result => expect(result).toBe(123)).then(done); 46 | }); 47 | it('should convert a function declaration', function(done) { 48 | const input = `async function f(){ return 123; } f();`; 49 | const output = eval(transformAsyncFunctions(input)); 50 | expect(output instanceof Promise).toBe(true); 51 | output.then(result => expect(result).toBe(123)).then(done); 52 | }); 53 | it('should convert await', function(done) { 54 | const input = `async function f(){ return 23 + await Promise.resolve(100); } f();`; 55 | const output = eval(transformAsyncFunctions(input)); 56 | expect(output instanceof Promise).toBe(true); 57 | output.then(result => expect(result).toBe(123)).then(done); 58 | }); 59 | it('should convert method', function(done) { 60 | const input = `class X{async f() { return 123 }} (new X()).f();`; 61 | const output = eval(transformAsyncFunctions(input)); 62 | expect(output instanceof Promise).toBe(true); 63 | output.then(result => expect(result).toBe(123)).then(done); 64 | }); 65 | it('should pass arguments', function(done) { 66 | const input = `(async function(a, b){ return await a + await b })(Promise.resolve(100), 23)`; 67 | const output = eval(transformAsyncFunctions(input)); 68 | expect(output instanceof Promise).toBe(true); 69 | output.then(result => expect(result).toBe(123)).then(done); 70 | }); 71 | it('should still work across eval', function(done) { 72 | const input = `var str = (async function(){ return 123; }).toString(); eval('(' + str + ')')();`; 73 | const output = eval(transformAsyncFunctions(input)); 74 | expect(output instanceof Promise).toBe(true); 75 | output.then(result => expect(result).toBe(123)).then(done); 76 | }); 77 | it('should work with double await', function(done) { 78 | const input = `async function f(){ return 23 + await Promise.resolve(50 + await Promise.resolve(50)); } f();`; 79 | const output = eval(transformAsyncFunctions(input)); 80 | expect(output instanceof Promise).toBe(true); 81 | output.then(result => expect(result).toBe(123)).then(done); 82 | }); 83 | it('should work paren around arrow function', function(done) { 84 | const input = `(async x => ( 123))()`; 85 | const output = eval(transformAsyncFunctions(input)); 86 | expect(output instanceof Promise).toBe(true); 87 | output.then(result => expect(result).toBe(123)).then(done); 88 | }); 89 | }); 90 | 91 | runner.run(); 92 | -------------------------------------------------------------------------------- /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; -------------------------------------------------------------------------------- /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 | buildNode6IfNecessary(); 18 | 19 | if (process.env.PUPPETEER_SKIP_CHROMIUM_DOWNLOAD) { 20 | console.log('**INFO** Skipping Chromium download. "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" environment variable was found.'); 21 | return; 22 | } 23 | if (process.env.NPM_CONFIG_PUPPETEER_SKIP_CHROMIUM_DOWNLOAD || process.env.npm_config_puppeteer_skip_chromium_download) { 24 | console.log('**INFO** Skipping Chromium download. "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" was set in npm config.'); 25 | return; 26 | } 27 | 28 | const Downloader = require('./lib/Downloader'); 29 | const downloader = Downloader.createDefault(); 30 | 31 | const platform = downloader.currentPlatform(); 32 | const revision = Downloader.defaultRevision(); 33 | const ProgressBar = require('progress'); 34 | 35 | const revisionInfo = downloader.revisionInfo(platform, revision); 36 | // Do nothing if the revision is already downloaded. 37 | if (revisionInfo.downloaded) 38 | return; 39 | 40 | // Override current environment proxy settings with npm configuration, if any. 41 | const NPM_HTTPS_PROXY = process.env.npm_config_https_proxy || process.env.npm_config_proxy; 42 | const NPM_HTTP_PROXY = process.env.npm_config_http_proxy || process.env.npm_config_proxy; 43 | const NPM_NO_PROXY = process.env.npm_config_no_proxy; 44 | 45 | if (NPM_HTTPS_PROXY) 46 | process.env.HTTPS_PROXY = NPM_HTTPS_PROXY; 47 | if (NPM_HTTP_PROXY) 48 | process.env.HTTP_PROXY = NPM_HTTP_PROXY; 49 | if (NPM_NO_PROXY) 50 | process.env.NO_PROXY = NPM_NO_PROXY; 51 | 52 | const allRevisions = downloader.downloadedRevisions(); 53 | const downloadHost = process.env.PUPPETEER_DOWNLOAD_HOST || process.env.npm_config_puppeteer_download_host; 54 | if (downloadHost) 55 | downloader.setDownloadHost(downloadHost); 56 | downloader.downloadRevision(platform, revision, onProgress) 57 | .then(onSuccess) 58 | .catch(onError); 59 | 60 | /** 61 | * @return {!Promise} 62 | */ 63 | function onSuccess() { 64 | console.log('Chromium downloaded to ' + revisionInfo.folderPath); 65 | // Remove previous chromium revisions. 66 | const cleanupOldVersions = allRevisions.map(({platform, revision}) => downloader.removeRevision(platform, revision)); 67 | return Promise.all(cleanupOldVersions); 68 | } 69 | 70 | /** 71 | * @param {!Error} error 72 | */ 73 | function onError(error) { 74 | console.error(`ERROR: Failed to download Chromium r${revision}! Set "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" env variable to skip download.`); 75 | console.error(error); 76 | process.exit(1); 77 | } 78 | 79 | let progressBar = null; 80 | function onProgress(bytesTotal, delta) { 81 | if (!progressBar) { 82 | progressBar = new ProgressBar(`Downloading Chromium r${revision} - ${toMegabytes(bytesTotal)} [:bar] :percent :etas `, { 83 | complete: '=', 84 | incomplete: ' ', 85 | width: 20, 86 | total: bytesTotal, 87 | }); 88 | } 89 | progressBar.tick(delta); 90 | } 91 | 92 | function toMegabytes(bytes) { 93 | const mb = bytes / 1024 / 1024; 94 | return `${Math.round(mb * 10) / 10} Mb`; 95 | } 96 | 97 | function buildNode6IfNecessary() { 98 | const fs = require('fs'); 99 | const path = require('path'); 100 | 101 | // if this package is installed from NPM, then it already has up-to-date node6 102 | // folder. 103 | if (!fs.existsSync(path.join('utils', 'node6-transform'))) 104 | return; 105 | let asyncawait = true; 106 | try { 107 | new Function('async function test(){await 1}'); 108 | } catch (error) { 109 | asyncawait = false; 110 | } 111 | // if async/await is supported, then node6 is not needed. 112 | if (asyncawait) 113 | return; 114 | // Re-build node6/ folder. 115 | console.log('Building Puppeteer for Node 6'); 116 | require(path.join(__dirname, 'utils', 'node6-transform')); 117 | } 118 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | const {FrameManager} = require('./FrameManager'); 19 | 20 | class NavigatorWatcher { 21 | /** 22 | * @param {!FrameManager} frameManager 23 | * @param {!Puppeteer.Frame} frame 24 | * @param {number} timeout 25 | * @param {!Object=} options 26 | */ 27 | constructor(frameManager, frame, timeout, options = {}) { 28 | console.assert(options.networkIdleTimeout === undefined, 'ERROR: networkIdleTimeout option is no longer supported.'); 29 | console.assert(options.networkIdleInflight === undefined, 'ERROR: networkIdleInflight option is no longer supported.'); 30 | console.assert(options.waitUntil !== 'networkidle', 'ERROR: "networkidle" option is no longer supported. Use "networkidle2" instead'); 31 | let waitUntil = ['load']; 32 | if (Array.isArray(options.waitUntil)) 33 | waitUntil = options.waitUntil.slice(); 34 | else if (typeof options.waitUntil === 'string') 35 | waitUntil = [options.waitUntil]; 36 | this._expectedLifecycle = waitUntil.map(value => { 37 | const protocolEvent = puppeteerToProtocolLifecycle[value]; 38 | console.assert(protocolEvent, 'Unknown value for options.waitUntil: ' + value); 39 | return protocolEvent; 40 | }); 41 | 42 | this._frameManager = frameManager; 43 | this._frame = frame; 44 | this._initialLoaderId = frame._loaderId; 45 | this._timeout = timeout; 46 | this._eventListeners = [ 47 | helper.addEventListener(this._frameManager, FrameManager.Events.LifecycleEvent, this._checkLifecycleComplete.bind(this)), 48 | helper.addEventListener(this._frameManager, FrameManager.Events.FrameDetached, this._checkLifecycleComplete.bind(this)) 49 | ]; 50 | 51 | const lifecycleCompletePromise = new Promise(fulfill => { 52 | this._lifecycleCompleteCallback = fulfill; 53 | }); 54 | this._navigationPromise = Promise.race([ 55 | this._createTimeoutPromise(), 56 | lifecycleCompletePromise 57 | ]).then(error => { 58 | this._cleanup(); 59 | return error; 60 | }); 61 | } 62 | 63 | /** 64 | * @return {!Promise} 65 | */ 66 | _createTimeoutPromise() { 67 | if (!this._timeout) 68 | return new Promise(() => {}); 69 | const errorMessage = 'Navigation Timeout Exceeded: ' + this._timeout + 'ms exceeded'; 70 | return new Promise(fulfill => this._maximumTimer = setTimeout(fulfill, this._timeout)) 71 | .then(() => new Error(errorMessage)); 72 | } 73 | 74 | /** 75 | * @return {!Promise} 76 | */ 77 | async navigationPromise() { 78 | return this._navigationPromise; 79 | } 80 | 81 | _checkLifecycleComplete() { 82 | // We expect navigation to commit. 83 | if (this._frame._loaderId === this._initialLoaderId) 84 | return; 85 | if (!checkLifecycle(this._frame, this._expectedLifecycle)) 86 | return; 87 | this._lifecycleCompleteCallback(); 88 | 89 | /** 90 | * @param {!Puppeteer.Frame} frame 91 | * @param {!Array} expectedLifecycle 92 | * @return {boolean} 93 | */ 94 | function checkLifecycle(frame, expectedLifecycle) { 95 | for (const event of expectedLifecycle) { 96 | if (!frame._lifecycleEvents.has(event)) 97 | return false; 98 | } 99 | for (const child of frame.childFrames()) { 100 | if (!checkLifecycle(child, expectedLifecycle)) 101 | return false; 102 | } 103 | return true; 104 | } 105 | } 106 | 107 | cancel() { 108 | this._cleanup(); 109 | } 110 | 111 | _cleanup() { 112 | helper.removeEventListeners(this._eventListeners); 113 | this._lifecycleCompleteCallback(new Error('Navigation failed')); 114 | clearTimeout(this._maximumTimer); 115 | } 116 | } 117 | 118 | const puppeteerToProtocolLifecycle = { 119 | 'load': 'load', 120 | 'domcontentloaded': 'DOMContentLoaded', 121 | 'networkidle0': 'networkIdle', 122 | 'networkidle2': 'networkAlmostIdle', 123 | }; 124 | 125 | module.exports = NavigatorWatcher; 126 | -------------------------------------------------------------------------------- /utils/testrunner/Reporter.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 RED_COLOR = '\x1b[31m'; 18 | const GREEN_COLOR = '\x1b[32m'; 19 | const YELLOW_COLOR = '\x1b[33m'; 20 | const RESET_COLOR = '\x1b[0m'; 21 | 22 | class Reporter { 23 | constructor(runner) { 24 | this._runner = runner; 25 | runner.on('started', this._onStarted.bind(this)); 26 | runner.on('terminated', this._onTerminated.bind(this)); 27 | runner.on('finished', this._onFinished.bind(this)); 28 | runner.on('teststarted', this._onTestStarted.bind(this)); 29 | runner.on('testfinished', this._onTestFinished.bind(this)); 30 | } 31 | 32 | _onStarted() { 33 | this._timestamp = Date.now(); 34 | console.log(`Running ${YELLOW_COLOR}${this._runner.parallel()}${RESET_COLOR} worker(s):\n`); 35 | } 36 | 37 | _onTerminated(message, error) { 38 | this._printTestResults(); 39 | console.log(`${RED_COLOR}## TERMINATED ##${RESET_COLOR}`); 40 | console.log('Message:'); 41 | console.log(` ${RED_COLOR}${message}${RESET_COLOR}`); 42 | if (error && error.stack) { 43 | console.log('Stack:'); 44 | console.log(error.stack.split('\n').map(line => ' ' + line).join('\n')); 45 | } 46 | process.exit(2); 47 | } 48 | 49 | _onFinished() { 50 | this._printTestResults(); 51 | const failedTests = this._runner.failedTests(); 52 | process.exit(failedTests.length > 0 ? 1 : 0); 53 | } 54 | 55 | _printTestResults() { 56 | // 2 newlines after completing all tests. 57 | console.log('\n'); 58 | 59 | const failedTests = this._runner.failedTests(); 60 | if (failedTests.length > 0) { 61 | console.log('\nFailures:'); 62 | for (let i = 0; i < failedTests.length; ++i) { 63 | const test = failedTests[i]; 64 | console.log(`${i + 1}) ${test.fullName}`); 65 | if (test.result === 'timedout') { 66 | console.log(' Message:'); 67 | console.log(` ${YELLOW_COLOR}Timeout Exceeded ${this._runner.timeout()}ms${RESET_COLOR} ${formatLocation(test)}`); 68 | } else { 69 | console.log(' Message:'); 70 | console.log(` ${RED_COLOR}${test.error.message || test.error}${RESET_COLOR} ${formatLocation(test)}`); 71 | console.log(' Stack:'); 72 | if (test.error.stack) 73 | console.log(test.error.stack.split('\n').map(line => ' ' + line).join('\n')); 74 | } 75 | console.log(''); 76 | } 77 | } 78 | 79 | const tests = this._runner.tests(); 80 | const skippedTests = tests.filter(test => test.result === 'skipped'); 81 | if (skippedTests.length > 0) { 82 | console.log('\nSkipped:'); 83 | for (let i = 0; i < skippedTests.length; ++i) { 84 | const test = skippedTests[i]; 85 | console.log(`${i + 1}) ${test.fullName}`); 86 | console.log(` ${YELLOW_COLOR}Temporary disabled with xit${RESET_COLOR} ${formatLocation(test)}\n`); 87 | } 88 | } 89 | 90 | const executedTests = tests.filter(test => test.result); 91 | console.log(`\nRan ${executedTests.length} of ${tests.length} test(s)`); 92 | const milliseconds = Date.now() - this._timestamp; 93 | const seconds = milliseconds / 1000; 94 | console.log(`Finished in ${YELLOW_COLOR}${seconds}${RESET_COLOR} seconds`); 95 | 96 | function formatLocation(test) { 97 | const location = test.location; 98 | if (!location) 99 | return ''; 100 | return `@ ${location.fileName}:${location.lineNumber}:${location.columnNumber}`; 101 | } 102 | } 103 | 104 | _onTestStarted() { 105 | } 106 | 107 | _onTestFinished(test) { 108 | if (test.result === 'ok') 109 | process.stdout.write(`${GREEN_COLOR}.${RESET_COLOR}`); 110 | else if (test.result === 'skipped') 111 | process.stdout.write(`${YELLOW_COLOR}*${RESET_COLOR}`); 112 | else if (test.result === 'failed') 113 | process.stdout.write(`${RED_COLOR}F${RESET_COLOR}`); 114 | else if (test.result === 'timedout') 115 | process.stdout.write(`${RED_COLOR}T${RESET_COLOR}`); 116 | } 117 | } 118 | 119 | module.exports = Reporter; 120 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/assets/csscoverage/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Edgar Tolentino and Pablo Impallari (www.impallari.com|impallari@gmail.com), 2 | Copyright (c) 2011, Igino Marini. (www.ikern.com|mail@iginomarini.com), 3 | with Reserved Font Names "Dosis". 4 | 5 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 6 | This license is copied below, and is also available with a FAQ at: 7 | http://scripts.sil.org/OFL 8 | 9 | 10 | ----------------------------------------------------------- 11 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 12 | ----------------------------------------------------------- 13 | 14 | PREAMBLE 15 | The goals of the Open Font License (OFL) are to stimulate worldwide 16 | development of collaborative font projects, to support the font creation 17 | efforts of academic and linguistic communities, and to provide a free and 18 | open framework in which fonts may be shared and improved in partnership 19 | with others. 20 | 21 | The OFL allows the licensed fonts to be used, studied, modified and 22 | redistributed freely as long as they are not sold by themselves. The 23 | fonts, including any derivative works, can be bundled, embedded, 24 | redistributed and/or sold with any software provided that any reserved 25 | names are not used by derivative works. The fonts and derivatives, 26 | however, cannot be released under any other type of license. The 27 | requirement for fonts to remain under this license does not apply 28 | to any document created using the fonts or their derivatives. 29 | 30 | DEFINITIONS 31 | "Font Software" refers to the set of files released by the Copyright 32 | Holder(s) under this license and clearly marked as such. This may 33 | include source files, build scripts and documentation. 34 | 35 | "Reserved Font Name" refers to any names specified as such after the 36 | copyright statement(s). 37 | 38 | "Original Version" refers to the collection of Font Software components as 39 | distributed by the Copyright Holder(s). 40 | 41 | "Modified Version" refers to any derivative made by adding to, deleting, 42 | or substituting -- in part or in whole -- any of the components of the 43 | Original Version, by changing formats or by porting the Font Software to a 44 | new environment. 45 | 46 | "Author" refers to any designer, engineer, programmer, technical 47 | writer or other person who contributed to the Font Software. 48 | 49 | PERMISSION & CONDITIONS 50 | Permission is hereby granted, free of charge, to any person obtaining 51 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 52 | redistribute, and sell modified and unmodified copies of the Font 53 | Software, subject to the following conditions: 54 | 55 | 1) Neither the Font Software nor any of its individual components, 56 | in Original or Modified Versions, may be sold by itself. 57 | 58 | 2) Original or Modified Versions of the Font Software may be bundled, 59 | redistributed and/or sold with any software, provided that each copy 60 | contains the above copyright notice and this license. These can be 61 | included either as stand-alone text files, human-readable headers or 62 | in the appropriate machine-readable metadata fields within text or 63 | binary files as long as those fields can be easily viewed by the user. 64 | 65 | 3) No Modified Version of the Font Software may use the Reserved Font 66 | Name(s) unless explicit written permission is granted by the corresponding 67 | Copyright Holder. This restriction only applies to the primary font name as 68 | presented to the users. 69 | 70 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 71 | Software shall not be used to promote, endorse or advertise any 72 | Modified Version, except to acknowledge the contribution(s) of the 73 | Copyright Holder(s) and the Author(s) or with their explicit written 74 | permission. 75 | 76 | 5) The Font Software, modified or unmodified, in part or in whole, 77 | must be distributed entirely under this license, and must not be 78 | distributed under any other license. The requirement for fonts to 79 | remain under this license does not apply to any document created 80 | using the Font Software. 81 | 82 | TERMINATION 83 | This license becomes null and void if any of the above conditions are 84 | not met. 85 | 86 | DISCLAIMER 87 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 88 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 89 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 90 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 91 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 92 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 93 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 94 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 95 | OTHER DEALINGS IN THE FONT SOFTWARE. 96 | -------------------------------------------------------------------------------- /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 = {compare}; 24 | 25 | const GoldenComparators = { 26 | 'image/png': compareImages, 27 | 'text/plain': compareText 28 | }; 29 | 30 | /** 31 | * @param {?Object} actualBuffer 32 | * @param {!Buffer} expectedBuffer 33 | * @return {?{diff: (!Object:undefined), errorMessage: (string|undefined)}} 34 | */ 35 | function compareImages(actualBuffer, expectedBuffer) { 36 | if (!actualBuffer || !(actualBuffer instanceof Buffer)) 37 | return { errorMessage: 'Actual result should be Buffer.' }; 38 | 39 | const actual = PNG.sync.read(actualBuffer); 40 | const expected = PNG.sync.read(expectedBuffer); 41 | if (expected.width !== actual.width || expected.height !== actual.height) { 42 | return { 43 | errorMessage: `Sizes differ: expected image ${expected.width}px X ${expected.height}px, but got ${actual.width}px X ${actual.height}px. ` 44 | }; 45 | } 46 | const diff = new PNG({width: expected.width, height: expected.height}); 47 | const count = pixelmatch(expected.data, actual.data, diff.data, expected.width, expected.height, {threshold: 0.1}); 48 | return count > 0 ? { diff: PNG.sync.write(diff) } : null; 49 | } 50 | 51 | /** 52 | * @param {?Object} actual 53 | * @param {!Buffer} expectedBuffer 54 | * @return {?{diff: (!Object:undefined), errorMessage: (string|undefined)}} 55 | */ 56 | function compareText(actual, expectedBuffer) { 57 | if (typeof actual !== 'string') 58 | return { errorMessage: 'Actual result should be string' }; 59 | const expected = expectedBuffer.toString('utf-8'); 60 | if (expected === actual) 61 | return null; 62 | const diff = new Diff(); 63 | const result = diff.main(expected, actual); 64 | diff.cleanupSemantic(result); 65 | let html = diff.prettyHtml(result); 66 | const diffStylePath = path.join(__dirname, 'diffstyle.css'); 67 | html = `` + html; 68 | return { 69 | diff: html, 70 | diffExtension: '.html' 71 | }; 72 | } 73 | 74 | /** 75 | * @param {?Object} actual 76 | * @param {string} goldenName 77 | * @return {!{pass: boolean, message: (undefined|string)}} 78 | */ 79 | function compare(goldenPath, outputPath, actual, goldenName) { 80 | goldenPath = path.normalize(goldenPath); 81 | outputPath = path.normalize(outputPath); 82 | const expectedPath = path.join(goldenPath, goldenName); 83 | const actualPath = path.join(outputPath, goldenName); 84 | 85 | const messageSuffix = 'Output is saved in "' + path.basename(outputPath + '" directory'); 86 | 87 | if (!fs.existsSync(expectedPath)) { 88 | ensureOutputDir(); 89 | fs.writeFileSync(actualPath, actual); 90 | return { 91 | pass: false, 92 | message: goldenName + ' is missing in golden results. ' + messageSuffix 93 | }; 94 | } 95 | const expected = fs.readFileSync(expectedPath); 96 | const comparator = GoldenComparators[mime.lookup(goldenName)]; 97 | if (!comparator) { 98 | return { 99 | pass: false, 100 | message: 'Failed to find comparator with type ' + mime.lookup(goldenName) + ': ' + goldenName 101 | }; 102 | } 103 | const result = comparator(actual, expected); 104 | if (!result) 105 | return { pass: true }; 106 | ensureOutputDir(); 107 | if (goldenPath === outputPath) { 108 | fs.writeFileSync(addSuffix(actualPath, '-actual'), actual); 109 | } else { 110 | fs.writeFileSync(actualPath, actual); 111 | // Copy expected to the output/ folder for convenience. 112 | fs.writeFileSync(addSuffix(actualPath, '-expected'), expected); 113 | } 114 | if (result.diff) { 115 | const diffPath = addSuffix(actualPath, '-diff', result.diffExtension); 116 | fs.writeFileSync(diffPath, result.diff); 117 | } 118 | 119 | let message = goldenName + ' mismatch!'; 120 | if (result.errorMessage) 121 | message += ' ' + result.errorMessage; 122 | return { 123 | pass: false, 124 | message: message + ' ' + messageSuffix 125 | }; 126 | 127 | function ensureOutputDir() { 128 | if (!fs.existsSync(outputPath)) 129 | fs.mkdirSync(outputPath); 130 | } 131 | } 132 | 133 | /** 134 | * @param {string} filePath 135 | * @param {string} suffix 136 | * @param {string=} customExtension 137 | * @return {string} 138 | */ 139 | function addSuffix(filePath, suffix, customExtension) { 140 | const dirname = path.dirname(filePath); 141 | const ext = path.extname(filePath); 142 | const name = path.basename(filePath, ext); 143 | return path.join(dirname, name + suffix + (customExtension || ext)); 144 | } 145 | -------------------------------------------------------------------------------- /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 | ## Commit Messages 49 | 50 | Commit messages should follow the Semantic Commit Messages format: 51 | 52 | ``` 53 | label(namespace): title 54 | 55 | description 56 | 57 | footer 58 | ``` 59 | 60 | 1. *label* is one of the following: 61 | - `fix` - puppeteer bug fixes 62 | - `feat` - puppeteer features 63 | - `docs` - changes to docs, e.g. `docs(api.md): ..` to change documentation 64 | - `test` - changes to puppeteer tests infrastructure 65 | - `style` - puppeteer code style: spaces/alignment/wrapping etc 66 | - `chore` - build-related work, e.g. doclint changes / travis / appveyor 67 | 1. *namespace* is put in parenthesis after label and is optional 68 | 2. *title* is a brief summary of changes 69 | 3. *description* is **optional**, new-line separated from title and is in present tense 70 | 4. *footer* is **optional**, new-line separated from *description* and contains "fixes" / "references" attribution to github issues 71 | 5. *footer* should also include "BREAKING CHANGE" if current API clients will break due to this change. It should explain what changed and how to get the old behavior. 72 | 73 | Example: 74 | 75 | ``` 76 | fix(Page): fix page.pizza method 77 | 78 | This patch fixes page.pizza so that it works with iframes. 79 | 80 | Fixes #123, Fixes #234 81 | 82 | BREAKING CHANGE: page.pizza now delivers pizza at home by default. 83 | To deliver to a different location, use "deliver" option: 84 | `page.pizza({deliver: 'work'})`. 85 | ``` 86 | 87 | 88 | ## Writing Documentation 89 | 90 | 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. 91 | 92 | To run documentation linter, use 93 | ``` 94 | npm run doc 95 | ``` 96 | 97 | ## Adding New Dependencies 98 | 99 | For all dependencies (both installation and development): 100 | - **Do not add** a dependency if the desired functionality is easily implementable 101 | - if adding a dependency, it should be well-maintained and trustworthy 102 | 103 | A barrier for introducing new installation dependencies is especially high: 104 | - **do not add** installation dependency unless it's critical to project success 105 | 106 | ## Writing Tests 107 | 108 | - every feature should be accompanied by a test 109 | - every public api event/method should be accompanied by a test 110 | - tests should be *hermetic*. Tests should not depend on external services. 111 | - tests should work on all three platforms: Mac, Linux and Win. This is especially important for screenshot tests. 112 | 113 | Puppeteer tests are located in [test/test.js](https://github.com/GoogleChrome/puppeteer/blob/master/test/test.js) 114 | and are written with a [TestRunner](https://github.com/GoogleChrome/puppeteer/tree/master/utils/testrunner) framework. 115 | Despite being named 'unit', these are integration tests, making sure public API methods and events work as expected. 116 | 117 | - To run all tests: 118 | ``` 119 | npm run unit 120 | ``` 121 | - To filter tests by name: 122 | ``` 123 | npm run unit -- --filter=waitFor 124 | ``` 125 | - To run a specific test, substitute the `it` with `fit` (mnemonic rule: '*focus it*'): 126 | ```js 127 | ... 128 | // Using "fit" to run specific test 129 | fit('should work', SX(async function() { 130 | const response = await page.goto(EMPTY_PAGE); 131 | expect(response.ok).toBe(true); 132 | })) 133 | ``` 134 | - To disable a specific test, substitute the `it` with `xit` (mnemonic rule: '*cross it*'): 135 | ```js 136 | ... 137 | // Using "xit" to skip specific test 138 | xit('should work', SX(async function() { 139 | const response = await page.goto(EMPTY_PAGE); 140 | expect(response.ok).toBe(true); 141 | })) 142 | ``` 143 | - To run tests in non-headless mode: 144 | ``` 145 | HEADLESS=false npm run unit 146 | ``` 147 | - To run tests with custom Chromium executable: 148 | ``` 149 | CHROME= npm run unit 150 | ``` 151 | - To run tests in slow-mode: 152 | ``` 153 | HEADLESS=false SLOW_MO=500 npm run unit 154 | ``` 155 | - To debug a test, "focus" a test first and then run: 156 | ``` 157 | npm run debug-unit 158 | ``` 159 | 160 | ## Public API Coverage 161 | 162 | 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. 163 | 164 | Run coverage: 165 | 166 | ``` 167 | npm run coverage 168 | ``` 169 | 170 | ## Debugging Puppeteer 171 | See [Debugging Tips](README.md#debugging-tips) in the readme 172 | 173 | -------------------------------------------------------------------------------- /utils/check_availability.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 Downloader = require('../lib/Downloader'); 19 | const https = require('https'); 20 | const OMAHA_PROXY = 'https://omahaproxy.appspot.com/all.json'; 21 | 22 | const downloader = Downloader.createDefault(); 23 | 24 | const colors = { 25 | reset: '\x1b[0m', 26 | red: '\x1b[31m', 27 | green: '\x1b[32m', 28 | yellow: '\x1b[33m' 29 | }; 30 | 31 | class Table { 32 | /** 33 | * @param {!Array} columnWidths 34 | */ 35 | constructor(columnWidths) { 36 | this.widths = columnWidths; 37 | } 38 | 39 | /** 40 | * @param {!Array} values 41 | */ 42 | drawRow(values) { 43 | console.assert(values.length === this.widths.length); 44 | let row = ''; 45 | for (let i = 0; i < values.length; ++i) 46 | row += padCenter(values[i], this.widths[i]); 47 | console.log(row); 48 | } 49 | } 50 | 51 | if (process.argv.length === 2) { 52 | checkOmahaProxyAvailability(); 53 | return; 54 | } 55 | if (process.argv.length !== 4) { 56 | console.log(` 57 | Usage: node check_revisions.js [fromRevision] [toRevision] 58 | 59 | This script checks availability of different prebuild chromium revisions. 60 | Running command without arguments will check against omahaproxy revisions.`); 61 | return; 62 | } 63 | 64 | const fromRevision = parseInt(process.argv[2], 10); 65 | const toRevision = parseInt(process.argv[3], 10); 66 | checkRangeAvailability(fromRevision, toRevision); 67 | 68 | async function checkOmahaProxyAvailability() { 69 | console.log('Fetching revisions from ' + OMAHA_PROXY); 70 | const platforms = await loadJSON(OMAHA_PROXY); 71 | if (!platforms) { 72 | console.error('ERROR: failed to fetch chromium revisions from omahaproxy.'); 73 | return; 74 | } 75 | const table = new Table([27, 7, 7, 7, 7]); 76 | table.drawRow([''].concat(downloader.supportedPlatforms())); 77 | for (const platform of platforms) { 78 | // Trust only to the main platforms. 79 | if (platform.os !== 'mac' && platform.os !== 'win' && platform.os !== 'win64' && platform.os !== 'linux') 80 | continue; 81 | const osName = platform.os === 'win' ? 'win32' : platform.os; 82 | for (const version of platform.versions) { 83 | if (version.channel !== 'dev' && version.channel !== 'beta' && version.channel !== 'canary' && version.channel !== 'stable') 84 | continue; 85 | const revisionName = padLeft('[' + osName + ' ' + version.channel + ']', 15); 86 | const revision = parseInt(version.branch_base_position, 10); 87 | await checkAndDrawRevisionAvailability(table, revisionName, revision); 88 | } 89 | } 90 | } 91 | 92 | /** 93 | * @param {number} fromRevision 94 | * @param {number} toRevision 95 | */ 96 | async function checkRangeAvailability(fromRevision, toRevision) { 97 | const table = new Table([10, 7, 7, 7, 7]); 98 | table.drawRow([''].concat(downloader.supportedPlatforms())); 99 | const inc = fromRevision < toRevision ? 1 : -1; 100 | for (let revision = fromRevision; revision !== toRevision; revision += inc) 101 | await checkAndDrawRevisionAvailability(table, '', revision); 102 | } 103 | 104 | /** 105 | * @param {!Table} table 106 | * @param {string} name 107 | * @param {number} revision 108 | */ 109 | async function checkAndDrawRevisionAvailability(table, name, revision) { 110 | const promises = []; 111 | for (const platform of downloader.supportedPlatforms()) 112 | promises.push(downloader.canDownloadRevision(platform, revision)); 113 | const availability = await Promise.all(promises); 114 | const allAvailable = availability.every(e => !!e); 115 | const values = [name + ' ' + (allAvailable ? colors.green + revision + colors.reset : revision)]; 116 | for (let i = 0; i < availability.length; ++i) { 117 | const decoration = availability[i] ? '+' : '-'; 118 | const color = availability[i] ? colors.green : colors.red; 119 | values.push(color + decoration + colors.reset); 120 | } 121 | table.drawRow(values); 122 | } 123 | 124 | /** 125 | * @param {string} url 126 | * @return {!Promise} 127 | */ 128 | function loadJSON(url) { 129 | let resolve; 130 | const promise = new Promise(x => resolve = x); 131 | https.get(url, response => { 132 | if (response.statusCode !== 200) { 133 | resolve(null); 134 | return; 135 | } 136 | let body = ''; 137 | response.on('data', function(chunk){ 138 | body += chunk; 139 | }); 140 | response.on('end', function(){ 141 | const json = JSON.parse(body); 142 | resolve(json); 143 | }); 144 | }).on('error', function(e){ 145 | console.error('Error fetching json: ' + e); 146 | resolve(null); 147 | }); 148 | return promise; 149 | } 150 | 151 | /** 152 | * @param {number} size 153 | * @return {string} 154 | */ 155 | function spaceString(size) { 156 | return new Array(size).fill(' ').join(''); 157 | } 158 | 159 | /** 160 | * @param {string} text 161 | * @return {string} 162 | */ 163 | function filterOutColors(text) { 164 | for (const colorName in colors) { 165 | const color = colors[colorName]; 166 | text = text.replace(color, ''); 167 | } 168 | return text; 169 | } 170 | 171 | /** 172 | * @param {string} text 173 | * @param {number} length 174 | * @return {string} 175 | */ 176 | function padLeft(text, length) { 177 | const printableCharacters = filterOutColors(text); 178 | return printableCharacters.length >= length ? text : spaceString(length - text.length) + text; 179 | } 180 | 181 | /** 182 | * @param {string} text 183 | * @param {number} length 184 | * @return {string} 185 | */ 186 | function padCenter(text, length) { 187 | const printableCharacters = filterOutColors(text); 188 | if (printableCharacters.length >= length) 189 | return text; 190 | const left = Math.floor((length - printableCharacters.length) / 2); 191 | const right = Math.ceil((length - printableCharacters.length) / 2); 192 | return spaceString(left) + text + spaceString(right); 193 | } 194 | -------------------------------------------------------------------------------- /test/server/SimpleServer.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 http = require('http'); 18 | const https = require('https'); 19 | const url = require('url'); 20 | const fs = require('fs'); 21 | const path = require('path'); 22 | const mime = require('mime'); 23 | const WebSocketServer = require('ws').Server; 24 | 25 | const fulfillSymbol = Symbol('fullfill callback'); 26 | const rejectSymbol = Symbol('reject callback'); 27 | 28 | class SimpleServer { 29 | /** 30 | * @param {string} dirPath 31 | * @param {number} port 32 | * @return {!SimpleServer} 33 | */ 34 | static async create(dirPath, port) { 35 | const server = new SimpleServer(dirPath, port); 36 | await new Promise(x => server._server.once('listening', x)); 37 | return server; 38 | } 39 | 40 | /** 41 | * @param {string} dirPath 42 | * @param {number} port 43 | * @return {!SimpleServer} 44 | */ 45 | static async createHTTPS(dirPath, port) { 46 | const server = new SimpleServer(dirPath, port, { 47 | key: fs.readFileSync(path.join(__dirname, 'key.pem')), 48 | cert: fs.readFileSync(path.join(__dirname, 'cert.pem')), 49 | passphrase: 'aaaa', 50 | }); 51 | await new Promise(x => server._server.once('listening', x)); 52 | return server; 53 | } 54 | 55 | /** 56 | * @param {string} dirPath 57 | * @param {number} port 58 | * @param {!Object=} sslOptions 59 | */ 60 | constructor(dirPath, port, sslOptions) { 61 | if (sslOptions) 62 | this._server = https.createServer(sslOptions, this._onRequest.bind(this)); 63 | else 64 | this._server = http.createServer(this._onRequest.bind(this)); 65 | this._server.on('connection', socket => this._onSocket(socket)); 66 | this._wsServer = new WebSocketServer({server: this._server}); 67 | this._wsServer.on('connection', this._onWebSocketConnection.bind(this)); 68 | this._server.listen(port); 69 | this._dirPath = dirPath; 70 | 71 | /** @type {!Set} */ 72 | this._sockets = new Set(); 73 | 74 | /** @type {!Map} */ 75 | this._routes = new Map(); 76 | /** @type {!Map} */ 77 | this._auths = new Map(); 78 | /** @type {!Map} */ 79 | this._requestSubscribers = new Map(); 80 | } 81 | 82 | _onSocket(socket) { 83 | this._sockets.add(socket); 84 | // ECONNRESET is a legit error given 85 | // that tab closing simply kills process. 86 | socket.on('error', error => { 87 | if (error.code !== 'ECONNRESET') 88 | throw error; 89 | }); 90 | socket.once('close', () => this._sockets.delete(socket)); 91 | } 92 | 93 | /** 94 | * @param {string} path 95 | * @param {string} username 96 | * @param {string} password 97 | */ 98 | setAuth(path, username, password) { 99 | this._auths.set(path, {username, password}); 100 | } 101 | 102 | async stop() { 103 | this.reset(); 104 | for (const socket of this._sockets) 105 | socket.destroy(); 106 | this._sockets.clear(); 107 | await new Promise(x => this._server.close(x)); 108 | } 109 | 110 | /** 111 | * @param {string} path 112 | * @param {function(!IncomingMessage, !ServerResponse)} handler 113 | */ 114 | setRoute(path, handler) { 115 | this._routes.set(path, handler); 116 | } 117 | 118 | /** 119 | * @param {string} fromPath 120 | * @param {string} toPath 121 | */ 122 | setRedirect(from, to) { 123 | this.setRoute(from, (req, res) => { 124 | res.writeHead(302, { location: to }); 125 | res.end(); 126 | }); 127 | } 128 | 129 | /** 130 | * @param {string} path 131 | * @return {!Promise} 132 | */ 133 | waitForRequest(path) { 134 | let promise = this._requestSubscribers.get(path); 135 | if (promise) 136 | return promise; 137 | let fulfill, reject; 138 | promise = new Promise((f, r) => { 139 | fulfill = f; 140 | reject = r; 141 | }); 142 | promise[fulfillSymbol] = fulfill; 143 | promise[rejectSymbol] = reject; 144 | this._requestSubscribers.set(path, promise); 145 | return promise; 146 | } 147 | 148 | reset() { 149 | this._routes.clear(); 150 | this._auths.clear(); 151 | const error = new Error('Static Server has been reset'); 152 | for (const subscriber of this._requestSubscribers.values()) 153 | subscriber[rejectSymbol].call(null, error); 154 | this._requestSubscribers.clear(); 155 | } 156 | 157 | _onRequest(request, response) { 158 | request.on('error', error => { 159 | if (error.code === 'ECONNRESET') 160 | response.end(); 161 | else 162 | throw error; 163 | }); 164 | const pathName = url.parse(request.url).path; 165 | if (this._auths.has(pathName)) { 166 | const auth = this._auths.get(pathName); 167 | const credentials = new Buffer((request.headers.authorization || '').split(' ')[1] || '', 'base64').toString(); 168 | if (credentials !== `${auth.username}:${auth.password}`) { 169 | response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Secure Area"' }); 170 | response.end('HTTP Error 401 Unauthorized: Access is denied'); 171 | return; 172 | } 173 | } 174 | // Notify request subscriber. 175 | if (this._requestSubscribers.has(pathName)) 176 | this._requestSubscribers.get(pathName)[fulfillSymbol].call(null, request); 177 | const handler = this._routes.get(pathName); 178 | if (handler) 179 | handler.call(null, request, response); 180 | else 181 | this.defaultHandler(request, response); 182 | } 183 | 184 | /** 185 | * @param {!IncomingMessage} request 186 | * @param {!ServerResponse} response 187 | */ 188 | defaultHandler(request, response) { 189 | let pathName = url.parse(request.url).path; 190 | if (pathName === '/') 191 | pathName = '/index.html'; 192 | pathName = path.join(this._dirPath, pathName.substring(1)); 193 | 194 | fs.readFile(pathName, function(err, data) { 195 | if (err) { 196 | response.statusCode = 404; 197 | response.end(`File not found: ${pathName}`); 198 | return; 199 | } 200 | response.setHeader('Content-Type', mime.lookup(pathName)); 201 | response.end(data); 202 | }); 203 | } 204 | 205 | _onWebSocketConnection(connection) { 206 | connection.send('opened'); 207 | } 208 | } 209 | 210 | module.exports = SimpleServer; 211 | -------------------------------------------------------------------------------- /utils/doclint/check_public_api/MDBuilder.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 Documentation = require('./Documentation'); 18 | const commonmark = require('commonmark'); 19 | 20 | class MDOutline { 21 | /** 22 | * @param {!Page} page 23 | * @param {string} text 24 | * @return {!MDOutline} 25 | */ 26 | static async create(page, text) { 27 | // Render markdown as HTML. 28 | const reader = new commonmark.Parser(); 29 | const parsed = reader.parse(text); 30 | const writer = new commonmark.HtmlRenderer(); 31 | const html = writer.render(parsed); 32 | 33 | // Extract headings. 34 | await page.setContent(html); 35 | const {classes, errors} = await page.evaluate(() => { 36 | const classes = []; 37 | let currentClass = {}; 38 | let member = {}; 39 | const errors = []; 40 | for (const element of document.body.querySelectorAll('h3, h4, h4 + ul > li')) { 41 | if (element.matches('h3')) { 42 | currentClass = { 43 | name: element.textContent, 44 | members: [], 45 | }; 46 | classes.push(currentClass); 47 | } else if (element.matches('h4')) { 48 | member = { 49 | name: element.textContent, 50 | args: [], 51 | hasReturn: false 52 | }; 53 | currentClass.members.push(member); 54 | } else if (element.matches('li') && element.firstChild.matches && element.firstChild.matches('code')) { 55 | member.args.push(element.firstChild.textContent); 56 | } else if (element.matches('li') && element.firstChild.nodeType === Element.TEXT_NODE && element.firstChild.textContent.toLowerCase().startsWith('retur')) { 57 | member.hasReturn = true; 58 | const expectedText = 'returns: '; 59 | let actualText = element.firstChild.textContent; 60 | let angleIndex = actualText.indexOf('<'); 61 | let spaceIndex = actualText.indexOf(' '); 62 | angleIndex = angleIndex === -1 ? actualText.length : angleIndex; 63 | spaceIndex = spaceIndex === -1 ? actualText.length : spaceIndex + 1; 64 | actualText = actualText.substring(0, Math.min(angleIndex, spaceIndex)); 65 | if (actualText !== expectedText) 66 | errors.push(`${member.name} has mistyped 'return' type declaration: expected exactly '${expectedText}', found '${actualText}'.`); 67 | } 68 | } 69 | return {classes, errors}; 70 | }); 71 | return new MDOutline(classes, errors); 72 | } 73 | 74 | constructor(classes, errors) { 75 | this.classes = []; 76 | this.errors = errors; 77 | const classHeading = /^class: (\w+)$/; 78 | const constructorRegex = /^new (\w+)\((.*)\)$/; 79 | const methodRegex = /^(\w+)\.([\w$]+)\((.*)\)$/; 80 | const propertyRegex = /^(\w+)\.(\w+)$/; 81 | const eventRegex = /^event: '(\w+)'$/; 82 | let currentClassName = null; 83 | let currentClassMembers = []; 84 | for (const cls of classes) { 85 | const match = cls.name.match(classHeading); 86 | if (!match) 87 | continue; 88 | currentClassName = match[1]; 89 | for (const member of cls.members) { 90 | if (constructorRegex.test(member.name)) { 91 | const match = member.name.match(constructorRegex); 92 | handleMethod.call(this, member, match[1], 'constructor', match[2]); 93 | } else if (methodRegex.test(member.name)) { 94 | const match = member.name.match(methodRegex); 95 | handleMethod.call(this, member, match[1], match[2], match[3]); 96 | } else if (propertyRegex.test(member.name)) { 97 | const match = member.name.match(propertyRegex); 98 | handleProperty.call(this, member, match[1], match[2]); 99 | } else if (eventRegex.test(member.name)) { 100 | const match = member.name.match(eventRegex); 101 | handleEvent.call(this, member, match[1]); 102 | } 103 | } 104 | flushClassIfNeeded.call(this); 105 | } 106 | 107 | function handleMethod(member, className, methodName, parameters) { 108 | if (!currentClassName || !className || !methodName || className.toLowerCase() !== currentClassName.toLowerCase()) { 109 | this.errors.push(`Failed to process header as method: ${member.name}`); 110 | return; 111 | } 112 | parameters = parameters.trim().replace(/[\[\]]/g, ''); 113 | if (parameters !== member.args.join(', ')) 114 | this.errors.push(`Heading arguments for "${member.name}" do not match described ones, i.e. "${parameters}" != "${member.args.join(', ')}"`); 115 | const args = member.args.map(arg => new Documentation.Argument(arg)); 116 | const method = Documentation.Member.createMethod(methodName, args, member.hasReturn, false); 117 | currentClassMembers.push(method); 118 | } 119 | 120 | function handleProperty(member, className, propertyName) { 121 | if (!currentClassName || !className || !propertyName || className.toLowerCase() !== currentClassName.toLowerCase()) { 122 | this.errors.push(`Failed to process header as property: ${member.name}`); 123 | return; 124 | } 125 | currentClassMembers.push(Documentation.Member.createProperty(propertyName)); 126 | } 127 | 128 | function handleEvent(member, eventName) { 129 | if (!currentClassName || !eventName) { 130 | this.errors.push(`Failed to process header as event: ${member.name}`); 131 | return; 132 | } 133 | currentClassMembers.push(Documentation.Member.createEvent(eventName)); 134 | } 135 | 136 | function flushClassIfNeeded() { 137 | if (currentClassName === null) 138 | return; 139 | this.classes.push(new Documentation.Class(currentClassName, currentClassMembers)); 140 | currentClassName = null; 141 | currentClassMembers = []; 142 | } 143 | } 144 | } 145 | 146 | /** 147 | * @param {!Page} page 148 | * @param {!Array} sources 149 | * @return {!Promise<{documentation: !Documentation, errors: !Array}>} 150 | */ 151 | module.exports = async function(page, sources) { 152 | const classes = []; 153 | const errors = []; 154 | for (const source of sources) { 155 | const outline = await MDOutline.create(page, source.text()); 156 | classes.push(...outline.classes); 157 | errors.push(...outline.errors); 158 | } 159 | const documentation = new Documentation(classes); 160 | return { documentation, errors }; 161 | }; 162 | 163 | -------------------------------------------------------------------------------- /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, debugError} = require('./helper'); 19 | 20 | class ElementHandle extends JSHandle { 21 | /** 22 | * @param {!Puppeteer.ExecutionContext} context 23 | * @param {!Puppeteer.CDPSession} 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.isConnected) 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 | if (!box) 63 | throw new Error('Node is not visible'); 64 | return { 65 | x: box.x + box.width / 2, 66 | y: box.y + box.height / 2 67 | }; 68 | } 69 | 70 | async hover() { 71 | const {x, y} = await this._visibleCenter(); 72 | await this._page.mouse.move(x, y); 73 | } 74 | 75 | /** 76 | * @param {!Object=} options 77 | */ 78 | async click(options = {}) { 79 | const {x, y} = await this._visibleCenter(); 80 | await this._page.mouse.click(x, y, options); 81 | } 82 | 83 | /** 84 | * @param {!Array} filePaths 85 | * @return {!Promise} 86 | */ 87 | async uploadFile(...filePaths) { 88 | const files = filePaths.map(filePath => path.resolve(filePath)); 89 | const objectId = this._remoteObject.objectId; 90 | return this._client.send('DOM.setFileInputFiles', { objectId, files }); 91 | } 92 | 93 | async tap() { 94 | const {x, y} = await this._visibleCenter(); 95 | await this._page.touchscreen.tap(x, y); 96 | } 97 | 98 | async focus() { 99 | await this.executionContext().evaluate(element => element.focus(), this); 100 | } 101 | 102 | /** 103 | * @param {string} text 104 | * @param {{delay: (number|undefined)}=} options 105 | */ 106 | async type(text, options) { 107 | await this.focus(); 108 | await this._page.keyboard.type(text, options); 109 | } 110 | 111 | /** 112 | * @param {string} key 113 | * @param {!Object=} options 114 | */ 115 | async press(key, options) { 116 | await this.focus(); 117 | await this._page.keyboard.press(key, options); 118 | } 119 | 120 | /** 121 | * @return {!Promise} 122 | */ 123 | async boundingBox() { 124 | const result = await this._client.send('DOM.getBoxModel', { 125 | objectId: this._remoteObject.objectId 126 | }).catch(error => void debugError(error)); 127 | 128 | if (!result) 129 | return null; 130 | 131 | const quad = result.model.border; 132 | const x = Math.min(quad[0], quad[2], quad[4], quad[6]); 133 | const y = Math.min(quad[1], quad[3], quad[5], quad[7]); 134 | const width = Math.max(quad[0], quad[2], quad[4], quad[6]) - x; 135 | const height = Math.max(quad[1], quad[3], quad[5], quad[7]) - y; 136 | 137 | return {x, y, width, height}; 138 | } 139 | 140 | /** 141 | * 142 | * @param {!Object=} options 143 | * @returns {!Promise} 144 | */ 145 | async screenshot(options = {}) { 146 | await this._scrollIntoViewIfNeeded(); 147 | const { layoutViewport: { pageX, pageY } } = await this._client.send('Page.getLayoutMetrics'); 148 | 149 | const boundingBox = await this.boundingBox(); 150 | if (!boundingBox) 151 | throw new Error('Node is not visible'); 152 | const clip = Object.assign({}, boundingBox); 153 | clip.x += pageX; 154 | clip.y += pageY; 155 | return await this._page.screenshot(Object.assign({}, { 156 | clip 157 | }, options)); 158 | } 159 | 160 | /** 161 | * @param {string} selector 162 | * @return {!Promise} 163 | */ 164 | async $(selector) { 165 | const handle = await this.executionContext().evaluateHandle( 166 | (element, selector) => element.querySelector(selector), 167 | this, selector 168 | ); 169 | const element = handle.asElement(); 170 | if (element) 171 | return element; 172 | await handle.dispose(); 173 | return null; 174 | } 175 | 176 | /** 177 | * @param {string} selector 178 | * @return {!Promise>} 179 | */ 180 | async $$(selector) { 181 | const arrayHandle = await this.executionContext().evaluateHandle( 182 | (element, selector) => element.querySelectorAll(selector), 183 | this, selector 184 | ); 185 | const properties = await arrayHandle.getProperties(); 186 | await arrayHandle.dispose(); 187 | const result = []; 188 | for (const property of properties.values()) { 189 | const elementHandle = property.asElement(); 190 | if (elementHandle) 191 | result.push(elementHandle); 192 | } 193 | return result; 194 | } 195 | 196 | /** 197 | * @param {string} expression 198 | * @return {!Promise>} 199 | */ 200 | async $x(expression) { 201 | const arrayHandle = await this.executionContext().evaluateHandle( 202 | (element, expression) => { 203 | const document = element.ownerDocument || element; 204 | const iterator = document.evaluate(expression, element, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE); 205 | const array = []; 206 | let item; 207 | while ((item = iterator.iterateNext())) 208 | array.push(item); 209 | return array; 210 | }, 211 | this, expression 212 | ); 213 | const properties = await arrayHandle.getProperties(); 214 | await arrayHandle.dispose(); 215 | const result = []; 216 | for (const property of properties.values()) { 217 | const elementHandle = property.asElement(); 218 | if (elementHandle) 219 | result.push(elementHandle); 220 | } 221 | return result; 222 | } 223 | } 224 | 225 | module.exports = ElementHandle; 226 | helper.tracePublicAPI(ElementHandle); 227 | -------------------------------------------------------------------------------- /lib/ExecutionContext.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 ExecutionContext { 20 | /** 21 | * @param {!Puppeteer.CDPSession} client 22 | * @param {!Object} contextPayload 23 | * @param {function(*):!JSHandle} objectHandleFactory 24 | */ 25 | constructor(client, contextPayload, objectHandleFactory) { 26 | this._client = client; 27 | this._contextId = contextPayload.id; 28 | 29 | const auxData = contextPayload.auxData || {isDefault: true}; 30 | this._frameId = auxData.frameId || null; 31 | this._isDefault = !!auxData.isDefault; 32 | this._objectHandleFactory = objectHandleFactory; 33 | } 34 | 35 | /** 36 | * @param {Function|string} pageFunction 37 | * @param {...*} args 38 | * @return {!Promise<(!Object|undefined)>} 39 | */ 40 | async evaluate(pageFunction, ...args) { 41 | const handle = await this.evaluateHandle(pageFunction, ...args); 42 | const result = await handle.jsonValue().catch(error => undefined); 43 | await handle.dispose(); 44 | return result; 45 | } 46 | 47 | /** 48 | * @param {Function|string} pageFunction 49 | * @param {...*} args 50 | * @return {!Promise} 51 | */ 52 | async evaluateHandle(pageFunction, ...args) { 53 | if (helper.isString(pageFunction)) { 54 | const contextId = this._contextId; 55 | const expression = pageFunction; 56 | const { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.evaluate', { expression, contextId, returnByValue: false, awaitPromise: true}); 57 | if (exceptionDetails) 58 | throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails)); 59 | return this._objectHandleFactory(remoteObject); 60 | } 61 | 62 | const { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.callFunctionOn', { 63 | functionDeclaration: pageFunction.toString(), 64 | executionContextId: this._contextId, 65 | arguments: args.map(convertArgument.bind(this)), 66 | returnByValue: false, 67 | awaitPromise: true 68 | }); 69 | if (exceptionDetails) 70 | throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails)); 71 | return this._objectHandleFactory(remoteObject); 72 | 73 | /** 74 | * @param {*} arg 75 | * @return {*} 76 | * @this {Frame} 77 | */ 78 | function convertArgument(arg) { 79 | if (Object.is(arg, -0)) 80 | return { unserializableValue: '-0' }; 81 | if (Object.is(arg, Infinity)) 82 | return { unserializableValue: 'Infinity' }; 83 | if (Object.is(arg, -Infinity)) 84 | return { unserializableValue: '-Infinity' }; 85 | if (Object.is(arg, NaN)) 86 | return { unserializableValue: 'NaN' }; 87 | const objectHandle = arg && (arg instanceof JSHandle) ? arg : null; 88 | if (objectHandle) { 89 | if (objectHandle._context !== this) 90 | throw new Error('JSHandles can be evaluated only in the context they were created!'); 91 | if (objectHandle._disposed) 92 | throw new Error('JSHandle is disposed!'); 93 | if (objectHandle._remoteObject.unserializableValue) 94 | return { unserializableValue: objectHandle._remoteObject.unserializableValue }; 95 | if (!objectHandle._remoteObject.objectId) 96 | return { value: objectHandle._remoteObject.value }; 97 | return { objectId: objectHandle._remoteObject.objectId }; 98 | } 99 | return { value: arg }; 100 | } 101 | } 102 | 103 | /** 104 | * @param {!JSHandle} prototypeHandle 105 | * @return {!Promise} 106 | */ 107 | async queryObjects(prototypeHandle) { 108 | console.assert(!prototypeHandle._disposed, 'Prototype JSHandle is disposed!'); 109 | console.assert(prototypeHandle._remoteObject.objectId, 'Prototype JSHandle must not be referencing primitive value'); 110 | const response = await this._client.send('Runtime.queryObjects', { 111 | prototypeObjectId: prototypeHandle._remoteObject.objectId 112 | }); 113 | return this._objectHandleFactory(response.objects); 114 | } 115 | } 116 | 117 | class JSHandle { 118 | /** 119 | * @param {!ExecutionContext} context 120 | * @param {!Puppeteer.CDPSession} client 121 | * @param {!Object} remoteObject 122 | */ 123 | constructor(context, client, remoteObject) { 124 | this._context = context; 125 | this._client = client; 126 | this._remoteObject = remoteObject; 127 | this._disposed = false; 128 | } 129 | 130 | /** 131 | * @return {!ExecutionContext} 132 | */ 133 | executionContext() { 134 | return this._context; 135 | } 136 | 137 | /** 138 | * @param {string} propertyName 139 | * @return {!Promise} 140 | */ 141 | async getProperty(propertyName) { 142 | const objectHandle = await this._context.evaluateHandle((object, propertyName) => { 143 | const result = {__proto__: null}; 144 | result[propertyName] = object[propertyName]; 145 | return result; 146 | }, this, propertyName); 147 | const properties = await objectHandle.getProperties(); 148 | const result = properties.get(propertyName) || null; 149 | await objectHandle.dispose(); 150 | return result; 151 | } 152 | 153 | /** 154 | * @return {!Promise>} 155 | */ 156 | async getProperties() { 157 | const response = await this._client.send('Runtime.getProperties', { 158 | objectId: this._remoteObject.objectId, 159 | ownProperties: true 160 | }); 161 | const result = new Map(); 162 | for (const property of response.result) { 163 | if (!property.enumerable) 164 | continue; 165 | result.set(property.name, this._context._objectHandleFactory(property.value)); 166 | } 167 | return result; 168 | } 169 | 170 | /** 171 | * @return {!Promise} 172 | */ 173 | async jsonValue() { 174 | if (this._remoteObject.objectId) { 175 | const response = await this._client.send('Runtime.callFunctionOn', { 176 | functionDeclaration: 'function() { return this; }', 177 | objectId: this._remoteObject.objectId, 178 | returnByValue: true, 179 | awaitPromise: true, 180 | }); 181 | return helper.valueFromRemoteObject(response.result); 182 | } 183 | return helper.valueFromRemoteObject(this._remoteObject); 184 | } 185 | 186 | /** 187 | * @return {?Puppeteer.ElementHandle} 188 | */ 189 | asElement() { 190 | return null; 191 | } 192 | 193 | async dispose() { 194 | if (this._disposed) 195 | return; 196 | this._disposed = true; 197 | await helper.releaseObject(this._client, this._remoteObject); 198 | } 199 | 200 | /** 201 | * @override 202 | * @return {string} 203 | */ 204 | toString() { 205 | if (this._remoteObject.objectId) { 206 | const type = this._remoteObject.subtype || this._remoteObject.type; 207 | return 'JSHandle@' + type; 208 | } 209 | return 'JSHandle:' + helper.valueFromRemoteObject(this._remoteObject); 210 | } 211 | } 212 | 213 | helper.tracePublicAPI(JSHandle); 214 | module.exports = {ExecutionContext, JSHandle}; 215 | --------------------------------------------------------------------------------