('aria/[role="button"]');
+ const ids = await getIds(found);
+ expect(ids).toEqual([
+ 'node5',
+ 'node6',
+ 'node7',
+ 'node8',
+ 'node10',
+ 'node21',
+ ]);
+ });
+ it('should find by role "heading"', async () => {
+ const { page } = getTestState();
+ const found = await page.$$('aria/[role="heading"]');
+ const ids = await getIds(found);
+ expect(ids).toEqual(['shown', 'hidden', 'node11', 'node13']);
+ });
+ it('should find both ignored and unignored', async () => {
+ const { page } = getTestState();
+ const found = await page.$$('aria/title');
+ const ids = await getIds(found);
+ expect(ids).toEqual(['shown', 'hidden']);
+ });
+ });
+});
diff --git a/test/assert-coverage-test.js b/test/assert-coverage-test.js
new file mode 100644
index 0000000000000..28b25dab948e1
--- /dev/null
+++ b/test/assert-coverage-test.js
@@ -0,0 +1,25 @@
+const { describe, it } = require('mocha');
+const { getCoverageResults } = require('./coverage-utils.js');
+const expect = require('expect');
+
+describe('API coverage test', () => {
+ it('calls every method', () => {
+ if (!process.env.COVERAGE) return;
+
+ const coverageMap = getCoverageResults();
+ const missingMethods = [];
+ for (const method of coverageMap.keys()) {
+ if (!coverageMap.get(method)) missingMethods.push(method);
+ }
+ if (missingMethods.length) {
+ console.error(
+ '\nCoverage check failed: not all API methods called. See above output for list of missing methods.'
+ );
+ console.error(missingMethods.join('\n'));
+ }
+
+ // We know this will fail because we checked above
+ // but we need the actual test to fail.
+ expect(missingMethods.length).toEqual(0);
+ });
+});
diff --git a/test/assets/beforeunload.html b/test/assets/beforeunload.html
new file mode 100644
index 0000000000000..3cef6763f39f5
--- /dev/null
+++ b/test/assets/beforeunload.html
@@ -0,0 +1,10 @@
+beforeunload demo.
+
+
diff --git a/test/assets/cached/one-style-font.css b/test/assets/cached/one-style-font.css
new file mode 100644
index 0000000000000..6178de0350e98
--- /dev/null
+++ b/test/assets/cached/one-style-font.css
@@ -0,0 +1,9 @@
+@font-face {
+ font-family: 'one-style';
+ src: url('./one-style.woff') format('woff');
+}
+
+body {
+ background-color: pink;
+ font-family: 'one-style', sans-serif;
+}
diff --git a/test/assets/cached/one-style-font.html b/test/assets/cached/one-style-font.html
new file mode 100644
index 0000000000000..8e7236dfb35e3
--- /dev/null
+++ b/test/assets/cached/one-style-font.html
@@ -0,0 +1,2 @@
+
+hello, world!
diff --git a/test/assets/cached/one-style.css b/test/assets/cached/one-style.css
new file mode 100644
index 0000000000000..04e7110b4142e
--- /dev/null
+++ b/test/assets/cached/one-style.css
@@ -0,0 +1,3 @@
+body {
+ background-color: pink;
+}
diff --git a/test/assets/cached/one-style.html b/test/assets/cached/one-style.html
new file mode 100644
index 0000000000000..4760f2b9f7e37
--- /dev/null
+++ b/test/assets/cached/one-style.html
@@ -0,0 +1,2 @@
+
+hello, world!
diff --git a/test/assets/chromium-linux.zip b/test/assets/chromium-linux.zip
new file mode 100644
index 0000000000000..9c00ec080d0e9
Binary files /dev/null and b/test/assets/chromium-linux.zip differ
diff --git a/test/assets/consolelog.html b/test/assets/consolelog.html
new file mode 100644
index 0000000000000..4a27803aa9a28
--- /dev/null
+++ b/test/assets/consolelog.html
@@ -0,0 +1,17 @@
+
+
+
+ console.log test
+
+
+
+
+
diff --git a/test/assets/csp.html b/test/assets/csp.html
new file mode 100644
index 0000000000000..34fc1fc1a5c41
--- /dev/null
+++ b/test/assets/csp.html
@@ -0,0 +1 @@
+
diff --git a/test/assets/csscoverage/Dosis-Regular.ttf b/test/assets/csscoverage/Dosis-Regular.ttf
new file mode 100644
index 0000000000000..4b208624e8c69
Binary files /dev/null and b/test/assets/csscoverage/Dosis-Regular.ttf differ
diff --git a/test/assets/csscoverage/OFL.txt b/test/assets/csscoverage/OFL.txt
new file mode 100644
index 0000000000000..a9b3c8b34eaac
--- /dev/null
+++ b/test/assets/csscoverage/OFL.txt
@@ -0,0 +1,95 @@
+Copyright (c) 2011, Edgar Tolentino and Pablo Impallari (www.impallari.com|impallari@gmail.com),
+Copyright (c) 2011, Igino Marini. (www.ikern.com|mail@iginomarini.com),
+with Reserved Font Names "Dosis".
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/test/assets/csscoverage/involved.html b/test/assets/csscoverage/involved.html
new file mode 100644
index 0000000000000..bcd9845b937c8
--- /dev/null
+++ b/test/assets/csscoverage/involved.html
@@ -0,0 +1,26 @@
+
+woof!
+fancy text
+
diff --git a/test/assets/csscoverage/media.html b/test/assets/csscoverage/media.html
new file mode 100644
index 0000000000000..bfb89f8f75a25
--- /dev/null
+++ b/test/assets/csscoverage/media.html
@@ -0,0 +1,4 @@
+
+hello, world
+
diff --git a/test/assets/csscoverage/multiple.html b/test/assets/csscoverage/multiple.html
new file mode 100644
index 0000000000000..0fd97e962a5a1
--- /dev/null
+++ b/test/assets/csscoverage/multiple.html
@@ -0,0 +1,8 @@
+
+
+
diff --git a/test/assets/csscoverage/simple.html b/test/assets/csscoverage/simple.html
new file mode 100644
index 0000000000000..3beae218293a7
--- /dev/null
+++ b/test/assets/csscoverage/simple.html
@@ -0,0 +1,6 @@
+
+hello, world
+
diff --git a/test/assets/csscoverage/sourceurl.html b/test/assets/csscoverage/sourceurl.html
new file mode 100644
index 0000000000000..df4e9c276c770
--- /dev/null
+++ b/test/assets/csscoverage/sourceurl.html
@@ -0,0 +1,7 @@
+
+
diff --git a/test/assets/csscoverage/stylesheet1.css b/test/assets/csscoverage/stylesheet1.css
new file mode 100644
index 0000000000000..60f1eab97137f
--- /dev/null
+++ b/test/assets/csscoverage/stylesheet1.css
@@ -0,0 +1,3 @@
+body {
+ color: red;
+}
diff --git a/test/assets/csscoverage/stylesheet2.css b/test/assets/csscoverage/stylesheet2.css
new file mode 100644
index 0000000000000..a87defb09878f
--- /dev/null
+++ b/test/assets/csscoverage/stylesheet2.css
@@ -0,0 +1,4 @@
+html {
+ margin: 0;
+ padding: 0;
+}
diff --git a/test/assets/csscoverage/unused.html b/test/assets/csscoverage/unused.html
new file mode 100644
index 0000000000000..5b8186a3bf799
--- /dev/null
+++ b/test/assets/csscoverage/unused.html
@@ -0,0 +1,7 @@
+
+
diff --git a/test/assets/detect-touch.html b/test/assets/detect-touch.html
new file mode 100644
index 0000000000000..80a4123fbd970
--- /dev/null
+++ b/test/assets/detect-touch.html
@@ -0,0 +1,12 @@
+
+
+
+ Detect Touch Test
+
+
+
+
+
+
diff --git a/test/assets/digits/0.png b/test/assets/digits/0.png
new file mode 100644
index 0000000000000..ac3c4768edfbe
Binary files /dev/null and b/test/assets/digits/0.png differ
diff --git a/test/assets/digits/1.png b/test/assets/digits/1.png
new file mode 100644
index 0000000000000..6768222729b7a
Binary files /dev/null and b/test/assets/digits/1.png differ
diff --git a/test/assets/digits/2.png b/test/assets/digits/2.png
new file mode 100644
index 0000000000000..b1daa4735d8a8
Binary files /dev/null and b/test/assets/digits/2.png differ
diff --git a/test/assets/digits/3.png b/test/assets/digits/3.png
new file mode 100644
index 0000000000000..6eca99b21bd93
Binary files /dev/null and b/test/assets/digits/3.png differ
diff --git a/test/assets/digits/4.png b/test/assets/digits/4.png
new file mode 100644
index 0000000000000..a721071e2cc4f
Binary files /dev/null and b/test/assets/digits/4.png differ
diff --git a/test/assets/digits/5.png b/test/assets/digits/5.png
new file mode 100644
index 0000000000000..15cb19932a5c1
Binary files /dev/null and b/test/assets/digits/5.png differ
diff --git a/test/assets/digits/6.png b/test/assets/digits/6.png
new file mode 100644
index 0000000000000..639f38439d94e
Binary files /dev/null and b/test/assets/digits/6.png differ
diff --git a/test/assets/digits/7.png b/test/assets/digits/7.png
new file mode 100644
index 0000000000000..5c1150b005a9f
Binary files /dev/null and b/test/assets/digits/7.png differ
diff --git a/test/assets/digits/8.png b/test/assets/digits/8.png
new file mode 100644
index 0000000000000..abb8b48b0b1e5
Binary files /dev/null and b/test/assets/digits/8.png differ
diff --git a/test/assets/digits/9.png b/test/assets/digits/9.png
new file mode 100644
index 0000000000000..6a40a21c6f585
Binary files /dev/null and b/test/assets/digits/9.png differ
diff --git a/test/assets/dynamic-oopif.html b/test/assets/dynamic-oopif.html
new file mode 100644
index 0000000000000..38614d0289e12
--- /dev/null
+++ b/test/assets/dynamic-oopif.html
@@ -0,0 +1,10 @@
+
diff --git a/test/assets/empty.html b/test/assets/empty.html
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/test/assets/error.html b/test/assets/error.html
new file mode 100644
index 0000000000000..130400c00612c
--- /dev/null
+++ b/test/assets/error.html
@@ -0,0 +1,15 @@
+
diff --git a/test/assets/es6/.eslintrc b/test/assets/es6/.eslintrc
new file mode 100644
index 0000000000000..1903e176f51a8
--- /dev/null
+++ b/test/assets/es6/.eslintrc
@@ -0,0 +1,5 @@
+{
+ "parserOptions": {
+ "sourceType": "module"
+ }
+}
\ No newline at end of file
diff --git a/test/assets/es6/es6import.js b/test/assets/es6/es6import.js
new file mode 100644
index 0000000000000..9aac2d4d64e0f
--- /dev/null
+++ b/test/assets/es6/es6import.js
@@ -0,0 +1,2 @@
+import num from './es6module.js';
+window.__es6injected = num;
diff --git a/test/assets/es6/es6module.js b/test/assets/es6/es6module.js
new file mode 100644
index 0000000000000..7a4e8a723a407
--- /dev/null
+++ b/test/assets/es6/es6module.js
@@ -0,0 +1 @@
+export default 42;
diff --git a/test/assets/es6/es6pathimport.js b/test/assets/es6/es6pathimport.js
new file mode 100644
index 0000000000000..eb17a9a3d108f
--- /dev/null
+++ b/test/assets/es6/es6pathimport.js
@@ -0,0 +1,2 @@
+import num from './es6/es6module.js';
+window.__es6injected = num;
diff --git a/test/assets/favicon.ico b/test/assets/favicon.ico
new file mode 100644
index 0000000000000..d4edd507993e8
Binary files /dev/null and b/test/assets/favicon.ico differ
diff --git a/test/assets/file-to-upload.txt b/test/assets/file-to-upload.txt
new file mode 100644
index 0000000000000..b4ad11848946d
--- /dev/null
+++ b/test/assets/file-to-upload.txt
@@ -0,0 +1 @@
+contents of the file
\ No newline at end of file
diff --git a/test/assets/firefox-75.0a1.en-US.linux-x86_64.tar.bz2 b/test/assets/firefox-75.0a1.en-US.linux-x86_64.tar.bz2
new file mode 100644
index 0000000000000..be6d1880276d5
Binary files /dev/null and b/test/assets/firefox-75.0a1.en-US.linux-x86_64.tar.bz2 differ
diff --git a/test/assets/frames/frame.html b/test/assets/frames/frame.html
new file mode 100644
index 0000000000000..8f20d2da9fb10
--- /dev/null
+++ b/test/assets/frames/frame.html
@@ -0,0 +1,8 @@
+
+
+
+Hi, I'm frame
diff --git a/test/assets/frames/frameset.html b/test/assets/frames/frameset.html
new file mode 100644
index 0000000000000..4d56f88839065
--- /dev/null
+++ b/test/assets/frames/frameset.html
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/test/assets/frames/lazy-frame.html b/test/assets/frames/lazy-frame.html
new file mode 100644
index 0000000000000..4821cd76cd2ab
--- /dev/null
+++ b/test/assets/frames/lazy-frame.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/test/assets/frames/nested-frames.html b/test/assets/frames/nested-frames.html
new file mode 100644
index 0000000000000..de1987586ff3d
--- /dev/null
+++ b/test/assets/frames/nested-frames.html
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/test/assets/frames/one-frame-url-fragment.html b/test/assets/frames/one-frame-url-fragment.html
new file mode 100644
index 0000000000000..d1462641ff267
--- /dev/null
+++ b/test/assets/frames/one-frame-url-fragment.html
@@ -0,0 +1 @@
+
diff --git a/test/assets/frames/one-frame.html b/test/assets/frames/one-frame.html
new file mode 100644
index 0000000000000..e941d795a2777
--- /dev/null
+++ b/test/assets/frames/one-frame.html
@@ -0,0 +1 @@
+
diff --git a/test/assets/frames/script.js b/test/assets/frames/script.js
new file mode 100644
index 0000000000000..be22256d16b33
--- /dev/null
+++ b/test/assets/frames/script.js
@@ -0,0 +1 @@
+console.log('Cheers!');
diff --git a/test/assets/frames/style.css b/test/assets/frames/style.css
new file mode 100644
index 0000000000000..5b5436e8740e7
--- /dev/null
+++ b/test/assets/frames/style.css
@@ -0,0 +1,3 @@
+div {
+ color: blue;
+}
diff --git a/test/assets/frames/two-frames.html b/test/assets/frames/two-frames.html
new file mode 100644
index 0000000000000..b2ee853edac52
--- /dev/null
+++ b/test/assets/frames/two-frames.html
@@ -0,0 +1,13 @@
+
+
+
diff --git a/test/assets/global-var.html b/test/assets/global-var.html
new file mode 100644
index 0000000000000..b6be975038f06
--- /dev/null
+++ b/test/assets/global-var.html
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/test/assets/grid.html b/test/assets/grid.html
new file mode 100644
index 0000000000000..0bdbb1220e5a1
--- /dev/null
+++ b/test/assets/grid.html
@@ -0,0 +1,52 @@
+
+
+
diff --git a/test/assets/historyapi.html b/test/assets/historyapi.html
new file mode 100644
index 0000000000000..bacaf9e9a0009
--- /dev/null
+++ b/test/assets/historyapi.html
@@ -0,0 +1,5 @@
+
diff --git a/test/assets/idle-detector.html b/test/assets/idle-detector.html
new file mode 100644
index 0000000000000..83b496c03dfaa
--- /dev/null
+++ b/test/assets/idle-detector.html
@@ -0,0 +1,23 @@
+
+
+
diff --git a/test/assets/initiator.html b/test/assets/initiator.html
new file mode 100644
index 0000000000000..12889d324271c
--- /dev/null
+++ b/test/assets/initiator.html
@@ -0,0 +1,2 @@
+
+
diff --git a/test/assets/initiator.js b/test/assets/initiator.js
new file mode 100644
index 0000000000000..642e775f313b5
--- /dev/null
+++ b/test/assets/initiator.js
@@ -0,0 +1,8 @@
+const script = document.createElement('script');
+script.src = './injectedfile.js';
+document.body.appendChild(script);
+
+const style = document.createElement('link');
+style.rel = 'stylesheet';
+style.href = './injectedstyle.css';
+document.head.appendChild(style);
diff --git a/test/assets/injectedfile.js b/test/assets/injectedfile.js
new file mode 100644
index 0000000000000..c211b62c167a3
--- /dev/null
+++ b/test/assets/injectedfile.js
@@ -0,0 +1,2 @@
+window.__injected = 42;
+window.__injectedError = new Error('hi');
diff --git a/test/assets/injectedstyle.css b/test/assets/injectedstyle.css
new file mode 100644
index 0000000000000..aa1634c255034
--- /dev/null
+++ b/test/assets/injectedstyle.css
@@ -0,0 +1,3 @@
+body {
+ background-color: red;
+}
diff --git a/test/assets/inner-frame1.html b/test/assets/inner-frame1.html
new file mode 100644
index 0000000000000..00f19ec166b19
--- /dev/null
+++ b/test/assets/inner-frame1.html
@@ -0,0 +1,10 @@
+
diff --git a/test/assets/inner-frame2.html b/test/assets/inner-frame2.html
new file mode 100644
index 0000000000000..9a236cc48f035
--- /dev/null
+++ b/test/assets/inner-frame2.html
@@ -0,0 +1 @@
+click
diff --git a/test/assets/input/button.html b/test/assets/input/button.html
new file mode 100644
index 0000000000000..d4c6e13fd2801
--- /dev/null
+++ b/test/assets/input/button.html
@@ -0,0 +1,16 @@
+
+
+
+ Button test
+
+
+
+ Click target
+
+
+
\ No newline at end of file
diff --git a/test/assets/input/checkbox.html b/test/assets/input/checkbox.html
new file mode 100644
index 0000000000000..ca56762e2b3ab
--- /dev/null
+++ b/test/assets/input/checkbox.html
@@ -0,0 +1,42 @@
+
+
+
+ Selection Test
+
+
+ Remember Me
+
+
+
+
diff --git a/test/assets/input/drag-and-drop.html b/test/assets/input/drag-and-drop.html
new file mode 100644
index 0000000000000..bc376a50451e0
--- /dev/null
+++ b/test/assets/input/drag-and-drop.html
@@ -0,0 +1,46 @@
+
+
+
+ Drag-and-drop test
+
+
+
+ drag me
+
+
+
+
diff --git a/test/assets/input/fileupload.html b/test/assets/input/fileupload.html
new file mode 100644
index 0000000000000..55fd7c5006016
--- /dev/null
+++ b/test/assets/input/fileupload.html
@@ -0,0 +1,9 @@
+
+
+
+ File upload test
+
+
+
+
+
\ No newline at end of file
diff --git a/test/assets/input/keyboard.html b/test/assets/input/keyboard.html
new file mode 100644
index 0000000000000..fd962c7518d66
--- /dev/null
+++ b/test/assets/input/keyboard.html
@@ -0,0 +1,42 @@
+
+
+
+ Keyboard test
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/assets/input/mouse-helper.js b/test/assets/input/mouse-helper.js
new file mode 100644
index 0000000000000..4f2824dceb021
--- /dev/null
+++ b/test/assets/input/mouse-helper.js
@@ -0,0 +1,74 @@
+// This injects a box into the page that moves with the mouse;
+// Useful for debugging
+(function () {
+ const box = document.createElement('div');
+ box.classList.add('mouse-helper');
+ const styleElement = document.createElement('style');
+ styleElement.innerHTML = `
+ .mouse-helper {
+ pointer-events: none;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 20px;
+ height: 20px;
+ background: rgba(0,0,0,.4);
+ border: 1px solid white;
+ border-radius: 10px;
+ margin-left: -10px;
+ margin-top: -10px;
+ transition: background .2s, border-radius .2s, border-color .2s;
+ }
+ .mouse-helper.button-1 {
+ transition: none;
+ background: rgba(0,0,0,0.9);
+ }
+ .mouse-helper.button-2 {
+ transition: none;
+ border-color: rgba(0,0,255,0.9);
+ }
+ .mouse-helper.button-3 {
+ transition: none;
+ border-radius: 4px;
+ }
+ .mouse-helper.button-4 {
+ transition: none;
+ border-color: rgba(255,0,0,0.9);
+ }
+ .mouse-helper.button-5 {
+ transition: none;
+ border-color: rgba(0,255,0,0.9);
+ }
+ `;
+ document.head.appendChild(styleElement);
+ document.body.appendChild(box);
+ document.addEventListener(
+ 'mousemove',
+ (event) => {
+ box.style.left = event.pageX + 'px';
+ box.style.top = event.pageY + 'px';
+ updateButtons(event.buttons);
+ },
+ true
+ );
+ document.addEventListener(
+ 'mousedown',
+ (event) => {
+ updateButtons(event.buttons);
+ box.classList.add('button-' + event.which);
+ },
+ true
+ );
+ document.addEventListener(
+ 'mouseup',
+ (event) => {
+ updateButtons(event.buttons);
+ box.classList.remove('button-' + event.which);
+ },
+ true
+ );
+ function updateButtons(buttons) {
+ for (let i = 0; i < 5; i++)
+ box.classList.toggle('button-' + i, buttons & (1 << i));
+ }
+})();
diff --git a/test/assets/input/rotatedButton.html b/test/assets/input/rotatedButton.html
new file mode 100644
index 0000000000000..1bce66cf5e261
--- /dev/null
+++ b/test/assets/input/rotatedButton.html
@@ -0,0 +1,21 @@
+
+
+
+ Rotated button test
+
+
+
+ Click target
+
+
+
+
diff --git a/test/assets/input/scrollable.html b/test/assets/input/scrollable.html
new file mode 100644
index 0000000000000..75757824a4a1a
--- /dev/null
+++ b/test/assets/input/scrollable.html
@@ -0,0 +1,37 @@
+
+
+
+ Scrollable test
+
+
+
+
+
+
diff --git a/test/assets/input/select.html b/test/assets/input/select.html
new file mode 100644
index 0000000000000..879a537a766fe
--- /dev/null
+++ b/test/assets/input/select.html
@@ -0,0 +1,69 @@
+
+
+
+ Selection Test
+
+
+
+ Black
+ Blue
+ Brown
+ Cyan
+ Gray
+ Green
+ Indigo
+ Magenta
+ Orange
+ Pink
+ Purple
+ Red
+ Violet
+ White
+ Yellow
+
+
+
+
diff --git a/test/assets/input/textarea.html b/test/assets/input/textarea.html
new file mode 100644
index 0000000000000..6d77f3106d31e
--- /dev/null
+++ b/test/assets/input/textarea.html
@@ -0,0 +1,15 @@
+
+
+
+ Textarea test
+
+
+
+
+
+
+
diff --git a/test/assets/input/touches.html b/test/assets/input/touches.html
new file mode 100644
index 0000000000000..4392cfacbd5bb
--- /dev/null
+++ b/test/assets/input/touches.html
@@ -0,0 +1,35 @@
+
+
+
+ Touch test
+
+
+
+ Click target
+
+
+
\ No newline at end of file
diff --git a/test/assets/input/wheel.html b/test/assets/input/wheel.html
new file mode 100644
index 0000000000000..3d093a993e70f
--- /dev/null
+++ b/test/assets/input/wheel.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+ Element: wheel event - Scaling_an_element_via_the_wheel - code sample
+
+
+ Scale me with your mouse wheel.
+
+
+
diff --git a/test/assets/jscoverage/eval.html b/test/assets/jscoverage/eval.html
new file mode 100644
index 0000000000000..838ae28763d9b
--- /dev/null
+++ b/test/assets/jscoverage/eval.html
@@ -0,0 +1 @@
+
diff --git a/test/assets/jscoverage/involved.html b/test/assets/jscoverage/involved.html
new file mode 100644
index 0000000000000..889c86bed58ed
--- /dev/null
+++ b/test/assets/jscoverage/involved.html
@@ -0,0 +1,15 @@
+
diff --git a/test/assets/jscoverage/multiple.html b/test/assets/jscoverage/multiple.html
new file mode 100644
index 0000000000000..bdef59885b295
--- /dev/null
+++ b/test/assets/jscoverage/multiple.html
@@ -0,0 +1,2 @@
+
+
diff --git a/test/assets/jscoverage/ranges.html b/test/assets/jscoverage/ranges.html
new file mode 100644
index 0000000000000..a537a7da6a707
--- /dev/null
+++ b/test/assets/jscoverage/ranges.html
@@ -0,0 +1,2 @@
+
diff --git a/test/assets/jscoverage/script1.js b/test/assets/jscoverage/script1.js
new file mode 100644
index 0000000000000..3bd241b50e7c4
--- /dev/null
+++ b/test/assets/jscoverage/script1.js
@@ -0,0 +1 @@
+console.log(3);
diff --git a/test/assets/jscoverage/script2.js b/test/assets/jscoverage/script2.js
new file mode 100644
index 0000000000000..3bd241b50e7c4
--- /dev/null
+++ b/test/assets/jscoverage/script2.js
@@ -0,0 +1 @@
+console.log(3);
diff --git a/test/assets/jscoverage/simple.html b/test/assets/jscoverage/simple.html
new file mode 100644
index 0000000000000..49eeeea6ae180
--- /dev/null
+++ b/test/assets/jscoverage/simple.html
@@ -0,0 +1,2 @@
+
diff --git a/test/assets/jscoverage/sourceurl.html b/test/assets/jscoverage/sourceurl.html
new file mode 100644
index 0000000000000..e477750320153
--- /dev/null
+++ b/test/assets/jscoverage/sourceurl.html
@@ -0,0 +1,4 @@
+
diff --git a/test/assets/jscoverage/unused.html b/test/assets/jscoverage/unused.html
new file mode 100644
index 0000000000000..59c4a5a70b44c
--- /dev/null
+++ b/test/assets/jscoverage/unused.html
@@ -0,0 +1 @@
+
diff --git a/test/assets/lazy-oopif-frame.html b/test/assets/lazy-oopif-frame.html
new file mode 100644
index 0000000000000..7c259b6673ac1
--- /dev/null
+++ b/test/assets/lazy-oopif-frame.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/test/assets/main-frame.html b/test/assets/main-frame.html
new file mode 100644
index 0000000000000..0c50feff85828
--- /dev/null
+++ b/test/assets/main-frame.html
@@ -0,0 +1,10 @@
+
diff --git a/test/assets/mobile.html b/test/assets/mobile.html
new file mode 100644
index 0000000000000..8e94b2fe2917e
--- /dev/null
+++ b/test/assets/mobile.html
@@ -0,0 +1 @@
+
diff --git a/test/assets/modernizr.js b/test/assets/modernizr.js
new file mode 100644
index 0000000000000..7991a4ec4025e
--- /dev/null
+++ b/test/assets/modernizr.js
@@ -0,0 +1,3 @@
+/*! modernizr 3.5.0 (Custom Build) | MIT *
+* https://modernizr.com/download/?-touchevents-setclasses !*/
+!function(e,n,t){function o(e,n){return typeof e===n}function s(){var e,n,t,s,a,i,r;for(var l in c)if(c.hasOwnProperty(l)){if(e=[],n=c[l],n.name&&(e.push(n.name.toLowerCase()),n.options&&n.options.aliases&&n.options.aliases.length))for(t=0;t
+ async function sleep(delay) {
+ return new Promise(resolve => setTimeout(resolve, delay));
+ }
+
+ async function main() {
+ const roundOne = Promise.all([
+ fetch('fetch-request-a.js'),
+ fetch('fetch-request-b.js'),
+ fetch('fetch-request-c.js'),
+ ]);
+
+ await roundOne;
+ await sleep(50);
+ await fetch('fetch-request-d.js');
+ }
+
+ main();
+
diff --git a/test/assets/offscreenbuttons.html b/test/assets/offscreenbuttons.html
new file mode 100644
index 0000000000000..e487caf4d35ef
--- /dev/null
+++ b/test/assets/offscreenbuttons.html
@@ -0,0 +1,40 @@
+
+
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
diff --git a/test/assets/one-style.css b/test/assets/one-style.css
new file mode 100644
index 0000000000000..7b26410d8a15e
--- /dev/null
+++ b/test/assets/one-style.css
@@ -0,0 +1,3 @@
+body {
+ background-color: pink;
+}
diff --git a/test/assets/one-style.html b/test/assets/one-style.html
new file mode 100644
index 0000000000000..4760f2b9f7e37
--- /dev/null
+++ b/test/assets/one-style.html
@@ -0,0 +1,2 @@
+
+hello, world!
diff --git a/test/assets/oopif.html b/test/assets/oopif.html
new file mode 100644
index 0000000000000..0761e8ab1134b
--- /dev/null
+++ b/test/assets/oopif.html
@@ -0,0 +1,2 @@
+Navigate within document
+
\ No newline at end of file
diff --git a/test/assets/pdf.html b/test/assets/pdf.html
new file mode 100644
index 0000000000000..987df27ebefda
--- /dev/null
+++ b/test/assets/pdf.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+ PDF
+
+
+ PDF Content
+
+
diff --git a/test/assets/playground.html b/test/assets/playground.html
new file mode 100644
index 0000000000000..828cfb1c70124
--- /dev/null
+++ b/test/assets/playground.html
@@ -0,0 +1,15 @@
+
+
+
+ Playground
+
+
+ A button
+
+ First div
+
+ Second div
+ Inner span
+
+
+
\ No newline at end of file
diff --git a/test/assets/popup/popup.html b/test/assets/popup/popup.html
new file mode 100644
index 0000000000000..b855162c25b27
--- /dev/null
+++ b/test/assets/popup/popup.html
@@ -0,0 +1,9 @@
+
+
+
+ Popup
+
+
+ I am a popup
+
+
diff --git a/test/assets/popup/window-open.html b/test/assets/popup/window-open.html
new file mode 100644
index 0000000000000..d138be1d22ba8
--- /dev/null
+++ b/test/assets/popup/window-open.html
@@ -0,0 +1,11 @@
+
+
+
+ Popup test
+
+
+
+
+
diff --git a/test/assets/pptr.png b/test/assets/pptr.png
new file mode 100644
index 0000000000000..65d87c68e6590
Binary files /dev/null and b/test/assets/pptr.png differ
diff --git a/test/assets/resetcss.html b/test/assets/resetcss.html
new file mode 100644
index 0000000000000..e4e04b1f8a393
--- /dev/null
+++ b/test/assets/resetcss.html
@@ -0,0 +1,50 @@
+
diff --git a/test/assets/self-request.html b/test/assets/self-request.html
new file mode 100644
index 0000000000000..88aff620ffd82
--- /dev/null
+++ b/test/assets/self-request.html
@@ -0,0 +1,5 @@
+
diff --git a/test/assets/serviceworkers/empty/sw.html b/test/assets/serviceworkers/empty/sw.html
new file mode 100644
index 0000000000000..bef85d985b072
--- /dev/null
+++ b/test/assets/serviceworkers/empty/sw.html
@@ -0,0 +1,3 @@
+
diff --git a/test/assets/serviceworkers/empty/sw.js b/test/assets/serviceworkers/empty/sw.js
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/test/assets/serviceworkers/fetch/style.css b/test/assets/serviceworkers/fetch/style.css
new file mode 100644
index 0000000000000..7b26410d8a15e
--- /dev/null
+++ b/test/assets/serviceworkers/fetch/style.css
@@ -0,0 +1,3 @@
+body {
+ background-color: pink;
+}
diff --git a/test/assets/serviceworkers/fetch/sw.html b/test/assets/serviceworkers/fetch/sw.html
new file mode 100644
index 0000000000000..a9d28acb0977c
--- /dev/null
+++ b/test/assets/serviceworkers/fetch/sw.html
@@ -0,0 +1,5 @@
+
+
diff --git a/test/assets/serviceworkers/fetch/sw.js b/test/assets/serviceworkers/fetch/sw.js
new file mode 100644
index 0000000000000..21381484b63f7
--- /dev/null
+++ b/test/assets/serviceworkers/fetch/sw.js
@@ -0,0 +1,7 @@
+self.addEventListener('fetch', (event) => {
+ event.respondWith(fetch(event.request));
+});
+
+self.addEventListener('activate', (event) => {
+ event.waitUntil(clients.claim());
+});
diff --git a/test/assets/shadow.html b/test/assets/shadow.html
new file mode 100644
index 0000000000000..3796ca768c44d
--- /dev/null
+++ b/test/assets/shadow.html
@@ -0,0 +1,17 @@
+
diff --git a/test/assets/simple-extension/content-script.js b/test/assets/simple-extension/content-script.js
new file mode 100644
index 0000000000000..0fd83b90f1eb5
--- /dev/null
+++ b/test/assets/simple-extension/content-script.js
@@ -0,0 +1,2 @@
+console.log('hey from the content-script');
+self.thisIsTheContentScript = true;
diff --git a/test/assets/simple-extension/index.js b/test/assets/simple-extension/index.js
new file mode 100644
index 0000000000000..a0bb3f4eae75a
--- /dev/null
+++ b/test/assets/simple-extension/index.js
@@ -0,0 +1,2 @@
+// Mock script for background extension
+window.MAGIC = 42;
diff --git a/test/assets/simple-extension/manifest.json b/test/assets/simple-extension/manifest.json
new file mode 100644
index 0000000000000..da2cd082edb64
--- /dev/null
+++ b/test/assets/simple-extension/manifest.json
@@ -0,0 +1,14 @@
+{
+ "name": "Simple extension",
+ "version": "0.1",
+ "background": {
+ "scripts": ["index.js"]
+ },
+ "content_scripts": [{
+ "matches": [""],
+ "css": [],
+ "js": ["content-script.js"]
+ }],
+ "permissions": ["background", "activeTab"],
+ "manifest_version": 2
+}
diff --git a/test/assets/simple.json b/test/assets/simple.json
new file mode 100644
index 0000000000000..6d9590305133e
--- /dev/null
+++ b/test/assets/simple.json
@@ -0,0 +1 @@
+{"foo": "bar"}
diff --git a/test/assets/tamperable.html b/test/assets/tamperable.html
new file mode 100644
index 0000000000000..d027e97038573
--- /dev/null
+++ b/test/assets/tamperable.html
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/test/assets/title.html b/test/assets/title.html
new file mode 100644
index 0000000000000..88a86ce412b07
--- /dev/null
+++ b/test/assets/title.html
@@ -0,0 +1 @@
+Woof-Woof
diff --git a/test/assets/worker/worker.html b/test/assets/worker/worker.html
new file mode 100644
index 0000000000000..7de2d9fd9e882
--- /dev/null
+++ b/test/assets/worker/worker.html
@@ -0,0 +1,14 @@
+
+
+
+ Worker test
+
+
+
+
+
\ No newline at end of file
diff --git a/test/assets/worker/worker.js b/test/assets/worker/worker.js
new file mode 100644
index 0000000000000..0626f13e58d70
--- /dev/null
+++ b/test/assets/worker/worker.js
@@ -0,0 +1,16 @@
+console.log('hello from the worker');
+
+function workerFunction() {
+ return 'worker function result';
+}
+
+self.addEventListener('message', (event) => {
+ console.log('got this data: ' + event.data);
+});
+
+(async function () {
+ while (true) {
+ self.postMessage(workerFunction.toString());
+ await new Promise((x) => setTimeout(x, 100));
+ }
+})();
diff --git a/test/assets/wrappedlink.html b/test/assets/wrappedlink.html
new file mode 100644
index 0000000000000..429b6e915671b
--- /dev/null
+++ b/test/assets/wrappedlink.html
@@ -0,0 +1,32 @@
+
+
+
diff --git a/test/browser.spec.ts b/test/browser.spec.ts
new file mode 100644
index 0000000000000..00da218d35229
--- /dev/null
+++ b/test/browser.spec.ts
@@ -0,0 +1,83 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import expect from 'expect';
+import { getTestState, setupTestBrowserHooks } from './mocha-utils'; // eslint-disable-line import/extensions
+
+describe('Browser specs', function () {
+ setupTestBrowserHooks();
+
+ describe('Browser.version', function () {
+ it('should return whether we are in headless', async () => {
+ const { browser, isHeadless, headless } = getTestState();
+
+ const version = await browser.version();
+ expect(version.length).toBeGreaterThan(0);
+ expect(version.startsWith('Headless')).toBe(
+ isHeadless && headless !== 'chrome'
+ );
+ });
+ });
+
+ describe('Browser.userAgent', function () {
+ it('should include WebKit', async () => {
+ const { browser, isChrome } = getTestState();
+
+ const userAgent = await browser.userAgent();
+ expect(userAgent.length).toBeGreaterThan(0);
+ if (isChrome) expect(userAgent).toContain('WebKit');
+ else expect(userAgent).toContain('Gecko');
+ });
+ });
+
+ describe('Browser.target', function () {
+ it('should return browser target', async () => {
+ const { browser } = getTestState();
+
+ const target = browser.target();
+ expect(target.type()).toBe('browser');
+ });
+ });
+
+ describe('Browser.process', function () {
+ it('should return child_process instance', async () => {
+ const { browser } = getTestState();
+
+ const process = await browser.process();
+ expect(process.pid).toBeGreaterThan(0);
+ });
+ it('should not return child_process for remote browser', async () => {
+ const { browser, puppeteer } = getTestState();
+
+ const browserWSEndpoint = browser.wsEndpoint();
+ const remoteBrowser = await puppeteer.connect({ browserWSEndpoint });
+ expect(remoteBrowser.process()).toBe(null);
+ remoteBrowser.disconnect();
+ });
+ });
+
+ describe('Browser.isConnected', () => {
+ it('should set the browser connected state', async () => {
+ const { browser, puppeteer } = getTestState();
+
+ const browserWSEndpoint = browser.wsEndpoint();
+ const newBrowser = await puppeteer.connect({ browserWSEndpoint });
+ expect(newBrowser.isConnected()).toBe(true);
+ newBrowser.disconnect();
+ expect(newBrowser.isConnected()).toBe(false);
+ });
+ });
+});
diff --git a/test/browsercontext.spec.ts b/test/browsercontext.spec.ts
new file mode 100644
index 0000000000000..88ff09b2c7029
--- /dev/null
+++ b/test/browsercontext.spec.ts
@@ -0,0 +1,207 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import expect from 'expect';
+import {
+ getTestState,
+ setupTestBrowserHooks,
+ itFailsFirefox,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+import utils from './utils.js';
+
+describe('BrowserContext', function () {
+ setupTestBrowserHooks();
+ it('should have default context', async () => {
+ const { browser } = getTestState();
+ expect(browser.browserContexts().length).toEqual(1);
+ const defaultContext = browser.browserContexts()[0];
+ expect(defaultContext.isIncognito()).toBe(false);
+ let error = null;
+ await defaultContext.close().catch((error_) => (error = error_));
+ expect(browser.defaultBrowserContext()).toBe(defaultContext);
+ expect(error.message).toContain('cannot be closed');
+ });
+ it('should create new incognito context', async () => {
+ const { browser } = getTestState();
+
+ expect(browser.browserContexts().length).toBe(1);
+ const context = await browser.createIncognitoBrowserContext();
+ expect(context.isIncognito()).toBe(true);
+ expect(browser.browserContexts().length).toBe(2);
+ expect(browser.browserContexts().indexOf(context) !== -1).toBe(true);
+ await context.close();
+ expect(browser.browserContexts().length).toBe(1);
+ });
+ it('should close all belonging targets once closing context', async () => {
+ const { browser } = getTestState();
+
+ expect((await browser.pages()).length).toBe(1);
+
+ const context = await browser.createIncognitoBrowserContext();
+ await context.newPage();
+ expect((await browser.pages()).length).toBe(2);
+ expect((await context.pages()).length).toBe(1);
+
+ await context.close();
+ expect((await browser.pages()).length).toBe(1);
+ });
+ itFailsFirefox('window.open should use parent tab context', async () => {
+ const { browser, server } = getTestState();
+
+ const context = await browser.createIncognitoBrowserContext();
+ const page = await context.newPage();
+ await page.goto(server.EMPTY_PAGE);
+ const [popupTarget] = await Promise.all([
+ utils.waitEvent(browser, 'targetcreated'),
+ page.evaluate<(url: string) => void>(
+ (url) => window.open(url),
+ server.EMPTY_PAGE
+ ),
+ ]);
+ expect(popupTarget.browserContext()).toBe(context);
+ await context.close();
+ });
+ itFailsFirefox('should fire target events', async () => {
+ const { browser, server } = getTestState();
+
+ const context = await browser.createIncognitoBrowserContext();
+ const events = [];
+ context.on('targetcreated', (target) =>
+ events.push('CREATED: ' + target.url())
+ );
+ context.on('targetchanged', (target) =>
+ events.push('CHANGED: ' + target.url())
+ );
+ context.on('targetdestroyed', (target) =>
+ events.push('DESTROYED: ' + target.url())
+ );
+ const page = await context.newPage();
+ await page.goto(server.EMPTY_PAGE);
+ await page.close();
+ expect(events).toEqual([
+ 'CREATED: about:blank',
+ `CHANGED: ${server.EMPTY_PAGE}`,
+ `DESTROYED: ${server.EMPTY_PAGE}`,
+ ]);
+ await context.close();
+ });
+ itFailsFirefox('should wait for a target', async () => {
+ const { browser, puppeteer, server } = getTestState();
+
+ const context = await browser.createIncognitoBrowserContext();
+ let resolved = false;
+
+ const targetPromise = context.waitForTarget(
+ (target) => target.url() === server.EMPTY_PAGE
+ );
+ targetPromise
+ .then(() => (resolved = true))
+ .catch((error) => {
+ resolved = true;
+ if (error instanceof puppeteer.errors.TimeoutError) {
+ console.error(error);
+ } else throw error;
+ });
+ const page = await context.newPage();
+ expect(resolved).toBe(false);
+ await page.goto(server.EMPTY_PAGE);
+ try {
+ const target = await targetPromise;
+ expect(await target.page()).toBe(page);
+ } catch (error) {
+ if (error instanceof puppeteer.errors.TimeoutError) {
+ console.error(error);
+ } else throw error;
+ }
+ await context.close();
+ });
+
+ it('should timeout waiting for a non-existent target', async () => {
+ const { browser, puppeteer, server } = getTestState();
+
+ const context = await browser.createIncognitoBrowserContext();
+ const error = await context
+ .waitForTarget((target) => target.url() === server.EMPTY_PAGE, {
+ timeout: 1,
+ })
+ .catch((error_) => error_);
+ expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
+ await context.close();
+ });
+
+ itFailsFirefox('should isolate localStorage and cookies', async () => {
+ const { browser, server } = getTestState();
+
+ // Create two incognito contexts.
+ const context1 = await browser.createIncognitoBrowserContext();
+ const context2 = await browser.createIncognitoBrowserContext();
+ expect(context1.targets().length).toBe(0);
+ expect(context2.targets().length).toBe(0);
+
+ // Create a page in first incognito context.
+ const page1 = await context1.newPage();
+ await page1.goto(server.EMPTY_PAGE);
+ await page1.evaluate(() => {
+ localStorage.setItem('name', 'page1');
+ document.cookie = 'name=page1';
+ });
+
+ expect(context1.targets().length).toBe(1);
+ expect(context2.targets().length).toBe(0);
+
+ // Create a page in second incognito context.
+ const page2 = await context2.newPage();
+ await page2.goto(server.EMPTY_PAGE);
+ await page2.evaluate(() => {
+ localStorage.setItem('name', 'page2');
+ document.cookie = 'name=page2';
+ });
+
+ expect(context1.targets().length).toBe(1);
+ expect(context1.targets()[0]).toBe(page1.target());
+ expect(context2.targets().length).toBe(1);
+ expect(context2.targets()[0]).toBe(page2.target());
+
+ // Make sure pages don't share localstorage or cookies.
+ expect(await page1.evaluate(() => localStorage.getItem('name'))).toBe(
+ 'page1'
+ );
+ expect(await page1.evaluate(() => document.cookie)).toBe('name=page1');
+ expect(await page2.evaluate(() => localStorage.getItem('name'))).toBe(
+ 'page2'
+ );
+ expect(await page2.evaluate(() => document.cookie)).toBe('name=page2');
+
+ // Cleanup contexts.
+ await Promise.all([context1.close(), context2.close()]);
+ expect(browser.browserContexts().length).toBe(1);
+ });
+
+ itFailsFirefox('should work across sessions', async () => {
+ const { browser, puppeteer } = getTestState();
+
+ expect(browser.browserContexts().length).toBe(1);
+ const context = await browser.createIncognitoBrowserContext();
+ expect(browser.browserContexts().length).toBe(2);
+ const remoteBrowser = await puppeteer.connect({
+ browserWSEndpoint: browser.wsEndpoint(),
+ });
+ const contexts = remoteBrowser.browserContexts();
+ expect(contexts.length).toBe(2);
+ remoteBrowser.disconnect();
+ await context.close();
+ });
+});
diff --git a/test/chromiumonly.spec.ts b/test/chromiumonly.spec.ts
new file mode 100644
index 0000000000000..7b64a70fd396c
--- /dev/null
+++ b/test/chromiumonly.spec.ts
@@ -0,0 +1,159 @@
+/**
+ * Copyright 2019 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import expect from 'expect';
+import {
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+ describeChromeOnly,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+
+describeChromeOnly('Chromium-Specific Launcher tests', function () {
+ describe('Puppeteer.launch |browserURL| option', function () {
+ it('should be able to connect using browserUrl, with and without trailing slash', async () => {
+ const { defaultBrowserOptions, puppeteer } = getTestState();
+
+ const originalBrowser = await puppeteer.launch(
+ Object.assign({}, defaultBrowserOptions, {
+ args: ['--remote-debugging-port=21222'],
+ })
+ );
+ const browserURL = 'http://127.0.0.1:21222';
+
+ const browser1 = await puppeteer.connect({ browserURL });
+ const page1 = await browser1.newPage();
+ expect(await page1.evaluate(() => 7 * 8)).toBe(56);
+ browser1.disconnect();
+
+ const browser2 = await puppeteer.connect({
+ browserURL: browserURL + '/',
+ });
+ const page2 = await browser2.newPage();
+ expect(await page2.evaluate(() => 8 * 7)).toBe(56);
+ browser2.disconnect();
+ originalBrowser.close();
+ });
+ it('should throw when using both browserWSEndpoint and browserURL', async () => {
+ const { defaultBrowserOptions, puppeteer } = getTestState();
+
+ const originalBrowser = await puppeteer.launch(
+ Object.assign({}, defaultBrowserOptions, {
+ args: ['--remote-debugging-port=21222'],
+ })
+ );
+ const browserURL = 'http://127.0.0.1:21222';
+
+ let error = null;
+ await puppeteer
+ .connect({
+ browserURL,
+ browserWSEndpoint: originalBrowser.wsEndpoint(),
+ })
+ .catch((error_) => (error = error_));
+ expect(error.message).toContain(
+ 'Exactly one of browserWSEndpoint, browserURL or transport'
+ );
+
+ originalBrowser.close();
+ });
+ it('should throw when trying to connect to non-existing browser', async () => {
+ const { defaultBrowserOptions, puppeteer } = getTestState();
+
+ const originalBrowser = await puppeteer.launch(
+ Object.assign({}, defaultBrowserOptions, {
+ args: ['--remote-debugging-port=21222'],
+ })
+ );
+ const browserURL = 'http://127.0.0.1:32333';
+
+ let error = null;
+ await puppeteer
+ .connect({ browserURL })
+ .catch((error_) => (error = error_));
+ expect(error.message).toContain(
+ 'Failed to fetch browser webSocket URL from'
+ );
+ originalBrowser.close();
+ });
+ });
+
+ describe('Puppeteer.launch |pipe| option', function () {
+ it('should support the pipe option', async () => {
+ const { defaultBrowserOptions, puppeteer } = getTestState();
+ const options = Object.assign({ pipe: true }, defaultBrowserOptions);
+ const browser = await puppeteer.launch(options);
+ expect((await browser.pages()).length).toBe(1);
+ expect(browser.wsEndpoint()).toBe('');
+ const page = await browser.newPage();
+ expect(await page.evaluate('11 * 11')).toBe(121);
+ await page.close();
+ await browser.close();
+ });
+ it('should support the pipe argument', async () => {
+ const { defaultBrowserOptions, puppeteer } = getTestState();
+ const options = Object.assign({}, defaultBrowserOptions);
+ options.args = ['--remote-debugging-pipe'].concat(options.args || []);
+ const browser = await puppeteer.launch(options);
+ expect(browser.wsEndpoint()).toBe('');
+ const page = await browser.newPage();
+ expect(await page.evaluate('11 * 11')).toBe(121);
+ await page.close();
+ await browser.close();
+ });
+ it('should fire "disconnected" when closing with pipe', async () => {
+ const { defaultBrowserOptions, puppeteer } = getTestState();
+ const options = Object.assign({ pipe: true }, defaultBrowserOptions);
+ const browser = await puppeteer.launch(options);
+ const disconnectedEventPromise = new Promise((resolve) =>
+ browser.once('disconnected', resolve)
+ );
+ // Emulate user exiting browser.
+ browser.process().kill();
+ await disconnectedEventPromise;
+ });
+ });
+});
+
+describeChromeOnly('Chromium-Specific Page Tests', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+ it('Page.setRequestInterception should work with intervention headers', async () => {
+ const { server, page } = getTestState();
+
+ server.setRoute('/intervention', (req, res) =>
+ res.end(`
+
+ `)
+ );
+ server.setRedirect('/intervention.js', '/redirect.js');
+ let serverRequest = null;
+ server.setRoute('/redirect.js', (req, res) => {
+ serverRequest = req;
+ res.end('console.log(1);');
+ });
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => request.continue());
+ await page.goto(server.PREFIX + '/intervention');
+ // Check for feature URL substring rather than https://www.chromestatus.com to
+ // make it work with Edgium.
+ expect(serverRequest.headers.intervention).toContain(
+ 'feature/5718547946799104'
+ );
+ });
+});
diff --git a/test/click.spec.ts b/test/click.spec.ts
new file mode 100644
index 0000000000000..947d8344451d8
--- /dev/null
+++ b/test/click.spec.ts
@@ -0,0 +1,373 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import expect from 'expect';
+import {
+ getTestState,
+ setupTestPageAndContextHooks,
+ setupTestBrowserHooks,
+ itFailsFirefox,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+import utils from './utils.js';
+
+describe('Page.click', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+ it('should click the button', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/button.html');
+ await page.click('button');
+ expect(await page.evaluate(() => globalThis.result)).toBe('Clicked');
+ });
+ it('should click svg', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(`
+
+
+
+ `);
+ await page.click('circle');
+ expect(await page.evaluate(() => globalThis.__CLICKED)).toBe(42);
+ });
+ itFailsFirefox(
+ 'should click the button if window.Node is removed',
+ async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/button.html');
+ await page.evaluate(() => delete window.Node);
+ await page.click('button');
+ expect(await page.evaluate(() => globalThis.result)).toBe('Clicked');
+ }
+ );
+ // @see https://github.com/puppeteer/puppeteer/issues/4281
+ it('should click on a span with an inline element inside', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(`
+
+
+ `);
+ await page.click('span');
+ expect(await page.evaluate(() => globalThis.CLICKED)).toBe(42);
+ });
+ it('should not throw UnhandledPromiseRejection when page closes', async () => {
+ const { page } = getTestState();
+
+ const newPage = await page.browser().newPage();
+ await Promise.all([newPage.close(), newPage.mouse.click(1, 2)]).catch(
+ () => {}
+ );
+ });
+ it('should click the button after navigation ', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/button.html');
+ await page.click('button');
+ await page.goto(server.PREFIX + '/input/button.html');
+ await page.click('button');
+ expect(await page.evaluate(() => globalThis.result)).toBe('Clicked');
+ });
+ itFailsFirefox('should click with disabled javascript', async () => {
+ const { page, server } = getTestState();
+
+ await page.setJavaScriptEnabled(false);
+ await page.goto(server.PREFIX + '/wrappedlink.html');
+ await Promise.all([page.click('a'), page.waitForNavigation()]);
+ expect(page.url()).toBe(server.PREFIX + '/wrappedlink.html#clicked');
+ });
+ it('should click when one of inline box children is outside of viewport', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(`
+
+ woof doggo
+ `);
+ await page.click('span');
+ expect(await page.evaluate(() => globalThis.CLICKED)).toBe(42);
+ });
+ it('should select the text by triple clicking', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/textarea.html');
+ await page.focus('textarea');
+ const text =
+ "This is the text that we are going to try to select. Let's see how it goes.";
+ await page.keyboard.type(text);
+ await page.click('textarea');
+ await page.click('textarea', { clickCount: 2 });
+ await page.click('textarea', { clickCount: 3 });
+ expect(
+ await page.evaluate(() => {
+ const textarea = document.querySelector('textarea');
+ return textarea.value.substring(
+ textarea.selectionStart,
+ textarea.selectionEnd
+ );
+ })
+ ).toBe(text);
+ });
+ it('should click offscreen buttons', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/offscreenbuttons.html');
+ const messages = [];
+ page.on('console', (msg) => messages.push(msg.text()));
+ for (let i = 0; i < 11; ++i) {
+ // We might've scrolled to click a button - reset to (0, 0).
+ await page.evaluate(() => window.scrollTo(0, 0));
+ await page.click(`#btn${i}`);
+ }
+ expect(messages).toEqual([
+ 'button #0 clicked',
+ 'button #1 clicked',
+ 'button #2 clicked',
+ 'button #3 clicked',
+ 'button #4 clicked',
+ 'button #5 clicked',
+ 'button #6 clicked',
+ 'button #7 clicked',
+ 'button #8 clicked',
+ 'button #9 clicked',
+ 'button #10 clicked',
+ ]);
+ });
+
+ it('should click wrapped links', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/wrappedlink.html');
+ await page.click('a');
+ expect(await page.evaluate(() => globalThis.__clicked)).toBe(true);
+ });
+
+ it('should click on checkbox input and toggle', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/checkbox.html');
+ expect(await page.evaluate(() => globalThis.result.check)).toBe(null);
+ await page.click('input#agree');
+ expect(await page.evaluate(() => globalThis.result.check)).toBe(true);
+ expect(await page.evaluate(() => globalThis.result.events)).toEqual([
+ 'mouseover',
+ 'mouseenter',
+ 'mousemove',
+ 'mousedown',
+ 'mouseup',
+ 'click',
+ 'input',
+ 'change',
+ ]);
+ await page.click('input#agree');
+ expect(await page.evaluate(() => globalThis.result.check)).toBe(false);
+ });
+
+ itFailsFirefox('should click on checkbox label and toggle', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/checkbox.html');
+ expect(await page.evaluate(() => globalThis.result.check)).toBe(null);
+ await page.click('label[for="agree"]');
+ expect(await page.evaluate(() => globalThis.result.check)).toBe(true);
+ expect(await page.evaluate(() => globalThis.result.events)).toEqual([
+ 'click',
+ 'input',
+ 'change',
+ ]);
+ await page.click('label[for="agree"]');
+ expect(await page.evaluate(() => globalThis.result.check)).toBe(false);
+ });
+
+ it('should fail to click a missing button', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/button.html');
+ let error = null;
+ await page
+ .click('button.does-not-exist')
+ .catch((error_) => (error = error_));
+ expect(error.message).toBe(
+ 'No node found for selector: button.does-not-exist'
+ );
+ });
+ // @see https://github.com/puppeteer/puppeteer/issues/161
+ it('should not hang with touch-enabled viewports', async () => {
+ const { page, puppeteer } = getTestState();
+
+ await page.setViewport(puppeteer.devices['iPhone 6'].viewport);
+ await page.mouse.down();
+ await page.mouse.move(100, 10);
+ await page.mouse.up();
+ });
+ it('should scroll and click the button', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/scrollable.html');
+ await page.click('#button-5');
+ expect(
+ await page.evaluate(() => document.querySelector('#button-5').textContent)
+ ).toBe('clicked');
+ await page.click('#button-80');
+ expect(
+ await page.evaluate(
+ () => document.querySelector('#button-80').textContent
+ )
+ ).toBe('clicked');
+ });
+ it('should double click the button', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/button.html');
+ await page.evaluate(() => {
+ globalThis.double = false;
+ const button = document.querySelector('button');
+ button.addEventListener('dblclick', () => {
+ globalThis.double = true;
+ });
+ });
+ const button = await page.$('button');
+ await button.click({ clickCount: 2 });
+ expect(await page.evaluate('double')).toBe(true);
+ expect(await page.evaluate('result')).toBe('Clicked');
+ });
+ it('should click a partially obscured button', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/button.html');
+ await page.evaluate(() => {
+ const button = document.querySelector('button');
+ button.textContent = 'Some really long text that will go offscreen';
+ button.style.position = 'absolute';
+ button.style.left = '368px';
+ });
+ await page.click('button');
+ expect(await page.evaluate(() => globalThis.result)).toBe('Clicked');
+ });
+ it('should click a rotated button', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/rotatedButton.html');
+ await page.click('button');
+ expect(await page.evaluate(() => globalThis.result)).toBe('Clicked');
+ });
+ it('should fire contextmenu event on right click', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/scrollable.html');
+ await page.click('#button-8', { button: 'right' });
+ expect(
+ await page.evaluate(() => document.querySelector('#button-8').textContent)
+ ).toBe('context menu');
+ });
+ it('should fire aux event on middle click', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/scrollable.html');
+ await page.click('#button-8', { button: 'middle' });
+ expect(
+ await page.evaluate(() => document.querySelector('#button-8').textContent)
+ ).toBe('aux click');
+ });
+ it('should fire back click', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/scrollable.html');
+ await page.click('#button-8', { button: 'back' });
+ expect(
+ await page.evaluate(() => document.querySelector('#button-8').textContent)
+ ).toBe('back click');
+ });
+ it('should fire forward click', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/scrollable.html');
+ await page.click('#button-8', { button: 'forward' });
+ expect(
+ await page.evaluate(() => document.querySelector('#button-8').textContent)
+ ).toBe('forward click');
+ });
+ // @see https://github.com/puppeteer/puppeteer/issues/206
+ it('should click links which cause navigation', async () => {
+ const { page, server } = getTestState();
+
+ await page.setContent(`empty.html `);
+ // This await should not hang.
+ await page.click('a');
+ });
+ it('should click the button inside an iframe', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.setContent('spacer
');
+ await utils.attachFrame(
+ page,
+ 'button-test',
+ server.PREFIX + '/input/button.html'
+ );
+ const frame = page.frames()[1];
+ const button = await frame.$('button');
+ await button.click();
+ expect(await frame.evaluate(() => globalThis.result)).toBe('Clicked');
+ });
+ // @see https://github.com/puppeteer/puppeteer/issues/4110
+ xit('should click the button with fixed position inside an iframe', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.setViewport({ width: 500, height: 500 });
+ await page.setContent(
+ 'spacer
'
+ );
+ await utils.attachFrame(
+ page,
+ 'button-test',
+ server.CROSS_PROCESS_PREFIX + '/input/button.html'
+ );
+ const frame = page.frames()[1];
+ await frame.$eval('button', (button: HTMLElement) =>
+ button.style.setProperty('position', 'fixed')
+ );
+ await frame.click('button');
+ expect(await frame.evaluate(() => globalThis.result)).toBe('Clicked');
+ });
+ it('should click the button with deviceScaleFactor set', async () => {
+ const { page, server } = getTestState();
+
+ await page.setViewport({ width: 400, height: 400, deviceScaleFactor: 5 });
+ expect(await page.evaluate(() => window.devicePixelRatio)).toBe(5);
+ await page.setContent('spacer
');
+ await utils.attachFrame(
+ page,
+ 'button-test',
+ server.PREFIX + '/input/button.html'
+ );
+ const frame = page.frames()[1];
+ const button = await frame.$('button');
+ await button.click();
+ expect(await frame.evaluate(() => globalThis.result)).toBe('Clicked');
+ });
+});
diff --git a/test/cookies.spec.ts b/test/cookies.spec.ts
new file mode 100644
index 0000000000000..ad7339b0d390a
--- /dev/null
+++ b/test/cookies.spec.ts
@@ -0,0 +1,564 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import expect from 'expect';
+import {
+ expectCookieEquals,
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+ itFailsFirefox,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+
+describe('Cookie specs', () => {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+
+ describe('Page.cookies', function () {
+ it('should return no cookies in pristine browser context', async () => {
+ const { page, server } = getTestState();
+ await page.goto(server.EMPTY_PAGE);
+ expectCookieEquals(await page.cookies(), []);
+ });
+ it('should get a cookie', async () => {
+ const { page, server } = getTestState();
+ await page.goto(server.EMPTY_PAGE);
+ await page.evaluate(() => {
+ document.cookie = 'username=John Doe';
+ });
+
+ expectCookieEquals(await page.cookies(), [
+ {
+ name: 'username',
+ value: 'John Doe',
+ domain: 'localhost',
+ path: '/',
+ sameParty: false,
+ expires: -1,
+ size: 16,
+ httpOnly: false,
+ secure: false,
+ session: true,
+ sourcePort: 8907,
+ sourceScheme: 'NonSecure',
+ },
+ ]);
+ });
+ it('should properly report httpOnly cookie', async () => {
+ const { page, server } = getTestState();
+ server.setRoute('/empty.html', (req, res) => {
+ res.setHeader('Set-Cookie', 'a=b; HttpOnly; Path=/');
+ res.end();
+ });
+ await page.goto(server.EMPTY_PAGE);
+ const cookies = await page.cookies();
+ expect(cookies.length).toBe(1);
+ expect(cookies[0].httpOnly).toBe(true);
+ });
+ it('should properly report "Strict" sameSite cookie', async () => {
+ const { page, server } = getTestState();
+ server.setRoute('/empty.html', (req, res) => {
+ res.setHeader('Set-Cookie', 'a=b; SameSite=Strict');
+ res.end();
+ });
+ await page.goto(server.EMPTY_PAGE);
+ const cookies = await page.cookies();
+ expect(cookies.length).toBe(1);
+ expect(cookies[0].sameSite).toBe('Strict');
+ });
+ it('should properly report "Lax" sameSite cookie', async () => {
+ const { page, server } = getTestState();
+ server.setRoute('/empty.html', (req, res) => {
+ res.setHeader('Set-Cookie', 'a=b; SameSite=Lax');
+ res.end();
+ });
+ await page.goto(server.EMPTY_PAGE);
+ const cookies = await page.cookies();
+ expect(cookies.length).toBe(1);
+ expect(cookies[0].sameSite).toBe('Lax');
+ });
+ it('should get multiple cookies', async () => {
+ const { page, server } = getTestState();
+ await page.goto(server.EMPTY_PAGE);
+ await page.evaluate(() => {
+ document.cookie = 'username=John Doe';
+ document.cookie = 'password=1234';
+ });
+ const cookies = await page.cookies();
+ cookies.sort((a, b) => a.name.localeCompare(b.name));
+ expectCookieEquals(cookies, [
+ {
+ name: 'password',
+ value: '1234',
+ domain: 'localhost',
+ path: '/',
+ sameParty: false,
+ expires: -1,
+ size: 12,
+ httpOnly: false,
+ secure: false,
+ session: true,
+ sourcePort: 8907,
+ sourceScheme: 'NonSecure',
+ },
+ {
+ name: 'username',
+ value: 'John Doe',
+ domain: 'localhost',
+ path: '/',
+ sameParty: false,
+ expires: -1,
+ size: 16,
+ httpOnly: false,
+ secure: false,
+ session: true,
+ sourcePort: 8907,
+ sourceScheme: 'NonSecure',
+ },
+ ]);
+ });
+ itFailsFirefox('should get cookies from multiple urls', async () => {
+ const { page } = getTestState();
+ await page.setCookie(
+ {
+ url: 'https://foo.com',
+ name: 'doggo',
+ value: 'woofs',
+ },
+ {
+ url: 'https://bar.com',
+ name: 'catto',
+ value: 'purrs',
+ },
+ {
+ url: 'https://baz.com',
+ name: 'birdo',
+ value: 'tweets',
+ }
+ );
+ const cookies = await page.cookies('https://foo.com', 'https://baz.com');
+ cookies.sort((a, b) => a.name.localeCompare(b.name));
+ expectCookieEquals(cookies, [
+ {
+ name: 'birdo',
+ value: 'tweets',
+ domain: 'baz.com',
+ path: '/',
+ sameParty: false,
+ expires: -1,
+ size: 11,
+ httpOnly: false,
+ secure: true,
+ session: true,
+ sourcePort: 443,
+ sourceScheme: 'Secure',
+ },
+ {
+ name: 'doggo',
+ value: 'woofs',
+ domain: 'foo.com',
+ path: '/',
+ sameParty: false,
+ expires: -1,
+ size: 10,
+ httpOnly: false,
+ secure: true,
+ session: true,
+ sourcePort: 443,
+ sourceScheme: 'Secure',
+ },
+ ]);
+ });
+ });
+ describe('Page.setCookie', function () {
+ itFailsFirefox('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.setCookie({
+ name: 'password',
+ value: '123456',
+ });
+ expect(await page.evaluate(() => document.cookie)).toEqual(
+ 'password=123456'
+ );
+ });
+ itFailsFirefox('should isolate cookies in browser contexts', async () => {
+ const { page, server, browser } = getTestState();
+
+ const anotherContext = await browser.createIncognitoBrowserContext();
+ const anotherPage = await anotherContext.newPage();
+
+ await page.goto(server.EMPTY_PAGE);
+ await anotherPage.goto(server.EMPTY_PAGE);
+
+ await page.setCookie({ name: 'page1cookie', value: 'page1value' });
+ await anotherPage.setCookie({ name: 'page2cookie', value: 'page2value' });
+
+ const cookies1 = await page.cookies();
+ const cookies2 = await anotherPage.cookies();
+ expect(cookies1.length).toBe(1);
+ expect(cookies2.length).toBe(1);
+ expect(cookies1[0].name).toBe('page1cookie');
+ expect(cookies1[0].value).toBe('page1value');
+ expect(cookies2[0].name).toBe('page2cookie');
+ expect(cookies2[0].value).toBe('page2value');
+ await anotherContext.close();
+ });
+ itFailsFirefox('should set multiple cookies', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.setCookie(
+ {
+ name: 'password',
+ value: '123456',
+ },
+ {
+ name: 'foo',
+ value: 'bar',
+ }
+ );
+ const cookieStrings = await page.evaluate(() => {
+ const cookies = document.cookie.split(';');
+ return cookies.map((cookie) => cookie.trim()).sort();
+ });
+
+ expect(cookieStrings).toEqual(['foo=bar', 'password=123456']);
+ });
+ it('should have |expires| set to |-1| for session cookies', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.setCookie({
+ name: 'password',
+ value: '123456',
+ });
+ const cookies = await page.cookies();
+ expect(cookies[0].session).toBe(true);
+ expect(cookies[0].expires).toBe(-1);
+ });
+ itFailsFirefox('should set cookie with reasonable defaults', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.setCookie({
+ name: 'password',
+ value: '123456',
+ });
+ const cookies = await page.cookies();
+ expectCookieEquals(
+ cookies.sort((a, b) => a.name.localeCompare(b.name)),
+ [
+ {
+ name: 'password',
+ value: '123456',
+ domain: 'localhost',
+ path: '/',
+ sameParty: false,
+ expires: -1,
+ size: 14,
+ httpOnly: false,
+ secure: false,
+ session: true,
+ sourcePort: 80,
+ sourceScheme: 'NonSecure',
+ },
+ ]
+ );
+ });
+ itFailsFirefox('should set a cookie with a path', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/grid.html');
+ await page.setCookie({
+ name: 'gridcookie',
+ value: 'GRID',
+ path: '/grid.html',
+ });
+ expectCookieEquals(await page.cookies(), [
+ {
+ name: 'gridcookie',
+ value: 'GRID',
+ domain: 'localhost',
+ path: '/grid.html',
+ sameParty: false,
+ expires: -1,
+ size: 14,
+ httpOnly: false,
+ secure: false,
+ session: true,
+ sourcePort: 80,
+ sourceScheme: 'NonSecure',
+ },
+ ]);
+ expect(await page.evaluate('document.cookie')).toBe('gridcookie=GRID');
+ await page.goto(server.EMPTY_PAGE);
+ expectCookieEquals(await page.cookies(), []);
+ expect(await page.evaluate('document.cookie')).toBe('');
+ await page.goto(server.PREFIX + '/grid.html');
+ expect(await page.evaluate('document.cookie')).toBe('gridcookie=GRID');
+ });
+ it('should not set a cookie on a blank page', async () => {
+ const { page } = getTestState();
+
+ await page.goto('about:blank');
+ let error = null;
+ try {
+ await page.setCookie({ name: 'example-cookie', value: 'best' });
+ } catch (error_) {
+ error = error_;
+ }
+ expect(error.message).toContain(
+ 'At least one of the url and domain needs to be specified'
+ );
+ });
+ it('should not set a cookie with blank page URL', async () => {
+ const { page, server } = getTestState();
+
+ let error = null;
+ await page.goto(server.EMPTY_PAGE);
+ try {
+ await page.setCookie(
+ { name: 'example-cookie', value: 'best' },
+ { url: 'about:blank', name: 'example-cookie-blank', value: 'best' }
+ );
+ } catch (error_) {
+ error = error_;
+ }
+ expect(error.message).toEqual(
+ `Blank page can not have cookie "example-cookie-blank"`
+ );
+ });
+ it('should not set a cookie on a data URL page', async () => {
+ const { page } = getTestState();
+
+ let error = null;
+ await page.goto('data:,Hello%2C%20World!');
+ try {
+ await page.setCookie({ name: 'example-cookie', value: 'best' });
+ } catch (error_) {
+ error = error_;
+ }
+ expect(error.message).toContain(
+ 'At least one of the url and domain needs to be specified'
+ );
+ });
+ itFailsFirefox(
+ 'should default to setting secure cookie for HTTPS websites',
+ async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const SECURE_URL = 'https://example.com';
+ await page.setCookie({
+ url: SECURE_URL,
+ name: 'foo',
+ value: 'bar',
+ });
+ const [cookie] = await page.cookies(SECURE_URL);
+ expect(cookie.secure).toBe(true);
+ }
+ );
+ it('should be able to set unsecure cookie for HTTP website', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const HTTP_URL = 'http://example.com';
+ await page.setCookie({
+ url: HTTP_URL,
+ name: 'foo',
+ value: 'bar',
+ });
+ const [cookie] = await page.cookies(HTTP_URL);
+ expect(cookie.secure).toBe(false);
+ });
+ itFailsFirefox('should set a cookie on a different domain', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.setCookie({
+ url: 'https://www.example.com',
+ name: 'example-cookie',
+ value: 'best',
+ });
+ expect(await page.evaluate('document.cookie')).toBe('');
+ expectCookieEquals(await page.cookies(), []);
+ expectCookieEquals(await page.cookies('https://www.example.com'), [
+ {
+ name: 'example-cookie',
+ value: 'best',
+ domain: 'www.example.com',
+ path: '/',
+ sameParty: false,
+ expires: -1,
+ size: 18,
+ httpOnly: false,
+ secure: true,
+ session: true,
+ sourcePort: 443,
+ sourceScheme: 'Secure',
+ },
+ ]);
+ });
+ itFailsFirefox('should set cookies from a frame', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/grid.html');
+ await page.setCookie({ name: 'localhost-cookie', value: 'best' });
+ await page.evaluate<(src: string) => Promise>((src) => {
+ let fulfill;
+ const promise = new Promise((x) => (fulfill = x));
+ const iframe = document.createElement('iframe');
+ document.body.appendChild(iframe);
+ iframe.onload = fulfill;
+ iframe.src = src;
+ return promise;
+ }, server.CROSS_PROCESS_PREFIX);
+ await page.setCookie({
+ name: '127-cookie',
+ value: 'worst',
+ url: server.CROSS_PROCESS_PREFIX,
+ });
+ expect(await page.evaluate('document.cookie')).toBe(
+ 'localhost-cookie=best'
+ );
+ expect(await page.frames()[1].evaluate('document.cookie')).toBe('');
+
+ expectCookieEquals(await page.cookies(), [
+ {
+ name: 'localhost-cookie',
+ value: 'best',
+ domain: 'localhost',
+ path: '/',
+ sameParty: false,
+ expires: -1,
+ size: 20,
+ httpOnly: false,
+ secure: false,
+ session: true,
+ sourcePort: 80,
+ sourceScheme: 'NonSecure',
+ },
+ ]);
+
+ expectCookieEquals(await page.cookies(server.CROSS_PROCESS_PREFIX), [
+ {
+ name: '127-cookie',
+ value: 'worst',
+ domain: '127.0.0.1',
+ path: '/',
+ sameParty: false,
+ expires: -1,
+ size: 15,
+ httpOnly: false,
+ secure: false,
+ session: true,
+ sourcePort: 80,
+ sourceScheme: 'NonSecure',
+ },
+ ]);
+ });
+ itFailsFirefox(
+ 'should set secure same-site cookies from a frame',
+ async () => {
+ const { httpsServer, puppeteer, defaultBrowserOptions } =
+ getTestState();
+
+ const browser = await puppeteer.launch({
+ ...defaultBrowserOptions,
+ ignoreHTTPSErrors: true,
+ });
+
+ const page = await browser.newPage();
+
+ try {
+ await page.goto(httpsServer.PREFIX + '/grid.html');
+ await page.evaluate<(src: string) => Promise>((src) => {
+ let fulfill;
+ const promise = new Promise((x) => (fulfill = x));
+ const iframe = document.createElement('iframe');
+ document.body.appendChild(iframe);
+ iframe.onload = fulfill;
+ iframe.src = src;
+ return promise;
+ }, httpsServer.CROSS_PROCESS_PREFIX);
+ await page.setCookie({
+ name: '127-same-site-cookie',
+ value: 'best',
+ url: httpsServer.CROSS_PROCESS_PREFIX,
+ sameSite: 'None',
+ });
+
+ expect(await page.frames()[1].evaluate('document.cookie')).toBe(
+ '127-same-site-cookie=best'
+ );
+ expectCookieEquals(
+ await page.cookies(httpsServer.CROSS_PROCESS_PREFIX),
+ [
+ {
+ name: '127-same-site-cookie',
+ value: 'best',
+ domain: '127.0.0.1',
+ path: '/',
+ sameParty: false,
+ expires: -1,
+ size: 24,
+ httpOnly: false,
+ sameSite: 'None',
+ secure: true,
+ session: true,
+ sourcePort: 443,
+ sourceScheme: 'Secure',
+ },
+ ]
+ );
+ } finally {
+ await page.close();
+ await browser.close();
+ }
+ }
+ );
+ });
+
+ describe('Page.deleteCookie', function () {
+ itFailsFirefox('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.setCookie(
+ {
+ name: 'cookie1',
+ value: '1',
+ },
+ {
+ name: 'cookie2',
+ value: '2',
+ },
+ {
+ name: 'cookie3',
+ value: '3',
+ }
+ );
+ expect(await page.evaluate('document.cookie')).toBe(
+ 'cookie1=1; cookie2=2; cookie3=3'
+ );
+ await page.deleteCookie({ name: 'cookie2' });
+ expect(await page.evaluate('document.cookie')).toBe(
+ 'cookie1=1; cookie3=3'
+ );
+ });
+ });
+});
diff --git a/test/coverage-utils.js b/test/coverage-utils.js
new file mode 100644
index 0000000000000..c23e507f9de2a
--- /dev/null
+++ b/test/coverage-utils.js
@@ -0,0 +1,164 @@
+/**
+ * Copyright 2020 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// TODO (@jackfranklin): convert this to TypeScript and enable type-checking
+// @ts-nocheck
+
+/* We want to ensure that all of Puppeteer's public API is tested via our unit
+ * tests but we can't use a tool like Istanbul because the way it instruments
+ * code unfortunately breaks in Puppeteer where some of that code is then being
+ * executed in a browser context.
+ *
+ * So instead we maintain this coverage code which does the following:
+ * * takes every public method that we expect to be tested
+ * * replaces it with a method that calls the original but also updates a Map of calls
+ * * in an after() test callback it asserts that every public method was called.
+ *
+ * We run this when COVERAGE=1.
+ */
+
+const path = require('path');
+const fs = require('fs');
+
+/**
+ * This object is also used by DocLint to know which classes to check are
+ * documented. It's a pretty hacky solution but DocLint is going away soon as
+ * part of the TSDoc migration.
+ */
+const MODULES_TO_CHECK_FOR_COVERAGE = {
+ Accessibility: '../lib/cjs/puppeteer/common/Accessibility',
+ Browser: '../lib/cjs/puppeteer/common/Browser',
+ BrowserContext: '../lib/cjs/puppeteer/common/Browser',
+ BrowserFetcher: '../lib/cjs/puppeteer/node/BrowserFetcher',
+ CDPSession: '../lib/cjs/puppeteer/common/Connection',
+ ConsoleMessage: '../lib/cjs/puppeteer/common/ConsoleMessage',
+ Coverage: '../lib/cjs/puppeteer/common/Coverage',
+ Dialog: '../lib/cjs/puppeteer/common/Dialog',
+ ElementHandle: '../lib/cjs/puppeteer/common/JSHandle',
+ ExecutionContext: '../lib/cjs/puppeteer/common/ExecutionContext',
+ EventEmitter: '../lib/cjs/puppeteer/common/EventEmitter',
+ FileChooser: '../lib/cjs/puppeteer/common/FileChooser',
+ Frame: '../lib/cjs/puppeteer/common/FrameManager',
+ JSHandle: '../lib/cjs/puppeteer/common/JSHandle',
+ Keyboard: '../lib/cjs/puppeteer/common/Input',
+ Mouse: '../lib/cjs/puppeteer/common/Input',
+ Page: '../lib/cjs/puppeteer/common/Page',
+ Puppeteer: '../lib/cjs/puppeteer/common/Puppeteer',
+ PuppeteerNode: '../lib/cjs/puppeteer/node/Puppeteer',
+ HTTPRequest: '../lib/cjs/puppeteer/common/HTTPRequest',
+ HTTPResponse: '../lib/cjs/puppeteer/common/HTTPResponse',
+ SecurityDetails: '../lib/cjs/puppeteer/common/SecurityDetails',
+ Target: '../lib/cjs/puppeteer/common/Target',
+ TimeoutError: '../lib/cjs/puppeteer/common/Errors',
+ Touchscreen: '../lib/cjs/puppeteer/common/Input',
+ Tracing: '../lib/cjs/puppeteer/common/Tracing',
+ WebWorker: '../lib/cjs/puppeteer/common/WebWorker',
+};
+
+function traceAPICoverage(apiCoverage, className, modulePath) {
+ const loadedModule = require(modulePath);
+ const classType = loadedModule[className];
+
+ if (!classType || !classType.prototype) {
+ console.error(
+ `Coverage error: could not find class for ${className}. Is src/api.ts up to date?`
+ );
+ process.exit(1);
+ }
+ for (const methodName of Reflect.ownKeys(classType.prototype)) {
+ const method = Reflect.get(classType.prototype, methodName);
+ if (
+ methodName === 'constructor' ||
+ typeof methodName !== 'string' ||
+ methodName.startsWith('_') ||
+ typeof method !== 'function'
+ )
+ continue;
+ apiCoverage.set(`${className}.${methodName}`, false);
+ Reflect.set(classType.prototype, methodName, function (...args) {
+ apiCoverage.set(`${className}.${methodName}`, true);
+ return method.call(this, ...args);
+ });
+ }
+
+ /**
+ * If classes emit events, those events are exposed via an object in the same
+ * module named XEmittedEvents, where X is the name of the class. For example,
+ * the Page module exposes PageEmittedEvents.
+ */
+ const eventsName = `${className}EmittedEvents`;
+ if (loadedModule[eventsName]) {
+ for (const event of Object.values(loadedModule[eventsName])) {
+ if (typeof event !== 'symbol')
+ apiCoverage.set(`${className}.emit(${JSON.stringify(event)})`, false);
+ }
+ const method = Reflect.get(classType.prototype, 'emit');
+ Reflect.set(classType.prototype, 'emit', function (event, ...args) {
+ if (typeof event !== 'symbol' && this.listenerCount(event))
+ apiCoverage.set(`${className}.emit(${JSON.stringify(event)})`, true);
+ return method.call(this, event, ...args);
+ });
+ }
+}
+
+const coverageLocation = path.join(__dirname, 'coverage.json');
+
+const clearOldCoverage = () => {
+ try {
+ fs.unlinkSync(coverageLocation);
+ } catch (error) {
+ // do nothing, the file didn't exist
+ }
+};
+const writeCoverage = (coverage) => {
+ fs.writeFileSync(coverageLocation, JSON.stringify([...coverage.entries()]));
+};
+
+const getCoverageResults = () => {
+ let contents;
+ try {
+ contents = fs.readFileSync(coverageLocation, { encoding: 'utf8' });
+ } catch (error) {
+ console.error('Warning: coverage file does not exist or is not readable.');
+ }
+
+ const coverageMap = new Map(JSON.parse(contents));
+ return coverageMap;
+};
+
+const trackCoverage = () => {
+ clearOldCoverage();
+ const coverageMap = new Map();
+
+ return {
+ beforeAll: () => {
+ for (const [className, moduleFilePath] of Object.entries(
+ MODULES_TO_CHECK_FOR_COVERAGE
+ )) {
+ traceAPICoverage(coverageMap, className, moduleFilePath);
+ }
+ },
+ afterAll: () => {
+ writeCoverage(coverageMap);
+ },
+ };
+};
+
+module.exports = {
+ trackCoverage,
+ getCoverageResults,
+ MODULES_TO_CHECK_FOR_COVERAGE,
+};
diff --git a/test/coverage.spec.ts b/test/coverage.spec.ts
new file mode 100644
index 0000000000000..4801a8eed5816
--- /dev/null
+++ b/test/coverage.spec.ts
@@ -0,0 +1,315 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import expect from 'expect';
+import {
+ getTestState,
+ setupTestPageAndContextHooks,
+ setupTestBrowserHooks,
+ describeChromeOnly,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+
+describe('Coverage specs', function () {
+ describeChromeOnly('JSCoverage', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+
+ it('should work', async () => {
+ const { page, server } = getTestState();
+ await page.coverage.startJSCoverage();
+ await page.goto(server.PREFIX + '/jscoverage/simple.html', {
+ waitUntil: 'networkidle0',
+ });
+ const coverage = await page.coverage.stopJSCoverage();
+ expect(coverage.length).toBe(1);
+ expect(coverage[0].url).toContain('/jscoverage/simple.html');
+ expect(coverage[0].ranges).toEqual([
+ { start: 0, end: 17 },
+ { start: 35, end: 61 },
+ ]);
+ });
+ it('should report sourceURLs', async () => {
+ const { page, server } = getTestState();
+
+ await page.coverage.startJSCoverage();
+ await page.goto(server.PREFIX + '/jscoverage/sourceurl.html');
+ const coverage = await page.coverage.stopJSCoverage();
+ expect(coverage.length).toBe(1);
+ expect(coverage[0].url).toBe('nicename.js');
+ });
+ it('should ignore eval() scripts by default', async () => {
+ const { page, server } = getTestState();
+
+ await page.coverage.startJSCoverage();
+ await page.goto(server.PREFIX + '/jscoverage/eval.html');
+ const coverage = await page.coverage.stopJSCoverage();
+ expect(coverage.length).toBe(1);
+ });
+ it("shouldn't ignore eval() scripts if reportAnonymousScripts is true", async () => {
+ const { page, server } = getTestState();
+
+ await page.coverage.startJSCoverage({ reportAnonymousScripts: true });
+ await page.goto(server.PREFIX + '/jscoverage/eval.html');
+ const coverage = await page.coverage.stopJSCoverage();
+ expect(
+ coverage.find((entry) => entry.url.startsWith('debugger://'))
+ ).not.toBe(null);
+ expect(coverage.length).toBe(2);
+ });
+ it('should ignore pptr internal scripts if reportAnonymousScripts is true', async () => {
+ const { page, server } = getTestState();
+
+ await page.coverage.startJSCoverage({ reportAnonymousScripts: true });
+ await page.goto(server.EMPTY_PAGE);
+ await page.evaluate('console.log("foo")');
+ await page.evaluate(() => console.log('bar'));
+ const coverage = await page.coverage.stopJSCoverage();
+ expect(coverage.length).toBe(0);
+ });
+ it('should report multiple scripts', async () => {
+ const { page, server } = getTestState();
+
+ await page.coverage.startJSCoverage();
+ await page.goto(server.PREFIX + '/jscoverage/multiple.html');
+ const coverage = await page.coverage.stopJSCoverage();
+ expect(coverage.length).toBe(2);
+ coverage.sort((a, b) => a.url.localeCompare(b.url));
+ expect(coverage[0].url).toContain('/jscoverage/script1.js');
+ expect(coverage[1].url).toContain('/jscoverage/script2.js');
+ });
+ it('should report right ranges', async () => {
+ const { page, server } = getTestState();
+
+ await page.coverage.startJSCoverage();
+ await page.goto(server.PREFIX + '/jscoverage/ranges.html');
+ const coverage = await page.coverage.stopJSCoverage();
+ expect(coverage.length).toBe(1);
+ const entry = coverage[0];
+ expect(entry.ranges.length).toBe(1);
+ const range = entry.ranges[0];
+ expect(entry.text.substring(range.start, range.end)).toBe(
+ `console.log('used!');`
+ );
+ });
+ it('should report scripts that have no coverage', async () => {
+ const { page, server } = getTestState();
+
+ await page.coverage.startJSCoverage();
+ await page.goto(server.PREFIX + '/jscoverage/unused.html');
+ const coverage = await page.coverage.stopJSCoverage();
+ expect(coverage.length).toBe(1);
+ const entry = coverage[0];
+ expect(entry.url).toContain('unused.html');
+ expect(entry.ranges.length).toBe(0);
+ });
+ it('should work with conditionals', async () => {
+ const { page, server } = getTestState();
+
+ await page.coverage.startJSCoverage();
+ await page.goto(server.PREFIX + '/jscoverage/involved.html');
+ const coverage = await page.coverage.stopJSCoverage();
+ expect(
+ JSON.stringify(coverage, null, 2).replace(/:\d{4}\//g, ':/')
+ ).toBeGolden('jscoverage-involved.txt');
+ });
+ // @see https://crbug.com/990945
+ xit('should not hang when there is a debugger statement', async () => {
+ const { page, server } = getTestState();
+
+ await page.coverage.startJSCoverage();
+ await page.goto(server.EMPTY_PAGE);
+ await page.evaluate(() => {
+ debugger; // eslint-disable-line no-debugger
+ });
+ await page.coverage.stopJSCoverage();
+ });
+ describe('resetOnNavigation', function () {
+ it('should report scripts across navigations when disabled', async () => {
+ const { page, server } = getTestState();
+
+ await page.coverage.startJSCoverage({ resetOnNavigation: false });
+ await page.goto(server.PREFIX + '/jscoverage/multiple.html');
+ await page.goto(server.EMPTY_PAGE);
+ const coverage = await page.coverage.stopJSCoverage();
+ expect(coverage.length).toBe(2);
+ });
+
+ it('should NOT report scripts across navigations when enabled', async () => {
+ const { page, server } = getTestState();
+
+ await page.coverage.startJSCoverage(); // Enabled by default.
+ await page.goto(server.PREFIX + '/jscoverage/multiple.html');
+ await page.goto(server.EMPTY_PAGE);
+ const coverage = await page.coverage.stopJSCoverage();
+ expect(coverage.length).toBe(0);
+ });
+ });
+ describe('includeRawScriptCoverage', function () {
+ it('should not include rawScriptCoverage field when disabled', async () => {
+ const { page, server } = getTestState();
+ await page.coverage.startJSCoverage();
+ await page.goto(server.PREFIX + '/jscoverage/simple.html', {
+ waitUntil: 'networkidle0',
+ });
+ const coverage = await page.coverage.stopJSCoverage();
+ expect(coverage.length).toBe(1);
+ expect(coverage[0].rawScriptCoverage).toBeUndefined();
+ });
+ it('should include rawScriptCoverage field when enabled', async () => {
+ const { page, server } = getTestState();
+ await page.coverage.startJSCoverage({
+ includeRawScriptCoverage: true,
+ });
+ await page.goto(server.PREFIX + '/jscoverage/simple.html', {
+ waitUntil: 'networkidle0',
+ });
+ const coverage = await page.coverage.stopJSCoverage();
+ expect(coverage.length).toBe(1);
+ expect(coverage[0].rawScriptCoverage).toBeTruthy();
+ });
+ });
+ // @see https://crbug.com/990945
+ xit('should not hang when there is a debugger statement', async () => {
+ const { page, server } = getTestState();
+
+ await page.coverage.startJSCoverage();
+ await page.goto(server.EMPTY_PAGE);
+ await page.evaluate(() => {
+ debugger; // eslint-disable-line no-debugger
+ });
+ await page.coverage.stopJSCoverage();
+ });
+ });
+
+ describeChromeOnly('CSSCoverage', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.coverage.startCSSCoverage();
+ await page.goto(server.PREFIX + '/csscoverage/simple.html');
+ const coverage = await page.coverage.stopCSSCoverage();
+ expect(coverage.length).toBe(1);
+ expect(coverage[0].url).toContain('/csscoverage/simple.html');
+ expect(coverage[0].ranges).toEqual([{ start: 1, end: 22 }]);
+ const range = coverage[0].ranges[0];
+ expect(coverage[0].text.substring(range.start, range.end)).toBe(
+ 'div { color: green; }'
+ );
+ });
+ it('should report sourceURLs', async () => {
+ const { page, server } = getTestState();
+
+ await page.coverage.startCSSCoverage();
+ await page.goto(server.PREFIX + '/csscoverage/sourceurl.html');
+ const coverage = await page.coverage.stopCSSCoverage();
+ expect(coverage.length).toBe(1);
+ expect(coverage[0].url).toBe('nicename.css');
+ });
+ it('should report multiple stylesheets', async () => {
+ const { page, server } = getTestState();
+
+ await page.coverage.startCSSCoverage();
+ await page.goto(server.PREFIX + '/csscoverage/multiple.html');
+ const coverage = await page.coverage.stopCSSCoverage();
+ expect(coverage.length).toBe(2);
+ coverage.sort((a, b) => a.url.localeCompare(b.url));
+ expect(coverage[0].url).toContain('/csscoverage/stylesheet1.css');
+ expect(coverage[1].url).toContain('/csscoverage/stylesheet2.css');
+ });
+ it('should report stylesheets that have no coverage', async () => {
+ const { page, server } = getTestState();
+
+ await page.coverage.startCSSCoverage();
+ await page.goto(server.PREFIX + '/csscoverage/unused.html');
+ const coverage = await page.coverage.stopCSSCoverage();
+ expect(coverage.length).toBe(1);
+ expect(coverage[0].url).toBe('unused.css');
+ expect(coverage[0].ranges.length).toBe(0);
+ });
+ it('should work with media queries', async () => {
+ const { page, server } = getTestState();
+
+ await page.coverage.startCSSCoverage();
+ await page.goto(server.PREFIX + '/csscoverage/media.html');
+ const coverage = await page.coverage.stopCSSCoverage();
+ expect(coverage.length).toBe(1);
+ expect(coverage[0].url).toContain('/csscoverage/media.html');
+ expect(coverage[0].ranges).toEqual([{ start: 17, end: 38 }]);
+ });
+ it('should work with complicated usecases', async () => {
+ const { page, server } = getTestState();
+
+ await page.coverage.startCSSCoverage();
+ await page.goto(server.PREFIX + '/csscoverage/involved.html');
+ const coverage = await page.coverage.stopCSSCoverage();
+ expect(
+ JSON.stringify(coverage, null, 2).replace(/:\d{4}\//g, ':/')
+ ).toBeGolden('csscoverage-involved.txt');
+ });
+ it('should ignore injected stylesheets', async () => {
+ const { page } = getTestState();
+
+ await page.coverage.startCSSCoverage();
+ await page.addStyleTag({ content: 'body { margin: 10px;}' });
+ // trigger style recalc
+ const margin = await page.evaluate(
+ () => window.getComputedStyle(document.body).margin
+ );
+ expect(margin).toBe('10px');
+ const coverage = await page.coverage.stopCSSCoverage();
+ expect(coverage.length).toBe(0);
+ });
+ it('should work with a recently loaded stylesheet', async () => {
+ const { page, server } = getTestState();
+
+ await page.coverage.startCSSCoverage();
+ await page.evaluate<(url: string) => Promise>(async (url) => {
+ document.body.textContent = 'hello, world';
+
+ const link = document.createElement('link');
+ link.rel = 'stylesheet';
+ link.href = url;
+ document.head.appendChild(link);
+ await new Promise((x) => (link.onload = x));
+ }, server.PREFIX + '/csscoverage/stylesheet1.css');
+ const coverage = await page.coverage.stopCSSCoverage();
+ expect(coverage.length).toBe(1);
+ });
+ describe('resetOnNavigation', function () {
+ it('should report stylesheets across navigations', async () => {
+ const { page, server } = getTestState();
+
+ await page.coverage.startCSSCoverage({ resetOnNavigation: false });
+ await page.goto(server.PREFIX + '/csscoverage/multiple.html');
+ await page.goto(server.EMPTY_PAGE);
+ const coverage = await page.coverage.stopCSSCoverage();
+ expect(coverage.length).toBe(2);
+ });
+ it('should NOT report scripts across navigations', async () => {
+ const { page, server } = getTestState();
+
+ await page.coverage.startCSSCoverage(); // Enabled by default.
+ await page.goto(server.PREFIX + '/csscoverage/multiple.html');
+ await page.goto(server.EMPTY_PAGE);
+ const coverage = await page.coverage.stopCSSCoverage();
+ expect(coverage.length).toBe(0);
+ });
+ });
+ });
+});
diff --git a/test/defaultbrowsercontext.spec.ts b/test/defaultbrowsercontext.spec.ts
new file mode 100644
index 0000000000000..b84428de0660d
--- /dev/null
+++ b/test/defaultbrowsercontext.spec.ts
@@ -0,0 +1,114 @@
+/**
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import expect from 'expect';
+import {
+ expectCookieEquals,
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+ itFailsFirefox,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+
+describe('DefaultBrowserContext', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+ it('page.cookies() should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.evaluate(() => {
+ document.cookie = 'username=John Doe';
+ });
+ expectCookieEquals(await page.cookies(), [
+ {
+ name: 'username',
+ value: 'John Doe',
+ domain: 'localhost',
+ path: '/',
+ sameParty: false,
+ expires: -1,
+ size: 16,
+ httpOnly: false,
+ secure: false,
+ session: true,
+ sourcePort: 8907,
+ sourceScheme: 'NonSecure',
+ },
+ ]);
+ });
+ itFailsFirefox('page.setCookie() should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.setCookie({
+ name: 'username',
+ value: 'John Doe',
+ });
+ expect(await page.evaluate(() => document.cookie)).toBe(
+ 'username=John Doe'
+ );
+ expectCookieEquals(await page.cookies(), [
+ {
+ name: 'username',
+ value: 'John Doe',
+ domain: 'localhost',
+ path: '/',
+ sameParty: false,
+ expires: -1,
+ size: 16,
+ httpOnly: false,
+ secure: false,
+ session: true,
+ sourcePort: 80,
+ sourceScheme: 'NonSecure',
+ },
+ ]);
+ });
+ itFailsFirefox('page.deleteCookie() should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.setCookie(
+ {
+ name: 'cookie1',
+ value: '1',
+ },
+ {
+ name: 'cookie2',
+ value: '2',
+ }
+ );
+ expect(await page.evaluate('document.cookie')).toBe('cookie1=1; cookie2=2');
+ await page.deleteCookie({ name: 'cookie2' });
+ expect(await page.evaluate('document.cookie')).toBe('cookie1=1');
+ expectCookieEquals(await page.cookies(), [
+ {
+ name: 'cookie1',
+ value: '1',
+ domain: 'localhost',
+ path: '/',
+ sameParty: false,
+ expires: -1,
+ size: 8,
+ httpOnly: false,
+ secure: false,
+ session: true,
+ sourcePort: 80,
+ sourceScheme: 'NonSecure',
+ },
+ ]);
+ });
+});
diff --git a/test/dialog.spec.ts b/test/dialog.spec.ts
new file mode 100644
index 0000000000000..39339f1fa2f0b
--- /dev/null
+++ b/test/dialog.spec.ts
@@ -0,0 +1,74 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import expect from 'expect';
+import sinon from 'sinon';
+
+import {
+ getTestState,
+ setupTestPageAndContextHooks,
+ setupTestBrowserHooks,
+ itFailsFirefox,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+
+describe('Page.Events.Dialog', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+
+ it('should fire', async () => {
+ const { page } = getTestState();
+
+ const onDialog = sinon.stub().callsFake((dialog) => {
+ dialog.accept();
+ });
+ page.on('dialog', onDialog);
+
+ await page.evaluate(() => alert('yo'));
+
+ expect(onDialog.callCount).toEqual(1);
+ const dialog = onDialog.firstCall.args[0];
+ expect(dialog.type()).toBe('alert');
+ expect(dialog.defaultValue()).toBe('');
+ expect(dialog.message()).toBe('yo');
+ });
+
+ itFailsFirefox('should allow accepting prompts', async () => {
+ const { page } = getTestState();
+
+ const onDialog = sinon.stub().callsFake((dialog) => {
+ dialog.accept('answer!');
+ });
+ page.on('dialog', onDialog);
+
+ const result = await page.evaluate(() => prompt('question?', 'yes.'));
+
+ expect(onDialog.callCount).toEqual(1);
+ const dialog = onDialog.firstCall.args[0];
+ expect(dialog.type()).toBe('prompt');
+ expect(dialog.defaultValue()).toBe('yes.');
+ expect(dialog.message()).toBe('question?');
+
+ expect(result).toBe('answer!');
+ });
+ it('should dismiss the prompt', async () => {
+ const { page } = getTestState();
+
+ page.on('dialog', (dialog) => {
+ dialog.dismiss();
+ });
+ const result = await page.evaluate(() => prompt('question?'));
+ expect(result).toBe(null);
+ });
+});
diff --git a/test/diffstyle.css b/test/diffstyle.css
new file mode 100644
index 0000000000000..202e85f41a208
--- /dev/null
+++ b/test/diffstyle.css
@@ -0,0 +1,13 @@
+body {
+ font-family: monospace;
+ white-space: pre;
+}
+
+ins {
+ background-color: #9cffa0;
+ text-decoration: none;
+}
+
+del {
+ background-color: #ff9e9e;
+}
diff --git a/test/drag-and-drop.spec.ts b/test/drag-and-drop.spec.ts
new file mode 100644
index 0000000000000..01f2257f43c33
--- /dev/null
+++ b/test/drag-and-drop.spec.ts
@@ -0,0 +1,137 @@
+/**
+ * Copyright 2021 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import expect from 'expect';
+import {
+ getTestState,
+ setupTestPageAndContextHooks,
+ setupTestBrowserHooks,
+ describeChromeOnly,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+
+describeChromeOnly('Input.drag', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+ it('should throw an exception if not enabled before usage', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/drag-and-drop.html');
+ const draggable = await page.$('#drag');
+
+ try {
+ await draggable.drag({ x: 1, y: 1 });
+ } catch (error) {
+ expect(error.message).toContain('Drag Interception is not enabled!');
+ }
+ });
+ it('should emit a dragIntercepted event when dragged', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/drag-and-drop.html');
+ expect(page.isDragInterceptionEnabled()).toBe(false);
+ await page.setDragInterception(true);
+ expect(page.isDragInterceptionEnabled()).toBe(true);
+ const draggable = await page.$('#drag');
+ const data = await draggable.drag({ x: 1, y: 1 });
+
+ expect(data.items.length).toBe(1);
+ expect(await page.evaluate(() => globalThis.didDragStart)).toBe(true);
+ });
+ it('should emit a dragEnter', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/drag-and-drop.html');
+ expect(page.isDragInterceptionEnabled()).toBe(false);
+ await page.setDragInterception(true);
+ expect(page.isDragInterceptionEnabled()).toBe(true);
+ const draggable = await page.$('#drag');
+ const data = await draggable.drag({ x: 1, y: 1 });
+ const dropzone = await page.$('#drop');
+ await dropzone.dragEnter(data);
+
+ expect(await page.evaluate(() => globalThis.didDragStart)).toBe(true);
+ expect(await page.evaluate(() => globalThis.didDragEnter)).toBe(true);
+ });
+ it('should emit a dragOver event', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/drag-and-drop.html');
+ expect(page.isDragInterceptionEnabled()).toBe(false);
+ await page.setDragInterception(true);
+ expect(page.isDragInterceptionEnabled()).toBe(true);
+ const draggable = await page.$('#drag');
+ const data = await draggable.drag({ x: 1, y: 1 });
+ const dropzone = await page.$('#drop');
+ await dropzone.dragEnter(data);
+ await dropzone.dragOver(data);
+
+ expect(await page.evaluate(() => globalThis.didDragStart)).toBe(true);
+ expect(await page.evaluate(() => globalThis.didDragEnter)).toBe(true);
+ expect(await page.evaluate(() => globalThis.didDragOver)).toBe(true);
+ });
+ it('can be dropped', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/drag-and-drop.html');
+ expect(page.isDragInterceptionEnabled()).toBe(false);
+ await page.setDragInterception(true);
+ expect(page.isDragInterceptionEnabled()).toBe(true);
+ const draggable = await page.$('#drag');
+ const dropzone = await page.$('#drop');
+ const data = await draggable.drag({ x: 1, y: 1 });
+ await dropzone.dragEnter(data);
+ await dropzone.dragOver(data);
+ await dropzone.drop(data);
+
+ expect(await page.evaluate(() => globalThis.didDragStart)).toBe(true);
+ expect(await page.evaluate(() => globalThis.didDragEnter)).toBe(true);
+ expect(await page.evaluate(() => globalThis.didDragOver)).toBe(true);
+ expect(await page.evaluate(() => globalThis.didDrop)).toBe(true);
+ });
+ it('can be dragged and dropped with a single function', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/drag-and-drop.html');
+ expect(page.isDragInterceptionEnabled()).toBe(false);
+ await page.setDragInterception(true);
+ expect(page.isDragInterceptionEnabled()).toBe(true);
+ const draggable = await page.$('#drag');
+ const dropzone = await page.$('#drop');
+ await draggable.dragAndDrop(dropzone);
+
+ expect(await page.evaluate(() => globalThis.didDragStart)).toBe(true);
+ expect(await page.evaluate(() => globalThis.didDragEnter)).toBe(true);
+ expect(await page.evaluate(() => globalThis.didDragOver)).toBe(true);
+ expect(await page.evaluate(() => globalThis.didDrop)).toBe(true);
+ });
+ it('can be disabled', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/drag-and-drop.html');
+ expect(page.isDragInterceptionEnabled()).toBe(false);
+ await page.setDragInterception(true);
+ expect(page.isDragInterceptionEnabled()).toBe(true);
+ const draggable = await page.$('#drag');
+ await draggable.drag({ x: 1, y: 1 });
+ await page.setDragInterception(false);
+
+ try {
+ await draggable.drag({ x: 1, y: 1 });
+ } catch (error) {
+ expect(error.message).toContain('Drag Interception is not enabled!');
+ }
+ });
+});
diff --git a/test/elementhandle.spec.ts b/test/elementhandle.spec.ts
new file mode 100644
index 0000000000000..e46e6ca0d0e2a
--- /dev/null
+++ b/test/elementhandle.spec.ts
@@ -0,0 +1,557 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import expect from 'expect';
+import sinon from 'sinon';
+import {
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+ describeFailsFirefox,
+ itFailsFirefox,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+
+import utils from './utils.js';
+import { ElementHandle } from '../lib/cjs/puppeteer/common/JSHandle.js';
+
+describe('ElementHandle specs', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+
+ describeFailsFirefox('ElementHandle.boundingBox', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.setViewport({ width: 500, height: 500 });
+ await page.goto(server.PREFIX + '/grid.html');
+ const elementHandle = await page.$('.box:nth-of-type(13)');
+ const box = await elementHandle.boundingBox();
+ expect(box).toEqual({ x: 100, y: 50, width: 50, height: 50 });
+ });
+ it('should handle nested frames', async () => {
+ const { page, server, isChrome } = getTestState();
+
+ await page.setViewport({ width: 500, height: 500 });
+ await page.goto(server.PREFIX + '/frames/nested-frames.html');
+ const nestedFrame = page.frames()[1].childFrames()[1];
+ const elementHandle = await nestedFrame.$('div');
+ const box = await elementHandle.boundingBox();
+ if (isChrome)
+ expect(box).toEqual({ x: 28, y: 182, width: 264, height: 18 });
+ else expect(box).toEqual({ x: 28, y: 182, width: 254, height: 18 });
+ });
+ it('should return null for invisible elements', async () => {
+ const { page } = getTestState();
+
+ await page.setContent('hi
');
+ const element = await page.$('div');
+ expect(await element.boundingBox()).toBe(null);
+ });
+ it('should force a layout', async () => {
+ const { page } = getTestState();
+
+ await page.setViewport({ width: 500, height: 500 });
+ await page.setContent(
+ 'hello
'
+ );
+ const elementHandle = await page.$('div');
+ await page.evaluate<(element: HTMLElement) => void>(
+ (element) => (element.style.height = '200px'),
+ elementHandle
+ );
+ const box = await elementHandle.boundingBox();
+ expect(box).toEqual({ x: 8, y: 8, width: 100, height: 200 });
+ });
+ it('should work with SVG nodes', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(`
+
+
+
+ `);
+ const element = await page.$('#therect');
+ const pptrBoundingBox = await element.boundingBox();
+ const webBoundingBox = await page.evaluate((e: HTMLElement) => {
+ const rect = e.getBoundingClientRect();
+ return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
+ }, element);
+ expect(pptrBoundingBox).toEqual(webBoundingBox);
+ });
+ });
+
+ describeFailsFirefox('ElementHandle.boxModel', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/resetcss.html');
+
+ // Step 1: Add Frame and position it absolutely.
+ await utils.attachFrame(page, 'frame1', server.PREFIX + '/resetcss.html');
+ await page.evaluate(() => {
+ const frame = document.querySelector('#frame1');
+ frame.style.position = 'absolute';
+ frame.style.left = '1px';
+ frame.style.top = '2px';
+ });
+
+ // Step 2: Add div and position it absolutely inside frame.
+ const frame = page.frames()[1];
+ const divHandle = (
+ await frame.evaluateHandle(() => {
+ const div = document.createElement('div');
+ document.body.appendChild(div);
+ div.style.boxSizing = 'border-box';
+ div.style.position = 'absolute';
+ div.style.borderLeft = '1px solid black';
+ div.style.paddingLeft = '2px';
+ div.style.marginLeft = '3px';
+ div.style.left = '4px';
+ div.style.top = '5px';
+ div.style.width = '6px';
+ div.style.height = '7px';
+ return div;
+ })
+ ).asElement();
+
+ // Step 3: query div's boxModel and assert box values.
+ const box = await divHandle.boxModel();
+ expect(box.width).toBe(6);
+ expect(box.height).toBe(7);
+ expect(box.margin[0]).toEqual({
+ x: 1 + 4, // frame.left + div.left
+ y: 2 + 5,
+ });
+ expect(box.border[0]).toEqual({
+ x: 1 + 4 + 3, // frame.left + div.left + div.margin-left
+ y: 2 + 5,
+ });
+ expect(box.padding[0]).toEqual({
+ x: 1 + 4 + 3 + 1, // frame.left + div.left + div.marginLeft + div.borderLeft
+ y: 2 + 5,
+ });
+ expect(box.content[0]).toEqual({
+ x: 1 + 4 + 3 + 1 + 2, // frame.left + div.left + div.marginLeft + div.borderLeft + dif.paddingLeft
+ y: 2 + 5,
+ });
+ });
+
+ it('should return null for invisible elements', async () => {
+ const { page } = getTestState();
+
+ await page.setContent('hi
');
+ const element = await page.$('div');
+ expect(await element.boxModel()).toBe(null);
+ });
+ });
+
+ describe('ElementHandle.contentFrame', function () {
+ itFailsFirefox('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ const elementHandle = await page.$('#frame1');
+ const frame = await elementHandle.contentFrame();
+ expect(frame).toBe(page.frames()[1]);
+ });
+ });
+
+ describe('ElementHandle.click', function () {
+ // See https://github.com/puppeteer/puppeteer/issues/7175
+ itFailsFirefox('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/button.html');
+ const button = await page.$('button');
+ await button.click();
+ expect(await page.evaluate(() => globalThis.result)).toBe('Clicked');
+ });
+ it('should work for Shadow DOM v1', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/shadow.html');
+ const buttonHandle = await page.evaluateHandle(
+ // @ts-expect-error button is expected to be in the page's scope.
+ () => button
+ );
+ await buttonHandle.click();
+ expect(
+ await page.evaluate(
+ // @ts-expect-error clicked is expected to be in the page's scope.
+ () => clicked
+ )
+ ).toBe(true);
+ });
+ it('should work for TextNodes', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/button.html');
+ const buttonTextNode = await page.evaluateHandle(
+ () => document.querySelector('button').firstChild
+ );
+ let error = null;
+ await buttonTextNode.click().catch((error_) => (error = error_));
+ expect(error.message).toBe('Node is not of type HTMLElement');
+ });
+ it('should throw for detached nodes', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/button.html');
+ const button = await page.$('button');
+ await page.evaluate((button: HTMLElement) => button.remove(), button);
+ let error = null;
+ await button.click().catch((error_) => (error = error_));
+ expect(error.message).toBe('Node is detached from document');
+ });
+ it('should throw for hidden nodes', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/button.html');
+ const button = await page.$('button');
+ await page.evaluate(
+ (button: HTMLElement) => (button.style.display = 'none'),
+ button
+ );
+ const error = await button.click().catch((error_) => error_);
+ expect(error.message).toBe(
+ 'Node is either not clickable or not an HTMLElement'
+ );
+ });
+ it('should throw for recursively hidden nodes', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/button.html');
+ const button = await page.$('button');
+ await page.evaluate(
+ (button: HTMLElement) => (button.parentElement.style.display = 'none'),
+ button
+ );
+ const error = await button.click().catch((error_) => error_);
+ expect(error.message).toBe(
+ 'Node is either not clickable or not an HTMLElement'
+ );
+ });
+ it('should throw for elements', async () => {
+ const { page } = getTestState();
+
+ await page.setContent('hello goodbye');
+ const br = await page.$('br');
+ const error = await br.click().catch((error_) => error_);
+ expect(error.message).toBe(
+ 'Node is either not clickable or not an HTMLElement'
+ );
+ });
+ });
+
+ describe('Element.waitForSelector', () => {
+ it('should wait correctly with waitForSelector on an element', async () => {
+ const { page } = getTestState();
+ const waitFor = page.waitForSelector('.foo');
+ // Set the page content after the waitFor has been started.
+ await page.setContent(
+ '
bar2
Foo1
'
+ );
+ let element = await waitFor;
+ expect(element).toBeDefined();
+
+ const innerWaitFor = element.waitForSelector('.bar');
+ await element.evaluate((el) => {
+ el.innerHTML = 'bar1
';
+ });
+ element = await innerWaitFor;
+ expect(element).toBeDefined();
+ expect(
+ await element.evaluate((el: HTMLElement) => el.innerText)
+ ).toStrictEqual('bar1');
+ });
+ });
+
+ describe('Element.waitForXPath', () => {
+ it('should wait correctly with waitForXPath on an element', async () => {
+ const { page } = getTestState();
+ // Set the page content after the waitFor has been started.
+ await page.setContent(
+ `
+
+ el3
+
`
+ );
+
+ const el2 = await page.waitForSelector('#el1');
+
+ expect(
+ await (await el2.waitForXPath('//div')).evaluate((el) => el.id)
+ ).toStrictEqual('el2');
+
+ expect(
+ await (await el2.waitForXPath('.//div')).evaluate((el) => el.id)
+ ).toStrictEqual('el2');
+ });
+ });
+
+ describe('ElementHandle.hover', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/scrollable.html');
+ const button = await page.$('#button-6');
+ await button.hover();
+ expect(
+ await page.evaluate(() => document.querySelector('button:hover').id)
+ ).toBe('button-6');
+ });
+ });
+
+ describe('ElementHandle.isIntersectingViewport', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/offscreenbuttons.html');
+ for (let i = 0; i < 11; ++i) {
+ const button = await page.$('#btn' + i);
+ // All but last button are visible.
+ const visible = i < 10;
+ expect(await button.isIntersectingViewport()).toBe(visible);
+ }
+ });
+ it('should work with threshold', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/offscreenbuttons.html');
+ // a button almost cannot be seen
+ // sometimes we expect to return false by isIntersectingViewport1
+ const button = await page.$('#btn11');
+ expect(
+ await button.isIntersectingViewport({
+ threshold: 0.001,
+ })
+ ).toBe(false);
+ });
+ it('should work with threshold of 1', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/offscreenbuttons.html');
+ // a button almost cannot be seen
+ // sometimes we expect to return false by isIntersectingViewport1
+ const button = await page.$('#btn0');
+ expect(
+ await button.isIntersectingViewport({
+ threshold: 1,
+ })
+ ).toBe(true);
+ });
+ });
+
+ describe('Custom queries', function () {
+ this.afterEach(() => {
+ const { puppeteer } = getTestState();
+ puppeteer.clearCustomQueryHandlers();
+ });
+ it('should register and unregister', async () => {
+ const { page, puppeteer } = getTestState();
+ await page.setContent('
');
+
+ // Register.
+ puppeteer.registerCustomQueryHandler('getById', {
+ queryOne: (element, selector) =>
+ document.querySelector(`[id="${selector}"]`),
+ });
+ const element = await page.$('getById/foo');
+ expect(
+ await page.evaluate<(element: HTMLElement) => string>(
+ (element) => element.id,
+ element
+ )
+ ).toBe('foo');
+ const handlerNamesAfterRegistering = puppeteer.customQueryHandlerNames();
+ expect(handlerNamesAfterRegistering.includes('getById')).toBeTruthy();
+
+ // Unregister.
+ puppeteer.unregisterCustomQueryHandler('getById');
+ try {
+ await page.$('getById/foo');
+ throw new Error('Custom query handler name not set - throw expected');
+ } catch (error) {
+ expect(error).toStrictEqual(
+ new Error(
+ 'Query set to use "getById", but no query handler of that name was found'
+ )
+ );
+ }
+ const handlerNamesAfterUnregistering =
+ puppeteer.customQueryHandlerNames();
+ expect(handlerNamesAfterUnregistering.includes('getById')).toBeFalsy();
+ });
+ it('should throw with invalid query names', () => {
+ try {
+ const { puppeteer } = getTestState();
+ puppeteer.registerCustomQueryHandler('1/2/3', {
+ queryOne: () => document.querySelector('foo'),
+ });
+ throw new Error(
+ 'Custom query handler name was invalid - throw expected'
+ );
+ } catch (error) {
+ expect(error).toStrictEqual(
+ new Error('Custom query handler names may only contain [a-zA-Z]')
+ );
+ }
+ });
+ it('should work for multiple elements', async () => {
+ const { page, puppeteer } = getTestState();
+ await page.setContent(
+ '
Foo1
Foo2
'
+ );
+ puppeteer.registerCustomQueryHandler('getByClass', {
+ queryAll: (element, selector) =>
+ document.querySelectorAll(`.${selector}`),
+ });
+ const elements = await page.$$('getByClass/foo');
+ const classNames = await Promise.all(
+ elements.map(
+ async (element) =>
+ await page.evaluate<(element: HTMLElement) => string>(
+ (element) => element.className,
+ element
+ )
+ )
+ );
+
+ expect(classNames).toStrictEqual(['foo', 'foo baz']);
+ });
+ it('should eval correctly', async () => {
+ const { page, puppeteer } = getTestState();
+ await page.setContent(
+ '
Foo1
Foo2
'
+ );
+ puppeteer.registerCustomQueryHandler('getByClass', {
+ queryAll: (element, selector) =>
+ document.querySelectorAll(`.${selector}`),
+ });
+ const elements = await page.$$eval(
+ 'getByClass/foo',
+ (divs) => divs.length
+ );
+
+ expect(elements).toBe(2);
+ });
+ it('should wait correctly with waitForSelector', async () => {
+ const { page, puppeteer } = getTestState();
+ puppeteer.registerCustomQueryHandler('getByClass', {
+ queryOne: (element, selector) => element.querySelector(`.${selector}`),
+ });
+ const waitFor = page.waitForSelector('getByClass/foo');
+
+ // Set the page content after the waitFor has been started.
+ await page.setContent(
+ '
Foo1
'
+ );
+ const element = await waitFor;
+
+ expect(element).toBeDefined();
+ });
+
+ it('should wait correctly with waitForSelector on an element', async () => {
+ const { page, puppeteer } = getTestState();
+ puppeteer.registerCustomQueryHandler('getByClass', {
+ queryOne: (element, selector) => element.querySelector(`.${selector}`),
+ });
+ const waitFor = page.waitForSelector('getByClass/foo');
+
+ // Set the page content after the waitFor has been started.
+ await page.setContent(
+ '
bar2
Foo1
'
+ );
+ let element = await waitFor;
+ expect(element).toBeDefined();
+
+ const innerWaitFor = element.waitForSelector('getByClass/bar');
+
+ await element.evaluate((el) => {
+ el.innerHTML = 'bar1
';
+ });
+
+ element = await innerWaitFor;
+ expect(element).toBeDefined();
+ expect(
+ await element.evaluate((el: HTMLElement) => el.innerText)
+ ).toStrictEqual('bar1');
+ });
+
+ it('should wait correctly with waitFor', async () => {
+ /* page.waitFor is deprecated so we silence the warning to avoid test noise */
+ sinon.stub(console, 'warn').callsFake(() => {});
+ const { page, puppeteer } = getTestState();
+ puppeteer.registerCustomQueryHandler('getByClass', {
+ queryOne: (element, selector) => element.querySelector(`.${selector}`),
+ });
+ const waitFor = page.waitFor('getByClass/foo');
+
+ // Set the page content after the waitFor has been started.
+ await page.setContent(
+ '
Foo1
'
+ );
+ const element = await waitFor;
+
+ expect(element).toBeDefined();
+ });
+ it('should work when both queryOne and queryAll are registered', async () => {
+ const { page, puppeteer } = getTestState();
+ await page.setContent(
+ '
Foo2
'
+ );
+ puppeteer.registerCustomQueryHandler('getByClass', {
+ queryOne: (element, selector) => element.querySelector(`.${selector}`),
+ queryAll: (element, selector) =>
+ element.querySelectorAll(`.${selector}`),
+ });
+
+ const element = await page.$('getByClass/foo');
+ expect(element).toBeDefined();
+
+ const elements = await page.$$('getByClass/foo');
+ expect(elements.length).toBe(3);
+ });
+ it('should eval when both queryOne and queryAll are registered', async () => {
+ const { page, puppeteer } = getTestState();
+ await page.setContent(
+ '
text
content
'
+ );
+ puppeteer.registerCustomQueryHandler('getByClass', {
+ queryOne: (element, selector) => element.querySelector(`.${selector}`),
+ queryAll: (element, selector) =>
+ element.querySelectorAll(`.${selector}`),
+ });
+
+ const txtContent = await page.$eval(
+ 'getByClass/foo',
+ (div) => div.textContent
+ );
+ expect(txtContent).toBe('text');
+
+ const txtContents = await page.$$eval('getByClass/foo', (divs) =>
+ divs.map((d) => d.textContent).join('')
+ );
+ expect(txtContents).toBe('textcontent');
+ });
+ });
+});
diff --git a/test/emulation.spec.ts b/test/emulation.spec.ts
new file mode 100644
index 0000000000000..ded262a77c170
--- /dev/null
+++ b/test/emulation.spec.ts
@@ -0,0 +1,420 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import expect from 'expect';
+import {
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+ itFailsFirefox,
+ describeFailsFirefox,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+
+describe('Emulation', () => {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+ let iPhone;
+ let iPhoneLandscape;
+
+ before(() => {
+ const { puppeteer } = getTestState();
+ iPhone = puppeteer.devices['iPhone 6'];
+ iPhoneLandscape = puppeteer.devices['iPhone 6 landscape'];
+ });
+
+ describe('Page.viewport', function () {
+ it('should get the proper viewport size', async () => {
+ const { page } = getTestState();
+
+ expect(page.viewport()).toEqual({ width: 800, height: 600 });
+ await page.setViewport({ width: 123, height: 456 });
+ expect(page.viewport()).toEqual({ width: 123, height: 456 });
+ });
+ it('should support mobile emulation', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/mobile.html');
+ expect(await page.evaluate(() => window.innerWidth)).toBe(800);
+ await page.setViewport(iPhone.viewport);
+ expect(await page.evaluate(() => window.innerWidth)).toBe(375);
+ await page.setViewport({ width: 400, height: 300 });
+ expect(await page.evaluate(() => window.innerWidth)).toBe(400);
+ });
+ it('should support touch emulation', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/mobile.html');
+ expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(false);
+ await page.setViewport(iPhone.viewport);
+ expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(true);
+ expect(await page.evaluate(dispatchTouch)).toBe('Received touch');
+ await page.setViewport({ width: 100, height: 100 });
+ expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(false);
+
+ function dispatchTouch() {
+ let fulfill;
+ const promise = new Promise((x) => (fulfill = x));
+ window.ontouchstart = () => {
+ fulfill('Received touch');
+ };
+ window.dispatchEvent(new Event('touchstart'));
+
+ fulfill('Did not receive touch');
+
+ return promise;
+ }
+ });
+ it('should be detectable by Modernizr', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/detect-touch.html');
+ expect(await page.evaluate(() => document.body.textContent.trim())).toBe(
+ 'NO'
+ );
+ await page.setViewport(iPhone.viewport);
+ await page.goto(server.PREFIX + '/detect-touch.html');
+ expect(await page.evaluate(() => document.body.textContent.trim())).toBe(
+ 'YES'
+ );
+ });
+ it('should detect touch when applying viewport with touches', async () => {
+ const { page, server } = getTestState();
+
+ await page.setViewport({ width: 800, height: 600, hasTouch: true });
+ await page.addScriptTag({ url: server.PREFIX + '/modernizr.js' });
+ expect(await page.evaluate(() => globalThis.Modernizr.touchevents)).toBe(
+ true
+ );
+ });
+ itFailsFirefox('should support landscape emulation', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/mobile.html');
+ expect(await page.evaluate(() => screen.orientation.type)).toBe(
+ 'portrait-primary'
+ );
+ await page.setViewport(iPhoneLandscape.viewport);
+ expect(await page.evaluate(() => screen.orientation.type)).toBe(
+ 'landscape-primary'
+ );
+ await page.setViewport({ width: 100, height: 100 });
+ expect(await page.evaluate(() => screen.orientation.type)).toBe(
+ 'portrait-primary'
+ );
+ });
+ });
+
+ describe('Page.emulate', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/mobile.html');
+ await page.emulate(iPhone);
+ expect(await page.evaluate(() => window.innerWidth)).toBe(375);
+ expect(await page.evaluate(() => navigator.userAgent)).toContain(
+ 'iPhone'
+ );
+ });
+ itFailsFirefox('should support clicking', async () => {
+ const { page, server } = getTestState();
+
+ await page.emulate(iPhone);
+ await page.goto(server.PREFIX + '/input/button.html');
+ const button = await page.$('button');
+ await page.evaluate(
+ (button: HTMLElement) => (button.style.marginTop = '200px'),
+ button
+ );
+ await button.click();
+ expect(await page.evaluate(() => globalThis.result)).toBe('Clicked');
+ });
+ });
+
+ describe('Page.emulateMediaType', function () {
+ itFailsFirefox('should work', async () => {
+ const { page } = getTestState();
+
+ expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(
+ true
+ );
+ expect(await page.evaluate(() => matchMedia('print').matches)).toBe(
+ false
+ );
+ await page.emulateMediaType('print');
+ expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(
+ false
+ );
+ expect(await page.evaluate(() => matchMedia('print').matches)).toBe(true);
+ await page.emulateMediaType(null);
+ expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(
+ true
+ );
+ expect(await page.evaluate(() => matchMedia('print').matches)).toBe(
+ false
+ );
+ });
+ it('should throw in case of bad argument', async () => {
+ const { page } = getTestState();
+
+ let error = null;
+ await page.emulateMediaType('bad').catch((error_) => (error = error_));
+ expect(error.message).toBe('Unsupported media type: bad');
+ });
+ });
+
+ describe('Page.emulateMediaFeatures', function () {
+ itFailsFirefox('should work', async () => {
+ const { page } = getTestState();
+
+ await page.emulateMediaFeatures([
+ { name: 'prefers-reduced-motion', value: 'reduce' },
+ ]);
+ expect(
+ await page.evaluate(
+ () => matchMedia('(prefers-reduced-motion: reduce)').matches
+ )
+ ).toBe(true);
+ expect(
+ await page.evaluate(
+ () => matchMedia('(prefers-reduced-motion: no-preference)').matches
+ )
+ ).toBe(false);
+ await page.emulateMediaFeatures([
+ { name: 'prefers-color-scheme', value: 'light' },
+ ]);
+ expect(
+ await page.evaluate(
+ () => matchMedia('(prefers-color-scheme: light)').matches
+ )
+ ).toBe(true);
+ expect(
+ await page.evaluate(
+ () => matchMedia('(prefers-color-scheme: dark)').matches
+ )
+ ).toBe(false);
+ await page.emulateMediaFeatures([
+ { name: 'prefers-color-scheme', value: 'dark' },
+ ]);
+ expect(
+ await page.evaluate(
+ () => matchMedia('(prefers-color-scheme: dark)').matches
+ )
+ ).toBe(true);
+ expect(
+ await page.evaluate(
+ () => matchMedia('(prefers-color-scheme: light)').matches
+ )
+ ).toBe(false);
+ await page.emulateMediaFeatures([
+ { name: 'prefers-reduced-motion', value: 'reduce' },
+ { name: 'prefers-color-scheme', value: 'light' },
+ ]);
+ expect(
+ await page.evaluate(
+ () => matchMedia('(prefers-reduced-motion: reduce)').matches
+ )
+ ).toBe(true);
+ expect(
+ await page.evaluate(
+ () => matchMedia('(prefers-reduced-motion: no-preference)').matches
+ )
+ ).toBe(false);
+ expect(
+ await page.evaluate(
+ () => matchMedia('(prefers-color-scheme: light)').matches
+ )
+ ).toBe(true);
+ expect(
+ await page.evaluate(
+ () => matchMedia('(prefers-color-scheme: dark)').matches
+ )
+ ).toBe(false);
+ await page.emulateMediaFeatures([{ name: 'color-gamut', value: 'srgb' }]);
+ expect(
+ await page.evaluate(() => matchMedia('(color-gamut: p3)').matches)
+ ).toBe(false);
+ expect(
+ await page.evaluate(() => matchMedia('(color-gamut: srgb)').matches)
+ ).toBe(true);
+ expect(
+ await page.evaluate(() => matchMedia('(color-gamut: rec2020)').matches)
+ ).toBe(false);
+ await page.emulateMediaFeatures([{ name: 'color-gamut', value: 'p3' }]);
+ expect(
+ await page.evaluate(() => matchMedia('(color-gamut: p3)').matches)
+ ).toBe(true);
+ expect(
+ await page.evaluate(() => matchMedia('(color-gamut: srgb)').matches)
+ ).toBe(true);
+ expect(
+ await page.evaluate(() => matchMedia('(color-gamut: rec2020)').matches)
+ ).toBe(false);
+ await page.emulateMediaFeatures([
+ { name: 'color-gamut', value: 'rec2020' },
+ ]);
+ expect(
+ await page.evaluate(() => matchMedia('(color-gamut: p3)').matches)
+ ).toBe(true);
+ expect(
+ await page.evaluate(() => matchMedia('(color-gamut: srgb)').matches)
+ ).toBe(true);
+ expect(
+ await page.evaluate(() => matchMedia('(color-gamut: rec2020)').matches)
+ ).toBe(true);
+ });
+ it('should throw in case of bad argument', async () => {
+ const { page } = getTestState();
+
+ let error = null;
+ await page
+ .emulateMediaFeatures([{ name: 'bad', value: '' }])
+ .catch((error_) => (error = error_));
+ expect(error.message).toBe('Unsupported media feature: bad');
+ });
+ });
+
+ describeFailsFirefox('Page.emulateTimezone', function () {
+ it('should work', async () => {
+ const { page } = getTestState();
+
+ await page.evaluate(() => {
+ globalThis.date = new Date(1479579154987);
+ });
+ await page.emulateTimezone('America/Jamaica');
+ expect(await page.evaluate(() => globalThis.date.toString())).toBe(
+ 'Sat Nov 19 2016 13:12:34 GMT-0500 (Eastern Standard Time)'
+ );
+
+ await page.emulateTimezone('Pacific/Honolulu');
+ expect(await page.evaluate(() => globalThis.date.toString())).toBe(
+ 'Sat Nov 19 2016 08:12:34 GMT-1000 (Hawaii-Aleutian Standard Time)'
+ );
+
+ await page.emulateTimezone('America/Buenos_Aires');
+ expect(await page.evaluate(() => globalThis.date.toString())).toBe(
+ 'Sat Nov 19 2016 15:12:34 GMT-0300 (Argentina Standard Time)'
+ );
+
+ await page.emulateTimezone('Europe/Berlin');
+ expect(await page.evaluate(() => globalThis.date.toString())).toBe(
+ 'Sat Nov 19 2016 19:12:34 GMT+0100 (Central European Standard Time)'
+ );
+ });
+
+ it('should throw for invalid timezone IDs', async () => {
+ const { page } = getTestState();
+
+ let error = null;
+ await page.emulateTimezone('Foo/Bar').catch((error_) => (error = error_));
+ expect(error.message).toBe('Invalid timezone ID: Foo/Bar');
+ await page.emulateTimezone('Baz/Qux').catch((error_) => (error = error_));
+ expect(error.message).toBe('Invalid timezone ID: Baz/Qux');
+ });
+ });
+
+ describeFailsFirefox('Page.emulateVisionDeficiency', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.setViewport({ width: 500, height: 500 });
+ await page.goto(server.PREFIX + '/grid.html');
+
+ {
+ await page.emulateVisionDeficiency('none');
+ const screenshot = await page.screenshot();
+ expect(screenshot).toBeGolden('screenshot-sanity.png');
+ }
+
+ {
+ await page.emulateVisionDeficiency('achromatopsia');
+ const screenshot = await page.screenshot();
+ expect(screenshot).toBeGolden('vision-deficiency-achromatopsia.png');
+ }
+
+ {
+ await page.emulateVisionDeficiency('blurredVision');
+ const screenshot = await page.screenshot();
+ expect(screenshot).toBeGolden('vision-deficiency-blurredVision.png');
+ }
+
+ {
+ await page.emulateVisionDeficiency('deuteranopia');
+ const screenshot = await page.screenshot();
+ expect(screenshot).toBeGolden('vision-deficiency-deuteranopia.png');
+ }
+
+ {
+ await page.emulateVisionDeficiency('protanopia');
+ const screenshot = await page.screenshot();
+ expect(screenshot).toBeGolden('vision-deficiency-protanopia.png');
+ }
+
+ {
+ await page.emulateVisionDeficiency('tritanopia');
+ const screenshot = await page.screenshot();
+ expect(screenshot).toBeGolden('vision-deficiency-tritanopia.png');
+ }
+
+ {
+ await page.emulateVisionDeficiency('none');
+ const screenshot = await page.screenshot();
+ expect(screenshot).toBeGolden('screenshot-sanity.png');
+ }
+ });
+
+ it('should throw for invalid vision deficiencies', async () => {
+ const { page } = getTestState();
+
+ let error = null;
+ await page
+ // @ts-expect-error deliberately passign invalid deficiency
+ .emulateVisionDeficiency('invalid')
+ .catch((error_) => (error = error_));
+ expect(error.message).toBe('Unsupported vision deficiency: invalid');
+ });
+ });
+
+ describeFailsFirefox('Page.emulateNetworkConditions', function () {
+ it('should change navigator.connection.effectiveType', async () => {
+ const { page, puppeteer } = getTestState();
+
+ const slow3G = puppeteer.networkConditions['Slow 3G'];
+ const fast3G = puppeteer.networkConditions['Fast 3G'];
+
+ expect(
+ await page.evaluate('window.navigator.connection.effectiveType')
+ ).toBe('4g');
+ await page.emulateNetworkConditions(fast3G);
+ expect(
+ await page.evaluate('window.navigator.connection.effectiveType')
+ ).toBe('3g');
+ await page.emulateNetworkConditions(slow3G);
+ expect(
+ await page.evaluate('window.navigator.connection.effectiveType')
+ ).toBe('2g');
+ await page.emulateNetworkConditions(null);
+ });
+ });
+
+ describeFailsFirefox('Page.emulateCPUThrottling', function () {
+ it('should change the CPU throttling rate successfully', async () => {
+ const { page } = getTestState();
+
+ await page.emulateCPUThrottling(100);
+ await page.emulateCPUThrottling(null);
+ });
+ });
+});
diff --git a/test/evaluation.spec.ts b/test/evaluation.spec.ts
new file mode 100644
index 0000000000000..5ff363f1d1c60
--- /dev/null
+++ b/test/evaluation.spec.ts
@@ -0,0 +1,476 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import utils from './utils.js';
+import expect from 'expect';
+import {
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+ itFailsFirefox,
+ describeFailsFirefox,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+
+const bigint = typeof BigInt !== 'undefined';
+
+describe('Evaluation specs', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+
+ describe('Page.evaluate', function () {
+ it('should work', async () => {
+ const { page } = getTestState();
+
+ const result = await page.evaluate(() => 7 * 3);
+ expect(result).toBe(21);
+ });
+ (bigint ? it : xit)('should transfer BigInt', async () => {
+ const { page } = getTestState();
+
+ const result = await page.evaluate((a: BigInt) => a, BigInt(42));
+ expect(result).toBe(BigInt(42));
+ });
+ it('should transfer NaN', async () => {
+ const { page } = getTestState();
+
+ const result = await page.evaluate((a) => a, NaN);
+ expect(Object.is(result, NaN)).toBe(true);
+ });
+ it('should transfer -0', async () => {
+ const { page } = getTestState();
+
+ const result = await page.evaluate((a) => a, -0);
+ expect(Object.is(result, -0)).toBe(true);
+ });
+ it('should transfer Infinity', async () => {
+ const { page } = getTestState();
+
+ const result = await page.evaluate((a) => a, Infinity);
+ expect(Object.is(result, Infinity)).toBe(true);
+ });
+ it('should transfer -Infinity', async () => {
+ const { page } = getTestState();
+
+ const result = await page.evaluate((a) => a, -Infinity);
+ expect(Object.is(result, -Infinity)).toBe(true);
+ });
+ it('should transfer arrays', async () => {
+ const { page } = getTestState();
+
+ const result = await page.evaluate((a) => a, [1, 2, 3]);
+ expect(result).toEqual([1, 2, 3]);
+ });
+ it('should transfer arrays as arrays, not objects', async () => {
+ const { page } = getTestState();
+
+ const result = await page.evaluate((a) => Array.isArray(a), [1, 2, 3]);
+ expect(result).toBe(true);
+ });
+ it('should modify global environment', async () => {
+ const { page } = getTestState();
+
+ await page.evaluate(() => (globalThis.globalVar = 123));
+ expect(await page.evaluate('globalVar')).toBe(123);
+ });
+ it('should evaluate in the page context', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/global-var.html');
+ expect(await page.evaluate('globalVar')).toBe(123);
+ });
+ itFailsFirefox(
+ 'should return undefined for objects with symbols',
+ async () => {
+ const { page } = getTestState();
+
+ expect(await page.evaluate(() => [Symbol('foo4')])).toBe(undefined);
+ }
+ );
+ it('should work with function shorthands', async () => {
+ const { page } = getTestState();
+
+ const a = {
+ sum(a, b) {
+ return a + b;
+ },
+
+ async mult(a, b) {
+ return a * b;
+ },
+ };
+ expect(await page.evaluate(a.sum, 1, 2)).toBe(3);
+ expect(await page.evaluate(a.mult, 2, 4)).toBe(8);
+ });
+ it('should work with unicode chars', async () => {
+ const { page } = getTestState();
+
+ const result = await page.evaluate((a) => a['中文字符'], {
+ 中文字符: 42,
+ });
+ expect(result).toBe(42);
+ });
+ itFailsFirefox('should throw when evaluation triggers reload', async () => {
+ const { page } = getTestState();
+
+ let error = null;
+ await page
+ .evaluate(() => {
+ location.reload();
+ return new Promise(() => {});
+ })
+ .catch((error_) => (error = error_));
+ expect(error.message).toContain('Protocol error');
+ });
+ it('should await promise', async () => {
+ const { page } = getTestState();
+
+ const result = await page.evaluate(() => Promise.resolve(8 * 7));
+ expect(result).toBe(56);
+ });
+ it('should work right after framenavigated', async () => {
+ const { page, server } = getTestState();
+
+ let frameEvaluation = null;
+ page.on('framenavigated', async (frame) => {
+ frameEvaluation = frame.evaluate(() => 6 * 7);
+ });
+ await page.goto(server.EMPTY_PAGE);
+ expect(await frameEvaluation).toBe(42);
+ });
+ itFailsFirefox('should work from-inside an exposed function', async () => {
+ const { page } = getTestState();
+
+ // Setup inpage callback, which calls Page.evaluate
+ await page.exposeFunction('callController', async function (a, b) {
+ return await page.evaluate<(a: number, b: number) => number>(
+ (a, b) => a * b,
+ a,
+ b
+ );
+ });
+ const result = await page.evaluate(async function () {
+ return await globalThis.callController(9, 3);
+ });
+ expect(result).toBe(27);
+ });
+ it('should reject promise with exception', async () => {
+ const { page } = getTestState();
+
+ let error = null;
+ await page
+ // @ts-expect-error we know the object doesn't exist
+ .evaluate(() => notExistingObject.property)
+ .catch((error_) => (error = error_));
+ expect(error).toBeTruthy();
+ expect(error.message).toContain('notExistingObject');
+ });
+ it('should support thrown strings as error messages', async () => {
+ const { page } = getTestState();
+
+ let error = null;
+ await page
+ .evaluate(() => {
+ throw 'qwerty';
+ })
+ .catch((error_) => (error = error_));
+ expect(error).toBeTruthy();
+ expect(error.message).toContain('qwerty');
+ });
+ it('should support thrown numbers as error messages', async () => {
+ const { page } = getTestState();
+
+ let error = null;
+ await page
+ .evaluate(() => {
+ throw 100500;
+ })
+ .catch((error_) => (error = error_));
+ expect(error).toBeTruthy();
+ expect(error.message).toContain('100500');
+ });
+ it('should return complex objects', async () => {
+ const { page } = getTestState();
+
+ const object = { foo: 'bar!' };
+ const result = await page.evaluate((a) => a, object);
+ expect(result).not.toBe(object);
+ expect(result).toEqual(object);
+ });
+ (bigint ? it : xit)('should return BigInt', async () => {
+ const { page } = getTestState();
+
+ const result = await page.evaluate(() => BigInt(42));
+ expect(result).toBe(BigInt(42));
+ });
+ it('should return NaN', async () => {
+ const { page } = getTestState();
+
+ const result = await page.evaluate(() => NaN);
+ expect(Object.is(result, NaN)).toBe(true);
+ });
+ it('should return -0', async () => {
+ const { page } = getTestState();
+
+ const result = await page.evaluate(() => -0);
+ expect(Object.is(result, -0)).toBe(true);
+ });
+ it('should return Infinity', async () => {
+ const { page } = getTestState();
+
+ const result = await page.evaluate(() => Infinity);
+ expect(Object.is(result, Infinity)).toBe(true);
+ });
+ it('should return -Infinity', async () => {
+ const { page } = getTestState();
+
+ const result = await page.evaluate(() => -Infinity);
+ expect(Object.is(result, -Infinity)).toBe(true);
+ });
+ it('should accept "undefined" as one of multiple parameters', async () => {
+ const { page } = getTestState();
+
+ const result = await page.evaluate(
+ (a, b) => Object.is(a, undefined) && Object.is(b, 'foo'),
+ undefined,
+ 'foo'
+ );
+ expect(result).toBe(true);
+ });
+ it('should properly serialize null fields', async () => {
+ const { page } = getTestState();
+
+ expect(await page.evaluate(() => ({ a: undefined }))).toEqual({});
+ });
+ itFailsFirefox(
+ 'should return undefined for non-serializable objects',
+ async () => {
+ const { page } = getTestState();
+
+ expect(await page.evaluate(() => window)).toBe(undefined);
+ }
+ );
+ itFailsFirefox('should fail for circular object', async () => {
+ const { page } = getTestState();
+
+ const result = await page.evaluate(() => {
+ const a: { [x: string]: any } = {};
+ const b = { a };
+ a.b = b;
+ return a;
+ });
+ expect(result).toBe(undefined);
+ });
+ itFailsFirefox('should be able to throw a tricky error', async () => {
+ const { page } = getTestState();
+
+ const windowHandle = await page.evaluateHandle(() => window);
+ const errorText = await windowHandle
+ .jsonValue()
+ .catch((error_) => error_.message);
+ const error = await page
+ .evaluate<(errorText: string) => Error>((errorText) => {
+ throw new Error(errorText);
+ }, errorText)
+ .catch((error_) => error_);
+ expect(error.message).toContain(errorText);
+ });
+ it('should accept a string', async () => {
+ const { page } = getTestState();
+
+ const result = await page.evaluate('1 + 2');
+ expect(result).toBe(3);
+ });
+ it('should accept a string with semi colons', async () => {
+ const { page } = getTestState();
+
+ const result = await page.evaluate('1 + 5;');
+ expect(result).toBe(6);
+ });
+ it('should accept a string with comments', async () => {
+ const { page } = getTestState();
+
+ const result = await page.evaluate('2 + 5;\n// do some math!');
+ expect(result).toBe(7);
+ });
+ it('should accept element handle as an argument', async () => {
+ const { page } = getTestState();
+
+ await page.setContent('');
+ const element = await page.$('section');
+ const text = await page.evaluate<(e: HTMLElement) => string>(
+ (e) => e.textContent,
+ element
+ );
+ expect(text).toBe('42');
+ });
+ it('should throw if underlying element was disposed', async () => {
+ const { page } = getTestState();
+
+ await page.setContent('');
+ const element = await page.$('section');
+ expect(element).toBeTruthy();
+ await element.dispose();
+ let error = null;
+ await page
+ .evaluate((e: HTMLElement) => e.textContent, element)
+ .catch((error_) => (error = error_));
+ expect(error.message).toContain('JSHandle is disposed');
+ });
+ itFailsFirefox(
+ 'should throw if elementHandles are from other frames',
+ async () => {
+ const { page, server } = getTestState();
+
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ const bodyHandle = await page.frames()[1].$('body');
+ let error = null;
+ await page
+ .evaluate((body: HTMLElement) => body.innerHTML, bodyHandle)
+ .catch((error_) => (error = error_));
+ expect(error).toBeTruthy();
+ expect(error.message).toContain(
+ 'JSHandles can be evaluated only in the context they were created'
+ );
+ }
+ );
+ itFailsFirefox('should simulate a user gesture', async () => {
+ const { page } = getTestState();
+
+ const result = await page.evaluate(() => {
+ document.body.appendChild(document.createTextNode('test'));
+ document.execCommand('selectAll');
+ return document.execCommand('copy');
+ });
+ expect(result).toBe(true);
+ });
+ itFailsFirefox('should throw a nice error after a navigation', async () => {
+ const { page } = getTestState();
+
+ const executionContext = await page.mainFrame().executionContext();
+
+ await Promise.all([
+ page.waitForNavigation(),
+ executionContext.evaluate(() => window.location.reload()),
+ ]);
+ const error = await executionContext
+ .evaluate(() => null)
+ .catch((error_) => error_);
+ expect((error as Error).message).toContain('navigation');
+ });
+ itFailsFirefox(
+ 'should not throw an error when evaluation does a navigation',
+ async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/one-style.html');
+ const result = await page.evaluate(() => {
+ (window as any).location = '/empty.html';
+ return [42];
+ });
+ expect(result).toEqual([42]);
+ }
+ );
+ it('should transfer 100Mb of data from page to node.js', async function () {
+ const { page } = getTestState();
+
+ const a = await page.evaluate<() => string>(() =>
+ Array(100 * 1024 * 1024 + 1).join('a')
+ );
+ expect(a.length).toBe(100 * 1024 * 1024);
+ });
+ it('should throw error with detailed information on exception inside promise ', async () => {
+ const { page } = getTestState();
+
+ let error = null;
+ await page
+ .evaluate(
+ () =>
+ new Promise(() => {
+ throw new Error('Error in promise');
+ })
+ )
+ .catch((error_) => (error = error_));
+ expect(error.message).toContain('Error in promise');
+ });
+ });
+
+ describeFailsFirefox('Page.evaluateOnNewDocument', function () {
+ it('should evaluate before anything else on the page', async () => {
+ const { page, server } = getTestState();
+
+ await page.evaluateOnNewDocument(function () {
+ globalThis.injected = 123;
+ });
+ await page.goto(server.PREFIX + '/tamperable.html');
+ expect(await page.evaluate(() => globalThis.result)).toBe(123);
+ });
+ it('should work with CSP', async () => {
+ const { page, server } = getTestState();
+
+ server.setCSP('/empty.html', 'script-src ' + server.PREFIX);
+ await page.evaluateOnNewDocument(function () {
+ globalThis.injected = 123;
+ });
+ await page.goto(server.PREFIX + '/empty.html');
+ expect(await page.evaluate(() => globalThis.injected)).toBe(123);
+
+ // Make sure CSP works.
+ await page
+ .addScriptTag({ content: 'window.e = 10;' })
+ .catch((error) => void error);
+ expect(await page.evaluate(() => (window as any).e)).toBe(undefined);
+ });
+ });
+
+ describe('Frame.evaluate', function () {
+ it('should have different execution contexts', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ expect(page.frames().length).toBe(2);
+ await page.frames()[0].evaluate(() => (globalThis.FOO = 'foo'));
+ await page.frames()[1].evaluate(() => (globalThis.FOO = 'bar'));
+ expect(await page.frames()[0].evaluate(() => globalThis.FOO)).toBe('foo');
+ expect(await page.frames()[1].evaluate(() => globalThis.FOO)).toBe('bar');
+ });
+ it('should have correct execution contexts', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/frames/one-frame.html');
+ expect(page.frames().length).toBe(2);
+ expect(
+ await page.frames()[0].evaluate(() => document.body.textContent.trim())
+ ).toBe('');
+ expect(
+ await page.frames()[1].evaluate(() => document.body.textContent.trim())
+ ).toBe(`Hi, I'm frame`);
+ });
+ it('should execute after cross-site navigation', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const mainFrame = page.mainFrame();
+ expect(await mainFrame.evaluate(() => window.location.href)).toContain(
+ 'localhost'
+ );
+ await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
+ expect(await mainFrame.evaluate(() => window.location.href)).toContain(
+ '127'
+ );
+ });
+ });
+});
diff --git a/test/fixtures.spec.ts b/test/fixtures.spec.ts
new file mode 100644
index 0000000000000..e2c76f67231aa
--- /dev/null
+++ b/test/fixtures.spec.ts
@@ -0,0 +1,93 @@
+/**
+ * Copyright 2019 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* eslint-disable @typescript-eslint/no-var-requires */
+
+import expect from 'expect';
+import { getTestState, itHeadlessOnly } from './mocha-utils'; // eslint-disable-line import/extensions
+
+import path from 'path';
+
+describe('Fixtures', function () {
+ itHeadlessOnly('dumpio option should work with pipe option ', async () => {
+ const { defaultBrowserOptions, puppeteerPath } = getTestState();
+
+ let dumpioData = '';
+ const { spawn } = require('child_process');
+ const options = Object.assign({}, defaultBrowserOptions, {
+ pipe: true,
+ dumpio: true,
+ });
+ const res = spawn('node', [
+ path.join(__dirname, 'fixtures', 'dumpio.js'),
+ puppeteerPath,
+ JSON.stringify(options),
+ ]);
+ res.stderr.on('data', (data) => (dumpioData += data.toString('utf8')));
+ await new Promise((resolve) => res.on('close', resolve));
+ expect(dumpioData).toContain('message from dumpio');
+ });
+ it('should dump browser process stderr', async () => {
+ const { defaultBrowserOptions, puppeteerPath } = getTestState();
+
+ let dumpioData = '';
+ const { spawn } = require('child_process');
+ const options = Object.assign({}, defaultBrowserOptions, { dumpio: true });
+ const res = spawn('node', [
+ path.join(__dirname, 'fixtures', 'dumpio.js'),
+ puppeteerPath,
+ JSON.stringify(options),
+ ]);
+ res.stderr.on('data', (data) => (dumpioData += data.toString('utf8')));
+ await new Promise((resolve) => res.on('close', resolve));
+ expect(dumpioData).toContain('DevTools listening on ws://');
+ });
+ it('should close the browser when the node process closes', async () => {
+ const { defaultBrowserOptions, puppeteerPath, puppeteer } = getTestState();
+
+ const { spawn, execSync } = require('child_process');
+ const options = Object.assign({}, defaultBrowserOptions, {
+ // Disable DUMPIO to cleanly read stdout.
+ dumpio: false,
+ });
+ const res = spawn('node', [
+ path.join(__dirname, 'fixtures', 'closeme.js'),
+ puppeteerPath,
+ JSON.stringify(options),
+ ]);
+ let wsEndPointCallback;
+ const wsEndPointPromise = new Promise(
+ (x) => (wsEndPointCallback = x)
+ );
+ let output = '';
+ res.stdout.on('data', (data) => {
+ output += data;
+ if (output.indexOf('\n'))
+ wsEndPointCallback(output.substring(0, output.indexOf('\n')));
+ });
+ const browser = await puppeteer.connect({
+ browserWSEndpoint: await wsEndPointPromise,
+ });
+ const promises = [
+ new Promise((resolve) => browser.once('disconnected', resolve)),
+ new Promise((resolve) => res.on('close', resolve)),
+ ];
+ if (process.platform === 'win32')
+ execSync(`taskkill /pid ${res.pid} /T /F`);
+ else process.kill(res.pid);
+ await Promise.all(promises);
+ });
+});
diff --git a/test/fixtures/closeme.js b/test/fixtures/closeme.js
new file mode 100644
index 0000000000000..dbe798f70dfa7
--- /dev/null
+++ b/test/fixtures/closeme.js
@@ -0,0 +1,5 @@
+(async () => {
+ const [, , puppeteerRoot, options] = process.argv;
+ const browser = await require(puppeteerRoot).launch(JSON.parse(options));
+ console.log(browser.wsEndpoint());
+})();
diff --git a/test/fixtures/dumpio.js b/test/fixtures/dumpio.js
new file mode 100644
index 0000000000000..40b9714f6caeb
--- /dev/null
+++ b/test/fixtures/dumpio.js
@@ -0,0 +1,8 @@
+(async () => {
+ const [, , puppeteerRoot, options] = process.argv;
+ const browser = await require(puppeteerRoot).launch(JSON.parse(options));
+ const page = await browser.newPage();
+ await page.evaluate(() => console.error('message from dumpio'));
+ await page.close();
+ await browser.close();
+})();
diff --git a/test/frame.spec.ts b/test/frame.spec.ts
new file mode 100644
index 0000000000000..94bf572153641
--- /dev/null
+++ b/test/frame.spec.ts
@@ -0,0 +1,298 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import utils from './utils.js';
+import expect from 'expect';
+import {
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+ itFailsFirefox,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+import { CDPSession } from '../lib/cjs/puppeteer/common/Connection.js';
+
+describe('Frame specs', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+
+ describe('Frame.executionContext', function () {
+ itFailsFirefox('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ expect(page.frames().length).toBe(2);
+ const [frame1, frame2] = page.frames();
+ const context1 = await frame1.executionContext();
+ const context2 = await frame2.executionContext();
+ expect(context1).toBeTruthy();
+ expect(context2).toBeTruthy();
+ expect(context1 !== context2).toBeTruthy();
+ expect(context1.frame()).toBe(frame1);
+ expect(context2.frame()).toBe(frame2);
+
+ await Promise.all([
+ context1.evaluate(() => (globalThis.a = 1)),
+ context2.evaluate(() => (globalThis.a = 2)),
+ ]);
+ const [a1, a2] = await Promise.all([
+ context1.evaluate(() => globalThis.a),
+ context2.evaluate(() => globalThis.a),
+ ]);
+ expect(a1).toBe(1);
+ expect(a2).toBe(2);
+ });
+ });
+
+ describe('Frame.evaluateHandle', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const mainFrame = page.mainFrame();
+ const windowHandle = await mainFrame.evaluateHandle(() => window);
+ expect(windowHandle).toBeTruthy();
+ });
+ });
+
+ describe('Frame.evaluate', function () {
+ itFailsFirefox('should throw for detached frames', async () => {
+ const { page, server } = getTestState();
+
+ const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ await utils.detachFrame(page, 'frame1');
+ let error = null;
+ await frame1.evaluate(() => 7 * 8).catch((error_) => (error = error_));
+ expect(error.message).toContain(
+ 'Execution context is not available in detached frame'
+ );
+ });
+
+ it('allows readonly array to be an argument', async () => {
+ const { page, server } = getTestState();
+ await page.goto(server.EMPTY_PAGE);
+ const mainFrame = page.mainFrame();
+
+ // This test checks if Frame.evaluate allows a readonly array to be an argument.
+ // See https://github.com/puppeteer/puppeteer/issues/6953.
+ const readonlyArray: readonly string[] = ['a', 'b', 'c'];
+ await mainFrame.evaluate((arr) => arr, readonlyArray);
+ });
+ });
+
+ describe('Frame Management', function () {
+ itFailsFirefox('should handle nested frames', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/frames/nested-frames.html');
+ expect(utils.dumpFrames(page.mainFrame())).toEqual([
+ 'http://localhost:/frames/nested-frames.html',
+ ' http://localhost:/frames/two-frames.html (2frames)',
+ ' http://localhost:/frames/frame.html (uno)',
+ ' http://localhost:/frames/frame.html (dos)',
+ ' http://localhost:/frames/frame.html (aframe)',
+ ]);
+ });
+ itFailsFirefox(
+ 'should send events when frames are manipulated dynamically',
+ async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ // validate frameattached events
+ const attachedFrames = [];
+ page.on('frameattached', (frame) => attachedFrames.push(frame));
+ await utils.attachFrame(page, 'frame1', './assets/frame.html');
+ expect(attachedFrames.length).toBe(1);
+ expect(attachedFrames[0].url()).toContain('/assets/frame.html');
+
+ // validate framenavigated events
+ const navigatedFrames = [];
+ page.on('framenavigated', (frame) => navigatedFrames.push(frame));
+ await utils.navigateFrame(page, 'frame1', './empty.html');
+ expect(navigatedFrames.length).toBe(1);
+ expect(navigatedFrames[0].url()).toBe(server.EMPTY_PAGE);
+
+ // validate framedetached events
+ const detachedFrames = [];
+ page.on('framedetached', (frame) => detachedFrames.push(frame));
+ await utils.detachFrame(page, 'frame1');
+ expect(detachedFrames.length).toBe(1);
+ expect(detachedFrames[0].isDetached()).toBe(true);
+ }
+ );
+ it('should send "framenavigated" when navigating on anchor URLs', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await Promise.all([
+ page.goto(server.EMPTY_PAGE + '#foo'),
+ utils.waitEvent(page, 'framenavigated'),
+ ]);
+ expect(page.url()).toBe(server.EMPTY_PAGE + '#foo');
+ });
+ it('should persist mainFrame on cross-process navigation', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const mainFrame = page.mainFrame();
+ await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
+ expect(page.mainFrame() === mainFrame).toBeTruthy();
+ });
+ it('should not send attach/detach events for main frame', async () => {
+ const { page, server } = getTestState();
+
+ let hasEvents = false;
+ page.on('frameattached', () => (hasEvents = true));
+ page.on('framedetached', () => (hasEvents = true));
+ await page.goto(server.EMPTY_PAGE);
+ expect(hasEvents).toBe(false);
+ });
+ it('should detach child frames on navigation', async () => {
+ const { page, server } = getTestState();
+
+ let attachedFrames = [];
+ let detachedFrames = [];
+ let navigatedFrames = [];
+ page.on('frameattached', (frame) => attachedFrames.push(frame));
+ page.on('framedetached', (frame) => detachedFrames.push(frame));
+ page.on('framenavigated', (frame) => navigatedFrames.push(frame));
+ await page.goto(server.PREFIX + '/frames/nested-frames.html');
+ expect(attachedFrames.length).toBe(4);
+ expect(detachedFrames.length).toBe(0);
+ expect(navigatedFrames.length).toBe(5);
+
+ attachedFrames = [];
+ detachedFrames = [];
+ navigatedFrames = [];
+ await page.goto(server.EMPTY_PAGE);
+ expect(attachedFrames.length).toBe(0);
+ expect(detachedFrames.length).toBe(4);
+ expect(navigatedFrames.length).toBe(1);
+ });
+ it('should support framesets', async () => {
+ const { page, server } = getTestState();
+
+ let attachedFrames = [];
+ let detachedFrames = [];
+ let navigatedFrames = [];
+ page.on('frameattached', (frame) => attachedFrames.push(frame));
+ page.on('framedetached', (frame) => detachedFrames.push(frame));
+ page.on('framenavigated', (frame) => navigatedFrames.push(frame));
+ await page.goto(server.PREFIX + '/frames/frameset.html');
+ expect(attachedFrames.length).toBe(4);
+ expect(detachedFrames.length).toBe(0);
+ expect(navigatedFrames.length).toBe(5);
+
+ attachedFrames = [];
+ detachedFrames = [];
+ navigatedFrames = [];
+ await page.goto(server.EMPTY_PAGE);
+ expect(attachedFrames.length).toBe(0);
+ expect(detachedFrames.length).toBe(4);
+ expect(navigatedFrames.length).toBe(1);
+ });
+ itFailsFirefox('should report frame from-inside shadow DOM', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/shadow.html');
+ await page.evaluate(async (url: string) => {
+ const frame = document.createElement('iframe');
+ frame.src = url;
+ document.body.shadowRoot.appendChild(frame);
+ await new Promise((x) => (frame.onload = x));
+ }, server.EMPTY_PAGE);
+ expect(page.frames().length).toBe(2);
+ expect(page.frames()[1].url()).toBe(server.EMPTY_PAGE);
+ });
+ itFailsFirefox('should report frame.name()', async () => {
+ const { page, server } = getTestState();
+
+ await utils.attachFrame(page, 'theFrameId', server.EMPTY_PAGE);
+ await page.evaluate((url: string) => {
+ const frame = document.createElement('iframe');
+ frame.name = 'theFrameName';
+ frame.src = url;
+ document.body.appendChild(frame);
+ return new Promise((x) => (frame.onload = x));
+ }, server.EMPTY_PAGE);
+ expect(page.frames()[0].name()).toBe('');
+ expect(page.frames()[1].name()).toBe('theFrameId');
+ expect(page.frames()[2].name()).toBe('theFrameName');
+ });
+ itFailsFirefox('should report frame.parent()', async () => {
+ const { page, server } = getTestState();
+
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
+ expect(page.frames()[0].parentFrame()).toBe(null);
+ expect(page.frames()[1].parentFrame()).toBe(page.mainFrame());
+ expect(page.frames()[2].parentFrame()).toBe(page.mainFrame());
+ });
+ itFailsFirefox(
+ 'should report different frame instance when frame re-attaches',
+ async () => {
+ const { page, server } = getTestState();
+
+ const frame1 = await utils.attachFrame(
+ page,
+ 'frame1',
+ server.EMPTY_PAGE
+ );
+ await page.evaluate(() => {
+ globalThis.frame = document.querySelector('#frame1');
+ globalThis.frame.remove();
+ });
+ expect(frame1.isDetached()).toBe(true);
+ const [frame2] = await Promise.all([
+ utils.waitEvent(page, 'frameattached'),
+ page.evaluate(() => document.body.appendChild(globalThis.frame)),
+ ]);
+ expect(frame2.isDetached()).toBe(false);
+ expect(frame1).not.toBe(frame2);
+ }
+ );
+ it('should support url fragment', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/frames/one-frame-url-fragment.html');
+
+ expect(page.frames().length).toBe(2);
+ expect(page.frames()[1].url()).toBe(
+ server.PREFIX + '/frames/frame.html?param=value#fragment'
+ );
+ });
+ itFailsFirefox('should support lazy frames', async () => {
+ const { page, server } = getTestState();
+
+ await page.setViewport({ width: 1000, height: 1000 });
+ await page.goto(server.PREFIX + '/frames/lazy-frame.html');
+
+ expect(page.frames().map((frame) => frame._hasStartedLoading)).toEqual([
+ true,
+ true,
+ false,
+ ]);
+ });
+ });
+
+ describe('Frame.client', function () {
+ it('should return the client instance', async () => {
+ const { page } = getTestState();
+ expect(page.mainFrame().client()).toBeInstanceOf(CDPSession);
+ });
+ });
+});
diff --git a/test/golden-chromium/csscoverage-involved.txt b/test/golden-chromium/csscoverage-involved.txt
new file mode 100644
index 0000000000000..9b851d0bd3c72
--- /dev/null
+++ b/test/golden-chromium/csscoverage-involved.txt
@@ -0,0 +1,16 @@
+[
+ {
+ "url": "http://localhost:/csscoverage/involved.html",
+ "ranges": [
+ {
+ "start": 149,
+ "end": 297
+ },
+ {
+ "start": 327,
+ "end": 433
+ }
+ ],
+ "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"
+ }
+]
\ No newline at end of file
diff --git a/test/golden-chromium/grid-cell-0.png b/test/golden-chromium/grid-cell-0.png
new file mode 100644
index 0000000000000..ff282e989b7ea
Binary files /dev/null and b/test/golden-chromium/grid-cell-0.png differ
diff --git a/test/golden-chromium/grid-cell-1.png b/test/golden-chromium/grid-cell-1.png
new file mode 100644
index 0000000000000..91a1cb8510a1d
Binary files /dev/null and b/test/golden-chromium/grid-cell-1.png differ
diff --git a/test/golden-chromium/grid-cell-2.png b/test/golden-chromium/grid-cell-2.png
new file mode 100644
index 0000000000000..7b01753b6a63d
Binary files /dev/null and b/test/golden-chromium/grid-cell-2.png differ
diff --git a/test/golden-chromium/grid-cell-3.png b/test/golden-chromium/grid-cell-3.png
new file mode 100644
index 0000000000000..b9b8b2922b380
Binary files /dev/null and b/test/golden-chromium/grid-cell-3.png differ
diff --git a/test/golden-chromium/jscoverage-involved.txt b/test/golden-chromium/jscoverage-involved.txt
new file mode 100644
index 0000000000000..6f28e1580ec9e
--- /dev/null
+++ b/test/golden-chromium/jscoverage-involved.txt
@@ -0,0 +1,28 @@
+[
+ {
+ "url": "http://localhost:/jscoverage/involved.html",
+ "ranges": [
+ {
+ "start": 0,
+ "end": 35
+ },
+ {
+ "start": 50,
+ "end": 100
+ },
+ {
+ "start": 107,
+ "end": 141
+ },
+ {
+ "start": 148,
+ "end": 160
+ },
+ {
+ "start": 168,
+ "end": 207
+ }
+ ],
+ "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"
+ }
+]
\ No newline at end of file
diff --git a/test/golden-chromium/mock-binary-response.png b/test/golden-chromium/mock-binary-response.png
new file mode 100644
index 0000000000000..8595e0598edf2
Binary files /dev/null and b/test/golden-chromium/mock-binary-response.png differ
diff --git a/test/golden-chromium/screenshot-clip-odd-size.png b/test/golden-chromium/screenshot-clip-odd-size.png
new file mode 100644
index 0000000000000..b010d1f87f0cd
Binary files /dev/null and b/test/golden-chromium/screenshot-clip-odd-size.png differ
diff --git a/test/golden-chromium/screenshot-clip-rect.png b/test/golden-chromium/screenshot-clip-rect.png
new file mode 100644
index 0000000000000..ac23b7de502a0
Binary files /dev/null and b/test/golden-chromium/screenshot-clip-rect.png differ
diff --git a/test/golden-chromium/screenshot-element-bounding-box.png b/test/golden-chromium/screenshot-element-bounding-box.png
new file mode 100644
index 0000000000000..32e05bf05b40c
Binary files /dev/null and b/test/golden-chromium/screenshot-element-bounding-box.png differ
diff --git a/test/golden-chromium/screenshot-element-fractional-offset.png b/test/golden-chromium/screenshot-element-fractional-offset.png
new file mode 100644
index 0000000000000..cc8669d598be1
Binary files /dev/null and b/test/golden-chromium/screenshot-element-fractional-offset.png differ
diff --git a/test/golden-chromium/screenshot-element-fractional.png b/test/golden-chromium/screenshot-element-fractional.png
new file mode 100644
index 0000000000000..35c53377f942c
Binary files /dev/null and b/test/golden-chromium/screenshot-element-fractional.png differ
diff --git a/test/golden-chromium/screenshot-element-larger-than-viewport.png b/test/golden-chromium/screenshot-element-larger-than-viewport.png
new file mode 100644
index 0000000000000..5fcdb923555dc
Binary files /dev/null and b/test/golden-chromium/screenshot-element-larger-than-viewport.png differ
diff --git a/test/golden-chromium/screenshot-element-padding-border.png b/test/golden-chromium/screenshot-element-padding-border.png
new file mode 100644
index 0000000000000..917dd48188d45
Binary files /dev/null and b/test/golden-chromium/screenshot-element-padding-border.png differ
diff --git a/test/golden-chromium/screenshot-element-rotate.png b/test/golden-chromium/screenshot-element-rotate.png
new file mode 100644
index 0000000000000..52e2a0f6d3c66
Binary files /dev/null and b/test/golden-chromium/screenshot-element-rotate.png differ
diff --git a/test/golden-chromium/screenshot-element-scrolled-into-view.png b/test/golden-chromium/screenshot-element-scrolled-into-view.png
new file mode 100644
index 0000000000000..917dd48188d45
Binary files /dev/null and b/test/golden-chromium/screenshot-element-scrolled-into-view.png differ
diff --git a/test/golden-chromium/screenshot-grid-fullpage.png b/test/golden-chromium/screenshot-grid-fullpage.png
new file mode 100644
index 0000000000000..d6d38217f7f8f
Binary files /dev/null and b/test/golden-chromium/screenshot-grid-fullpage.png differ
diff --git a/test/golden-chromium/screenshot-offscreen-clip.png b/test/golden-chromium/screenshot-offscreen-clip.png
new file mode 100644
index 0000000000000..e503f801ec6a1
Binary files /dev/null and b/test/golden-chromium/screenshot-offscreen-clip.png differ
diff --git a/test/golden-chromium/screenshot-sanity.png b/test/golden-chromium/screenshot-sanity.png
new file mode 100644
index 0000000000000..ecab61fe179e8
Binary files /dev/null and b/test/golden-chromium/screenshot-sanity.png differ
diff --git a/test/golden-chromium/transparent.png b/test/golden-chromium/transparent.png
new file mode 100644
index 0000000000000..1cf45d8688fc6
Binary files /dev/null and b/test/golden-chromium/transparent.png differ
diff --git a/test/golden-chromium/vision-deficiency-achromatopsia.png b/test/golden-chromium/vision-deficiency-achromatopsia.png
new file mode 100644
index 0000000000000..4d74aac44c04a
Binary files /dev/null and b/test/golden-chromium/vision-deficiency-achromatopsia.png differ
diff --git a/test/golden-chromium/vision-deficiency-blurredVision.png b/test/golden-chromium/vision-deficiency-blurredVision.png
new file mode 100644
index 0000000000000..78979425a9738
Binary files /dev/null and b/test/golden-chromium/vision-deficiency-blurredVision.png differ
diff --git a/test/golden-chromium/vision-deficiency-deuteranopia.png b/test/golden-chromium/vision-deficiency-deuteranopia.png
new file mode 100644
index 0000000000000..79b4b0fa1bc6a
Binary files /dev/null and b/test/golden-chromium/vision-deficiency-deuteranopia.png differ
diff --git a/test/golden-chromium/vision-deficiency-protanopia.png b/test/golden-chromium/vision-deficiency-protanopia.png
new file mode 100644
index 0000000000000..bede7c1ed050a
Binary files /dev/null and b/test/golden-chromium/vision-deficiency-protanopia.png differ
diff --git a/test/golden-chromium/vision-deficiency-tritanopia.png b/test/golden-chromium/vision-deficiency-tritanopia.png
new file mode 100644
index 0000000000000..d5f6bbec2e858
Binary files /dev/null and b/test/golden-chromium/vision-deficiency-tritanopia.png differ
diff --git a/test/golden-chromium/white.jpg b/test/golden-chromium/white.jpg
new file mode 100644
index 0000000000000..fb9070def3dab
Binary files /dev/null and b/test/golden-chromium/white.jpg differ
diff --git a/test/golden-firefox/grid-cell-0.png b/test/golden-firefox/grid-cell-0.png
new file mode 100644
index 0000000000000..4677bdbc4f849
Binary files /dev/null and b/test/golden-firefox/grid-cell-0.png differ
diff --git a/test/golden-firefox/grid-cell-1.png b/test/golden-firefox/grid-cell-1.png
new file mode 100644
index 0000000000000..532dc8db65b04
Binary files /dev/null and b/test/golden-firefox/grid-cell-1.png differ
diff --git a/test/golden-firefox/screenshot-clip-odd-size.png b/test/golden-firefox/screenshot-clip-odd-size.png
new file mode 100644
index 0000000000000..8e86dc90178ac
Binary files /dev/null and b/test/golden-firefox/screenshot-clip-odd-size.png differ
diff --git a/test/golden-firefox/screenshot-clip-rect.png b/test/golden-firefox/screenshot-clip-rect.png
new file mode 100644
index 0000000000000..7a744578693d3
Binary files /dev/null and b/test/golden-firefox/screenshot-clip-rect.png differ
diff --git a/test/golden-firefox/screenshot-element-bounding-box.png b/test/golden-firefox/screenshot-element-bounding-box.png
new file mode 100644
index 0000000000000..f4e059c300ccd
Binary files /dev/null and b/test/golden-firefox/screenshot-element-bounding-box.png differ
diff --git a/test/golden-firefox/screenshot-element-fractional-offset.png b/test/golden-firefox/screenshot-element-fractional-offset.png
new file mode 100644
index 0000000000000..f554b1d62c4ab
Binary files /dev/null and b/test/golden-firefox/screenshot-element-fractional-offset.png differ
diff --git a/test/golden-firefox/screenshot-element-fractional.png b/test/golden-firefox/screenshot-element-fractional.png
new file mode 100644
index 0000000000000..d1431bd91dea1
Binary files /dev/null and b/test/golden-firefox/screenshot-element-fractional.png differ
diff --git a/test/golden-firefox/screenshot-element-larger-than-viewport.png b/test/golden-firefox/screenshot-element-larger-than-viewport.png
new file mode 100644
index 0000000000000..6d28cddcea336
Binary files /dev/null and b/test/golden-firefox/screenshot-element-larger-than-viewport.png differ
diff --git a/test/golden-firefox/screenshot-element-padding-border.png b/test/golden-firefox/screenshot-element-padding-border.png
new file mode 100644
index 0000000000000..2b72c7528b256
Binary files /dev/null and b/test/golden-firefox/screenshot-element-padding-border.png differ
diff --git a/test/golden-firefox/screenshot-element-rotate.png b/test/golden-firefox/screenshot-element-rotate.png
new file mode 100644
index 0000000000000..0a78fb1ae7ba5
Binary files /dev/null and b/test/golden-firefox/screenshot-element-rotate.png differ
diff --git a/test/golden-firefox/screenshot-element-scrolled-into-view.png b/test/golden-firefox/screenshot-element-scrolled-into-view.png
new file mode 100644
index 0000000000000..2b72c7528b256
Binary files /dev/null and b/test/golden-firefox/screenshot-element-scrolled-into-view.png differ
diff --git a/test/golden-firefox/screenshot-grid-fullpage.png b/test/golden-firefox/screenshot-grid-fullpage.png
new file mode 100644
index 0000000000000..ac47ec83b19f2
Binary files /dev/null and b/test/golden-firefox/screenshot-grid-fullpage.png differ
diff --git a/test/golden-firefox/screenshot-offscreen-clip.png b/test/golden-firefox/screenshot-offscreen-clip.png
new file mode 100644
index 0000000000000..846b810386f7c
Binary files /dev/null and b/test/golden-firefox/screenshot-offscreen-clip.png differ
diff --git a/test/golden-firefox/screenshot-sanity.png b/test/golden-firefox/screenshot-sanity.png
new file mode 100644
index 0000000000000..07890a04b342a
Binary files /dev/null and b/test/golden-firefox/screenshot-sanity.png differ
diff --git a/test/golden-utils.js b/test/golden-utils.js
new file mode 100644
index 0000000000000..f820afe6bfbb7
--- /dev/null
+++ b/test/golden-utils.js
@@ -0,0 +1,160 @@
+// @ts-nocheck
+/**
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+const path = require('path');
+const fs = require('fs');
+const Diff = require('text-diff');
+const mime = require('mime');
+const PNG = require('pngjs').PNG;
+const jpeg = require('jpeg-js');
+const pixelmatch = require('pixelmatch');
+
+module.exports = { compare };
+
+const GoldenComparators = {
+ 'image/png': compareImages,
+ 'image/jpeg': compareImages,
+ 'text/plain': compareText,
+};
+
+/**
+ * @param {?Object} actualBuffer
+ * @param {!Buffer} expectedBuffer
+ * @param {!string} mimeType
+ * @returns {?{diff: (!Object:undefined), errorMessage: (string|undefined)}}
+ */
+function compareImages(actualBuffer, expectedBuffer, mimeType) {
+ if (!actualBuffer || !(actualBuffer instanceof Buffer))
+ return { errorMessage: 'Actual result should be Buffer.' };
+
+ const actual =
+ mimeType === 'image/png'
+ ? PNG.sync.read(actualBuffer)
+ : jpeg.decode(actualBuffer);
+ const expected =
+ mimeType === 'image/png'
+ ? PNG.sync.read(expectedBuffer)
+ : jpeg.decode(expectedBuffer);
+ if (expected.width !== actual.width || expected.height !== actual.height) {
+ return {
+ errorMessage: `Sizes differ: expected image ${expected.width}px X ${expected.height}px, but got ${actual.width}px X ${actual.height}px. `,
+ };
+ }
+ const diff = new PNG({ width: expected.width, height: expected.height });
+ const count = pixelmatch(
+ expected.data,
+ actual.data,
+ diff.data,
+ expected.width,
+ expected.height,
+ { threshold: 0.1 }
+ );
+ return count > 0 ? { diff: PNG.sync.write(diff) } : null;
+}
+
+/**
+ * @param {?Object} actual
+ * @param {!Buffer} expectedBuffer
+ * @returns {?{diff: (!Object:undefined), errorMessage: (string|undefined)}}
+ */
+function compareText(actual, expectedBuffer) {
+ if (typeof actual !== 'string')
+ return { errorMessage: 'Actual result should be string' };
+ const expected = expectedBuffer.toString('utf-8');
+ if (expected === actual) return null;
+ const diff = new Diff();
+ const result = diff.main(expected, actual);
+ diff.cleanupSemantic(result);
+ let html = diff.prettyHtml(result);
+ const diffStylePath = path.join(__dirname, 'diffstyle.css');
+ html = ` ` + html;
+ return {
+ diff: html,
+ diffExtension: '.html',
+ };
+}
+
+/**
+ * @param {?Object} actual
+ * @param {string} goldenName
+ * @returns {!{pass: boolean, message: (undefined|string)}}
+ */
+function compare(goldenPath, outputPath, actual, goldenName) {
+ goldenPath = path.normalize(goldenPath);
+ outputPath = path.normalize(outputPath);
+ const expectedPath = path.join(goldenPath, goldenName);
+ const actualPath = path.join(outputPath, goldenName);
+
+ const messageSuffix =
+ 'Output is saved in "' + path.basename(outputPath + '" directory');
+
+ if (!fs.existsSync(expectedPath)) {
+ ensureOutputDir();
+ fs.writeFileSync(actualPath, actual);
+ return {
+ pass: false,
+ message: goldenName + ' is missing in golden results. ' + messageSuffix,
+ };
+ }
+ const expected = fs.readFileSync(expectedPath);
+ const mimeType = mime.getType(goldenName);
+ const comparator = GoldenComparators[mimeType];
+ if (!comparator) {
+ return {
+ pass: false,
+ message:
+ 'Failed to find comparator with type ' + mimeType + ': ' + goldenName,
+ };
+ }
+ const result = comparator(actual, expected, mimeType);
+ if (!result) return { pass: true };
+ ensureOutputDir();
+ if (goldenPath === outputPath) {
+ fs.writeFileSync(addSuffix(actualPath, '-actual'), actual);
+ } else {
+ fs.writeFileSync(actualPath, actual);
+ // Copy expected to the output/ folder for convenience.
+ fs.writeFileSync(addSuffix(actualPath, '-expected'), expected);
+ }
+ if (result.diff) {
+ const diffPath = addSuffix(actualPath, '-diff', result.diffExtension);
+ fs.writeFileSync(diffPath, result.diff);
+ }
+
+ let message = goldenName + ' mismatch!';
+ if (result.errorMessage) message += ' ' + result.errorMessage;
+ return {
+ pass: false,
+ message: message + ' ' + messageSuffix,
+ };
+
+ function ensureOutputDir() {
+ if (!fs.existsSync(outputPath)) fs.mkdirSync(outputPath);
+ }
+}
+
+/**
+ * @param {string} filePath
+ * @param {string} suffix
+ * @param {string=} customExtension
+ * @returns {string}
+ */
+function addSuffix(filePath, suffix, customExtension) {
+ const dirname = path.dirname(filePath);
+ const ext = path.extname(filePath);
+ const name = path.basename(filePath, ext);
+ return path.join(dirname, name + suffix + (customExtension || ext));
+}
diff --git a/test/headful.spec.ts b/test/headful.spec.ts
new file mode 100644
index 0000000000000..b41eabc8ea934
--- /dev/null
+++ b/test/headful.spec.ts
@@ -0,0 +1,354 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import path from 'path';
+import os from 'os';
+import fs from 'fs';
+import { promisify } from 'util';
+import expect from 'expect';
+import {
+ getTestState,
+ describeChromeOnly,
+ itFailsWindows,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+import rimraf from 'rimraf';
+
+const rmAsync = promisify(rimraf);
+const mkdtempAsync = promisify(fs.mkdtemp);
+
+const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-');
+
+const extensionPath = path.join(__dirname, 'assets', 'simple-extension');
+
+describeChromeOnly('headful tests', function () {
+ /* These tests fire up an actual browser so let's
+ * allow a higher timeout
+ */
+ this.timeout(20 * 1000);
+
+ let headfulOptions;
+ let headlessOptions;
+ let extensionOptions;
+ let forcedOopifOptions;
+ let devtoolsOptions;
+ const browsers = [];
+
+ beforeEach(() => {
+ const { server, defaultBrowserOptions } = getTestState();
+ headfulOptions = Object.assign({}, defaultBrowserOptions, {
+ headless: false,
+ });
+ headlessOptions = Object.assign({}, defaultBrowserOptions, {
+ headless: true,
+ });
+
+ extensionOptions = Object.assign({}, defaultBrowserOptions, {
+ headless: false,
+ args: [
+ `--disable-extensions-except=${extensionPath}`,
+ `--load-extension=${extensionPath}`,
+ ],
+ });
+
+ forcedOopifOptions = Object.assign({}, defaultBrowserOptions, {
+ headless: false,
+ devtools: true,
+ args: [
+ `--host-rules=MAP oopifdomain 127.0.0.1`,
+ `--isolate-origins=${server.PREFIX.replace(
+ 'localhost',
+ 'oopifdomain'
+ )}`,
+ ],
+ });
+
+ devtoolsOptions = Object.assign({}, defaultBrowserOptions, {
+ headless: false,
+ devtools: true,
+ });
+ });
+
+ async function launchBrowser(puppeteer, options) {
+ const browser = await puppeteer.launch(options);
+ browsers.push(browser);
+ return browser;
+ }
+
+ afterEach(() => {
+ for (const i in browsers) {
+ const browser = browsers[i];
+ if (browser.isConnected()) {
+ browser.close();
+ }
+ delete browsers[i];
+ }
+ });
+
+ describe('HEADFUL', function () {
+ it('background_page target type should be available', async () => {
+ const { puppeteer } = getTestState();
+ const browserWithExtension = await launchBrowser(
+ puppeteer,
+ extensionOptions
+ );
+ const page = await browserWithExtension.newPage();
+ const backgroundPageTarget = await browserWithExtension.waitForTarget(
+ (target) => target.type() === 'background_page'
+ );
+ await page.close();
+ await browserWithExtension.close();
+ expect(backgroundPageTarget).toBeTruthy();
+ });
+ it('target.page() should return a background_page', async function () {
+ const { puppeteer } = getTestState();
+ const browserWithExtension = await launchBrowser(
+ puppeteer,
+ extensionOptions
+ );
+ const backgroundPageTarget = await browserWithExtension.waitForTarget(
+ (target) => target.type() === 'background_page'
+ );
+ const page = await backgroundPageTarget.page();
+ expect(await page.evaluate(() => 2 * 3)).toBe(6);
+ expect(await page.evaluate(() => globalThis.MAGIC)).toBe(42);
+ await browserWithExtension.close();
+ });
+ it('target.page() should return a DevTools page if custom isPageTarget is provided', async function () {
+ const { puppeteer } = getTestState();
+ const originalBrowser = await launchBrowser(puppeteer, devtoolsOptions);
+
+ const browserWSEndpoint = originalBrowser.wsEndpoint();
+
+ const browser = await puppeteer.connect({
+ browserWSEndpoint,
+ isPageTarget: (target) => {
+ return (
+ target.type === 'other' && target.url.startsWith('devtools://')
+ );
+ },
+ });
+ const devtoolsPageTarget = await browser.waitForTarget(
+ (target) => target.type() === 'other'
+ );
+ const page = await devtoolsPageTarget.page();
+ expect(await page.evaluate(() => 2 * 3)).toBe(6);
+ await browser.close();
+ });
+ it('should have default url when launching browser', async function () {
+ const { puppeteer } = getTestState();
+ const browser = await launchBrowser(puppeteer, extensionOptions);
+ const pages = (await browser.pages()).map((page) => page.url());
+ expect(pages).toEqual(['about:blank']);
+ await browser.close();
+ });
+ itFailsWindows(
+ 'headless should be able to read cookies written by headful',
+ async () => {
+ /* Needs investigation into why but this fails consistently on Windows CI. */
+ const { server, puppeteer } = getTestState();
+
+ const userDataDir = await mkdtempAsync(TMP_FOLDER);
+ // Write a cookie in headful chrome
+ const headfulBrowser = await launchBrowser(
+ puppeteer,
+ Object.assign({ userDataDir }, headfulOptions)
+ );
+ const headfulPage = await headfulBrowser.newPage();
+ await headfulPage.goto(server.EMPTY_PAGE);
+ await headfulPage.evaluate(
+ () =>
+ (document.cookie =
+ 'foo=true; expires=Fri, 31 Dec 9999 23:59:59 GMT')
+ );
+ await headfulBrowser.close();
+ // Read the cookie from headless chrome
+ const headlessBrowser = await launchBrowser(
+ puppeteer,
+ Object.assign({ userDataDir }, headlessOptions)
+ );
+ const headlessPage = await headlessBrowser.newPage();
+ await headlessPage.goto(server.EMPTY_PAGE);
+ const cookie = await headlessPage.evaluate(() => document.cookie);
+ await headlessBrowser.close();
+ // This might throw. See https://github.com/puppeteer/puppeteer/issues/2778
+ await rmAsync(userDataDir).catch(() => {});
+ expect(cookie).toBe('foo=true');
+ }
+ );
+ // TODO: Support OOOPIF. @see https://github.com/puppeteer/puppeteer/issues/2548
+ xit('OOPIF: should report google.com frame', async () => {
+ const { server, puppeteer } = getTestState();
+
+ // https://google.com is isolated by default in Chromium embedder.
+ const browser = await launchBrowser(puppeteer, headfulOptions);
+ const page = await browser.newPage();
+ await page.goto(server.EMPTY_PAGE);
+ await page.setRequestInterception(true);
+ page.on('request', (r) => r.respond({ body: 'YO, GOOGLE.COM' }));
+ await page.evaluate(() => {
+ const frame = document.createElement('iframe');
+ frame.setAttribute('src', 'https://google.com/');
+ document.body.appendChild(frame);
+ return new Promise((x) => (frame.onload = x));
+ });
+ await page.waitForSelector('iframe[src="https://google.com/"]');
+ const urls = page
+ .frames()
+ .map((frame) => frame.url())
+ .sort();
+ expect(urls).toEqual([server.EMPTY_PAGE, 'https://google.com/']);
+ await browser.close();
+ });
+ it('OOPIF: should expose events within OOPIFs', async () => {
+ const { server, puppeteer } = getTestState();
+
+ const browser = await launchBrowser(puppeteer, forcedOopifOptions);
+ const page = await browser.newPage();
+
+ // Setup our session listeners to observe OOPIF activity.
+ const session = await page.target().createCDPSession();
+ const networkEvents = [];
+ const otherSessions = [];
+ await session.send('Target.setAutoAttach', {
+ autoAttach: true,
+ flatten: true,
+ waitForDebuggerOnStart: true,
+ });
+ session.on('sessionattached', async (session) => {
+ otherSessions.push(session);
+
+ session.on('Network.requestWillBeSent', (params) =>
+ networkEvents.push(params)
+ );
+ await session.send('Network.enable');
+ await session.send('Runtime.runIfWaitingForDebugger');
+ });
+
+ // Navigate to the empty page and add an OOPIF iframe with at least one request.
+ await page.goto(server.EMPTY_PAGE);
+ await page.evaluate((frameUrl) => {
+ const frame = document.createElement('iframe');
+ frame.setAttribute('src', frameUrl);
+ document.body.appendChild(frame);
+ return new Promise((x, y) => {
+ frame.onload = x;
+ frame.onerror = y;
+ });
+ }, server.PREFIX.replace('localhost', 'oopifdomain') + '/one-style.html');
+ await page.waitForSelector('iframe');
+
+ // Ensure we found the iframe session.
+ expect(otherSessions).toHaveLength(1);
+
+ // Resume the iframe and trigger another request.
+ const iframeSession = otherSessions[0];
+ await iframeSession.send('Runtime.evaluate', {
+ expression: `fetch('/fetch')`,
+ awaitPromise: true,
+ });
+ await browser.close();
+
+ const requests = networkEvents.map((event) => event.request.url);
+ expect(requests).toContain(`http://oopifdomain:${server.PORT}/fetch`);
+ });
+ it('should close browser with beforeunload page', async () => {
+ const { server, puppeteer } = getTestState();
+
+ const browser = await launchBrowser(puppeteer, headfulOptions);
+ const page = await browser.newPage();
+ await page.goto(server.PREFIX + '/beforeunload.html');
+ // We have to interact with a page so that 'beforeunload' handlers
+ // fire.
+ await page.click('body');
+ await browser.close();
+ });
+ it('should open devtools when "devtools: true" option is given', async () => {
+ const { puppeteer } = getTestState();
+
+ const browser = await launchBrowser(
+ puppeteer,
+ Object.assign({ devtools: true }, headfulOptions)
+ );
+ const context = await browser.createIncognitoBrowserContext();
+ await Promise.all([
+ context.newPage(),
+ browser.waitForTarget((target) => target.url().includes('devtools://')),
+ ]);
+ await browser.close();
+ });
+ });
+
+ describe('Page.bringToFront', function () {
+ it('should work', async () => {
+ const { puppeteer } = getTestState();
+ const browser = await launchBrowser(puppeteer, headfulOptions);
+ const page1 = await browser.newPage();
+ const page2 = await browser.newPage();
+
+ await page1.bringToFront();
+ expect(await page1.evaluate(() => document.visibilityState)).toBe(
+ 'visible'
+ );
+ expect(await page2.evaluate(() => document.visibilityState)).toBe(
+ 'hidden'
+ );
+
+ await page2.bringToFront();
+ expect(await page1.evaluate(() => document.visibilityState)).toBe(
+ 'hidden'
+ );
+ expect(await page2.evaluate(() => document.visibilityState)).toBe(
+ 'visible'
+ );
+
+ await page1.close();
+ await page2.close();
+ await browser.close();
+ });
+ });
+
+ describe('Page.screenshot', function () {
+ it('should run in parallel in multiple pages', async () => {
+ const { server, puppeteer } = getTestState();
+ const browser = await puppeteer.launch(headfulOptions);
+ const context = await browser.createIncognitoBrowserContext();
+
+ const N = 2;
+ const pages = await Promise.all(
+ Array(N)
+ .fill(0)
+ .map(async () => {
+ const page = await context.newPage();
+ await page.goto(server.PREFIX + '/grid.html');
+ return page;
+ })
+ );
+ const promises = [];
+ for (let i = 0; i < N; ++i)
+ promises.push(
+ pages[i].screenshot({
+ clip: { x: 50 * i, y: 0, width: 50, height: 50 },
+ })
+ );
+ const screenshots = await Promise.all(promises);
+ for (let i = 0; i < N; ++i)
+ expect(screenshots[i]).toBeGolden(`grid-cell-${i}.png`);
+ await Promise.all(pages.map((page) => page.close()));
+
+ await browser.close();
+ });
+ });
+});
diff --git a/test/idle_override.spec.ts b/test/idle_override.spec.ts
new file mode 100644
index 0000000000000..18e7da3986c70
--- /dev/null
+++ b/test/idle_override.spec.ts
@@ -0,0 +1,94 @@
+/**
+ * Copyright 2020 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import expect from 'expect';
+import {
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+ describeFailsFirefox,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+
+describeFailsFirefox('Emulate idle state', () => {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+
+ async function getIdleState() {
+ const { page } = getTestState();
+
+ const stateElement = await page.$('#state');
+ return await page.evaluate((element: HTMLElement) => {
+ return element.innerText;
+ }, stateElement);
+ }
+
+ async function verifyState(expectedState: string) {
+ const actualState = await getIdleState();
+ expect(actualState).toEqual(expectedState);
+ }
+
+ it('changing idle state emulation causes change of the IdleDetector state', async () => {
+ const { page, server, context } = getTestState();
+ await context.overridePermissions(server.PREFIX + '/idle-detector.html', [
+ 'idle-detection',
+ ]);
+
+ await page.goto(server.PREFIX + '/idle-detector.html');
+
+ // Store initial state, as soon as it is not guaranteed to be `active, unlocked`.
+ const initialState = await getIdleState();
+
+ // Emulate Idle states and verify IdleDetector updates state accordingly.
+ await page.emulateIdleState({
+ isUserActive: false,
+ isScreenUnlocked: false,
+ });
+ await verifyState('Idle state: idle, locked.');
+
+ await page.emulateIdleState({
+ isUserActive: true,
+ isScreenUnlocked: false,
+ });
+ await verifyState('Idle state: active, locked.');
+
+ await page.emulateIdleState({
+ isUserActive: true,
+ isScreenUnlocked: true,
+ });
+ await verifyState('Idle state: active, unlocked.');
+
+ await page.emulateIdleState({
+ isUserActive: false,
+ isScreenUnlocked: true,
+ });
+ await verifyState('Idle state: idle, unlocked.');
+
+ // Remove Idle emulation and verify IdleDetector is in initial state.
+ await page.emulateIdleState();
+ await verifyState(initialState);
+
+ // Emulate idle state again after removing emulation.
+ await page.emulateIdleState({
+ isUserActive: false,
+ isScreenUnlocked: false,
+ });
+ await verifyState('Idle state: idle, locked.');
+
+ // Remove emulation second time.
+ await page.emulateIdleState();
+ await verifyState(initialState);
+ });
+});
diff --git a/test/ignorehttpserrors.spec.ts b/test/ignorehttpserrors.spec.ts
new file mode 100644
index 0000000000000..de6f05040c01f
--- /dev/null
+++ b/test/ignorehttpserrors.spec.ts
@@ -0,0 +1,135 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import expect from 'expect';
+import {
+ getTestState,
+ describeFailsFirefox,
+ itFailsFirefox,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+
+describe('ignoreHTTPSErrors', function () {
+ /* Note that this test creates its own browser rather than use
+ * the one provided by the test set-up as we need one
+ * with ignoreHTTPSErrors set to true
+ */
+ let browser;
+ let context;
+ let page;
+
+ before(async () => {
+ const { defaultBrowserOptions, puppeteer } = getTestState();
+ const options = Object.assign(
+ { ignoreHTTPSErrors: true },
+ defaultBrowserOptions
+ );
+ browser = await puppeteer.launch(options);
+ });
+
+ after(async () => {
+ await browser.close();
+ browser = null;
+ });
+
+ beforeEach(async () => {
+ context = await browser.createIncognitoBrowserContext();
+ page = await context.newPage();
+ });
+
+ afterEach(async () => {
+ await context.close();
+ context = null;
+ page = null;
+ });
+
+ describeFailsFirefox('Response.securityDetails', function () {
+ it('should work', async () => {
+ const { httpsServer } = getTestState();
+
+ const [serverRequest, response] = await Promise.all([
+ httpsServer.waitForRequest('/empty.html'),
+ page.goto(httpsServer.EMPTY_PAGE),
+ ]);
+ const securityDetails = response.securityDetails();
+ expect(securityDetails.issuer()).toBe('puppeteer-tests');
+ const protocol = serverRequest.socket.getProtocol().replace('v', ' ');
+ expect(securityDetails.protocol()).toBe(protocol);
+ expect(securityDetails.subjectName()).toBe('puppeteer-tests');
+ expect(securityDetails.validFrom()).toBe(1589357069);
+ expect(securityDetails.validTo()).toBe(1904717069);
+ expect(securityDetails.subjectAlternativeNames()).toEqual([
+ 'www.puppeteer-tests.test',
+ 'www.puppeteer-tests-1.test',
+ ]);
+ });
+ it('should be |null| for non-secure requests', async () => {
+ const { server } = getTestState();
+
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.securityDetails()).toBe(null);
+ });
+ it('Network redirects should report SecurityDetails', async () => {
+ const { httpsServer } = getTestState();
+
+ httpsServer.setRedirect('/plzredirect', '/empty.html');
+ const responses = [];
+ page.on('response', (response) => responses.push(response));
+ const [serverRequest] = await Promise.all([
+ httpsServer.waitForRequest('/plzredirect'),
+ page.goto(httpsServer.PREFIX + '/plzredirect'),
+ ]);
+ expect(responses.length).toBe(2);
+ expect(responses[0].status()).toBe(302);
+ const securityDetails = responses[0].securityDetails();
+ const protocol = serverRequest.socket.getProtocol().replace('v', ' ');
+ expect(securityDetails.protocol()).toBe(protocol);
+ });
+ });
+
+ it('should work', async () => {
+ const { httpsServer } = getTestState();
+
+ let error = null;
+ const response = await page
+ .goto(httpsServer.EMPTY_PAGE)
+ .catch((error_) => (error = error_));
+ expect(error).toBe(null);
+ expect(response.ok()).toBe(true);
+ });
+ itFailsFirefox('should work with request interception', async () => {
+ const { httpsServer } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => request.continue());
+ const response = await page.goto(httpsServer.EMPTY_PAGE);
+ expect(response.status()).toBe(200);
+ });
+ itFailsFirefox('should work with mixed content', async () => {
+ const { server, httpsServer } = getTestState();
+
+ httpsServer.setRoute('/mixedcontent.html', (req, res) => {
+ res.end(``);
+ });
+ await page.goto(httpsServer.PREFIX + '/mixedcontent.html', {
+ waitUntil: 'load',
+ });
+ expect(page.frames().length).toBe(2);
+ // Make sure blocked iframe has functional execution context
+ // @see https://github.com/puppeteer/puppeteer/issues/2709
+ expect(await page.frames()[0].evaluate('1 + 2')).toBe(3);
+ expect(await page.frames()[1].evaluate('2 + 3')).toBe(5);
+ });
+});
diff --git a/test/input.spec.ts b/test/input.spec.ts
new file mode 100644
index 0000000000000..9244356333a34
--- /dev/null
+++ b/test/input.spec.ts
@@ -0,0 +1,343 @@
+/**
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import path from 'path';
+import expect from 'expect';
+import {
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+ describeFailsFirefox,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+
+const FILE_TO_UPLOAD = path.join(__dirname, '/assets/file-to-upload.txt');
+
+describe('input tests', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+
+ describeFailsFirefox('input', function () {
+ it('should upload the file', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/fileupload.html');
+ const filePath = path.relative(process.cwd(), FILE_TO_UPLOAD);
+ const input = await page.$('input');
+ await page.evaluate((e: HTMLElement) => {
+ globalThis._inputEvents = [];
+ e.addEventListener('change', (ev) =>
+ globalThis._inputEvents.push(ev.type)
+ );
+ e.addEventListener('input', (ev) =>
+ globalThis._inputEvents.push(ev.type)
+ );
+ }, input);
+ await input.uploadFile(filePath);
+ expect(
+ await page.evaluate((e: HTMLInputElement) => e.files[0].name, input)
+ ).toBe('file-to-upload.txt');
+ expect(
+ await page.evaluate((e: HTMLInputElement) => e.files[0].type, input)
+ ).toBe('text/plain');
+ expect(await page.evaluate(() => globalThis._inputEvents)).toEqual([
+ 'input',
+ 'change',
+ ]);
+ expect(
+ await page.evaluate((e: HTMLInputElement) => {
+ const reader = new FileReader();
+ const promise = new Promise((fulfill) => (reader.onload = fulfill));
+ reader.readAsText(e.files[0]);
+ return promise.then(() => reader.result);
+ }, input)
+ ).toBe('contents of the file');
+ });
+ });
+
+ describeFailsFirefox('Page.waitForFileChooser', function () {
+ it('should work when file input is attached to DOM', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(` `);
+ const [chooser] = await Promise.all([
+ page.waitForFileChooser(),
+ page.click('input'),
+ ]);
+ expect(chooser).toBeTruthy();
+ });
+ it('should work when file input is not attached to DOM', async () => {
+ const { page } = getTestState();
+
+ const [chooser] = await Promise.all([
+ page.waitForFileChooser(),
+ page.evaluate(() => {
+ const el = document.createElement('input');
+ el.type = 'file';
+ el.click();
+ }),
+ ]);
+ expect(chooser).toBeTruthy();
+ });
+ it('should respect timeout', async () => {
+ const { page, puppeteer } = getTestState();
+
+ let error = null;
+ await page
+ .waitForFileChooser({ timeout: 1 })
+ .catch((error_) => (error = error_));
+ expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
+ });
+ it('should respect default timeout when there is no custom timeout', async () => {
+ const { page, puppeteer } = getTestState();
+
+ page.setDefaultTimeout(1);
+ let error = null;
+ await page.waitForFileChooser().catch((error_) => (error = error_));
+ expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
+ });
+ it('should prioritize exact timeout over default timeout', async () => {
+ const { page, puppeteer } = getTestState();
+
+ page.setDefaultTimeout(0);
+ let error = null;
+ await page
+ .waitForFileChooser({ timeout: 1 })
+ .catch((error_) => (error = error_));
+ expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
+ });
+ it('should work with no timeout', async () => {
+ const { page } = getTestState();
+
+ const [chooser] = await Promise.all([
+ page.waitForFileChooser({ timeout: 0 }),
+ page.evaluate(() =>
+ setTimeout(() => {
+ const el = document.createElement('input');
+ el.type = 'file';
+ el.click();
+ }, 50)
+ ),
+ ]);
+ expect(chooser).toBeTruthy();
+ });
+ it('should return the same file chooser when there are many watchdogs simultaneously', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(` `);
+ const [fileChooser1, fileChooser2] = await Promise.all([
+ page.waitForFileChooser(),
+ page.waitForFileChooser(),
+ page.$eval('input', (input: HTMLInputElement) => input.click()),
+ ]);
+ expect(fileChooser1 === fileChooser2).toBe(true);
+ });
+ });
+
+ describeFailsFirefox('FileChooser.accept', function () {
+ it('should accept single file', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(
+ ` `
+ );
+ const [chooser] = await Promise.all([
+ page.waitForFileChooser(),
+ page.click('input'),
+ ]);
+ await Promise.all([
+ chooser.accept([FILE_TO_UPLOAD]),
+ new Promise((x) => page.once('metrics', x)),
+ ]);
+ expect(
+ await page.$eval(
+ 'input',
+ (input: HTMLInputElement) => input.files.length
+ )
+ ).toBe(1);
+ expect(
+ await page.$eval(
+ 'input',
+ (input: HTMLInputElement) => input.files[0].name
+ )
+ ).toBe('file-to-upload.txt');
+ });
+ it('should be able to read selected file', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(` `);
+ page
+ .waitForFileChooser()
+ .then((chooser) => chooser.accept([FILE_TO_UPLOAD]));
+ expect(
+ await page.$eval('input', async (picker: HTMLInputElement) => {
+ picker.click();
+ await new Promise((x) => (picker.oninput = x));
+ const reader = new FileReader();
+ const promise = new Promise((fulfill) => (reader.onload = fulfill));
+ reader.readAsText(picker.files[0]);
+ return promise.then(() => reader.result);
+ })
+ ).toBe('contents of the file');
+ });
+ it('should be able to reset selected files with empty file list', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(` `);
+ page
+ .waitForFileChooser()
+ .then((chooser) => chooser.accept([FILE_TO_UPLOAD]));
+ expect(
+ await page.$eval('input', async (picker: HTMLInputElement) => {
+ picker.click();
+ await new Promise((x) => (picker.oninput = x));
+ return picker.files.length;
+ })
+ ).toBe(1);
+ page.waitForFileChooser().then((chooser) => chooser.accept([]));
+ expect(
+ await page.$eval('input', async (picker: HTMLInputElement) => {
+ picker.click();
+ await new Promise((x) => (picker.oninput = x));
+ return picker.files.length;
+ })
+ ).toBe(0);
+ });
+ it('should not accept multiple files for single-file input', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(` `);
+ const [chooser] = await Promise.all([
+ page.waitForFileChooser(),
+ page.click('input'),
+ ]);
+ let error = null;
+ await chooser
+ .accept([
+ path.relative(
+ process.cwd(),
+ __dirname + '/assets/file-to-upload.txt'
+ ),
+ path.relative(process.cwd(), __dirname + '/assets/pptr.png'),
+ ])
+ .catch((error_) => (error = error_));
+ expect(error).not.toBe(null);
+ });
+ it('should fail for non-existent files', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(` `);
+ const [chooser] = await Promise.all([
+ page.waitForFileChooser(),
+ page.click('input'),
+ ]);
+ let error = null;
+ await chooser
+ .accept(['file-does-not-exist.txt'])
+ .catch((error_) => (error = error_));
+ expect(error).not.toBe(null);
+ });
+ it('should fail when accepting file chooser twice', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(` `);
+ const [fileChooser] = await Promise.all([
+ page.waitForFileChooser(),
+ page.$eval('input', (input: HTMLInputElement) => input.click()),
+ ]);
+ await fileChooser.accept([]);
+ let error = null;
+ await fileChooser.accept([]).catch((error_) => (error = error_));
+ expect(error.message).toBe(
+ 'Cannot accept FileChooser which is already handled!'
+ );
+ });
+ });
+
+ describeFailsFirefox('FileChooser.cancel', function () {
+ it('should cancel dialog', async () => {
+ const { page } = getTestState();
+
+ // Consider file chooser canceled if we can summon another one.
+ // There's no reliable way in WebPlatform to see that FileChooser was
+ // canceled.
+ await page.setContent(` `);
+ const [fileChooser1] = await Promise.all([
+ page.waitForFileChooser(),
+ page.$eval('input', (input: HTMLInputElement) => input.click()),
+ ]);
+ await fileChooser1.cancel();
+ // If this resolves, than we successfully canceled file chooser.
+ await Promise.all([
+ page.waitForFileChooser(),
+ page.$eval('input', (input: HTMLInputElement) => input.click()),
+ ]);
+ });
+ it('should fail when canceling file chooser twice', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(` `);
+ const [fileChooser] = await Promise.all([
+ page.waitForFileChooser(),
+ page.$eval('input', (input: HTMLInputElement) => input.click()),
+ ]);
+ await fileChooser.cancel();
+ let error = null;
+
+ try {
+ fileChooser.cancel();
+ } catch (error_) {
+ error = error_;
+ }
+
+ expect(error.message).toBe(
+ 'Cannot cancel FileChooser which is already handled!'
+ );
+ });
+ });
+
+ describeFailsFirefox('FileChooser.isMultiple', () => {
+ it('should work for single file pick', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(` `);
+ const [chooser] = await Promise.all([
+ page.waitForFileChooser(),
+ page.click('input'),
+ ]);
+ expect(chooser.isMultiple()).toBe(false);
+ });
+ it('should work for "multiple"', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(` `);
+ const [chooser] = await Promise.all([
+ page.waitForFileChooser(),
+ page.click('input'),
+ ]);
+ expect(chooser.isMultiple()).toBe(true);
+ });
+ it('should work for "webkitdirectory"', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(` `);
+ const [chooser] = await Promise.all([
+ page.waitForFileChooser(),
+ page.click('input'),
+ ]);
+ expect(chooser.isMultiple()).toBe(true);
+ });
+ });
+});
diff --git a/test/jshandle.spec.ts b/test/jshandle.spec.ts
new file mode 100644
index 0000000000000..ae1c186e43097
--- /dev/null
+++ b/test/jshandle.spec.ts
@@ -0,0 +1,401 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import expect from 'expect';
+import { JSHandle } from '../lib/cjs/puppeteer/common/JSHandle.js';
+import {
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+ itFailsFirefox,
+ shortWaitForArrayToHaveAtLeastNElements,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+
+describe('JSHandle', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+
+ describe('Page.evaluateHandle', function () {
+ it('should work', async () => {
+ const { page } = getTestState();
+
+ const windowHandle = await page.evaluateHandle(() => window);
+ expect(windowHandle).toBeTruthy();
+ });
+ it('should accept object handle as an argument', async () => {
+ const { page } = getTestState();
+
+ const navigatorHandle = await page.evaluateHandle(() => navigator);
+ const text = await page.evaluate(
+ (e: Navigator) => e.userAgent,
+ navigatorHandle
+ );
+ expect(text).toContain('Mozilla');
+ });
+ it('should accept object handle to primitive types', async () => {
+ const { page } = getTestState();
+
+ const aHandle = await page.evaluateHandle(() => 5);
+ const isFive = await page.evaluate((e) => Object.is(e, 5), aHandle);
+ expect(isFive).toBeTruthy();
+ });
+ it('should warn on nested object handles', async () => {
+ const { page } = getTestState();
+
+ const aHandle = await page.evaluateHandle(() => document.body);
+ let error = null;
+ await page
+ // @ts-expect-error we are deliberately passing a bad type here (nested object)
+ .evaluateHandle((opts) => opts.elem.querySelector('p'), {
+ elem: aHandle,
+ })
+ .catch((error_) => (error = error_));
+ expect(error.message).toContain('Are you passing a nested JSHandle?');
+ });
+ it('should accept object handle to unserializable value', async () => {
+ const { page } = getTestState();
+
+ const aHandle = await page.evaluateHandle(() => Infinity);
+ expect(await page.evaluate((e) => Object.is(e, Infinity), aHandle)).toBe(
+ true
+ );
+ });
+ it('should use the same JS wrappers', async () => {
+ const { page } = getTestState();
+
+ const aHandle = await page.evaluateHandle(() => {
+ globalThis.FOO = 123;
+ return window;
+ });
+ expect(await page.evaluate((e: { FOO: number }) => e.FOO, aHandle)).toBe(
+ 123
+ );
+ });
+ });
+
+ describe('JSHandle.getProperty', function () {
+ it('should work', async () => {
+ const { page } = getTestState();
+
+ const aHandle = await page.evaluateHandle(() => ({
+ one: 1,
+ two: 2,
+ three: 3,
+ }));
+ const twoHandle = await aHandle.getProperty('two');
+ expect(await twoHandle.jsonValue()).toEqual(2);
+ });
+
+ it('should return a JSHandle even if the property does not exist', async () => {
+ const { page } = getTestState();
+
+ const aHandle = await page.evaluateHandle(() => ({
+ one: 1,
+ two: 2,
+ three: 3,
+ }));
+ const undefinedHandle = await aHandle.getProperty('doesnotexist');
+ expect(undefinedHandle).toBeInstanceOf(JSHandle);
+ expect(await undefinedHandle.jsonValue()).toBe(undefined);
+ });
+ });
+
+ describe('JSHandle.jsonValue', function () {
+ it('should work', async () => {
+ const { page } = getTestState();
+
+ const aHandle = await page.evaluateHandle(() => ({ foo: 'bar' }));
+ const json = await aHandle.jsonValue>();
+ expect(json).toEqual({ foo: 'bar' });
+ });
+
+ it('works with jsonValues that are not objects', async () => {
+ const { page } = getTestState();
+
+ const aHandle = await page.evaluateHandle(() => ['a', 'b']);
+ const json = await aHandle.jsonValue();
+ expect(json).toEqual(['a', 'b']);
+ });
+
+ it('works with jsonValues that are primitives', async () => {
+ const { page } = getTestState();
+
+ const aHandle = await page.evaluateHandle(() => 'foo');
+ const json = await aHandle.jsonValue();
+ expect(json).toEqual('foo');
+ });
+
+ itFailsFirefox('should not work with dates', async () => {
+ const { page } = getTestState();
+
+ const dateHandle = await page.evaluateHandle(
+ () => new Date('2017-09-26T00:00:00.000Z')
+ );
+ const json = await dateHandle.jsonValue();
+ expect(json).toEqual({});
+ });
+ it('should throw for circular objects', async () => {
+ const { page, isChrome } = getTestState();
+
+ const windowHandle = await page.evaluateHandle('window');
+ let error = null;
+ await windowHandle.jsonValue().catch((error_) => (error = error_));
+ if (isChrome)
+ expect(error.message).toContain('Object reference chain is too long');
+ else expect(error.message).toContain('Object is not serializable');
+ });
+ });
+
+ describe('JSHandle.getProperties', function () {
+ it('should work', async () => {
+ const { page } = getTestState();
+
+ const aHandle = await page.evaluateHandle(() => ({
+ foo: 'bar',
+ }));
+ const properties = await aHandle.getProperties();
+ const foo = properties.get('foo');
+ expect(foo).toBeTruthy();
+ expect(await foo.jsonValue()).toBe('bar');
+ });
+ it('should return even non-own properties', async () => {
+ const { page } = getTestState();
+
+ const aHandle = await page.evaluateHandle(() => {
+ class A {
+ a: string;
+ constructor() {
+ this.a = '1';
+ }
+ }
+ class B extends A {
+ b: string;
+ constructor() {
+ super();
+ this.b = '2';
+ }
+ }
+ return new B();
+ });
+ const properties = await aHandle.getProperties();
+ expect(await properties.get('a').jsonValue()).toBe('1');
+ expect(await properties.get('b').jsonValue()).toBe('2');
+ });
+ });
+
+ describe('JSHandle.asElement', function () {
+ it('should work', async () => {
+ const { page } = getTestState();
+
+ const aHandle = await page.evaluateHandle(() => document.body);
+ const element = aHandle.asElement();
+ expect(element).toBeTruthy();
+ });
+ it('should return null for non-elements', async () => {
+ const { page } = getTestState();
+
+ const aHandle = await page.evaluateHandle(() => 2);
+ const element = aHandle.asElement();
+ expect(element).toBeFalsy();
+ });
+ it('should return ElementHandle for TextNodes', async () => {
+ const { page } = getTestState();
+
+ await page.setContent('ee!
');
+ const aHandle = await page.evaluateHandle(
+ () => document.querySelector('div').firstChild
+ );
+ const element = aHandle.asElement();
+ expect(element).toBeTruthy();
+ expect(
+ await page.evaluate(
+ (e: HTMLElement) => e.nodeType === Node.TEXT_NODE,
+ element
+ )
+ );
+ });
+ });
+
+ describe('JSHandle.toString', function () {
+ it('should work for primitives', async () => {
+ const { page } = getTestState();
+
+ const numberHandle = await page.evaluateHandle(() => 2);
+ expect(numberHandle.toString()).toBe('JSHandle:2');
+ const stringHandle = await page.evaluateHandle(() => 'a');
+ expect(stringHandle.toString()).toBe('JSHandle:a');
+ });
+ it('should work for complicated objects', async () => {
+ const { page } = getTestState();
+
+ const aHandle = await page.evaluateHandle(() => window);
+ expect(aHandle.toString()).toBe('JSHandle@object');
+ });
+ it('should work with different subtypes', async () => {
+ const { page } = getTestState();
+
+ expect((await page.evaluateHandle('(function(){})')).toString()).toBe(
+ 'JSHandle@function'
+ );
+ expect((await page.evaluateHandle('12')).toString()).toBe('JSHandle:12');
+ expect((await page.evaluateHandle('true')).toString()).toBe(
+ 'JSHandle:true'
+ );
+ expect((await page.evaluateHandle('undefined')).toString()).toBe(
+ 'JSHandle:undefined'
+ );
+ expect((await page.evaluateHandle('"foo"')).toString()).toBe(
+ 'JSHandle:foo'
+ );
+ expect((await page.evaluateHandle('Symbol()')).toString()).toBe(
+ 'JSHandle@symbol'
+ );
+ expect((await page.evaluateHandle('new Map()')).toString()).toBe(
+ 'JSHandle@map'
+ );
+ expect((await page.evaluateHandle('new Set()')).toString()).toBe(
+ 'JSHandle@set'
+ );
+ expect((await page.evaluateHandle('[]')).toString()).toBe(
+ 'JSHandle@array'
+ );
+ expect((await page.evaluateHandle('null')).toString()).toBe(
+ 'JSHandle:null'
+ );
+ expect((await page.evaluateHandle('/foo/')).toString()).toBe(
+ 'JSHandle@regexp'
+ );
+ expect((await page.evaluateHandle('document.body')).toString()).toBe(
+ 'JSHandle@node'
+ );
+ expect((await page.evaluateHandle('new Date()')).toString()).toBe(
+ 'JSHandle@date'
+ );
+ expect((await page.evaluateHandle('new WeakMap()')).toString()).toBe(
+ 'JSHandle@weakmap'
+ );
+ expect((await page.evaluateHandle('new WeakSet()')).toString()).toBe(
+ 'JSHandle@weakset'
+ );
+ expect((await page.evaluateHandle('new Error()')).toString()).toBe(
+ 'JSHandle@error'
+ );
+ expect((await page.evaluateHandle('new Int32Array()')).toString()).toBe(
+ 'JSHandle@typedarray'
+ );
+ expect((await page.evaluateHandle('new Proxy({}, {})')).toString()).toBe(
+ 'JSHandle@proxy'
+ );
+ });
+ });
+
+ describe('JSHandle.clickablePoint', function () {
+ it('should work', async () => {
+ const { page } = getTestState();
+
+ await page.evaluate(() => {
+ document.body.style.padding = '0';
+ document.body.style.margin = '0';
+ document.body.innerHTML = `
+
+ `;
+ });
+ await page.evaluate(async () => {
+ return new Promise((resolve) => window.requestAnimationFrame(resolve));
+ });
+ const divHandle = await page.$('div');
+ expect(await divHandle.clickablePoint()).toEqual({
+ x: 45 + 60, // margin + middle point offset
+ y: 45 + 30, // margin + middle point offset
+ });
+ expect(
+ await divHandle.clickablePoint({
+ x: 10,
+ y: 15,
+ })
+ ).toEqual({
+ x: 30 + 10, // margin + offset
+ y: 30 + 15, // margin + offset
+ });
+ });
+
+ it('should work for iframes', async () => {
+ const { page } = getTestState();
+ await page.evaluate(() => {
+ document.body.style.padding = '10px';
+ document.body.style.margin = '10px';
+ document.body.innerHTML = `
+
+ `;
+ });
+ await page.evaluate(async () => {
+ return new Promise((resolve) => window.requestAnimationFrame(resolve));
+ });
+ const frame = page.frames()[1];
+ const divHandle = await frame.$('div');
+ expect(await divHandle.clickablePoint()).toEqual({
+ x: 20 + 45 + 60, // iframe pos + margin + middle point offset
+ y: 20 + 45 + 30, // iframe pos + margin + middle point offset
+ });
+ expect(
+ await divHandle.clickablePoint({
+ x: 10,
+ y: 15,
+ })
+ ).toEqual({
+ x: 20 + 30 + 10, // iframe pos + margin + offset
+ y: 20 + 30 + 15, // iframe pos + margin + offset
+ });
+ });
+ });
+
+ describe('JSHandle.click', function () {
+ itFailsFirefox('should work', async () => {
+ const { page } = getTestState();
+
+ const clicks = [];
+
+ await page.exposeFunction('reportClick', (x: number, y: number): void => {
+ clicks.push([x, y]);
+ });
+
+ await page.evaluate(() => {
+ document.body.style.padding = '0';
+ document.body.style.margin = '0';
+ document.body.innerHTML = `
+
+ `;
+ document.body.addEventListener('click', (e) => {
+ (window as any).reportClick(e.clientX, e.clientY);
+ });
+ });
+
+ const divHandle = await page.$('div');
+ await divHandle.click();
+ await divHandle.click({
+ offset: {
+ x: 10,
+ y: 15,
+ },
+ });
+ await shortWaitForArrayToHaveAtLeastNElements(clicks, 2);
+ expect(clicks).toEqual([
+ [45 + 60, 45 + 30], // margin + middle point offset
+ [30 + 10, 30 + 15], // margin + offset
+ ]);
+ });
+ });
+});
diff --git a/test/keyboard.spec.ts b/test/keyboard.spec.ts
new file mode 100644
index 0000000000000..5aff0f95e7945
--- /dev/null
+++ b/test/keyboard.spec.ts
@@ -0,0 +1,408 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import utils from './utils.js';
+import os from 'os';
+import expect from 'expect';
+import {
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+ itFailsFirefox,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+import { KeyInput } from '../lib/cjs/puppeteer/common/USKeyboardLayout.js';
+
+describe('Keyboard', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+
+ it('should type into a textarea', async () => {
+ const { page } = getTestState();
+
+ await page.evaluate(() => {
+ const textarea = document.createElement('textarea');
+ document.body.appendChild(textarea);
+ textarea.focus();
+ });
+ const text = 'Hello world. I am the text that was typed!';
+ await page.keyboard.type(text);
+ expect(
+ await page.evaluate(() => document.querySelector('textarea').value)
+ ).toBe(text);
+ });
+ itFailsFirefox('should press the metaKey', async () => {
+ const { page, isFirefox } = getTestState();
+
+ await page.evaluate(() => {
+ (window as any).keyPromise = new Promise((resolve) =>
+ document.addEventListener('keydown', (event) => resolve(event.key))
+ );
+ });
+ await page.keyboard.press('Meta');
+ expect(await page.evaluate('keyPromise')).toBe(
+ isFirefox && os.platform() !== 'darwin' ? 'OS' : 'Meta'
+ );
+ });
+ it('should move with the arrow keys', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/textarea.html');
+ await page.type('textarea', 'Hello World!');
+ expect(
+ await page.evaluate(() => document.querySelector('textarea').value)
+ ).toBe('Hello World!');
+ for (let i = 0; i < 'World!'.length; i++) page.keyboard.press('ArrowLeft');
+ await page.keyboard.type('inserted ');
+ expect(
+ await page.evaluate(() => document.querySelector('textarea').value)
+ ).toBe('Hello inserted World!');
+ page.keyboard.down('Shift');
+ for (let i = 0; i < 'inserted '.length; i++)
+ page.keyboard.press('ArrowLeft');
+ page.keyboard.up('Shift');
+ await page.keyboard.press('Backspace');
+ expect(
+ await page.evaluate(() => document.querySelector('textarea').value)
+ ).toBe('Hello World!');
+ });
+ it('should send a character with ElementHandle.press', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/textarea.html');
+ const textarea = await page.$('textarea');
+ await textarea.press('a');
+ expect(
+ await page.evaluate(() => document.querySelector('textarea').value)
+ ).toBe('a');
+
+ await page.evaluate(() =>
+ window.addEventListener('keydown', (e) => e.preventDefault(), true)
+ );
+
+ await textarea.press('b');
+ expect(
+ await page.evaluate(() => document.querySelector('textarea').value)
+ ).toBe('a');
+ });
+ itFailsFirefox(
+ 'ElementHandle.press should support |text| option',
+ async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/textarea.html');
+ const textarea = await page.$('textarea');
+ await textarea.press('a', { text: 'ё' });
+ expect(
+ await page.evaluate(() => document.querySelector('textarea').value)
+ ).toBe('ё');
+ }
+ );
+ itFailsFirefox('should send a character with sendCharacter', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/textarea.html');
+ await page.focus('textarea');
+ await page.keyboard.sendCharacter('嗨');
+ expect(
+ await page.evaluate(() => document.querySelector('textarea').value)
+ ).toBe('嗨');
+ await page.evaluate(() =>
+ window.addEventListener('keydown', (e) => e.preventDefault(), true)
+ );
+ await page.keyboard.sendCharacter('a');
+ expect(
+ await page.evaluate(() => document.querySelector('textarea').value)
+ ).toBe('嗨a');
+ });
+ itFailsFirefox('should report shiftKey', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/keyboard.html');
+ const keyboard = page.keyboard;
+ const codeForKey = new Map([
+ ['Shift', 16],
+ ['Alt', 18],
+ ['Control', 17],
+ ]);
+ for (const [modifierKey, modifierCode] of codeForKey) {
+ await keyboard.down(modifierKey);
+ expect(await page.evaluate(() => globalThis.getResult())).toBe(
+ 'Keydown: ' +
+ modifierKey +
+ ' ' +
+ modifierKey +
+ 'Left ' +
+ modifierCode +
+ ' [' +
+ modifierKey +
+ ']'
+ );
+ await keyboard.down('!');
+ // Shift+! will generate a keypress
+ if (modifierKey === 'Shift')
+ expect(await page.evaluate(() => globalThis.getResult())).toBe(
+ 'Keydown: ! Digit1 49 [' +
+ modifierKey +
+ ']\nKeypress: ! Digit1 33 33 [' +
+ modifierKey +
+ ']'
+ );
+ else
+ expect(await page.evaluate(() => globalThis.getResult())).toBe(
+ 'Keydown: ! Digit1 49 [' + modifierKey + ']'
+ );
+
+ await keyboard.up('!');
+ expect(await page.evaluate(() => globalThis.getResult())).toBe(
+ 'Keyup: ! Digit1 49 [' + modifierKey + ']'
+ );
+ await keyboard.up(modifierKey);
+ expect(await page.evaluate(() => globalThis.getResult())).toBe(
+ 'Keyup: ' +
+ modifierKey +
+ ' ' +
+ modifierKey +
+ 'Left ' +
+ modifierCode +
+ ' []'
+ );
+ }
+ });
+ it('should report multiple modifiers', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/keyboard.html');
+ const keyboard = page.keyboard;
+ await keyboard.down('Control');
+ expect(await page.evaluate(() => globalThis.getResult())).toBe(
+ 'Keydown: Control ControlLeft 17 [Control]'
+ );
+ await keyboard.down('Alt');
+ expect(await page.evaluate(() => globalThis.getResult())).toBe(
+ 'Keydown: Alt AltLeft 18 [Alt Control]'
+ );
+ await keyboard.down(';');
+ expect(await page.evaluate(() => globalThis.getResult())).toBe(
+ 'Keydown: ; Semicolon 186 [Alt Control]'
+ );
+ await keyboard.up(';');
+ expect(await page.evaluate(() => globalThis.getResult())).toBe(
+ 'Keyup: ; Semicolon 186 [Alt Control]'
+ );
+ await keyboard.up('Control');
+ expect(await page.evaluate(() => globalThis.getResult())).toBe(
+ 'Keyup: Control ControlLeft 17 [Alt]'
+ );
+ await keyboard.up('Alt');
+ expect(await page.evaluate(() => globalThis.getResult())).toBe(
+ 'Keyup: Alt AltLeft 18 []'
+ );
+ });
+ it('should send proper codes while typing', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/keyboard.html');
+ await page.keyboard.type('!');
+ expect(await page.evaluate(() => globalThis.getResult())).toBe(
+ [
+ 'Keydown: ! Digit1 49 []',
+ 'Keypress: ! Digit1 33 33 []',
+ 'Keyup: ! Digit1 49 []',
+ ].join('\n')
+ );
+ await page.keyboard.type('^');
+ expect(await page.evaluate(() => globalThis.getResult())).toBe(
+ [
+ 'Keydown: ^ Digit6 54 []',
+ 'Keypress: ^ Digit6 94 94 []',
+ 'Keyup: ^ Digit6 54 []',
+ ].join('\n')
+ );
+ });
+ it('should send proper codes while typing with shift', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/keyboard.html');
+ const keyboard = page.keyboard;
+ await keyboard.down('Shift');
+ await page.keyboard.type('~');
+ expect(await page.evaluate(() => globalThis.getResult())).toBe(
+ [
+ 'Keydown: Shift ShiftLeft 16 [Shift]',
+ 'Keydown: ~ Backquote 192 [Shift]', // 192 is ` keyCode
+ 'Keypress: ~ Backquote 126 126 [Shift]', // 126 is ~ charCode
+ 'Keyup: ~ Backquote 192 [Shift]',
+ ].join('\n')
+ );
+ await keyboard.up('Shift');
+ });
+ it('should not type canceled events', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/textarea.html');
+ await page.focus('textarea');
+ await page.evaluate(() => {
+ window.addEventListener(
+ 'keydown',
+ (event) => {
+ event.stopPropagation();
+ event.stopImmediatePropagation();
+ if (event.key === 'l') event.preventDefault();
+ if (event.key === 'o') event.preventDefault();
+ },
+ false
+ );
+ });
+ await page.keyboard.type('Hello World!');
+ expect(await page.evaluate(() => globalThis.textarea.value)).toBe(
+ 'He Wrd!'
+ );
+ });
+ itFailsFirefox('should specify repeat property', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/textarea.html');
+ await page.focus('textarea');
+ await page.evaluate(() =>
+ document
+ .querySelector('textarea')
+ .addEventListener('keydown', (e) => (globalThis.lastEvent = e), true)
+ );
+ await page.keyboard.down('a');
+ expect(await page.evaluate(() => globalThis.lastEvent.repeat)).toBe(false);
+ await page.keyboard.press('a');
+ expect(await page.evaluate(() => globalThis.lastEvent.repeat)).toBe(true);
+
+ await page.keyboard.down('b');
+ expect(await page.evaluate(() => globalThis.lastEvent.repeat)).toBe(false);
+ await page.keyboard.down('b');
+ expect(await page.evaluate(() => globalThis.lastEvent.repeat)).toBe(true);
+
+ await page.keyboard.up('a');
+ await page.keyboard.down('a');
+ expect(await page.evaluate(() => globalThis.lastEvent.repeat)).toBe(false);
+ });
+ itFailsFirefox('should type all kinds of characters', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/textarea.html');
+ await page.focus('textarea');
+ const text = 'This text goes onto two lines.\nThis character is 嗨.';
+ await page.keyboard.type(text);
+ expect(await page.evaluate('result')).toBe(text);
+ });
+ itFailsFirefox('should specify location', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/textarea.html');
+ await page.evaluate(() => {
+ window.addEventListener(
+ 'keydown',
+ (event) => (globalThis.keyLocation = event.location),
+ true
+ );
+ });
+ const textarea = await page.$('textarea');
+
+ await textarea.press('Digit5');
+ expect(await page.evaluate('keyLocation')).toBe(0);
+
+ await textarea.press('ControlLeft');
+ expect(await page.evaluate('keyLocation')).toBe(1);
+
+ await textarea.press('ControlRight');
+ expect(await page.evaluate('keyLocation')).toBe(2);
+
+ await textarea.press('NumpadSubtract');
+ expect(await page.evaluate('keyLocation')).toBe(3);
+ });
+ it('should throw on unknown keys', async () => {
+ const { page } = getTestState();
+
+ let error = await page.keyboard
+ // @ts-expect-error bad input
+ .press('NotARealKey')
+ .catch((error_) => error_);
+ expect(error.message).toBe('Unknown key: "NotARealKey"');
+
+ // @ts-expect-error bad input
+ error = await page.keyboard.press('ё').catch((error_) => error_);
+ expect(error && error.message).toBe('Unknown key: "ё"');
+
+ // @ts-expect-error bad input
+ error = await page.keyboard.press('😊').catch((error_) => error_);
+ expect(error && error.message).toBe('Unknown key: "😊"');
+ });
+ itFailsFirefox('should type emoji', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/textarea.html');
+ await page.type('textarea', '👹 Tokyo street Japan 🇯🇵');
+ expect(
+ await page.$eval(
+ 'textarea',
+ (textarea: HTMLInputElement) => textarea.value
+ )
+ ).toBe('👹 Tokyo street Japan 🇯🇵');
+ });
+ itFailsFirefox('should type emoji into an iframe', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await utils.attachFrame(
+ page,
+ 'emoji-test',
+ server.PREFIX + '/input/textarea.html'
+ );
+ const frame = page.frames()[1];
+ const textarea = await frame.$('textarea');
+ await textarea.type('👹 Tokyo street Japan 🇯🇵');
+ expect(
+ await frame.$eval(
+ 'textarea',
+ (textarea: HTMLInputElement) => textarea.value
+ )
+ ).toBe('👹 Tokyo street Japan 🇯🇵');
+ });
+ itFailsFirefox('should press the meta key', async () => {
+ const { page, isFirefox } = getTestState();
+
+ await page.evaluate(() => {
+ globalThis.result = null;
+ document.addEventListener('keydown', (event) => {
+ globalThis.result = [event.key, event.code, event.metaKey];
+ });
+ });
+ await page.keyboard.press('Meta');
+ // Have to do this because we lose a lot of type info when evaluating a
+ // string not a function. This is why functions are recommended rather than
+ // using strings (although we'll leave this test so we have coverage of both
+ // approaches.)
+ const [key, code, metaKey] = (await page.evaluate('result')) as [
+ string,
+ string,
+ boolean
+ ];
+ if (isFirefox && os.platform() !== 'darwin') expect(key).toBe('OS');
+ else expect(key).toBe('Meta');
+
+ if (isFirefox) expect(code).toBe('OSLeft');
+ else expect(code).toBe('MetaLeft');
+
+ if (isFirefox && os.platform() !== 'darwin') expect(metaKey).toBe(false);
+ else expect(metaKey).toBe(true);
+ });
+});
diff --git a/test/launcher.spec.ts b/test/launcher.spec.ts
new file mode 100644
index 0000000000000..33cb1ff71fbd4
--- /dev/null
+++ b/test/launcher.spec.ts
@@ -0,0 +1,925 @@
+/**
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import fs from 'fs';
+import os from 'os';
+import path from 'path';
+import sinon from 'sinon';
+import { promisify } from 'util';
+import Protocol from 'devtools-protocol';
+import {
+ getTestState,
+ itChromeOnly,
+ itFailsFirefox,
+ itFirefoxOnly,
+ itOnlyRegularInstall,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+import utils from './utils.js';
+import expect from 'expect';
+import rimraf from 'rimraf';
+import { Page } from '../lib/cjs/puppeteer/common/Page.js';
+
+const mkdtempAsync = promisify(fs.mkdtemp);
+const readFileAsync = promisify(fs.readFile);
+const rmAsync = promisify(rimraf);
+const statAsync = promisify(fs.stat);
+const writeFileAsync = promisify(fs.writeFile);
+
+const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-');
+const FIREFOX_TIMEOUT = 30 * 1000;
+
+describe('Launcher specs', function () {
+ if (getTestState().isFirefox) this.timeout(FIREFOX_TIMEOUT);
+
+ describe('Puppeteer', function () {
+ describe('BrowserFetcher', function () {
+ it('should download and extract chrome linux binary', async () => {
+ const { server, puppeteer } = getTestState();
+
+ const downloadsFolder = await mkdtempAsync(TMP_FOLDER);
+ const browserFetcher = puppeteer.createBrowserFetcher({
+ platform: 'linux',
+ path: downloadsFolder,
+ host: server.PREFIX,
+ });
+ const expectedRevision = '123456';
+ let revisionInfo = browserFetcher.revisionInfo(expectedRevision);
+ server.setRoute(
+ revisionInfo.url.substring(server.PREFIX.length),
+ (req, res) => {
+ server.serveFile(req, res, '/chromium-linux.zip');
+ }
+ );
+
+ expect(revisionInfo.local).toBe(false);
+ expect(browserFetcher.platform()).toBe('linux');
+ expect(browserFetcher.product()).toBe('chrome');
+ expect(!!browserFetcher.host()).toBe(true);
+ expect(await browserFetcher.canDownload('100000')).toBe(false);
+ expect(await browserFetcher.canDownload(expectedRevision)).toBe(true);
+
+ revisionInfo = await browserFetcher.download(expectedRevision);
+ expect(revisionInfo.local).toBe(true);
+ expect(await readFileAsync(revisionInfo.executablePath, 'utf8')).toBe(
+ 'LINUX BINARY\n'
+ );
+ const expectedPermissions = os.platform() === 'win32' ? 0o666 : 0o755;
+ expect(
+ (await statAsync(revisionInfo.executablePath)).mode & 0o777
+ ).toBe(expectedPermissions);
+ expect(await browserFetcher.localRevisions()).toEqual([
+ expectedRevision,
+ ]);
+ await browserFetcher.remove(expectedRevision);
+ expect(await browserFetcher.localRevisions()).toEqual([]);
+ await rmAsync(downloadsFolder);
+ });
+ it('should download and extract firefox linux binary', async () => {
+ const { server, puppeteer } = getTestState();
+
+ const downloadsFolder = await mkdtempAsync(TMP_FOLDER);
+ const browserFetcher = puppeteer.createBrowserFetcher({
+ platform: 'linux',
+ path: downloadsFolder,
+ host: server.PREFIX,
+ product: 'firefox',
+ });
+ const expectedVersion = '75.0a1';
+ let revisionInfo = browserFetcher.revisionInfo(expectedVersion);
+ server.setRoute(
+ revisionInfo.url.substring(server.PREFIX.length),
+ (req, res) => {
+ server.serveFile(
+ req,
+ res,
+ `/firefox-${expectedVersion}.en-US.linux-x86_64.tar.bz2`
+ );
+ }
+ );
+
+ expect(revisionInfo.local).toBe(false);
+ expect(browserFetcher.platform()).toBe('linux');
+ expect(browserFetcher.product()).toBe('firefox');
+ expect(await browserFetcher.canDownload('100000')).toBe(false);
+ expect(await browserFetcher.canDownload(expectedVersion)).toBe(true);
+
+ revisionInfo = await browserFetcher.download(expectedVersion);
+ expect(revisionInfo.local).toBe(true);
+ expect(await readFileAsync(revisionInfo.executablePath, 'utf8')).toBe(
+ 'FIREFOX LINUX BINARY\n'
+ );
+ const expectedPermissions = os.platform() === 'win32' ? 0o666 : 0o755;
+ expect(
+ (await statAsync(revisionInfo.executablePath)).mode & 0o777
+ ).toBe(expectedPermissions);
+ expect(await browserFetcher.localRevisions()).toEqual([
+ expectedVersion,
+ ]);
+ await browserFetcher.remove(expectedVersion);
+ expect(await browserFetcher.localRevisions()).toEqual([]);
+ await rmAsync(downloadsFolder);
+ });
+ });
+
+ describe('Browser.disconnect', function () {
+ it('should reject navigation when browser closes', async () => {
+ const { server, puppeteer, defaultBrowserOptions } = getTestState();
+ server.setRoute('/one-style.css', () => {});
+ const browser = await puppeteer.launch(defaultBrowserOptions);
+ const remote = await puppeteer.connect({
+ browserWSEndpoint: browser.wsEndpoint(),
+ });
+ const page = await remote.newPage();
+ const navigationPromise = page
+ .goto(server.PREFIX + '/one-style.html', { timeout: 60000 })
+ .catch((error_) => error_);
+ await server.waitForRequest('/one-style.css');
+ remote.disconnect();
+ const error = await navigationPromise;
+ expect(error.message).toBe(
+ 'Navigation failed because browser has disconnected!'
+ );
+ await browser.close();
+ });
+ it('should reject waitForSelector when browser closes', async () => {
+ const { server, puppeteer, defaultBrowserOptions } = getTestState();
+
+ server.setRoute('/empty.html', () => {});
+ const browser = await puppeteer.launch(defaultBrowserOptions);
+ const remote = await puppeteer.connect({
+ browserWSEndpoint: browser.wsEndpoint(),
+ });
+ const page = await remote.newPage();
+ const watchdog = page
+ .waitForSelector('div', { timeout: 60000 })
+ .catch((error_) => error_);
+ remote.disconnect();
+ const error = await watchdog;
+ expect(error.message).toContain('Protocol error');
+ await browser.close();
+ });
+ });
+ describe('Browser.close', function () {
+ it('should terminate network waiters', async () => {
+ const { server, puppeteer, defaultBrowserOptions } = getTestState();
+
+ const browser = await puppeteer.launch(defaultBrowserOptions);
+ const remote = await puppeteer.connect({
+ browserWSEndpoint: browser.wsEndpoint(),
+ });
+ const newPage = await remote.newPage();
+ const results = await Promise.all([
+ newPage.waitForRequest(server.EMPTY_PAGE).catch((error) => error),
+ newPage.waitForResponse(server.EMPTY_PAGE).catch((error) => error),
+ browser.close(),
+ ]);
+ for (let i = 0; i < 2; i++) {
+ const message = results[i].message;
+ expect(message).toContain('Target closed');
+ expect(message).not.toContain('Timeout');
+ }
+ await browser.close();
+ });
+ });
+ describe('Puppeteer.launch', function () {
+ it('should reject all promises when browser is closed', async () => {
+ const { defaultBrowserOptions, puppeteer } = getTestState();
+ const browser = await puppeteer.launch(defaultBrowserOptions);
+ const page = await browser.newPage();
+ let error = null;
+ const neverResolves = page
+ .evaluate(() => new Promise(() => {}))
+ .catch((error_) => (error = error_));
+ await browser.close();
+ await neverResolves;
+ expect(error.message).toContain('Protocol error');
+ });
+ it('should reject if executable path is invalid', async () => {
+ const { defaultBrowserOptions, puppeteer } = getTestState();
+
+ let waitError = null;
+ const options = Object.assign({}, defaultBrowserOptions, {
+ executablePath: 'random-invalid-path',
+ });
+ await puppeteer.launch(options).catch((error) => (waitError = error));
+ expect(waitError.message).toContain('Failed to launch');
+ });
+ it('userDataDir option', async () => {
+ const { defaultBrowserOptions, puppeteer } = getTestState();
+
+ const userDataDir = await mkdtempAsync(TMP_FOLDER);
+ const options = Object.assign({ userDataDir }, defaultBrowserOptions);
+ const browser = await puppeteer.launch(options);
+ // Open a page to make sure its functional.
+ await browser.newPage();
+ expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0);
+ await browser.close();
+ expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0);
+ // This might throw. See https://github.com/puppeteer/puppeteer/issues/2778
+ await rmAsync(userDataDir).catch(() => {});
+ });
+ itFirefoxOnly('userDataDir option restores preferences', async () => {
+ const { defaultBrowserOptions, puppeteer } = getTestState();
+
+ const userDataDir = await mkdtempAsync(TMP_FOLDER);
+
+ const prefsJSPath = path.join(userDataDir, 'prefs.js');
+ const prefsJSContent = 'user_pref("browser.warnOnQuit", true)';
+ await writeFileAsync(prefsJSPath, prefsJSContent);
+
+ const options = Object.assign({ userDataDir }, defaultBrowserOptions);
+ const browser = await puppeteer.launch(options);
+ // Open a page to make sure its functional.
+ await browser.newPage();
+ expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0);
+ await browser.close();
+ expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0);
+
+ expect(await readFileAsync(prefsJSPath, 'utf8')).toBe(prefsJSContent);
+
+ // This might throw. See https://github.com/puppeteer/puppeteer/issues/2778
+ await rmAsync(userDataDir).catch(() => {});
+ });
+ it('userDataDir argument', async () => {
+ const { isChrome, puppeteer, defaultBrowserOptions } = getTestState();
+
+ const userDataDir = await mkdtempAsync(TMP_FOLDER);
+ const options = Object.assign({}, defaultBrowserOptions);
+ if (isChrome) {
+ options.args = [
+ ...(defaultBrowserOptions.args || []),
+ `--user-data-dir=${userDataDir}`,
+ ];
+ } else {
+ options.args = [
+ ...(defaultBrowserOptions.args || []),
+ '-profile',
+ userDataDir,
+ ];
+ }
+ const browser = await puppeteer.launch(options);
+ expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0);
+ await browser.close();
+ expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0);
+ // This might throw. See https://github.com/puppeteer/puppeteer/issues/2778
+ await rmAsync(userDataDir).catch(() => {});
+ });
+ itChromeOnly('userDataDir argument with non-existent dir', async () => {
+ const { isChrome, puppeteer, defaultBrowserOptions } = getTestState();
+
+ const userDataDir = await mkdtempAsync(TMP_FOLDER);
+ await rmAsync(userDataDir);
+ const options = Object.assign({}, defaultBrowserOptions);
+ if (isChrome) {
+ options.args = [
+ ...(defaultBrowserOptions.args || []),
+ `--user-data-dir=${userDataDir}`,
+ ];
+ } else {
+ options.args = [
+ ...(defaultBrowserOptions.args || []),
+ '-profile',
+ userDataDir,
+ ];
+ }
+ const browser = await puppeteer.launch(options);
+ expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0);
+ await browser.close();
+ expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0);
+ // This might throw. See https://github.com/puppeteer/puppeteer/issues/2778
+ await rmAsync(userDataDir).catch(() => {});
+ });
+ it('userDataDir option should restore state', async () => {
+ const { server, puppeteer, defaultBrowserOptions } = getTestState();
+
+ const userDataDir = await mkdtempAsync(TMP_FOLDER);
+ const options = Object.assign({ userDataDir }, defaultBrowserOptions);
+ const browser = await puppeteer.launch(options);
+ const page = await browser.newPage();
+ await page.goto(server.EMPTY_PAGE);
+ await page.evaluate(() => (localStorage.hey = 'hello'));
+ await browser.close();
+
+ const browser2 = await puppeteer.launch(options);
+ const page2 = await browser2.newPage();
+ await page2.goto(server.EMPTY_PAGE);
+ expect(await page2.evaluate(() => localStorage.hey)).toBe('hello');
+ await browser2.close();
+ // This might throw. See https://github.com/puppeteer/puppeteer/issues/2778
+ await rmAsync(userDataDir).catch(() => {});
+ });
+ // This mysteriously fails on Windows on AppVeyor. See
+ // https://github.com/puppeteer/puppeteer/issues/4111
+ xit('userDataDir option should restore cookies', async () => {
+ const { server, puppeteer, defaultBrowserOptions } = getTestState();
+
+ const userDataDir = await mkdtempAsync(TMP_FOLDER);
+ const options = Object.assign({ userDataDir }, defaultBrowserOptions);
+ const browser = await puppeteer.launch(options);
+ const page = await browser.newPage();
+ await page.goto(server.EMPTY_PAGE);
+ await page.evaluate(
+ () =>
+ (document.cookie =
+ 'doSomethingOnlyOnce=true; expires=Fri, 31 Dec 9999 23:59:59 GMT')
+ );
+ await browser.close();
+
+ const browser2 = await puppeteer.launch(options);
+ const page2 = await browser2.newPage();
+ await page2.goto(server.EMPTY_PAGE);
+ expect(await page2.evaluate(() => document.cookie)).toBe(
+ 'doSomethingOnlyOnce=true'
+ );
+ await browser2.close();
+ // This might throw. See https://github.com/puppeteer/puppeteer/issues/2778
+ await rmAsync(userDataDir).catch(() => {});
+ });
+ it('should return the default arguments', async () => {
+ const { isChrome, isFirefox, puppeteer } = getTestState();
+
+ if (isChrome) {
+ expect(puppeteer.defaultArgs()).toContain('--no-first-run');
+ expect(puppeteer.defaultArgs()).toContain('--headless');
+ expect(puppeteer.defaultArgs({ headless: false })).not.toContain(
+ '--headless'
+ );
+ expect(puppeteer.defaultArgs({ userDataDir: 'foo' })).toContain(
+ `--user-data-dir=${path.resolve('foo')}`
+ );
+ } else if (isFirefox) {
+ expect(puppeteer.defaultArgs()).toContain('--headless');
+ expect(puppeteer.defaultArgs()).toContain('--no-remote');
+ if (os.platform() === 'darwin') {
+ expect(puppeteer.defaultArgs()).toContain('--foreground');
+ } else {
+ expect(puppeteer.defaultArgs()).not.toContain('--foreground');
+ }
+ expect(puppeteer.defaultArgs({ headless: false })).not.toContain(
+ '--headless'
+ );
+ expect(puppeteer.defaultArgs({ userDataDir: 'foo' })).toContain(
+ '--profile'
+ );
+ expect(puppeteer.defaultArgs({ userDataDir: 'foo' })).toContain(
+ 'foo'
+ );
+ } else {
+ expect(puppeteer.defaultArgs()).toContain('-headless');
+ expect(puppeteer.defaultArgs({ headless: false })).not.toContain(
+ '-headless'
+ );
+ expect(puppeteer.defaultArgs({ userDataDir: 'foo' })).toContain(
+ '-profile'
+ );
+ expect(puppeteer.defaultArgs({ userDataDir: 'foo' })).toContain(
+ path.resolve('foo')
+ );
+ }
+ });
+ it('should report the correct product', async () => {
+ const { isChrome, isFirefox, puppeteer } = getTestState();
+ if (isChrome) expect(puppeteer.product).toBe('chrome');
+ else if (isFirefox) expect(puppeteer.product).toBe('firefox');
+ });
+ it('should work with no default arguments', async () => {
+ const { defaultBrowserOptions, puppeteer } = getTestState();
+ const options = Object.assign({}, defaultBrowserOptions);
+ options.ignoreDefaultArgs = true;
+ const browser = await puppeteer.launch(options);
+ const page = await browser.newPage();
+ expect(await page.evaluate('11 * 11')).toBe(121);
+ await page.close();
+ await browser.close();
+ });
+ itChromeOnly(
+ 'should filter out ignored default arguments in Chrome',
+ async () => {
+ const { defaultBrowserOptions, puppeteer } = getTestState();
+ // Make sure we launch with `--enable-automation` by default.
+ const defaultArgs = puppeteer.defaultArgs();
+ const browser = await puppeteer.launch(
+ Object.assign({}, defaultBrowserOptions, {
+ // Ignore first and third default argument.
+ ignoreDefaultArgs: [defaultArgs[0], defaultArgs[2]],
+ })
+ );
+ const spawnargs = browser.process().spawnargs;
+ if (!spawnargs) {
+ throw new Error('spawnargs not present');
+ }
+ expect(spawnargs.indexOf(defaultArgs[0])).toBe(-1);
+ expect(spawnargs.indexOf(defaultArgs[1])).not.toBe(-1);
+ expect(spawnargs.indexOf(defaultArgs[2])).toBe(-1);
+ await browser.close();
+ }
+ );
+ itFirefoxOnly(
+ 'should filter out ignored default argument in Firefox',
+ async () => {
+ const { defaultBrowserOptions, puppeteer } = getTestState();
+
+ const defaultArgs = puppeteer.defaultArgs();
+ const browser = await puppeteer.launch(
+ Object.assign({}, defaultBrowserOptions, {
+ // Only the first argument is fixed, others are optional.
+ ignoreDefaultArgs: [defaultArgs[0]],
+ })
+ );
+ const spawnargs = browser.process().spawnargs;
+ if (!spawnargs) {
+ throw new Error('spawnargs not present');
+ }
+ expect(spawnargs.indexOf(defaultArgs[0])).toBe(-1);
+ expect(spawnargs.indexOf(defaultArgs[1])).not.toBe(-1);
+ await browser.close();
+ }
+ );
+ it('should have default URL when launching browser', async function () {
+ const { defaultBrowserOptions, puppeteer } = getTestState();
+ const browser = await puppeteer.launch(defaultBrowserOptions);
+ const pages = (await browser.pages()).map((page) => page.url());
+ expect(pages).toEqual(['about:blank']);
+ await browser.close();
+ });
+ itFailsFirefox(
+ 'should have custom URL when launching browser',
+ async () => {
+ const { server, puppeteer, defaultBrowserOptions } = getTestState();
+
+ const options = Object.assign({}, defaultBrowserOptions);
+ options.args = [server.EMPTY_PAGE].concat(options.args || []);
+ const browser = await puppeteer.launch(options);
+ const pages = await browser.pages();
+ expect(pages.length).toBe(1);
+ const page = pages[0];
+ if (page.url() !== server.EMPTY_PAGE) await page.waitForNavigation();
+ expect(page.url()).toBe(server.EMPTY_PAGE);
+ await browser.close();
+ }
+ );
+ it('should pass the timeout parameter to browser.waitForTarget', async () => {
+ const { puppeteer, defaultBrowserOptions } = getTestState();
+ const options = Object.assign({}, defaultBrowserOptions, {
+ timeout: 1,
+ });
+ let error = null;
+ await puppeteer.launch(options).catch((error_) => (error = error_));
+ expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
+ });
+ it('should set the default viewport', async () => {
+ const { puppeteer, defaultBrowserOptions } = getTestState();
+ const options = Object.assign({}, defaultBrowserOptions, {
+ defaultViewport: {
+ width: 456,
+ height: 789,
+ },
+ });
+ const browser = await puppeteer.launch(options);
+ const page = await browser.newPage();
+ expect(await page.evaluate('window.innerWidth')).toBe(456);
+ expect(await page.evaluate('window.innerHeight')).toBe(789);
+ await browser.close();
+ });
+ it('should disable the default viewport', async () => {
+ const { puppeteer, defaultBrowserOptions } = getTestState();
+ const options = Object.assign({}, defaultBrowserOptions, {
+ defaultViewport: null,
+ });
+ const browser = await puppeteer.launch(options);
+ const page = await browser.newPage();
+ expect(page.viewport()).toBe(null);
+ await browser.close();
+ });
+ it('should take fullPage screenshots when defaultViewport is null', async () => {
+ const { server, puppeteer, defaultBrowserOptions } = getTestState();
+
+ const options = Object.assign({}, defaultBrowserOptions, {
+ defaultViewport: null,
+ });
+ const browser = await puppeteer.launch(options);
+ const page = await browser.newPage();
+ await page.goto(server.PREFIX + '/grid.html');
+ const screenshot = await page.screenshot({
+ fullPage: true,
+ });
+ expect(screenshot).toBeInstanceOf(Buffer);
+ await browser.close();
+ });
+ it('should set the debugging port', async () => {
+ const { puppeteer, defaultBrowserOptions } = getTestState();
+
+ const options = Object.assign({}, defaultBrowserOptions, {
+ defaultViewport: null,
+ debuggingPort: 9999,
+ });
+ const browser = await puppeteer.launch(options);
+ const url = new URL(browser.wsEndpoint());
+ await browser.close();
+ expect(url.port).toBe('9999');
+ });
+ it('should not allow setting debuggingPort and pipe', async () => {
+ const { puppeteer, defaultBrowserOptions } = getTestState();
+
+ const options = Object.assign({}, defaultBrowserOptions, {
+ defaultViewport: null,
+ debuggingPort: 9999,
+ pipe: true,
+ });
+
+ let error = null;
+ await puppeteer.launch(options).catch((error_) => (error = error_));
+ expect(error.message).toContain('either pipe or debugging port');
+ });
+ itChromeOnly(
+ 'should launch Chrome properly with --no-startup-window and waitForInitialPage=false',
+ async () => {
+ const { defaultBrowserOptions, puppeteer } = getTestState();
+ const options = {
+ waitForInitialPage: false,
+ // This is needed to prevent Puppeteer from adding an initial blank page.
+ // See also https://github.com/puppeteer/puppeteer/blob/ad6b736039436fcc5c0a262e5b575aa041427be3/src/node/Launcher.ts#L200
+ ignoreDefaultArgs: true,
+ ...defaultBrowserOptions,
+ args: ['--no-startup-window'],
+ };
+ const browser = await puppeteer.launch(options);
+ const pages = await browser.pages();
+ expect(pages.length).toBe(0);
+ await browser.close();
+ }
+ );
+ });
+
+ describe('Puppeteer.launch', function () {
+ let productName;
+
+ before(async () => {
+ const { puppeteer } = getTestState();
+ productName = puppeteer._productName;
+ });
+
+ after(async () => {
+ const { puppeteer } = getTestState();
+ // @ts-expect-error launcher is a private property that users can't
+ // touch, but for testing purposes we need to reset it.
+ puppeteer._lazyLauncher = undefined;
+ puppeteer._productName = productName;
+ });
+
+ itOnlyRegularInstall('should be able to launch Chrome', async () => {
+ const { puppeteer } = getTestState();
+ const browser = await puppeteer.launch({ product: 'chrome' });
+ const userAgent = await browser.userAgent();
+ await browser.close();
+ expect(userAgent).toContain('Chrome');
+ });
+
+ itOnlyRegularInstall(
+ 'falls back to launching chrome if there is an unknown product but logs a warning',
+ async () => {
+ const { puppeteer } = getTestState();
+ const consoleStub = sinon.stub(console, 'warn');
+ const browser = await puppeteer.launch({
+ // @ts-expect-error purposeful bad input
+ product: 'SO_NOT_A_PRODUCT',
+ });
+ const userAgent = await browser.userAgent();
+ await browser.close();
+ expect(userAgent).toContain('Chrome');
+ expect(consoleStub.callCount).toEqual(1);
+ expect(consoleStub.firstCall.args).toEqual([
+ 'Warning: unknown product name SO_NOT_A_PRODUCT. Falling back to chrome.',
+ ]);
+ }
+ );
+
+ itOnlyRegularInstall(
+ 'should be able to launch Firefox',
+ async function () {
+ this.timeout(FIREFOX_TIMEOUT);
+ const { puppeteer } = getTestState();
+ const browser = await puppeteer.launch({ product: 'firefox' });
+ const userAgent = await browser.userAgent();
+ await browser.close();
+ expect(userAgent).toContain('Firefox');
+ }
+ );
+ });
+
+ describe('Puppeteer.connect', function () {
+ it('should be able to connect multiple times to the same browser', async () => {
+ const { puppeteer, defaultBrowserOptions } = getTestState();
+
+ const originalBrowser = await puppeteer.launch(defaultBrowserOptions);
+ const otherBrowser = await puppeteer.connect({
+ browserWSEndpoint: originalBrowser.wsEndpoint(),
+ });
+ const page = await otherBrowser.newPage();
+ expect(await page.evaluate(() => 7 * 8)).toBe(56);
+ otherBrowser.disconnect();
+
+ const secondPage = await originalBrowser.newPage();
+ expect(await secondPage.evaluate(() => 7 * 6)).toBe(42);
+ await originalBrowser.close();
+ });
+ it('should be able to close remote browser', async () => {
+ const { defaultBrowserOptions, puppeteer } = getTestState();
+
+ const originalBrowser = await puppeteer.launch(defaultBrowserOptions);
+ const remoteBrowser = await puppeteer.connect({
+ browserWSEndpoint: originalBrowser.wsEndpoint(),
+ });
+ await Promise.all([
+ utils.waitEvent(originalBrowser, 'disconnected'),
+ remoteBrowser.close(),
+ ]);
+ });
+ it('should support ignoreHTTPSErrors option', async () => {
+ const { httpsServer, puppeteer, defaultBrowserOptions } =
+ getTestState();
+
+ const originalBrowser = await puppeteer.launch(defaultBrowserOptions);
+ const browserWSEndpoint = originalBrowser.wsEndpoint();
+
+ const browser = await puppeteer.connect({
+ browserWSEndpoint,
+ ignoreHTTPSErrors: true,
+ });
+ const page = await browser.newPage();
+ let error = null;
+ const [serverRequest, response] = await Promise.all([
+ httpsServer.waitForRequest('/empty.html'),
+ page.goto(httpsServer.EMPTY_PAGE).catch((error_) => (error = error_)),
+ ]);
+ expect(error).toBe(null);
+ expect(response.ok()).toBe(true);
+ expect(response.securityDetails()).toBeTruthy();
+ const protocol = serverRequest.socket.getProtocol().replace('v', ' ');
+ expect(response.securityDetails().protocol()).toBe(protocol);
+ await page.close();
+ await browser.close();
+ });
+ // @see https://github.com/puppeteer/puppeteer/issues/4197
+ itFailsFirefox('should support targetFilter option', async () => {
+ const { server, puppeteer, defaultBrowserOptions } = getTestState();
+
+ const originalBrowser = await puppeteer.launch(defaultBrowserOptions);
+ const browserWSEndpoint = originalBrowser.wsEndpoint();
+
+ const page1 = await originalBrowser.newPage();
+ await page1.goto(server.EMPTY_PAGE);
+
+ const page2 = await originalBrowser.newPage();
+ await page2.goto(server.EMPTY_PAGE + '?should-be-ignored');
+
+ const browser = await puppeteer.connect({
+ browserWSEndpoint,
+ targetFilter: (targetInfo: Protocol.Target.TargetInfo) =>
+ !targetInfo.url?.includes('should-be-ignored'),
+ });
+
+ const pages = await browser.pages();
+
+ await page2.close();
+ await page1.close();
+ await browser.disconnect();
+ await originalBrowser.close();
+
+ expect(pages.map((p: Page) => p.url()).sort()).toEqual([
+ 'about:blank',
+ server.EMPTY_PAGE,
+ ]);
+ });
+ itFailsFirefox(
+ 'should be able to reconnect to a disconnected browser',
+ async () => {
+ const { server, puppeteer, defaultBrowserOptions } = getTestState();
+
+ const originalBrowser = await puppeteer.launch(defaultBrowserOptions);
+ const browserWSEndpoint = originalBrowser.wsEndpoint();
+ const page = await originalBrowser.newPage();
+ await page.goto(server.PREFIX + '/frames/nested-frames.html');
+ originalBrowser.disconnect();
+
+ const browser = await puppeteer.connect({ browserWSEndpoint });
+ const pages = await browser.pages();
+ const restoredPage = pages.find(
+ (page) =>
+ page.url() === server.PREFIX + '/frames/nested-frames.html'
+ );
+ expect(utils.dumpFrames(restoredPage.mainFrame())).toEqual([
+ 'http://localhost:/frames/nested-frames.html',
+ ' http://localhost:/frames/two-frames.html (2frames)',
+ ' http://localhost:/frames/frame.html (uno)',
+ ' http://localhost:/frames/frame.html (dos)',
+ ' http://localhost:/frames/frame.html (aframe)',
+ ]);
+ expect(await restoredPage.evaluate(() => 7 * 8)).toBe(56);
+ await browser.close();
+ }
+ );
+ // @see https://github.com/puppeteer/puppeteer/issues/4197#issuecomment-481793410
+ itFailsFirefox(
+ 'should be able to connect to the same page simultaneously',
+ async () => {
+ const { puppeteer, defaultBrowserOptions } = getTestState();
+
+ const browserOne = await puppeteer.launch(defaultBrowserOptions);
+ const browserTwo = await puppeteer.connect({
+ browserWSEndpoint: browserOne.wsEndpoint(),
+ });
+ const [page1, page2] = await Promise.all([
+ new Promise((x) =>
+ browserOne.once('targetcreated', (target) => x(target.page()))
+ ),
+ browserTwo.newPage(),
+ ]);
+ expect(await page1.evaluate(() => 7 * 8)).toBe(56);
+ expect(await page2.evaluate(() => 7 * 6)).toBe(42);
+ await browserOne.close();
+ }
+ );
+ it('should be able to reconnect', async () => {
+ const { puppeteer, server, defaultBrowserOptions } = getTestState();
+ const browserOne = await puppeteer.launch(defaultBrowserOptions);
+ const browserWSEndpoint = browserOne.wsEndpoint();
+ const pageOne = await browserOne.newPage();
+ await pageOne.goto(server.EMPTY_PAGE);
+ browserOne.disconnect();
+
+ const browserTwo = await puppeteer.connect({ browserWSEndpoint });
+ const pages = await browserTwo.pages();
+ const pageTwo = pages.find((page) => page.url() === server.EMPTY_PAGE);
+ await pageTwo.reload();
+ const bodyHandle = await pageTwo.waitForSelector('body', {
+ timeout: 10000,
+ });
+ await bodyHandle.dispose();
+ await browserTwo.close();
+ });
+ });
+ describe('Puppeteer.executablePath', function () {
+ itOnlyRegularInstall('should work', async () => {
+ const { puppeteer } = getTestState();
+
+ const executablePath = puppeteer.executablePath();
+ expect(fs.existsSync(executablePath)).toBe(true);
+ expect(fs.realpathSync(executablePath)).toBe(executablePath);
+ });
+ it('returns executablePath for channel', () => {
+ const { puppeteer } = getTestState();
+
+ const executablePath = puppeteer.executablePath('chrome');
+ expect(executablePath).toBeTruthy();
+ });
+ describe('when PUPPETEER_EXECUTABLE_PATH is set', () => {
+ const sandbox = sinon.createSandbox();
+
+ beforeEach(() => {
+ process.env.PUPPETEER_EXECUTABLE_PATH = '';
+ sandbox
+ .stub(process.env, 'PUPPETEER_EXECUTABLE_PATH')
+ .value('SOME_CUSTOM_EXECUTABLE');
+ });
+
+ afterEach(() => sandbox.restore());
+
+ it('its value is returned', async () => {
+ const { puppeteer } = getTestState();
+
+ const executablePath = puppeteer.executablePath();
+
+ expect(executablePath).toEqual('SOME_CUSTOM_EXECUTABLE');
+ });
+ });
+
+ describe('when the product is chrome, platform is not darwin, and arch is arm64', () => {
+ describe('and the executable exists', () => {
+ itChromeOnly('returns /usr/bin/chromium-browser', async () => {
+ const { puppeteer } = getTestState();
+ const osPlatformStub = sinon.stub(os, 'platform').returns('linux');
+ const osArchStub = sinon.stub(os, 'arch').returns('arm64');
+ const fsExistsStub = sinon.stub(fs, 'existsSync');
+ fsExistsStub.withArgs('/usr/bin/chromium-browser').returns(true);
+
+ const executablePath = puppeteer.executablePath();
+
+ expect(executablePath).toEqual('/usr/bin/chromium-browser');
+
+ osPlatformStub.restore();
+ osArchStub.restore();
+ fsExistsStub.restore();
+ });
+ describe('and PUPPETEER_EXECUTABLE_PATH is set', () => {
+ const sandbox = sinon.createSandbox();
+
+ beforeEach(() => {
+ process.env.PUPPETEER_EXECUTABLE_PATH = '';
+ sandbox
+ .stub(process.env, 'PUPPETEER_EXECUTABLE_PATH')
+ .value('SOME_CUSTOM_EXECUTABLE');
+ });
+
+ afterEach(() => sandbox.restore());
+
+ it('its value is returned', async () => {
+ const { puppeteer } = getTestState();
+
+ const executablePath = puppeteer.executablePath();
+
+ expect(executablePath).toEqual('SOME_CUSTOM_EXECUTABLE');
+ });
+ });
+ });
+ describe('and the executable does not exist', () => {
+ itChromeOnly(
+ 'does not return /usr/bin/chromium-browser',
+ async () => {
+ const { puppeteer } = getTestState();
+ const osPlatformStub = sinon
+ .stub(os, 'platform')
+ .returns('linux');
+ const osArchStub = sinon.stub(os, 'arch').returns('arm64');
+ const fsExistsStub = sinon.stub(fs, 'existsSync');
+ fsExistsStub.withArgs('/usr/bin/chromium-browser').returns(false);
+
+ const executablePath = puppeteer.executablePath();
+
+ expect(executablePath).not.toEqual('/usr/bin/chromium-browser');
+
+ osPlatformStub.restore();
+ osArchStub.restore();
+ fsExistsStub.restore();
+ }
+ );
+ });
+ });
+ });
+ });
+
+ describe('Browser target events', function () {
+ itFailsFirefox('should work', async () => {
+ const { server, puppeteer, defaultBrowserOptions } = getTestState();
+
+ const browser = await puppeteer.launch(defaultBrowserOptions);
+ const events = [];
+ browser.on('targetcreated', () => events.push('CREATED'));
+ browser.on('targetchanged', () => events.push('CHANGED'));
+ browser.on('targetdestroyed', () => events.push('DESTROYED'));
+ const page = await browser.newPage();
+ await page.goto(server.EMPTY_PAGE);
+ await page.close();
+ expect(events).toEqual(['CREATED', 'CHANGED', 'DESTROYED']);
+ await browser.close();
+ });
+ });
+
+ describe('Browser.Events.disconnected', function () {
+ it('should be emitted when: browser gets closed, disconnected or underlying websocket gets closed', async () => {
+ const { puppeteer, defaultBrowserOptions } = getTestState();
+ const originalBrowser = await puppeteer.launch(defaultBrowserOptions);
+ const browserWSEndpoint = originalBrowser.wsEndpoint();
+ const remoteBrowser1 = await puppeteer.connect({ browserWSEndpoint });
+ const remoteBrowser2 = await puppeteer.connect({ browserWSEndpoint });
+
+ let disconnectedOriginal = 0;
+ let disconnectedRemote1 = 0;
+ let disconnectedRemote2 = 0;
+ originalBrowser.on('disconnected', () => ++disconnectedOriginal);
+ remoteBrowser1.on('disconnected', () => ++disconnectedRemote1);
+ remoteBrowser2.on('disconnected', () => ++disconnectedRemote2);
+
+ await Promise.all([
+ utils.waitEvent(remoteBrowser2, 'disconnected'),
+ remoteBrowser2.disconnect(),
+ ]);
+
+ expect(disconnectedOriginal).toBe(0);
+ expect(disconnectedRemote1).toBe(0);
+ expect(disconnectedRemote2).toBe(1);
+
+ await Promise.all([
+ utils.waitEvent(remoteBrowser1, 'disconnected'),
+ utils.waitEvent(originalBrowser, 'disconnected'),
+ originalBrowser.close(),
+ ]);
+
+ expect(disconnectedOriginal).toBe(1);
+ expect(disconnectedRemote1).toBe(1);
+ expect(disconnectedRemote2).toBe(1);
+ });
+ });
+});
diff --git a/test/mocha-ts-require.js b/test/mocha-ts-require.js
new file mode 100644
index 0000000000000..a0ac64fa62b7a
--- /dev/null
+++ b/test/mocha-ts-require.js
@@ -0,0 +1,11 @@
+const path = require('path');
+
+require('ts-node').register({
+ /**
+ * We ignore the lib/ directory because that's already been TypeScript
+ * compiled and checked. So we don't want to check it again as part of running
+ * the unit tests.
+ */
+ ignore: ['lib/*', 'node_modules'],
+ project: path.join(__dirname, 'tsconfig.test.json'),
+});
diff --git a/test/mocha-utils.ts b/test/mocha-utils.ts
new file mode 100644
index 0000000000000..9593c04f9be0d
--- /dev/null
+++ b/test/mocha-utils.ts
@@ -0,0 +1,345 @@
+/**
+ * Copyright 2020 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { TestServer } from '../utils/testserver/index.js';
+import * as path from 'path';
+import * as fs from 'fs';
+import * as os from 'os';
+import sinon from 'sinon';
+import puppeteer from '../lib/cjs/puppeteer/node.js';
+import {
+ Browser,
+ BrowserContext,
+} from '../lib/cjs/puppeteer/common/Browser.js';
+import { Page } from '../lib/cjs/puppeteer/common/Page.js';
+import { PuppeteerNode } from '../lib/cjs/puppeteer/node/Puppeteer.js';
+import utils from './utils.js';
+import rimraf from 'rimraf';
+import expect from 'expect';
+
+import { trackCoverage } from './coverage-utils.js';
+import Protocol from 'devtools-protocol';
+
+const setupServer = async () => {
+ const assetsPath = path.join(__dirname, 'assets');
+ const cachedPath = path.join(__dirname, 'assets', 'cached');
+
+ const port = 8907;
+ const server = await TestServer.create(assetsPath, port);
+ server.enableHTTPCache(cachedPath);
+ server.PORT = port;
+ server.PREFIX = `http://localhost:${port}`;
+ server.CROSS_PROCESS_PREFIX = `http://127.0.0.1:${port}`;
+ server.EMPTY_PAGE = `http://localhost:${port}/empty.html`;
+
+ const httpsPort = port + 1;
+ const httpsServer = await TestServer.createHTTPS(assetsPath, httpsPort);
+ httpsServer.enableHTTPCache(cachedPath);
+ httpsServer.PORT = httpsPort;
+ httpsServer.PREFIX = `https://localhost:${httpsPort}`;
+ httpsServer.CROSS_PROCESS_PREFIX = `https://127.0.0.1:${httpsPort}`;
+ httpsServer.EMPTY_PAGE = `https://localhost:${httpsPort}/empty.html`;
+
+ return { server, httpsServer };
+};
+
+export const getTestState = (): PuppeteerTestState =>
+ state as PuppeteerTestState;
+
+const product =
+ process.env.PRODUCT || process.env.PUPPETEER_PRODUCT || 'Chromium';
+
+const alternativeInstall = process.env.PUPPETEER_ALT_INSTALL || false;
+
+const headless = (process.env.HEADLESS || 'true').trim().toLowerCase();
+const isHeadless = headless === 'true' || headless === 'chrome';
+const isFirefox = product === 'firefox';
+const isChrome = product === 'Chromium';
+
+let extraLaunchOptions = {};
+try {
+ extraLaunchOptions = JSON.parse(process.env.EXTRA_LAUNCH_OPTIONS || '{}');
+} catch (error) {
+ console.warn(
+ `Error parsing EXTRA_LAUNCH_OPTIONS: ${error.message}. Skipping.`
+ );
+}
+
+const defaultBrowserOptions = Object.assign(
+ {
+ handleSIGINT: true,
+ executablePath: process.env.BINARY,
+ headless: headless === 'chrome' ? ('chrome' as const) : isHeadless,
+ dumpio: !!process.env.DUMPIO,
+ },
+ extraLaunchOptions
+);
+
+(async (): Promise => {
+ if (defaultBrowserOptions.executablePath) {
+ console.warn(
+ `WARN: running ${product} tests with ${defaultBrowserOptions.executablePath}`
+ );
+ } else {
+ // TODO(jackfranklin): declare updateRevision in some form for the Firefox
+ // launcher.
+ // @ts-expect-error _updateRevision is defined on the FF launcher
+ // but not the Chrome one. The types need tidying so that TS can infer that
+ // properly and not error here.
+ if (product === 'firefox') await puppeteer._launcher._updateRevision();
+ const executablePath = puppeteer.executablePath();
+ if (!fs.existsSync(executablePath))
+ throw new Error(
+ `Browser is not downloaded at ${executablePath}. Run 'npm install' and try to re-run tests`
+ );
+ }
+})();
+
+declare module 'expect/build/types' {
+ interface Matchers {
+ toBeGolden(x: string): R;
+ }
+}
+
+const setupGoldenAssertions = (): void => {
+ const suffix = product.toLowerCase();
+ const GOLDEN_DIR = path.join(__dirname, 'golden-' + suffix);
+ const OUTPUT_DIR = path.join(__dirname, 'output-' + suffix);
+ if (fs.existsSync(OUTPUT_DIR)) rimraf.sync(OUTPUT_DIR);
+ utils.extendExpectWithToBeGolden(GOLDEN_DIR, OUTPUT_DIR);
+};
+
+setupGoldenAssertions();
+
+interface PuppeteerTestState {
+ browser: Browser;
+ context: BrowserContext;
+ page: Page;
+ puppeteer: PuppeteerNode;
+ defaultBrowserOptions: {
+ [x: string]: any;
+ };
+ server: any;
+ httpsServer: any;
+ isFirefox: boolean;
+ isChrome: boolean;
+ isHeadless: boolean;
+ headless: string;
+ puppeteerPath: string;
+}
+const state: Partial = {};
+
+export const itFailsFirefox = (
+ description: string,
+ body: Mocha.Func
+): Mocha.Test => {
+ if (isFirefox) return xit(description, body);
+ else return it(description, body);
+};
+
+export const itChromeOnly = (
+ description: string,
+ body: Mocha.Func
+): Mocha.Test => {
+ if (isChrome) return it(description, body);
+ else return xit(description, body);
+};
+
+export const itHeadlessOnly = (
+ description: string,
+ body: Mocha.Func
+): Mocha.Test => {
+ if (isChrome && isHeadless === true) return it(description, body);
+ else return xit(description, body);
+};
+
+export const itFirefoxOnly = (
+ description: string,
+ body: Mocha.Func
+): Mocha.Test => {
+ if (isFirefox) return it(description, body);
+ else return xit(description, body);
+};
+
+export const itOnlyRegularInstall = (
+ description: string,
+ body: Mocha.Func
+): Mocha.Test => {
+ if (alternativeInstall || process.env.BINARY) return xit(description, body);
+ else return it(description, body);
+};
+
+export const itFailsWindowsUntilDate = (
+ date: Date,
+ description: string,
+ body: Mocha.Func
+): Mocha.Test => {
+ if (os.platform() === 'win32' && Date.now() < date.getTime()) {
+ // we are within the deferred time so skip the test
+ return xit(description, body);
+ }
+
+ return it(description, body);
+};
+
+export const itFailsWindows = (
+ description: string,
+ body: Mocha.Func
+): Mocha.Test => {
+ if (os.platform() === 'win32') {
+ return xit(description, body);
+ }
+ return it(description, body);
+};
+
+export const describeFailsFirefox = (
+ description: string,
+ body: (this: Mocha.Suite) => void
+): void | Mocha.Suite => {
+ if (isFirefox) return xdescribe(description, body);
+ else return describe(description, body);
+};
+
+export const describeChromeOnly = (
+ description: string,
+ body: (this: Mocha.Suite) => void
+): Mocha.Suite => {
+ if (isChrome) return describe(description, body);
+};
+
+let coverageHooks = {
+ beforeAll: (): void => {},
+ afterAll: (): void => {},
+};
+
+if (process.env.COVERAGE) {
+ coverageHooks = trackCoverage();
+}
+
+console.log(
+ `Running unit tests with:
+ -> product: ${product}
+ -> binary: ${
+ defaultBrowserOptions.executablePath ||
+ path.relative(process.cwd(), puppeteer.executablePath())
+ }`
+);
+
+process.on('unhandledRejection', (reason) => {
+ throw reason;
+});
+
+export const setupTestBrowserHooks = (): void => {
+ before(async () => {
+ const browser = await puppeteer.launch(defaultBrowserOptions);
+ state.browser = browser;
+ });
+
+ after(async () => {
+ await state.browser.close();
+ state.browser = null;
+ });
+};
+
+export const setupTestPageAndContextHooks = (): void => {
+ beforeEach(async () => {
+ state.context = await state.browser.createIncognitoBrowserContext();
+ state.page = await state.context.newPage();
+ });
+
+ afterEach(async () => {
+ await state.context.close();
+ state.context = null;
+ state.page = null;
+ });
+};
+
+export const mochaHooks = {
+ beforeAll: [
+ async (): Promise => {
+ const { server, httpsServer } = await setupServer();
+
+ state.puppeteer = puppeteer;
+ state.defaultBrowserOptions = defaultBrowserOptions;
+ state.server = server;
+ state.httpsServer = httpsServer;
+ state.isFirefox = isFirefox;
+ state.isChrome = isChrome;
+ state.isHeadless = isHeadless;
+ state.headless = headless;
+ state.puppeteerPath = path.resolve(path.join(__dirname, '..'));
+ },
+ coverageHooks.beforeAll,
+ ],
+
+ beforeEach: async (): Promise => {
+ state.server.reset();
+ state.httpsServer.reset();
+ },
+
+ afterAll: [
+ async (): Promise => {
+ await state.server.stop();
+ state.server = null;
+ await state.httpsServer.stop();
+ state.httpsServer = null;
+ },
+ coverageHooks.afterAll,
+ ],
+
+ afterEach: (): void => {
+ sinon.restore();
+ },
+};
+
+export const expectCookieEquals = (
+ cookies: Protocol.Network.Cookie[],
+ expectedCookies: Array>
+): void => {
+ const { isChrome } = getTestState();
+ if (!isChrome) {
+ // Only keep standard properties when testing on a browser other than Chrome.
+ expectedCookies = expectedCookies.map((cookie) => {
+ return {
+ domain: cookie.domain,
+ expires: cookie.expires,
+ httpOnly: cookie.httpOnly,
+ name: cookie.name,
+ path: cookie.path,
+ secure: cookie.secure,
+ session: cookie.session,
+ size: cookie.size,
+ value: cookie.value,
+ };
+ });
+ }
+
+ expect(cookies).toEqual(expectedCookies);
+};
+
+export const shortWaitForArrayToHaveAtLeastNElements = async (
+ data: unknown[],
+ minLength: number,
+ attempts = 3,
+ timeout = 50
+): Promise => {
+ for (let i = 0; i < attempts; i++) {
+ if (data.length >= minLength) {
+ break;
+ }
+ await new Promise((resolve) => setTimeout(resolve, timeout));
+ }
+};
diff --git a/test/mouse.spec.ts b/test/mouse.spec.ts
new file mode 100644
index 0000000000000..33d0053386607
--- /dev/null
+++ b/test/mouse.spec.ts
@@ -0,0 +1,239 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import os from 'os';
+import expect from 'expect';
+import {
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+ itFailsFirefox,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+import { KeyInput } from '../lib/cjs/puppeteer/common/USKeyboardLayout.js';
+
+interface Dimensions {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+}
+
+function dimensions(): Dimensions {
+ const rect = document.querySelector('textarea').getBoundingClientRect();
+ return {
+ x: rect.left,
+ y: rect.top,
+ width: rect.width,
+ height: rect.height,
+ };
+}
+
+describe('Mouse', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+ it('should click the document', async () => {
+ const { page } = getTestState();
+
+ await page.evaluate(() => {
+ globalThis.clickPromise = new Promise((resolve) => {
+ document.addEventListener('click', (event) => {
+ resolve({
+ type: event.type,
+ detail: event.detail,
+ clientX: event.clientX,
+ clientY: event.clientY,
+ isTrusted: event.isTrusted,
+ button: event.button,
+ });
+ });
+ });
+ });
+ await page.mouse.click(50, 60);
+ const event = await page.evaluate<() => MouseEvent>(
+ () => globalThis.clickPromise
+ );
+ expect(event.type).toBe('click');
+ expect(event.detail).toBe(1);
+ expect(event.clientX).toBe(50);
+ expect(event.clientY).toBe(60);
+ expect(event.isTrusted).toBe(true);
+ expect(event.button).toBe(0);
+ });
+ it('should resize the textarea', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/textarea.html');
+ const { x, y, width, height } = await page.evaluate<() => Dimensions>(
+ dimensions
+ );
+ const mouse = page.mouse;
+ await mouse.move(x + width - 4, y + height - 4);
+ await mouse.down();
+ await mouse.move(x + width + 100, y + height + 100);
+ await mouse.up();
+ const newDimensions = await page.evaluate<() => Dimensions>(dimensions);
+ expect(newDimensions.width).toBe(Math.round(width + 104));
+ expect(newDimensions.height).toBe(Math.round(height + 104));
+ });
+ it('should select the text with mouse', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/textarea.html');
+ await page.focus('textarea');
+ const text =
+ "This is the text that we are going to try to select. Let's see how it goes.";
+ await page.keyboard.type(text);
+ // Firefox needs an extra frame here after typing or it will fail to set the scrollTop
+ await page.evaluate(() => new Promise(requestAnimationFrame));
+ await page.evaluate(
+ () => (document.querySelector('textarea').scrollTop = 0)
+ );
+ const { x, y } = await page.evaluate(dimensions);
+ await page.mouse.move(x + 2, y + 2);
+ await page.mouse.down();
+ await page.mouse.move(100, 100);
+ await page.mouse.up();
+ expect(
+ await page.evaluate(() => {
+ const textarea = document.querySelector('textarea');
+ return textarea.value.substring(
+ textarea.selectionStart,
+ textarea.selectionEnd
+ );
+ })
+ ).toBe(text);
+ });
+ itFailsFirefox('should trigger hover state', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/scrollable.html');
+ await page.hover('#button-6');
+ expect(
+ await page.evaluate(() => document.querySelector('button:hover').id)
+ ).toBe('button-6');
+ await page.hover('#button-2');
+ expect(
+ await page.evaluate(() => document.querySelector('button:hover').id)
+ ).toBe('button-2');
+ await page.hover('#button-91');
+ expect(
+ await page.evaluate(() => document.querySelector('button:hover').id)
+ ).toBe('button-91');
+ });
+ itFailsFirefox(
+ 'should trigger hover state with removed window.Node',
+ async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/scrollable.html');
+ await page.evaluate(() => delete window.Node);
+ await page.hover('#button-6');
+ expect(
+ await page.evaluate(() => document.querySelector('button:hover').id)
+ ).toBe('button-6');
+ }
+ );
+ it('should set modifier keys on click', async () => {
+ const { page, server, isFirefox } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/scrollable.html');
+ await page.evaluate(() =>
+ document
+ .querySelector('#button-3')
+ .addEventListener('mousedown', (e) => (globalThis.lastEvent = e), true)
+ );
+ const modifiers = new Map([
+ ['Shift', 'shiftKey'],
+ ['Control', 'ctrlKey'],
+ ['Alt', 'altKey'],
+ ['Meta', 'metaKey'],
+ ]);
+ // In Firefox, the Meta modifier only exists on Mac
+ if (isFirefox && os.platform() !== 'darwin') delete modifiers['Meta'];
+ for (const [modifier, key] of modifiers) {
+ await page.keyboard.down(modifier);
+ await page.click('#button-3');
+ if (
+ !(await page.evaluate((mod: string) => globalThis.lastEvent[mod], key))
+ )
+ throw new Error(key + ' should be true');
+ await page.keyboard.up(modifier);
+ }
+ await page.click('#button-3');
+ for (const [modifier, key] of modifiers) {
+ if (await page.evaluate((mod: string) => globalThis.lastEvent[mod], key))
+ throw new Error(modifiers[modifier] + ' should be false');
+ }
+ });
+ itFailsFirefox('should send mouse wheel events', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/wheel.html');
+ const elem = await page.$('div');
+ const boundingBoxBefore = await elem.boundingBox();
+ expect(boundingBoxBefore).toMatchObject({
+ width: 115,
+ height: 115,
+ });
+
+ await page.mouse.move(
+ boundingBoxBefore.x + boundingBoxBefore.width / 2,
+ boundingBoxBefore.y + boundingBoxBefore.height / 2
+ );
+
+ await page.mouse.wheel({ deltaY: -100 });
+ const boundingBoxAfter = await elem.boundingBox();
+ expect(boundingBoxAfter).toMatchObject({
+ width: 230,
+ height: 230,
+ });
+ });
+ itFailsFirefox('should tween mouse movement', async () => {
+ const { page } = getTestState();
+
+ await page.mouse.move(100, 100);
+ await page.evaluate(() => {
+ globalThis.result = [];
+ document.addEventListener('mousemove', (event) => {
+ globalThis.result.push([event.clientX, event.clientY]);
+ });
+ });
+ await page.mouse.move(200, 300, { steps: 5 });
+ expect(await page.evaluate('result')).toEqual([
+ [120, 140],
+ [140, 180],
+ [160, 220],
+ [180, 260],
+ [200, 300],
+ ]);
+ });
+ // @see https://crbug.com/929806
+ it('should work with mobile viewports and cross process navigations', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.setViewport({ width: 360, height: 640, isMobile: true });
+ await page.goto(server.CROSS_PROCESS_PREFIX + '/mobile.html');
+ await page.evaluate(() => {
+ document.addEventListener('click', (event) => {
+ globalThis.result = { x: event.clientX, y: event.clientY };
+ });
+ });
+
+ await page.mouse.click(30, 40);
+
+ expect(await page.evaluate('result')).toEqual({ x: 30, y: 40 });
+ });
+});
diff --git a/test/navigation.spec.ts b/test/navigation.spec.ts
new file mode 100644
index 0000000000000..a7d0e36b8988c
--- /dev/null
+++ b/test/navigation.spec.ts
@@ -0,0 +1,776 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import utils from './utils.js';
+import expect from 'expect';
+import {
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+ itFailsFirefox,
+ describeFailsFirefox,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+import os from 'os';
+
+describe('navigation', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+ describe('Page.goto', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ expect(page.url()).toBe(server.EMPTY_PAGE);
+ });
+ it('should work with anchor navigation', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ expect(page.url()).toBe(server.EMPTY_PAGE);
+ await page.goto(server.EMPTY_PAGE + '#foo');
+ expect(page.url()).toBe(server.EMPTY_PAGE + '#foo');
+ await page.goto(server.EMPTY_PAGE + '#bar');
+ expect(page.url()).toBe(server.EMPTY_PAGE + '#bar');
+ });
+ it('should work with redirects', async () => {
+ const { page, server } = getTestState();
+
+ server.setRedirect('/redirect/1.html', '/redirect/2.html');
+ server.setRedirect('/redirect/2.html', '/empty.html');
+ await page.goto(server.PREFIX + '/redirect/1.html');
+ expect(page.url()).toBe(server.EMPTY_PAGE);
+ });
+ it('should navigate to about:blank', async () => {
+ const { page } = getTestState();
+
+ const response = await page.goto('about:blank');
+ expect(response).toBe(null);
+ });
+ it('should return response when page changes its URL after load', async () => {
+ const { page, server } = getTestState();
+
+ const response = await page.goto(server.PREFIX + '/historyapi.html');
+ expect(response.status()).toBe(200);
+ });
+ itFailsFirefox('should work with subframes return 204', async () => {
+ const { page, server } = getTestState();
+
+ server.setRoute('/frames/frame.html', (req, res) => {
+ res.statusCode = 204;
+ res.end();
+ });
+ let error = null;
+ await page
+ .goto(server.PREFIX + '/frames/one-frame.html')
+ .catch((error_) => (error = error_));
+ expect(error).toBe(null);
+ });
+ itFailsFirefox('should fail when server returns 204', async () => {
+ const { page, server, isChrome } = getTestState();
+
+ server.setRoute('/empty.html', (req, res) => {
+ res.statusCode = 204;
+ res.end();
+ });
+ let error = null;
+ await page.goto(server.EMPTY_PAGE).catch((error_) => (error = error_));
+ expect(error).not.toBe(null);
+ if (isChrome) expect(error.message).toContain('net::ERR_ABORTED');
+ else expect(error.message).toContain('NS_BINDING_ABORTED');
+ });
+ it('should navigate to empty page with domcontentloaded', async () => {
+ const { page, server } = getTestState();
+
+ const response = await page.goto(server.EMPTY_PAGE, {
+ waitUntil: 'domcontentloaded',
+ });
+ expect(response.status()).toBe(200);
+ });
+ it('should work when page calls history API in beforeunload', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.evaluate(() => {
+ window.addEventListener(
+ 'beforeunload',
+ () => history.replaceState(null, 'initial', window.location.href),
+ false
+ );
+ });
+ const response = await page.goto(server.PREFIX + '/grid.html');
+ expect(response.status()).toBe(200);
+ });
+ itFailsFirefox(
+ 'should navigate to empty page with networkidle0',
+ async () => {
+ const { page, server } = getTestState();
+
+ const response = await page.goto(server.EMPTY_PAGE, {
+ waitUntil: 'networkidle0',
+ });
+ expect(response.status()).toBe(200);
+ }
+ );
+ itFailsFirefox(
+ 'should navigate to empty page with networkidle2',
+ async () => {
+ const { page, server } = getTestState();
+
+ const response = await page.goto(server.EMPTY_PAGE, {
+ waitUntil: 'networkidle2',
+ });
+ expect(response.status()).toBe(200);
+ }
+ );
+ itFailsFirefox('should fail when navigating to bad url', async () => {
+ const { page, isChrome } = getTestState();
+
+ let error = null;
+ await page.goto('asdfasdf').catch((error_) => (error = error_));
+ if (isChrome)
+ expect(error.message).toContain('Cannot navigate to invalid URL');
+ else expect(error.message).toContain('Invalid url');
+ });
+
+ /* If you are running this on pre-Catalina versions of macOS this will fail locally.
+ /* Mac OSX Catalina outputs a different message than other platforms.
+ * See https://support.google.com/chrome/thread/18125056?hl=en for details.
+ * If you're running pre-Catalina Mac OSX this test will fail locally.
+ */
+ const EXPECTED_SSL_CERT_MESSAGE =
+ os.platform() === 'darwin'
+ ? 'net::ERR_CERT_INVALID'
+ : 'net::ERR_CERT_AUTHORITY_INVALID';
+
+ itFailsFirefox('should fail when navigating to bad SSL', async () => {
+ const { page, httpsServer, isChrome } = getTestState();
+
+ // Make sure that network events do not emit 'undefined'.
+ // @see https://crbug.com/750469
+ const requests = [];
+ page.on('request', () => requests.push('request'));
+ page.on('requestfinished', () => requests.push('requestfinished'));
+ page.on('requestfailed', () => requests.push('requestfailed'));
+
+ let error = null;
+ await page
+ .goto(httpsServer.EMPTY_PAGE)
+ .catch((error_) => (error = error_));
+ if (isChrome) expect(error.message).toContain(EXPECTED_SSL_CERT_MESSAGE);
+ else expect(error.message).toContain('SSL_ERROR_UNKNOWN');
+
+ expect(requests.length).toBe(2);
+ expect(requests[0]).toBe('request');
+ expect(requests[1]).toBe('requestfailed');
+ });
+ it('should fail when navigating to bad SSL after redirects', async () => {
+ const { page, server, httpsServer, isChrome } = getTestState();
+
+ server.setRedirect('/redirect/1.html', '/redirect/2.html');
+ server.setRedirect('/redirect/2.html', '/empty.html');
+ let error = null;
+ await page
+ .goto(httpsServer.PREFIX + '/redirect/1.html')
+ .catch((error_) => (error = error_));
+ if (isChrome) expect(error.message).toContain(EXPECTED_SSL_CERT_MESSAGE);
+ else expect(error.message).toContain('SSL_ERROR_UNKNOWN');
+ });
+ it('should throw if networkidle is passed as an option', async () => {
+ const { page, server } = getTestState();
+
+ let error = null;
+ await page
+ // @ts-expect-error purposefully passing an old option
+ .goto(server.EMPTY_PAGE, { waitUntil: 'networkidle' })
+ .catch((error_) => (error = error_));
+ expect(error.message).toContain(
+ '"networkidle" option is no longer supported'
+ );
+ });
+ it('should fail when main resources failed to load', async () => {
+ const { page, isChrome } = getTestState();
+
+ let error = null;
+ await page
+ .goto('http://localhost:44123/non-existing-url')
+ .catch((error_) => (error = error_));
+ if (isChrome)
+ expect(error.message).toContain('net::ERR_CONNECTION_REFUSED');
+ else expect(error.message).toContain('NS_ERROR_CONNECTION_REFUSED');
+ });
+ it('should fail when exceeding maximum navigation timeout', async () => {
+ const { page, server, puppeteer } = getTestState();
+
+ // Hang for request to the empty.html
+ server.setRoute('/empty.html', () => {});
+ let error = null;
+ await page
+ .goto(server.PREFIX + '/empty.html', { timeout: 1 })
+ .catch((error_) => (error = error_));
+ expect(error.message).toContain('Navigation timeout of 1 ms exceeded');
+ expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
+ });
+ it('should fail when exceeding default maximum navigation timeout', async () => {
+ const { page, server, puppeteer } = getTestState();
+
+ // Hang for request to the empty.html
+ server.setRoute('/empty.html', () => {});
+ let error = null;
+ page.setDefaultNavigationTimeout(1);
+ await page
+ .goto(server.PREFIX + '/empty.html')
+ .catch((error_) => (error = error_));
+ expect(error.message).toContain('Navigation timeout of 1 ms exceeded');
+ expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
+ });
+ it('should fail when exceeding default maximum timeout', async () => {
+ const { page, server, puppeteer } = getTestState();
+
+ // Hang for request to the empty.html
+ server.setRoute('/empty.html', () => {});
+ let error = null;
+ page.setDefaultTimeout(1);
+ await page
+ .goto(server.PREFIX + '/empty.html')
+ .catch((error_) => (error = error_));
+ expect(error.message).toContain('Navigation timeout of 1 ms exceeded');
+ expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
+ });
+ it('should prioritize default navigation timeout over default timeout', async () => {
+ const { page, server, puppeteer } = getTestState();
+
+ // Hang for request to the empty.html
+ server.setRoute('/empty.html', () => {});
+ let error = null;
+ page.setDefaultTimeout(0);
+ page.setDefaultNavigationTimeout(1);
+ await page
+ .goto(server.PREFIX + '/empty.html')
+ .catch((error_) => (error = error_));
+ expect(error.message).toContain('Navigation timeout of 1 ms exceeded');
+ expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
+ });
+ it('should disable timeout when its set to 0', async () => {
+ const { page, server } = getTestState();
+
+ let error = null;
+ let loaded = false;
+ page.once('load', () => (loaded = true));
+ await page
+ .goto(server.PREFIX + '/grid.html', { timeout: 0, waitUntil: ['load'] })
+ .catch((error_) => (error = error_));
+ expect(error).toBe(null);
+ expect(loaded).toBe(true);
+ });
+ it('should work when navigating to valid url', async () => {
+ const { page, server } = getTestState();
+
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.ok()).toBe(true);
+ });
+ itFailsFirefox('should work when navigating to data url', async () => {
+ const { page } = getTestState();
+
+ const response = await page.goto('data:text/html,hello');
+ expect(response.ok()).toBe(true);
+ });
+ it('should work when navigating to 404', async () => {
+ const { page, server } = getTestState();
+
+ const response = await page.goto(server.PREFIX + '/not-found');
+ expect(response.ok()).toBe(false);
+ expect(response.status()).toBe(404);
+ });
+ it('should return last response in redirect chain', async () => {
+ const { page, server } = getTestState();
+
+ server.setRedirect('/redirect/1.html', '/redirect/2.html');
+ server.setRedirect('/redirect/2.html', '/redirect/3.html');
+ server.setRedirect('/redirect/3.html', server.EMPTY_PAGE);
+ const response = await page.goto(server.PREFIX + '/redirect/1.html');
+ expect(response.ok()).toBe(true);
+ expect(response.url()).toBe(server.EMPTY_PAGE);
+ });
+ itFailsFirefox(
+ 'should wait for network idle to succeed navigation',
+ async () => {
+ const { page, server } = getTestState();
+
+ let responses = [];
+ // Hold on to a bunch of requests without answering.
+ server.setRoute('/fetch-request-a.js', (req, res) =>
+ responses.push(res)
+ );
+ server.setRoute('/fetch-request-b.js', (req, res) =>
+ responses.push(res)
+ );
+ server.setRoute('/fetch-request-c.js', (req, res) =>
+ responses.push(res)
+ );
+ server.setRoute('/fetch-request-d.js', (req, res) =>
+ responses.push(res)
+ );
+ const initialFetchResourcesRequested = Promise.all([
+ server.waitForRequest('/fetch-request-a.js'),
+ server.waitForRequest('/fetch-request-b.js'),
+ server.waitForRequest('/fetch-request-c.js'),
+ ]);
+ const secondFetchResourceRequested = server.waitForRequest(
+ '/fetch-request-d.js'
+ );
+
+ // Navigate to a page which loads immediately and then does a bunch of
+ // requests via javascript's fetch method.
+ const navigationPromise = page.goto(
+ server.PREFIX + '/networkidle.html',
+ {
+ waitUntil: 'networkidle0',
+ }
+ );
+ // Track when the navigation gets completed.
+ let navigationFinished = false;
+ navigationPromise.then(() => (navigationFinished = true));
+
+ // Wait for the page's 'load' event.
+ await new Promise((fulfill) => page.once('load', fulfill));
+ expect(navigationFinished).toBe(false);
+
+ // Wait for the initial three resources to be requested.
+ await initialFetchResourcesRequested;
+
+ // Expect navigation still to be not finished.
+ expect(navigationFinished).toBe(false);
+
+ // Respond to initial requests.
+ for (const response of responses) {
+ response.statusCode = 404;
+ response.end(`File not found`);
+ }
+
+ // Reset responses array
+ responses = [];
+
+ // Wait for the second round to be requested.
+ await secondFetchResourceRequested;
+ // Expect navigation still to be not finished.
+ expect(navigationFinished).toBe(false);
+
+ // Respond to requests.
+ for (const response of responses) {
+ response.statusCode = 404;
+ response.end(`File not found`);
+ }
+
+ const response = await navigationPromise;
+ // Expect navigation to succeed.
+ expect(response.ok()).toBe(true);
+ }
+ );
+ it('should not leak listeners during navigation', async () => {
+ const { page, server } = getTestState();
+
+ let warning = null;
+ const warningHandler = (w) => (warning = w);
+ process.on('warning', warningHandler);
+ for (let i = 0; i < 20; ++i) await page.goto(server.EMPTY_PAGE);
+ process.removeListener('warning', warningHandler);
+ expect(warning).toBe(null);
+ });
+ it('should not leak listeners during bad navigation', async () => {
+ const { page } = getTestState();
+
+ let warning = null;
+ const warningHandler = (w) => (warning = w);
+ process.on('warning', warningHandler);
+ for (let i = 0; i < 20; ++i)
+ await page.goto('asdf').catch(() => {
+ /* swallow navigation error */
+ });
+ process.removeListener('warning', warningHandler);
+ expect(warning).toBe(null);
+ });
+ it('should not leak listeners during navigation of 11 pages', async () => {
+ const { context, server } = getTestState();
+
+ let warning = null;
+ const warningHandler = (w) => (warning = w);
+ process.on('warning', warningHandler);
+ await Promise.all(
+ [...Array(20)].map(async () => {
+ const page = await context.newPage();
+ await page.goto(server.EMPTY_PAGE);
+ await page.close();
+ })
+ );
+ process.removeListener('warning', warningHandler);
+ expect(warning).toBe(null);
+ });
+ itFailsFirefox(
+ 'should navigate to dataURL and fire dataURL requests',
+ async () => {
+ const { page } = getTestState();
+
+ const requests = [];
+ page.on(
+ 'request',
+ (request) => !utils.isFavicon(request) && requests.push(request)
+ );
+ const dataURL = 'data:text/html,yo
';
+ const response = await page.goto(dataURL);
+ expect(response.status()).toBe(200);
+ expect(requests.length).toBe(1);
+ expect(requests[0].url()).toBe(dataURL);
+ }
+ );
+ itFailsFirefox(
+ 'should navigate to URL with hash and fire requests without hash',
+ async () => {
+ const { page, server } = getTestState();
+
+ const requests = [];
+ page.on(
+ 'request',
+ (request) => !utils.isFavicon(request) && requests.push(request)
+ );
+ const response = await page.goto(server.EMPTY_PAGE + '#hash');
+ expect(response.status()).toBe(200);
+ expect(response.url()).toBe(server.EMPTY_PAGE);
+ expect(requests.length).toBe(1);
+ expect(requests[0].url()).toBe(server.EMPTY_PAGE);
+ }
+ );
+ it('should work with self requesting page', async () => {
+ const { page, server } = getTestState();
+
+ const response = await page.goto(server.PREFIX + '/self-request.html');
+ expect(response.status()).toBe(200);
+ expect(response.url()).toContain('self-request.html');
+ });
+ it('should fail when navigating and show the url at the error message', async () => {
+ const { page, httpsServer } = getTestState();
+
+ const url = httpsServer.PREFIX + '/redirect/1.html';
+ let error = null;
+ try {
+ await page.goto(url);
+ } catch (error_) {
+ error = error_;
+ }
+ expect(error.message).toContain(url);
+ });
+ itFailsFirefox('should send referer', async () => {
+ const { page, server } = getTestState();
+
+ const [request1, request2] = await Promise.all([
+ server.waitForRequest('/grid.html'),
+ server.waitForRequest('/digits/1.png'),
+ page.goto(server.PREFIX + '/grid.html', {
+ referer: 'http://google.com/',
+ }),
+ ]);
+ expect(request1.headers['referer']).toBe('http://google.com/');
+ // Make sure subresources do not inherit referer.
+ expect(request2.headers['referer']).toBe(server.PREFIX + '/grid.html');
+ });
+ });
+
+ describe('Page.waitForNavigation', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const [response] = await Promise.all([
+ page.waitForNavigation(),
+ page.evaluate(
+ (url: string) => (window.location.href = url),
+ server.PREFIX + '/grid.html'
+ ),
+ ]);
+ expect(response.ok()).toBe(true);
+ expect(response.url()).toContain('grid.html');
+ });
+ it('should work with both domcontentloaded and load', async () => {
+ const { page, server } = getTestState();
+
+ let response = null;
+ server.setRoute('/one-style.css', (req, res) => (response = res));
+ const navigationPromise = page.goto(server.PREFIX + '/one-style.html');
+ const domContentLoadedPromise = page.waitForNavigation({
+ waitUntil: 'domcontentloaded',
+ });
+
+ let bothFired = false;
+ const bothFiredPromise = page
+ .waitForNavigation({
+ waitUntil: ['load', 'domcontentloaded'],
+ })
+ .then(() => (bothFired = true));
+
+ await server.waitForRequest('/one-style.css');
+ await domContentLoadedPromise;
+ expect(bothFired).toBe(false);
+ response.end();
+ await bothFiredPromise;
+ await navigationPromise;
+ });
+ it('should work with clicking on anchor links', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.setContent(`foobar `);
+ const [response] = await Promise.all([
+ page.waitForNavigation(),
+ page.click('a'),
+ ]);
+ expect(response).toBe(null);
+ expect(page.url()).toBe(server.EMPTY_PAGE + '#foobar');
+ });
+ itFailsFirefox('should work with history.pushState()', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.setContent(`
+ SPA
+
+ `);
+ const [response] = await Promise.all([
+ page.waitForNavigation(),
+ page.click('a'),
+ ]);
+ expect(response).toBe(null);
+ expect(page.url()).toBe(server.PREFIX + '/wow.html');
+ });
+ itFailsFirefox('should work with history.replaceState()', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.setContent(`
+ SPA
+
+ `);
+ const [response] = await Promise.all([
+ page.waitForNavigation(),
+ page.click('a'),
+ ]);
+ expect(response).toBe(null);
+ expect(page.url()).toBe(server.PREFIX + '/replaced.html');
+ });
+ itFailsFirefox(
+ 'should work with DOM history.back()/history.forward()',
+ async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.setContent(`
+ back
+ forward
+
+ `);
+ expect(page.url()).toBe(server.PREFIX + '/second.html');
+ const [backResponse] = await Promise.all([
+ page.waitForNavigation(),
+ page.click('a#back'),
+ ]);
+ expect(backResponse).toBe(null);
+ expect(page.url()).toBe(server.PREFIX + '/first.html');
+ const [forwardResponse] = await Promise.all([
+ page.waitForNavigation(),
+ page.click('a#forward'),
+ ]);
+ expect(forwardResponse).toBe(null);
+ expect(page.url()).toBe(server.PREFIX + '/second.html');
+ }
+ );
+ itFailsFirefox(
+ 'should work when subframe issues window.stop()',
+ async () => {
+ const { page, server } = getTestState();
+
+ server.setRoute('/frames/style.css', () => {});
+ const navigationPromise = page.goto(
+ server.PREFIX + '/frames/one-frame.html'
+ );
+ const frame = await utils.waitEvent(page, 'frameattached');
+ await new Promise((fulfill) => {
+ page.on('framenavigated', (f) => {
+ if (f === frame) fulfill();
+ });
+ });
+ await Promise.all([
+ frame.evaluate(() => window.stop()),
+ navigationPromise,
+ ]);
+ }
+ );
+ });
+
+ describe('Page.goBack', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.goto(server.PREFIX + '/grid.html');
+
+ let response = await page.goBack();
+ expect(response.ok()).toBe(true);
+ expect(response.url()).toContain(server.EMPTY_PAGE);
+
+ response = await page.goForward();
+ expect(response.ok()).toBe(true);
+ expect(response.url()).toContain('/grid.html');
+
+ response = await page.goForward();
+ expect(response).toBe(null);
+ });
+ itFailsFirefox('should work with HistoryAPI', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.evaluate(() => {
+ history.pushState({}, '', '/first.html');
+ history.pushState({}, '', '/second.html');
+ });
+ expect(page.url()).toBe(server.PREFIX + '/second.html');
+
+ await page.goBack();
+ expect(page.url()).toBe(server.PREFIX + '/first.html');
+ await page.goBack();
+ expect(page.url()).toBe(server.EMPTY_PAGE);
+ await page.goForward();
+ expect(page.url()).toBe(server.PREFIX + '/first.html');
+ });
+ });
+
+ describeFailsFirefox('Frame.goto', function () {
+ it('should navigate subframes', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/frames/one-frame.html');
+ expect(page.frames()[0].url()).toContain('/frames/one-frame.html');
+ expect(page.frames()[1].url()).toContain('/frames/frame.html');
+
+ const response = await page.frames()[1].goto(server.EMPTY_PAGE);
+ expect(response.ok()).toBe(true);
+ expect(response.frame()).toBe(page.frames()[1]);
+ });
+ it('should reject when frame detaches', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/frames/one-frame.html');
+
+ server.setRoute('/empty.html', () => {});
+ const navigationPromise = page
+ .frames()[1]
+ .goto(server.EMPTY_PAGE)
+ .catch((error_) => error_);
+ await server.waitForRequest('/empty.html');
+
+ await page.$eval('iframe', (frame) => frame.remove());
+ const error = await navigationPromise;
+ expect(error.message).toBe('Navigating frame was detached');
+ });
+ it('should return matching responses', async () => {
+ const { page, server } = getTestState();
+
+ // Disable cache: otherwise, chromium will cache similar requests.
+ await page.setCacheEnabled(false);
+ await page.goto(server.EMPTY_PAGE);
+ // Attach three frames.
+ const frames = await Promise.all([
+ utils.attachFrame(page, 'frame1', server.EMPTY_PAGE),
+ utils.attachFrame(page, 'frame2', server.EMPTY_PAGE),
+ utils.attachFrame(page, 'frame3', server.EMPTY_PAGE),
+ ]);
+ // Navigate all frames to the same URL.
+ const serverResponses = [];
+ server.setRoute('/one-style.html', (req, res) =>
+ serverResponses.push(res)
+ );
+ const navigations = [];
+ for (let i = 0; i < 3; ++i) {
+ navigations.push(frames[i].goto(server.PREFIX + '/one-style.html'));
+ await server.waitForRequest('/one-style.html');
+ }
+ // Respond from server out-of-order.
+ const serverResponseTexts = ['AAA', 'BBB', 'CCC'];
+ for (const i of [1, 2, 0]) {
+ serverResponses[i].end(serverResponseTexts[i]);
+ const response = await navigations[i];
+ expect(response.frame()).toBe(frames[i]);
+ expect(await response.text()).toBe(serverResponseTexts[i]);
+ }
+ });
+ });
+
+ describeFailsFirefox('Frame.waitForNavigation', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/frames/one-frame.html');
+ const frame = page.frames()[1];
+ const [response] = await Promise.all([
+ frame.waitForNavigation(),
+ frame.evaluate(
+ (url: string) => (window.location.href = url),
+ server.PREFIX + '/grid.html'
+ ),
+ ]);
+ expect(response.ok()).toBe(true);
+ expect(response.url()).toContain('grid.html');
+ expect(response.frame()).toBe(frame);
+ expect(page.url()).toContain('/frames/one-frame.html');
+ });
+ it('should fail when frame detaches', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/frames/one-frame.html');
+ const frame = page.frames()[1];
+
+ server.setRoute('/empty.html', () => {});
+ let error = null;
+ const navigationPromise = frame
+ .waitForNavigation()
+ .catch((error_) => (error = error_));
+ await Promise.all([
+ server.waitForRequest('/empty.html'),
+ frame.evaluate(() => ((window as any).location = '/empty.html')),
+ ]);
+ await page.$eval('iframe', (frame) => frame.remove());
+ await navigationPromise;
+ expect(error.message).toBe('Navigating frame was detached');
+ });
+ });
+
+ describe('Page.reload', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.evaluate(() => (globalThis._foo = 10));
+ await page.reload();
+ expect(await page.evaluate(() => globalThis._foo)).toBe(undefined);
+ });
+ });
+});
diff --git a/test/network.spec.ts b/test/network.spec.ts
new file mode 100644
index 0000000000000..f4d4b5069cfc5
--- /dev/null
+++ b/test/network.spec.ts
@@ -0,0 +1,809 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import fs from 'fs';
+import path from 'path';
+import utils from './utils.js';
+import expect from 'expect';
+import {
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+ itFailsFirefox,
+ describeFailsFirefox,
+ itChromeOnly,
+ itFirefoxOnly,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+import { HTTPResponse } from '../lib/cjs/puppeteer/api-docs-entry.js';
+
+describe('network', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+
+ describe('Page.Events.Request', function () {
+ it('should fire for navigation requests', async () => {
+ const { page, server } = getTestState();
+
+ const requests = [];
+ page.on(
+ 'request',
+ (request) => !utils.isFavicon(request) && requests.push(request)
+ );
+ await page.goto(server.EMPTY_PAGE);
+ expect(requests.length).toBe(1);
+ });
+ it('should fire for iframes', async () => {
+ const { page, server } = getTestState();
+
+ const requests = [];
+ page.on(
+ 'request',
+ (request) => !utils.isFavicon(request) && requests.push(request)
+ );
+ await page.goto(server.EMPTY_PAGE);
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ expect(requests.length).toBe(2);
+ });
+ it('should fire for fetches', async () => {
+ const { page, server } = getTestState();
+
+ const requests = [];
+ page.on(
+ 'request',
+ (request) => !utils.isFavicon(request) && requests.push(request)
+ );
+ await page.goto(server.EMPTY_PAGE);
+ await page.evaluate(() => fetch('/empty.html'));
+ expect(requests.length).toBe(2);
+ });
+ });
+ describe('Request.frame', function () {
+ it('should work for main frame navigation request', async () => {
+ const { page, server } = getTestState();
+
+ const requests = [];
+ page.on(
+ 'request',
+ (request) => !utils.isFavicon(request) && requests.push(request)
+ );
+ await page.goto(server.EMPTY_PAGE);
+ expect(requests.length).toBe(1);
+ expect(requests[0].frame()).toBe(page.mainFrame());
+ });
+ itFailsFirefox('should work for subframe navigation request', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const requests = [];
+ page.on(
+ 'request',
+ (request) => !utils.isFavicon(request) && requests.push(request)
+ );
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ expect(requests.length).toBe(1);
+ expect(requests[0].frame()).toBe(page.frames()[1]);
+ });
+ it('should work for fetch requests', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ let requests = [];
+ page.on(
+ 'request',
+ (request) => !utils.isFavicon(request) && requests.push(request)
+ );
+ await page.evaluate(() => fetch('/digits/1.png'));
+ requests = requests.filter(
+ (request) => !request.url().includes('favicon')
+ );
+ expect(requests.length).toBe(1);
+ expect(requests[0].frame()).toBe(page.mainFrame());
+ });
+ });
+
+ describe('Request.headers', function () {
+ itChromeOnly('should define Chrome as user agent header', async () => {
+ const { page, server } = getTestState();
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.request().headers()['user-agent']).toContain('Chrome');
+ });
+
+ itFirefoxOnly('should define Firefox as user agent header', async () => {
+ const { page, server } = getTestState();
+
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.request().headers()['user-agent']).toContain('Firefox');
+ });
+ });
+
+ describe('Response.headers', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ server.setRoute('/empty.html', (req, res) => {
+ res.setHeader('foo', 'bar');
+ res.end();
+ });
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.headers()['foo']).toBe('bar');
+ });
+ });
+
+ describeFailsFirefox('Request.initiator', () => {
+ it('should return the initiator', async () => {
+ const { page, server } = getTestState();
+
+ const initiators = new Map();
+ page.on('request', (request) =>
+ initiators.set(request.url().split('/').pop(), request.initiator())
+ );
+ await page.goto(server.PREFIX + '/initiator.html');
+
+ expect(initiators.get('initiator.html').type).toBe('other');
+ expect(initiators.get('initiator.js').type).toBe('parser');
+ expect(initiators.get('initiator.js').url).toBe(
+ server.PREFIX + '/initiator.html'
+ );
+ expect(initiators.get('frame.html').type).toBe('parser');
+ expect(initiators.get('frame.html').url).toBe(
+ server.PREFIX + '/initiator.html'
+ );
+ expect(initiators.get('script.js').type).toBe('parser');
+ expect(initiators.get('script.js').url).toBe(
+ server.PREFIX + '/frames/frame.html'
+ );
+ expect(initiators.get('style.css').type).toBe('parser');
+ expect(initiators.get('style.css').url).toBe(
+ server.PREFIX + '/frames/frame.html'
+ );
+ expect(initiators.get('initiator.js').type).toBe('parser');
+ expect(initiators.get('injectedfile.js').type).toBe('script');
+ expect(initiators.get('injectedfile.js').stack.callFrames[0].url).toBe(
+ server.PREFIX + '/initiator.js'
+ );
+ expect(initiators.get('injectedstyle.css').type).toBe('script');
+ expect(initiators.get('injectedstyle.css').stack.callFrames[0].url).toBe(
+ server.PREFIX + '/initiator.js'
+ );
+ expect(initiators.get('initiator.js').url).toBe(
+ server.PREFIX + '/initiator.html'
+ );
+ });
+ });
+
+ describeFailsFirefox('Response.fromCache', function () {
+ it('should return |false| for non-cached content', async () => {
+ const { page, server } = getTestState();
+
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.fromCache()).toBe(false);
+ });
+
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ const responses = new Map();
+ page.on(
+ 'response',
+ (r) =>
+ !utils.isFavicon(r.request()) &&
+ responses.set(r.url().split('/').pop(), r)
+ );
+
+ // Load and re-load to make sure it's cached.
+ await page.goto(server.PREFIX + '/cached/one-style.html');
+ await page.reload();
+
+ expect(responses.size).toBe(2);
+ expect(responses.get('one-style.css').status()).toBe(200);
+ expect(responses.get('one-style.css').fromCache()).toBe(true);
+ expect(responses.get('one-style.html').status()).toBe(304);
+ expect(responses.get('one-style.html').fromCache()).toBe(false);
+ });
+ });
+
+ describeFailsFirefox('Response.fromServiceWorker', function () {
+ it('should return |false| for non-service-worker content', async () => {
+ const { page, server } = getTestState();
+
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.fromServiceWorker()).toBe(false);
+ });
+
+ it('Response.fromServiceWorker', async () => {
+ const { page, server } = getTestState();
+
+ const responses = new Map();
+ page.on(
+ 'response',
+ (r) => !utils.isFavicon(r) && responses.set(r.url().split('/').pop(), r)
+ );
+
+ // Load and re-load to make sure serviceworker is installed and running.
+ await page.goto(server.PREFIX + '/serviceworkers/fetch/sw.html', {
+ waitUntil: 'networkidle2',
+ });
+ await page.evaluate(async () => await globalThis.activationPromise);
+ await page.reload();
+
+ expect(responses.size).toBe(2);
+ expect(responses.get('sw.html').status()).toBe(200);
+ expect(responses.get('sw.html').fromServiceWorker()).toBe(true);
+ expect(responses.get('style.css').status()).toBe(200);
+ expect(responses.get('style.css').fromServiceWorker()).toBe(true);
+ });
+ });
+
+ describeFailsFirefox('Request.postData', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ server.setRoute('/post', (req, res) => res.end());
+ let request = null;
+ page.on('request', (r) => {
+ if (!utils.isFavicon(r)) request = r;
+ });
+ await page.evaluate(() =>
+ fetch('./post', {
+ method: 'POST',
+ body: JSON.stringify({ foo: 'bar' }),
+ })
+ );
+ expect(request).toBeTruthy();
+ expect(request.postData()).toBe('{"foo":"bar"}');
+ });
+ it('should be |undefined| when there is no post data', async () => {
+ const { page, server } = getTestState();
+
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.request().postData()).toBe(undefined);
+ });
+ });
+
+ describeFailsFirefox('Response.text', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ const response = await page.goto(server.PREFIX + '/simple.json');
+ const responseText = (await response.text()).trimEnd();
+ expect(responseText).toBe('{"foo": "bar"}');
+ });
+ it('should return uncompressed text', async () => {
+ const { page, server } = getTestState();
+
+ server.enableGzip('/simple.json');
+ const response = await page.goto(server.PREFIX + '/simple.json');
+ expect(response.headers()['content-encoding']).toBe('gzip');
+ const responseText = (await response.text()).trimEnd();
+ expect(responseText).toBe('{"foo": "bar"}');
+ });
+ it('should throw when requesting body of redirected response', async () => {
+ const { page, server } = getTestState();
+
+ server.setRedirect('/foo.html', '/empty.html');
+ const response = await page.goto(server.PREFIX + '/foo.html');
+ const redirectChain = response.request().redirectChain();
+ expect(redirectChain.length).toBe(1);
+ const redirected = redirectChain[0].response();
+ expect(redirected.status()).toBe(302);
+ let error = null;
+ await redirected.text().catch((error_) => (error = error_));
+ expect(error.message).toContain(
+ 'Response body is unavailable for redirect responses'
+ );
+ });
+ it('should wait until response completes', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ // Setup server to trap request.
+ let serverResponse = null;
+ server.setRoute('/get', (req, res) => {
+ serverResponse = res;
+ // In Firefox, |fetch| will be hanging until it receives |Content-Type| header
+ // from server.
+ res.setHeader('Content-Type', 'text/plain; charset=utf-8');
+ res.write('hello ');
+ });
+ // Setup page to trap response.
+ let requestFinished = false;
+ page.on(
+ 'requestfinished',
+ (r) => (requestFinished = requestFinished || r.url().includes('/get'))
+ );
+ // send request and wait for server response
+ const [pageResponse] = await Promise.all([
+ page.waitForResponse((r) => !utils.isFavicon(r.request())),
+ page.evaluate(() => fetch('./get', { method: 'GET' })),
+ server.waitForRequest('/get'),
+ ]);
+
+ expect(serverResponse).toBeTruthy();
+ expect(pageResponse).toBeTruthy();
+ expect(pageResponse.status()).toBe(200);
+ expect(requestFinished).toBe(false);
+
+ const responseText = pageResponse.text();
+ // Write part of the response and wait for it to be flushed.
+ await new Promise((x) => serverResponse.write('wor', x));
+ // Finish response.
+ await new Promise((x) => serverResponse.end('ld!', x));
+ expect(await responseText).toBe('hello world!');
+ });
+ });
+
+ describeFailsFirefox('Response.json', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ const response = await page.goto(server.PREFIX + '/simple.json');
+ expect(await response.json()).toEqual({ foo: 'bar' });
+ });
+ });
+
+ describeFailsFirefox('Response.buffer', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ const response = await page.goto(server.PREFIX + '/pptr.png');
+ const imageBuffer = fs.readFileSync(
+ path.join(__dirname, 'assets', 'pptr.png')
+ );
+ const responseBuffer = await response.buffer();
+ expect(responseBuffer.equals(imageBuffer)).toBe(true);
+ });
+ it('should work with compression', async () => {
+ const { page, server } = getTestState();
+
+ server.enableGzip('/pptr.png');
+ const response = await page.goto(server.PREFIX + '/pptr.png');
+ const imageBuffer = fs.readFileSync(
+ path.join(__dirname, 'assets', 'pptr.png')
+ );
+ const responseBuffer = await response.buffer();
+ expect(responseBuffer.equals(imageBuffer)).toBe(true);
+ });
+ it('should throw if the response does not have a body', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/empty.html');
+
+ server.setRoute('/test.html', (req, res) => {
+ res.setHeader('Access-Control-Allow-Origin', '*');
+ res.setHeader('Access-Control-Allow-Headers', 'x-ping');
+ res.end('Hello World');
+ });
+ const url = server.CROSS_PROCESS_PREFIX + '/test.html';
+ const responsePromise = new Promise((resolve) => {
+ page.on('response', (response) => {
+ // Get the preflight response.
+ if (
+ response.request().method() === 'OPTIONS' &&
+ response.url() === url
+ ) {
+ resolve(response);
+ }
+ });
+ });
+
+ // Trigger a request with a preflight.
+ await page.evaluate<(src: string) => void>(async (src) => {
+ const response = await fetch(src, {
+ method: 'POST',
+ headers: { 'x-ping': 'pong' },
+ });
+ return response;
+ }, url);
+
+ const response = await responsePromise;
+ await expect(response.buffer()).rejects.toThrowError(
+ 'Could not load body for this request. This might happen if the request is a preflight request.'
+ );
+ });
+ });
+
+ describe('Response.statusText', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ server.setRoute('/cool', (req, res) => {
+ res.writeHead(200, 'cool!');
+ res.end();
+ });
+ const response = await page.goto(server.PREFIX + '/cool');
+ expect(response.statusText()).toBe('cool!');
+ });
+
+ it('handles missing status text', async () => {
+ const { page, server } = getTestState();
+
+ server.setRoute('/nostatus', (req, res) => {
+ res.writeHead(200, '');
+ res.end();
+ });
+ const response = await page.goto(server.PREFIX + '/nostatus');
+ expect(response.statusText()).toBe('');
+ });
+ });
+
+ describeFailsFirefox('Response.timing', function () {
+ it('returns timing information', async () => {
+ const { page, server } = getTestState();
+ const responses = [];
+ page.on('response', (response) => responses.push(response));
+ await page.goto(server.EMPTY_PAGE);
+ expect(responses.length).toBe(1);
+ expect(responses[0].timing().receiveHeadersEnd).toBeGreaterThan(0);
+ });
+ });
+
+ describeFailsFirefox('Network Events', function () {
+ it('Page.Events.Request', async () => {
+ const { page, server } = getTestState();
+
+ const requests = [];
+ page.on('request', (request) => requests.push(request));
+ await page.goto(server.EMPTY_PAGE);
+ expect(requests.length).toBe(1);
+ expect(requests[0].url()).toBe(server.EMPTY_PAGE);
+ expect(requests[0].resourceType()).toBe('document');
+ expect(requests[0].method()).toBe('GET');
+ expect(requests[0].response()).toBeTruthy();
+ expect(requests[0].frame() === page.mainFrame()).toBe(true);
+ expect(requests[0].frame().url()).toBe(server.EMPTY_PAGE);
+ });
+ it('Page.Events.RequestServedFromCache', async () => {
+ const { page, server } = getTestState();
+
+ const cached = [];
+ page.on('requestservedfromcache', (r) =>
+ cached.push(r.url().split('/').pop())
+ );
+
+ await page.goto(server.PREFIX + '/cached/one-style.html');
+ expect(cached).toEqual([]);
+
+ await page.reload();
+ expect(cached).toEqual(['one-style.css']);
+ });
+ it('Page.Events.Response', async () => {
+ const { page, server } = getTestState();
+
+ const responses = [];
+ page.on('response', (response) => responses.push(response));
+ await page.goto(server.EMPTY_PAGE);
+ expect(responses.length).toBe(1);
+ expect(responses[0].url()).toBe(server.EMPTY_PAGE);
+ expect(responses[0].status()).toBe(200);
+ expect(responses[0].ok()).toBe(true);
+ expect(responses[0].request()).toBeTruthy();
+ const remoteAddress = responses[0].remoteAddress();
+ // Either IPv6 or IPv4, depending on environment.
+ expect(
+ remoteAddress.ip.includes('::1') || remoteAddress.ip === '127.0.0.1'
+ ).toBe(true);
+ expect(remoteAddress.port).toBe(server.PORT);
+ });
+
+ it('Page.Events.RequestFailed', async () => {
+ const { page, server, isChrome } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ if (request.url().endsWith('css')) request.abort();
+ else request.continue();
+ });
+ const failedRequests = [];
+ page.on('requestfailed', (request) => failedRequests.push(request));
+ await page.goto(server.PREFIX + '/one-style.html');
+ expect(failedRequests.length).toBe(1);
+ expect(failedRequests[0].url()).toContain('one-style.css');
+ expect(failedRequests[0].response()).toBe(null);
+ expect(failedRequests[0].resourceType()).toBe('stylesheet');
+ if (isChrome)
+ expect(failedRequests[0].failure().errorText).toBe('net::ERR_FAILED');
+ else
+ expect(failedRequests[0].failure().errorText).toBe('NS_ERROR_FAILURE');
+ expect(failedRequests[0].frame()).toBeTruthy();
+ });
+ it('Page.Events.RequestFinished', async () => {
+ const { page, server } = getTestState();
+
+ const requests = [];
+ page.on('requestfinished', (request) => requests.push(request));
+ await page.goto(server.EMPTY_PAGE);
+ expect(requests.length).toBe(1);
+ expect(requests[0].url()).toBe(server.EMPTY_PAGE);
+ expect(requests[0].response()).toBeTruthy();
+ expect(requests[0].frame() === page.mainFrame()).toBe(true);
+ expect(requests[0].frame().url()).toBe(server.EMPTY_PAGE);
+ });
+ it('should fire events in proper order', async () => {
+ const { page, server } = getTestState();
+
+ const events = [];
+ page.on('request', () => events.push('request'));
+ page.on('response', () => events.push('response'));
+ page.on('requestfinished', () => events.push('requestfinished'));
+ await page.goto(server.EMPTY_PAGE);
+ expect(events).toEqual(['request', 'response', 'requestfinished']);
+ });
+ it('should support redirects', async () => {
+ const { page, server } = getTestState();
+
+ const events = [];
+ page.on('request', (request) =>
+ events.push(`${request.method()} ${request.url()}`)
+ );
+ page.on('response', (response) =>
+ events.push(`${response.status()} ${response.url()}`)
+ );
+ page.on('requestfinished', (request) =>
+ events.push(`DONE ${request.url()}`)
+ );
+ page.on('requestfailed', (request) =>
+ events.push(`FAIL ${request.url()}`)
+ );
+ server.setRedirect('/foo.html', '/empty.html');
+ const FOO_URL = server.PREFIX + '/foo.html';
+ const response = await page.goto(FOO_URL);
+ expect(events).toEqual([
+ `GET ${FOO_URL}`,
+ `302 ${FOO_URL}`,
+ `DONE ${FOO_URL}`,
+ `GET ${server.EMPTY_PAGE}`,
+ `200 ${server.EMPTY_PAGE}`,
+ `DONE ${server.EMPTY_PAGE}`,
+ ]);
+
+ // Check redirect chain
+ const redirectChain = response.request().redirectChain();
+ expect(redirectChain.length).toBe(1);
+ expect(redirectChain[0].url()).toContain('/foo.html');
+ expect(redirectChain[0].response().remoteAddress().port).toBe(
+ server.PORT
+ );
+ });
+ });
+
+ describe('Request.isNavigationRequest', () => {
+ itFailsFirefox('should work', async () => {
+ const { page, server } = getTestState();
+
+ const requests = new Map();
+ page.on('request', (request) =>
+ requests.set(request.url().split('/').pop(), request)
+ );
+ server.setRedirect('/rrredirect', '/frames/one-frame.html');
+ await page.goto(server.PREFIX + '/rrredirect');
+ expect(requests.get('rrredirect').isNavigationRequest()).toBe(true);
+ expect(requests.get('one-frame.html').isNavigationRequest()).toBe(true);
+ expect(requests.get('frame.html').isNavigationRequest()).toBe(true);
+ expect(requests.get('script.js').isNavigationRequest()).toBe(false);
+ expect(requests.get('style.css').isNavigationRequest()).toBe(false);
+ });
+ itFailsFirefox('should work with request interception', async () => {
+ const { page, server } = getTestState();
+
+ const requests = new Map();
+ page.on('request', (request) => {
+ requests.set(request.url().split('/').pop(), request);
+ request.continue();
+ });
+ await page.setRequestInterception(true);
+ server.setRedirect('/rrredirect', '/frames/one-frame.html');
+ await page.goto(server.PREFIX + '/rrredirect');
+ expect(requests.get('rrredirect').isNavigationRequest()).toBe(true);
+ expect(requests.get('one-frame.html').isNavigationRequest()).toBe(true);
+ expect(requests.get('frame.html').isNavigationRequest()).toBe(true);
+ expect(requests.get('script.js').isNavigationRequest()).toBe(false);
+ expect(requests.get('style.css').isNavigationRequest()).toBe(false);
+ });
+ itFailsFirefox('should work when navigating to image', async () => {
+ const { page, server } = getTestState();
+
+ const requests = [];
+ page.on('request', (request) => requests.push(request));
+ await page.goto(server.PREFIX + '/pptr.png');
+ expect(requests[0].isNavigationRequest()).toBe(true);
+ });
+ });
+
+ describeFailsFirefox('Page.setExtraHTTPHeaders', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.setExtraHTTPHeaders({
+ foo: 'bar',
+ });
+ const [request] = await Promise.all([
+ server.waitForRequest('/empty.html'),
+ page.goto(server.EMPTY_PAGE),
+ ]);
+ expect(request.headers['foo']).toBe('bar');
+ });
+ it('should throw for non-string header values', async () => {
+ const { page } = getTestState();
+
+ let error = null;
+ try {
+ // @ts-expect-error purposeful bad input
+ await page.setExtraHTTPHeaders({ foo: 1 });
+ } catch (error_) {
+ error = error_;
+ }
+ expect(error.message).toBe(
+ 'Expected value of header "foo" to be String, but "number" is found.'
+ );
+ });
+ });
+
+ describeFailsFirefox('Page.authenticate', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ server.setAuth('/empty.html', 'user', 'pass');
+ let response;
+ try {
+ response = await page.goto(server.EMPTY_PAGE);
+ expect(response.status()).toBe(401);
+ } catch (error) {
+ // In headful, an error is thrown instead of 401.
+ if (!error.message.startsWith('net::ERR_INVALID_AUTH_CREDENTIALS')) {
+ throw error;
+ }
+ }
+ await page.authenticate({
+ username: 'user',
+ password: 'pass',
+ });
+ response = await page.reload();
+ expect(response.status()).toBe(200);
+ });
+ it('should fail if wrong credentials', async () => {
+ const { page, server } = getTestState();
+
+ // Use unique user/password since Chrome caches credentials per origin.
+ server.setAuth('/empty.html', 'user2', 'pass2');
+ await page.authenticate({
+ username: 'foo',
+ password: 'bar',
+ });
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.status()).toBe(401);
+ });
+ it('should allow disable authentication', async () => {
+ const { page, server } = getTestState();
+
+ // Use unique user/password since Chrome caches credentials per origin.
+ server.setAuth('/empty.html', 'user3', 'pass3');
+ await page.authenticate({
+ username: 'user3',
+ password: 'pass3',
+ });
+ let response = await page.goto(server.EMPTY_PAGE);
+ expect(response.status()).toBe(200);
+ await page.authenticate(null);
+ // Navigate to a different origin to bust Chrome's credential caching.
+ try {
+ response = await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
+ expect(response.status()).toBe(401);
+ } catch (error) {
+ // In headful, an error is thrown instead of 401.
+ if (!error.message.startsWith('net::ERR_INVALID_AUTH_CREDENTIALS')) {
+ throw error;
+ }
+ }
+ });
+ it('should not disable caching', async () => {
+ const { page, server } = getTestState();
+
+ // Use unique user/password since Chrome caches credentials per origin.
+ server.setAuth('/cached/one-style.css', 'user4', 'pass4');
+ server.setAuth('/cached/one-style.html', 'user4', 'pass4');
+ await page.authenticate({
+ username: 'user4',
+ password: 'pass4',
+ });
+
+ const responses = new Map();
+ page.on('response', (r) => responses.set(r.url().split('/').pop(), r));
+
+ // Load and re-load to make sure it's cached.
+ await page.goto(server.PREFIX + '/cached/one-style.html');
+ await page.reload();
+
+ expect(responses.get('one-style.css').status()).toBe(200);
+ expect(responses.get('one-style.css').fromCache()).toBe(true);
+ expect(responses.get('one-style.html').status()).toBe(304);
+ expect(responses.get('one-style.html').fromCache()).toBe(false);
+ });
+ });
+
+ describeFailsFirefox('raw network headers', async () => {
+ it('Same-origin set-cookie navigation', async () => {
+ const { page, server } = getTestState();
+
+ const setCookieString = 'foo=bar';
+ server.setRoute('/empty.html', (req, res) => {
+ res.setHeader('set-cookie', setCookieString);
+ res.end('hello world');
+ });
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.headers()['set-cookie']).toBe(setCookieString);
+ });
+
+ it('Same-origin set-cookie subresource', async () => {
+ const { page, server } = getTestState();
+ await page.goto(server.EMPTY_PAGE);
+
+ const setCookieString = 'foo=bar';
+ server.setRoute('/foo', (req, res) => {
+ res.setHeader('set-cookie', setCookieString);
+ res.end('hello world');
+ });
+
+ const responsePromise = new Promise((resolve) =>
+ page.on('response', (response) => resolve(response))
+ );
+ page.evaluate(() => {
+ const xhr = new XMLHttpRequest();
+ xhr.open('GET', '/foo');
+ xhr.send();
+ });
+ const subresourceResponse = await responsePromise;
+ expect(subresourceResponse.headers()['set-cookie']).toBe(setCookieString);
+ });
+
+ it('Cross-origin set-cookie', async () => {
+ const { httpsServer, puppeteer, defaultBrowserOptions } = getTestState();
+
+ const browser = await puppeteer.launch({
+ ...defaultBrowserOptions,
+ ignoreHTTPSErrors: true,
+ });
+
+ const page = await browser.newPage();
+
+ try {
+ await page.goto(httpsServer.PREFIX + '/empty.html');
+
+ const setCookieString = 'hello=world';
+ httpsServer.setRoute('/setcookie.html', (req, res) => {
+ res.setHeader('Access-Control-Allow-Origin', '*');
+ res.setHeader('set-cookie', setCookieString);
+ res.end();
+ });
+ await page.goto(httpsServer.PREFIX + '/setcookie.html');
+
+ const response = await new Promise((resolve) => {
+ page.on('response', resolve);
+ const url = httpsServer.CROSS_PROCESS_PREFIX + '/setcookie.html';
+ page.evaluate<(src: string) => void>((src) => {
+ const xhr = new XMLHttpRequest();
+ xhr.open('GET', src);
+ xhr.send();
+ }, url);
+ });
+ expect(response.headers()['set-cookie']).toBe(setCookieString);
+ } finally {
+ await page.close();
+ await browser.close();
+ }
+ });
+ });
+});
diff --git a/test/oopif.spec.ts b/test/oopif.spec.ts
new file mode 100644
index 0000000000000..2c7d9baca92e3
--- /dev/null
+++ b/test/oopif.spec.ts
@@ -0,0 +1,429 @@
+/**
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import utils from './utils.js';
+import expect from 'expect';
+import {
+ getTestState,
+ describeChromeOnly,
+ itFailsFirefox,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+
+describeChromeOnly('OOPIF', function () {
+ /* We use a special browser for this test as we need the --site-per-process flag */
+ let browser;
+ let context;
+ let page;
+
+ before(async () => {
+ const { puppeteer, defaultBrowserOptions } = getTestState();
+ browser = await puppeteer.launch(
+ Object.assign({}, defaultBrowserOptions, {
+ args: (defaultBrowserOptions.args || []).concat([
+ '--site-per-process',
+ '--remote-debugging-port=21222',
+ '--host-rules=MAP * 127.0.0.1',
+ ]),
+ })
+ );
+ });
+
+ beforeEach(async () => {
+ context = await browser.createIncognitoBrowserContext();
+ page = await context.newPage();
+ });
+
+ afterEach(async () => {
+ await context.close();
+ page = null;
+ context = null;
+ });
+
+ after(async () => {
+ await browser.close();
+ browser = null;
+ });
+ it('should treat OOP iframes and normal iframes the same', async () => {
+ const { server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const framePromise = page.waitForFrame((frame) =>
+ frame.url().endsWith('/empty.html')
+ );
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ await utils.attachFrame(
+ page,
+ 'frame2',
+ server.CROSS_PROCESS_PREFIX + '/empty.html'
+ );
+ await framePromise;
+ expect(page.mainFrame().childFrames()).toHaveLength(2);
+ });
+ it('should track navigations within OOP iframes', async () => {
+ const { server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const framePromise = page.waitForFrame((frame) => {
+ return page.frames().indexOf(frame) === 1;
+ });
+ await utils.attachFrame(
+ page,
+ 'frame1',
+ server.CROSS_PROCESS_PREFIX + '/empty.html'
+ );
+ const frame = await framePromise;
+ expect(frame.url()).toContain('/empty.html');
+ await utils.navigateFrame(
+ page,
+ 'frame1',
+ server.CROSS_PROCESS_PREFIX + '/assets/frame.html'
+ );
+ expect(frame.url()).toContain('/assets/frame.html');
+ });
+ it('should support OOP iframes becoming normal iframes again', async () => {
+ const { server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const framePromise = page.waitForFrame((frame) => {
+ return page.frames().indexOf(frame) === 1;
+ });
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+
+ const frame = await framePromise;
+ expect(frame.isOOPFrame()).toBe(false);
+ await utils.navigateFrame(
+ page,
+ 'frame1',
+ server.CROSS_PROCESS_PREFIX + '/empty.html'
+ );
+ expect(frame.isOOPFrame()).toBe(true);
+ await utils.navigateFrame(page, 'frame1', server.EMPTY_PAGE);
+ expect(frame.isOOPFrame()).toBe(false);
+ expect(page.frames()).toHaveLength(2);
+ });
+ it('should support frames within OOP frames', async () => {
+ const { server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const frame1Promise = page.waitForFrame((frame) => {
+ return page.frames().indexOf(frame) === 1;
+ });
+ const frame2Promise = page.waitForFrame((frame) => {
+ return page.frames().indexOf(frame) === 2;
+ });
+ await utils.attachFrame(
+ page,
+ 'frame1',
+ server.CROSS_PROCESS_PREFIX + '/frames/one-frame.html'
+ );
+
+ const [frame1, frame2] = await Promise.all([frame1Promise, frame2Promise]);
+
+ expect(await frame1.evaluate(() => document.location.href)).toMatch(
+ /one-frame\.html$/
+ );
+ expect(await frame2.evaluate(() => document.location.href)).toMatch(
+ /frames\/frame\.html$/
+ );
+ });
+ it('should support OOP iframes getting detached', async () => {
+ const { server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const framePromise = page.waitForFrame((frame) => {
+ return page.frames().indexOf(frame) === 1;
+ });
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+
+ const frame = await framePromise;
+ expect(frame.isOOPFrame()).toBe(false);
+ await utils.navigateFrame(
+ page,
+ 'frame1',
+ server.CROSS_PROCESS_PREFIX + '/empty.html'
+ );
+ expect(frame.isOOPFrame()).toBe(true);
+ await utils.detachFrame(page, 'frame1');
+ expect(page.frames()).toHaveLength(1);
+ });
+
+ it('should support wait for navigation for transitions from local to OOPIF', async () => {
+ const { server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const framePromise = page.waitForFrame((frame) => {
+ return page.frames().indexOf(frame) === 1;
+ });
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+
+ const frame = await framePromise;
+ expect(frame.isOOPFrame()).toBe(false);
+ const nav = frame.waitForNavigation();
+ await utils.navigateFrame(
+ page,
+ 'frame1',
+ server.CROSS_PROCESS_PREFIX + '/empty.html'
+ );
+ await nav;
+ expect(frame.isOOPFrame()).toBe(true);
+ await utils.detachFrame(page, 'frame1');
+ expect(page.frames()).toHaveLength(1);
+ });
+
+ it('should keep track of a frames OOP state', async () => {
+ const { server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const framePromise = page.waitForFrame((frame) => {
+ return page.frames().indexOf(frame) === 1;
+ });
+ await utils.attachFrame(
+ page,
+ 'frame1',
+ server.CROSS_PROCESS_PREFIX + '/empty.html'
+ );
+ const frame = await framePromise;
+ expect(frame.url()).toContain('/empty.html');
+ await utils.navigateFrame(page, 'frame1', server.EMPTY_PAGE);
+ expect(frame.url()).toBe(server.EMPTY_PAGE);
+ });
+ it('should support evaluating in oop iframes', async () => {
+ const { server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const framePromise = page.waitForFrame((frame) => {
+ return page.frames().indexOf(frame) === 1;
+ });
+ await utils.attachFrame(
+ page,
+ 'frame1',
+ server.CROSS_PROCESS_PREFIX + '/empty.html'
+ );
+ const frame = await framePromise;
+ await frame.evaluate(() => {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ _test = 'Test 123!';
+ });
+ const result = await frame.evaluate(() => {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ return window._test;
+ });
+ expect(result).toBe('Test 123!');
+ });
+ it('should provide access to elements', async () => {
+ const { server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const framePromise = page.waitForFrame((frame) => {
+ return page.frames().indexOf(frame) === 1;
+ });
+ await utils.attachFrame(
+ page,
+ 'frame1',
+ server.CROSS_PROCESS_PREFIX + '/empty.html'
+ );
+
+ const frame = await framePromise;
+ await frame.evaluate(() => {
+ const button = document.createElement('button');
+ button.id = 'test-button';
+ button.innerText = 'click';
+ button.onclick = () => {
+ button.id = 'clicked';
+ };
+ document.body.appendChild(button);
+ });
+ await page.evaluate(() => {
+ document.body.style.border = '150px solid black';
+ document.body.style.margin = '250px';
+ document.body.style.padding = '50px';
+ });
+ await frame.waitForSelector('#test-button', { visible: true });
+ await frame.click('#test-button');
+ await frame.waitForSelector('#clicked');
+ });
+ it('should report oopif frames', async () => {
+ const { server } = getTestState();
+
+ const frame = page.waitForFrame((frame) =>
+ frame.url().endsWith('/oopif.html')
+ );
+ await page.goto(server.PREFIX + '/dynamic-oopif.html');
+ await frame;
+ expect(oopifs(context).length).toBe(1);
+ expect(page.frames().length).toBe(2);
+ });
+
+ it('should wait for inner OOPIFs', async () => {
+ const { server } = getTestState();
+ await page.goto(`http://mainframe:${server.PORT}/main-frame.html`);
+ const frame2 = await page.waitForFrame((frame) =>
+ frame.url().endsWith('inner-frame2.html')
+ );
+ expect(oopifs(context).length).toBe(2);
+ expect(page.frames().filter((frame) => frame.isOOPFrame()).length).toBe(2);
+ expect(
+ await frame2.evaluate(() => document.querySelectorAll('button').length)
+ ).toStrictEqual(1);
+ });
+
+ it('should load oopif iframes with subresources and request interception', async () => {
+ const { server } = getTestState();
+
+ const frame = page.waitForFrame((frame) =>
+ frame.url().endsWith('/oopif.html')
+ );
+ await page.setRequestInterception(true);
+ page.on('request', (request) => request.continue());
+ await page.goto(server.PREFIX + '/dynamic-oopif.html');
+ await frame;
+ expect(oopifs(context).length).toBe(1);
+ });
+ it('should support frames within OOP iframes', async () => {
+ const { server } = getTestState();
+
+ const oopIframePromise = page.waitForFrame((frame) => {
+ return frame.url().endsWith('/oopif.html');
+ });
+ await page.goto(server.PREFIX + '/dynamic-oopif.html');
+ const oopIframe = await oopIframePromise;
+ await utils.attachFrame(
+ oopIframe,
+ 'frame1',
+ server.CROSS_PROCESS_PREFIX + '/empty.html'
+ );
+
+ const frame1 = oopIframe.childFrames()[0];
+ expect(frame1.url()).toMatch(/empty.html$/);
+ await utils.navigateFrame(
+ oopIframe,
+ 'frame1',
+ server.CROSS_PROCESS_PREFIX + '/oopif.html'
+ );
+ expect(frame1.url()).toMatch(/oopif.html$/);
+ await frame1.goto(
+ server.CROSS_PROCESS_PREFIX + '/oopif.html#navigate-within-document',
+ { waitUntil: 'load' }
+ );
+ expect(frame1.url()).toMatch(/oopif.html#navigate-within-document$/);
+ await utils.detachFrame(oopIframe, 'frame1');
+ expect(oopIframe.childFrames()).toHaveLength(0);
+ });
+
+ it('clickablePoint, boundingBox, boxModel should work for elements inside OOPIFs', async () => {
+ const { server } = getTestState();
+ await page.goto(server.EMPTY_PAGE);
+ const framePromise = page.waitForFrame((frame) => {
+ return page.frames().indexOf(frame) === 1;
+ });
+ await utils.attachFrame(
+ page,
+ 'frame1',
+ server.CROSS_PROCESS_PREFIX + '/empty.html'
+ );
+ const frame = await framePromise;
+ await page.evaluate(() => {
+ document.body.style.border = '50px solid black';
+ document.body.style.margin = '50px';
+ document.body.style.padding = '50px';
+ });
+ await frame.evaluate(() => {
+ const button = document.createElement('button');
+ button.id = 'test-button';
+ button.innerText = 'click';
+ document.body.appendChild(button);
+ });
+ const button = await frame.waitForSelector('#test-button', {
+ visible: true,
+ });
+ const result = await button.clickablePoint();
+ expect(result.x).toBeGreaterThan(150); // padding + margin + border left
+ expect(result.y).toBeGreaterThan(150); // padding + margin + border top
+ const resultBoxModel = await button.boxModel();
+ for (const quad of [
+ resultBoxModel.content,
+ resultBoxModel.border,
+ resultBoxModel.margin,
+ resultBoxModel.padding,
+ ]) {
+ for (const part of quad) {
+ expect(part.x).toBeGreaterThan(150); // padding + margin + border left
+ expect(part.y).toBeGreaterThan(150); // padding + margin + border top
+ }
+ }
+ const resultBoundingBox = await button.boundingBox();
+ expect(resultBoundingBox.x).toBeGreaterThan(150); // padding + margin + border left
+ expect(resultBoundingBox.y).toBeGreaterThan(150); // padding + margin + border top
+ });
+
+ it('should detect existing OOPIFs when Puppeteer connects to an existing page', async () => {
+ const { server, puppeteer } = getTestState();
+
+ const frame = page.waitForFrame((frame) =>
+ frame.url().endsWith('/oopif.html')
+ );
+ await page.goto(server.PREFIX + '/dynamic-oopif.html');
+ await frame;
+ expect(oopifs(context).length).toBe(1);
+ expect(page.frames().length).toBe(2);
+
+ const browserURL = 'http://127.0.0.1:21222';
+ const browser1 = await puppeteer.connect({ browserURL });
+ const target = await browser1.waitForTarget((target) =>
+ target.url().endsWith('dynamic-oopif.html')
+ );
+ await target.page();
+ browser1.disconnect();
+ });
+ itFailsFirefox('should support lazy OOP frames', async () => {
+ const { server } = getTestState();
+
+ await page.goto(server.PREFIX + '/lazy-oopif-frame.html');
+ await page.setViewport({ width: 1000, height: 1000 });
+
+ expect(page.frames().map((frame) => frame._hasStartedLoading)).toEqual([
+ true,
+ true,
+ false,
+ ]);
+ });
+
+ describe('waitForFrame', () => {
+ it('should resolve immediately if the frame already exists', async () => {
+ const { server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await utils.attachFrame(
+ page,
+ 'frame2',
+ server.CROSS_PROCESS_PREFIX + '/empty.html'
+ );
+
+ await page.waitForFrame((frame) => frame.url().endsWith('/empty.html'));
+ });
+ });
+});
+
+/**
+ * @param {!BrowserContext} context
+ */
+function oopifs(context) {
+ return context
+ .targets()
+ .filter((target) => target._targetInfo.type === 'iframe');
+}
diff --git a/test/page.spec.ts b/test/page.spec.ts
new file mode 100644
index 0000000000000..9c31f5b7a4fe2
--- /dev/null
+++ b/test/page.spec.ts
@@ -0,0 +1,1976 @@
+/**
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import fs from 'fs';
+import path from 'path';
+import utils from './utils.js';
+const { waitEvent } = utils;
+import expect from 'expect';
+import sinon from 'sinon';
+import {
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+ itFailsFirefox,
+ describeFailsFirefox,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+import { Page, Metrics } from '../lib/cjs/puppeteer/common/Page.js';
+import { CDPSession } from '../lib/cjs/puppeteer/common/Connection.js';
+import { JSHandle } from '../lib/cjs/puppeteer/common/JSHandle.js';
+
+describe('Page', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+ describe('Page.close', function () {
+ it('should reject all promises when page is closed', async () => {
+ const { context } = getTestState();
+
+ const newPage = await context.newPage();
+ let error = null;
+ await Promise.all([
+ newPage
+ .evaluate(() => new Promise(() => {}))
+ .catch((error_) => (error = error_)),
+ newPage.close(),
+ ]);
+ expect(error.message).toContain('Protocol error');
+ });
+ it('should not be visible in browser.pages', async () => {
+ const { browser } = getTestState();
+
+ const newPage = await browser.newPage();
+ expect(await browser.pages()).toContain(newPage);
+ await newPage.close();
+ expect(await browser.pages()).not.toContain(newPage);
+ });
+ itFailsFirefox('should run beforeunload if asked for', async () => {
+ const { context, server, isChrome } = getTestState();
+
+ const newPage = await context.newPage();
+ await newPage.goto(server.PREFIX + '/beforeunload.html');
+ // We have to interact with a page so that 'beforeunload' handlers
+ // fire.
+ await newPage.click('body');
+ const pageClosingPromise = newPage.close({ runBeforeUnload: true });
+ const dialog = await waitEvent(newPage, 'dialog');
+ expect(dialog.type()).toBe('beforeunload');
+ expect(dialog.defaultValue()).toBe('');
+ if (isChrome) expect(dialog.message()).toBe('');
+ else expect(dialog.message()).toBeTruthy();
+ await dialog.accept();
+ await pageClosingPromise;
+ });
+ itFailsFirefox('should *not* run beforeunload by default', async () => {
+ const { context, server } = getTestState();
+
+ const newPage = await context.newPage();
+ await newPage.goto(server.PREFIX + '/beforeunload.html');
+ // We have to interact with a page so that 'beforeunload' handlers
+ // fire.
+ await newPage.click('body');
+ await newPage.close();
+ });
+ it('should set the page close state', async () => {
+ const { context } = getTestState();
+
+ const newPage = await context.newPage();
+ expect(newPage.isClosed()).toBe(false);
+ await newPage.close();
+ expect(newPage.isClosed()).toBe(true);
+ });
+ itFailsFirefox('should terminate network waiters', async () => {
+ const { context, server } = getTestState();
+
+ const newPage = await context.newPage();
+ const results = await Promise.all([
+ newPage.waitForRequest(server.EMPTY_PAGE).catch((error) => error),
+ newPage.waitForResponse(server.EMPTY_PAGE).catch((error) => error),
+ newPage.close(),
+ ]);
+ for (let i = 0; i < 2; i++) {
+ const message = results[i].message;
+ expect(message).toContain('Target closed');
+ expect(message).not.toContain('Timeout');
+ }
+ });
+ });
+
+ describe('Page.Events.Load', function () {
+ it('should fire when expected', async () => {
+ const { page } = getTestState();
+
+ await Promise.all([
+ page.goto('about:blank'),
+ utils.waitEvent(page, 'load'),
+ ]);
+ });
+ });
+
+ describe('removing and adding event handlers', () => {
+ it('should correctly fire event handlers as they are added and then removed', async () => {
+ const { page, server } = getTestState();
+
+ const handler = sinon.spy();
+ const onResponse = (response) => {
+ // Ignore default favicon requests.
+ if (!response.url().endsWith('favicon.ico')) {
+ handler();
+ }
+ };
+ page.on('response', onResponse);
+ await page.goto(server.EMPTY_PAGE);
+ expect(handler.callCount).toBe(1);
+ page.off('response', onResponse);
+ await page.goto(server.EMPTY_PAGE);
+ // Still one because we removed the handler.
+ expect(handler.callCount).toBe(1);
+ page.on('response', onResponse);
+ await page.goto(server.EMPTY_PAGE);
+ // Two now because we added the handler back.
+ expect(handler.callCount).toBe(2);
+ });
+
+ it('should correctly added and removed request events', async () => {
+ const { page, server } = getTestState();
+
+ const handler = sinon.spy();
+ const onResponse = (response) => {
+ // Ignore default favicon requests.
+ if (!response.url().endsWith('favicon.ico')) {
+ handler();
+ }
+ };
+
+ page.on('request', onResponse);
+ page.on('request', onResponse);
+ await page.goto(server.EMPTY_PAGE);
+ expect(handler.callCount).toBe(2);
+ page.off('request', onResponse);
+ await page.goto(server.EMPTY_PAGE);
+ // Still one because we removed the handler.
+ expect(handler.callCount).toBe(3);
+ page.off('request', onResponse);
+ await page.goto(server.EMPTY_PAGE);
+ expect(handler.callCount).toBe(3);
+ page.on('request', onResponse);
+ await page.goto(server.EMPTY_PAGE);
+ // Two now because we added the handler back.
+ expect(handler.callCount).toBe(4);
+ });
+ });
+
+ describeFailsFirefox('Page.Events.error', function () {
+ it('should throw when page crashes', async () => {
+ const { page } = getTestState();
+
+ let error = null;
+ page.on('error', (err) => (error = err));
+ page.goto('chrome://crash').catch(() => {});
+ await waitEvent(page, 'error');
+ expect(error.message).toBe('Page crashed!');
+ });
+ });
+
+ describeFailsFirefox('Page.Events.Popup', function () {
+ it('should work', async () => {
+ const { page } = getTestState();
+
+ const [popup] = await Promise.all([
+ new Promise((x) => page.once('popup', x)),
+ page.evaluate(() => window.open('about:blank')),
+ ]);
+ expect(await page.evaluate(() => !!window.opener)).toBe(false);
+ expect(await popup.evaluate(() => !!window.opener)).toBe(true);
+ });
+ it('should work with noopener', async () => {
+ const { page } = getTestState();
+
+ const [popup] = await Promise.all([
+ new Promise((x) => page.once('popup', x)),
+ page.evaluate(() => window.open('about:blank', null, 'noopener')),
+ ]);
+ expect(await page.evaluate(() => !!window.opener)).toBe(false);
+ expect(await popup.evaluate(() => !!window.opener)).toBe(false);
+ });
+ it('should work with clicking target=_blank and without rel=opener', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.setContent('yo ');
+ const [popup] = await Promise.all([
+ new Promise((x) => page.once('popup', x)),
+ page.click('a'),
+ ]);
+ expect(await page.evaluate(() => !!window.opener)).toBe(false);
+ expect(await popup.evaluate(() => !!window.opener)).toBe(false);
+ });
+ it('should work with clicking target=_blank and with rel=opener', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.setContent(
+ 'yo '
+ );
+ const [popup] = await Promise.all([
+ new Promise((x) => page.once('popup', x)),
+ page.click('a'),
+ ]);
+ expect(await page.evaluate(() => !!window.opener)).toBe(false);
+ expect(await popup.evaluate(() => !!window.opener)).toBe(true);
+ });
+ it('should work with fake-clicking target=_blank and rel=noopener', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.setContent(
+ 'yo '
+ );
+ const [popup] = await Promise.all([
+ new Promise((x) => page.once('popup', x)),
+ page.$eval('a', (a: HTMLAnchorElement) => a.click()),
+ ]);
+ expect(await page.evaluate(() => !!window.opener)).toBe(false);
+ expect(await popup.evaluate(() => !!window.opener)).toBe(false);
+ });
+ it('should work with clicking target=_blank and rel=noopener', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.setContent(
+ 'yo '
+ );
+ const [popup] = await Promise.all([
+ new Promise((x) => page.once('popup', x)),
+ page.click('a'),
+ ]);
+ expect(await page.evaluate(() => !!window.opener)).toBe(false);
+ expect(await popup.evaluate(() => !!window.opener)).toBe(false);
+ });
+ });
+
+ describe('BrowserContext.overridePermissions', function () {
+ function getPermission(page, name) {
+ return page.evaluate(
+ (name) =>
+ navigator.permissions.query({ name }).then((result) => result.state),
+ name
+ );
+ }
+
+ it('should be prompt by default', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ expect(await getPermission(page, 'geolocation')).toBe('prompt');
+ });
+ itFailsFirefox('should deny permission when not listed', async () => {
+ const { page, server, context } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await context.overridePermissions(server.EMPTY_PAGE, []);
+ expect(await getPermission(page, 'geolocation')).toBe('denied');
+ });
+ it('should fail when bad permission is given', async () => {
+ const { page, server, context } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ let error = null;
+ await context
+ // @ts-expect-error purposeful bad input for test
+ .overridePermissions(server.EMPTY_PAGE, ['foo'])
+ .catch((error_) => (error = error_));
+ expect(error.message).toBe('Unknown permission: foo');
+ });
+ itFailsFirefox('should grant permission when listed', async () => {
+ const { page, server, context } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await context.overridePermissions(server.EMPTY_PAGE, ['geolocation']);
+ expect(await getPermission(page, 'geolocation')).toBe('granted');
+ });
+ itFailsFirefox('should reset permissions', async () => {
+ const { page, server, context } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await context.overridePermissions(server.EMPTY_PAGE, ['geolocation']);
+ expect(await getPermission(page, 'geolocation')).toBe('granted');
+ await context.clearPermissionOverrides();
+ expect(await getPermission(page, 'geolocation')).toBe('prompt');
+ });
+ itFailsFirefox('should trigger permission onchange', async () => {
+ const { page, server, context, isHeadless } = getTestState();
+
+ // TODO: re-enable this test in headful once crbug.com/1324480 rolls out.
+ if (!isHeadless) return;
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.evaluate(() => {
+ globalThis.events = [];
+ return navigator.permissions
+ .query({ name: 'geolocation' })
+ .then(function (result) {
+ globalThis.events.push(result.state);
+ result.onchange = function () {
+ globalThis.events.push(result.state);
+ };
+ });
+ });
+ expect(await page.evaluate(() => globalThis.events)).toEqual(['prompt']);
+ await context.overridePermissions(server.EMPTY_PAGE, []);
+ expect(await page.evaluate(() => globalThis.events)).toEqual([
+ 'prompt',
+ 'denied',
+ ]);
+ await context.overridePermissions(server.EMPTY_PAGE, ['geolocation']);
+ expect(await page.evaluate(() => globalThis.events)).toEqual([
+ 'prompt',
+ 'denied',
+ 'granted',
+ ]);
+ await context.clearPermissionOverrides();
+ expect(await page.evaluate(() => globalThis.events)).toEqual([
+ 'prompt',
+ 'denied',
+ 'granted',
+ 'prompt',
+ ]);
+ });
+ itFailsFirefox(
+ 'should isolate permissions between browser contexts',
+ async () => {
+ const { page, server, context, browser } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const otherContext = await browser.createIncognitoBrowserContext();
+ const otherPage = await otherContext.newPage();
+ await otherPage.goto(server.EMPTY_PAGE);
+ expect(await getPermission(page, 'geolocation')).toBe('prompt');
+ expect(await getPermission(otherPage, 'geolocation')).toBe('prompt');
+
+ await context.overridePermissions(server.EMPTY_PAGE, []);
+ await otherContext.overridePermissions(server.EMPTY_PAGE, [
+ 'geolocation',
+ ]);
+ expect(await getPermission(page, 'geolocation')).toBe('denied');
+ expect(await getPermission(otherPage, 'geolocation')).toBe('granted');
+
+ await context.clearPermissionOverrides();
+ expect(await getPermission(page, 'geolocation')).toBe('prompt');
+ expect(await getPermission(otherPage, 'geolocation')).toBe('granted');
+
+ await otherContext.close();
+ }
+ );
+ itFailsFirefox('should grant persistent-storage', async () => {
+ const { page, server, context } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ expect(await getPermission(page, 'persistent-storage')).not.toBe(
+ 'granted'
+ );
+ await context.overridePermissions(server.EMPTY_PAGE, [
+ 'persistent-storage',
+ ]);
+ expect(await getPermission(page, 'persistent-storage')).toBe('granted');
+ });
+ });
+
+ describe('Page.setGeolocation', function () {
+ itFailsFirefox('should work', async () => {
+ const { page, server, context } = getTestState();
+
+ await context.overridePermissions(server.PREFIX, ['geolocation']);
+ await page.goto(server.EMPTY_PAGE);
+ await page.setGeolocation({ longitude: 10, latitude: 10 });
+ const geolocation = await page.evaluate(
+ () =>
+ new Promise((resolve) =>
+ navigator.geolocation.getCurrentPosition((position) => {
+ resolve({
+ latitude: position.coords.latitude,
+ longitude: position.coords.longitude,
+ });
+ })
+ )
+ );
+ expect(geolocation).toEqual({
+ latitude: 10,
+ longitude: 10,
+ });
+ });
+ it('should throw when invalid longitude', async () => {
+ const { page } = getTestState();
+
+ let error = null;
+ try {
+ await page.setGeolocation({ longitude: 200, latitude: 10 });
+ } catch (error_) {
+ error = error_;
+ }
+ expect(error.message).toContain('Invalid longitude "200"');
+ });
+ });
+
+ describeFailsFirefox('Page.setOfflineMode', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.setOfflineMode(true);
+ let error = null;
+ await page.goto(server.EMPTY_PAGE).catch((error_) => (error = error_));
+ expect(error).toBeTruthy();
+ await page.setOfflineMode(false);
+ const response = await page.reload();
+ expect(response.status()).toBe(200);
+ });
+ it('should emulate navigator.onLine', async () => {
+ const { page } = getTestState();
+
+ expect(await page.evaluate(() => window.navigator.onLine)).toBe(true);
+ await page.setOfflineMode(true);
+ expect(await page.evaluate(() => window.navigator.onLine)).toBe(false);
+ await page.setOfflineMode(false);
+ expect(await page.evaluate(() => window.navigator.onLine)).toBe(true);
+ });
+ });
+
+ describe('ExecutionContext.queryObjects', function () {
+ itFailsFirefox('should work', async () => {
+ const { page } = getTestState();
+
+ // Instantiate an object
+ await page.evaluate(() => (globalThis.set = new Set(['hello', 'world'])));
+ const prototypeHandle = await page.evaluateHandle(() => Set.prototype);
+ const objectsHandle = await page.queryObjects(prototypeHandle);
+ const count = await page.evaluate(
+ (objects: JSHandle[]) => objects.length,
+ objectsHandle
+ );
+ expect(count).toBe(1);
+ const values = await page.evaluate(
+ (objects) => Array.from(objects[0].values()),
+ objectsHandle
+ );
+ expect(values).toEqual(['hello', 'world']);
+ });
+ itFailsFirefox('should work for non-blank page', async () => {
+ const { page, server } = getTestState();
+
+ // Instantiate an object
+ await page.goto(server.EMPTY_PAGE);
+ await page.evaluate(() => (globalThis.set = new Set(['hello', 'world'])));
+ const prototypeHandle = await page.evaluateHandle(() => Set.prototype);
+ const objectsHandle = await page.queryObjects(prototypeHandle);
+ const count = await page.evaluate(
+ (objects: JSHandle[]) => objects.length,
+ objectsHandle
+ );
+ expect(count).toBe(1);
+ });
+ it('should fail for disposed handles', async () => {
+ const { page } = getTestState();
+
+ const prototypeHandle = await page.evaluateHandle(
+ () => HTMLBodyElement.prototype
+ );
+ await prototypeHandle.dispose();
+ let error = null;
+ await page
+ .queryObjects(prototypeHandle)
+ .catch((error_) => (error = error_));
+ expect(error.message).toBe('Prototype JSHandle is disposed!');
+ });
+ it('should fail primitive values as prototypes', async () => {
+ const { page } = getTestState();
+
+ const prototypeHandle = await page.evaluateHandle(() => 42);
+ let error = null;
+ await page
+ .queryObjects(prototypeHandle)
+ .catch((error_) => (error = error_));
+ expect(error.message).toBe(
+ 'Prototype JSHandle must not be referencing primitive value'
+ );
+ });
+ });
+
+ describeFailsFirefox('Page.Events.Console', function () {
+ it('should work', async () => {
+ const { page } = getTestState();
+
+ let message = null;
+ page.once('console', (m) => (message = m));
+ await Promise.all([
+ page.evaluate(() => console.log('hello', 5, { foo: 'bar' })),
+ waitEvent(page, 'console'),
+ ]);
+ expect(message.text()).toEqual('hello 5 JSHandle@object');
+ expect(message.type()).toEqual('log');
+ expect(message.args()).toHaveLength(3);
+ expect(message.location()).toEqual({
+ url: expect.any(String),
+ lineNumber: expect.any(Number),
+ columnNumber: expect.any(Number),
+ });
+
+ expect(await message.args()[0].jsonValue()).toEqual('hello');
+ expect(await message.args()[1].jsonValue()).toEqual(5);
+ expect(await message.args()[2].jsonValue()).toEqual({ foo: 'bar' });
+ });
+ it('should work for different console API calls', async () => {
+ const { page } = getTestState();
+
+ const messages = [];
+ page.on('console', (msg) => messages.push(msg));
+ // All console events will be reported before `page.evaluate` is finished.
+ await page.evaluate(() => {
+ // A pair of time/timeEnd generates only one Console API call.
+ console.time('calling console.time');
+ console.timeEnd('calling console.time');
+ console.trace('calling console.trace');
+ console.dir('calling console.dir');
+ console.warn('calling console.warn');
+ console.error('calling console.error');
+ console.log(Promise.resolve('should not wait until resolved!'));
+ });
+ expect(messages.map((msg) => msg.type())).toEqual([
+ 'timeEnd',
+ 'trace',
+ 'dir',
+ 'warning',
+ 'error',
+ 'log',
+ ]);
+ expect(messages[0].text()).toContain('calling console.time');
+ expect(messages.slice(1).map((msg) => msg.text())).toEqual([
+ 'calling console.trace',
+ 'calling console.dir',
+ 'calling console.warn',
+ 'calling console.error',
+ 'JSHandle@promise',
+ ]);
+ });
+ it('should not fail for window object', async () => {
+ const { page } = getTestState();
+
+ let message = null;
+ page.once('console', (msg) => (message = msg));
+ await Promise.all([
+ page.evaluate(() => console.error(window)),
+ waitEvent(page, 'console'),
+ ]);
+ expect(message.text()).toBe('JSHandle@object');
+ });
+ it('should trigger correct Log', async () => {
+ const { page, server, isChrome } = getTestState();
+
+ await page.goto('about:blank');
+ const [message] = await Promise.all([
+ waitEvent(page, 'console'),
+ page.evaluate(
+ async (url: string) => fetch(url).catch(() => {}),
+ server.EMPTY_PAGE
+ ),
+ ]);
+ expect(message.text()).toContain('Access-Control-Allow-Origin');
+ if (isChrome) expect(message.type()).toEqual('error');
+ else expect(message.type()).toEqual('warn');
+ });
+ it('should have location when fetch fails', async () => {
+ const { page, server } = getTestState();
+
+ // The point of this test is to make sure that we report console messages from
+ // Log domain: https://vanilla.aslushnikov.com/?Log.entryAdded
+ await page.goto(server.EMPTY_PAGE);
+ const [message] = await Promise.all([
+ waitEvent(page, 'console'),
+ page.setContent(``),
+ ]);
+ expect(message.text()).toContain(`ERR_NAME_NOT_RESOLVED`);
+ expect(message.type()).toEqual('error');
+ expect(message.location()).toEqual({
+ url: 'http://wat/',
+ lineNumber: undefined,
+ });
+ });
+ it('should have location and stack trace for console API calls', async () => {
+ const { page, server, isChrome } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const [message] = await Promise.all([
+ waitEvent(page, 'console'),
+ page.goto(server.PREFIX + '/consolelog.html'),
+ ]);
+ expect(message.text()).toBe('yellow');
+ expect(message.type()).toBe('log');
+ expect(message.location()).toEqual({
+ url: server.PREFIX + '/consolelog.html',
+ lineNumber: 8,
+ columnNumber: isChrome ? 16 : 8, // console.|log vs |console.log
+ });
+ expect(message.stackTrace()).toEqual([
+ {
+ url: server.PREFIX + '/consolelog.html',
+ lineNumber: 8,
+ columnNumber: isChrome ? 16 : 8, // console.|log vs |console.log
+ },
+ {
+ url: server.PREFIX + '/consolelog.html',
+ lineNumber: 11,
+ columnNumber: 8,
+ },
+ {
+ url: server.PREFIX + '/consolelog.html',
+ lineNumber: 13,
+ columnNumber: 6,
+ },
+ ]);
+ });
+ // @see https://github.com/puppeteer/puppeteer/issues/3865
+ it('should not throw when there are console messages in detached iframes', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.evaluate(async () => {
+ // 1. Create a popup that Puppeteer is not connected to.
+ const win = window.open(
+ window.location.href,
+ 'Title',
+ 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=780,height=200,top=0,left=0'
+ );
+ await new Promise((x) => (win.onload = x));
+ // 2. In this popup, create an iframe that console.logs a message.
+ win.document.body.innerHTML = ``;
+ const frame = win.document.querySelector('iframe');
+ await new Promise((x) => (frame.onload = x));
+ // 3. After that, remove the iframe.
+ frame.remove();
+ });
+ const popupTarget = page
+ .browserContext()
+ .targets()
+ .find((target) => target !== page.target());
+ // 4. Connect to the popup and make sure it doesn't throw.
+ await popupTarget.page();
+ });
+ });
+
+ describe('Page.Events.DOMContentLoaded', function () {
+ it('should fire when expected', async () => {
+ const { page } = getTestState();
+
+ page.goto('about:blank');
+ await waitEvent(page, 'domcontentloaded');
+ });
+ });
+
+ describeFailsFirefox('Page.metrics', function () {
+ it('should get metrics from a page', async () => {
+ const { page } = getTestState();
+
+ await page.goto('about:blank');
+ const metrics = await page.metrics();
+ checkMetrics(metrics);
+ });
+ it('metrics event fired on console.timeStamp', async () => {
+ const { page } = getTestState();
+
+ const metricsPromise = new Promise<{ metrics: Metrics; title: string }>(
+ (fulfill) => page.once('metrics', fulfill)
+ );
+ await page.evaluate(() => console.timeStamp('test42'));
+ const metrics = await metricsPromise;
+ expect(metrics.title).toBe('test42');
+ checkMetrics(metrics.metrics);
+ });
+ function checkMetrics(metrics) {
+ const metricsToCheck = new Set([
+ 'Timestamp',
+ 'Documents',
+ 'Frames',
+ 'JSEventListeners',
+ 'Nodes',
+ 'LayoutCount',
+ 'RecalcStyleCount',
+ 'LayoutDuration',
+ 'RecalcStyleDuration',
+ 'ScriptDuration',
+ 'TaskDuration',
+ 'JSHeapUsedSize',
+ 'JSHeapTotalSize',
+ ]);
+ for (const name in metrics) {
+ expect(metricsToCheck.has(name)).toBeTruthy();
+ expect(metrics[name]).toBeGreaterThanOrEqual(0);
+ metricsToCheck.delete(name);
+ }
+ expect(metricsToCheck.size).toBe(0);
+ }
+ });
+
+ describe('Page.waitForRequest', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const [request] = await Promise.all([
+ page.waitForRequest(server.PREFIX + '/digits/2.png'),
+ page.evaluate(() => {
+ fetch('/digits/1.png');
+ fetch('/digits/2.png');
+ fetch('/digits/3.png');
+ }),
+ ]);
+ expect(request.url()).toBe(server.PREFIX + '/digits/2.png');
+ });
+ it('should work with predicate', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const [request] = await Promise.all([
+ page.waitForRequest(
+ (request) => request.url() === server.PREFIX + '/digits/2.png'
+ ),
+ page.evaluate(() => {
+ fetch('/digits/1.png');
+ fetch('/digits/2.png');
+ fetch('/digits/3.png');
+ }),
+ ]);
+ expect(request.url()).toBe(server.PREFIX + '/digits/2.png');
+ });
+ it('should respect timeout', async () => {
+ const { page, puppeteer } = getTestState();
+
+ let error = null;
+ await page
+ .waitForRequest(() => false, { timeout: 1 })
+ .catch((error_) => (error = error_));
+ expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
+ });
+ it('should respect default timeout', async () => {
+ const { page, puppeteer } = getTestState();
+
+ let error = null;
+ page.setDefaultTimeout(1);
+ await page
+ .waitForRequest(() => false)
+ .catch((error_) => (error = error_));
+ expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
+ });
+ it('should work with no timeout', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const [request] = await Promise.all([
+ page.waitForRequest(server.PREFIX + '/digits/2.png', { timeout: 0 }),
+ page.evaluate(() =>
+ setTimeout(() => {
+ fetch('/digits/1.png');
+ fetch('/digits/2.png');
+ fetch('/digits/3.png');
+ }, 50)
+ ),
+ ]);
+ expect(request.url()).toBe(server.PREFIX + '/digits/2.png');
+ });
+ });
+
+ describe('Page.waitForResponse', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const [response] = await Promise.all([
+ page.waitForResponse(server.PREFIX + '/digits/2.png'),
+ page.evaluate(() => {
+ fetch('/digits/1.png');
+ fetch('/digits/2.png');
+ fetch('/digits/3.png');
+ }),
+ ]);
+ expect(response.url()).toBe(server.PREFIX + '/digits/2.png');
+ });
+ it('should respect timeout', async () => {
+ const { page, puppeteer } = getTestState();
+
+ let error = null;
+ await page
+ .waitForResponse(() => false, { timeout: 1 })
+ .catch((error_) => (error = error_));
+ expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
+ });
+ it('should respect default timeout', async () => {
+ const { page, puppeteer } = getTestState();
+
+ let error = null;
+ page.setDefaultTimeout(1);
+ await page
+ .waitForResponse(() => false)
+ .catch((error_) => (error = error_));
+ expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
+ });
+ it('should work with predicate', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const [response] = await Promise.all([
+ page.waitForResponse(
+ (response) => response.url() === server.PREFIX + '/digits/2.png'
+ ),
+ page.evaluate(() => {
+ fetch('/digits/1.png');
+ fetch('/digits/2.png');
+ fetch('/digits/3.png');
+ }),
+ ]);
+ expect(response.url()).toBe(server.PREFIX + '/digits/2.png');
+ });
+ it('should work with async predicate', async () => {
+ const { page, server } = getTestState();
+ await page.goto(server.EMPTY_PAGE);
+ const [response] = await Promise.all([
+ page.waitForResponse(async (response) => {
+ return response.url() === server.PREFIX + '/digits/2.png';
+ }),
+ page.evaluate(() => {
+ fetch('/digits/1.png');
+ fetch('/digits/2.png');
+ fetch('/digits/3.png');
+ }),
+ ]);
+ expect(response.url()).toBe(server.PREFIX + '/digits/2.png');
+ });
+ it('should work with no timeout', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const [response] = await Promise.all([
+ page.waitForResponse(server.PREFIX + '/digits/2.png', { timeout: 0 }),
+ page.evaluate(() =>
+ setTimeout(() => {
+ fetch('/digits/1.png');
+ fetch('/digits/2.png');
+ fetch('/digits/3.png');
+ }, 50)
+ ),
+ ]);
+ expect(response.url()).toBe(server.PREFIX + '/digits/2.png');
+ });
+ });
+
+ describe('Page.waitForNetworkIdle', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+ await page.goto(server.EMPTY_PAGE);
+ let res;
+ const [t1, t2] = await Promise.all([
+ page.waitForNetworkIdle().then((r) => {
+ res = r;
+ return Date.now();
+ }),
+ page
+ .evaluate(() =>
+ (async () => {
+ await Promise.all([
+ fetch('/digits/1.png'),
+ fetch('/digits/2.png'),
+ ]);
+ await new Promise((resolve) => setTimeout(resolve, 200));
+ await fetch('/digits/3.png');
+ await new Promise((resolve) => setTimeout(resolve, 200));
+ await fetch('/digits/4.png');
+ })()
+ )
+ .then(() => Date.now()),
+ ]);
+ expect(res).toBe(undefined);
+ expect(t1).toBeGreaterThan(t2);
+ expect(t1 - t2).toBeGreaterThanOrEqual(400);
+ });
+ it('should respect timeout', async () => {
+ const { page, puppeteer } = getTestState();
+ let error = null;
+ await page
+ .waitForNetworkIdle({ timeout: 1 })
+ .catch((error_) => (error = error_));
+ expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
+ });
+ it('should respect idleTime', async () => {
+ const { page, server } = getTestState();
+ await page.goto(server.EMPTY_PAGE);
+ const [t1, t2] = await Promise.all([
+ page.waitForNetworkIdle({ idleTime: 10 }).then(() => Date.now()),
+ page
+ .evaluate(() =>
+ (async () => {
+ await Promise.all([
+ fetch('/digits/1.png'),
+ fetch('/digits/2.png'),
+ ]);
+ await new Promise((resolve) => setTimeout(resolve, 250));
+ })()
+ )
+ .then(() => Date.now()),
+ ]);
+ expect(t2).toBeGreaterThan(t1);
+ });
+ it('should work with no timeout', async () => {
+ const { page, server } = getTestState();
+ await page.goto(server.EMPTY_PAGE);
+ const [result] = await Promise.all([
+ page.waitForNetworkIdle({ timeout: 0 }),
+ page.evaluate(() =>
+ setTimeout(() => {
+ fetch('/digits/1.png');
+ fetch('/digits/2.png');
+ fetch('/digits/3.png');
+ }, 50)
+ ),
+ ]);
+ expect(result).toBe(undefined);
+ });
+ });
+
+ describeFailsFirefox('Page.exposeFunction', function () {
+ it('should work', async () => {
+ const { page } = getTestState();
+
+ await page.exposeFunction('compute', function (a, b) {
+ return a * b;
+ });
+ const result = await page.evaluate(async function () {
+ return await globalThis.compute(9, 4);
+ });
+ expect(result).toBe(36);
+ });
+ it('should throw exception in page context', async () => {
+ const { page } = getTestState();
+
+ await page.exposeFunction('woof', function () {
+ throw new Error('WOOF WOOF');
+ });
+ const { message, stack } = await page.evaluate(async () => {
+ try {
+ await globalThis.woof();
+ } catch (error) {
+ return { message: error.message, stack: error.stack };
+ }
+ });
+ expect(message).toBe('WOOF WOOF');
+ expect(stack).toContain(__filename);
+ });
+ it('should support throwing "null"', async () => {
+ const { page } = getTestState();
+
+ await page.exposeFunction('woof', function () {
+ throw null;
+ });
+ const thrown = await page.evaluate(async () => {
+ try {
+ await globalThis.woof();
+ } catch (error) {
+ return error;
+ }
+ });
+ expect(thrown).toBe(null);
+ });
+ it('should be callable from-inside evaluateOnNewDocument', async () => {
+ const { page } = getTestState();
+
+ let called = false;
+ await page.exposeFunction('woof', function () {
+ called = true;
+ });
+ await page.evaluateOnNewDocument(() => globalThis.woof());
+ await page.reload();
+ expect(called).toBe(true);
+ });
+ it('should survive navigation', async () => {
+ const { page, server } = getTestState();
+
+ await page.exposeFunction('compute', function (a, b) {
+ return a * b;
+ });
+
+ await page.goto(server.EMPTY_PAGE);
+ const result = await page.evaluate(async function () {
+ return await globalThis.compute(9, 4);
+ });
+ expect(result).toBe(36);
+ });
+ it('should await returned promise', async () => {
+ const { page } = getTestState();
+
+ await page.exposeFunction('compute', function (a, b) {
+ return Promise.resolve(a * b);
+ });
+
+ const result = await page.evaluate(async function () {
+ return await globalThis.compute(3, 5);
+ });
+ expect(result).toBe(15);
+ });
+ it('should work on frames', async () => {
+ const { page, server } = getTestState();
+
+ await page.exposeFunction('compute', function (a, b) {
+ return Promise.resolve(a * b);
+ });
+
+ await page.goto(server.PREFIX + '/frames/nested-frames.html');
+ const frame = page.frames()[1];
+ const result = await frame.evaluate(async function () {
+ return await globalThis.compute(3, 5);
+ });
+ expect(result).toBe(15);
+ });
+ it('should work on frames before navigation', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/frames/nested-frames.html');
+ await page.exposeFunction('compute', function (a, b) {
+ return Promise.resolve(a * b);
+ });
+
+ const frame = page.frames()[1];
+ const result = await frame.evaluate(async function () {
+ return await globalThis.compute(3, 5);
+ });
+ expect(result).toBe(15);
+ });
+ it('should not throw when frames detach', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ await page.exposeFunction('compute', function (a, b) {
+ return Promise.resolve(a * b);
+ });
+ await utils.detachFrame(page, 'frame1');
+
+ await expect(
+ page.evaluate(async function () {
+ return await globalThis.compute(3, 5);
+ })
+ ).resolves.toEqual(15);
+ });
+ it('should work with complex objects', async () => {
+ const { page } = getTestState();
+
+ await page.exposeFunction('complexObject', function (a, b) {
+ return { x: a.x + b.x };
+ });
+ const result = await page.evaluate<() => Promise<{ x: number }>>(
+ async () => globalThis.complexObject({ x: 5 }, { x: 2 })
+ );
+ expect(result.x).toBe(7);
+ });
+ it('should fallback to default export when passed a module object', async () => {
+ const { page, server } = getTestState();
+ const moduleObject = {
+ default: function (a, b) {
+ return a * b;
+ },
+ };
+ await page.goto(server.EMPTY_PAGE);
+ await page.exposeFunction('compute', moduleObject);
+ const result = await page.evaluate(async function () {
+ return await globalThis.compute(9, 4);
+ });
+ expect(result).toBe(36);
+ });
+ });
+
+ describeFailsFirefox('Page.Events.PageError', function () {
+ it('should fire', async () => {
+ const { page, server } = getTestState();
+
+ let error = null;
+ page.once('pageerror', (e) => (error = e));
+ await Promise.all([
+ page.goto(server.PREFIX + '/error.html'),
+ waitEvent(page, 'pageerror'),
+ ]);
+ expect(error.message).toContain('Fancy');
+ });
+ });
+
+ describe('Page.setUserAgent', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ expect(await page.evaluate(() => navigator.userAgent)).toContain(
+ 'Mozilla'
+ );
+ await page.setUserAgent('foobar');
+ const [request] = await Promise.all([
+ server.waitForRequest('/empty.html'),
+ page.goto(server.EMPTY_PAGE),
+ ]);
+ expect(request.headers['user-agent']).toBe('foobar');
+ });
+ it('should work for subframes', async () => {
+ const { page, server } = getTestState();
+
+ expect(await page.evaluate(() => navigator.userAgent)).toContain(
+ 'Mozilla'
+ );
+ await page.setUserAgent('foobar');
+ const [request] = await Promise.all([
+ server.waitForRequest('/empty.html'),
+ utils.attachFrame(page, 'frame1', server.EMPTY_PAGE),
+ ]);
+ expect(request.headers['user-agent']).toBe('foobar');
+ });
+ it('should emulate device user-agent', async () => {
+ const { page, server, puppeteer } = getTestState();
+
+ await page.goto(server.PREFIX + '/mobile.html');
+ expect(await page.evaluate(() => navigator.userAgent)).not.toContain(
+ 'iPhone'
+ );
+ await page.setUserAgent(puppeteer.devices['iPhone 6'].userAgent);
+ expect(await page.evaluate(() => navigator.userAgent)).toContain(
+ 'iPhone'
+ );
+ });
+ itFailsFirefox('should work with additional userAgentMetdata', async () => {
+ const { page, server } = getTestState();
+
+ await page.setUserAgent('MockBrowser', {
+ architecture: 'Mock1',
+ mobile: false,
+ model: 'Mockbook',
+ platform: 'MockOS',
+ platformVersion: '3.1',
+ });
+ const [request] = await Promise.all([
+ server.waitForRequest('/empty.html'),
+ page.goto(server.EMPTY_PAGE),
+ ]);
+ expect(
+ await page.evaluate(() => {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore: userAgentData not yet in TypeScript DOM API
+ return navigator.userAgentData.mobile;
+ })
+ ).toBe(false);
+
+ const uaData = await page.evaluate(() => {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore: userAgentData not yet in TypeScript DOM API
+ return navigator.userAgentData.getHighEntropyValues([
+ 'architecture',
+ 'model',
+ 'platform',
+ 'platformVersion',
+ ]);
+ });
+ expect(uaData['architecture']).toBe('Mock1');
+ expect(uaData['model']).toBe('Mockbook');
+ expect(uaData['platform']).toBe('MockOS');
+ expect(uaData['platformVersion']).toBe('3.1');
+ expect(request.headers['user-agent']).toBe('MockBrowser');
+ });
+ });
+
+ describe('Page.setContent', function () {
+ const expectedOutput =
+ 'hello
';
+ it('should work', async () => {
+ const { page } = getTestState();
+
+ await page.setContent('hello
');
+ const result = await page.content();
+ expect(result).toBe(expectedOutput);
+ });
+ it('should work with doctype', async () => {
+ const { page } = getTestState();
+
+ const doctype = '';
+ await page.setContent(`${doctype}hello
`);
+ const result = await page.content();
+ expect(result).toBe(`${doctype}${expectedOutput}`);
+ });
+ it('should work with HTML 4 doctype', async () => {
+ const { page } = getTestState();
+
+ const doctype =
+ '';
+ await page.setContent(`${doctype}hello
`);
+ const result = await page.content();
+ expect(result).toBe(`${doctype}${expectedOutput}`);
+ });
+ it('should respect timeout', async () => {
+ const { page, server, puppeteer } = getTestState();
+
+ const imgPath = '/img.png';
+ // stall for image
+ server.setRoute(imgPath, () => {});
+ let error = null;
+ await page
+ .setContent(` `, {
+ timeout: 1,
+ })
+ .catch((error_) => (error = error_));
+ expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
+ });
+ it('should respect default navigation timeout', async () => {
+ const { page, server, puppeteer } = getTestState();
+
+ page.setDefaultNavigationTimeout(1);
+ const imgPath = '/img.png';
+ // stall for image
+ server.setRoute(imgPath, () => {});
+ let error = null;
+ await page
+ .setContent(` `)
+ .catch((error_) => (error = error_));
+ expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
+ });
+ it('should await resources to load', async () => {
+ const { page, server } = getTestState();
+
+ const imgPath = '/img.png';
+ let imgResponse = null;
+ server.setRoute(imgPath, (req, res) => (imgResponse = res));
+ let loaded = false;
+ const contentPromise = page
+ .setContent(` `)
+ .then(() => (loaded = true));
+ await server.waitForRequest(imgPath);
+ expect(loaded).toBe(false);
+ imgResponse.end();
+ await contentPromise;
+ });
+ it('should work fast enough', async () => {
+ const { page } = getTestState();
+
+ for (let i = 0; i < 20; ++i) await page.setContent('yo
');
+ });
+ it('should work with tricky content', async () => {
+ const { page } = getTestState();
+
+ await page.setContent('hello world
' + '\x7F');
+ expect(await page.$eval('div', (div) => div.textContent)).toBe(
+ 'hello world'
+ );
+ });
+ it('should work with accents', async () => {
+ const { page } = getTestState();
+
+ await page.setContent('aberración
');
+ expect(await page.$eval('div', (div) => div.textContent)).toBe(
+ 'aberración'
+ );
+ });
+ it('should work with emojis', async () => {
+ const { page } = getTestState();
+
+ await page.setContent('🐥
');
+ expect(await page.$eval('div', (div) => div.textContent)).toBe('🐥');
+ });
+ it('should work with newline', async () => {
+ const { page } = getTestState();
+
+ await page.setContent('\n
');
+ expect(await page.$eval('div', (div) => div.textContent)).toBe('\n');
+ });
+ });
+
+ describeFailsFirefox('Page.setBypassCSP', function () {
+ it('should bypass CSP meta tag', async () => {
+ const { page, server } = getTestState();
+
+ // Make sure CSP prohibits addScriptTag.
+ await page.goto(server.PREFIX + '/csp.html');
+ await page
+ .addScriptTag({ content: 'window.__injected = 42;' })
+ .catch((error) => void error);
+ expect(await page.evaluate(() => globalThis.__injected)).toBe(undefined);
+
+ // By-pass CSP and try one more time.
+ await page.setBypassCSP(true);
+ await page.reload();
+ await page.addScriptTag({ content: 'window.__injected = 42;' });
+ expect(await page.evaluate(() => globalThis.__injected)).toBe(42);
+ });
+
+ it('should bypass CSP header', async () => {
+ const { page, server } = getTestState();
+
+ // Make sure CSP prohibits addScriptTag.
+ server.setCSP('/empty.html', 'default-src "self"');
+ await page.goto(server.EMPTY_PAGE);
+ await page
+ .addScriptTag({ content: 'window.__injected = 42;' })
+ .catch((error) => void error);
+ expect(await page.evaluate(() => globalThis.__injected)).toBe(undefined);
+
+ // By-pass CSP and try one more time.
+ await page.setBypassCSP(true);
+ await page.reload();
+ await page.addScriptTag({ content: 'window.__injected = 42;' });
+ expect(await page.evaluate(() => globalThis.__injected)).toBe(42);
+ });
+
+ it('should bypass after cross-process navigation', async () => {
+ const { page, server } = getTestState();
+
+ await page.setBypassCSP(true);
+ await page.goto(server.PREFIX + '/csp.html');
+ await page.addScriptTag({ content: 'window.__injected = 42;' });
+ expect(await page.evaluate(() => globalThis.__injected)).toBe(42);
+
+ await page.goto(server.CROSS_PROCESS_PREFIX + '/csp.html');
+ await page.addScriptTag({ content: 'window.__injected = 42;' });
+ expect(await page.evaluate(() => globalThis.__injected)).toBe(42);
+ });
+ it('should bypass CSP in iframes as well', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ {
+ // Make sure CSP prohibits addScriptTag in an iframe.
+ const frame = await utils.attachFrame(
+ page,
+ 'frame1',
+ server.PREFIX + '/csp.html'
+ );
+ await frame
+ .addScriptTag({ content: 'window.__injected = 42;' })
+ .catch((error) => void error);
+ expect(await frame.evaluate(() => globalThis.__injected)).toBe(
+ undefined
+ );
+ }
+
+ // By-pass CSP and try one more time.
+ await page.setBypassCSP(true);
+ await page.reload();
+
+ {
+ const frame = await utils.attachFrame(
+ page,
+ 'frame1',
+ server.PREFIX + '/csp.html'
+ );
+ await frame
+ .addScriptTag({ content: 'window.__injected = 42;' })
+ .catch((error) => void error);
+ expect(await frame.evaluate(() => globalThis.__injected)).toBe(42);
+ }
+ });
+ });
+
+ describe('Page.addScriptTag', function () {
+ it('should throw an error if no options are provided', async () => {
+ const { page } = getTestState();
+
+ let error = null;
+ try {
+ // @ts-expect-error purposefully passing bad options
+ await page.addScriptTag('/injectedfile.js');
+ } catch (error_) {
+ error = error_;
+ }
+ expect(error.message).toBe(
+ 'Provide an object with a `url`, `path` or `content` property'
+ );
+ });
+
+ it('should work with a url', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const scriptHandle = await page.addScriptTag({ url: '/injectedfile.js' });
+ expect(scriptHandle.asElement()).not.toBeNull();
+ expect(await page.evaluate(() => globalThis.__injected)).toBe(42);
+ });
+
+ it('should work with a url and type=module', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.addScriptTag({ url: '/es6/es6import.js', type: 'module' });
+ expect(await page.evaluate(() => globalThis.__es6injected)).toBe(42);
+ });
+
+ it('should work with a path and type=module', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.addScriptTag({
+ path: path.join(__dirname, 'assets/es6/es6pathimport.js'),
+ type: 'module',
+ });
+ await page.waitForFunction('window.__es6injected');
+ expect(await page.evaluate(() => globalThis.__es6injected)).toBe(42);
+ });
+
+ it('should work with a content and type=module', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.addScriptTag({
+ content: `import num from '/es6/es6module.js';window.__es6injected = num;`,
+ type: 'module',
+ });
+ await page.waitForFunction('window.__es6injected');
+ expect(await page.evaluate(() => globalThis.__es6injected)).toBe(42);
+ });
+
+ it('should throw an error if loading from url fail', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ let error = null;
+ try {
+ await page.addScriptTag({ url: '/nonexistfile.js' });
+ } catch (error_) {
+ error = error_;
+ }
+ expect(error.message).toBe('Loading script from /nonexistfile.js failed');
+ });
+
+ it('should work with a path', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const scriptHandle = await page.addScriptTag({
+ path: path.join(__dirname, 'assets/injectedfile.js'),
+ });
+ expect(scriptHandle.asElement()).not.toBeNull();
+ expect(await page.evaluate(() => globalThis.__injected)).toBe(42);
+ });
+
+ it('should include sourcemap when path is provided', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.addScriptTag({
+ path: path.join(__dirname, 'assets/injectedfile.js'),
+ });
+ const result = await page.evaluate(
+ () => globalThis.__injectedError.stack
+ );
+ expect(result).toContain(path.join('assets', 'injectedfile.js'));
+ });
+
+ it('should work with content', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const scriptHandle = await page.addScriptTag({
+ content: 'window.__injected = 35;',
+ });
+ expect(scriptHandle.asElement()).not.toBeNull();
+ expect(await page.evaluate(() => globalThis.__injected)).toBe(35);
+ });
+
+ it('should add id when provided', async () => {
+ const { page, server } = getTestState();
+ await page.goto(server.EMPTY_PAGE);
+ await page.addScriptTag({ content: 'window.__injected = 1;', id: 'one' });
+ await page.addScriptTag({ url: '/injectedfile.js', id: 'two' });
+ expect(await page.$('#one')).not.toBeNull();
+ expect(await page.$('#two')).not.toBeNull();
+ });
+
+ // @see https://github.com/puppeteer/puppeteer/issues/4840
+ xit('should throw when added with content to the CSP page', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/csp.html');
+ let error = null;
+ await page
+ .addScriptTag({ content: 'window.__injected = 35;' })
+ .catch((error_) => (error = error_));
+ expect(error).toBeTruthy();
+ });
+
+ it('should throw when added with URL to the CSP page', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/csp.html');
+ let error = null;
+ await page
+ .addScriptTag({ url: server.CROSS_PROCESS_PREFIX + '/injectedfile.js' })
+ .catch((error_) => (error = error_));
+ expect(error).toBeTruthy();
+ });
+ });
+
+ describe('Page.addStyleTag', function () {
+ it('should throw an error if no options are provided', async () => {
+ const { page } = getTestState();
+
+ let error = null;
+ try {
+ // @ts-expect-error purposefully passing bad input
+ await page.addStyleTag('/injectedstyle.css');
+ } catch (error_) {
+ error = error_;
+ }
+ expect(error.message).toBe(
+ 'Provide an object with a `url`, `path` or `content` property'
+ );
+ });
+
+ it('should work with a url', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const styleHandle = await page.addStyleTag({ url: '/injectedstyle.css' });
+ expect(styleHandle.asElement()).not.toBeNull();
+ expect(
+ await page.evaluate(
+ `window.getComputedStyle(document.querySelector('body')).getPropertyValue('background-color')`
+ )
+ ).toBe('rgb(255, 0, 0)');
+ });
+
+ it('should throw an error if loading from url fail', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ let error = null;
+ try {
+ await page.addStyleTag({ url: '/nonexistfile.js' });
+ } catch (error_) {
+ error = error_;
+ }
+ expect(error.message).toBe('Loading style from /nonexistfile.js failed');
+ });
+
+ it('should work with a path', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const styleHandle = await page.addStyleTag({
+ path: path.join(__dirname, 'assets/injectedstyle.css'),
+ });
+ expect(styleHandle.asElement()).not.toBeNull();
+ expect(
+ await page.evaluate(
+ `window.getComputedStyle(document.querySelector('body')).getPropertyValue('background-color')`
+ )
+ ).toBe('rgb(255, 0, 0)');
+ });
+
+ it('should include sourcemap when path is provided', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.addStyleTag({
+ path: path.join(__dirname, 'assets/injectedstyle.css'),
+ });
+ const styleHandle = await page.$('style');
+ const styleContent = await page.evaluate(
+ (style: HTMLStyleElement) => style.innerHTML,
+ styleHandle
+ );
+ expect(styleContent).toContain(path.join('assets', 'injectedstyle.css'));
+ });
+
+ it('should work with content', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const styleHandle = await page.addStyleTag({
+ content: 'body { background-color: green; }',
+ });
+ expect(styleHandle.asElement()).not.toBeNull();
+ expect(
+ await page.evaluate(
+ `window.getComputedStyle(document.querySelector('body')).getPropertyValue('background-color')`
+ )
+ ).toBe('rgb(0, 128, 0)');
+ });
+
+ itFailsFirefox(
+ 'should throw when added with content to the CSP page',
+ async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/csp.html');
+ let error = null;
+ await page
+ .addStyleTag({ content: 'body { background-color: green; }' })
+ .catch((error_) => (error = error_));
+ expect(error).toBeTruthy();
+ }
+ );
+
+ it('should throw when added with URL to the CSP page', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/csp.html');
+ let error = null;
+ await page
+ .addStyleTag({
+ url: server.CROSS_PROCESS_PREFIX + '/injectedstyle.css',
+ })
+ .catch((error_) => (error = error_));
+ expect(error).toBeTruthy();
+ });
+ });
+
+ describe('Page.url', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ expect(page.url()).toBe('about:blank');
+ await page.goto(server.EMPTY_PAGE);
+ expect(page.url()).toBe(server.EMPTY_PAGE);
+ });
+ });
+
+ describeFailsFirefox('Page.setJavaScriptEnabled', function () {
+ it('should work', async () => {
+ const { page } = getTestState();
+
+ await page.setJavaScriptEnabled(false);
+ await page.goto(
+ 'data:text/html, '
+ );
+ let error = null;
+ await page.evaluate('something').catch((error_) => (error = error_));
+ expect(error.message).toContain('something is not defined');
+
+ await page.setJavaScriptEnabled(true);
+ await page.goto(
+ 'data:text/html, '
+ );
+ expect(await page.evaluate('something')).toBe('forbidden');
+ });
+ });
+
+ describe('Page.setCacheEnabled', function () {
+ it('should enable or disable the cache based on the state passed', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/cached/one-style.html');
+ const [cachedRequest] = await Promise.all([
+ server.waitForRequest('/cached/one-style.html'),
+ page.reload(),
+ ]);
+ // Rely on "if-modified-since" caching in our test server.
+ expect(cachedRequest.headers['if-modified-since']).not.toBe(undefined);
+
+ await page.setCacheEnabled(false);
+ const [nonCachedRequest] = await Promise.all([
+ server.waitForRequest('/cached/one-style.html'),
+ page.reload(),
+ ]);
+ expect(nonCachedRequest.headers['if-modified-since']).toBe(undefined);
+ });
+ itFailsFirefox(
+ 'should stay disabled when toggling request interception on/off',
+ async () => {
+ const { page, server } = getTestState();
+
+ await page.setCacheEnabled(false);
+ await page.setRequestInterception(true);
+ await page.setRequestInterception(false);
+
+ await page.goto(server.PREFIX + '/cached/one-style.html');
+ const [nonCachedRequest] = await Promise.all([
+ server.waitForRequest('/cached/one-style.html'),
+ page.reload(),
+ ]);
+ expect(nonCachedRequest.headers['if-modified-since']).toBe(undefined);
+ }
+ );
+ });
+
+ describe('printing to PDF', function () {
+ it('can print to PDF and save to file', async () => {
+ // Printing to pdf is currently only supported in headless
+ const { isHeadless, page } = getTestState();
+
+ if (!isHeadless) return;
+
+ const outputFile = __dirname + '/assets/output.pdf';
+ await page.pdf({ path: outputFile });
+ expect(fs.readFileSync(outputFile).byteLength).toBeGreaterThan(0);
+ fs.unlinkSync(outputFile);
+ });
+
+ it('can print to PDF and stream the result', async () => {
+ // Printing to pdf is currently only supported in headless
+ const { isHeadless, page } = getTestState();
+
+ if (!isHeadless) return;
+
+ const stream = await page.createPDFStream();
+ let size = 0;
+ for await (const chunk of stream) {
+ size += chunk.length;
+ }
+ expect(size).toBeGreaterThan(0);
+ });
+
+ it('should respect timeout', async () => {
+ const { isHeadless, page, server, puppeteer } = getTestState();
+ if (!isHeadless) return;
+
+ await page.goto(server.PREFIX + '/pdf.html');
+
+ let error = null;
+ await page.pdf({ timeout: 1 }).catch((_error) => (error = _error));
+ expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
+ });
+ });
+
+ describe('Page.title', function () {
+ it('should return the page title', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/title.html');
+ expect(await page.title()).toBe('Woof-Woof');
+ });
+ });
+
+ describe('Page.select', function () {
+ it('should select single option', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/select.html');
+ await page.select('select', 'blue');
+ expect(await page.evaluate(() => globalThis.result.onInput)).toEqual([
+ 'blue',
+ ]);
+ expect(await page.evaluate(() => globalThis.result.onChange)).toEqual([
+ 'blue',
+ ]);
+ });
+ it('should select only first option', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/select.html');
+ await page.select('select', 'blue', 'green', 'red');
+ expect(await page.evaluate(() => globalThis.result.onInput)).toEqual([
+ 'blue',
+ ]);
+ expect(await page.evaluate(() => globalThis.result.onChange)).toEqual([
+ 'blue',
+ ]);
+ });
+ it('should not throw when select causes navigation', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/select.html');
+ await page.$eval('select', (select) =>
+ select.addEventListener(
+ 'input',
+ () => ((window as any).location = '/empty.html')
+ )
+ );
+ await Promise.all([
+ page.select('select', 'blue'),
+ page.waitForNavigation(),
+ ]);
+ expect(page.url()).toContain('empty.html');
+ });
+ it('should select multiple options', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/select.html');
+ await page.evaluate(() => globalThis.makeMultiple());
+ await page.select('select', 'blue', 'green', 'red');
+ expect(await page.evaluate(() => globalThis.result.onInput)).toEqual([
+ 'blue',
+ 'green',
+ 'red',
+ ]);
+ expect(await page.evaluate(() => globalThis.result.onChange)).toEqual([
+ 'blue',
+ 'green',
+ 'red',
+ ]);
+ });
+ it('should respect event bubbling', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/select.html');
+ await page.select('select', 'blue');
+ expect(
+ await page.evaluate(() => globalThis.result.onBubblingInput)
+ ).toEqual(['blue']);
+ expect(
+ await page.evaluate(() => globalThis.result.onBubblingChange)
+ ).toEqual(['blue']);
+ });
+ it('should throw when element is not a ', async () => {
+ const { page, server } = getTestState();
+
+ let error = null;
+ await page.goto(server.PREFIX + '/input/select.html');
+ await page.select('body', '').catch((error_) => (error = error_));
+ expect(error.message).toContain('Element is not a element.');
+ });
+ it('should return [] on no matched values', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/select.html');
+ const result = await page.select('select', '42', 'abc');
+ expect(result).toEqual([]);
+ });
+ it('should return an array of matched values', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/select.html');
+ await page.evaluate(() => globalThis.makeMultiple());
+ const result = await page.select('select', 'blue', 'black', 'magenta');
+ expect(
+ result.reduce(
+ (accumulator, current) =>
+ ['blue', 'black', 'magenta'].includes(current) && accumulator,
+ true
+ )
+ ).toEqual(true);
+ });
+ it('should return an array of one element when multiple is not set', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/select.html');
+ const result = await page.select(
+ 'select',
+ '42',
+ 'blue',
+ 'black',
+ 'magenta'
+ );
+ expect(result.length).toEqual(1);
+ });
+ it('should return [] on no values', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/select.html');
+ const result = await page.select('select');
+ expect(result).toEqual([]);
+ });
+ it('should deselect all options when passed no values for a multiple select', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/select.html');
+ await page.evaluate(() => globalThis.makeMultiple());
+ await page.select('select', 'blue', 'black', 'magenta');
+ await page.select('select');
+ expect(
+ await page.$eval('select', (select: HTMLSelectElement) =>
+ Array.from(select.options).every(
+ (option: HTMLOptionElement) => !option.selected
+ )
+ )
+ ).toEqual(true);
+ });
+ it('should deselect all options when passed no values for a select without multiple', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/select.html');
+ await page.select('select', 'blue', 'black', 'magenta');
+ await page.select('select');
+ expect(
+ await page.$eval('select', (select: HTMLSelectElement) =>
+ Array.from(select.options).every(
+ (option: HTMLOptionElement) => !option.selected
+ )
+ )
+ ).toEqual(true);
+ });
+ it('should throw if passed in non-strings', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(' ');
+ let error = null;
+ try {
+ // @ts-expect-error purposefully passing bad input
+ await page.select('select', 12);
+ } catch (error_) {
+ error = error_;
+ }
+ expect(error.message).toContain('Values must be strings');
+ });
+ // @see https://github.com/puppeteer/puppeteer/issues/3327
+ itFailsFirefox(
+ 'should work when re-defining top-level Event class',
+ async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/input/select.html');
+ await page.evaluate(() => (window.Event = null));
+ await page.select('select', 'blue');
+ expect(await page.evaluate(() => globalThis.result.onInput)).toEqual([
+ 'blue',
+ ]);
+ expect(await page.evaluate(() => globalThis.result.onChange)).toEqual([
+ 'blue',
+ ]);
+ }
+ );
+ });
+
+ describe('Page.Events.Close', function () {
+ itFailsFirefox('should work with window.close', async () => {
+ const { page, context } = getTestState();
+
+ const newPagePromise = new Promise((fulfill) =>
+ context.once('targetcreated', (target) => fulfill(target.page()))
+ );
+ await page.evaluate(
+ () => (window['newPage'] = window.open('about:blank'))
+ );
+ const newPage = await newPagePromise;
+ const closedPromise = new Promise((x) => newPage.on('close', x));
+ await page.evaluate(() => window['newPage'].close());
+ await closedPromise;
+ });
+ it('should work with page.close', async () => {
+ const { context } = getTestState();
+
+ const newPage = await context.newPage();
+ const closedPromise = new Promise((x) => newPage.on('close', x));
+ await newPage.close();
+ await closedPromise;
+ });
+ });
+
+ describe('Page.browser', function () {
+ it('should return the correct browser instance', async () => {
+ const { page, browser } = getTestState();
+
+ expect(page.browser()).toBe(browser);
+ });
+ });
+
+ describe('Page.browserContext', function () {
+ it('should return the correct browser context instance', async () => {
+ const { page, context } = getTestState();
+
+ expect(page.browserContext()).toBe(context);
+ });
+ });
+
+ describe('Page.client', function () {
+ it('should return the client instance', async () => {
+ const { page } = getTestState();
+ expect(page.client()).toBeInstanceOf(CDPSession);
+ });
+ });
+});
diff --git a/test/proxy.spec.ts b/test/proxy.spec.ts
new file mode 100644
index 0000000000000..d26d64e69afe6
--- /dev/null
+++ b/test/proxy.spec.ts
@@ -0,0 +1,226 @@
+/**
+ * Copyright 2021 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import expect from 'expect';
+import http from 'http';
+import os from 'os';
+import {
+ getTestState,
+ describeFailsFirefox,
+ itFailsWindows,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+import type { Server, IncomingMessage, ServerResponse } from 'http';
+import type { Browser } from '../lib/cjs/puppeteer/common/Browser.js';
+import type { AddressInfo } from 'net';
+
+const HOSTNAME = os.hostname().toLowerCase();
+
+/**
+ * Requests to localhost do not get proxied by default. Create a URL using the hostname
+ * instead.
+ */
+function getEmptyPageUrl(server: { PORT: number; EMPTY_PAGE: string }): string {
+ const emptyPagePath = new URL(server.EMPTY_PAGE).pathname;
+
+ return `http://${HOSTNAME}:${server.PORT}${emptyPagePath}`;
+}
+
+describeFailsFirefox('request proxy', () => {
+ let browser: Browser;
+ let proxiedRequestUrls: string[];
+ let proxyServer: Server;
+ let proxyServerUrl: string;
+ const defaultArgs = [
+ '--disable-features=NetworkTimeServiceQuerying', // We disable this in tests so that proxy-related tests don't intercept queries from this service in headful.
+ ];
+
+ beforeEach(() => {
+ proxiedRequestUrls = [];
+
+ proxyServer = http
+ .createServer(
+ (
+ originalRequest: IncomingMessage,
+ originalResponse: ServerResponse
+ ) => {
+ proxiedRequestUrls.push(originalRequest.url as string);
+
+ const proxyRequest = http.request(
+ originalRequest.url as string,
+ {
+ method: originalRequest.method,
+ headers: originalRequest.headers,
+ },
+ (proxyResponse) => {
+ originalResponse.writeHead(
+ proxyResponse.statusCode as number,
+ proxyResponse.headers
+ );
+ proxyResponse.pipe(originalResponse, { end: true });
+ }
+ );
+
+ originalRequest.pipe(proxyRequest, { end: true });
+ }
+ )
+ .listen();
+
+ proxyServerUrl = `http://${HOSTNAME}:${
+ (proxyServer.address() as AddressInfo).port
+ }`;
+ });
+
+ afterEach(async () => {
+ await browser.close();
+
+ await new Promise((resolve, reject) => {
+ proxyServer.close((error) => {
+ if (error) {
+ reject(error);
+ } else {
+ resolve(undefined);
+ }
+ });
+ });
+ });
+
+ it('should proxy requests when configured', async () => {
+ const { puppeteer, defaultBrowserOptions, server } = getTestState();
+ const emptyPageUrl = getEmptyPageUrl(server);
+
+ browser = await puppeteer.launch({
+ ...defaultBrowserOptions,
+ args: [...defaultArgs, `--proxy-server=${proxyServerUrl}`],
+ });
+
+ const page = await browser.newPage();
+ const response = await page.goto(emptyPageUrl);
+
+ expect(response.ok()).toBe(true);
+
+ expect(proxiedRequestUrls).toEqual([emptyPageUrl]);
+ });
+
+ it('should respect proxy bypass list', async () => {
+ const { puppeteer, defaultBrowserOptions, server } = getTestState();
+ const emptyPageUrl = getEmptyPageUrl(server);
+
+ browser = await puppeteer.launch({
+ ...defaultBrowserOptions,
+ args: [
+ ...defaultArgs,
+ `--proxy-server=${proxyServerUrl}`,
+ `--proxy-bypass-list=${new URL(emptyPageUrl).host}`,
+ ],
+ });
+
+ const page = await browser.newPage();
+ const response = await page.goto(emptyPageUrl);
+
+ expect(response.ok()).toBe(true);
+
+ expect(proxiedRequestUrls).toEqual([]);
+ });
+
+ describe('in incognito browser context', () => {
+ it('should proxy requests when configured at browser level', async () => {
+ const { puppeteer, defaultBrowserOptions, server } = getTestState();
+ const emptyPageUrl = getEmptyPageUrl(server);
+
+ browser = await puppeteer.launch({
+ ...defaultBrowserOptions,
+ args: [...defaultArgs, `--proxy-server=${proxyServerUrl}`],
+ });
+
+ const context = await browser.createIncognitoBrowserContext();
+ const page = await context.newPage();
+ const response = await page.goto(emptyPageUrl);
+
+ expect(response.ok()).toBe(true);
+
+ expect(proxiedRequestUrls).toEqual([emptyPageUrl]);
+ });
+
+ it('should respect proxy bypass list when configured at browser level', async () => {
+ const { puppeteer, defaultBrowserOptions, server } = getTestState();
+ const emptyPageUrl = getEmptyPageUrl(server);
+
+ browser = await puppeteer.launch({
+ ...defaultBrowserOptions,
+ args: [
+ ...defaultArgs,
+ `--proxy-server=${proxyServerUrl}`,
+ `--proxy-bypass-list=${new URL(emptyPageUrl).host}`,
+ ],
+ });
+
+ const context = await browser.createIncognitoBrowserContext();
+ const page = await context.newPage();
+ const response = await page.goto(emptyPageUrl);
+
+ expect(response.ok()).toBe(true);
+
+ expect(proxiedRequestUrls).toEqual([]);
+ });
+
+ /**
+ * See issues #7873, #7719, and #7698.
+ */
+ itFailsWindows(
+ 'should proxy requests when configured at context level',
+ async () => {
+ const { puppeteer, defaultBrowserOptions, server } = getTestState();
+ const emptyPageUrl = getEmptyPageUrl(server);
+
+ browser = await puppeteer.launch({
+ ...defaultBrowserOptions,
+ args: defaultArgs,
+ });
+
+ const context = await browser.createIncognitoBrowserContext({
+ proxyServer: proxyServerUrl,
+ });
+ const page = await context.newPage();
+ const response = await page.goto(emptyPageUrl);
+
+ expect(response.ok()).toBe(true);
+
+ expect(proxiedRequestUrls).toEqual([emptyPageUrl]);
+ }
+ );
+
+ it('should respect proxy bypass list when configured at context level', async () => {
+ const { puppeteer, defaultBrowserOptions, server } = getTestState();
+ const emptyPageUrl = getEmptyPageUrl(server);
+
+ browser = await puppeteer.launch({
+ ...defaultBrowserOptions,
+ args: defaultArgs,
+ });
+
+ const context = await browser.createIncognitoBrowserContext({
+ proxyServer: proxyServerUrl,
+ proxyBypassList: [new URL(emptyPageUrl).host],
+ });
+ const page = await context.newPage();
+ const response = await page.goto(emptyPageUrl);
+
+ expect(response.ok()).toBe(true);
+
+ expect(proxiedRequestUrls).toEqual([]);
+ });
+ });
+});
diff --git a/test/queryselector.spec.ts b/test/queryselector.spec.ts
new file mode 100644
index 0000000000000..be05fb3bd6da6
--- /dev/null
+++ b/test/queryselector.spec.ts
@@ -0,0 +1,527 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import expect from 'expect';
+import {
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+import { CustomQueryHandler } from '../lib/cjs/puppeteer/common/QueryHandler.js';
+
+describe('querySelector', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+ describe('Page.$eval', function () {
+ it('should work', async () => {
+ const { page } = getTestState();
+
+ await page.setContent('');
+ const idAttribute = await page.$eval('section', (e) => e.id);
+ expect(idAttribute).toBe('testAttribute');
+ });
+ it('should accept arguments', async () => {
+ const { page } = getTestState();
+
+ await page.setContent('');
+ const text = await page.$eval(
+ 'section',
+ (e, suffix) => e.textContent + suffix,
+ ' world!'
+ );
+ expect(text).toBe('hello world!');
+ });
+ it('should accept ElementHandles as arguments', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(' world
');
+ const divHandle = await page.$('div');
+ const text = await page.$eval(
+ 'section',
+ (e, div: HTMLElement) => e.textContent + div.textContent,
+ divHandle
+ );
+ expect(text).toBe('hello world');
+ });
+ it('should throw error if no element is found', async () => {
+ const { page } = getTestState();
+
+ let error = null;
+ await page
+ .$eval('section', (e) => e.id)
+ .catch((error_) => (error = error_));
+ expect(error.message).toContain(
+ 'failed to find element matching selector "section"'
+ );
+ });
+ });
+
+ describe('pierceHandler', function () {
+ beforeEach(async () => {
+ const { page } = getTestState();
+ await page.setContent(
+ ``
+ );
+ });
+ it('should find first element in shadow', async () => {
+ const { page } = getTestState();
+ const div = await page.$('pierce/.foo');
+ const text = await div.evaluate(
+ (element: Element) => element.textContent
+ );
+ expect(text).toBe('Hello');
+ });
+ it('should find all elements in shadow', async () => {
+ const { page } = getTestState();
+ const divs = await page.$$('pierce/.foo');
+ const text = await Promise.all(
+ divs.map((div) =>
+ div.evaluate((element: Element) => element.textContent)
+ )
+ );
+ expect(text.join(' ')).toBe('Hello World');
+ });
+ it('should find first child element', async () => {
+ const { page } = getTestState();
+ const parentElement = await page.$('html > div');
+ const childElement = await parentElement.$('pierce/div');
+ const text = await childElement.evaluate(
+ (element: Element) => element.textContent
+ );
+ expect(text).toBe('Hello');
+ });
+ it('should find all child elements', async () => {
+ const { page } = getTestState();
+ const parentElement = await page.$('html > div');
+ const childElements = await parentElement.$$('pierce/div');
+ const text = await Promise.all(
+ childElements.map((div) =>
+ div.evaluate((element: Element) => element.textContent)
+ )
+ );
+ expect(text.join(' ')).toBe('Hello World');
+ });
+ });
+
+ // The tests for $$eval are repeated later in this file in the test group 'QueryAll'.
+ // This is done to also test a query handler where QueryAll returns an Element[]
+ // as opposed to NodeListOf.
+ describe('Page.$$eval', function () {
+ it('should work', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(
+ 'hello
beautiful
world!
'
+ );
+ const divsCount = await page.$$eval('div', (divs) => divs.length);
+ expect(divsCount).toBe(3);
+ });
+ it('should accept extra arguments', async () => {
+ const { page } = getTestState();
+ await page.setContent(
+ 'hello
beautiful
world!
'
+ );
+ const divsCountPlus5 = await page.$$eval(
+ 'div',
+ (divs, two: number, three: number) => divs.length + two + three,
+ 2,
+ 3
+ );
+ expect(divsCountPlus5).toBe(8);
+ });
+ it('should accept ElementHandles as arguments', async () => {
+ const { page } = getTestState();
+ await page.setContent(
+ '3
'
+ );
+ const divHandle = await page.$('div');
+ const sum = await page.$$eval(
+ 'section',
+ (sections, div: HTMLElement) =>
+ sections.reduce(
+ (acc, section) => acc + Number(section.textContent),
+ 0
+ ) + Number(div.textContent),
+ divHandle
+ );
+ expect(sum).toBe(8);
+ });
+ it('should handle many elements', async () => {
+ const { page } = getTestState();
+ await page.evaluate(
+ `
+ for (var i = 0; i <= 1000; i++) {
+ const section = document.createElement('section');
+ section.textContent = i;
+ document.body.appendChild(section);
+ }
+ `
+ );
+ const sum = await page.$$eval('section', (sections) =>
+ sections.reduce((acc, section) => acc + Number(section.textContent), 0)
+ );
+ expect(sum).toBe(500500);
+ });
+ });
+
+ describe('Page.$', function () {
+ it('should query existing element', async () => {
+ const { page } = getTestState();
+
+ await page.setContent('');
+ const element = await page.$('section');
+ expect(element).toBeTruthy();
+ });
+ it('should return null for non-existing element', async () => {
+ const { page } = getTestState();
+
+ const element = await page.$('non-existing-element');
+ expect(element).toBe(null);
+ });
+ });
+
+ describe('Page.$$', function () {
+ it('should query existing elements', async () => {
+ const { page } = getTestState();
+
+ await page.setContent('A
B
');
+ const elements = await page.$$('div');
+ expect(elements.length).toBe(2);
+ const promises = elements.map((element) =>
+ page.evaluate((e: HTMLElement) => e.textContent, element)
+ );
+ expect(await Promise.all(promises)).toEqual(['A', 'B']);
+ });
+ it('should return empty array if nothing is found', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const elements = await page.$$('div');
+ expect(elements.length).toBe(0);
+ });
+ });
+
+ describe('Path.$x', function () {
+ it('should query existing element', async () => {
+ const { page } = getTestState();
+
+ await page.setContent('');
+ const elements = await page.$x('/html/body/section');
+ expect(elements[0]).toBeTruthy();
+ expect(elements.length).toBe(1);
+ });
+ it('should return empty array for non-existing element', async () => {
+ const { page } = getTestState();
+
+ const element = await page.$x('/html/body/non-existing-element');
+ expect(element).toEqual([]);
+ });
+ it('should return multiple elements', async () => {
+ const { page } = getTestState();
+
+ await page.setContent('
');
+ const elements = await page.$x('/html/body/div');
+ expect(elements.length).toBe(2);
+ });
+ });
+
+ describe('ElementHandle.$', function () {
+ it('should query existing element', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/playground.html');
+ await page.setContent(
+ ''
+ );
+ const html = await page.$('html');
+ const second = await html.$('.second');
+ const inner = await second.$('.inner');
+ const content = await page.evaluate(
+ (e: HTMLElement) => e.textContent,
+ inner
+ );
+ expect(content).toBe('A');
+ });
+
+ it('should return null for non-existing element', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(
+ ''
+ );
+ const html = await page.$('html');
+ const second = await html.$('.third');
+ expect(second).toBe(null);
+ });
+ });
+ describe('ElementHandle.$eval', function () {
+ it('should work', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(
+ ''
+ );
+ const tweet = await page.$('.tweet');
+ const content = await tweet.$eval(
+ '.like',
+ (node: HTMLElement) => node.innerText
+ );
+ expect(content).toBe('100');
+ });
+
+ it('should retrieve content from subtree', async () => {
+ const { page } = getTestState();
+
+ const htmlContent =
+ 'not-a-child-div
';
+ await page.setContent(htmlContent);
+ const elementHandle = await page.$('#myId');
+ const content = await elementHandle.$eval(
+ '.a',
+ (node: HTMLElement) => node.innerText
+ );
+ expect(content).toBe('a-child-div');
+ });
+
+ it('should throw in case of missing selector', async () => {
+ const { page } = getTestState();
+
+ const htmlContent =
+ 'not-a-child-div
';
+ await page.setContent(htmlContent);
+ const elementHandle = await page.$('#myId');
+ const errorMessage = await elementHandle
+ .$eval('.a', (node: HTMLElement) => node.innerText)
+ .catch((error) => error.message);
+ expect(errorMessage).toBe(
+ `Error: failed to find element matching selector ".a"`
+ );
+ });
+ });
+ describe('ElementHandle.$$eval', function () {
+ it('should work', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(
+ ''
+ );
+ const tweet = await page.$('.tweet');
+ const content = await tweet.$$eval('.like', (nodes: HTMLElement[]) =>
+ nodes.map((n) => n.innerText)
+ );
+ expect(content).toEqual(['100', '10']);
+ });
+
+ it('should retrieve content from subtree', async () => {
+ const { page } = getTestState();
+
+ const htmlContent =
+ 'not-a-child-div
';
+ await page.setContent(htmlContent);
+ const elementHandle = await page.$('#myId');
+ const content = await elementHandle.$$eval('.a', (nodes: HTMLElement[]) =>
+ nodes.map((n) => n.innerText)
+ );
+ expect(content).toEqual(['a1-child-div', 'a2-child-div']);
+ });
+
+ it('should not throw in case of missing selector', async () => {
+ const { page } = getTestState();
+
+ const htmlContent =
+ 'not-a-child-div
';
+ await page.setContent(htmlContent);
+ const elementHandle = await page.$('#myId');
+ const nodesLength = await elementHandle.$$eval(
+ '.a',
+ (nodes) => nodes.length
+ );
+ expect(nodesLength).toBe(0);
+ });
+ });
+
+ describe('ElementHandle.$$', function () {
+ it('should query existing elements', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(
+ 'A
B
'
+ );
+ const html = await page.$('html');
+ const elements = await html.$$('div');
+ expect(elements.length).toBe(2);
+ const promises = elements.map((element) =>
+ page.evaluate((e: HTMLElement) => e.textContent, element)
+ );
+ expect(await Promise.all(promises)).toEqual(['A', 'B']);
+ });
+
+ it('should return empty array for non-existing elements', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(
+ 'A B '
+ );
+ const html = await page.$('html');
+ const elements = await html.$$('div');
+ expect(elements.length).toBe(0);
+ });
+ });
+
+ describe('ElementHandle.$x', function () {
+ it('should query existing element', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.PREFIX + '/playground.html');
+ await page.setContent(
+ ''
+ );
+ const html = await page.$('html');
+ const second = await html.$x(`./body/div[contains(@class, 'second')]`);
+ const inner = await second[0].$x(`./div[contains(@class, 'inner')]`);
+ const content = await page.evaluate(
+ (e: HTMLElement) => e.textContent,
+ inner[0]
+ );
+ expect(content).toBe('A');
+ });
+
+ it('should return null for non-existing element', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(
+ ''
+ );
+ const html = await page.$('html');
+ const second = await html.$x(`/div[contains(@class, 'third')]`);
+ expect(second).toEqual([]);
+ });
+ });
+
+ // This is the same tests for `$$eval` and `$$` as above, but with a queryAll
+ // handler that returns an array instead of a list of nodes.
+ describe('QueryAll', function () {
+ const handler: CustomQueryHandler = {
+ queryAll: (element: Element, selector: string) =>
+ Array.from(element.querySelectorAll(selector)),
+ };
+ before(() => {
+ const { puppeteer } = getTestState();
+ puppeteer.registerCustomQueryHandler('allArray', handler);
+ });
+
+ it('should have registered handler', async () => {
+ const { puppeteer } = getTestState();
+ expect(
+ puppeteer.customQueryHandlerNames().includes('allArray')
+ ).toBeTruthy();
+ });
+ it('$$ should query existing elements', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(
+ 'A
B
'
+ );
+ const html = await page.$('html');
+ const elements = await html.$$('allArray/div');
+ expect(elements.length).toBe(2);
+ const promises = elements.map((element) =>
+ page.evaluate((e: HTMLElement) => e.textContent, element)
+ );
+ expect(await Promise.all(promises)).toEqual(['A', 'B']);
+ });
+
+ it('$$ should return empty array for non-existing elements', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(
+ 'A B '
+ );
+ const html = await page.$('html');
+ const elements = await html.$$('allArray/div');
+ expect(elements.length).toBe(0);
+ });
+ it('$$eval should work', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(
+ 'hello
beautiful
world!
'
+ );
+ const divsCount = await page.$$eval(
+ 'allArray/div',
+ (divs) => divs.length
+ );
+ expect(divsCount).toBe(3);
+ });
+ it('$$eval should accept extra arguments', async () => {
+ const { page } = getTestState();
+ await page.setContent(
+ 'hello
beautiful
world!
'
+ );
+ const divsCountPlus5 = await page.$$eval(
+ 'allArray/div',
+ (divs, two: number, three: number) => divs.length + two + three,
+ 2,
+ 3
+ );
+ expect(divsCountPlus5).toBe(8);
+ });
+ it('$$eval should accept ElementHandles as arguments', async () => {
+ const { page } = getTestState();
+ await page.setContent(
+ '3
'
+ );
+ const divHandle = await page.$('div');
+ const sum = await page.$$eval(
+ 'allArray/section',
+ (sections, div: HTMLElement) =>
+ sections.reduce(
+ (acc, section) => acc + Number(section.textContent),
+ 0
+ ) + Number(div.textContent),
+ divHandle
+ );
+ expect(sum).toBe(8);
+ });
+ it('$$eval should handle many elements', async () => {
+ const { page } = getTestState();
+ await page.evaluate(
+ `
+ for (var i = 0; i <= 1000; i++) {
+ const section = document.createElement('section');
+ section.textContent = i;
+ document.body.appendChild(section);
+ }
+ `
+ );
+ const sum = await page.$$eval('allArray/section', (sections) =>
+ sections.reduce((acc, section) => acc + Number(section.textContent), 0)
+ );
+ expect(sum).toBe(500500);
+ });
+ });
+});
diff --git a/test/requestinterception-experimental.spec.ts b/test/requestinterception-experimental.spec.ts
new file mode 100644
index 0000000000000..70f87b3604b57
--- /dev/null
+++ b/test/requestinterception-experimental.spec.ts
@@ -0,0 +1,873 @@
+/**
+ * Copyright 2021 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import fs from 'fs';
+import path from 'path';
+import utils from './utils.js';
+import expect from 'expect';
+import {
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+ describeFailsFirefox,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+import { ActionResult } from '../lib/cjs/puppeteer/api-docs-entry.js';
+import { InterceptResolutionAction } from '../lib/cjs/puppeteer/common/HTTPRequest.js';
+
+describe('request interception', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+ describeFailsFirefox('Page.setRequestInterception', function () {
+ const expectedActions: ActionResult[] = ['abort', 'continue', 'respond'];
+ while (expectedActions.length > 0) {
+ const expectedAction = expectedActions.pop();
+ it(`should cooperatively ${expectedAction} by priority`, async () => {
+ const { page, server } = getTestState();
+
+ const actionResults: ActionResult[] = [];
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ if (request.url().endsWith('.css'))
+ request.continue(
+ { headers: { ...request.headers(), xaction: 'continue' } },
+ expectedAction === 'continue' ? 1 : 0
+ );
+ else request.continue({}, 0);
+ });
+ page.on('request', (request) => {
+ if (request.url().endsWith('.css'))
+ request.respond(
+ { headers: { xaction: 'respond' } },
+ expectedAction === 'respond' ? 1 : 0
+ );
+ else request.continue({}, 0);
+ });
+ page.on('request', (request) => {
+ if (request.url().endsWith('.css'))
+ request.abort('aborted', expectedAction === 'abort' ? 1 : 0);
+ else request.continue({}, 0);
+ });
+ page.on('response', (response) => {
+ const { xaction } = response.headers();
+ if (response.url().endsWith('.css') && !!xaction)
+ actionResults.push(xaction as ActionResult);
+ });
+ page.on('requestfailed', (request) => {
+ if (request.url().endsWith('.css')) actionResults.push('abort');
+ });
+
+ const response = await (async () => {
+ if (expectedAction === 'continue') {
+ const [serverRequest, response] = await Promise.all([
+ server.waitForRequest('/one-style.css'),
+ page.goto(server.PREFIX + '/one-style.html'),
+ ]);
+ actionResults.push(serverRequest.headers.xaction as ActionResult);
+ return response;
+ } else {
+ return await page.goto(server.PREFIX + '/one-style.html');
+ }
+ })();
+
+ expect(actionResults.length).toBe(1);
+ expect(actionResults[0]).toBe(expectedAction);
+ expect(response.ok()).toBe(true);
+ });
+ }
+
+ it('should intercept', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ if (utils.isFavicon(request)) {
+ request.continue({}, 0);
+ return;
+ }
+ expect(request.url()).toContain('empty.html');
+ expect(request.headers()['user-agent']).toBeTruthy();
+ expect(request.method()).toBe('GET');
+ expect(request.postData()).toBe(undefined);
+ expect(request.isNavigationRequest()).toBe(true);
+ expect(request.resourceType()).toBe('document');
+ expect(request.frame() === page.mainFrame()).toBe(true);
+ expect(request.frame().url()).toBe('about:blank');
+ request.continue({}, 0);
+ });
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.ok()).toBe(true);
+ expect(response.remoteAddress().port).toBe(server.PORT);
+ });
+ // @see https://github.com/puppeteer/puppeteer/pull/3105
+ it('should work when POST is redirected with 302', async () => {
+ const { page, server } = getTestState();
+
+ server.setRedirect('/rredirect', '/empty.html');
+ await page.goto(server.EMPTY_PAGE);
+ await page.setRequestInterception(true);
+ page.on('request', (request) => request.continue({}, 0));
+ await page.setContent(`
+
+ `);
+ await Promise.all([
+ page.$eval('form', (form: HTMLFormElement) => form.submit()),
+ page.waitForNavigation(),
+ ]);
+ });
+ // @see https://github.com/puppeteer/puppeteer/issues/3973
+ it('should work when header manipulation headers with redirect', async () => {
+ const { page, server } = getTestState();
+
+ server.setRedirect('/rrredirect', '/empty.html');
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ const headers = Object.assign({}, request.headers(), {
+ foo: 'bar',
+ });
+ request.continue({ headers }, 0);
+
+ expect(request.continueRequestOverrides()).toEqual({ headers });
+ });
+ // Make sure that the goto does not time out.
+ await page.goto(server.PREFIX + '/rrredirect');
+ });
+ // @see https://github.com/puppeteer/puppeteer/issues/4743
+ it('should be able to remove headers', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ const headers = Object.assign({}, request.headers(), {
+ foo: 'bar',
+ origin: undefined, // remove "origin" header
+ });
+ request.continue({ headers }, 0);
+ });
+
+ const [serverRequest] = await Promise.all([
+ server.waitForRequest('/empty.html'),
+ page.goto(server.PREFIX + '/empty.html'),
+ ]);
+
+ expect(serverRequest.headers.origin).toBe(undefined);
+ });
+ it('should contain referer header', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ const requests = [];
+ page.on('request', (request) => {
+ if (!utils.isFavicon(request)) requests.push(request);
+ request.continue({}, 0);
+ });
+ await page.goto(server.PREFIX + '/one-style.html');
+ expect(requests[1].url()).toContain('/one-style.css');
+ expect(requests[1].headers().referer).toContain('/one-style.html');
+ });
+ it('should properly return navigation response when URL has cookies', async () => {
+ const { page, server } = getTestState();
+
+ // Setup cookie.
+ await page.goto(server.EMPTY_PAGE);
+ await page.setCookie({ name: 'foo', value: 'bar' });
+
+ // Setup request interception.
+ await page.setRequestInterception(true);
+ page.on('request', (request) => request.continue({}, 0));
+ const response = await page.reload();
+ expect(response.status()).toBe(200);
+ });
+ it('should stop intercepting', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.once('request', (request) => request.continue({}, 0));
+ await page.goto(server.EMPTY_PAGE);
+ await page.setRequestInterception(false);
+ await page.goto(server.EMPTY_PAGE);
+ });
+ it('should show custom HTTP headers', async () => {
+ const { page, server } = getTestState();
+
+ await page.setExtraHTTPHeaders({
+ foo: 'bar',
+ });
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ expect(request.headers()['foo']).toBe('bar');
+ request.continue({}, 0);
+ });
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.ok()).toBe(true);
+ });
+ // @see https://github.com/puppeteer/puppeteer/issues/4337
+ it('should work with redirect inside sync XHR', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ server.setRedirect('/logo.png', '/pptr.png');
+ await page.setRequestInterception(true);
+ page.on('request', (request) => request.continue({}, 0));
+ const status = await page.evaluate(async () => {
+ const request = new XMLHttpRequest();
+ request.open('GET', '/logo.png', false); // `false` makes the request synchronous
+ request.send(null);
+ return request.status;
+ });
+ expect(status).toBe(200);
+ });
+ it('should work with custom referer headers', async () => {
+ const { page, server } = getTestState();
+
+ await page.setExtraHTTPHeaders({ referer: server.EMPTY_PAGE });
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ expect(request.headers()['referer']).toBe(server.EMPTY_PAGE);
+ request.continue({}, 0);
+ });
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.ok()).toBe(true);
+ });
+ it('should be abortable', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ if (request.url().endsWith('.css')) request.abort('failed', 0);
+ else request.continue({}, 0);
+ });
+ let failedRequests = 0;
+ page.on('requestfailed', () => ++failedRequests);
+ const response = await page.goto(server.PREFIX + '/one-style.html');
+ expect(response.ok()).toBe(true);
+ expect(response.request().failure()).toBe(null);
+ expect(failedRequests).toBe(1);
+ });
+ it('should be able to access the error reason', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ request.abort('failed', 0);
+ });
+ let abortReason = null;
+ page.on('request', (request) => {
+ abortReason = request.abortErrorReason();
+ request.continue({}, 0);
+ });
+ await page.goto(server.EMPTY_PAGE).catch(() => {});
+ expect(abortReason).toBe('Failed');
+ });
+ it('should be abortable with custom error codes', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ request.abort('internetdisconnected', 0);
+ });
+ let failedRequest = null;
+ page.on('requestfailed', (request) => (failedRequest = request));
+ await page.goto(server.EMPTY_PAGE).catch(() => {});
+ expect(failedRequest).toBeTruthy();
+ expect(failedRequest.failure().errorText).toBe(
+ 'net::ERR_INTERNET_DISCONNECTED'
+ );
+ });
+ it('should send referer', async () => {
+ const { page, server } = getTestState();
+
+ await page.setExtraHTTPHeaders({
+ referer: 'http://google.com/',
+ });
+ await page.setRequestInterception(true);
+ page.on('request', (request) => request.continue({}, 0));
+ const [request] = await Promise.all([
+ server.waitForRequest('/grid.html'),
+ page.goto(server.PREFIX + '/grid.html'),
+ ]);
+ expect(request.headers['referer']).toBe('http://google.com/');
+ });
+ it('should fail navigation when aborting main resource', async () => {
+ const { page, server, isChrome } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => request.abort('failed', 0));
+ let error = null;
+ await page.goto(server.EMPTY_PAGE).catch((error_) => (error = error_));
+ expect(error).toBeTruthy();
+ if (isChrome) expect(error.message).toContain('net::ERR_FAILED');
+ else expect(error.message).toContain('NS_ERROR_FAILURE');
+ });
+ it('should work with redirects', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ const requests = [];
+ page.on('request', (request) => {
+ request.continue({}, 0);
+ requests.push(request);
+ });
+ server.setRedirect(
+ '/non-existing-page.html',
+ '/non-existing-page-2.html'
+ );
+ server.setRedirect(
+ '/non-existing-page-2.html',
+ '/non-existing-page-3.html'
+ );
+ server.setRedirect(
+ '/non-existing-page-3.html',
+ '/non-existing-page-4.html'
+ );
+ server.setRedirect('/non-existing-page-4.html', '/empty.html');
+ const response = await page.goto(
+ server.PREFIX + '/non-existing-page.html'
+ );
+ expect(response.status()).toBe(200);
+ expect(response.url()).toContain('empty.html');
+ expect(requests.length).toBe(5);
+ expect(requests[2].resourceType()).toBe('document');
+ // Check redirect chain
+ const redirectChain = response.request().redirectChain();
+ expect(redirectChain.length).toBe(4);
+ expect(redirectChain[0].url()).toContain('/non-existing-page.html');
+ expect(redirectChain[2].url()).toContain('/non-existing-page-3.html');
+ for (let i = 0; i < redirectChain.length; ++i) {
+ const request = redirectChain[i];
+ expect(request.isNavigationRequest()).toBe(true);
+ expect(request.redirectChain().indexOf(request)).toBe(i);
+ }
+ });
+ it('should work with redirects for subresources', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ const requests = [];
+ page.on('request', (request) => {
+ request.continue({}, 0);
+ if (!utils.isFavicon(request)) requests.push(request);
+ });
+ server.setRedirect('/one-style.css', '/two-style.css');
+ server.setRedirect('/two-style.css', '/three-style.css');
+ server.setRedirect('/three-style.css', '/four-style.css');
+ server.setRoute('/four-style.css', (req, res) =>
+ res.end('body {box-sizing: border-box; }')
+ );
+
+ const response = await page.goto(server.PREFIX + '/one-style.html');
+ expect(response.status()).toBe(200);
+ expect(response.url()).toContain('one-style.html');
+ expect(requests.length).toBe(5);
+ expect(requests[0].resourceType()).toBe('document');
+ expect(requests[1].resourceType()).toBe('stylesheet');
+ // Check redirect chain
+ const redirectChain = requests[1].redirectChain();
+ expect(redirectChain.length).toBe(3);
+ expect(redirectChain[0].url()).toContain('/one-style.css');
+ expect(redirectChain[2].url()).toContain('/three-style.css');
+ });
+ it('should be able to abort redirects', async () => {
+ const { page, server, isChrome } = getTestState();
+
+ await page.setRequestInterception(true);
+ server.setRedirect('/non-existing.json', '/non-existing-2.json');
+ server.setRedirect('/non-existing-2.json', '/simple.html');
+ page.on('request', (request) => {
+ if (request.url().includes('non-existing-2'))
+ request.abort('failed', 0);
+ else request.continue({}, 0);
+ });
+ await page.goto(server.EMPTY_PAGE);
+ const result = await page.evaluate(async () => {
+ try {
+ await fetch('/non-existing.json');
+ } catch (error) {
+ return error.message;
+ }
+ });
+ if (isChrome) expect(result).toContain('Failed to fetch');
+ else expect(result).toContain('NetworkError');
+ });
+ it('should work with equal requests', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ let responseCount = 1;
+ server.setRoute('/zzz', (req, res) => res.end(responseCount++ * 11 + ''));
+ await page.setRequestInterception(true);
+
+ let spinner = false;
+ // Cancel 2nd request.
+ page.on('request', (request) => {
+ if (utils.isFavicon(request)) {
+ request.continue({}, 0);
+ return;
+ }
+ spinner ? request.abort('failed', 0) : request.continue({}, 0);
+ spinner = !spinner;
+ });
+ const results = await page.evaluate(() =>
+ Promise.all([
+ fetch('/zzz')
+ .then((response) => response.text())
+ .catch(() => 'FAILED'),
+ fetch('/zzz')
+ .then((response) => response.text())
+ .catch(() => 'FAILED'),
+ fetch('/zzz')
+ .then((response) => response.text())
+ .catch(() => 'FAILED'),
+ ])
+ );
+ expect(results).toEqual(['11', 'FAILED', '22']);
+ });
+ it('should navigate to dataURL and fire dataURL requests', async () => {
+ const { page } = getTestState();
+
+ await page.setRequestInterception(true);
+ const requests = [];
+ page.on('request', (request) => {
+ requests.push(request);
+ request.continue({}, 0);
+ });
+ const dataURL = 'data:text/html,yo
';
+ const response = await page.goto(dataURL);
+ expect(response.status()).toBe(200);
+ expect(requests.length).toBe(1);
+ expect(requests[0].url()).toBe(dataURL);
+ });
+ it('should be able to fetch dataURL and fire dataURL requests', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.setRequestInterception(true);
+ const requests = [];
+ page.on('request', (request) => {
+ !utils.isFavicon(request) && requests.push(request);
+ request.continue({}, 0);
+ });
+ const dataURL = 'data:text/html,yo
';
+ const text = await page.evaluate(
+ (url: string) => fetch(url).then((r) => r.text()),
+ dataURL
+ );
+ expect(text).toBe('yo
');
+ expect(requests.length).toBe(1);
+ expect(requests[0].url()).toBe(dataURL);
+ });
+ it('should navigate to URL with hash and fire requests without hash', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ const requests = [];
+ page.on('request', (request) => {
+ requests.push(request);
+ request.continue({}, 0);
+ });
+ const response = await page.goto(server.EMPTY_PAGE + '#hash');
+ expect(response.status()).toBe(200);
+ expect(response.url()).toBe(server.EMPTY_PAGE);
+ expect(requests.length).toBe(1);
+ expect(requests[0].url()).toBe(server.EMPTY_PAGE);
+ });
+ it('should work with encoded server', async () => {
+ const { page, server } = getTestState();
+
+ // The requestWillBeSent will report encoded URL, whereas interception will
+ // report URL as-is. @see crbug.com/759388
+ await page.setRequestInterception(true);
+ page.on('request', (request) => request.continue({}, 0));
+ const response = await page.goto(
+ server.PREFIX + '/some nonexisting page'
+ );
+ expect(response.status()).toBe(404);
+ });
+ it('should work with badly encoded server', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ server.setRoute('/malformed?rnd=%911', (req, res) => res.end());
+ page.on('request', (request) => request.continue({}, 0));
+ const response = await page.goto(server.PREFIX + '/malformed?rnd=%911');
+ expect(response.status()).toBe(200);
+ });
+ it('should work with encoded server - 2', async () => {
+ const { page, server } = getTestState();
+
+ // The requestWillBeSent will report URL as-is, whereas interception will
+ // report encoded URL for stylesheet. @see crbug.com/759388
+ await page.setRequestInterception(true);
+ const requests = [];
+ page.on('request', (request) => {
+ request.continue({}, 0);
+ requests.push(request);
+ });
+ const response = await page.goto(
+ `data:text/html, `
+ );
+ expect(response.status()).toBe(200);
+ expect(requests.length).toBe(2);
+ expect(requests[1].response().status()).toBe(404);
+ });
+ it('should not throw "Invalid Interception Id" if the request was cancelled', async () => {
+ const { page, server } = getTestState();
+
+ await page.setContent('');
+ await page.setRequestInterception(true);
+ let request = null;
+ page.on('request', async (r) => (request = r));
+ page.$eval(
+ 'iframe',
+ (frame: HTMLIFrameElement, url: string) => (frame.src = url),
+ server.EMPTY_PAGE
+ ),
+ // Wait for request interception.
+ await utils.waitEvent(page, 'request');
+ // Delete frame to cause request to be canceled.
+ await page.$eval('iframe', (frame) => frame.remove());
+ let error = null;
+ await request.continue({}, 0).catch((error_) => (error = error_));
+ expect(error).toBe(null);
+ });
+ it('should throw if interception is not enabled', async () => {
+ const { page, server } = getTestState();
+
+ let error = null;
+ page.on('request', async (request) => {
+ try {
+ await request.continue({}, 0);
+ } catch (error_) {
+ error = error_;
+ }
+ });
+ await page.goto(server.EMPTY_PAGE);
+ expect(error.message).toContain('Request Interception is not enabled');
+ });
+ it('should work with file URLs', async () => {
+ const { page } = getTestState();
+
+ await page.setRequestInterception(true);
+ const urls = new Set();
+ page.on('request', (request) => {
+ urls.add(request.url().split('/').pop());
+ request.continue({}, 0);
+ });
+ await page.goto(
+ pathToFileURL(path.join(__dirname, 'assets', 'one-style.html'))
+ );
+ expect(urls.size).toBe(2);
+ expect(urls.has('one-style.html')).toBe(true);
+ expect(urls.has('one-style.css')).toBe(true);
+ });
+ it('should not cache if cache disabled', async () => {
+ const { page, server } = getTestState();
+
+ // Load and re-load to make sure it's cached.
+ await page.goto(server.PREFIX + '/cached/one-style.html');
+
+ await page.setRequestInterception(true);
+ await page.setCacheEnabled(false);
+ page.on('request', (request) => request.continue({}, 0));
+
+ const cached = [];
+ page.on('requestservedfromcache', (r) => cached.push(r));
+
+ await page.reload();
+ expect(cached.length).toBe(0);
+ });
+ it('should cache if cache enabled', async () => {
+ const { page, server } = getTestState();
+
+ // Load and re-load to make sure it's cached.
+ await page.goto(server.PREFIX + '/cached/one-style.html');
+
+ await page.setRequestInterception(true);
+ await page.setCacheEnabled(true);
+ page.on('request', (request) => request.continue({}, 0));
+
+ const cached = [];
+ page.on('requestservedfromcache', (r) => cached.push(r));
+
+ await page.reload();
+ expect(cached.length).toBe(1);
+ });
+ it('should load fonts if cache enabled', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ await page.setCacheEnabled(true);
+ page.on('request', (request) => request.continue({}, 0));
+
+ await page.goto(server.PREFIX + '/cached/one-style-font.html');
+ await page.waitForResponse((r) => r.url().endsWith('/one-style.woff'));
+ });
+ });
+
+ describeFailsFirefox('Request.continue', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => request.continue({}, 0));
+ await page.goto(server.EMPTY_PAGE);
+ });
+ it('should amend HTTP headers', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ const headers = Object.assign({}, request.headers());
+ headers['FOO'] = 'bar';
+ request.continue({ headers }, 0);
+ });
+ await page.goto(server.EMPTY_PAGE);
+ const [request] = await Promise.all([
+ server.waitForRequest('/sleep.zzz'),
+ page.evaluate(() => fetch('/sleep.zzz')),
+ ]);
+ expect(request.headers['foo']).toBe('bar');
+ });
+ it('should redirect in a way non-observable to page', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ const redirectURL = request.url().includes('/empty.html')
+ ? server.PREFIX + '/consolelog.html'
+ : undefined;
+ request.continue({ url: redirectURL }, 0);
+ });
+ let consoleMessage = null;
+ page.on('console', (msg) => (consoleMessage = msg));
+ await page.goto(server.EMPTY_PAGE);
+ expect(page.url()).toBe(server.EMPTY_PAGE);
+ expect(consoleMessage.text()).toBe('yellow');
+ });
+ it('should amend method', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ request.continue({ method: 'POST' }, 0);
+ });
+ const [request] = await Promise.all([
+ server.waitForRequest('/sleep.zzz'),
+ page.evaluate(() => fetch('/sleep.zzz')),
+ ]);
+ expect(request.method).toBe('POST');
+ });
+ it('should amend post data', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ request.continue({ postData: 'doggo' }, 0);
+ });
+ const [serverRequest] = await Promise.all([
+ server.waitForRequest('/sleep.zzz'),
+ page.evaluate(() =>
+ fetch('/sleep.zzz', { method: 'POST', body: 'birdy' })
+ ),
+ ]);
+ expect(await serverRequest.postBody).toBe('doggo');
+ });
+ it('should amend both post data and method on navigation', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ request.continue({ method: 'POST', postData: 'doggo' }, 0);
+ });
+ const [serverRequest] = await Promise.all([
+ server.waitForRequest('/empty.html'),
+ page.goto(server.EMPTY_PAGE),
+ ]);
+ expect(serverRequest.method).toBe('POST');
+ expect(await serverRequest.postBody).toBe('doggo');
+ });
+ });
+
+ describeFailsFirefox('Request.respond', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ request.respond(
+ {
+ status: 201,
+ headers: {
+ foo: 'bar',
+ },
+ body: 'Yo, page!',
+ },
+ 0
+ );
+ });
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.status()).toBe(201);
+ expect(response.headers().foo).toBe('bar');
+ expect(await page.evaluate(() => document.body.textContent)).toBe(
+ 'Yo, page!'
+ );
+ });
+ it('should be able to access the response', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ request.respond(
+ {
+ status: 200,
+ body: 'Yo, page!',
+ },
+ 0
+ );
+ });
+ let response = null;
+ page.on('request', (request) => {
+ response = request.responseForRequest();
+ request.continue({}, 0);
+ });
+ await page.goto(server.EMPTY_PAGE);
+ expect(response).toEqual({ status: 200, body: 'Yo, page!' });
+ });
+ it('should work with status code 422', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ request.respond(
+ {
+ status: 422,
+ body: 'Yo, page!',
+ },
+ 0
+ );
+ });
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.status()).toBe(422);
+ expect(response.statusText()).toBe('Unprocessable Entity');
+ expect(await page.evaluate(() => document.body.textContent)).toBe(
+ 'Yo, page!'
+ );
+ });
+ it('should redirect', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ if (!request.url().includes('rrredirect')) {
+ request.continue({}, 0);
+ return;
+ }
+ request.respond(
+ {
+ status: 302,
+ headers: {
+ location: server.EMPTY_PAGE,
+ },
+ },
+ 0
+ );
+ });
+ const response = await page.goto(server.PREFIX + '/rrredirect');
+ expect(response.request().redirectChain().length).toBe(1);
+ expect(response.request().redirectChain()[0].url()).toBe(
+ server.PREFIX + '/rrredirect'
+ );
+ expect(response.url()).toBe(server.EMPTY_PAGE);
+ });
+ it('should allow mocking binary responses', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ const imageBuffer = fs.readFileSync(
+ path.join(__dirname, 'assets', 'pptr.png')
+ );
+ request.respond(
+ {
+ contentType: 'image/png',
+ body: imageBuffer,
+ },
+ 0
+ );
+ });
+ await page.evaluate((PREFIX) => {
+ const img = document.createElement('img');
+ img.src = PREFIX + '/does-not-exist.png';
+ document.body.appendChild(img);
+ return new Promise((fulfill) => (img.onload = fulfill));
+ }, server.PREFIX);
+ const img = await page.$('img');
+ expect(await img.screenshot()).toBeGolden('mock-binary-response.png');
+ });
+ it('should stringify intercepted request response headers', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ request.respond(
+ {
+ status: 200,
+ headers: {
+ foo: true,
+ },
+ body: 'Yo, page!',
+ },
+ 0
+ );
+ });
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.status()).toBe(200);
+ const headers = response.headers();
+ expect(headers.foo).toBe('true');
+ expect(await page.evaluate(() => document.body.textContent)).toBe(
+ 'Yo, page!'
+ );
+ });
+ it('should indicate already-handled if an intercept has been handled', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ request.continue();
+ });
+ page.on('request', (request) => {
+ expect(request.isInterceptResolutionHandled()).toBeTruthy();
+ });
+ page.on('request', (request) => {
+ const { action } = request.interceptResolutionState();
+ expect(action).toBe(InterceptResolutionAction.AlreadyHandled);
+ });
+ await page.goto(server.EMPTY_PAGE);
+ });
+ });
+});
+
+function pathToFileURL(path: string): string {
+ let pathName = path.replace(/\\/g, '/');
+ // Windows drive letter must be prefixed with a slash.
+ if (!pathName.startsWith('/')) pathName = '/' + pathName;
+ return 'file://' + pathName;
+}
diff --git a/test/requestinterception.spec.ts b/test/requestinterception.spec.ts
new file mode 100644
index 0000000000000..0602e2f915c94
--- /dev/null
+++ b/test/requestinterception.spec.ts
@@ -0,0 +1,818 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import fs from 'fs';
+import path from 'path';
+import utils from './utils.js';
+import expect from 'expect';
+import {
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+ describeFailsFirefox,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+
+describe('request interception', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+ describeFailsFirefox('Page.setRequestInterception', function () {
+ it('should intercept', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ if (utils.isFavicon(request)) {
+ request.continue();
+ return;
+ }
+ expect(request.url()).toContain('empty.html');
+ expect(request.headers()['user-agent']).toBeTruthy();
+ expect(request.headers()['accept']).toBeTruthy();
+ expect(request.method()).toBe('GET');
+ expect(request.postData()).toBe(undefined);
+ expect(request.isNavigationRequest()).toBe(true);
+ expect(request.resourceType()).toBe('document');
+ expect(request.frame() === page.mainFrame()).toBe(true);
+ expect(request.frame().url()).toBe('about:blank');
+ request.continue();
+ });
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.ok()).toBe(true);
+ expect(response.remoteAddress().port).toBe(server.PORT);
+ });
+ // @see https://github.com/puppeteer/puppeteer/pull/3105
+ it('should work when POST is redirected with 302', async () => {
+ const { page, server } = getTestState();
+
+ server.setRedirect('/rredirect', '/empty.html');
+ await page.goto(server.EMPTY_PAGE);
+ await page.setRequestInterception(true);
+ page.on('request', (request) => request.continue());
+ await page.setContent(`
+
+ `);
+ await Promise.all([
+ page.$eval('form', (form: HTMLFormElement) => form.submit()),
+ page.waitForNavigation(),
+ ]);
+ });
+ // @see https://github.com/puppeteer/puppeteer/issues/3973
+ it('should work when header manipulation headers with redirect', async () => {
+ const { page, server } = getTestState();
+
+ server.setRedirect('/rrredirect', '/empty.html');
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ const headers = Object.assign({}, request.headers(), {
+ foo: 'bar',
+ });
+ request.continue({ headers });
+ });
+ await page.goto(server.PREFIX + '/rrredirect');
+ });
+ // @see https://github.com/puppeteer/puppeteer/issues/4743
+ it('should be able to remove headers', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ const headers = Object.assign({}, request.headers(), {
+ foo: 'bar',
+ origin: undefined, // remove "origin" header
+ });
+ request.continue({ headers });
+ });
+
+ const [serverRequest] = await Promise.all([
+ server.waitForRequest('/empty.html'),
+ page.goto(server.PREFIX + '/empty.html'),
+ ]);
+
+ expect(serverRequest.headers.origin).toBe(undefined);
+ });
+ it('should contain referer header', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ const requests = [];
+ page.on('request', (request) => {
+ if (!utils.isFavicon(request)) requests.push(request);
+ request.continue();
+ });
+ await page.goto(server.PREFIX + '/one-style.html');
+ expect(requests[1].url()).toContain('/one-style.css');
+ expect(requests[1].headers().referer).toContain('/one-style.html');
+ });
+ it('should properly return navigation response when URL has cookies', async () => {
+ const { page, server } = getTestState();
+
+ // Setup cookie.
+ await page.goto(server.EMPTY_PAGE);
+ await page.setCookie({ name: 'foo', value: 'bar' });
+
+ // Setup request interception.
+ await page.setRequestInterception(true);
+ page.on('request', (request) => request.continue());
+ const response = await page.reload();
+ expect(response.status()).toBe(200);
+ });
+ it('should stop intercepting', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.once('request', (request) => request.continue());
+ await page.goto(server.EMPTY_PAGE);
+ await page.setRequestInterception(false);
+ await page.goto(server.EMPTY_PAGE);
+ });
+ it('should show custom HTTP headers', async () => {
+ const { page, server } = getTestState();
+
+ await page.setExtraHTTPHeaders({
+ foo: 'bar',
+ });
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ expect(request.headers()['foo']).toBe('bar');
+ request.continue();
+ });
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.ok()).toBe(true);
+ });
+ // @see https://github.com/puppeteer/puppeteer/issues/4337
+ it('should work with redirect inside sync XHR', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ server.setRedirect('/logo.png', '/pptr.png');
+ await page.setRequestInterception(true);
+ page.on('request', (request) => request.continue());
+ const status = await page.evaluate(async () => {
+ const request = new XMLHttpRequest();
+ request.open('GET', '/logo.png', false); // `false` makes the request synchronous
+ request.send(null);
+ return request.status;
+ });
+ expect(status).toBe(200);
+ });
+ it('should work with custom referer headers', async () => {
+ const { page, server } = getTestState();
+
+ await page.setExtraHTTPHeaders({ referer: server.EMPTY_PAGE });
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ expect(request.headers()['referer']).toBe(server.EMPTY_PAGE);
+ request.continue();
+ });
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.ok()).toBe(true);
+ });
+ it('should be abortable', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ if (request.url().endsWith('.css')) request.abort();
+ else request.continue();
+ });
+ let failedRequests = 0;
+ page.on('requestfailed', () => ++failedRequests);
+ const response = await page.goto(server.PREFIX + '/one-style.html');
+ expect(response.ok()).toBe(true);
+ expect(response.request().failure()).toBe(null);
+ expect(failedRequests).toBe(1);
+ });
+ it('should be abortable with custom error codes', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ request.abort('internetdisconnected');
+ });
+ let failedRequest = null;
+ page.on('requestfailed', (request) => (failedRequest = request));
+ await page.goto(server.EMPTY_PAGE).catch(() => {});
+ expect(failedRequest).toBeTruthy();
+ expect(failedRequest.failure().errorText).toBe(
+ 'net::ERR_INTERNET_DISCONNECTED'
+ );
+ });
+ it('should send referer', async () => {
+ const { page, server } = getTestState();
+
+ await page.setExtraHTTPHeaders({
+ referer: 'http://google.com/',
+ });
+ await page.setRequestInterception(true);
+ page.on('request', (request) => request.continue());
+ const [request] = await Promise.all([
+ server.waitForRequest('/grid.html'),
+ page.goto(server.PREFIX + '/grid.html'),
+ ]);
+ expect(request.headers['referer']).toBe('http://google.com/');
+ });
+ it('should fail navigation when aborting main resource', async () => {
+ const { page, server, isChrome } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => request.abort());
+ let error = null;
+ await page.goto(server.EMPTY_PAGE).catch((error_) => (error = error_));
+ expect(error).toBeTruthy();
+ if (isChrome) expect(error.message).toContain('net::ERR_FAILED');
+ else expect(error.message).toContain('NS_ERROR_FAILURE');
+ });
+ it('should work with redirects', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ const requests = [];
+ page.on('request', (request) => {
+ request.continue();
+ requests.push(request);
+ });
+ server.setRedirect(
+ '/non-existing-page.html',
+ '/non-existing-page-2.html'
+ );
+ server.setRedirect(
+ '/non-existing-page-2.html',
+ '/non-existing-page-3.html'
+ );
+ server.setRedirect(
+ '/non-existing-page-3.html',
+ '/non-existing-page-4.html'
+ );
+ server.setRedirect('/non-existing-page-4.html', '/empty.html');
+ const response = await page.goto(
+ server.PREFIX + '/non-existing-page.html'
+ );
+ expect(response.status()).toBe(200);
+ expect(response.url()).toContain('empty.html');
+ expect(requests.length).toBe(5);
+ expect(requests[2].resourceType()).toBe('document');
+ // Check redirect chain
+ const redirectChain = response.request().redirectChain();
+ expect(redirectChain.length).toBe(4);
+ expect(redirectChain[0].url()).toContain('/non-existing-page.html');
+ expect(redirectChain[2].url()).toContain('/non-existing-page-3.html');
+ for (let i = 0; i < redirectChain.length; ++i) {
+ const request = redirectChain[i];
+ expect(request.isNavigationRequest()).toBe(true);
+ expect(request.redirectChain().indexOf(request)).toBe(i);
+ }
+ });
+ it('should work with redirects for subresources', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ const requests = [];
+ page.on('request', (request) => {
+ request.continue();
+ if (!utils.isFavicon(request)) requests.push(request);
+ });
+ server.setRedirect('/one-style.css', '/two-style.css');
+ server.setRedirect('/two-style.css', '/three-style.css');
+ server.setRedirect('/three-style.css', '/four-style.css');
+ server.setRoute('/four-style.css', (req, res) =>
+ res.end('body {box-sizing: border-box; }')
+ );
+
+ const response = await page.goto(server.PREFIX + '/one-style.html');
+ expect(response.status()).toBe(200);
+ expect(response.url()).toContain('one-style.html');
+ expect(requests.length).toBe(5);
+ expect(requests[0].resourceType()).toBe('document');
+ expect(requests[1].resourceType()).toBe('stylesheet');
+ // Check redirect chain
+ const redirectChain = requests[1].redirectChain();
+ expect(redirectChain.length).toBe(3);
+ expect(redirectChain[0].url()).toContain('/one-style.css');
+ expect(redirectChain[2].url()).toContain('/three-style.css');
+ });
+ it('should be able to abort redirects', async () => {
+ const { page, server, isChrome } = getTestState();
+
+ await page.setRequestInterception(true);
+ server.setRedirect('/non-existing.json', '/non-existing-2.json');
+ server.setRedirect('/non-existing-2.json', '/simple.html');
+ page.on('request', (request) => {
+ if (request.url().includes('non-existing-2')) request.abort();
+ else request.continue();
+ });
+ await page.goto(server.EMPTY_PAGE);
+ const result = await page.evaluate(async () => {
+ try {
+ await fetch('/non-existing.json');
+ } catch (error) {
+ return error.message;
+ }
+ });
+ if (isChrome) expect(result).toContain('Failed to fetch');
+ else expect(result).toContain('NetworkError');
+ });
+ it('should work with equal requests', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ let responseCount = 1;
+ server.setRoute('/zzz', (req, res) => res.end(responseCount++ * 11 + ''));
+ await page.setRequestInterception(true);
+
+ let spinner = false;
+ // Cancel 2nd request.
+ page.on('request', (request) => {
+ if (utils.isFavicon(request)) {
+ request.continue();
+ return;
+ }
+ spinner ? request.abort() : request.continue();
+ spinner = !spinner;
+ });
+ const results = await page.evaluate(() =>
+ Promise.all([
+ fetch('/zzz')
+ .then((response) => response.text())
+ .catch(() => 'FAILED'),
+ fetch('/zzz')
+ .then((response) => response.text())
+ .catch(() => 'FAILED'),
+ fetch('/zzz')
+ .then((response) => response.text())
+ .catch(() => 'FAILED'),
+ ])
+ );
+ expect(results).toEqual(['11', 'FAILED', '22']);
+ });
+ it('should navigate to dataURL and fire dataURL requests', async () => {
+ const { page } = getTestState();
+
+ await page.setRequestInterception(true);
+ const requests = [];
+ page.on('request', (request) => {
+ requests.push(request);
+ request.continue();
+ });
+ const dataURL = 'data:text/html,yo
';
+ const response = await page.goto(dataURL);
+ expect(response.status()).toBe(200);
+ expect(requests.length).toBe(1);
+ expect(requests[0].url()).toBe(dataURL);
+ });
+ it('should be able to fetch dataURL and fire dataURL requests', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.setRequestInterception(true);
+ const requests = [];
+ page.on('request', (request) => {
+ !utils.isFavicon(request) && requests.push(request);
+ request.continue();
+ });
+ const dataURL = 'data:text/html,yo
';
+ const text = await page.evaluate(
+ (url: string) => fetch(url).then((r) => r.text()),
+ dataURL
+ );
+ expect(text).toBe('yo
');
+ expect(requests.length).toBe(1);
+ expect(requests[0].url()).toBe(dataURL);
+ });
+ it('should navigate to URL with hash and fire requests without hash', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ const requests = [];
+ page.on('request', (request) => {
+ requests.push(request);
+ request.continue();
+ });
+ const response = await page.goto(server.EMPTY_PAGE + '#hash');
+ expect(response.status()).toBe(200);
+ expect(response.url()).toBe(server.EMPTY_PAGE);
+ expect(requests.length).toBe(1);
+ expect(requests[0].url()).toBe(server.EMPTY_PAGE);
+ });
+ it('should work with encoded server', async () => {
+ const { page, server } = getTestState();
+
+ // The requestWillBeSent will report encoded URL, whereas interception will
+ // report URL as-is. @see crbug.com/759388
+ await page.setRequestInterception(true);
+ page.on('request', (request) => request.continue());
+ const response = await page.goto(
+ server.PREFIX + '/some nonexisting page'
+ );
+ expect(response.status()).toBe(404);
+ });
+ it('should work with badly encoded server', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ server.setRoute('/malformed?rnd=%911', (req, res) => res.end());
+ page.on('request', (request) => request.continue());
+ const response = await page.goto(server.PREFIX + '/malformed?rnd=%911');
+ expect(response.status()).toBe(200);
+ });
+ it('should work with encoded server - 2', async () => {
+ const { page, server } = getTestState();
+
+ // The requestWillBeSent will report URL as-is, whereas interception will
+ // report encoded URL for stylesheet. @see crbug.com/759388
+ await page.setRequestInterception(true);
+ const requests = [];
+ page.on('request', (request) => {
+ request.continue();
+ requests.push(request);
+ });
+ const response = await page.goto(
+ `data:text/html, `
+ );
+ expect(response.status()).toBe(200);
+ expect(requests.length).toBe(2);
+ expect(requests[1].response().status()).toBe(404);
+ });
+ it('should not throw "Invalid Interception Id" if the request was cancelled', async () => {
+ const { page, server } = getTestState();
+
+ await page.setContent('');
+ await page.setRequestInterception(true);
+ let request = null;
+ page.on('request', async (r) => (request = r));
+ page.$eval(
+ 'iframe',
+ (frame: HTMLIFrameElement, url: string) => (frame.src = url),
+ server.EMPTY_PAGE
+ ),
+ // Wait for request interception.
+ await utils.waitEvent(page, 'request');
+ // Delete frame to cause request to be canceled.
+ await page.$eval('iframe', (frame) => frame.remove());
+ let error = null;
+ await request.continue().catch((error_) => (error = error_));
+ expect(error).toBe(null);
+ });
+ it('should throw if interception is not enabled', async () => {
+ const { page, server } = getTestState();
+
+ let error = null;
+ page.on('request', async (request) => {
+ try {
+ await request.continue();
+ } catch (error_) {
+ error = error_;
+ }
+ });
+ await page.goto(server.EMPTY_PAGE);
+ expect(error.message).toContain('Request Interception is not enabled');
+ });
+ it('should work with file URLs', async () => {
+ const { page } = getTestState();
+
+ await page.setRequestInterception(true);
+ const urls = new Set();
+ page.on('request', (request) => {
+ urls.add(request.url().split('/').pop());
+ request.continue();
+ });
+ await page.goto(
+ pathToFileURL(path.join(__dirname, 'assets', 'one-style.html'))
+ );
+ expect(urls.size).toBe(2);
+ expect(urls.has('one-style.html')).toBe(true);
+ expect(urls.has('one-style.css')).toBe(true);
+ });
+ it('should not cache if cache disabled', async () => {
+ const { page, server } = getTestState();
+
+ // Load and re-load to make sure it's cached.
+ await page.goto(server.PREFIX + '/cached/one-style.html');
+
+ await page.setRequestInterception(true);
+ await page.setCacheEnabled(false);
+ page.on('request', (request) => request.continue());
+
+ const cached = [];
+ page.on('requestservedfromcache', (r) => cached.push(r));
+
+ await page.reload();
+ expect(cached.length).toBe(0);
+ });
+ it('should cache if cache enabled', async () => {
+ const { page, server } = getTestState();
+
+ // Load and re-load to make sure it's cached.
+ await page.goto(server.PREFIX + '/cached/one-style.html');
+
+ await page.setRequestInterception(true);
+ await page.setCacheEnabled(true);
+ page.on('request', (request) => request.continue());
+
+ const cached = [];
+ page.on('requestservedfromcache', (r) => cached.push(r));
+
+ await page.reload();
+ expect(cached.length).toBe(1);
+ });
+ it('should load fonts if cache enabled', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ await page.setCacheEnabled(true);
+ page.on('request', (request) => request.continue());
+
+ await page.goto(server.PREFIX + '/cached/one-style-font.html');
+ await page.waitForResponse((r) => r.url().endsWith('/one-style.woff'));
+ });
+ });
+
+ describeFailsFirefox('Request.continue', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => request.continue());
+ await page.goto(server.EMPTY_PAGE);
+ });
+ it('should amend HTTP headers', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ const headers = Object.assign({}, request.headers());
+ headers['FOO'] = 'bar';
+ request.continue({ headers });
+ });
+ await page.goto(server.EMPTY_PAGE);
+ const [request] = await Promise.all([
+ server.waitForRequest('/sleep.zzz'),
+ page.evaluate(() => fetch('/sleep.zzz')),
+ ]);
+ expect(request.headers['foo']).toBe('bar');
+ });
+ it('should redirect in a way non-observable to page', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ const redirectURL = request.url().includes('/empty.html')
+ ? server.PREFIX + '/consolelog.html'
+ : undefined;
+ request.continue({ url: redirectURL });
+ });
+ let consoleMessage = null;
+ page.on('console', (msg) => (consoleMessage = msg));
+ await page.goto(server.EMPTY_PAGE);
+ expect(page.url()).toBe(server.EMPTY_PAGE);
+ expect(consoleMessage.text()).toBe('yellow');
+ });
+ it('should amend method', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ request.continue({ method: 'POST' });
+ });
+ const [request] = await Promise.all([
+ server.waitForRequest('/sleep.zzz'),
+ page.evaluate(() => fetch('/sleep.zzz')),
+ ]);
+ expect(request.method).toBe('POST');
+ });
+ it('should amend post data', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ request.continue({ postData: 'doggo' });
+ });
+ const [serverRequest] = await Promise.all([
+ server.waitForRequest('/sleep.zzz'),
+ page.evaluate(() =>
+ fetch('/sleep.zzz', { method: 'POST', body: 'birdy' })
+ ),
+ ]);
+ expect(await serverRequest.postBody).toBe('doggo');
+ });
+ it('should amend both post data and method on navigation', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ request.continue({ method: 'POST', postData: 'doggo' });
+ });
+ const [serverRequest] = await Promise.all([
+ server.waitForRequest('/empty.html'),
+ page.goto(server.EMPTY_PAGE),
+ ]);
+ expect(serverRequest.method).toBe('POST');
+ expect(await serverRequest.postBody).toBe('doggo');
+ });
+ it('should fail if the header value is invalid', async () => {
+ const { page, server } = getTestState();
+
+ let error;
+ await page.setRequestInterception(true);
+ page.on('request', async (request) => {
+ await request
+ .continue({
+ headers: {
+ 'X-Invalid-Header': 'a\nb',
+ },
+ })
+ .catch((error_) => {
+ error = error_;
+ });
+ await request.continue();
+ });
+ await page.goto(server.PREFIX + '/empty.html');
+ expect(error.message).toMatch(/Invalid header/);
+ });
+ });
+
+ describeFailsFirefox('Request.respond', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ request.respond({
+ status: 201,
+ headers: {
+ foo: 'bar',
+ },
+ body: 'Yo, page!',
+ });
+ });
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.status()).toBe(201);
+ expect(response.headers().foo).toBe('bar');
+ expect(await page.evaluate(() => document.body.textContent)).toBe(
+ 'Yo, page!'
+ );
+ });
+ it('should work with status code 422', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ request.respond({
+ status: 422,
+ body: 'Yo, page!',
+ });
+ });
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.status()).toBe(422);
+ expect(response.statusText()).toBe('Unprocessable Entity');
+ expect(await page.evaluate(() => document.body.textContent)).toBe(
+ 'Yo, page!'
+ );
+ });
+ it('should redirect', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ if (!request.url().includes('rrredirect')) {
+ request.continue();
+ return;
+ }
+ request.respond({
+ status: 302,
+ headers: {
+ location: server.EMPTY_PAGE,
+ },
+ });
+ });
+ const response = await page.goto(server.PREFIX + '/rrredirect');
+ expect(response.request().redirectChain().length).toBe(1);
+ expect(response.request().redirectChain()[0].url()).toBe(
+ server.PREFIX + '/rrredirect'
+ );
+ expect(response.url()).toBe(server.EMPTY_PAGE);
+ });
+ it('should allow mocking multiple headers with same key', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ request.respond({
+ status: 200,
+ headers: {
+ foo: 'bar',
+ arr: ['1', '2'],
+ 'set-cookie': ['first=1', 'second=2'],
+ },
+ body: 'Hello world',
+ });
+ });
+ const response = await page.goto(server.EMPTY_PAGE);
+ const cookies = await page.cookies();
+ const firstCookie = cookies.find((cookie) => cookie.name === 'first');
+ const secondCookie = cookies.find((cookie) => cookie.name === 'second');
+ expect(response.status()).toBe(200);
+ expect(response.headers().foo).toBe('bar');
+ expect(response.headers().arr).toBe('1\n2');
+ // request.respond() will not trigger Network.responseReceivedExtraInfo
+ // fail to get 'set-cookie' header from response
+ expect(firstCookie?.value).toBe('1');
+ expect(secondCookie?.value).toBe('2');
+ });
+ it('should allow mocking binary responses', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ const imageBuffer = fs.readFileSync(
+ path.join(__dirname, 'assets', 'pptr.png')
+ );
+ request.respond({
+ contentType: 'image/png',
+ body: imageBuffer,
+ });
+ });
+ await page.evaluate((PREFIX) => {
+ const img = document.createElement('img');
+ img.src = PREFIX + '/does-not-exist.png';
+ document.body.appendChild(img);
+ return new Promise((fulfill) => (img.onload = fulfill));
+ }, server.PREFIX);
+ const img = await page.$('img');
+ expect(await img.screenshot()).toBeGolden('mock-binary-response.png');
+ });
+ it('should stringify intercepted request response headers', async () => {
+ const { page, server } = getTestState();
+
+ await page.setRequestInterception(true);
+ page.on('request', (request) => {
+ request.respond({
+ status: 200,
+ headers: {
+ foo: true,
+ },
+ body: 'Yo, page!',
+ });
+ });
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.status()).toBe(200);
+ const headers = response.headers();
+ expect(headers.foo).toBe('true');
+ expect(await page.evaluate(() => document.body.textContent)).toBe(
+ 'Yo, page!'
+ );
+ });
+ it('should fail if the header value is invalid', async () => {
+ const { page, server } = getTestState();
+
+ let error;
+ await page.setRequestInterception(true);
+ page.on('request', async (request) => {
+ await request
+ .respond({
+ headers: {
+ 'X-Invalid-Header': 'a\nb',
+ },
+ })
+ .catch((error_) => {
+ error = error_;
+ });
+ await request.respond({
+ status: 200,
+ body: 'Hello World',
+ });
+ });
+ await page.goto(server.PREFIX + '/empty.html');
+ expect(error.message).toMatch(/Invalid header/);
+ });
+ });
+});
+
+/**
+ * @param {string} path
+ * @returns {string}
+ */
+function pathToFileURL(path) {
+ let pathName = path.replace(/\\/g, '/');
+ // Windows drive letter must be prefixed with a slash.
+ if (!pathName.startsWith('/')) pathName = '/' + pathName;
+ return 'file://' + pathName;
+}
diff --git a/test/run_static_server.js b/test/run_static_server.js
new file mode 100755
index 0000000000000..0cd10f2646624
--- /dev/null
+++ b/test/run_static_server.js
@@ -0,0 +1,33 @@
+#!/usr/bin/env node
+/**
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+const path = require('path');
+const { TestServer } = require('../utils/testserver/index.js');
+
+const port = 8907;
+const httpsPort = 8908;
+const assetsPath = path.join(__dirname, 'assets');
+const cachedPath = path.join(__dirname, 'assets', 'cached');
+
+Promise.all([
+ TestServer.create(assetsPath, port),
+ TestServer.createHTTPS(assetsPath, httpsPort),
+]).then(([server, httpsServer]) => {
+ server.enableHTTPCache(cachedPath);
+ httpsServer.enableHTTPCache(cachedPath);
+ console.log(`HTTP: server is running on http://localhost:${port}`);
+ console.log(`HTTPS: server is running on https://localhost:${httpsPort}`);
+});
diff --git a/test/screenshot.spec.ts b/test/screenshot.spec.ts
new file mode 100644
index 0000000000000..f2a6adc6d5fe1
--- /dev/null
+++ b/test/screenshot.spec.ts
@@ -0,0 +1,340 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import expect from 'expect';
+import {
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+ itFailsFirefox,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+
+describe('Screenshots', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+
+ describe('Page.screenshot', function () {
+ itFailsFirefox('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.setViewport({ width: 500, height: 500 });
+ await page.goto(server.PREFIX + '/grid.html');
+ const screenshot = await page.screenshot();
+ expect(screenshot).toBeGolden('screenshot-sanity.png');
+ });
+ itFailsFirefox('should clip rect', async () => {
+ const { page, server } = getTestState();
+
+ await page.setViewport({ width: 500, height: 500 });
+ await page.goto(server.PREFIX + '/grid.html');
+ const screenshot = await page.screenshot({
+ clip: {
+ x: 50,
+ y: 100,
+ width: 150,
+ height: 100,
+ },
+ });
+ expect(screenshot).toBeGolden('screenshot-clip-rect.png');
+ });
+ itFailsFirefox(
+ 'should get screenshot bigger than the viewport',
+ async () => {
+ const { page, server } = getTestState();
+ await page.setViewport({ width: 50, height: 50 });
+ await page.goto(server.PREFIX + '/grid.html');
+ const screenshot = await page.screenshot({
+ clip: {
+ x: 25,
+ y: 25,
+ width: 100,
+ height: 100,
+ },
+ });
+ expect(screenshot).toBeGolden('screenshot-offscreen-clip.png');
+ }
+ );
+ it('should run in parallel', async () => {
+ const { page, server } = getTestState();
+
+ await page.setViewport({ width: 500, height: 500 });
+ await page.goto(server.PREFIX + '/grid.html');
+ const promises = [];
+ for (let i = 0; i < 3; ++i) {
+ promises.push(
+ page.screenshot({
+ clip: {
+ x: 50 * i,
+ y: 0,
+ width: 50,
+ height: 50,
+ },
+ })
+ );
+ }
+ const screenshots = await Promise.all(promises);
+ expect(screenshots[1]).toBeGolden('grid-cell-1.png');
+ });
+ itFailsFirefox('should take fullPage screenshots', async () => {
+ const { page, server } = getTestState();
+
+ await page.setViewport({ width: 500, height: 500 });
+ await page.goto(server.PREFIX + '/grid.html');
+ const screenshot = await page.screenshot({
+ fullPage: true,
+ });
+ expect(screenshot).toBeGolden('screenshot-grid-fullpage.png');
+ });
+ it('should run in parallel in multiple pages', async () => {
+ const { server, context } = getTestState();
+
+ const N = 2;
+ const pages = await Promise.all(
+ Array(N)
+ .fill(0)
+ .map(async () => {
+ const page = await context.newPage();
+ await page.goto(server.PREFIX + '/grid.html');
+ return page;
+ })
+ );
+ const promises = [];
+ for (let i = 0; i < N; ++i)
+ promises.push(
+ pages[i].screenshot({
+ clip: { x: 50 * i, y: 0, width: 50, height: 50 },
+ })
+ );
+ const screenshots = await Promise.all(promises);
+ for (let i = 0; i < N; ++i)
+ expect(screenshots[i]).toBeGolden(`grid-cell-${i}.png`);
+ await Promise.all(pages.map((page) => page.close()));
+ });
+ itFailsFirefox('should allow transparency', async () => {
+ const { page, server } = getTestState();
+
+ await page.setViewport({ width: 100, height: 100 });
+ await page.goto(server.EMPTY_PAGE);
+ const screenshot = await page.screenshot({ omitBackground: true });
+ expect(screenshot).toBeGolden('transparent.png');
+ });
+ itFailsFirefox('should render white background on jpeg file', async () => {
+ const { page, server } = getTestState();
+
+ await page.setViewport({ width: 100, height: 100 });
+ await page.goto(server.EMPTY_PAGE);
+ const screenshot = await page.screenshot({
+ omitBackground: true,
+ type: 'jpeg',
+ });
+ expect(screenshot).toBeGolden('white.jpg');
+ });
+ itFailsFirefox('should work with webp', async () => {
+ const { page, server } = getTestState();
+
+ await page.setViewport({ width: 100, height: 100 });
+ await page.goto(server.PREFIX + '/grid.html');
+ const screenshot = await page.screenshot({
+ type: 'webp',
+ });
+
+ expect(screenshot).toBeInstanceOf(Buffer);
+ });
+ it('should work with odd clip size on Retina displays', async () => {
+ const { page } = getTestState();
+
+ const screenshot = await page.screenshot({
+ clip: {
+ x: 0,
+ y: 0,
+ width: 11,
+ height: 11,
+ },
+ });
+ expect(screenshot).toBeGolden('screenshot-clip-odd-size.png');
+ });
+ itFailsFirefox('should return base64', async () => {
+ const { page, server } = getTestState();
+
+ await page.setViewport({ width: 500, height: 500 });
+ await page.goto(server.PREFIX + '/grid.html');
+ const screenshot = await page.screenshot({
+ encoding: 'base64',
+ });
+ // TODO (@jackfranklin): improve the screenshot types.
+ // - if we pass encoding: 'base64', it returns a string
+ // - else it returns a buffer.
+ // If we can fix that we can avoid this "as string" here.
+ expect(Buffer.from(screenshot as string, 'base64')).toBeGolden(
+ 'screenshot-sanity.png'
+ );
+ });
+ });
+
+ describe('ElementHandle.screenshot', function () {
+ it('should work', async () => {
+ const { page, server } = getTestState();
+
+ await page.setViewport({ width: 500, height: 500 });
+ await page.goto(server.PREFIX + '/grid.html');
+ await page.evaluate(() => window.scrollBy(50, 100));
+ const elementHandle = await page.$('.box:nth-of-type(3)');
+ const screenshot = await elementHandle.screenshot();
+ expect(screenshot).toBeGolden('screenshot-element-bounding-box.png');
+ });
+ it('should take into account padding and border', async () => {
+ const { page } = getTestState();
+
+ await page.setViewport({ width: 500, height: 500 });
+ await page.setContent(`
+ something above
+
+
+ `);
+ const elementHandle = await page.$('div');
+ const screenshot = await elementHandle.screenshot();
+ expect(screenshot).toBeGolden('screenshot-element-padding-border.png');
+ });
+ itFailsFirefox(
+ 'should capture full element when larger than viewport',
+ async () => {
+ const { page } = getTestState();
+
+ await page.setViewport({ width: 500, height: 500 });
+
+ await page.setContent(`
+ something above
+
+
+ `);
+ const elementHandle = await page.$('div.to-screenshot');
+ const screenshot = await elementHandle.screenshot();
+ expect(screenshot).toBeGolden(
+ 'screenshot-element-larger-than-viewport.png'
+ );
+
+ expect(
+ await page.evaluate(() => ({
+ w: window.innerWidth,
+ h: window.innerHeight,
+ }))
+ ).toEqual({ w: 500, h: 500 });
+ }
+ );
+ it('should scroll element into view', async () => {
+ const { page } = getTestState();
+
+ await page.setViewport({ width: 500, height: 500 });
+ await page.setContent(`
+ something above
+
+
+
+ `);
+ const elementHandle = await page.$('div.to-screenshot');
+ const screenshot = await elementHandle.screenshot();
+ expect(screenshot).toBeGolden(
+ 'screenshot-element-scrolled-into-view.png'
+ );
+ });
+ itFailsFirefox('should work with a rotated element', async () => {
+ const { page } = getTestState();
+
+ await page.setViewport({ width: 500, height: 500 });
+ await page.setContent(`
`);
+ const elementHandle = await page.$('div');
+ const screenshot = await elementHandle.screenshot();
+ expect(screenshot).toBeGolden('screenshot-element-rotate.png');
+ });
+ itFailsFirefox('should fail to screenshot a detached element', async () => {
+ const { page } = getTestState();
+
+ await page.setContent('remove this ');
+ const elementHandle = await page.$('h1');
+ await page.evaluate(
+ (element: HTMLElement) => element.remove(),
+ elementHandle
+ );
+ const screenshotError = await elementHandle
+ .screenshot()
+ .catch((error) => error);
+ expect(screenshotError.message).toBe(
+ 'Node is either not visible or not an HTMLElement'
+ );
+ });
+ it('should not hang with zero width/height element', async () => {
+ const { page } = getTestState();
+
+ await page.setContent('
');
+ const div = await page.$('div');
+ const error = await div.screenshot().catch((error_) => error_);
+ expect(error.message).toBe('Node has 0 height.');
+ });
+ it('should work for an element with fractional dimensions', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(
+ '
'
+ );
+ const elementHandle = await page.$('div');
+ const screenshot = await elementHandle.screenshot();
+ expect(screenshot).toBeGolden('screenshot-element-fractional.png');
+ });
+ itFailsFirefox('should work for an element with an offset', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(
+ '
'
+ );
+ const elementHandle = await page.$('div');
+ const screenshot = await elementHandle.screenshot();
+ expect(screenshot).toBeGolden('screenshot-element-fractional-offset.png');
+ });
+ });
+});
diff --git a/test/target.spec.ts b/test/target.spec.ts
new file mode 100644
index 0000000000000..d1081d65f838b
--- /dev/null
+++ b/test/target.spec.ts
@@ -0,0 +1,318 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import utils from './utils.js';
+const { waitEvent } = utils;
+import expect from 'expect';
+import {
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+ itFailsFirefox,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+import { Target } from '../lib/cjs/puppeteer/common/Target.js';
+
+describe('Target', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+
+ it('Browser.targets should return all of the targets', async () => {
+ const { browser } = getTestState();
+
+ // The pages will be the testing page and the original newtab page
+ const targets = browser.targets();
+ expect(
+ targets.some(
+ (target) => target.type() === 'page' && target.url() === 'about:blank'
+ )
+ ).toBeTruthy();
+ expect(targets.some((target) => target.type() === 'browser')).toBeTruthy();
+ });
+ it('Browser.pages should return all of the pages', async () => {
+ const { page, context } = getTestState();
+
+ // The pages will be the testing page
+ const allPages = await context.pages();
+ expect(allPages.length).toBe(1);
+ expect(allPages).toContain(page);
+ });
+ it('should contain browser target', async () => {
+ const { browser } = getTestState();
+
+ const targets = browser.targets();
+ const browserTarget = targets.find((target) => target.type() === 'browser');
+ expect(browserTarget).toBeTruthy();
+ });
+ it('should be able to use the default page in the browser', async () => {
+ const { page, browser } = getTestState();
+
+ // The pages will be the testing page and the original newtab page
+ const allPages = await browser.pages();
+ const originalPage = allPages.find((p) => p !== page);
+ expect(
+ await originalPage.evaluate(() => ['Hello', 'world'].join(' '))
+ ).toBe('Hello world');
+ expect(await originalPage.$('body')).toBeTruthy();
+ });
+ itFailsFirefox('should be able to use async waitForTarget', async () => {
+ const { page, server, context } = getTestState();
+
+ const [otherPage] = await Promise.all([
+ context
+ .waitForTarget((target) =>
+ target
+ .page()
+ .then(
+ (page) =>
+ page.url() === server.CROSS_PROCESS_PREFIX + '/empty.html'
+ )
+ )
+ .then((target) => target.page()),
+ page.evaluate(
+ (url: string) => window.open(url),
+ server.CROSS_PROCESS_PREFIX + '/empty.html'
+ ),
+ ]);
+ expect(otherPage.url()).toEqual(
+ server.CROSS_PROCESS_PREFIX + '/empty.html'
+ );
+ expect(page).not.toEqual(otherPage);
+ });
+ itFailsFirefox(
+ 'should report when a new page is created and closed',
+ async () => {
+ const { page, server, context } = getTestState();
+
+ const [otherPage] = await Promise.all([
+ context
+ .waitForTarget(
+ (target) =>
+ target.url() === server.CROSS_PROCESS_PREFIX + '/empty.html'
+ )
+ .then((target) => target.page()),
+ page.evaluate(
+ (url: string) => window.open(url),
+ server.CROSS_PROCESS_PREFIX + '/empty.html'
+ ),
+ ]);
+ expect(otherPage.url()).toContain(server.CROSS_PROCESS_PREFIX);
+ expect(await otherPage.evaluate(() => ['Hello', 'world'].join(' '))).toBe(
+ 'Hello world'
+ );
+ expect(await otherPage.$('body')).toBeTruthy();
+
+ let allPages = await context.pages();
+ expect(allPages).toContain(page);
+ expect(allPages).toContain(otherPage);
+
+ const closePagePromise = new Promise((fulfill) =>
+ context.once('targetdestroyed', (target) => fulfill(target.page()))
+ );
+ await otherPage.close();
+ expect(await closePagePromise).toBe(otherPage);
+
+ allPages = await Promise.all(
+ context.targets().map((target) => target.page())
+ );
+ expect(allPages).toContain(page);
+ expect(allPages).not.toContain(otherPage);
+ }
+ );
+ itFailsFirefox(
+ 'should report when a service worker is created and destroyed',
+ async () => {
+ const { page, server, context } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const createdTarget = new Promise((fulfill) =>
+ context.once('targetcreated', (target) => fulfill(target))
+ );
+
+ await page.goto(server.PREFIX + '/serviceworkers/empty/sw.html');
+
+ expect((await createdTarget).type()).toBe('service_worker');
+ expect((await createdTarget).url()).toBe(
+ server.PREFIX + '/serviceworkers/empty/sw.js'
+ );
+
+ const destroyedTarget = new Promise((fulfill) =>
+ context.once('targetdestroyed', (target) => fulfill(target))
+ );
+ await page.evaluate(() =>
+ globalThis.registrationPromise.then((registration) =>
+ registration.unregister()
+ )
+ );
+ expect(await destroyedTarget).toBe(await createdTarget);
+ }
+ );
+ itFailsFirefox('should create a worker from a service worker', async () => {
+ const { page, server, context } = getTestState();
+
+ await page.goto(server.PREFIX + '/serviceworkers/empty/sw.html');
+
+ const target = await context.waitForTarget(
+ (target) => target.type() === 'service_worker'
+ );
+ const worker = await target.worker();
+ expect(await worker.evaluate(() => self.toString())).toBe(
+ '[object ServiceWorkerGlobalScope]'
+ );
+ });
+ itFailsFirefox('should create a worker from a shared worker', async () => {
+ const { page, server, context } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await page.evaluate(() => {
+ new SharedWorker('data:text/javascript,console.log("hi")');
+ });
+ const target = await context.waitForTarget(
+ (target) => target.type() === 'shared_worker'
+ );
+ const worker = await target.worker();
+ expect(await worker.evaluate(() => self.toString())).toBe(
+ '[object SharedWorkerGlobalScope]'
+ );
+ });
+ itFailsFirefox('should report when a target url changes', async () => {
+ const { page, server, context } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ let changedTarget = new Promise((fulfill) =>
+ context.once('targetchanged', (target) => fulfill(target))
+ );
+ await page.goto(server.CROSS_PROCESS_PREFIX + '/');
+ expect((await changedTarget).url()).toBe(server.CROSS_PROCESS_PREFIX + '/');
+
+ changedTarget = new Promise((fulfill) =>
+ context.once('targetchanged', (target) => fulfill(target))
+ );
+ await page.goto(server.EMPTY_PAGE);
+ expect((await changedTarget).url()).toBe(server.EMPTY_PAGE);
+ });
+ itFailsFirefox('should not report uninitialized pages', async () => {
+ const { context } = getTestState();
+
+ let targetChanged = false;
+ const listener = () => (targetChanged = true);
+ context.on('targetchanged', listener);
+ const targetPromise = new Promise((fulfill) =>
+ context.once('targetcreated', (target) => fulfill(target))
+ );
+ const newPagePromise = context.newPage();
+ const target = await targetPromise;
+ expect(target.url()).toBe('about:blank');
+
+ const newPage = await newPagePromise;
+ const targetPromise2 = new Promise((fulfill) =>
+ context.once('targetcreated', (target) => fulfill(target))
+ );
+ const evaluatePromise = newPage.evaluate(() => window.open('about:blank'));
+ const target2 = await targetPromise2;
+ expect(target2.url()).toBe('about:blank');
+ await evaluatePromise;
+ await newPage.close();
+ expect(targetChanged).toBe(false);
+ context.removeListener('targetchanged', listener);
+ });
+ itFailsFirefox(
+ 'should not crash while redirecting if original request was missed',
+ async () => {
+ const { page, server, context } = getTestState();
+
+ let serverResponse = null;
+ server.setRoute('/one-style.css', (req, res) => (serverResponse = res));
+ // Open a new page. Use window.open to connect to the page later.
+ await Promise.all([
+ page.evaluate(
+ (url: string) => window.open(url),
+ server.PREFIX + '/one-style.html'
+ ),
+ server.waitForRequest('/one-style.css'),
+ ]);
+ // Connect to the opened page.
+ const target = await context.waitForTarget((target) =>
+ target.url().includes('one-style.html')
+ );
+ const newPage = await target.page();
+ // Issue a redirect.
+ serverResponse.writeHead(302, { location: '/injectedstyle.css' });
+ serverResponse.end();
+ // Wait for the new page to load.
+ await waitEvent(newPage, 'load');
+ // Cleanup.
+ await newPage.close();
+ }
+ );
+ itFailsFirefox('should have an opener', async () => {
+ const { page, server, context } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const [createdTarget] = await Promise.all([
+ new Promise((fulfill) =>
+ context.once('targetcreated', (target) => fulfill(target))
+ ),
+ page.goto(server.PREFIX + '/popup/window-open.html'),
+ ]);
+ expect((await createdTarget.page()).url()).toBe(
+ server.PREFIX + '/popup/popup.html'
+ );
+ expect(createdTarget.opener()).toBe(page.target());
+ expect(page.target().opener()).toBe(null);
+ });
+
+ describe('Browser.waitForTarget', () => {
+ itFailsFirefox('should wait for a target', async () => {
+ const { browser, puppeteer, server } = getTestState();
+
+ let resolved = false;
+ const targetPromise = browser.waitForTarget(
+ (target) => target.url() === server.EMPTY_PAGE
+ );
+ targetPromise
+ .then(() => (resolved = true))
+ .catch((error) => {
+ resolved = true;
+ if (error instanceof puppeteer.errors.TimeoutError) {
+ console.error(error);
+ } else throw error;
+ });
+ const page = await browser.newPage();
+ expect(resolved).toBe(false);
+ await page.goto(server.EMPTY_PAGE);
+ try {
+ const target = await targetPromise;
+ expect(await target.page()).toBe(page);
+ } catch (error) {
+ if (error instanceof puppeteer.errors.TimeoutError) {
+ console.error(error);
+ } else throw error;
+ }
+ await page.close();
+ });
+ it('should timeout waiting for a non-existent target', async () => {
+ const { browser, server, puppeteer } = getTestState();
+
+ let error = null;
+ await browser
+ .waitForTarget((target) => target.url() === server.EMPTY_PAGE, {
+ timeout: 1,
+ })
+ .catch((error_) => (error = error_));
+ expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
+ });
+ });
+});
diff --git a/test/touchscreen.spec.ts b/test/touchscreen.spec.ts
new file mode 100644
index 0000000000000..36931d5852699
--- /dev/null
+++ b/test/touchscreen.spec.ts
@@ -0,0 +1,49 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import expect from 'expect';
+import {
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+ describeFailsFirefox,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+
+describeFailsFirefox('Touchscreen', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+
+ it('should tap the button', async () => {
+ const { puppeteer, page, server } = getTestState();
+ const iPhone = puppeteer.devices['iPhone 6'];
+ await page.emulate(iPhone);
+ await page.goto(server.PREFIX + '/input/button.html');
+ await page.tap('button');
+ expect(await page.evaluate(() => globalThis.result)).toBe('Clicked');
+ });
+ it('should report touches', async () => {
+ const { puppeteer, page, server } = getTestState();
+ const iPhone = puppeteer.devices['iPhone 6'];
+ await page.emulate(iPhone);
+ await page.goto(server.PREFIX + '/input/touches.html');
+ const button = await page.$('button');
+ await button.tap();
+ expect(await page.evaluate(() => globalThis.getResult())).toEqual([
+ 'Touchstart: 0',
+ 'Touchend: 0',
+ ]);
+ });
+});
diff --git a/test/tracing.spec.ts b/test/tracing.spec.ts
new file mode 100644
index 0000000000000..8425652e97b35
--- /dev/null
+++ b/test/tracing.spec.ts
@@ -0,0 +1,156 @@
+/**
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import fs from 'fs';
+import path from 'path';
+import expect from 'expect';
+import { getTestState, describeChromeOnly } from './mocha-utils'; // eslint-disable-line import/extensions
+
+describeChromeOnly('Tracing', function () {
+ let outputFile;
+ let browser;
+ let page;
+
+ /* we manually manage the browser here as we want a new browser for each
+ * individual test, which isn't the default behaviour of getTestState()
+ */
+
+ beforeEach(async () => {
+ const { defaultBrowserOptions, puppeteer } = getTestState();
+ browser = await puppeteer.launch(defaultBrowserOptions);
+ page = await browser.newPage();
+ outputFile = path.join(__dirname, 'assets', 'trace.json');
+ });
+
+ afterEach(async () => {
+ await browser.close();
+ browser = null;
+ page = null;
+ if (fs.existsSync(outputFile)) {
+ fs.unlinkSync(outputFile);
+ outputFile = null;
+ }
+ });
+ it('should output a trace', async () => {
+ const { server } = getTestState();
+
+ await page.tracing.start({ screenshots: true, path: outputFile });
+ await page.goto(server.PREFIX + '/grid.html');
+ await page.tracing.stop();
+ expect(fs.existsSync(outputFile)).toBe(true);
+ });
+
+ it('should run with custom categories if provided', async () => {
+ await page.tracing.start({
+ path: outputFile,
+ categories: ['-*', 'disabled-by-default-devtools.timeline.frame'],
+ });
+ await page.tracing.stop();
+
+ const traceJson = JSON.parse(
+ fs.readFileSync(outputFile, { encoding: 'utf8' })
+ );
+ const traceConfig = JSON.parse(traceJson.metadata['trace-config']);
+ expect(traceConfig.included_categories).toEqual([
+ 'disabled-by-default-devtools.timeline.frame',
+ ]);
+ expect(traceConfig.excluded_categories).toEqual(['*']);
+ expect(traceJson.traceEvents).not.toContainEqual(
+ expect.objectContaining({
+ cat: 'toplevel',
+ })
+ );
+ });
+
+ it('should run with default categories', async () => {
+ await page.tracing.start({
+ path: outputFile,
+ });
+ await page.tracing.stop();
+
+ const traceJson = JSON.parse(
+ fs.readFileSync(outputFile, { encoding: 'utf8' })
+ );
+ expect(traceJson.traceEvents).toContainEqual(
+ expect.objectContaining({
+ cat: 'toplevel',
+ })
+ );
+ });
+ it('should throw if tracing on two pages', async () => {
+ await page.tracing.start({ path: outputFile });
+ const newPage = await browser.newPage();
+ let error = null;
+ await newPage.tracing
+ .start({ path: outputFile })
+ .catch((error_) => (error = error_));
+ await newPage.close();
+ expect(error).toBeTruthy();
+ await page.tracing.stop();
+ });
+ it('should return a buffer', async () => {
+ const { server } = getTestState();
+
+ await page.tracing.start({ screenshots: true, path: outputFile });
+ await page.goto(server.PREFIX + '/grid.html');
+ const trace = await page.tracing.stop();
+ const buf = fs.readFileSync(outputFile);
+ expect(trace.toString()).toEqual(buf.toString());
+ });
+ it('should work without options', async () => {
+ const { server } = getTestState();
+
+ await page.tracing.start();
+ await page.goto(server.PREFIX + '/grid.html');
+ const trace = await page.tracing.stop();
+ expect(trace).toBeTruthy();
+ });
+
+ it('should return null in case of Buffer error', async () => {
+ const { server } = getTestState();
+
+ await page.tracing.start({ screenshots: true });
+ await page.goto(server.PREFIX + '/grid.html');
+ const oldBufferConcat = Buffer.concat;
+ Buffer.concat = () => {
+ throw 'error';
+ };
+ const trace = await page.tracing.stop();
+ expect(trace).toEqual(null);
+ Buffer.concat = oldBufferConcat;
+ });
+
+ it('should support a buffer without a path', async () => {
+ const { server } = getTestState();
+
+ await page.tracing.start({ screenshots: true });
+ await page.goto(server.PREFIX + '/grid.html');
+ const trace = await page.tracing.stop();
+ expect(trace.toString()).toContain('screenshot');
+ });
+
+ it('should properly fail if readProtocolStream errors out', async () => {
+ await page.tracing.start({ path: __dirname });
+
+ let error: Error = null;
+ try {
+ await page.tracing.stop();
+ } catch (error_) {
+ error = error_;
+ }
+ expect(error).toBeDefined();
+ });
+});
diff --git a/test/tsconfig.json b/test/tsconfig.json
new file mode 100644
index 0000000000000..8b1f1e866cbd4
--- /dev/null
+++ b/test/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "noEmit": true
+ },
+ "include": ["*.ts", "*.js"]
+}
diff --git a/test/tsconfig.test.json b/test/tsconfig.test.json
new file mode 100644
index 0000000000000..3432441200fc1
--- /dev/null
+++ b/test/tsconfig.test.json
@@ -0,0 +1,6 @@
+{
+ "extends": "../tsconfig.base.json",
+ "compilerOptions": {
+ "module": "CommonJS"
+ }
+}
diff --git a/test/utils.js b/test/utils.js
new file mode 100644
index 0000000000000..f70cb47e714c5
--- /dev/null
+++ b/test/utils.js
@@ -0,0 +1,135 @@
+/**
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// TODO (@jackfranklin): convert to TS and enable type checking.
+
+// @ts-nocheck
+const fs = require('fs');
+const path = require('path');
+const expect = require('expect');
+const GoldenUtils = require('./golden-utils.js');
+const PROJECT_ROOT = fs.existsSync(path.join(__dirname, '..', 'package.json'))
+ ? path.join(__dirname, '..')
+ : path.join(__dirname, '..', '..');
+
+const utils = (module.exports = {
+ extendExpectWithToBeGolden: function (goldenDir, outputDir) {
+ expect.extend({
+ toBeGolden: (testScreenshot, goldenFilePath) => {
+ const result = GoldenUtils.compare(
+ goldenDir,
+ outputDir,
+ testScreenshot,
+ goldenFilePath
+ );
+
+ return {
+ message: () => result.message,
+ pass: result.pass,
+ };
+ },
+ });
+ },
+
+ /**
+ * @returns {string}
+ */
+ projectRoot: function () {
+ return PROJECT_ROOT;
+ },
+
+ /**
+ * @param {!Page} page
+ * @param {string} frameId
+ * @param {string} url
+ * @returns {!Frame}
+ */
+ attachFrame: async function (page, frameId, url) {
+ const handle = await page.evaluateHandle(attachFrame, frameId, url);
+ return await handle.asElement().contentFrame();
+
+ async function attachFrame(frameId, url) {
+ const frame = document.createElement('iframe');
+ frame.src = url;
+ frame.id = frameId;
+ document.body.appendChild(frame);
+ await new Promise((x) => (frame.onload = x));
+ return frame;
+ }
+ },
+
+ isFavicon: function (request) {
+ return request.url().includes('favicon.ico');
+ },
+
+ /**
+ * @param {!Page} page
+ * @param {string} frameId
+ */
+ detachFrame: async function (page, frameId) {
+ await page.evaluate(detachFrame, frameId);
+
+ function detachFrame(frameId) {
+ const frame = document.getElementById(frameId);
+ frame.remove();
+ }
+ },
+
+ /**
+ * @param {!Page} page
+ * @param {string} frameId
+ * @param {string} url
+ */
+ navigateFrame: async function (page, frameId, url) {
+ await page.evaluate(navigateFrame, frameId, url);
+
+ function navigateFrame(frameId, url) {
+ const frame = document.getElementById(frameId);
+ frame.src = url;
+ return new Promise((x) => (frame.onload = x));
+ }
+ },
+
+ /**
+ * @param {!Frame} frame
+ * @param {string=} indentation
+ * @returns {Array}
+ */
+ dumpFrames: function (frame, indentation) {
+ indentation = indentation || '';
+ let description = frame.url().replace(/:\d{4}\//, ':/');
+ if (frame.name()) description += ' (' + frame.name() + ')';
+ const result = [indentation + description];
+ for (const child of frame.childFrames())
+ result.push(...utils.dumpFrames(child, ' ' + indentation));
+ return result;
+ },
+
+ /**
+ * @param {!EventEmitter} emitter
+ * @param {string} eventName
+ * @returns {!Promise}
+ */
+ waitEvent: function (emitter, eventName, predicate = () => true) {
+ return new Promise((fulfill) => {
+ emitter.on(eventName, function listener(event) {
+ if (!predicate(event)) return;
+ emitter.removeListener(eventName, listener);
+ fulfill(event);
+ });
+ });
+ },
+});
diff --git a/test/waittask.spec.ts b/test/waittask.spec.ts
new file mode 100644
index 0000000000000..7b7f409e8cea1
--- /dev/null
+++ b/test/waittask.spec.ts
@@ -0,0 +1,789 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import utils from './utils.js';
+import sinon from 'sinon';
+import expect from 'expect';
+import {
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+ itFailsFirefox,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+
+describe('waittask specs', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+
+ describe('Page.waitFor', function () {
+ /* This method is deprecated but we don't want the warnings showing up in
+ * tests. Until we remove this method we still want to ensure we don't break
+ * it.
+ */
+ beforeEach(() => sinon.stub(console, 'warn').callsFake(() => {}));
+
+ it('should wait for selector', async () => {
+ const { page, server } = getTestState();
+
+ let found = false;
+ const waitFor = page.waitFor('div').then(() => (found = true));
+ await page.goto(server.EMPTY_PAGE);
+ expect(found).toBe(false);
+ await page.goto(server.PREFIX + '/grid.html');
+ await waitFor;
+ expect(found).toBe(true);
+ });
+
+ it('should wait for an xpath', async () => {
+ const { page, server } = getTestState();
+
+ let found = false;
+ const waitFor = page.waitFor('//div').then(() => (found = true));
+ await page.goto(server.EMPTY_PAGE);
+ expect(found).toBe(false);
+ await page.goto(server.PREFIX + '/grid.html');
+ await waitFor;
+ expect(found).toBe(true);
+ });
+ it('should allow you to select an element with parenthesis-starting xpath', async () => {
+ const { page, server } = getTestState();
+ let found = false;
+ const waitFor = page.waitFor('(//img)[200]').then(() => {
+ found = true;
+ });
+ await page.goto(server.EMPTY_PAGE);
+ expect(found).toBe(false);
+ await page.goto(server.PREFIX + '/grid.html');
+ await waitFor;
+ expect(found).toBe(true);
+ });
+ it('should not allow you to select an element with single slash xpath', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(`some text
`);
+ let error = null;
+ await page.waitFor('/html/body/div').catch((error_) => (error = error_));
+ expect(error).toBeTruthy();
+ });
+ it('should timeout', async () => {
+ const { page } = getTestState();
+
+ const startTime = Date.now();
+ const timeout = 42;
+ await page.waitFor(timeout);
+ expect(Date.now() - startTime).not.toBeLessThan(timeout / 2);
+ });
+ it('should work with multiline body', async () => {
+ const { page } = getTestState();
+
+ const result = await page.waitForFunction(`
+ (() => true)()
+ `);
+ expect(await result.jsonValue()).toBe(true);
+ });
+ it('should wait for predicate', async () => {
+ const { page } = getTestState();
+
+ await Promise.all([
+ page.waitFor(() => window.innerWidth < 100),
+ page.setViewport({ width: 10, height: 10 }),
+ ]);
+ });
+ it('should throw when unknown type', async () => {
+ const { page } = getTestState();
+
+ let error = null;
+ // @ts-expect-error purposefully passing bad type for test
+ await page.waitFor({ foo: 'bar' }).catch((error_) => (error = error_));
+ expect(error.message).toContain('Unsupported target type');
+ });
+ it('should wait for predicate with arguments', async () => {
+ const { page } = getTestState();
+
+ await page.waitFor((arg1, arg2) => arg1 !== arg2, {}, 1, 2);
+ });
+
+ it('should log a deprecation warning', async () => {
+ const { page } = getTestState();
+
+ await page.waitFor(() => true);
+
+ const consoleWarnStub = console.warn as sinon.SinonSpy;
+
+ expect(consoleWarnStub.calledOnce).toBe(true);
+ expect(
+ consoleWarnStub.firstCall.calledWith(
+ 'waitFor is deprecated and will be removed in a future release. See https://github.com/puppeteer/puppeteer/issues/6214 for details and how to migrate your code.'
+ )
+ ).toBe(true);
+ expect((console.warn as sinon.SinonSpy).calledOnce).toBe(true);
+ });
+ });
+
+ describe('Frame.waitForFunction', function () {
+ it('should accept a string', async () => {
+ const { page } = getTestState();
+
+ const watchdog = page.waitForFunction('window.__FOO === 1');
+ await page.evaluate(() => (globalThis.__FOO = 1));
+ await watchdog;
+ });
+ it('should work when resolved right before execution context disposal', async () => {
+ const { page } = getTestState();
+
+ await page.evaluateOnNewDocument(() => (globalThis.__RELOADED = true));
+ await page.waitForFunction(() => {
+ if (!globalThis.__RELOADED) window.location.reload();
+ return true;
+ });
+ });
+ it('should poll on interval', async () => {
+ const { page } = getTestState();
+
+ let success = false;
+ const startTime = Date.now();
+ const polling = 100;
+ const watchdog = page
+ .waitForFunction(() => globalThis.__FOO === 'hit', { polling })
+ .then(() => (success = true));
+ await page.evaluate(() => (globalThis.__FOO = 'hit'));
+ expect(success).toBe(false);
+ await page.evaluate(() =>
+ document.body.appendChild(document.createElement('div'))
+ );
+ await watchdog;
+ expect(Date.now() - startTime).not.toBeLessThan(polling / 2);
+ });
+ it('should poll on interval async', async () => {
+ const { page } = getTestState();
+ let success = false;
+ const startTime = Date.now();
+ const polling = 100;
+ const watchdog = page
+ .waitForFunction(async () => globalThis.__FOO === 'hit', { polling })
+ .then(() => (success = true));
+ await page.evaluate(async () => (globalThis.__FOO = 'hit'));
+ expect(success).toBe(false);
+ await page.evaluate(async () =>
+ document.body.appendChild(document.createElement('div'))
+ );
+ await watchdog;
+ expect(Date.now() - startTime).not.toBeLessThan(polling / 2);
+ });
+ it('should poll on mutation', async () => {
+ const { page } = getTestState();
+
+ let success = false;
+ const watchdog = page
+ .waitForFunction(() => globalThis.__FOO === 'hit', {
+ polling: 'mutation',
+ })
+ .then(() => (success = true));
+ await page.evaluate(() => (globalThis.__FOO = 'hit'));
+ expect(success).toBe(false);
+ await page.evaluate(() =>
+ document.body.appendChild(document.createElement('div'))
+ );
+ await watchdog;
+ });
+ it('should poll on mutation async', async () => {
+ const { page } = getTestState();
+
+ let success = false;
+ const watchdog = page
+ .waitForFunction(async () => globalThis.__FOO === 'hit', {
+ polling: 'mutation',
+ })
+ .then(() => (success = true));
+ await page.evaluate(async () => (globalThis.__FOO = 'hit'));
+ expect(success).toBe(false);
+ await page.evaluate(async () =>
+ document.body.appendChild(document.createElement('div'))
+ );
+ await watchdog;
+ });
+ it('should poll on raf', async () => {
+ const { page } = getTestState();
+
+ const watchdog = page.waitForFunction(() => globalThis.__FOO === 'hit', {
+ polling: 'raf',
+ });
+ await page.evaluate(() => (globalThis.__FOO = 'hit'));
+ await watchdog;
+ });
+ it('should poll on raf async', async () => {
+ const { page } = getTestState();
+
+ const watchdog = page.waitForFunction(
+ async () => globalThis.__FOO === 'hit',
+ {
+ polling: 'raf',
+ }
+ );
+ await page.evaluate(async () => (globalThis.__FOO = 'hit'));
+ await watchdog;
+ });
+ itFailsFirefox('should work with strict CSP policy', async () => {
+ const { page, server } = getTestState();
+
+ server.setCSP('/empty.html', 'script-src ' + server.PREFIX);
+ await page.goto(server.EMPTY_PAGE);
+ let error = null;
+ await Promise.all([
+ page
+ .waitForFunction(() => globalThis.__FOO === 'hit', { polling: 'raf' })
+ .catch((error_) => (error = error_)),
+ page.evaluate(() => (globalThis.__FOO = 'hit')),
+ ]);
+ expect(error).toBe(null);
+ });
+ it('should throw on bad polling value', async () => {
+ const { page } = getTestState();
+
+ let error = null;
+ try {
+ await page.waitForFunction(() => !!document.body, {
+ polling: 'unknown',
+ });
+ } catch (error_) {
+ error = error_;
+ }
+ expect(error).toBeTruthy();
+ expect(error.message).toContain('polling');
+ });
+ it('should throw negative polling interval', async () => {
+ const { page } = getTestState();
+
+ let error = null;
+ try {
+ await page.waitForFunction(() => !!document.body, { polling: -10 });
+ } catch (error_) {
+ error = error_;
+ }
+ expect(error).toBeTruthy();
+ expect(error.message).toContain('Cannot poll with non-positive interval');
+ });
+ it('should return the success value as a JSHandle', async () => {
+ const { page } = getTestState();
+
+ expect(await (await page.waitForFunction(() => 5)).jsonValue()).toBe(5);
+ });
+ it('should return the window as a success value', async () => {
+ const { page } = getTestState();
+
+ expect(await page.waitForFunction(() => window)).toBeTruthy();
+ });
+ it('should accept ElementHandle arguments', async () => {
+ const { page } = getTestState();
+
+ await page.setContent('
');
+ const div = await page.$('div');
+ let resolved = false;
+ const waitForFunction = page
+ .waitForFunction(
+ (element) => element.localName === 'div' && !element.parentElement,
+ {},
+ div
+ )
+ .then(() => (resolved = true));
+ expect(resolved).toBe(false);
+ await page.evaluate((element: HTMLElement) => element.remove(), div);
+ await waitForFunction;
+ });
+ it('should respect timeout', async () => {
+ const { page, puppeteer } = getTestState();
+
+ let error = null;
+ await page
+ .waitForFunction('false', { timeout: 10 })
+ .catch((error_) => (error = error_));
+ expect(error).toBeTruthy();
+ expect(error.message).toContain('waiting for function failed: timeout');
+ expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
+ });
+ it('should respect default timeout', async () => {
+ const { page, puppeteer } = getTestState();
+
+ page.setDefaultTimeout(1);
+ let error = null;
+ await page.waitForFunction('false').catch((error_) => (error = error_));
+ expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
+ expect(error.message).toContain('waiting for function failed: timeout');
+ });
+ it('should disable timeout when its set to 0', async () => {
+ const { page } = getTestState();
+
+ const watchdog = page.waitForFunction(
+ () => {
+ globalThis.__counter = (globalThis.__counter || 0) + 1;
+ return globalThis.__injected;
+ },
+ { timeout: 0, polling: 10 }
+ );
+ await page.waitForFunction(() => globalThis.__counter > 10);
+ await page.evaluate(() => (globalThis.__injected = true));
+ await watchdog;
+ });
+ it('should survive cross-process navigation', async () => {
+ const { page, server } = getTestState();
+
+ let fooFound = false;
+ const waitForFunction = page
+ .waitForFunction('globalThis.__FOO === 1')
+ .then(() => (fooFound = true));
+ await page.goto(server.EMPTY_PAGE);
+ expect(fooFound).toBe(false);
+ await page.reload();
+ expect(fooFound).toBe(false);
+ await page.goto(server.CROSS_PROCESS_PREFIX + '/grid.html');
+ expect(fooFound).toBe(false);
+ await page.evaluate(() => (globalThis.__FOO = 1));
+ await waitForFunction;
+ expect(fooFound).toBe(true);
+ });
+ it('should survive navigations', async () => {
+ const { page, server } = getTestState();
+
+ const watchdog = page.waitForFunction(() => globalThis.__done);
+ await page.goto(server.EMPTY_PAGE);
+ await page.goto(server.PREFIX + '/consolelog.html');
+ await page.evaluate(() => (globalThis.__done = true));
+ await watchdog;
+ });
+ });
+
+ describe('Page.waitForTimeout', () => {
+ it('waits for the given timeout before resolving', async () => {
+ const { page, server } = getTestState();
+ await page.goto(server.EMPTY_PAGE);
+ const startTime = Date.now();
+ await page.waitForTimeout(1000);
+ const endTime = Date.now();
+ /* In a perfect world endTime - startTime would be exactly 1000 but we
+ * expect some fluctuations and for it to be off by a little bit. So to
+ * avoid a flaky test we'll make sure it waited for roughly 1 second.
+ */
+ expect(endTime - startTime).toBeGreaterThan(700);
+ expect(endTime - startTime).toBeLessThan(1300);
+ });
+ });
+
+ describe('Frame.waitForTimeout', () => {
+ it('waits for the given timeout before resolving', async () => {
+ const { page, server } = getTestState();
+ await page.goto(server.EMPTY_PAGE);
+ const frame = page.mainFrame();
+ const startTime = Date.now();
+ await frame.waitForTimeout(1000);
+ const endTime = Date.now();
+ /* In a perfect world endTime - startTime would be exactly 1000 but we
+ * expect some fluctuations and for it to be off by a little bit. So to
+ * avoid a flaky test we'll make sure it waited for roughly 1 second
+ */
+ expect(endTime - startTime).toBeGreaterThan(700);
+ expect(endTime - startTime).toBeLessThan(1300);
+ });
+ });
+
+ describe('Frame.waitForSelector', function () {
+ const addElement = (tag) =>
+ document.body.appendChild(document.createElement(tag));
+
+ it('should immediately resolve promise if node exists', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const frame = page.mainFrame();
+ await frame.waitForSelector('*');
+ await frame.evaluate(addElement, 'div');
+ await frame.waitForSelector('div');
+ });
+
+ itFailsFirefox('should work with removed MutationObserver', async () => {
+ const { page } = getTestState();
+
+ await page.evaluate(() => delete window.MutationObserver);
+ const [handle] = await Promise.all([
+ page.waitForSelector('.zombo'),
+ page.setContent(`anything
`),
+ ]);
+ expect(
+ await page.evaluate((x: HTMLElement) => x.textContent, handle)
+ ).toBe('anything');
+ });
+
+ it('should resolve promise when node is added', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const frame = page.mainFrame();
+ const watchdog = frame.waitForSelector('div');
+ await frame.evaluate(addElement, 'br');
+ await frame.evaluate(addElement, 'div');
+ const eHandle = await watchdog;
+ const tagName = await eHandle
+ .getProperty('tagName')
+ .then((e) => e.jsonValue());
+ expect(tagName).toBe('DIV');
+ });
+
+ it('should work when node is added through innerHTML', async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const watchdog = page.waitForSelector('h3 div');
+ await page.evaluate(addElement, 'span');
+ await page.evaluate(
+ () =>
+ (document.querySelector('span').innerHTML = '
')
+ );
+ await watchdog;
+ });
+
+ itFailsFirefox(
+ 'Page.waitForSelector is shortcut for main frame',
+ async () => {
+ const { page, server } = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ const otherFrame = page.frames()[1];
+ const watchdog = page.waitForSelector('div');
+ await otherFrame.evaluate(addElement, 'div');
+ await page.evaluate(addElement, 'div');
+ const eHandle = await watchdog;
+ expect(eHandle.executionContext().frame()).toBe(page.mainFrame());
+ }
+ );
+
+ itFailsFirefox('should run in specified frame', async () => {
+ const { page, server } = getTestState();
+
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
+ const frame1 = page.frames()[1];
+ const frame2 = page.frames()[2];
+ const waitForSelectorPromise = frame2.waitForSelector('div');
+ await frame1.evaluate(addElement, 'div');
+ await frame2.evaluate(addElement, 'div');
+ const eHandle = await waitForSelectorPromise;
+ expect(eHandle.executionContext().frame()).toBe(frame2);
+ });
+
+ itFailsFirefox('should throw when frame is detached', async () => {
+ const { page, server } = getTestState();
+
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ const frame = page.frames()[1];
+ let waitError = null;
+ const waitPromise = frame
+ .waitForSelector('.box')
+ .catch((error) => (waitError = error));
+ await utils.detachFrame(page, 'frame1');
+ await waitPromise;
+ expect(waitError).toBeTruthy();
+ expect(waitError.message).toContain(
+ 'waitForFunction failed: frame got detached.'
+ );
+ });
+ it('should survive cross-process navigation', async () => {
+ const { page, server } = getTestState();
+
+ let boxFound = false;
+ const waitForSelector = page
+ .waitForSelector('.box')
+ .then(() => (boxFound = true));
+ await page.goto(server.EMPTY_PAGE);
+ expect(boxFound).toBe(false);
+ await page.reload();
+ expect(boxFound).toBe(false);
+ await page.goto(server.CROSS_PROCESS_PREFIX + '/grid.html');
+ await waitForSelector;
+ expect(boxFound).toBe(true);
+ });
+ it('should wait for visible', async () => {
+ const { page } = getTestState();
+
+ let divFound = false;
+ const waitForSelector = page
+ .waitForSelector('div', { visible: true })
+ .then(() => (divFound = true));
+ await page.setContent(
+ `1
`
+ );
+ expect(divFound).toBe(false);
+ await page.evaluate(() =>
+ document.querySelector('div').style.removeProperty('display')
+ );
+ expect(divFound).toBe(false);
+ await page.evaluate(() =>
+ document.querySelector('div').style.removeProperty('visibility')
+ );
+ expect(await waitForSelector).toBe(true);
+ expect(divFound).toBe(true);
+ });
+ it('should wait for visible recursively', async () => {
+ const { page } = getTestState();
+
+ let divVisible = false;
+ const waitForSelector = page
+ .waitForSelector('div#inner', { visible: true })
+ .then(() => (divVisible = true));
+ await page.setContent(
+ ``
+ );
+ expect(divVisible).toBe(false);
+ await page.evaluate(() =>
+ document.querySelector('div').style.removeProperty('display')
+ );
+ expect(divVisible).toBe(false);
+ await page.evaluate(() =>
+ document.querySelector('div').style.removeProperty('visibility')
+ );
+ expect(await waitForSelector).toBe(true);
+ expect(divVisible).toBe(true);
+ });
+ it('hidden should wait for visibility: hidden', async () => {
+ const { page } = getTestState();
+
+ let divHidden = false;
+ await page.setContent(`
`);
+ const waitForSelector = page
+ .waitForSelector('div', { hidden: true })
+ .then(() => (divHidden = true));
+ await page.waitForSelector('div'); // do a round trip
+ expect(divHidden).toBe(false);
+ await page.evaluate(() =>
+ document.querySelector('div').style.setProperty('visibility', 'hidden')
+ );
+ expect(await waitForSelector).toBe(true);
+ expect(divHidden).toBe(true);
+ });
+ it('hidden should wait for display: none', async () => {
+ const { page } = getTestState();
+
+ let divHidden = false;
+ await page.setContent(`
`);
+ const waitForSelector = page
+ .waitForSelector('div', { hidden: true })
+ .then(() => (divHidden = true));
+ await page.waitForSelector('div'); // do a round trip
+ expect(divHidden).toBe(false);
+ await page.evaluate(() =>
+ document.querySelector('div').style.setProperty('display', 'none')
+ );
+ expect(await waitForSelector).toBe(true);
+ expect(divHidden).toBe(true);
+ });
+ it('hidden should wait for removal', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(`
`);
+ let divRemoved = false;
+ const waitForSelector = page
+ .waitForSelector('div', { hidden: true })
+ .then(() => (divRemoved = true));
+ await page.waitForSelector('div'); // do a round trip
+ expect(divRemoved).toBe(false);
+ await page.evaluate(() => document.querySelector('div').remove());
+ expect(await waitForSelector).toBe(true);
+ expect(divRemoved).toBe(true);
+ });
+ it('should return null if waiting to hide non-existing element', async () => {
+ const { page } = getTestState();
+
+ const handle = await page.waitForSelector('non-existing', {
+ hidden: true,
+ });
+ expect(handle).toBe(null);
+ });
+ it('should respect timeout', async () => {
+ const { page, puppeteer } = getTestState();
+
+ let error = null;
+ await page
+ .waitForSelector('div', { timeout: 10 })
+ .catch((error_) => (error = error_));
+ expect(error).toBeTruthy();
+ expect(error.message).toContain(
+ 'waiting for selector `div` failed: timeout'
+ );
+ expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
+ });
+ it('should have an error message specifically for awaiting an element to be hidden', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(`
`);
+ let error = null;
+ await page
+ .waitForSelector('div', { hidden: true, timeout: 10 })
+ .catch((error_) => (error = error_));
+ expect(error).toBeTruthy();
+ expect(error.message).toContain(
+ 'waiting for selector `div` to be hidden failed: timeout'
+ );
+ });
+
+ it('should respond to node attribute mutation', async () => {
+ const { page } = getTestState();
+
+ let divFound = false;
+ const waitForSelector = page
+ .waitForSelector('.zombo')
+ .then(() => (divFound = true));
+ await page.setContent(`
`);
+ expect(divFound).toBe(false);
+ await page.evaluate(
+ () => (document.querySelector('div').className = 'zombo')
+ );
+ expect(await waitForSelector).toBe(true);
+ });
+ it('should return the element handle', async () => {
+ const { page } = getTestState();
+
+ const waitForSelector = page.waitForSelector('.zombo');
+ await page.setContent(`anything
`);
+ expect(
+ await page.evaluate(
+ (x: HTMLElement) => x.textContent,
+ await waitForSelector
+ )
+ ).toBe('anything');
+ });
+ it('should have correct stack trace for timeout', async () => {
+ const { page } = getTestState();
+
+ let error;
+ await page
+ .waitForSelector('.zombo', { timeout: 10 })
+ .catch((error_) => (error = error_));
+ expect(error.stack).toContain('waiting for selector `.zombo` failed');
+ // The extension is ts here as Mocha maps back via sourcemaps.
+ expect(error.stack).toContain('waittask.spec.ts');
+ });
+ });
+
+ describe('Frame.waitForXPath', function () {
+ const addElement = (tag) =>
+ document.body.appendChild(document.createElement(tag));
+
+ it('should support some fancy xpath', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(`red herring
hello world
`);
+ const waitForXPath = page.waitForXPath(
+ '//p[normalize-space(.)="hello world"]'
+ );
+ expect(
+ await page.evaluate(
+ (x: HTMLElement) => x.textContent,
+ await waitForXPath
+ )
+ ).toBe('hello world ');
+ });
+ it('should respect timeout', async () => {
+ const { page, puppeteer } = getTestState();
+
+ let error = null;
+ await page
+ .waitForXPath('//div', { timeout: 10 })
+ .catch((error_) => (error = error_));
+ expect(error).toBeTruthy();
+ expect(error.message).toContain(
+ 'waiting for XPath `//div` failed: timeout'
+ );
+ expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
+ });
+ itFailsFirefox('should run in specified frame', async () => {
+ const { page, server } = getTestState();
+
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
+ const frame1 = page.frames()[1];
+ const frame2 = page.frames()[2];
+ const waitForXPathPromise = frame2.waitForXPath('//div');
+ await frame1.evaluate(addElement, 'div');
+ await frame2.evaluate(addElement, 'div');
+ const eHandle = await waitForXPathPromise;
+ expect(eHandle.executionContext().frame()).toBe(frame2);
+ });
+ itFailsFirefox('should throw when frame is detached', async () => {
+ const { page, server } = getTestState();
+
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ const frame = page.frames()[1];
+ let waitError = null;
+ const waitPromise = frame
+ .waitForXPath('//*[@class="box"]')
+ .catch((error) => (waitError = error));
+ await utils.detachFrame(page, 'frame1');
+ await waitPromise;
+ expect(waitError).toBeTruthy();
+ expect(waitError.message).toContain(
+ 'waitForFunction failed: frame got detached.'
+ );
+ });
+ it('hidden should wait for display: none', async () => {
+ const { page } = getTestState();
+
+ let divHidden = false;
+ await page.setContent(`
`);
+ const waitForXPath = page
+ .waitForXPath('//div', { hidden: true })
+ .then(() => (divHidden = true));
+ await page.waitForXPath('//div'); // do a round trip
+ expect(divHidden).toBe(false);
+ await page.evaluate(() =>
+ document.querySelector('div').style.setProperty('display', 'none')
+ );
+ expect(await waitForXPath).toBe(true);
+ expect(divHidden).toBe(true);
+ });
+ it('should return the element handle', async () => {
+ const { page } = getTestState();
+
+ const waitForXPath = page.waitForXPath('//*[@class="zombo"]');
+ await page.setContent(`anything
`);
+ expect(
+ await page.evaluate(
+ (x: HTMLElement) => x.textContent,
+ await waitForXPath
+ )
+ ).toBe('anything');
+ });
+ it('should allow you to select a text node', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(`some text
`);
+ const text = await page.waitForXPath('//div/text()');
+ expect(await (await text.getProperty('nodeType')).jsonValue()).toBe(
+ 3 /* Node.TEXT_NODE */
+ );
+ });
+ it('should allow you to select an element with single slash', async () => {
+ const { page } = getTestState();
+
+ await page.setContent(`some text
`);
+ const waitForXPath = page.waitForXPath('/html/body/div');
+ expect(
+ await page.evaluate(
+ (x: HTMLElement) => x.textContent,
+ await waitForXPath
+ )
+ ).toBe('some text');
+ });
+ });
+});
diff --git a/test/worker.spec.ts b/test/worker.spec.ts
new file mode 100644
index 0000000000000..b4f81dbb8cf04
--- /dev/null
+++ b/test/worker.spec.ts
@@ -0,0 +1,126 @@
+/**
+ * Copyright 2020 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import expect from 'expect';
+import {
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+ describeFailsFirefox,
+} from './mocha-utils'; // eslint-disable-line import/extensions
+import utils from './utils.js';
+import { WebWorker } from '../lib/cjs/puppeteer/common/WebWorker.js';
+import { ConsoleMessage } from '../lib/cjs/puppeteer/common/ConsoleMessage.js';
+const { waitEvent } = utils;
+
+describeFailsFirefox('Workers', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+ it('Page.workers', async () => {
+ const { page, server } = getTestState();
+
+ await Promise.all([
+ new Promise((x) => page.once('workercreated', x)),
+ page.goto(server.PREFIX + '/worker/worker.html'),
+ ]);
+ const worker = page.workers()[0];
+ expect(worker.url()).toContain('worker.js');
+
+ expect(await worker.evaluate(() => globalThis.workerFunction())).toBe(
+ 'worker function result'
+ );
+
+ await page.goto(server.EMPTY_PAGE);
+ expect(page.workers().length).toBe(0);
+ });
+ it('should emit created and destroyed events', async () => {
+ const { page } = getTestState();
+
+ const workerCreatedPromise = new Promise((x) =>
+ page.once('workercreated', x)
+ );
+ const workerObj = await page.evaluateHandle(
+ () => new Worker('data:text/javascript,1')
+ );
+ const worker = await workerCreatedPromise;
+ const workerThisObj = await worker.evaluateHandle(() => this);
+ const workerDestroyedPromise = new Promise((x) =>
+ page.once('workerdestroyed', x)
+ );
+ await page.evaluate(
+ (workerObj: Worker) => workerObj.terminate(),
+ workerObj
+ );
+ expect(await workerDestroyedPromise).toBe(worker);
+ const error = await workerThisObj
+ .getProperty('self')
+ .catch((error) => error);
+ expect(error.message).toContain('Most likely the worker has been closed.');
+ });
+ it('should report console logs', async () => {
+ const { page } = getTestState();
+
+ const [message] = await Promise.all([
+ waitEvent(page, 'console'),
+ page.evaluate(() => new Worker(`data:text/javascript,console.log(1)`)),
+ ]);
+ expect(message.text()).toBe('1');
+ expect(message.location()).toEqual({
+ url: '',
+ lineNumber: 0,
+ columnNumber: 8,
+ });
+ });
+ it('should have JSHandles for console logs', async () => {
+ const { page } = getTestState();
+
+ const logPromise = new Promise((x) =>
+ page.on('console', x)
+ );
+ await page.evaluate(
+ () => new Worker(`data:text/javascript,console.log(1,2,3,this)`)
+ );
+ const log = await logPromise;
+ expect(log.text()).toBe('1 2 3 JSHandle@object');
+ expect(log.args().length).toBe(4);
+ expect(await (await log.args()[3].getProperty('origin')).jsonValue()).toBe(
+ 'null'
+ );
+ });
+ it('should have an execution context', async () => {
+ const { page } = getTestState();
+
+ const workerCreatedPromise = new Promise((x) =>
+ page.once('workercreated', x)
+ );
+ await page.evaluate(
+ () => new Worker(`data:text/javascript,console.log(1)`)
+ );
+ const worker = await workerCreatedPromise;
+ expect(await (await worker.executionContext()).evaluate('1+1')).toBe(2);
+ });
+ it('should report errors', async () => {
+ const { page } = getTestState();
+
+ const errorPromise = new Promise((x) => page.on('pageerror', x));
+ await page.evaluate(
+ () =>
+ new Worker(`data:text/javascript, throw new Error('this is my error');`)
+ );
+ const errorLog = await errorPromise;
+ expect(errorLog.message).toContain('this is my error');
+ });
+});
diff --git a/tsconfig.base.json b/tsconfig.base.json
new file mode 100644
index 0000000000000..571f4cc62e4e2
--- /dev/null
+++ b/tsconfig.base.json
@@ -0,0 +1,13 @@
+{
+ "compilerOptions": {
+ "esModuleInterop": true,
+ "allowJs": true,
+ "checkJs": true,
+ "target": "ES2019",
+ "moduleResolution": "node",
+ "declaration": true,
+ "declarationMap": true,
+ "resolveJsonModule": true,
+ "sourceMap": true
+ }
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000000000..0fe7e06ad83c7
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,16 @@
+/**
+ * This configuration only exists for the API Extractor tool and for VSCode to use. It is NOT the tsconfig used for compilation.
+ * For CJS builds, `tsconfig.cjs.json` is used, and for ESM, it's `tsconfig.esm.json`.
+ * See the details in CONTRIBUTING.md that describes our TypeScript setup.
+ */
+{
+ "extends": "./tsconfig.base.json",
+ "compilerOptions": {
+ "noEmit": true,
+ /* This module setting is just for VSCode so it doesn't throw error when we
+ use dynamic imports.
+ */
+ "module": "esnext"
+ },
+ "include": ["src"]
+}
diff --git a/typescript-if-required.js b/typescript-if-required.js
new file mode 100644
index 0000000000000..96e6b541a7d27
--- /dev/null
+++ b/typescript-if-required.js
@@ -0,0 +1,61 @@
+/**
+ * Copyright 2020 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const child_process = require('child_process');
+const path = require('path');
+const fs = require('fs');
+const { promisify } = require('util');
+
+const exec = promisify(child_process.exec);
+const fsAccess = promisify(fs.access);
+
+const fileExists = async (filePath) =>
+ fsAccess(filePath)
+ .then(() => true)
+ .catch(() => false);
+/*
+
+ * Now Puppeteer is built with TypeScript, we need to ensure that
+ * locally we have the generated output before trying to install.
+ *
+ * For users installing puppeteer this is fine, they will have the
+ * generated lib/ directory as we ship it when we publish to npm.
+ *
+ * However, if you're cloning the repo to contribute, you won't have the
+ * generated lib/ directory so this script checks if we need to run
+ * TypeScript first to ensure the output exists and is in the right
+ * place.
+ */
+async function compileTypeScript() {
+ return exec('npm run tsc').catch((error) => {
+ console.error('Error running TypeScript', error);
+ process.exit(1);
+ });
+}
+
+async function compileTypeScriptIfRequired() {
+ const libPath = path.join(__dirname, 'lib');
+ const libExists = await fileExists(libPath);
+ if (libExists) return;
+
+ console.log('Puppeteer:', 'Compiling TypeScript...');
+ await compileTypeScript();
+}
+
+// It's being run as node typescript-if-required.js, not require('..')
+if (require.main === module) compileTypeScriptIfRequired();
+
+module.exports = compileTypeScriptIfRequired;
diff --git a/utils/ESTreeWalker.js b/utils/ESTreeWalker.js
new file mode 100644
index 0000000000000..1c6c6d47828ed
--- /dev/null
+++ b/utils/ESTreeWalker.js
@@ -0,0 +1,135 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @unrestricted
+ */
+class ESTreeWalker {
+ /**
+ * @param {function(!ESTree.Node):(!Object|undefined)} beforeVisit
+ * @param {function(!ESTree.Node)=} afterVisit
+ */
+ constructor(beforeVisit, afterVisit) {
+ this._beforeVisit = beforeVisit;
+ this._afterVisit = afterVisit || new Function();
+ }
+
+ /**
+ * @param {!ESTree.Node} ast
+ */
+ walk(ast) {
+ this._innerWalk(ast, null);
+ }
+
+ /**
+ * @param {!ESTree.Node} node
+ * @param {?ESTree.Node} parent
+ */
+ _innerWalk(node, parent) {
+ if (!node) return;
+ node.parent = parent;
+
+ if (this._beforeVisit.call(null, node) === ESTreeWalker.SkipSubtree) {
+ this._afterVisit.call(null, node);
+ return;
+ }
+
+ const walkOrder = ESTreeWalker._walkOrder[node.type];
+ if (!walkOrder) return;
+
+ if (node.type === 'TemplateLiteral') {
+ const templateLiteral = /** @type {!ESTree.TemplateLiteralNode} */ (node);
+ const expressionsLength = templateLiteral.expressions.length;
+ for (let i = 0; i < expressionsLength; ++i) {
+ this._innerWalk(templateLiteral.quasis[i], templateLiteral);
+ this._innerWalk(templateLiteral.expressions[i], templateLiteral);
+ }
+ this._innerWalk(
+ templateLiteral.quasis[expressionsLength],
+ templateLiteral
+ );
+ } else {
+ for (let i = 0; i < walkOrder.length; ++i) {
+ const entity = node[walkOrder[i]];
+ if (Array.isArray(entity)) this._walkArray(entity, node);
+ else this._innerWalk(entity, node);
+ }
+ }
+
+ this._afterVisit.call(null, node);
+ }
+
+ /**
+ * @param {!Array.} nodeArray
+ * @param {?ESTree.Node} parentNode
+ */
+ _walkArray(nodeArray, parentNode) {
+ for (let i = 0; i < nodeArray.length; ++i)
+ this._innerWalk(nodeArray[i], parentNode);
+ }
+}
+
+/** @typedef {!Object} ESTreeWalker.SkipSubtree */
+ESTreeWalker.SkipSubtree = {};
+
+/** @enum {!Array.} */
+ESTreeWalker._walkOrder = {
+ AwaitExpression: ['argument'],
+ ArrayExpression: ['elements'],
+ ArrowFunctionExpression: ['params', 'body'],
+ AssignmentExpression: ['left', 'right'],
+ AssignmentPattern: ['left', 'right'],
+ BinaryExpression: ['left', 'right'],
+ BlockStatement: ['body'],
+ BreakStatement: ['label'],
+ CallExpression: ['callee', 'arguments'],
+ CatchClause: ['param', 'body'],
+ ClassBody: ['body'],
+ ClassDeclaration: ['id', 'superClass', 'body'],
+ ClassExpression: ['id', 'superClass', 'body'],
+ ConditionalExpression: ['test', 'consequent', 'alternate'],
+ ContinueStatement: ['label'],
+ DebuggerStatement: [],
+ DoWhileStatement: ['body', 'test'],
+ EmptyStatement: [],
+ ExpressionStatement: ['expression'],
+ ForInStatement: ['left', 'right', 'body'],
+ ForOfStatement: ['left', 'right', 'body'],
+ ForStatement: ['init', 'test', 'update', 'body'],
+ FunctionDeclaration: ['id', 'params', 'body'],
+ FunctionExpression: ['id', 'params', 'body'],
+ Identifier: [],
+ IfStatement: ['test', 'consequent', 'alternate'],
+ LabeledStatement: ['label', 'body'],
+ Literal: [],
+ LogicalExpression: ['left', 'right'],
+ MemberExpression: ['object', 'property'],
+ MethodDefinition: ['key', 'value'],
+ NewExpression: ['callee', 'arguments'],
+ ObjectExpression: ['properties'],
+ ObjectPattern: ['properties'],
+ ParenthesizedExpression: ['expression'],
+ Program: ['body'],
+ Property: ['key', 'value'],
+ ReturnStatement: ['argument'],
+ SequenceExpression: ['expressions'],
+ Super: [],
+ SwitchCase: ['test', 'consequent'],
+ SwitchStatement: ['discriminant', 'cases'],
+ TaggedTemplateExpression: ['tag', 'quasi'],
+ TemplateElement: [],
+ TemplateLiteral: ['quasis', 'expressions'],
+ ThisExpression: [],
+ ThrowStatement: ['argument'],
+ TryStatement: ['block', 'handler', 'finalizer'],
+ UnaryExpression: ['argument'],
+ UpdateExpression: ['argument'],
+ VariableDeclaration: ['declarations'],
+ VariableDeclarator: ['id', 'init'],
+ WhileStatement: ['test', 'body'],
+ WithStatement: ['object', 'body'],
+ YieldExpression: ['argument'],
+};
+
+module.exports = ESTreeWalker;
diff --git a/utils/apply_next_version.js b/utils/apply_next_version.js
new file mode 100644
index 0000000000000..2882eed45b791
--- /dev/null
+++ b/utils/apply_next_version.js
@@ -0,0 +1,31 @@
+const path = require('path');
+const fs = require('fs');
+const execSync = require('child_process').execSync;
+
+// Compare current HEAD to upstream main SHA.
+// If they are not equal - refuse to publish since
+// we're not tip-of-tree.
+const upstream_sha = execSync(
+ `git ls-remote https://github.com/puppeteer/puppeteer --tags main | cut -f1`
+).toString('utf8');
+const current_sha = execSync(`git rev-parse HEAD`).toString('utf8');
+if (upstream_sha.trim() !== current_sha.trim()) {
+ console.log('REFUSING TO PUBLISH: this is not tip-of-tree!');
+ process.exit(1);
+}
+
+const package = require('../package.json');
+let version = package.version;
+const dashIndex = version.indexOf('-');
+if (dashIndex !== -1) version = version.substring(0, dashIndex);
+version += '-next.' + Date.now();
+console.log('Setting version to ' + version);
+package.version = version;
+fs.writeFileSync(
+ path.join(__dirname, '..', 'package.json'),
+ JSON.stringify(package, undefined, 2) + '\n'
+);
+
+console.log(
+ 'IMPORTANT: you should update the pinned version of devtools-protocol to match the new revision.'
+);
diff --git a/utils/bisect.js b/utils/bisect.js
new file mode 100755
index 0000000000000..60b7ba07c61d5
--- /dev/null
+++ b/utils/bisect.js
@@ -0,0 +1,288 @@
+#!/usr/bin/env node
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const URL = require('url');
+const debug = require('debug');
+const pptr = require('..');
+const browserFetcher = pptr.createBrowserFetcher();
+const path = require('path');
+const fs = require('fs');
+const { fork, spawn, execSync } = require('child_process');
+
+const COLOR_RESET = '\x1b[0m';
+const COLOR_RED = '\x1b[31m';
+const COLOR_GREEN = '\x1b[32m';
+const COLOR_YELLOW = '\x1b[33m';
+
+const argv = require('minimist')(process.argv.slice(2), {});
+
+const help = `
+Usage:
+ node bisect.js --good --bad
+```
+
+You can find the library on `window.mitt`.
+
+## Usage
+
+```js
+import mitt from 'mitt'
+
+const emitter = mitt()
+
+// listen to an event
+emitter.on('foo', e => console.log('foo', e) )
+
+// listen to all events
+emitter.on('*', (type, e) => console.log(type, e) )
+
+// fire an event
+emitter.emit('foo', { a: 'b' })
+
+// clearing all events
+emitter.all.clear()
+
+// working with handler references:
+function onFoo() {}
+emitter.on('foo', onFoo) // listen
+emitter.off('foo', onFoo) // unlisten
+```
+
+### Typescript
+
+```ts
+import mitt from 'mitt';
+const emitter: mitt.Emitter = mitt();
+```
+
+## Examples & Demos
+
+
+ Preact + Mitt Codepen Demo
+
+
+
+
+* * *
+
+## API
+
+
+
+#### Table of Contents
+
+- [mitt](#mitt)
+- [all](#all)
+- [on](#on)
+ - [Parameters](#parameters)
+- [off](#off)
+ - [Parameters](#parameters-1)
+- [emit](#emit)
+ - [Parameters](#parameters-2)
+
+### mitt
+
+Mitt: Tiny (~200b) functional event emitter / pubsub.
+
+Returns **Mitt**
+
+### all
+
+A Map of event names to registered handler functions.
+
+### on
+
+Register an event handler for the given type.
+
+#### Parameters
+
+- `type` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [symbol](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Symbol))** Type of event to listen for, or `"*"` for all events
+- `handler` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** Function to call in response to given event
+
+### off
+
+Remove an event handler for the given type.
+
+#### Parameters
+
+- `type` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [symbol](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Symbol))** Type of event to unregister `handler` from, or `"*"`
+- `handler` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** Handler function to remove
+
+### emit
+
+Invoke all handlers for the given type.
+If present, `"*"` handlers are invoked after type-matched handlers.
+
+Note: Manually firing "\*" handlers is not supported.
+
+#### Parameters
+
+- `type` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [symbol](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Symbol))** The event type to invoke
+- `evt` **Any?** Any value (object is recommended and powerful), passed to each handler
+
+## Contribute
+
+First off, thanks for taking the time to contribute!
+Now, take a moment to be sure your contributions make sense to everyone else.
+
+### Reporting Issues
+
+Found a problem? Want a new feature? First of all see if your issue or idea has [already been reported](../../issues).
+If don't, just open a [new clear and descriptive issue](../../issues/new).
+
+### Submitting pull requests
+
+Pull requests are the greatest contributions, so be sure they are focused in scope, and do avoid unrelated commits.
+
+- Fork it!
+- Clone your fork: `git clone https://github.com//mitt`
+- Navigate to the newly cloned directory: `cd mitt`
+- Create a new branch for the new feature: `git checkout -b my-new-feature`
+- Install the tools necessary for development: `npm install`
+- Make your changes.
+- Commit your changes: `git commit -am 'Add some feature'`
+- Push to the branch: `git push origin my-new-feature`
+- Submit a pull request with full remarks documenting your changes.
+
+## License
+
+[MIT License](https://opensource.org/licenses/MIT) © [Jason Miller](https://jasonformat.com/)
diff --git a/vendor/mitt/dist/mitt.es.js b/vendor/mitt/dist/mitt.es.js
new file mode 100644
index 0000000000000..889e27282f65c
--- /dev/null
+++ b/vendor/mitt/dist/mitt.es.js
@@ -0,0 +1,2 @@
+export default function(n){return{all:n=n||new Map,on:function(t,e){var i=n.get(t);i&&i.push(e)||n.set(t,[e])},off:function(t,e){var i=n.get(t);i&&i.splice(i.indexOf(e)>>>0,1)},emit:function(t,e){(n.get(t)||[]).slice().map(function(n){n(e)}),(n.get("*")||[]).slice().map(function(n){n(t,e)})}}}
+//# sourceMappingURL=mitt.es.js.map
diff --git a/vendor/mitt/dist/mitt.es.js.map b/vendor/mitt/dist/mitt.es.js.map
new file mode 100644
index 0000000000000..6576278e2da07
--- /dev/null
+++ b/vendor/mitt/dist/mitt.es.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"mitt.es.js","sources":["../src/index.ts"],"sourcesContent":["export type EventType = string | symbol;\n\n// An event handler can take an optional event argument\n// and should not return a value\nexport type Handler = (event?: T) => void;\nexport type WildcardHandler = (type: EventType, event?: any) => void;\n\n// An array of all currently registered event handlers for a type\nexport type EventHandlerList = Array;\nexport type WildCardEventHandlerList = Array;\n\n// A map of event types and their corresponding event handlers.\nexport type EventHandlerMap = Map;\n\nexport interface Emitter {\n\tall: EventHandlerMap;\n\n\ton(type: EventType, handler: Handler): void;\n\ton(type: '*', handler: WildcardHandler): void;\n\n\toff(type: EventType, handler: Handler): void;\n\toff(type: '*', handler: WildcardHandler): void;\n\n\temit(type: EventType, event?: T): void;\n\temit(type: '*', event?: any): void;\n}\n\n/**\n * Mitt: Tiny (~200b) functional event emitter / pubsub.\n * @name mitt\n * @returns {Mitt}\n */\nexport default function mitt(all?: EventHandlerMap): Emitter {\n\tall = all || new Map();\n\n\treturn {\n\n\t\t/**\n\t\t * A Map of event names to registered handler functions.\n\t\t */\n\t\tall,\n\n\t\t/**\n\t\t * Register an event handler for the given type.\n\t\t * @param {string|symbol} type Type of event to listen for, or `\"*\"` for all events\n\t\t * @param {Function} handler Function to call in response to given event\n\t\t * @memberOf mitt\n\t\t */\n\t\ton(type: EventType, handler: Handler) {\n\t\t\tconst handlers = all.get(type);\n\t\t\tconst added = handlers && handlers.push(handler);\n\t\t\tif (!added) {\n\t\t\t\tall.set(type, [handler]);\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Remove an event handler for the given type.\n\t\t * @param {string|symbol} type Type of event to unregister `handler` from, or `\"*\"`\n\t\t * @param {Function} handler Handler function to remove\n\t\t * @memberOf mitt\n\t\t */\n\t\toff(type: EventType, handler: Handler) {\n\t\t\tconst handlers = all.get(type);\n\t\t\tif (handlers) {\n\t\t\t\thandlers.splice(handlers.indexOf(handler) >>> 0, 1);\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Invoke all handlers for the given type.\n\t\t * If present, `\"*\"` handlers are invoked after type-matched handlers.\n\t\t *\n\t\t * Note: Manually firing \"*\" handlers is not supported.\n\t\t *\n\t\t * @param {string|symbol} type The event type to invoke\n\t\t * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler\n\t\t * @memberOf mitt\n\t\t */\n\t\temit(type: EventType, evt: T) {\n\t\t\t((all.get(type) || []) as EventHandlerList).slice().map((handler) => { handler(evt); });\n\t\t\t((all.get('*') || []) as WildCardEventHandlerList).slice().map((handler) => { handler(type, evt); });\n\t\t}\n\t};\n}\n"],"names":["all","Map","on","type","handler","handlers","get","push","set","off","splice","indexOf","emit","evt","slice","map"],"mappings":"wBAgC6BA,GAG5B,MAAO,CAKNA,IAPDA,EAAMA,GAAO,IAAIC,IAehBC,YAAYC,EAAiBC,GAC5B,IAAMC,EAAWL,EAAIM,IAAIH,GACXE,GAAYA,EAASE,KAAKH,IAEvCJ,EAAIQ,IAAIL,EAAM,CAACC,KAUjBK,aAAaN,EAAiBC,GAC7B,IAAMC,EAAWL,EAAIM,IAAIH,GACrBE,GACHA,EAASK,OAAOL,EAASM,QAAQP,KAAa,EAAG,IAcnDQ,cAAcT,EAAiBU,IAC5Bb,EAAIM,IAAIH,IAAS,IAAyBW,QAAQC,IAAI,SAACX,GAAcA,EAAQS,MAC7Eb,EAAIM,IAAI,MAAQ,IAAiCQ,QAAQC,IAAI,SAACX,GAAcA,EAAQD,EAAMU"}
\ No newline at end of file
diff --git a/vendor/mitt/dist/mitt.js b/vendor/mitt/dist/mitt.js
new file mode 100644
index 0000000000000..2bd0cf9e44e53
--- /dev/null
+++ b/vendor/mitt/dist/mitt.js
@@ -0,0 +1,2 @@
+module.exports=function(n){return{all:n=n||new Map,on:function(e,t){var i=n.get(e);i&&i.push(t)||n.set(e,[t])},off:function(e,t){var i=n.get(e);i&&i.splice(i.indexOf(t)>>>0,1)},emit:function(e,t){(n.get(e)||[]).slice().map(function(n){n(t)}),(n.get("*")||[]).slice().map(function(n){n(e,t)})}}};
+//# sourceMappingURL=mitt.js.map
diff --git a/vendor/mitt/dist/mitt.js.map b/vendor/mitt/dist/mitt.js.map
new file mode 100644
index 0000000000000..37f6f59ebd6bd
--- /dev/null
+++ b/vendor/mitt/dist/mitt.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"mitt.js","sources":["../src/index.ts"],"sourcesContent":["export type EventType = string | symbol;\n\n// An event handler can take an optional event argument\n// and should not return a value\nexport type Handler = (event?: T) => void;\nexport type WildcardHandler = (type: EventType, event?: any) => void;\n\n// An array of all currently registered event handlers for a type\nexport type EventHandlerList = Array;\nexport type WildCardEventHandlerList = Array;\n\n// A map of event types and their corresponding event handlers.\nexport type EventHandlerMap = Map;\n\nexport interface Emitter {\n\tall: EventHandlerMap;\n\n\ton(type: EventType, handler: Handler): void;\n\ton(type: '*', handler: WildcardHandler): void;\n\n\toff(type: EventType, handler: Handler): void;\n\toff(type: '*', handler: WildcardHandler): void;\n\n\temit(type: EventType, event?: T): void;\n\temit(type: '*', event?: any): void;\n}\n\n/**\n * Mitt: Tiny (~200b) functional event emitter / pubsub.\n * @name mitt\n * @returns {Mitt}\n */\nexport default function mitt(all?: EventHandlerMap): Emitter {\n\tall = all || new Map();\n\n\treturn {\n\n\t\t/**\n\t\t * A Map of event names to registered handler functions.\n\t\t */\n\t\tall,\n\n\t\t/**\n\t\t * Register an event handler for the given type.\n\t\t * @param {string|symbol} type Type of event to listen for, or `\"*\"` for all events\n\t\t * @param {Function} handler Function to call in response to given event\n\t\t * @memberOf mitt\n\t\t */\n\t\ton(type: EventType, handler: Handler) {\n\t\t\tconst handlers = all.get(type);\n\t\t\tconst added = handlers && handlers.push(handler);\n\t\t\tif (!added) {\n\t\t\t\tall.set(type, [handler]);\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Remove an event handler for the given type.\n\t\t * @param {string|symbol} type Type of event to unregister `handler` from, or `\"*\"`\n\t\t * @param {Function} handler Handler function to remove\n\t\t * @memberOf mitt\n\t\t */\n\t\toff(type: EventType, handler: Handler) {\n\t\t\tconst handlers = all.get(type);\n\t\t\tif (handlers) {\n\t\t\t\thandlers.splice(handlers.indexOf(handler) >>> 0, 1);\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Invoke all handlers for the given type.\n\t\t * If present, `\"*\"` handlers are invoked after type-matched handlers.\n\t\t *\n\t\t * Note: Manually firing \"*\" handlers is not supported.\n\t\t *\n\t\t * @param {string|symbol} type The event type to invoke\n\t\t * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler\n\t\t * @memberOf mitt\n\t\t */\n\t\temit(type: EventType, evt: T) {\n\t\t\t((all.get(type) || []) as EventHandlerList).slice().map((handler) => { handler(evt); });\n\t\t\t((all.get('*') || []) as WildCardEventHandlerList).slice().map((handler) => { handler(type, evt); });\n\t\t}\n\t};\n}\n"],"names":["all","Map","on","type","handler","handlers","get","push","set","off","splice","indexOf","emit","evt","slice","map"],"mappings":"wBAgC6BA,GAG5B,MAAO,CAKNA,IAPDA,EAAMA,GAAO,IAAIC,IAehBC,YAAYC,EAAiBC,GAC5B,IAAMC,EAAWL,EAAIM,IAAIH,GACXE,GAAYA,EAASE,KAAKH,IAEvCJ,EAAIQ,IAAIL,EAAM,CAACC,KAUjBK,aAAaN,EAAiBC,GAC7B,IAAMC,EAAWL,EAAIM,IAAIH,GACrBE,GACHA,EAASK,OAAOL,EAASM,QAAQP,KAAa,EAAG,IAcnDQ,cAAcT,EAAiBU,IAC5Bb,EAAIM,IAAIH,IAAS,IAAyBW,QAAQC,IAAI,SAACX,GAAcA,EAAQS,MAC7Eb,EAAIM,IAAI,MAAQ,IAAiCQ,QAAQC,IAAI,SAACX,GAAcA,EAAQD,EAAMU"}
\ No newline at end of file
diff --git a/vendor/mitt/dist/mitt.modern.js b/vendor/mitt/dist/mitt.modern.js
new file mode 100644
index 0000000000000..0777f6de72093
--- /dev/null
+++ b/vendor/mitt/dist/mitt.modern.js
@@ -0,0 +1,2 @@
+export default function(e){return{all:e=e||new Map,on(t,n){const s=e.get(t);s&&s.push(n)||e.set(t,[n])},off(t,n){const s=e.get(t);s&&s.splice(s.indexOf(n)>>>0,1)},emit(t,n){(e.get(t)||[]).slice().map(e=>{e(n)}),(e.get("*")||[]).slice().map(e=>{e(t,n)})}}}
+//# sourceMappingURL=mitt.modern.js.map
diff --git a/vendor/mitt/dist/mitt.modern.js.map b/vendor/mitt/dist/mitt.modern.js.map
new file mode 100644
index 0000000000000..5f669b2d61d0b
--- /dev/null
+++ b/vendor/mitt/dist/mitt.modern.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"mitt.modern.js","sources":["../src/index.ts"],"sourcesContent":["export type EventType = string | symbol;\n\n// An event handler can take an optional event argument\n// and should not return a value\nexport type Handler = (event?: T) => void;\nexport type WildcardHandler = (type: EventType, event?: any) => void;\n\n// An array of all currently registered event handlers for a type\nexport type EventHandlerList = Array;\nexport type WildCardEventHandlerList = Array;\n\n// A map of event types and their corresponding event handlers.\nexport type EventHandlerMap = Map;\n\nexport interface Emitter {\n\tall: EventHandlerMap;\n\n\ton(type: EventType, handler: Handler): void;\n\ton(type: '*', handler: WildcardHandler): void;\n\n\toff(type: EventType, handler: Handler): void;\n\toff(type: '*', handler: WildcardHandler): void;\n\n\temit(type: EventType, event?: T): void;\n\temit(type: '*', event?: any): void;\n}\n\n/**\n * Mitt: Tiny (~200b) functional event emitter / pubsub.\n * @name mitt\n * @returns {Mitt}\n */\nexport default function mitt(all?: EventHandlerMap): Emitter {\n\tall = all || new Map();\n\n\treturn {\n\n\t\t/**\n\t\t * A Map of event names to registered handler functions.\n\t\t */\n\t\tall,\n\n\t\t/**\n\t\t * Register an event handler for the given type.\n\t\t * @param {string|symbol} type Type of event to listen for, or `\"*\"` for all events\n\t\t * @param {Function} handler Function to call in response to given event\n\t\t * @memberOf mitt\n\t\t */\n\t\ton(type: EventType, handler: Handler) {\n\t\t\tconst handlers = all.get(type);\n\t\t\tconst added = handlers && handlers.push(handler);\n\t\t\tif (!added) {\n\t\t\t\tall.set(type, [handler]);\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Remove an event handler for the given type.\n\t\t * @param {string|symbol} type Type of event to unregister `handler` from, or `\"*\"`\n\t\t * @param {Function} handler Handler function to remove\n\t\t * @memberOf mitt\n\t\t */\n\t\toff(type: EventType, handler: Handler) {\n\t\t\tconst handlers = all.get(type);\n\t\t\tif (handlers) {\n\t\t\t\thandlers.splice(handlers.indexOf(handler) >>> 0, 1);\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Invoke all handlers for the given type.\n\t\t * If present, `\"*\"` handlers are invoked after type-matched handlers.\n\t\t *\n\t\t * Note: Manually firing \"*\" handlers is not supported.\n\t\t *\n\t\t * @param {string|symbol} type The event type to invoke\n\t\t * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler\n\t\t * @memberOf mitt\n\t\t */\n\t\temit(type: EventType, evt: T) {\n\t\t\t((all.get(type) || []) as EventHandlerList).slice().map((handler) => { handler(evt); });\n\t\t\t((all.get('*') || []) as WildCardEventHandlerList).slice().map((handler) => { handler(type, evt); });\n\t\t}\n\t};\n}\n"],"names":["all","Map","on","type","handler","handlers","get","push","set","off","splice","indexOf","emit","evt","slice","map"],"mappings":"wBAgC6BA,GAG5B,MAAO,CAKNA,IAPDA,EAAMA,GAAO,IAAIC,IAehBC,GAAYC,EAAiBC,GAC5B,MAAMC,EAAWL,EAAIM,IAAIH,GACXE,GAAYA,EAASE,KAAKH,IAEvCJ,EAAIQ,IAAIL,EAAM,CAACC,KAUjBK,IAAaN,EAAiBC,GAC7B,MAAMC,EAAWL,EAAIM,IAAIH,GACrBE,GACHA,EAASK,OAAOL,EAASM,QAAQP,KAAa,EAAG,IAcnDQ,KAAcT,EAAiBU,IAC5Bb,EAAIM,IAAIH,IAAS,IAAyBW,QAAQC,IAAKX,IAAcA,EAAQS,MAC7Eb,EAAIM,IAAI,MAAQ,IAAiCQ,QAAQC,IAAKX,IAAcA,EAAQD,EAAMU"}
\ No newline at end of file
diff --git a/vendor/mitt/dist/mitt.umd.js b/vendor/mitt/dist/mitt.umd.js
new file mode 100644
index 0000000000000..ce0e0059aef8f
--- /dev/null
+++ b/vendor/mitt/dist/mitt.umd.js
@@ -0,0 +1,2 @@
+!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(e=e||self).mitt=n()}(this,function(){return function(e){return{all:e=e||new Map,on:function(n,t){var f=e.get(n);f&&f.push(t)||e.set(n,[t])},off:function(n,t){var f=e.get(n);f&&f.splice(f.indexOf(t)>>>0,1)},emit:function(n,t){(e.get(n)||[]).slice().map(function(e){e(t)}),(e.get("*")||[]).slice().map(function(e){e(n,t)})}}}});
+//# sourceMappingURL=mitt.umd.js.map
diff --git a/vendor/mitt/dist/mitt.umd.js.map b/vendor/mitt/dist/mitt.umd.js.map
new file mode 100644
index 0000000000000..642c89400653d
--- /dev/null
+++ b/vendor/mitt/dist/mitt.umd.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"mitt.umd.js","sources":["../src/index.ts"],"sourcesContent":["export type EventType = string | symbol;\n\n// An event handler can take an optional event argument\n// and should not return a value\nexport type Handler = (event?: T) => void;\nexport type WildcardHandler = (type: EventType, event?: any) => void;\n\n// An array of all currently registered event handlers for a type\nexport type EventHandlerList = Array;\nexport type WildCardEventHandlerList = Array;\n\n// A map of event types and their corresponding event handlers.\nexport type EventHandlerMap = Map;\n\nexport interface Emitter {\n\tall: EventHandlerMap;\n\n\ton(type: EventType, handler: Handler): void;\n\ton(type: '*', handler: WildcardHandler): void;\n\n\toff(type: EventType, handler: Handler): void;\n\toff(type: '*', handler: WildcardHandler): void;\n\n\temit(type: EventType, event?: T): void;\n\temit(type: '*', event?: any): void;\n}\n\n/**\n * Mitt: Tiny (~200b) functional event emitter / pubsub.\n * @name mitt\n * @returns {Mitt}\n */\nexport default function mitt(all?: EventHandlerMap): Emitter {\n\tall = all || new Map();\n\n\treturn {\n\n\t\t/**\n\t\t * A Map of event names to registered handler functions.\n\t\t */\n\t\tall,\n\n\t\t/**\n\t\t * Register an event handler for the given type.\n\t\t * @param {string|symbol} type Type of event to listen for, or `\"*\"` for all events\n\t\t * @param {Function} handler Function to call in response to given event\n\t\t * @memberOf mitt\n\t\t */\n\t\ton(type: EventType, handler: Handler) {\n\t\t\tconst handlers = all.get(type);\n\t\t\tconst added = handlers && handlers.push(handler);\n\t\t\tif (!added) {\n\t\t\t\tall.set(type, [handler]);\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Remove an event handler for the given type.\n\t\t * @param {string|symbol} type Type of event to unregister `handler` from, or `\"*\"`\n\t\t * @param {Function} handler Handler function to remove\n\t\t * @memberOf mitt\n\t\t */\n\t\toff(type: EventType, handler: Handler) {\n\t\t\tconst handlers = all.get(type);\n\t\t\tif (handlers) {\n\t\t\t\thandlers.splice(handlers.indexOf(handler) >>> 0, 1);\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Invoke all handlers for the given type.\n\t\t * If present, `\"*\"` handlers are invoked after type-matched handlers.\n\t\t *\n\t\t * Note: Manually firing \"*\" handlers is not supported.\n\t\t *\n\t\t * @param {string|symbol} type The event type to invoke\n\t\t * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler\n\t\t * @memberOf mitt\n\t\t */\n\t\temit(type: EventType, evt: T) {\n\t\t\t((all.get(type) || []) as EventHandlerList).slice().map((handler) => { handler(evt); });\n\t\t\t((all.get('*') || []) as WildCardEventHandlerList).slice().map((handler) => { handler(type, evt); });\n\t\t}\n\t};\n}\n"],"names":["all","Map","on","type","handler","handlers","get","push","set","off","splice","indexOf","emit","evt","slice","map"],"mappings":"6LAgC6BA,GAG5B,MAAO,CAKNA,IAPDA,EAAMA,GAAO,IAAIC,IAehBC,YAAYC,EAAiBC,GAC5B,IAAMC,EAAWL,EAAIM,IAAIH,GACXE,GAAYA,EAASE,KAAKH,IAEvCJ,EAAIQ,IAAIL,EAAM,CAACC,KAUjBK,aAAaN,EAAiBC,GAC7B,IAAMC,EAAWL,EAAIM,IAAIH,GACrBE,GACHA,EAASK,OAAOL,EAASM,QAAQP,KAAa,EAAG,IAcnDQ,cAAcT,EAAiBU,IAC5Bb,EAAIM,IAAIH,IAAS,IAAyBW,QAAQC,IAAI,SAACX,GAAcA,EAAQS,MAC7Eb,EAAIM,IAAI,MAAQ,IAAiCQ,QAAQC,IAAI,SAACX,GAAcA,EAAQD,EAAMU"}
\ No newline at end of file
diff --git a/vendor/mitt/index.d.ts b/vendor/mitt/index.d.ts
new file mode 100644
index 0000000000000..55346dd9769a8
--- /dev/null
+++ b/vendor/mitt/index.d.ts
@@ -0,0 +1,21 @@
+export declare type EventType = string | symbol;
+export declare type Handler = (event?: T) => void;
+export declare type WildcardHandler = (type: EventType, event?: any) => void;
+export declare type EventHandlerList = Array;
+export declare type WildCardEventHandlerList = Array;
+export declare type EventHandlerMap = Map;
+export interface Emitter {
+ all: EventHandlerMap;
+ on(type: EventType, handler: Handler): void;
+ on(type: '*', handler: WildcardHandler): void;
+ off(type: EventType, handler: Handler): void;
+ off(type: '*', handler: WildcardHandler): void;
+ emit(type: EventType, event?: T): void;
+ emit(type: '*', event?: any): void;
+}
+/**
+ * Mitt: Tiny (~200b) functional event emitter / pubsub.
+ * @name mitt
+ * @returns {Mitt}
+ */
+export default function mitt(all?: EventHandlerMap): Emitter;
diff --git a/vendor/mitt/package.json b/vendor/mitt/package.json
new file mode 100644
index 0000000000000..0105524a0d290
--- /dev/null
+++ b/vendor/mitt/package.json
@@ -0,0 +1,141 @@
+{
+ "_from": "mitt@latest",
+ "_id": "mitt@2.1.0",
+ "_inBundle": false,
+ "_integrity": "sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg==",
+ "_location": "/mitt",
+ "_phantomChildren": {},
+ "_requested": {
+ "type": "tag",
+ "registry": true,
+ "raw": "mitt@latest",
+ "name": "mitt",
+ "escapedName": "mitt",
+ "rawSpec": "latest",
+ "saveSpec": null,
+ "fetchSpec": "latest"
+ },
+ "_requiredBy": [
+ "#USER",
+ "/"
+ ],
+ "_resolved": "https://registry.npmjs.org/mitt/-/mitt-2.1.0.tgz",
+ "_shasum": "f740577c23176c6205b121b2973514eade1b2230",
+ "_spec": "mitt@latest",
+ "_where": "/Users/jacktfranklin/src/puppeteer",
+ "authors": [
+ "Jason Miller "
+ ],
+ "bugs": {
+ "url": "https://github.com/developit/mitt/issues"
+ },
+ "bundleDependencies": false,
+ "deprecated": false,
+ "description": "Tiny 200b functional Event Emitter / pubsub.",
+ "devDependencies": {
+ "@types/chai": "^4.2.11",
+ "@types/mocha": "^7.0.2",
+ "@types/sinon": "^9.0.4",
+ "@types/sinon-chai": "^3.2.4",
+ "@typescript-eslint/eslint-plugin": "^3.0.1",
+ "@typescript-eslint/parser": "^3.0.1",
+ "chai": "^4.2.0",
+ "documentation": "^13.0.0",
+ "eslint": "^7.1.0",
+ "eslint-config-developit": "^1.2.0",
+ "esm": "^3.2.25",
+ "microbundle": "^0.12.3",
+ "mocha": "^8.0.1",
+ "npm-run-all": "^4.1.5",
+ "rimraf": "^3.0.2",
+ "sinon": "^9.0.2",
+ "sinon-chai": "^3.5.0",
+ "ts-node": "^8.10.2",
+ "typescript": "^3.9.3"
+ },
+ "eslintConfig": {
+ "extends": [
+ "developit",
+ "plugin:@typescript-eslint/eslint-recommended",
+ "plugin:@typescript-eslint/recommended"
+ ],
+ "parser": "@typescript-eslint/parser",
+ "parserOptions": {
+ "sourceType": "module"
+ },
+ "env": {
+ "browser": true,
+ "mocha": true,
+ "jest": false,
+ "es6": true
+ },
+ "globals": {
+ "expect": true
+ },
+ "rules": {
+ "semi": [
+ 2,
+ "always"
+ ],
+ "jest/valid-expect": 0,
+ "@typescript-eslint/no-explicit-any": 0,
+ "@typescript-eslint/explicit-function-return-type": 0,
+ "@typescript-eslint/explicit-module-boundary-types": 0,
+ "@typescript-eslint/no-empty-function": 0
+ }
+ },
+ "eslintIgnore": [
+ "dist",
+ "index.d.ts"
+ ],
+ "esmodules": "dist/mitt.modern.js",
+ "files": [
+ "src",
+ "dist",
+ "index.d.ts"
+ ],
+ "homepage": "https://github.com/developit/mitt",
+ "jsnext:main": "dist/mitt.es.js",
+ "keywords": [
+ "events",
+ "eventemitter",
+ "emitter",
+ "pubsub"
+ ],
+ "license": "MIT",
+ "main": "dist/mitt.js",
+ "mocha": {
+ "extension": [
+ "ts"
+ ],
+ "require": [
+ "ts-node/register",
+ "esm"
+ ],
+ "spec": [
+ "test/*_test.ts"
+ ]
+ },
+ "module": "dist/mitt.es.js",
+ "name": "mitt",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/developit/mitt.git"
+ },
+ "scripts": {
+ "build": "npm-run-all --silent clean -p bundle -s docs",
+ "bundle": "microbundle",
+ "clean": "rimraf dist",
+ "docs": "documentation readme src/index.ts --section API -q --parse-extension ts",
+ "lint": "eslint src test --ext ts --ext js",
+ "mocha": "mocha test",
+ "release": "npm run -s build -s && npm t && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish",
+ "test": "npm-run-all --silent typecheck lint mocha test-types",
+ "test-types": "tsc test/test-types-compilation.ts --noEmit",
+ "typecheck": "tsc --noEmit"
+ },
+ "source": "src/index.ts",
+ "typings": "index.d.ts",
+ "umd:main": "dist/mitt.umd.js",
+ "version": "2.1.0"
+}
diff --git a/vendor/mitt/src/index.ts b/vendor/mitt/src/index.ts
new file mode 100644
index 0000000000000..7b5342f09e1e4
--- /dev/null
+++ b/vendor/mitt/src/index.ts
@@ -0,0 +1,92 @@
+
+/**
+ * @public
+ */
+export type EventType = string | symbol;
+
+// An event handler can take an optional event argument
+// and should not return a value
+/**
+ * @public
+ */
+export type Handler = (event?: T) => void;
+export type WildcardHandler = (type: EventType, event?: any) => void;
+
+// An array of all currently registered event handlers for a type
+export type EventHandlerList = Array;
+export type WildCardEventHandlerList = Array;
+
+// A map of event types and their corresponding event handlers.
+export type EventHandlerMap = Map;
+
+export interface Emitter {
+ all: EventHandlerMap;
+
+ on(type: EventType, handler: Handler): void;
+ on(type: '*', handler: WildcardHandler): void;
+
+ off(type: EventType, handler: Handler): void;
+ off(type: '*', handler: WildcardHandler): void;
+
+ emit(type: EventType, event?: T): void;
+ emit(type: '*', event?: any): void;
+}
+
+/**
+ * Mitt: Tiny (~200b) functional event emitter / pubsub.
+ * @name mitt
+ * @returns {Mitt}
+ */
+export default function mitt(all?: EventHandlerMap): Emitter {
+ all = all || new Map();
+
+ return {
+
+ /**
+ * A Map of event names to registered handler functions.
+ */
+ all,
+
+ /**
+ * Register an event handler for the given type.
+ * @param {string|symbol} type Type of event to listen for, or `"*"` for all events
+ * @param {Function} handler Function to call in response to given event
+ * @memberOf mitt
+ */
+ on(type: EventType, handler: Handler) {
+ const handlers = all.get(type);
+ const added = handlers && handlers.push(handler);
+ if (!added) {
+ all.set(type, [handler]);
+ }
+ },
+
+ /**
+ * Remove an event handler for the given type.
+ * @param {string|symbol} type Type of event to unregister `handler` from, or `"*"`
+ * @param {Function} handler Handler function to remove
+ * @memberOf mitt
+ */
+ off(type: EventType, handler: Handler) {
+ const handlers = all.get(type);
+ if (handlers) {
+ handlers.splice(handlers.indexOf(handler) >>> 0, 1);
+ }
+ },
+
+ /**
+ * Invoke all handlers for the given type.
+ * If present, `"*"` handlers are invoked after type-matched handlers.
+ *
+ * Note: Manually firing "*" handlers is not supported.
+ *
+ * @param {string|symbol} type The event type to invoke
+ * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler
+ * @memberOf mitt
+ */
+ emit(type: EventType, evt: T) {
+ ((all.get(type) || []) as EventHandlerList).slice().map((handler) => { handler(evt); });
+ ((all.get('*') || []) as WildCardEventHandlerList).slice().map((handler) => { handler(type, evt); });
+ }
+ };
+}
diff --git a/vendor/tsconfig.cjs.json b/vendor/tsconfig.cjs.json
new file mode 100644
index 0000000000000..0b74a00b15434
--- /dev/null
+++ b/vendor/tsconfig.cjs.json
@@ -0,0 +1,12 @@
+{
+ "extends": "../tsconfig.base.json",
+ "exclude": [
+ "mitt/dist"
+ ],
+ "compilerOptions": {
+ "composite": true,
+ "outDir": "../lib/cjs/vendor",
+ "module": "CommonJS",
+ "strict": false
+ }
+}
diff --git a/vendor/tsconfig.esm.json b/vendor/tsconfig.esm.json
new file mode 100644
index 0000000000000..8f3ff529589d1
--- /dev/null
+++ b/vendor/tsconfig.esm.json
@@ -0,0 +1,12 @@
+{
+ "extends": "../tsconfig.base.json",
+ "exclude": [
+ "mitt/dist"
+ ],
+ "compilerOptions": {
+ "composite": true,
+ "outDir": "../lib/esm/vendor",
+ "module": "esnext",
+ "strict": false
+ }
+}
diff --git a/versions.js b/versions.js
new file mode 100644
index 0000000000000..3e7c11efc6af8
--- /dev/null
+++ b/versions.js
@@ -0,0 +1,62 @@
+/**
+ * Copyright 2020 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const versionsPerRelease = new Map([
+ // This is a mapping from Chromium version => Puppeteer version.
+ // In Chromium roll patches, use 'NEXT' for the Puppeteer version.
+ ['103.0.5059.0', 'v14.2.0'],
+ ['102.0.5002.0', 'v14.0.0'],
+ ['101.0.4950.0', 'v13.6.0'],
+ ['100.0.4889.0', 'v13.5.0'],
+ ['99.0.4844.16', 'v13.2.0'],
+ ['98.0.4758.0', 'v13.1.0'],
+ ['97.0.4692.0', 'v12.0.0'],
+ ['93.0.4577.0', 'v10.2.0'],
+ ['92.0.4512.0', 'v10.0.0'],
+ ['91.0.4469.0', 'v9.0.0'],
+ ['90.0.4427.0', 'v8.0.0'],
+ ['90.0.4403.0', 'v7.0.0'],
+ ['89.0.4389.0', 'v6.0.0'],
+ ['88.0.4298.0', 'v5.5.0'],
+ ['87.0.4272.0', 'v5.4.0'],
+ ['86.0.4240.0', 'v5.3.0'],
+ ['85.0.4182.0', 'v5.2.1'],
+ ['84.0.4147.0', 'v5.1.0'],
+ ['83.0.4103.0', 'v3.1.0'],
+ ['81.0.4044.0', 'v3.0.0'],
+ ['80.0.3987.0', 'v2.1.0'],
+ ['79.0.3942.0', 'v2.0.0'],
+ ['78.0.3882.0', 'v1.20.0'],
+ ['77.0.3803.0', 'v1.19.0'],
+ ['76.0.3803.0', 'v1.17.0'],
+ ['75.0.3765.0', 'v1.15.0'],
+ ['74.0.3723.0', 'v1.13.0'],
+ ['73.0.3679.0', 'v1.12.2'],
+]);
+
+// The same major version as the current Chrome Stable per https://chromestatus.com/roadmap.
+const lastMaintainedChromiumVersion = '101.0.4950.0';
+
+if (!versionsPerRelease.has(lastMaintainedChromiumVersion)) {
+ throw new Error(
+ 'lastMaintainedChromiumVersion is missing from versionsPerRelease'
+ );
+}
+
+module.exports = {
+ versionsPerRelease,
+ lastMaintainedChromiumVersion,
+};
diff --git a/web-test-runner.config.js b/web-test-runner.config.js
new file mode 100644
index 0000000000000..39a9b0167466e
--- /dev/null
+++ b/web-test-runner.config.js
@@ -0,0 +1,44 @@
+/**
+ * Copyright 2020 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+const { chromeLauncher } = require('@web/test-runner-chrome');
+
+module.exports = {
+ files: ['test-browser/**/*.spec.js'],
+ browserStartTimeout: 60 * 1000,
+ browsers: [
+ chromeLauncher({
+ async createPage({ browser }) {
+ const page = await browser.newPage();
+ page.evaluateOnNewDocument((wsEndpoint) => {
+ window.__ENV__ = { wsEndpoint };
+ }, browser.wsEndpoint());
+
+ return page;
+ },
+ }),
+ ],
+ plugins: [
+ {
+ // turn expect UMD into an es module
+ name: 'esmify-expect',
+ transform(context) {
+ if (context.path === '/node_modules/expect/build-es5/index.js') {
+ return `const module = {}; const exports = {};\n${context.body};\n export default module.exports;`;
+ }
+ },
+ },
+ ],
+};
diff --git a/website/.gitignore b/website/.gitignore
new file mode 100644
index 0000000000000..b2d6de30624f6
--- /dev/null
+++ b/website/.gitignore
@@ -0,0 +1,20 @@
+# Dependencies
+/node_modules
+
+# Production
+/build
+
+# Generated files
+.docusaurus
+.cache-loader
+
+# Misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
diff --git a/website/README.md b/website/README.md
new file mode 100644
index 0000000000000..1c9cdcb1ff6db
--- /dev/null
+++ b/website/README.md
@@ -0,0 +1,31 @@
+# Website
+
+This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator.
+
+Its dependencies are purposefully kept separate from the main Puppeteer codebase's in order to avoid having all our end users install them when installing Puppeteer. In the future we may move this website into its own repository.
+
+## Installation
+
+```console
+npm install
+```
+
+## Local Development
+
+```console
+npm start
+```
+
+This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
+
+## Build
+
+```console
+npm build
+```
+
+This command generates static content into the `build` directory and can be served using any static contents hosting service.
+
+## Deployment
+
+Deploys are automatically handled by the `deploy-docs.yml` workflow.
diff --git a/website/babel.config.js b/website/babel.config.js
new file mode 100644
index 0000000000000..cea7e04a43c80
--- /dev/null
+++ b/website/babel.config.js
@@ -0,0 +1,18 @@
+/**
+ * Copyright 2021 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+module.exports = {
+ presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
+};
diff --git a/website/blog/CONTRIBUTING.md b/website/blog/CONTRIBUTING.md
new file mode 100644
index 0000000000000..ba0b0936e65e8
--- /dev/null
+++ b/website/blog/CONTRIBUTING.md
@@ -0,0 +1,3 @@
+
+
+Head to GitHub to view our CONTRIBUTING.md document
diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js
new file mode 100644
index 0000000000000..94cdc36a47897
--- /dev/null
+++ b/website/docusaurus.config.js
@@ -0,0 +1,95 @@
+/**
+ * Copyright 2021 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+const lightCodeTheme = require('prism-react-renderer/themes/github');
+const darkCodeTheme = require('prism-react-renderer/themes/dracula');
+
+/** @type {import('@docusaurus/types').DocusaurusConfig} */
+module.exports = {
+ title: 'Puppeteer documentation',
+ tagline: 'Note: this documentation is WIP. Please use https://pptr.dev.',
+ url: 'https://puppeteer.github.io/',
+ baseUrl: '/puppeteer/',
+ onBrokenLinks: 'warn',
+ onBrokenMarkdownLinks: 'ignore',
+ favicon: 'img/favicon.ico',
+ organizationName: 'puppeteer', // Usually your GitHub org/user name.
+ projectName: 'puppeteer', // Usually your repo name.
+ themeConfig: {
+ hideableSidebar: true,
+ navbar: {
+ style: "primary",
+ title: 'Puppeteer',
+ logo: {
+ alt: 'My Site Logo',
+ src: 'img/logo.svg',
+ },
+ hideOnScroll: true,
+ items: [
+ {
+ to: 'docs/puppeteer.puppeteer',
+ label: 'APIs',
+ position: 'left',
+ },
+ {
+ to: 'blog/contributing',
+ label: 'Contribute',
+ position: 'left',
+ },
+ {
+ type: 'docsVersionDropdown',
+ },
+ {
+ label: 'Github',
+ href: 'https://github.com/puppeteer/puppeteer',
+ position: 'right',
+ },
+ {
+ label: 'Stack',
+ href: 'https://stackoverflow.com/questions/tagged/puppeteer',
+ position: 'right'
+ },
+ {
+ label: 'Version 1.0',
+ href: 'https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md',
+ position: 'right'
+ }
+ ],
+ },
+ footer: {
+ style: 'dark',
+ links: [],
+ },
+ prism: {
+ theme: lightCodeTheme,
+ darkTheme: darkCodeTheme,
+ },
+ },
+ presets: [
+ [
+ '@docusaurus/preset-classic',
+ {
+ docs: {
+ sidebarPath: require.resolve('./sidebars.js'),
+ // Please change this to your repo.
+ editUrl: 'https://github.com/facebook/puppeteer/edit/main/website/',
+ },
+ theme: {
+ customCss: require.resolve("./src/css/custom.css"),
+ },
+ },
+ ],
+ ],
+};
diff --git a/website/package.json b/website/package.json
new file mode 100644
index 0000000000000..e1e0542f3b89a
--- /dev/null
+++ b/website/package.json
@@ -0,0 +1,39 @@
+{
+ "name": "website",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "docusaurus": "docusaurus",
+ "start": "docusaurus start",
+ "build": "docusaurus build --out-dir=../docs-dist/",
+ "swizzle": "docusaurus swizzle",
+ "clear": "docusaurus clear",
+ "serve": "docusaurus serve --out-dir=../docs-dist/",
+ "write-translations": "docusaurus write-translations",
+ "write-heading-ids": "docusaurus write-heading-ids"
+ },
+ "dependencies": {
+ "@docusaurus/core": "^2.0.0-beta.6",
+ "@docusaurus/preset-classic": "^2.0.0-beta.6",
+ "@mdx-js/react": "^1.6.21",
+ "@svgr/webpack": "^5.5.0",
+ "clsx": "^1.1.1",
+ "file-loader": "^6.2.0",
+ "prism-react-renderer": "^1.2.1",
+ "react": "^17.0.1",
+ "react-dom": "^17.0.1",
+ "url-loader": "^4.1.1"
+ },
+ "browserslist": {
+ "production": [
+ ">0.5%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ }
+}
diff --git a/website/sidebars.js b/website/sidebars.js
new file mode 100644
index 0000000000000..ee061505d3007
--- /dev/null
+++ b/website/sidebars.js
@@ -0,0 +1,1714 @@
+module.exports = {
+ docs: {
+ Puppeteer: [
+ {
+ type: 'doc',
+ id: 'puppeteer.puppeteer',
+ label: 'Puppeteer',
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.puppeteer.clearcustomqueryhandlers',
+ label: 'clearcustomqueryhandlers',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.puppeteer.connect',
+ label: 'connect',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.puppeteer.customqueryhandlernames',
+ label: 'customqueryhandlersnames',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.puppeteer.devices',
+ label: 'devices',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.puppeteer.errors',
+ label: 'errors',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.puppeteer.networkconditions',
+ label: 'networkconditions',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.puppeteer.registercustomqueryhandler',
+ label: 'registercustomqueryhandler',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.puppeteer.unregistercustomqueryhandler',
+ label: 'unregistercustomqueryhandler',
+ },
+ ]
+ }
+ ],
+ "BrowserFetcher": [
+ {
+ type: 'doc',
+ id: 'puppeteer.browserfetcher',
+ label: 'BrowserFetcher',
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.browserfetcher.candownload',
+ label: 'candownload',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browserfetcher.download',
+ label: 'download',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browserfetcher.host',
+ label: 'host',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browserfetcher.localrevisions',
+ label: 'localrevisions',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browserfetcher.platform',
+ label: 'platform',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browserfetcher.product',
+ label: 'product',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browserfetcher.remove',
+ label: 'remove',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browserfetcher.revisioninfo',
+ label: 'revisioninfo',
+ },
+ ]
+ },
+ ],
+ "Browser": [
+ {
+ type: 'doc',
+ id: 'puppeteer.browser',
+ label: 'Browser',
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.browser.browsercontexts',
+ label: 'browsercontexts',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browser.close',
+ label: 'close',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browser.createincognitobrowsercontext',
+ label: 'createincognitobrowsercontext',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browser.defaultbrowsercontext',
+ label: 'defaultbrowsercontext',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browser.disconnect',
+ label: 'disconnect',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browser.isconnected',
+ label: 'isconnected',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browser.newpage',
+ label: 'newpage',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browser.pages',
+ label: 'pages',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browser.process',
+ label: 'process',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browser.target',
+ label: 'target',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browser.useragent',
+ label: 'useragent',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browser.waitfortarget',
+ label: 'waitfortarget',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browser.wsendpoint',
+ label: 'wsendpoint',
+ },
+ ]
+ },
+ ],
+ "BrowserContext": [
+ {
+ type: 'doc',
+ id: 'puppeteer.browsercontext',
+ label: 'BrowserContext',
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.browsercontext.browser',
+ label: 'browser',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browsercontext.overridepermissions',
+ label: 'overridepermissions',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browsercontext.close',
+ label: 'close',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browsercontext.isincognito',
+ label: 'isincognito',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browsercontext.newpage',
+ label: 'newpage',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browsercontext.overridepermissions',
+ label: 'overridepermissions',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browsercontext.pages',
+ label: 'pages',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browsercontext.targets',
+ label: 'targets',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.browsercontext.waitfortarget',
+ label: 'waitfortarget',
+ },
+ ]
+ },
+ ],
+ "Page": [
+ {
+ type: 'doc',
+ id: 'puppeteer.page',
+ label: 'Page',
+ },
+ {
+ Namespaces: [
+ {
+ type: 'doc',
+ id: 'puppeteer.page.accessibility',
+ label: 'accessibility',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.coverage',
+ label: 'coverage',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.isdraginterceptionenabled',
+ label: 'isDragInterceptionEnabled',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.keyboard',
+ label: 'keyboard',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.mouse',
+ label: 'mouse',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.touchscreen',
+ label: 'touchScreen',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.tracing',
+ label: 'tracing',
+ },
+ ]
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.page._',
+ label: '$',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.__',
+ label: '$$',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.__eval',
+ label: '$$eval',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page._eval',
+ label: '$eval',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page._x',
+ label: '$x',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.addscripttag',
+ label: 'addScriptTag',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.addstyletag',
+ label: 'addStyleTag',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.authenticate',
+ label: 'authenticate',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.bringtofront',
+ label: 'bringToFront',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.browser',
+ label: 'browser',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.browsercontext',
+ label: 'browserContext',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.click',
+ label: 'click',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.close',
+ label: 'close',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.content',
+ label: 'content()',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.cookies',
+ label: 'cookies',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.createpdfstream',
+ label: 'createPDFStream',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.deletecookie',
+ label: 'deleteCookie',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.emulate',
+ label: 'emulate',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.emulatecputhrottling',
+ label: 'emulateCPUThrottling',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.emulateidlestate',
+ label: 'emulateIdleState',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.emulatemediafeatures',
+ label: 'emulateMediaFeatures',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.emulatenetworkconditions',
+ label: 'emulateNetworkConditions',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.emulatetimezone',
+ label: 'emulateTimeZone',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.emulatevisiondeficiency',
+ label: 'emulateVisionDefinciency',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.evaluate',
+ label: 'evaluate',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.evaluatehandle',
+ label: 'evaluateHandle',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.evaluateonnewdocument',
+ label: 'evaluateOnNewDocument',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.exposefunction',
+ label: 'exposeFunction',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.focus',
+ label: 'focus',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.frames',
+ label: 'frames',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.goback',
+ label: 'goBack',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.goforward',
+ label: 'goForward',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.goto',
+ label: 'goTo',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.hover',
+ label: 'hover',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.isclosed',
+ label: 'isClosed',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.isjavascriptenabled',
+ label: 'isJavaScriptEnbled',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.mainframe',
+ label: 'mainFrame',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.metrics',
+ label: 'metrics',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.once',
+ label: 'once',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.pdf',
+ label: 'PDF',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.queryobjects',
+ label: 'queryObjects',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.reload',
+ label: 'reload',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.screenshot',
+ label: 'screenshot',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.select',
+ label: 'select',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.setbypasscsp',
+ label: 'setByPassCSP',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.setcacheenabled',
+ label: 'setCacheEnaled',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.setcontent',
+ label: 'setContent',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.setcookie',
+ label: 'setCookie',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.setdefaultnavigationtimeout',
+ label: 'setDefaultNavigationTimeOut',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.setdefaulttimeout',
+ label: 'setDefaultTimeOut',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.setdraginterception',
+ label: 'setDragInterception',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.setextrahttpheaders',
+ label: 'setExtraHTTPHeader',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.setgeolocation',
+ label: 'setGeoLocation',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.setjavascriptenabled',
+ label: 'setJavaScriptEnabled',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.setofflinemode',
+ label: 'setOfflineMode',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.setrequestinterception',
+ label: 'setRequestInterception',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.setuseragent',
+ label: 'setUserAgent',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.setviewport',
+ label: 'setViewPort',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.tap',
+ label: 'tap',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.target',
+ label: 'target',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.title',
+ label: 'title',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.type',
+ label: 'type',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.url',
+ label: 'url',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.viewport',
+ label: 'viewport',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.waitfor',
+ label: 'waitFor',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.waitforfilechooser',
+ label: 'waitForFileChooser',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.waitforfunction',
+ label: 'waitForFunction',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.waitfornavigation',
+ label: 'waitForNavigation',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.waitforrequest',
+ label: 'waitForRequest',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.waitforresponse',
+ label: 'waitForResponse',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.waitforselector',
+ label: 'waitForSelector',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.waitfortimeout',
+ label: 'waittimeout',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.waitforxpath',
+ label: 'waitForXPath',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.page.workers',
+ label: 'workers',
+ },
+ ]
+ },
+ ],
+ "WebWorker": [
+ {
+ type: 'doc',
+ id: 'puppeteer.webworker',
+ label: 'WebWorker',
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.webworker.evaluate',
+ label: 'evaluate',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.webworker.evaluatehandle',
+ label: 'evaluatehandle',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.webworker.executioncontext',
+ label: 'executioncontext',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.webworker.url',
+ label: 'url',
+ },
+ ]
+ },
+ ],
+ "Accessibility": [
+ {
+ type: 'doc',
+ id: 'puppeteer.accessibility',
+ label: 'Accessibility',
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.accessibility.snapshot',
+ label: 'snapshot',
+ },
+ ]
+ },
+ ],
+ "Keyboard": [
+ {
+ type: 'doc',
+ id: 'puppeteer.keyboard',
+ label: 'keyboard',
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.keyboard.down',
+ label: 'down',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.keyboard.press',
+ label: 'press',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.keyboard.sendcharacter',
+ label: 'sendCharacter',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.keyboard.type',
+ label: 'type',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.keyboard.up',
+ label: 'up',
+ },
+ ]
+ },
+ ],
+ "Mouse": [
+ {
+ type: 'doc',
+ id: 'puppeteer.mouse',
+ label: 'mouse',
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.mouse.click',
+ label: 'click',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.mouse.down',
+ label: 'down',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.mouse.drag',
+ label: 'drag',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.mouse.draganddrop',
+ label: 'dragAndDrop',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.mouse.dragenter',
+ label: 'dragEnter',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.mouse.dragover',
+ label: 'dragOver',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.mouse.drop',
+ label: 'drop',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.mouse.move',
+ label: 'move',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.mouse.up',
+ label: 'up',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.mouse.wheel',
+ label: 'wheel',
+ },
+ ]
+ },
+ ],
+ "TouchScreen": [
+ {
+ type: 'doc',
+ id: 'puppeteer.touchscreen',
+ label: 'touchScreen',
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.touchscreen.tap',
+ label: 'tap'
+ }
+ ]
+ },
+ ],
+ "Tracing": [
+ {
+ type: 'doc',
+ id: 'puppeteer.tracing',
+ label: 'Tracing',
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.tracing._client',
+ label: 'client',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.tracing._path',
+ label: 'path',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.tracing._recording',
+ label: 'recording',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.tracing.start',
+ label: 'start',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.tracing.stop',
+ label: 'stop',
+ },
+ ]
+ },
+ ],
+ "Dialog": [
+ {
+ type: 'doc',
+ id: 'puppeteer.dialog',
+ label: 'dialog',
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.dialog.accept',
+ label: 'accept',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.dialog.defaultvalue',
+ label: 'defaultValue',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.dialog.dismiss',
+ label: 'dismiss',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.dialog.message',
+ label: 'message',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.dialog.type',
+ label: 'type',
+ },
+ ]
+ },
+ ],
+ "ConsoleMessage": [
+ {
+ type: 'doc',
+ id: 'puppeteer.consolemessage',
+ label: 'consoleMessage',
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.consolemessage.args',
+ label: 'args',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.consolemessage.location',
+ label: 'location',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.consolemessage.stacktrace',
+ label: 'stackTrace',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.consolemessage.text',
+ label: 'text',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.consolemessage.type',
+ label: 'type',
+ },
+ ]
+ },
+ ],
+ "Frame": [
+ {
+ type: 'doc',
+ id: 'puppeteer.frame',
+ label: 'frame',
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.frame._',
+ label: '$',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.__',
+ label: '$$',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.__eval',
+ label: '$$eval',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame._eval',
+ label: '$eval',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame._x',
+ label: '$x',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.addscripttag',
+ label: 'addScriptTag',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.addstyletag',
+ label: 'addStyleTag',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.childframes',
+ label: 'childFrames',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.click',
+ label: 'click',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.content',
+ label: 'content',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.evaluate',
+ label: 'evaluate',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.evaluatehandle',
+ label: 'evaluateHandle',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.executioncontext',
+ label: 'executionContext',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.focus',
+ label: 'focus',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.goto',
+ label: 'goTo',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.hover',
+ label: 'hover',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.isdetached',
+ label: 'isDetached',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.name',
+ label: 'name',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.parentframe',
+ label: 'parentFrame',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.select',
+ label: 'select',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.setcontent',
+ label: 'setContent',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.tap',
+ label: 'tap',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.title',
+ label: 'title',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.type',
+ label: 'type',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.url',
+ label: 'url',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.waitfor',
+ label: 'waitFor',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.waitforfunction',
+ label: 'waitForFunction',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.waitfornavigation',
+ label: 'waitForNavigation',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.waitforselector',
+ label: 'waitForSelector',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.waitfortimeout',
+ label: 'waitForTimeOut',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.frame.waitforxpath',
+ label: 'waitForXPath',
+ },
+ ]
+ }
+ ],
+ "FileChooser": [
+ {
+ type: 'doc',
+ id: 'puppeteer.filechooser',
+ label: 'FileChooser',
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.filechooser.accept',
+ label: 'accept',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.filechooser.cancel',
+ label: 'cancel',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.filechooser.ismultiple',
+ label: 'ismultiple',
+ },
+ ]
+ },
+ ],
+ "ExecutionContext": [
+ {
+ type: 'doc',
+ id: 'puppeteer.executioncontext',
+ label: 'executionContext',
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.executioncontext.evaluate',
+ label: 'evaluate',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.executioncontext.evaluatehandle',
+ label: 'evaluateHandle',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.executioncontext.frame',
+ label: 'frame',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.executioncontext.queryobjects',
+ label: 'queryobjects',
+ },
+ ]
+ },
+ ],
+ "JSHandle": [
+ {
+ type: 'doc',
+ id: 'puppeteer.jshandle',
+ label: 'JSHandle',
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.jshandle.aselement',
+ label: 'asElement',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.jshandle.dispose',
+ label: 'dispose',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.jshandle.evaluate',
+ label: 'evaluate',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.jshandle.evaluatehandle',
+ label: 'evaluateHandle',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.jshandle.executioncontext',
+ label: 'executionContext',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.jshandle.getproperties',
+ label: 'getProperties',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.jshandle.getproperty',
+ label: 'getProperty',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.jshandle.jsonvalue',
+ label: 'JSONValue',
+ },
+ ]
+ },
+ ],
+ "ElementHandle": [
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle',
+ label: 'elementHandle',
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle._',
+ label: '$',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle.__',
+ label: '$$',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle.__eval',
+ label: '$$eval',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle._eval',
+ label: '$eval',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle._x',
+ label: '$x',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle.aselement',
+ label: 'asElement',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle.boundingbox',
+ label: 'boundingBox',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle.boxmodel',
+ label: 'boxModel',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle.click',
+ label: 'click',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle.contentframe',
+ label: 'contentFrame',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle.focus',
+ label: 'focus',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle.hover',
+ label: 'hover',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle.clickablepoint',
+ label: 'clickablePoint',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle.drag',
+ label: 'drag',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle.draganddrop',
+ label: 'dragAndDrop',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle.dragenter',
+ label: 'dragEnter',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle.dragover',
+ label: 'dragOver',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle.drop',
+ label: 'drop',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle.isintersectingviewport',
+ label: 'isIntersectingViewPort',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle.press',
+ label: 'press',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle.screenshot',
+ label: 'screenshot',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle.select',
+ label: 'select',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle.tap',
+ label: 'tap',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle.type',
+ label: 'type',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.elementhandle.uploadfile',
+ label: 'uploadFile',
+ },
+ ]
+ },
+ ],
+ "HTTPRequest": [
+ {
+ type: 'doc',
+ id: 'puppeteer.httprequest',
+ label: 'httpRequest',
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.httprequest.abort',
+ label: 'abort',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httprequest.aborterrorreason',
+ label: 'abortErrorReason',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httprequest.enqueueinterceptaction',
+ label: 'enqueueInterCeptaction',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httprequest.failure',
+ label: 'failure',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httprequest.finalizeinterceptions',
+ label: 'finalizeInterception',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httprequest.frame',
+ label: 'frame',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httprequest.headers',
+ label: 'headers',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httprequest.isnavigationrequest',
+ label: 'isNavigationRequest',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httprequest.method',
+ label: 'method',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httprequest.postdata',
+ label: 'postData',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httprequest.redirectchain',
+ label: 'redirectChain',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httprequest.resourcetype',
+ label: 'resourceType',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httprequest.respond',
+ label: 'respond',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httprequest.response',
+ label: 'response',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httprequest.responseforrequest',
+ label: 'responseForRequest',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httprequest.url',
+ label: 'hurl',
+ },
+ ]
+ },
+ ],
+ "HTTPRespose": [
+ {
+ type: 'doc',
+ id: 'puppeteer.httpresponse',
+ label: 'httpResponse',
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.httpresponse.buffer',
+ label: 'buffer',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httpresponse.frame',
+ label: 'frame',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httpresponse.fromcache',
+ label: 'fromCache',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httpresponse.fromserviceworker',
+ label: 'fromServiceWorker',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httpresponse.headers',
+ label: 'headers',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httpresponse.json',
+ label: 'JSON',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httpresponse.ok',
+ label: 'OK',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httpresponse.remoteaddress',
+ label: 'remoteAddress',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httpresponse.request',
+ label: 'request',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httpresponse.securitydetails',
+ label: 'securityDetails',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httpresponse.status',
+ label: 'status',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httpresponse.statustext',
+ label: 'statusText',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httpresponse.text',
+ label: 'text',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.httpresponse.url',
+ label: 'URL',
+ },
+ ]
+ },
+ ],
+ "SecurityDetails": [
+ {
+ type: 'doc',
+ id: 'puppeteer.securitydetails',
+ label: 'securityDetails',
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.securitydetails.issuer',
+ label: 'issuer',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.securitydetails.protocol',
+ label: 'protocol',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.securitydetails.subjectalternativenames',
+ label: 'subjectAlternativeNames',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.securitydetails.subjectname',
+ label: 'subjectName',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.securitydetails.validfrom',
+ label: 'validFrom',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.securitydetails.validto',
+ label: 'validTo',
+ },
+ ]
+ },
+ ],
+ "Target": [
+ {
+ type: 'doc',
+ id: 'puppeteer.target',
+ label: 'target',
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.target.browser',
+ label: 'browser',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.target.browsercontext',
+ label: 'browserContext',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.target.createcdpsession',
+ label: 'createCDPSSession',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.target.opener',
+ label: 'opener',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.target.page',
+ label: 'page',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.target.type',
+ label: 'type',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.target.url',
+ label: 'url',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.target.worker',
+ label: 'worker',
+ },
+ ]
+ },
+ ],
+ "CDPSession": [
+ {
+ type: 'doc',
+ id: 'puppeteer.cdpsession',
+ label: 'CDPSession',
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.cdpsession.connection',
+ label: 'connection',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.cdpsession.detach',
+ label: 'detach',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.cdpsession.send',
+ label: 'send',
+ },
+ ]
+ },
+ ],
+ "Coverage": [
+ {
+ type: 'doc',
+ id: 'puppeteer.coverage',
+ label: 'coverage',
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.coverage.startcsscoverage',
+ label: 'startCSSCoverage',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.coverage.startjscoverage',
+ label: 'startJSCoverage',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.coverage.stopcsscoverage',
+ label: 'stopCSSCoverage',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.coverage.stopjscoverage',
+ label: 'stopJSCoverage',
+ },
+ ]
+ },
+ ],
+ "TimeOutError": [
+ {
+ type: 'doc',
+ id: 'puppeteer.timeouterror',
+ label: 'timeOutError',
+ },
+ ],
+ "EventEmitter": [
+ {
+ type: 'doc',
+ id: 'puppeteer.eventemitter',
+ label: 'eventEmitter',
+ },
+ {
+ Methods: [
+ {
+ type: 'doc',
+ id: 'puppeteer.eventemitter.addlistener',
+ label: 'addListener',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.eventemitter.emit',
+ label: 'emit',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.eventemitter.listenercount',
+ label: 'listenerCount',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.eventemitter.off',
+ label: 'off',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.eventemitter.on',
+ label: 'on',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.eventemitter.once',
+ label: 'once',
+ },
+ {
+ type: 'doc',
+ id: 'puppeteer.eventemitter.removelistener',
+ label: 'removeListener',
+ },{
+ type: 'doc',
+ id: 'puppeteer.eventemitter.removealllisteners',
+ label: 'removeAllListener',
+ },
+ ]
+ },
+ ],
+ },
+};
diff --git a/website/src/css/custom.css b/website/src/css/custom.css
new file mode 100644
index 0000000000000..ae8906ba7ed8f
--- /dev/null
+++ b/website/src/css/custom.css
@@ -0,0 +1,45 @@
+/**
+ * Copyright 2021 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* stylelint-disable docusaurus/copyright-header */
+/**
+ * Any CSS included here will be global. The classic template
+ * bundles Infima by default. Infima is a CSS framework designed to
+ * work well for content-centric websites.
+ */
+
+/* You can override the default Infima variables here. */
+:root {
+ --ifm-color-primary: rgb(64 181 164);
+ --ifm-color-primary-dark: rgb(33, 175, 144);
+ --ifm-color-primary-darker: rgb(31, 165, 136);
+ --ifm-color-primary-darkest: rgb(26, 136, 112);
+ --ifm-color-primary-light: rgb(70, 203, 174);
+ --ifm-color-primary-lighter: rgb(102, 212, 189);
+ --ifm-color-primary-lightest: rgb(146, 224, 208);
+ --ifm-code-font-size: 95%;
+}
+
+.docusaurus-highlight-code-line {
+ background-color: rgba(0, 0, 0, 0.1);
+ display: block;
+ margin: 0 calc(-1 * var(--ifm-pre-padding));
+ padding: 0 var(--ifm-pre-padding);
+}
+
+html[data-theme='dark'] .docusaurus-highlight-code-line {
+ background-color: rgba(0, 0, 0, 0.3);
+}
diff --git a/website/src/pages/index.md b/website/src/pages/index.md
new file mode 100644
index 0000000000000..e3a2ad653ccbc
--- /dev/null
+++ b/website/src/pages/index.md
@@ -0,0 +1,462 @@
+# Puppeteer
+
+
+
+[![Build status](https://github.com/puppeteer/puppeteer/workflows/run-checks/badge.svg)](https://github.com/puppeteer/puppeteer/actions?query=workflow%3Arun-checks) [![npm puppeteer package](https://img.shields.io/npm/v/puppeteer.svg)](https://npmjs.org/package/puppeteer)
+
+
+
+
+
+###### [API](https://github.com/puppeteer/puppeteer/blob/v10.0.0/docs/api.md) | [FAQ](#faq) | [Contributing](https://github.com/puppeteer/puppeteer/blob/main/CONTRIBUTING.md) | [Troubleshooting](https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md)
+
+> Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the [DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/). Puppeteer runs [headless](https://developers.google.com/web/updates/2017/04/headless-chrome) by default, but can be configured to run full (non-headless) Chrome or Chromium.
+
+
+
+###### What can I do?
+
+Most things that you can do manually in the browser can be done using Puppeteer! Here are a few examples to get you started:
+
+- Generate screenshots and PDFs of pages.
+- Crawl a SPA (Single-Page Application) and generate pre-rendered content (i.e. "SSR" (Server-Side Rendering)).
+- Automate form submission, UI testing, keyboard input, etc.
+- Create an up-to-date, automated testing environment. Run your tests directly in the latest version of Chrome using the latest JavaScript and browser features.
+- Capture a [timeline trace](https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/reference) of your site to help diagnose performance issues.
+- Test Chrome Extensions.
+
+
+Give it a spin: https://try-puppeteer.appspot.com/
+
+
+
+## Getting Started
+
+### Installation
+
+To use Puppeteer in your project, run:
+
+```bash
+npm i puppeteer
+# or "yarn add puppeteer"
+```
+
+Note: When you install Puppeteer, it downloads a recent version of Chromium (~170MB Mac, ~282MB Linux, ~280MB Win) that is guaranteed to work with the API. To skip the download, or to download a different browser, see [Environment variables](https://github.com/puppeteer/puppeteer/blob/v10.0.0/docs/api.md#environment-variables).
+
+### puppeteer-core
+
+Since version 1.7.0 we publish the [`puppeteer-core`](https://www.npmjs.com/package/puppeteer-core) package,
+a version of Puppeteer that doesn't download any browser by default.
+
+```bash
+npm i puppeteer-core
+# or "yarn add puppeteer-core"
+```
+
+`puppeteer-core` is intended to be a lightweight version of Puppeteer for launching an existing browser installation or for connecting to a remote one. Be sure that the version of puppeteer-core you install is compatible with the
+browser you intend to connect to.
+
+See [puppeteer vs puppeteer-core](https://github.com/puppeteer/puppeteer/blob/main/docs/api.md#puppeteer-vs-puppeteer-core).
+
+### Usage
+
+Puppeteer follows the latest [maintenance LTS](https://github.com/nodejs/Release#release-schedule) version of Node.
+
+Note: Prior to v1.18.1, Puppeteer required at least Node v6.4.0. Versions from v1.18.1 to v2.1.0 rely on
+Node 8.9.0+. Starting from v3.0.0 Puppeteer starts to rely on Node 10.18.1+. All examples below use async/await which is only supported in Node v7.6.0 or greater.
+
+Puppeteer will be familiar to people using other browser testing frameworks. You create an instance
+of `Browser`, open pages, and then manipulate them with [Puppeteer's API](https://github.com/puppeteer/puppeteer/blob/v10.0.0/docs/api.md#).
+
+**Example** - navigating to https://example.com and saving a screenshot as _example.png_:
+
+Save file as **example.js**
+
+```js
+const puppeteer = require('puppeteer');
+
+(async () => {
+ const browser = await puppeteer.launch();
+ const page = await browser.newPage();
+ await page.goto('https://example.com');
+ await page.screenshot({ path: 'example.png' });
+
+ await browser.close();
+})();
+```
+
+Execute script on the command line
+
+```bash
+node example.js
+```
+
+Puppeteer sets an initial page size to 800×600px, which defines the screenshot size. The page size can be customized with [`Page.setViewport()`](https://github.com/puppeteer/puppeteer/blob/v10.0.0/docs/api.md#pagesetviewportviewport).
+
+**Example** - create a PDF.
+
+Save file as **hn.js**
+
+```js
+const puppeteer = require('puppeteer');
+
+(async () => {
+ const browser = await puppeteer.launch();
+ const page = await browser.newPage();
+ await page.goto('https://news.ycombinator.com', {
+ waitUntil: 'networkidle2',
+ });
+ await page.pdf({ path: 'hn.pdf', format: 'a4' });
+
+ await browser.close();
+})();
+```
+
+Execute script on the command line
+
+```bash
+node hn.js
+```
+
+See [`Page.pdf()`](https://github.com/puppeteer/puppeteer/blob/v10.0.0/docs/api.md#pagepdfoptions) for more information about creating pdfs.
+
+**Example** - evaluate script in the context of the page
+
+Save file as **get-dimensions.js**
+
+```js
+const puppeteer = require('puppeteer');
+
+(async () => {
+ const browser = await puppeteer.launch();
+ const page = await browser.newPage();
+ await page.goto('https://example.com');
+
+ // Get the "viewport" of the page, as reported by the page.
+ const dimensions = await page.evaluate(() => {
+ return {
+ width: document.documentElement.clientWidth,
+ height: document.documentElement.clientHeight,
+ deviceScaleFactor: window.devicePixelRatio,
+ };
+ });
+
+ console.log('Dimensions:', dimensions);
+
+ await browser.close();
+})();
+```
+
+Execute script on the command line
+
+```bash
+node get-dimensions.js
+```
+
+See [`Page.evaluate()`](https://github.com/puppeteer/puppeteer/blob/v10.0.0/docs/api.md#pageevaluatepagefunction-args) for more information on `evaluate` and related methods like `evaluateOnNewDocument` and `exposeFunction`.
+
+
+
+
+
+## Default runtime settings
+
+**1. Uses Headless mode**
+
+Puppeteer launches Chromium in [headless mode](https://developers.google.com/web/updates/2017/04/headless-chrome). To launch a full version of Chromium, set the [`headless` option](https://github.com/puppeteer/puppeteer/blob/v10.0.0/docs/api.md#puppeteerlaunchoptions) when launching a browser:
+
+```js
+const browser = await puppeteer.launch({ headless: false }); // default is true
+```
+
+**2. Runs a bundled version of Chromium**
+
+By default, Puppeteer downloads and uses a specific version of Chromium so its API
+is guaranteed to work out of the box. To use Puppeteer with a different version of Chrome or Chromium,
+pass in the executable's path when creating a `Browser` instance:
+
+```js
+const browser = await puppeteer.launch({ executablePath: '/path/to/Chrome' });
+```
+
+You can also use Puppeteer with Firefox Nightly (experimental support). See [`Puppeteer.launch()`](https://github.com/puppeteer/puppeteer/blob/v10.0.0/docs/api.md#puppeteerlaunchoptions) for more information.
+
+See [`this article`](https://www.howtogeek.com/202825/what%E2%80%99s-the-difference-between-chromium-and-chrome/) for a description of the differences between Chromium and Chrome. [`This article`](https://chromium.googlesource.com/chromium/src/+/master/docs/chromium_browser_vs_google_chrome.md) describes some differences for Linux users.
+
+**3. Creates a fresh user profile**
+
+Puppeteer creates its own browser user profile which it **cleans up on every run**.
+
+
+
+## Resources
+
+- [API Documentation](https://github.com/puppeteer/puppeteer/blob/v10.0.0/docs/api.md)
+- [Examples](https://github.com/puppeteer/puppeteer/tree/main/examples/)
+- [Community list of Puppeteer resources](https://github.com/transitive-bullshit/awesome-puppeteer)
+
+
+
+## Debugging tips
+
+1. Turn off headless mode - sometimes it's useful to see what the browser is
+ displaying. Instead of launching in headless mode, launch a full version of
+ the browser using `headless: false`:
+
+ ```js
+ const browser = await puppeteer.launch({ headless: false });
+ ```
+
+2. Slow it down - the `slowMo` option slows down Puppeteer operations by the
+ specified amount of milliseconds. It's another way to help see what's going on.
+
+ ```js
+ const browser = await puppeteer.launch({
+ headless: false,
+ slowMo: 250, // slow down by 250ms
+ });
+ ```
+
+3. Capture console output - You can listen for the `console` event.
+ This is also handy when debugging code in `page.evaluate()`:
+
+ ```js
+ page.on('console', (msg) => console.log('PAGE LOG:', msg.text()));
+
+ await page.evaluate(() => console.log(`url is ${location.href}`));
+ ```
+
+4. Use debugger in application code browser
+
+ There are two execution context: node.js that is running test code, and the browser
+ running application code being tested. This lets you debug code in the
+ application code browser; ie code inside `evaluate()`.
+
+ - Use `{devtools: true}` when launching Puppeteer:
+
+ ```js
+ const browser = await puppeteer.launch({ devtools: true });
+ ```
+
+ - Change default test timeout:
+
+ jest: `jest.setTimeout(100000);`
+
+ jasmine: `jasmine.DEFAULT_TIMEOUT_INTERVAL = 100000;`
+
+ mocha: `this.timeout(100000);` (don't forget to change test to use [function and not '=>'](https://stackoverflow.com/a/23492442))
+
+ - Add an evaluate statement with `debugger` inside / add `debugger` to an existing evaluate statement:
+
+ ```js
+ await page.evaluate(() => {
+ debugger;
+ });
+ ```
+
+ The test will now stop executing in the above evaluate statement, and chromium will stop in debug mode.
+
+5. Use debugger in node.js
+
+ This will let you debug test code. For example, you can step over `await page.click()` in the node.js script and see the click happen in the application code browser.
+
+ Note that you won't be able to run `await page.click()` in
+ DevTools console due to this [Chromium bug](https://bugs.chromium.org/p/chromium/issues/detail?id=833928). So if
+ you want to try something out, you have to add it to your test file.
+
+ - Add `debugger;` to your test, eg:
+
+ ```js
+ debugger;
+ await page.click('a[target=_blank]');
+ ```
+
+ - Set `headless` to `false`
+ - Run `node --inspect-brk`, eg `node --inspect-brk node_modules/.bin/jest tests`
+ - In Chrome open `chrome://inspect/#devices` and click `inspect`
+ - In the newly opened test browser, type `F8` to resume test execution
+ - Now your `debugger` will be hit and you can debug in the test browser
+
+6. Enable verbose logging - internal DevTools protocol traffic
+ will be logged via the [`debug`](https://github.com/visionmedia/debug) module under the `puppeteer` namespace.
+
+ # Basic verbose logging
+ env DEBUG="puppeteer:*" node script.js
+
+ # Protocol traffic can be rather noisy. This example filters out all Network domain messages
+ env DEBUG="puppeteer:*" env DEBUG_COLORS=true node script.js 2>&1 | grep -v '"Network'
+
+7. Debug your Puppeteer (node) code easily, using [ndb](https://github.com/GoogleChromeLabs/ndb)
+
+- `npm install -g ndb` (or even better, use [npx](https://github.com/zkat/npx)!)
+
+- add a `debugger` to your Puppeteer (node) code
+
+- add `ndb` (or `npx ndb`) before your test command. For example:
+
+ `ndb jest` or `ndb mocha` (or `npx ndb jest` / `npx ndb mocha`)
+
+- debug your test inside chromium like a boss!
+
+
+
+
+
+## Usage with TypeScript
+
+We have recently completed a migration to move the Puppeteer source code from JavaScript to TypeScript and as of version 7.0.1 we ship our own built-in type definitions.
+
+If you are on a version older than 7, we recommend installing the Puppeteer type definitions from the [DefinitelyTyped](https://definitelytyped.org/) repository:
+
+```bash
+npm install --save-dev @types/puppeteer
+```
+
+The types that you'll see appearing in the Puppeteer source code are based off the great work of those who have contributed to the `@types/puppeteer` package. We really appreciate the hard work those people put in to providing high quality TypeScript definitions for Puppeteer's users.
+
+
+
+## Contributing to Puppeteer
+
+Check out [contributing guide](https://github.com/puppeteer/puppeteer/blob/main/CONTRIBUTING.md) to get an overview of Puppeteer development.
+
+
+
+# FAQ
+
+#### Q: Who maintains Puppeteer?
+
+The Chrome DevTools team maintains the library, but we'd love your help and expertise on the project!
+See [Contributing](https://github.com/puppeteer/puppeteer/blob/main/CONTRIBUTING.md).
+
+#### Q: What is the status of cross-browser support?
+
+Official Firefox support is currently experimental. The ongoing collaboration with Mozilla aims to support common end-to-end testing use cases, for which developers expect cross-browser coverage. The Puppeteer team needs input from users to stabilize Firefox support and to bring missing APIs to our attention.
+
+From Puppeteer v2.1.0 onwards you can specify [`puppeteer.launch({product: 'firefox'})`](https://github.com/puppeteer/puppeteer/blob/v10.0.0/docs/api.md#puppeteerlaunchoptions) to run your Puppeteer scripts in Firefox Nightly, without any additional custom patches. While [an older experiment](https://www.npmjs.com/package/puppeteer-firefox) required a patched version of Firefox, [the current approach](https://wiki.mozilla.org/Remote) works with “stock” Firefox.
+
+We will continue to collaborate with other browser vendors to bring Puppeteer support to browsers such as Safari.
+This effort includes exploration of a standard for executing cross-browser commands (instead of relying on the non-standard DevTools Protocol used by Chrome).
+
+#### Q: What are Puppeteer’s goals and principles?
+
+The goals of the project are:
+
+- Provide a slim, canonical library that highlights the capabilities of the [DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/).
+- Provide a reference implementation for similar testing libraries. Eventually, these other frameworks could adopt Puppeteer as their foundational layer.
+- Grow the adoption of headless/automated browser testing.
+- Help dogfood new DevTools Protocol features...and catch bugs!
+- Learn more about the pain points of automated browser testing and help fill those gaps.
+
+We adapt [Chromium principles](https://www.chromium.org/developers/core-principles) to help us drive product decisions:
+
+- **Speed**: Puppeteer has almost zero performance overhead over an automated page.
+- **Security**: Puppeteer operates off-process with respect to Chromium, making it safe to automate potentially malicious pages.
+- **Stability**: Puppeteer should not be flaky and should not leak memory.
+- **Simplicity**: Puppeteer provides a high-level API that’s easy to use, understand, and debug.
+
+#### Q: Is Puppeteer replacing Selenium/WebDriver?
+
+**No**. Both projects are valuable for very different reasons:
+
+- Selenium/WebDriver focuses on cross-browser automation; its value proposition is a single standard API that works across all major browsers.
+- Puppeteer focuses on Chromium; its value proposition is richer functionality and higher reliability.
+
+That said, you **can** use Puppeteer to run tests against Chromium, e.g. using the community-driven [jest-puppeteer](https://github.com/smooth-code/jest-puppeteer). While this probably shouldn’t be your only testing solution, it does have a few good points compared to WebDriver:
+
+- Puppeteer requires zero setup and comes bundled with the Chromium version it works best with, making it [very easy to start with](https://github.com/puppeteer/puppeteer/#getting-started). At the end of the day, it’s better to have a few tests running chromium-only, than no tests at all.
+- Puppeteer has event-driven architecture, which removes a lot of potential flakiness. There’s no need for evil “sleep(1000)” calls in puppeteer scripts.
+- Puppeteer runs headless by default, which makes it fast to run. Puppeteer v1.5.0 also exposes browser contexts, making it possible to efficiently parallelize test execution.
+- Puppeteer shines when it comes to debugging: flip the “headless” bit to false, add “slowMo”, and you’ll see what the browser is doing. You can even open Chrome DevTools to inspect the test environment.
+
+#### Q: Why doesn’t Puppeteer v.XXX work with Chromium v.YYY?
+
+We see Puppeteer as an **indivisible entity** with Chromium. Each version of Puppeteer bundles a specific version of Chromium – **the only** version it is guaranteed to work with.
+
+This is not an artificial constraint: A lot of work on Puppeteer is actually taking place in the Chromium repository. Here’s a typical story:
+
+- A Puppeteer bug is reported: https://github.com/puppeteer/puppeteer/issues/2709
+- It turned out this is an issue with the DevTools protocol, so we’re fixing it in Chromium: https://chromium-review.googlesource.com/c/chromium/src/+/1102154
+- Once the upstream fix is landed, we roll updated Chromium into Puppeteer: https://github.com/puppeteer/puppeteer/pull/2769
+
+However, oftentimes it is desirable to use Puppeteer with the official Google Chrome rather than Chromium. For this to work, you should install a `puppeteer-core` version that corresponds to the Chrome version.
+
+For example, in order to drive Chrome 71 with puppeteer-core, use `chrome-71` npm tag:
+
+```bash
+npm install puppeteer-core@chrome-71
+```
+
+#### Q: Which Chromium version does Puppeteer use?
+
+Look for the `chromium` entry in [revisions.ts](https://github.com/puppeteer/puppeteer/blob/main/src/revisions.ts). To find the corresponding Chromium commit and version number, search for the revision prefixed by an `r` in [OmahaProxy](https://omahaproxy.appspot.com/)'s "Find Releases" section.
+
+#### Q: Which Firefox version does Puppeteer use?
+
+Since Firefox support is experimental, Puppeteer downloads the latest [Firefox Nightly](https://wiki.mozilla.org/Nightly) when the `PUPPETEER_PRODUCT` environment variable is set to `firefox`. That's also why the value of `firefox` in [revisions.ts](https://github.com/puppeteer/puppeteer/blob/main/src/revisions.ts) is `latest` -- Puppeteer isn't tied to a particular Firefox version.
+
+To fetch Firefox Nightly as part of Puppeteer installation:
+
+```bash
+PUPPETEER_PRODUCT=firefox npm i puppeteer
+# or "yarn add puppeteer"
+```
+
+#### Q: What’s considered a “Navigation”?
+
+From Puppeteer’s standpoint, **“navigation” is anything that changes a page’s URL**.
+Aside from regular navigation where the browser hits the network to fetch a new document from the web server, this includes [anchor navigations](https://www.w3.org/TR/html5/single-page.html#scroll-to-fragid) and [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API) usage.
+
+With this definition of “navigation,” **Puppeteer works seamlessly with single-page applications.**
+
+#### Q: What’s the difference between a “trusted" and "untrusted" input event?
+
+In browsers, input events could be divided into two big groups: trusted vs. untrusted.
+
+- **Trusted events**: events generated by users interacting with the page, e.g. using a mouse or keyboard.
+- **Untrusted event**: events generated by Web APIs, e.g. `document.createEvent` or `element.click()` methods.
+
+Websites can distinguish between these two groups:
+
+- using an [`Event.isTrusted`](https://developer.mozilla.org/en-US/docs/Web/API/Event/isTrusted) event flag
+- sniffing for accompanying events. For example, every trusted `'click'` event is preceded by `'mousedown'` and `'mouseup'` events.
+
+For automation purposes it’s important to generate trusted events. **All input events generated with Puppeteer are trusted and fire proper accompanying events.** If, for some reason, one needs an untrusted event, it’s always possible to hop into a page context with `page.evaluate` and generate a fake event:
+
+```js
+await page.evaluate(() => {
+ document.querySelector('button[type=submit]').click();
+});
+```
+
+#### Q: What features does Puppeteer not support?
+
+You may find that Puppeteer does not behave as expected when controlling pages that incorporate audio and video. (For example, [video playback/screenshots is likely to fail](https://github.com/puppeteer/puppeteer/issues/291).) There are two reasons for this:
+
+- Puppeteer is bundled with Chromium — not Chrome — and so by default, it inherits all of [Chromium's media-related limitations](https://www.chromium.org/audio-video). This means that Puppeteer does not support licensed formats such as AAC or H.264. (However, it is possible to force Puppeteer to use a separately-installed version Chrome instead of Chromium via the [`executablePath` option to `puppeteer.launch`](https://github.com/puppeteer/puppeteer/blob/v10.0.0/docs/api.md#puppeteerlaunchoptions). You should only use this configuration if you need an official release of Chrome that supports these media formats.)
+- Since Puppeteer (in all configurations) controls a desktop version of Chromium/Chrome, features that are only supported by the mobile version of Chrome are not supported. This means that Puppeteer [does not support HTTP Live Streaming (HLS)](https://caniuse.com/#feat=http-live-streaming).
+
+#### Q: I am having trouble installing / running Puppeteer in my test environment. Where should I look for help?
+
+We have a [troubleshooting](https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md) guide for various operating systems that lists the required dependencies.
+
+#### Q: How do I try/test a prerelease version of Puppeteer?
+
+You can check out this repo or install the latest prerelease from npm:
+
+```bash
+npm i --save puppeteer@next
+```
+
+Please note that prerelease may be unstable and contain bugs.
+
+#### Q: I have more questions! Where do I ask?
+
+There are many ways to get help on Puppeteer:
+
+- [bugtracker](https://github.com/puppeteer/puppeteer/issues)
+- [Stack Overflow](https://stackoverflow.com/questions/tagged/puppeteer)
+
+Make sure to search these channels before posting your question.
+
+
diff --git a/website/static/.nojekyll b/website/static/.nojekyll
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/website/static/img/docusaurus.png b/website/static/img/docusaurus.png
new file mode 100644
index 0000000000000..f458149e3c8f5
Binary files /dev/null and b/website/static/img/docusaurus.png differ
diff --git a/website/static/img/favicon.ico b/website/static/img/favicon.ico
new file mode 100644
index 0000000000000..c01d54bcd39a5
Binary files /dev/null and b/website/static/img/favicon.ico differ
diff --git a/website/static/img/logo.svg b/website/static/img/logo.svg
new file mode 100644
index 0000000000000..9db6d0d066e3d
--- /dev/null
+++ b/website/static/img/logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/website/static/img/tutorial/docsVersionDropdown.png b/website/static/img/tutorial/docsVersionDropdown.png
new file mode 100644
index 0000000000000..ff1cbe68893d2
Binary files /dev/null and b/website/static/img/tutorial/docsVersionDropdown.png differ
diff --git a/website/static/img/tutorial/localeDropdown.png b/website/static/img/tutorial/localeDropdown.png
new file mode 100644
index 0000000000000..d7163f9675249
Binary files /dev/null and b/website/static/img/tutorial/localeDropdown.png differ
diff --git a/website/static/img/undraw_docusaurus_mountain.svg b/website/static/img/undraw_docusaurus_mountain.svg
new file mode 100644
index 0000000000000..431cef2f7fece
--- /dev/null
+++ b/website/static/img/undraw_docusaurus_mountain.svg
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/website/static/img/undraw_docusaurus_react.svg b/website/static/img/undraw_docusaurus_react.svg
new file mode 100644
index 0000000000000..e417050433381
--- /dev/null
+++ b/website/static/img/undraw_docusaurus_react.svg
@@ -0,0 +1,169 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/website/static/img/undraw_docusaurus_tree.svg b/website/static/img/undraw_docusaurus_tree.svg
new file mode 100644
index 0000000000000..a05cc03dda90f
--- /dev/null
+++ b/website/static/img/undraw_docusaurus_tree.svg
@@ -0,0 +1 @@
+docu_tree
\ No newline at end of file
diff --git a/website/versioned_docs/version-10.0.0/index.md b/website/versioned_docs/version-10.0.0/index.md
new file mode 100644
index 0000000000000..ee99080bb5206
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/index.md
@@ -0,0 +1,12 @@
+
+
+[Home](./index.md)
+
+## API Reference
+
+## Packages
+
+| Package | Description |
+| --- | --- |
+| [puppeteer](./puppeteer.md) | |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.accessibility.md b/website/versioned_docs/version-10.0.0/puppeteer.accessibility.md
new file mode 100644
index 0000000000000..45f0550e40fe5
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.accessibility.md
@@ -0,0 +1,30 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Accessibility](./puppeteer.accessibility.md)
+
+## Accessibility class
+
+The Accessibility class provides methods for inspecting Chromium's accessibility tree. The accessibility tree is used by assistive technology such as [screen readers](https://en.wikipedia.org/wiki/Screen_reader) or [switches](https://en.wikipedia.org/wiki/Switch_access).
+
+Signature:
+
+```typescript
+export declare class Accessibility
+```
+
+## Remarks
+
+Accessibility is a very platform-specific thing. On different platforms, there are different screen readers that might have wildly different output.
+
+Blink - Chrome's rendering engine - has a concept of "accessibility tree", which is then translated into different platform-specific APIs. Accessibility namespace gives users access to the Blink Accessibility Tree.
+
+Most of the accessibility tree gets filtered out when converting from Blink AX Tree to Platform-specific AX-Tree or by assistive technologies themselves. By default, Puppeteer tries to approximate this filtering, exposing only the "interesting" nodes of the tree.
+
+The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `Accessibility` class.
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [snapshot(options)](./puppeteer.accessibility.snapshot.md) | | Captures the current state of the accessibility tree. The returned object represents the root accessible node of the page. |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.accessibility.snapshot.md b/website/versioned_docs/version-10.0.0/puppeteer.accessibility.snapshot.md
new file mode 100644
index 0000000000000..5304955f2ddeb
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.accessibility.snapshot.md
@@ -0,0 +1,61 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Accessibility](./puppeteer.accessibility.md) > [snapshot](./puppeteer.accessibility.snapshot.md)
+
+## Accessibility.snapshot() method
+
+Captures the current state of the accessibility tree. The returned object represents the root accessible node of the page.
+
+Signature:
+
+```typescript
+snapshot(options?: SnapshotOptions): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| options | [SnapshotOptions](./puppeteer.snapshotoptions.md) | |
+
+Returns:
+
+Promise<[SerializedAXNode](./puppeteer.serializedaxnode.md)>
+
+An AXNode object representing the snapshot.
+
+## Remarks
+
+\*\*NOTE\*\* The Chromium accessibility tree contains nodes that go unused on most platforms and by most screen readers. Puppeteer will discard them as well for an easier to process tree, unless `interestingOnly` is set to `false`.
+
+## Example 1
+
+An example of dumping the entire accessibility tree:
+
+```js
+const snapshot = await page.accessibility.snapshot();
+console.log(snapshot);
+
+```
+
+## Example 2
+
+An example of logging the focused node's name:
+
+```js
+const snapshot = await page.accessibility.snapshot();
+const node = findFocusedNode(snapshot);
+console.log(node && node.name);
+
+function findFocusedNode(node) {
+ if (node.focused)
+ return node;
+ for (const child of node.children || []) {
+ const foundNode = findFocusedNode(child);
+ return foundNode;
+ }
+ return null;
+}
+
+```
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.actionresult.md b/website/versioned_docs/version-10.0.0/puppeteer.actionresult.md
new file mode 100644
index 0000000000000..e8e6334dace98
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.actionresult.md
@@ -0,0 +1,12 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ActionResult](./puppeteer.actionresult.md)
+
+## ActionResult type
+
+
+Signature:
+
+```typescript
+export declare type ActionResult = 'continue' | 'abort' | 'respond';
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.boundingbox.height.md b/website/versioned_docs/version-10.0.0/puppeteer.boundingbox.height.md
new file mode 100644
index 0000000000000..9fb33326555d9
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.boundingbox.height.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BoundingBox](./puppeteer.boundingbox.md) > [height](./puppeteer.boundingbox.height.md)
+
+## BoundingBox.height property
+
+the height of the element in pixels.
+
+Signature:
+
+```typescript
+height: number;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.boundingbox.md b/website/versioned_docs/version-10.0.0/puppeteer.boundingbox.md
new file mode 100644
index 0000000000000..3ff8637291869
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.boundingbox.md
@@ -0,0 +1,22 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BoundingBox](./puppeteer.boundingbox.md)
+
+## BoundingBox interface
+
+
+Signature:
+
+```typescript
+export interface BoundingBox
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [height](./puppeteer.boundingbox.height.md) | number | the height of the element in pixels. |
+| [width](./puppeteer.boundingbox.width.md) | number | the width of the element in pixels. |
+| [x](./puppeteer.boundingbox.x.md) | number | the x coordinate of the element in pixels. |
+| [y](./puppeteer.boundingbox.y.md) | number | the y coordinate of the element in pixels. |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.boundingbox.width.md b/website/versioned_docs/version-10.0.0/puppeteer.boundingbox.width.md
new file mode 100644
index 0000000000000..f0fc401e4c945
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.boundingbox.width.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BoundingBox](./puppeteer.boundingbox.md) > [width](./puppeteer.boundingbox.width.md)
+
+## BoundingBox.width property
+
+the width of the element in pixels.
+
+Signature:
+
+```typescript
+width: number;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.boundingbox.x.md b/website/versioned_docs/version-10.0.0/puppeteer.boundingbox.x.md
new file mode 100644
index 0000000000000..b64a4bdb3c8e4
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.boundingbox.x.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BoundingBox](./puppeteer.boundingbox.md) > [x](./puppeteer.boundingbox.x.md)
+
+## BoundingBox.x property
+
+the x coordinate of the element in pixels.
+
+Signature:
+
+```typescript
+x: number;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.boundingbox.y.md b/website/versioned_docs/version-10.0.0/puppeteer.boundingbox.y.md
new file mode 100644
index 0000000000000..73194ab629914
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.boundingbox.y.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BoundingBox](./puppeteer.boundingbox.md) > [y](./puppeteer.boundingbox.y.md)
+
+## BoundingBox.y property
+
+the y coordinate of the element in pixels.
+
+Signature:
+
+```typescript
+y: number;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.boxmodel.border.md b/website/versioned_docs/version-10.0.0/puppeteer.boxmodel.border.md
new file mode 100644
index 0000000000000..7c0d7c2b9300e
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.boxmodel.border.md
@@ -0,0 +1,14 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BoxModel](./puppeteer.boxmodel.md) > [border](./puppeteer.boxmodel.border.md)
+
+## BoxModel.border property
+
+Signature:
+
+```typescript
+border: Array<{
+ x: number;
+ y: number;
+ }>;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.boxmodel.content.md b/website/versioned_docs/version-10.0.0/puppeteer.boxmodel.content.md
new file mode 100644
index 0000000000000..1abc3e8598edc
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.boxmodel.content.md
@@ -0,0 +1,14 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BoxModel](./puppeteer.boxmodel.md) > [content](./puppeteer.boxmodel.content.md)
+
+## BoxModel.content property
+
+Signature:
+
+```typescript
+content: Array<{
+ x: number;
+ y: number;
+ }>;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.boxmodel.height.md b/website/versioned_docs/version-10.0.0/puppeteer.boxmodel.height.md
new file mode 100644
index 0000000000000..c28cb8bf53c36
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.boxmodel.height.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BoxModel](./puppeteer.boxmodel.md) > [height](./puppeteer.boxmodel.height.md)
+
+## BoxModel.height property
+
+Signature:
+
+```typescript
+height: number;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.boxmodel.margin.md b/website/versioned_docs/version-10.0.0/puppeteer.boxmodel.margin.md
new file mode 100644
index 0000000000000..876a8d089f808
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.boxmodel.margin.md
@@ -0,0 +1,14 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BoxModel](./puppeteer.boxmodel.md) > [margin](./puppeteer.boxmodel.margin.md)
+
+## BoxModel.margin property
+
+Signature:
+
+```typescript
+margin: Array<{
+ x: number;
+ y: number;
+ }>;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.boxmodel.md b/website/versioned_docs/version-10.0.0/puppeteer.boxmodel.md
new file mode 100644
index 0000000000000..eadbcab83e2ff
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.boxmodel.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BoxModel](./puppeteer.boxmodel.md)
+
+## BoxModel interface
+
+
+Signature:
+
+```typescript
+export interface BoxModel
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [border](./puppeteer.boxmodel.border.md) | Array<{ x: number; y: number; }> | |
+| [content](./puppeteer.boxmodel.content.md) | Array<{ x: number; y: number; }> | |
+| [height](./puppeteer.boxmodel.height.md) | number | |
+| [margin](./puppeteer.boxmodel.margin.md) | Array<{ x: number; y: number; }> | |
+| [padding](./puppeteer.boxmodel.padding.md) | Array<{ x: number; y: number; }> | |
+| [width](./puppeteer.boxmodel.width.md) | number | |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.boxmodel.padding.md b/website/versioned_docs/version-10.0.0/puppeteer.boxmodel.padding.md
new file mode 100644
index 0000000000000..11761bb412d6d
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.boxmodel.padding.md
@@ -0,0 +1,14 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BoxModel](./puppeteer.boxmodel.md) > [padding](./puppeteer.boxmodel.padding.md)
+
+## BoxModel.padding property
+
+Signature:
+
+```typescript
+padding: Array<{
+ x: number;
+ y: number;
+ }>;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.boxmodel.width.md b/website/versioned_docs/version-10.0.0/puppeteer.boxmodel.width.md
new file mode 100644
index 0000000000000..4e9cfb3239d59
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.boxmodel.width.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BoxModel](./puppeteer.boxmodel.md) > [width](./puppeteer.boxmodel.width.md)
+
+## BoxModel.width property
+
+Signature:
+
+```typescript
+width: number;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browser.browsercontexts.md b/website/versioned_docs/version-10.0.0/puppeteer.browser.browsercontexts.md
new file mode 100644
index 0000000000000..4ec2e04d917ae
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browser.browsercontexts.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Browser](./puppeteer.browser.md) > [browserContexts](./puppeteer.browser.browsercontexts.md)
+
+## Browser.browserContexts() method
+
+Returns an array of all open browser contexts. In a newly created browser, this will return a single instance of [BrowserContext](./puppeteer.browsercontext.md).
+
+Signature:
+
+```typescript
+browserContexts(): BrowserContext[];
+```
+Returns:
+
+[BrowserContext](./puppeteer.browsercontext.md)\[\]
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browser.close.md b/website/versioned_docs/version-10.0.0/puppeteer.browser.close.md
new file mode 100644
index 0000000000000..725492710b023
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browser.close.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Browser](./puppeteer.browser.md) > [close](./puppeteer.browser.close.md)
+
+## Browser.close() method
+
+Closes Chromium and all of its pages (if any were opened). The [Browser](./puppeteer.browser.md) object itself is considered to be disposed and cannot be used anymore.
+
+Signature:
+
+```typescript
+close(): Promise;
+```
+Returns:
+
+Promise<void>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browser.createincognitobrowsercontext.md b/website/versioned_docs/version-10.0.0/puppeteer.browser.createincognitobrowsercontext.md
new file mode 100644
index 0000000000000..8bd4f9ebed3e5
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browser.createincognitobrowsercontext.md
@@ -0,0 +1,33 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Browser](./puppeteer.browser.md) > [createIncognitoBrowserContext](./puppeteer.browser.createincognitobrowsercontext.md)
+
+## Browser.createIncognitoBrowserContext() method
+
+Creates a new incognito browser context. This won't share cookies/cache with other browser contexts.
+
+Signature:
+
+```typescript
+createIncognitoBrowserContext(): Promise;
+```
+Returns:
+
+Promise<[BrowserContext](./puppeteer.browsercontext.md)>
+
+## Example
+
+
+```js
+(async () => {
+ const browser = await puppeteer.launch();
+ // Create a new incognito browser context.
+ const context = await browser.createIncognitoBrowserContext();
+ // Create a new page in a pristine context.
+ const page = await context.newPage();
+ // Do stuff
+ await page.goto('https://example.com');
+})();
+
+```
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browser.defaultbrowsercontext.md b/website/versioned_docs/version-10.0.0/puppeteer.browser.defaultbrowsercontext.md
new file mode 100644
index 0000000000000..92fd82c81744c
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browser.defaultbrowsercontext.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Browser](./puppeteer.browser.md) > [defaultBrowserContext](./puppeteer.browser.defaultbrowsercontext.md)
+
+## Browser.defaultBrowserContext() method
+
+Returns the default browser context. The default browser context cannot be closed.
+
+Signature:
+
+```typescript
+defaultBrowserContext(): BrowserContext;
+```
+Returns:
+
+[BrowserContext](./puppeteer.browsercontext.md)
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browser.disconnect.md b/website/versioned_docs/version-10.0.0/puppeteer.browser.disconnect.md
new file mode 100644
index 0000000000000..2b4a3f9f80fea
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browser.disconnect.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Browser](./puppeteer.browser.md) > [disconnect](./puppeteer.browser.disconnect.md)
+
+## Browser.disconnect() method
+
+Disconnects Puppeteer from the browser, but leaves the Chromium process running. After calling `disconnect`, the [Browser](./puppeteer.browser.md) object is considered disposed and cannot be used anymore.
+
+Signature:
+
+```typescript
+disconnect(): void;
+```
+Returns:
+
+void
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browser.isconnected.md b/website/versioned_docs/version-10.0.0/puppeteer.browser.isconnected.md
new file mode 100644
index 0000000000000..756998990e567
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browser.isconnected.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Browser](./puppeteer.browser.md) > [isConnected](./puppeteer.browser.isconnected.md)
+
+## Browser.isConnected() method
+
+Indicates that the browser is connected.
+
+Signature:
+
+```typescript
+isConnected(): boolean;
+```
+Returns:
+
+boolean
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browser.md b/website/versioned_docs/version-10.0.0/puppeteer.browser.md
new file mode 100644
index 0000000000000..9d001d205df1e
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browser.md
@@ -0,0 +1,79 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Browser](./puppeteer.browser.md)
+
+## Browser class
+
+A Browser is created when Puppeteer connects to a Chromium instance, either through [PuppeteerNode.launch()](./puppeteer.puppeteernode.launch.md) or [Puppeteer.connect()](./puppeteer.puppeteer.connect.md).
+
+Signature:
+
+```typescript
+export declare class Browser extends EventEmitter
+```
+Extends: [EventEmitter](./puppeteer.eventemitter.md)
+
+## Remarks
+
+The Browser class extends from Puppeteer's [EventEmitter](./puppeteer.eventemitter.md) class and will emit various events which are documented in the [BrowserEmittedEvents](./puppeteer.browseremittedevents.md) enum.
+
+The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `Browser` class.
+
+## Example 1
+
+An example of using a [Browser](./puppeteer.browser.md) to create a [Page](./puppeteer.page.md):
+
+```js
+const puppeteer = require('puppeteer');
+
+(async () => {
+ const browser = await puppeteer.launch();
+ const page = await browser.newPage();
+ await page.goto('https://example.com');
+ await browser.close();
+})();
+
+```
+
+## Example 2
+
+An example of disconnecting from and reconnecting to a [Browser](./puppeteer.browser.md):
+
+```js
+const puppeteer = require('puppeteer');
+
+(async () => {
+ const browser = await puppeteer.launch();
+ // Store the endpoint to be able to reconnect to Chromium
+ const browserWSEndpoint = browser.wsEndpoint();
+ // Disconnect puppeteer from Chromium
+ browser.disconnect();
+
+ // Use the endpoint to reestablish a connection
+ const browser2 = await puppeteer.connect({browserWSEndpoint});
+ // Close Chromium
+ await browser2.close();
+})();
+
+```
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [browserContexts()](./puppeteer.browser.browsercontexts.md) | | Returns an array of all open browser contexts. In a newly created browser, this will return a single instance of [BrowserContext](./puppeteer.browsercontext.md). |
+| [close()](./puppeteer.browser.close.md) | | Closes Chromium and all of its pages (if any were opened). The [Browser](./puppeteer.browser.md) object itself is considered to be disposed and cannot be used anymore. |
+| [createIncognitoBrowserContext()](./puppeteer.browser.createincognitobrowsercontext.md) | | Creates a new incognito browser context. This won't share cookies/cache with other browser contexts. |
+| [defaultBrowserContext()](./puppeteer.browser.defaultbrowsercontext.md) | | Returns the default browser context. The default browser context cannot be closed. |
+| [disconnect()](./puppeteer.browser.disconnect.md) | | Disconnects Puppeteer from the browser, but leaves the Chromium process running. After calling disconnect
, the [Browser](./puppeteer.browser.md) object is considered disposed and cannot be used anymore. |
+| [isConnected()](./puppeteer.browser.isconnected.md) | | Indicates that the browser is connected. |
+| [newPage()](./puppeteer.browser.newpage.md) | | Promise which resolves to a new [Page](./puppeteer.page.md) object. The Page is created in a default browser context. |
+| [pages()](./puppeteer.browser.pages.md) | | An array of all open pages inside the Browser. |
+| [process()](./puppeteer.browser.process.md) | | The spawned browser process. Returns null
if the browser instance was created with [Puppeteer.connect()](./puppeteer.puppeteer.connect.md). |
+| [target()](./puppeteer.browser.target.md) | | The target associated with the browser. |
+| [targets()](./puppeteer.browser.targets.md) | | All active targets inside the Browser. In case of multiple browser contexts, returns an array with all the targets in all browser contexts. |
+| [userAgent()](./puppeteer.browser.useragent.md) | | The browser's original user agent. Pages can override the browser user agent with [Page.setUserAgent()](./puppeteer.page.setuseragent.md). |
+| [version()](./puppeteer.browser.version.md) | | A string representing the browser name and version. |
+| [waitForTarget(predicate, options)](./puppeteer.browser.waitfortarget.md) | | Searches for a target in all browser contexts. |
+| [wsEndpoint()](./puppeteer.browser.wsendpoint.md) | | The browser websocket endpoint which can be used as an argument to [Puppeteer.connect()](./puppeteer.puppeteer.connect.md). |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browser.newpage.md b/website/versioned_docs/version-10.0.0/puppeteer.browser.newpage.md
new file mode 100644
index 0000000000000..7a4b47ef1fb8a
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browser.newpage.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Browser](./puppeteer.browser.md) > [newPage](./puppeteer.browser.newpage.md)
+
+## Browser.newPage() method
+
+Promise which resolves to a new [Page](./puppeteer.page.md) object. The Page is created in a default browser context.
+
+Signature:
+
+```typescript
+newPage(): Promise;
+```
+Returns:
+
+Promise<[Page](./puppeteer.page.md)>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browser.pages.md b/website/versioned_docs/version-10.0.0/puppeteer.browser.pages.md
new file mode 100644
index 0000000000000..f16533c0fc9ad
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browser.pages.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Browser](./puppeteer.browser.md) > [pages](./puppeteer.browser.pages.md)
+
+## Browser.pages() method
+
+An array of all open pages inside the Browser.
+
+Signature:
+
+```typescript
+pages(): Promise;
+```
+Returns:
+
+Promise<[Page](./puppeteer.page.md)\[\]>
+
+## Remarks
+
+In case of multiple browser contexts, returns an array with all the pages in all browser contexts. Non-visible pages, such as `"background_page"`, will not be listed here. You can find them using [Target.page()](./puppeteer.target.page.md).
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browser.process.md b/website/versioned_docs/version-10.0.0/puppeteer.browser.process.md
new file mode 100644
index 0000000000000..5b34ad71cea2c
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browser.process.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Browser](./puppeteer.browser.md) > [process](./puppeteer.browser.process.md)
+
+## Browser.process() method
+
+The spawned browser process. Returns `null` if the browser instance was created with [Puppeteer.connect()](./puppeteer.puppeteer.connect.md).
+
+Signature:
+
+```typescript
+process(): ChildProcess | null;
+```
+Returns:
+
+ChildProcess \| null
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browser.target.md b/website/versioned_docs/version-10.0.0/puppeteer.browser.target.md
new file mode 100644
index 0000000000000..0546aace49048
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browser.target.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Browser](./puppeteer.browser.md) > [target](./puppeteer.browser.target.md)
+
+## Browser.target() method
+
+The target associated with the browser.
+
+Signature:
+
+```typescript
+target(): Target;
+```
+Returns:
+
+[Target](./puppeteer.target.md)
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browser.targets.md b/website/versioned_docs/version-10.0.0/puppeteer.browser.targets.md
new file mode 100644
index 0000000000000..14eb2d4a17517
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browser.targets.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Browser](./puppeteer.browser.md) > [targets](./puppeteer.browser.targets.md)
+
+## Browser.targets() method
+
+All active targets inside the Browser. In case of multiple browser contexts, returns an array with all the targets in all browser contexts.
+
+Signature:
+
+```typescript
+targets(): Target[];
+```
+Returns:
+
+[Target](./puppeteer.target.md)\[\]
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browser.useragent.md b/website/versioned_docs/version-10.0.0/puppeteer.browser.useragent.md
new file mode 100644
index 0000000000000..ae1f1f5919d54
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browser.useragent.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Browser](./puppeteer.browser.md) > [userAgent](./puppeteer.browser.useragent.md)
+
+## Browser.userAgent() method
+
+The browser's original user agent. Pages can override the browser user agent with [Page.setUserAgent()](./puppeteer.page.setuseragent.md).
+
+Signature:
+
+```typescript
+userAgent(): Promise;
+```
+Returns:
+
+Promise<string>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browser.version.md b/website/versioned_docs/version-10.0.0/puppeteer.browser.version.md
new file mode 100644
index 0000000000000..8e2f3e5b048c0
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browser.version.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Browser](./puppeteer.browser.md) > [version](./puppeteer.browser.version.md)
+
+## Browser.version() method
+
+A string representing the browser name and version.
+
+Signature:
+
+```typescript
+version(): Promise;
+```
+Returns:
+
+Promise<string>
+
+## Remarks
+
+For headless Chromium, this is similar to `HeadlessChrome/61.0.3153.0`. For non-headless, this is similar to `Chrome/61.0.3153.0`.
+
+The format of browser.version() might change with future releases of Chromium.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browser.waitfortarget.md b/website/versioned_docs/version-10.0.0/puppeteer.browser.waitfortarget.md
new file mode 100644
index 0000000000000..a607dcbc98196
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browser.waitfortarget.md
@@ -0,0 +1,37 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Browser](./puppeteer.browser.md) > [waitForTarget](./puppeteer.browser.waitfortarget.md)
+
+## Browser.waitForTarget() method
+
+Searches for a target in all browser contexts.
+
+Signature:
+
+```typescript
+waitForTarget(predicate: (x: Target) => boolean, options?: WaitForTargetOptions): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| predicate | (x: [Target](./puppeteer.target.md)) => boolean | A function to be run for every target. |
+| options | [WaitForTargetOptions](./puppeteer.waitfortargetoptions.md) | |
+
+Returns:
+
+Promise<[Target](./puppeteer.target.md)>
+
+The first target found that matches the `predicate` function.
+
+## Example
+
+An example of finding a target for a page opened via `window.open`:
+
+```js
+await page.evaluate(() => window.open('https://www.example.com/'));
+const newWindowTarget = await browser.waitForTarget(target => target.url() === 'https://www.example.com/');
+
+```
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browser.wsendpoint.md b/website/versioned_docs/version-10.0.0/puppeteer.browser.wsendpoint.md
new file mode 100644
index 0000000000000..751cad5c18075
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browser.wsendpoint.md
@@ -0,0 +1,25 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Browser](./puppeteer.browser.md) > [wsEndpoint](./puppeteer.browser.wsendpoint.md)
+
+## Browser.wsEndpoint() method
+
+The browser websocket endpoint which can be used as an argument to [Puppeteer.connect()](./puppeteer.puppeteer.connect.md).
+
+Signature:
+
+```typescript
+wsEndpoint(): string;
+```
+Returns:
+
+string
+
+The Browser websocket url.
+
+## Remarks
+
+The format is `ws://${host}:${port}/devtools/browser/`.
+
+You can find the `webSocketDebuggerUrl` from `http://${host}:${port}/json/version`. Learn more about the [devtools protocol](https://chromedevtools.github.io/devtools-protocol) and the [browser endpoint](https://chromedevtools.github.io/devtools-protocol/#how-do-i-access-the-browser-target).
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserconnectoptions.defaultviewport.md b/website/versioned_docs/version-10.0.0/puppeteer.browserconnectoptions.defaultviewport.md
new file mode 100644
index 0000000000000..d610fd65fa4b2
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserconnectoptions.defaultviewport.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserConnectOptions](./puppeteer.browserconnectoptions.md) > [defaultViewport](./puppeteer.browserconnectoptions.defaultviewport.md)
+
+## BrowserConnectOptions.defaultViewport property
+
+Sets the viewport for each page.
+
+Signature:
+
+```typescript
+defaultViewport?: Viewport | null;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserconnectoptions.ignorehttpserrors.md b/website/versioned_docs/version-10.0.0/puppeteer.browserconnectoptions.ignorehttpserrors.md
new file mode 100644
index 0000000000000..627cbe2c95661
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserconnectoptions.ignorehttpserrors.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserConnectOptions](./puppeteer.browserconnectoptions.md) > [ignoreHTTPSErrors](./puppeteer.browserconnectoptions.ignorehttpserrors.md)
+
+## BrowserConnectOptions.ignoreHTTPSErrors property
+
+Whether to ignore HTTPS errors during navigation.
+
+Signature:
+
+```typescript
+ignoreHTTPSErrors?: boolean;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserconnectoptions.md b/website/versioned_docs/version-10.0.0/puppeteer.browserconnectoptions.md
new file mode 100644
index 0000000000000..c473f69d559d1
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserconnectoptions.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserConnectOptions](./puppeteer.browserconnectoptions.md)
+
+## BrowserConnectOptions interface
+
+Generic browser options that can be passed when launching any browser or when connecting to an existing browser instance.
+
+Signature:
+
+```typescript
+export interface BrowserConnectOptions
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [defaultViewport?](./puppeteer.browserconnectoptions.defaultviewport.md) | [Viewport](./puppeteer.viewport.md) \| null | (Optional) Sets the viewport for each page. |
+| [ignoreHTTPSErrors?](./puppeteer.browserconnectoptions.ignorehttpserrors.md) | boolean | (Optional) Whether to ignore HTTPS errors during navigation. |
+| [slowMo?](./puppeteer.browserconnectoptions.slowmo.md) | number | (Optional) Slows down Puppeteer operations by the specified amount of milliseconds to aid debugging. |
+| [targetFilter?](./puppeteer.browserconnectoptions.targetfilter.md) | [TargetFilterCallback](./puppeteer.targetfiltercallback.md) | (Optional) Callback to decide if Puppeteer should connect to a given target or not. |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserconnectoptions.slowmo.md b/website/versioned_docs/version-10.0.0/puppeteer.browserconnectoptions.slowmo.md
new file mode 100644
index 0000000000000..f0eacae09353c
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserconnectoptions.slowmo.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserConnectOptions](./puppeteer.browserconnectoptions.md) > [slowMo](./puppeteer.browserconnectoptions.slowmo.md)
+
+## BrowserConnectOptions.slowMo property
+
+Slows down Puppeteer operations by the specified amount of milliseconds to aid debugging.
+
+Signature:
+
+```typescript
+slowMo?: number;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserconnectoptions.targetfilter.md b/website/versioned_docs/version-10.0.0/puppeteer.browserconnectoptions.targetfilter.md
new file mode 100644
index 0000000000000..6c58b075a7877
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserconnectoptions.targetfilter.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserConnectOptions](./puppeteer.browserconnectoptions.md) > [targetFilter](./puppeteer.browserconnectoptions.targetfilter.md)
+
+## BrowserConnectOptions.targetFilter property
+
+Callback to decide if Puppeteer should connect to a given target or not.
+
+Signature:
+
+```typescript
+targetFilter?: TargetFilterCallback;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.browser.md b/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.browser.md
new file mode 100644
index 0000000000000..85dadca18e3d9
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.browser.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserContext](./puppeteer.browsercontext.md) > [browser](./puppeteer.browsercontext.browser.md)
+
+## BrowserContext.browser() method
+
+The browser this browser context belongs to.
+
+Signature:
+
+```typescript
+browser(): Browser;
+```
+Returns:
+
+[Browser](./puppeteer.browser.md)
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.clearpermissionoverrides.md b/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.clearpermissionoverrides.md
new file mode 100644
index 0000000000000..85872769eb80b
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.clearpermissionoverrides.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserContext](./puppeteer.browsercontext.md) > [clearPermissionOverrides](./puppeteer.browsercontext.clearpermissionoverrides.md)
+
+## BrowserContext.clearPermissionOverrides() method
+
+Clears all permission overrides for the browser context.
+
+Signature:
+
+```typescript
+clearPermissionOverrides(): Promise;
+```
+Returns:
+
+Promise<void>
+
+## Example
+
+
+```js
+const context = browser.defaultBrowserContext();
+context.overridePermissions('https://example.com', ['clipboard-read']);
+// do stuff ..
+context.clearPermissionOverrides();
+
+```
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.close.md b/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.close.md
new file mode 100644
index 0000000000000..7b6a9c75f2628
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.close.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserContext](./puppeteer.browsercontext.md) > [close](./puppeteer.browsercontext.close.md)
+
+## BrowserContext.close() method
+
+Closes the browser context. All the targets that belong to the browser context will be closed.
+
+Signature:
+
+```typescript
+close(): Promise;
+```
+Returns:
+
+Promise<void>
+
+## Remarks
+
+Only incognito browser contexts can be closed.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.isincognito.md b/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.isincognito.md
new file mode 100644
index 0000000000000..412665d0386b1
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.isincognito.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserContext](./puppeteer.browsercontext.md) > [isIncognito](./puppeteer.browsercontext.isincognito.md)
+
+## BrowserContext.isIncognito() method
+
+Returns whether BrowserContext is incognito. The default browser context is the only non-incognito browser context.
+
+Signature:
+
+```typescript
+isIncognito(): boolean;
+```
+Returns:
+
+boolean
+
+## Remarks
+
+The default browser context cannot be closed.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.md b/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.md
new file mode 100644
index 0000000000000..3379d33dc6dd6
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.md
@@ -0,0 +1,54 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserContext](./puppeteer.browsercontext.md)
+
+## BrowserContext class
+
+BrowserContexts provide a way to operate multiple independent browser sessions. When a browser is launched, it has a single BrowserContext used by default. The method [Browser.newPage](./puppeteer.browser.newpage.md) creates a page in the default browser context.
+
+Signature:
+
+```typescript
+export declare class BrowserContext extends EventEmitter
+```
+Extends: [EventEmitter](./puppeteer.eventemitter.md)
+
+## Remarks
+
+The Browser class extends from Puppeteer's [EventEmitter](./puppeteer.eventemitter.md) class and will emit various events which are documented in the [BrowserContextEmittedEvents](./puppeteer.browsercontextemittedevents.md) enum.
+
+If a page opens another page, e.g. with a `window.open` call, the popup will belong to the parent page's browser context.
+
+Puppeteer allows creation of "incognito" browser contexts with [Browser.createIncognitoBrowserContext](./puppeteer.browser.createincognitobrowsercontext.md) method. "Incognito" browser contexts don't write any browsing data to disk.
+
+The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `BrowserContext` class.
+
+## Example
+
+
+```js
+// Create a new incognito browser context
+const context = await browser.createIncognitoBrowserContext();
+// Create a new page inside context.
+const page = await context.newPage();
+// ... do stuff with page ...
+await page.goto('https://example.com');
+// Dispose context once it's no longer needed.
+await context.close();
+
+```
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [browser()](./puppeteer.browsercontext.browser.md) | | The browser this browser context belongs to. |
+| [clearPermissionOverrides()](./puppeteer.browsercontext.clearpermissionoverrides.md) | | Clears all permission overrides for the browser context. |
+| [close()](./puppeteer.browsercontext.close.md) | | Closes the browser context. All the targets that belong to the browser context will be closed. |
+| [isIncognito()](./puppeteer.browsercontext.isincognito.md) | | Returns whether BrowserContext is incognito. The default browser context is the only non-incognito browser context. |
+| [newPage()](./puppeteer.browsercontext.newpage.md) | | Creates a new page in the browser context. |
+| [overridePermissions(origin, permissions)](./puppeteer.browsercontext.overridepermissions.md) | | |
+| [pages()](./puppeteer.browsercontext.pages.md) | | An array of all pages inside the browser context. |
+| [targets()](./puppeteer.browsercontext.targets.md) | | An array of all active targets inside the browser context. |
+| [waitForTarget(predicate, options)](./puppeteer.browsercontext.waitfortarget.md) | | This searches for a target in this specific browser context. |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.newpage.md b/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.newpage.md
new file mode 100644
index 0000000000000..440e0e0121427
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.newpage.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserContext](./puppeteer.browsercontext.md) > [newPage](./puppeteer.browsercontext.newpage.md)
+
+## BrowserContext.newPage() method
+
+Creates a new page in the browser context.
+
+Signature:
+
+```typescript
+newPage(): Promise;
+```
+Returns:
+
+Promise<[Page](./puppeteer.page.md)>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.overridepermissions.md b/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.overridepermissions.md
new file mode 100644
index 0000000000000..a79725be37df2
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.overridepermissions.md
@@ -0,0 +1,32 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserContext](./puppeteer.browsercontext.md) > [overridePermissions](./puppeteer.browsercontext.overridepermissions.md)
+
+## BrowserContext.overridePermissions() method
+
+Signature:
+
+```typescript
+overridePermissions(origin: string, permissions: Permission[]): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| origin | string | The origin to grant permissions to, e.g. "https://example.com". |
+| permissions | [Permission](./puppeteer.permission.md)\[\] | An array of permissions to grant. All permissions that are not listed here will be automatically denied. |
+
+Returns:
+
+Promise<void>
+
+## Example
+
+
+```js
+const context = browser.defaultBrowserContext();
+await context.overridePermissions('https://html5demos.com', ['geolocation']);
+
+```
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.pages.md b/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.pages.md
new file mode 100644
index 0000000000000..b56ec861383cf
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.pages.md
@@ -0,0 +1,19 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserContext](./puppeteer.browsercontext.md) > [pages](./puppeteer.browsercontext.pages.md)
+
+## BrowserContext.pages() method
+
+An array of all pages inside the browser context.
+
+Signature:
+
+```typescript
+pages(): Promise;
+```
+Returns:
+
+Promise<[Page](./puppeteer.page.md)\[\]>
+
+Promise which resolves to an array of all open pages. Non visible pages, such as `"background_page"`, will not be listed here. You can find them using [the target page](./puppeteer.target.page.md).
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.targets.md b/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.targets.md
new file mode 100644
index 0000000000000..b3bb64d88292b
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.targets.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserContext](./puppeteer.browsercontext.md) > [targets](./puppeteer.browsercontext.targets.md)
+
+## BrowserContext.targets() method
+
+An array of all active targets inside the browser context.
+
+Signature:
+
+```typescript
+targets(): Target[];
+```
+Returns:
+
+[Target](./puppeteer.target.md)\[\]
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.waitfortarget.md b/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.waitfortarget.md
new file mode 100644
index 0000000000000..46f3fee4493d7
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browsercontext.waitfortarget.md
@@ -0,0 +1,39 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserContext](./puppeteer.browsercontext.md) > [waitForTarget](./puppeteer.browsercontext.waitfortarget.md)
+
+## BrowserContext.waitForTarget() method
+
+This searches for a target in this specific browser context.
+
+Signature:
+
+```typescript
+waitForTarget(predicate: (x: Target) => boolean, options?: {
+ timeout?: number;
+ }): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| predicate | (x: [Target](./puppeteer.target.md)) => boolean | A function to be run for every target |
+| options | { timeout?: number; } | An object of options. Accepts a timout, which is the maximum wait time in milliseconds. Pass 0
to disable the timeout. Defaults to 30 seconds. |
+
+Returns:
+
+Promise<[Target](./puppeteer.target.md)>
+
+Promise which resolves to the first target found that matches the `predicate` function.
+
+## Example
+
+An example of finding a target for a page opened via `window.open`:
+
+```js
+await page.evaluate(() => window.open('https://www.example.com/'));
+const newWindowTarget = await browserContext.waitForTarget(target => target.url() === 'https://www.example.com/');
+
+```
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browsercontextemittedevents.md b/website/versioned_docs/version-10.0.0/puppeteer.browsercontextemittedevents.md
new file mode 100644
index 0000000000000..2302875de6584
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browsercontextemittedevents.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserContextEmittedEvents](./puppeteer.browsercontextemittedevents.md)
+
+## BrowserContextEmittedEvents enum
+
+
+Signature:
+
+```typescript
+export declare const enum BrowserContextEmittedEvents
+```
+
+## Enumeration Members
+
+| Member | Value | Description |
+| --- | --- | --- |
+| TargetChanged | "targetchanged"
| Emitted when the url of a target inside the browser context changes. Contains a [Target](./puppeteer.target.md) instance. |
+| TargetCreated | "targetcreated"
| Emitted when a target is created within the browser context, for example when a new page is opened by [window.open](https://developer.mozilla.org/en-US/docs/Web/API/Window/open) or by [browserContext.newPage](./puppeteer.browsercontext.newpage.md)Contains a [Target](./puppeteer.target.md) instance. |
+| TargetDestroyed | "targetdestroyed"
| Emitted when a target is destroyed within the browser context, for example when a page is closed. Contains a [Target](./puppeteer.target.md) instance. |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browseremittedevents.md b/website/versioned_docs/version-10.0.0/puppeteer.browseremittedevents.md
new file mode 100644
index 0000000000000..779d0ba75dff4
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browseremittedevents.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserEmittedEvents](./puppeteer.browseremittedevents.md)
+
+## BrowserEmittedEvents enum
+
+All the events a [browser instance](./puppeteer.browser.md) may emit.
+
+Signature:
+
+```typescript
+export declare const enum BrowserEmittedEvents
+```
+
+## Enumeration Members
+
+| Member | Value | Description |
+| --- | --- | --- |
+| Disconnected | "disconnected"
| Emitted when Puppeteer gets disconnected from the Chromium instance. This might happen because of one of the following:- Chromium is closed or crashed- The [browser.disconnect](./puppeteer.browser.disconnect.md) method was called. |
+| TargetChanged | "targetchanged"
| Emitted when the url of a target changes. Contains a [Target](./puppeteer.target.md) instance. |
+| TargetCreated | "targetcreated"
| Emitted when a target is created, for example when a new page is opened by [window.open](https://developer.mozilla.org/en-US/docs/Web/API/Window/open) or by [browser.newPage](./puppeteer.browser.newpage.md)Contains a [Target](./puppeteer.target.md) instance. |
+| TargetDestroyed | "targetdestroyed"
| Emitted when a target is destroyed, for example when a page is closed. Contains a [Target](./puppeteer.target.md) instance. |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.candownload.md b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.candownload.md
new file mode 100644
index 0000000000000..11da9ded7e791
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.candownload.md
@@ -0,0 +1,30 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserFetcher](./puppeteer.browserfetcher.md) > [canDownload](./puppeteer.browserfetcher.candownload.md)
+
+## BrowserFetcher.canDownload() method
+
+Initiates a HEAD request to check if the revision is available.
+
+Signature:
+
+```typescript
+canDownload(revision: string): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| revision | string | The revision to check availability for. |
+
+Returns:
+
+Promise<boolean>
+
+A promise that resolves to `true` if the revision could be downloaded from the host.
+
+## Remarks
+
+This method is affected by the current `product`.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.download.md b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.download.md
new file mode 100644
index 0000000000000..d026a7cfe1996
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.download.md
@@ -0,0 +1,31 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserFetcher](./puppeteer.browserfetcher.md) > [download](./puppeteer.browserfetcher.download.md)
+
+## BrowserFetcher.download() method
+
+Initiates a GET request to download the revision from the host.
+
+Signature:
+
+```typescript
+download(revision: string, progressCallback?: (x: number, y: number) => void): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| revision | string | The revision to download. |
+| progressCallback | (x: number, y: number) => void | A function that will be called with two arguments: How many bytes have been downloaded and the total number of bytes of the download. |
+
+Returns:
+
+Promise<[BrowserFetcherRevisionInfo](./puppeteer.browserfetcherrevisioninfo.md)>
+
+A promise with revision information when the revision is downloaded and extracted.
+
+## Remarks
+
+This method is affected by the current `product`.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.host.md b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.host.md
new file mode 100644
index 0000000000000..863932bf0961a
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.host.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserFetcher](./puppeteer.browserfetcher.md) > [host](./puppeteer.browserfetcher.host.md)
+
+## BrowserFetcher.host() method
+
+Signature:
+
+```typescript
+host(): string;
+```
+Returns:
+
+string
+
+The download host being used.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.localrevisions.md b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.localrevisions.md
new file mode 100644
index 0000000000000..e0a9bb93c02c4
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.localrevisions.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserFetcher](./puppeteer.browserfetcher.md) > [localRevisions](./puppeteer.browserfetcher.localrevisions.md)
+
+## BrowserFetcher.localRevisions() method
+
+Signature:
+
+```typescript
+localRevisions(): Promise;
+```
+Returns:
+
+Promise<string\[\]>
+
+A promise with a list of all revision strings (for the current `product`) available locally on disk.
+
+## Remarks
+
+This method is affected by the current `product`.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.md b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.md
new file mode 100644
index 0000000000000..0b674fc12a54d
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.md
@@ -0,0 +1,45 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserFetcher](./puppeteer.browserfetcher.md)
+
+## BrowserFetcher class
+
+BrowserFetcher can download and manage different versions of Chromium and Firefox.
+
+Signature:
+
+```typescript
+export declare class BrowserFetcher
+```
+
+## Remarks
+
+BrowserFetcher operates on revision strings that specify a precise version of Chromium, e.g. `"533271"`. Revision strings can be obtained from [omahaproxy.appspot.com](http://omahaproxy.appspot.com/). In the Firefox case, BrowserFetcher downloads Firefox Nightly and operates on version numbers such as `"75"`.
+
+The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `BrowserFetcher` class.
+
+## Example
+
+An example of using BrowserFetcher to download a specific version of Chromium and running Puppeteer against it:
+
+```js
+const browserFetcher = puppeteer.createBrowserFetcher();
+const revisionInfo = await browserFetcher.download('533271');
+const browser = await puppeteer.launch({executablePath: revisionInfo.executablePath})
+
+```
+\*\*NOTE\*\* BrowserFetcher is not designed to work concurrently with other instances of BrowserFetcher that share the same downloads directory.
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [canDownload(revision)](./puppeteer.browserfetcher.candownload.md) | | Initiates a HEAD request to check if the revision is available. |
+| [download(revision, progressCallback)](./puppeteer.browserfetcher.download.md) | | Initiates a GET request to download the revision from the host. |
+| [host()](./puppeteer.browserfetcher.host.md) | | |
+| [localRevisions()](./puppeteer.browserfetcher.localrevisions.md) | | |
+| [platform()](./puppeteer.browserfetcher.platform.md) | | |
+| [product()](./puppeteer.browserfetcher.product.md) | | |
+| [remove(revision)](./puppeteer.browserfetcher.remove.md) | | |
+| [revisionInfo(revision)](./puppeteer.browserfetcher.revisioninfo.md) | | |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.platform.md b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.platform.md
new file mode 100644
index 0000000000000..c02300923e82f
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.platform.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserFetcher](./puppeteer.browserfetcher.md) > [platform](./puppeteer.browserfetcher.platform.md)
+
+## BrowserFetcher.platform() method
+
+Signature:
+
+```typescript
+platform(): Platform;
+```
+Returns:
+
+[Platform](./puppeteer.platform.md)
+
+Returns the current `Platform`, which is one of `mac`, `linux`, `win32` or `win64`.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.product.md b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.product.md
new file mode 100644
index 0000000000000..42fe893feca33
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.product.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserFetcher](./puppeteer.browserfetcher.md) > [product](./puppeteer.browserfetcher.product.md)
+
+## BrowserFetcher.product() method
+
+Signature:
+
+```typescript
+product(): Product;
+```
+Returns:
+
+[Product](./puppeteer.product.md)
+
+Returns the current `Product`, which is one of `chrome` or `firefox`.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.remove.md b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.remove.md
new file mode 100644
index 0000000000000..9577aada68346
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.remove.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserFetcher](./puppeteer.browserfetcher.md) > [remove](./puppeteer.browserfetcher.remove.md)
+
+## BrowserFetcher.remove() method
+
+Signature:
+
+```typescript
+remove(revision: string): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| revision | string | A revision to remove for the current product
. |
+
+Returns:
+
+Promise<void>
+
+A promise that resolves when the revision has been removes or throws if the revision has not been downloaded.
+
+## Remarks
+
+This method is affected by the current `product`.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.revisioninfo.md b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.revisioninfo.md
new file mode 100644
index 0000000000000..6b1636b8ab978
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcher.revisioninfo.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserFetcher](./puppeteer.browserfetcher.md) > [revisionInfo](./puppeteer.browserfetcher.revisioninfo.md)
+
+## BrowserFetcher.revisionInfo() method
+
+Signature:
+
+```typescript
+revisionInfo(revision: string): BrowserFetcherRevisionInfo;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| revision | string | The revision to get info for. |
+
+Returns:
+
+[BrowserFetcherRevisionInfo](./puppeteer.browserfetcherrevisioninfo.md)
+
+The revision info for the given revision.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserfetcheroptions.host.md b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcheroptions.host.md
new file mode 100644
index 0000000000000..c30723c16dece
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcheroptions.host.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserFetcherOptions](./puppeteer.browserfetcheroptions.md) > [host](./puppeteer.browserfetcheroptions.host.md)
+
+## BrowserFetcherOptions.host property
+
+Signature:
+
+```typescript
+host?: string;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserfetcheroptions.md b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcheroptions.md
new file mode 100644
index 0000000000000..16d69d6482295
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcheroptions.md
@@ -0,0 +1,22 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserFetcherOptions](./puppeteer.browserfetcheroptions.md)
+
+## BrowserFetcherOptions interface
+
+
+Signature:
+
+```typescript
+export interface BrowserFetcherOptions
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [host?](./puppeteer.browserfetcheroptions.host.md) | string | (Optional) |
+| [path?](./puppeteer.browserfetcheroptions.path.md) | string | (Optional) |
+| [platform?](./puppeteer.browserfetcheroptions.platform.md) | [Platform](./puppeteer.platform.md) | (Optional) |
+| [product?](./puppeteer.browserfetcheroptions.product.md) | string | (Optional) |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserfetcheroptions.path.md b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcheroptions.path.md
new file mode 100644
index 0000000000000..e6a4bcdb265a3
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcheroptions.path.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserFetcherOptions](./puppeteer.browserfetcheroptions.md) > [path](./puppeteer.browserfetcheroptions.path.md)
+
+## BrowserFetcherOptions.path property
+
+Signature:
+
+```typescript
+path?: string;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserfetcheroptions.platform.md b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcheroptions.platform.md
new file mode 100644
index 0000000000000..b37d0ead489ff
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcheroptions.platform.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserFetcherOptions](./puppeteer.browserfetcheroptions.md) > [platform](./puppeteer.browserfetcheroptions.platform.md)
+
+## BrowserFetcherOptions.platform property
+
+Signature:
+
+```typescript
+platform?: Platform;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserfetcheroptions.product.md b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcheroptions.product.md
new file mode 100644
index 0000000000000..c5caa876ac867
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcheroptions.product.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserFetcherOptions](./puppeteer.browserfetcheroptions.md) > [product](./puppeteer.browserfetcheroptions.product.md)
+
+## BrowserFetcherOptions.product property
+
+Signature:
+
+```typescript
+product?: string;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserfetcherrevisioninfo.executablepath.md b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcherrevisioninfo.executablepath.md
new file mode 100644
index 0000000000000..22e8fbfb6f662
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcherrevisioninfo.executablepath.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserFetcherRevisionInfo](./puppeteer.browserfetcherrevisioninfo.md) > [executablePath](./puppeteer.browserfetcherrevisioninfo.executablepath.md)
+
+## BrowserFetcherRevisionInfo.executablePath property
+
+Signature:
+
+```typescript
+executablePath: string;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserfetcherrevisioninfo.folderpath.md b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcherrevisioninfo.folderpath.md
new file mode 100644
index 0000000000000..bc31e043e38e4
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcherrevisioninfo.folderpath.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserFetcherRevisionInfo](./puppeteer.browserfetcherrevisioninfo.md) > [folderPath](./puppeteer.browserfetcherrevisioninfo.folderpath.md)
+
+## BrowserFetcherRevisionInfo.folderPath property
+
+Signature:
+
+```typescript
+folderPath: string;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserfetcherrevisioninfo.local.md b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcherrevisioninfo.local.md
new file mode 100644
index 0000000000000..f35fc71741283
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcherrevisioninfo.local.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserFetcherRevisionInfo](./puppeteer.browserfetcherrevisioninfo.md) > [local](./puppeteer.browserfetcherrevisioninfo.local.md)
+
+## BrowserFetcherRevisionInfo.local property
+
+Signature:
+
+```typescript
+local: boolean;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserfetcherrevisioninfo.md b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcherrevisioninfo.md
new file mode 100644
index 0000000000000..e7dabf4dc05dc
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcherrevisioninfo.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserFetcherRevisionInfo](./puppeteer.browserfetcherrevisioninfo.md)
+
+## BrowserFetcherRevisionInfo interface
+
+
+Signature:
+
+```typescript
+export interface BrowserFetcherRevisionInfo
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [executablePath](./puppeteer.browserfetcherrevisioninfo.executablepath.md) | string | |
+| [folderPath](./puppeteer.browserfetcherrevisioninfo.folderpath.md) | string | |
+| [local](./puppeteer.browserfetcherrevisioninfo.local.md) | boolean | |
+| [product](./puppeteer.browserfetcherrevisioninfo.product.md) | string | |
+| [revision](./puppeteer.browserfetcherrevisioninfo.revision.md) | string | |
+| [url](./puppeteer.browserfetcherrevisioninfo.url.md) | string | |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserfetcherrevisioninfo.product.md b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcherrevisioninfo.product.md
new file mode 100644
index 0000000000000..99bdca573e00a
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcherrevisioninfo.product.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserFetcherRevisionInfo](./puppeteer.browserfetcherrevisioninfo.md) > [product](./puppeteer.browserfetcherrevisioninfo.product.md)
+
+## BrowserFetcherRevisionInfo.product property
+
+Signature:
+
+```typescript
+product: string;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserfetcherrevisioninfo.revision.md b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcherrevisioninfo.revision.md
new file mode 100644
index 0000000000000..e084a3327e3b4
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcherrevisioninfo.revision.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserFetcherRevisionInfo](./puppeteer.browserfetcherrevisioninfo.md) > [revision](./puppeteer.browserfetcherrevisioninfo.revision.md)
+
+## BrowserFetcherRevisionInfo.revision property
+
+Signature:
+
+```typescript
+revision: string;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserfetcherrevisioninfo.url.md b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcherrevisioninfo.url.md
new file mode 100644
index 0000000000000..eee943e9897d7
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserfetcherrevisioninfo.url.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserFetcherRevisionInfo](./puppeteer.browserfetcherrevisioninfo.md) > [url](./puppeteer.browserfetcherrevisioninfo.url.md)
+
+## BrowserFetcherRevisionInfo.url property
+
+Signature:
+
+```typescript
+url: string;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserlaunchargumentoptions.args.md b/website/versioned_docs/version-10.0.0/puppeteer.browserlaunchargumentoptions.args.md
new file mode 100644
index 0000000000000..7fcc88938afd5
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserlaunchargumentoptions.args.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserLaunchArgumentOptions](./puppeteer.browserlaunchargumentoptions.md) > [args](./puppeteer.browserlaunchargumentoptions.args.md)
+
+## BrowserLaunchArgumentOptions.args property
+
+Additional command line arguments to pass to the browser instance.
+
+Signature:
+
+```typescript
+args?: string[];
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserlaunchargumentoptions.devtools.md b/website/versioned_docs/version-10.0.0/puppeteer.browserlaunchargumentoptions.devtools.md
new file mode 100644
index 0000000000000..0bc2fbf8018e0
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserlaunchargumentoptions.devtools.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserLaunchArgumentOptions](./puppeteer.browserlaunchargumentoptions.md) > [devtools](./puppeteer.browserlaunchargumentoptions.devtools.md)
+
+## BrowserLaunchArgumentOptions.devtools property
+
+Whether to auto-open a DevTools panel for each tab. If this is set to `true`, then `headless` will be set to `false` automatically.
+
+Signature:
+
+```typescript
+devtools?: boolean;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserlaunchargumentoptions.headless.md b/website/versioned_docs/version-10.0.0/puppeteer.browserlaunchargumentoptions.headless.md
new file mode 100644
index 0000000000000..cc881e3ce10bf
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserlaunchargumentoptions.headless.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserLaunchArgumentOptions](./puppeteer.browserlaunchargumentoptions.md) > [headless](./puppeteer.browserlaunchargumentoptions.headless.md)
+
+## BrowserLaunchArgumentOptions.headless property
+
+Whether to run the browser in headless mode.
+
+Signature:
+
+```typescript
+headless?: boolean;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserlaunchargumentoptions.md b/website/versioned_docs/version-10.0.0/puppeteer.browserlaunchargumentoptions.md
new file mode 100644
index 0000000000000..ec48b01c55c27
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserlaunchargumentoptions.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserLaunchArgumentOptions](./puppeteer.browserlaunchargumentoptions.md)
+
+## BrowserLaunchArgumentOptions interface
+
+Launcher options that only apply to Chrome.
+
+Signature:
+
+```typescript
+export interface BrowserLaunchArgumentOptions
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [args?](./puppeteer.browserlaunchargumentoptions.args.md) | string\[\] | (Optional) Additional command line arguments to pass to the browser instance. |
+| [devtools?](./puppeteer.browserlaunchargumentoptions.devtools.md) | boolean | (Optional) Whether to auto-open a DevTools panel for each tab. If this is set to true
, then headless
will be set to false
automatically. |
+| [headless?](./puppeteer.browserlaunchargumentoptions.headless.md) | boolean | (Optional) Whether to run the browser in headless mode. |
+| [userDataDir?](./puppeteer.browserlaunchargumentoptions.userdatadir.md) | string | (Optional) Path to a user data directory. [see the Chromium docs](https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md) for more info. |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.browserlaunchargumentoptions.userdatadir.md b/website/versioned_docs/version-10.0.0/puppeteer.browserlaunchargumentoptions.userdatadir.md
new file mode 100644
index 0000000000000..03badd8a30a5c
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.browserlaunchargumentoptions.userdatadir.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [BrowserLaunchArgumentOptions](./puppeteer.browserlaunchargumentoptions.md) > [userDataDir](./puppeteer.browserlaunchargumentoptions.userdatadir.md)
+
+## BrowserLaunchArgumentOptions.userDataDir property
+
+Path to a user data directory. [see the Chromium docs](https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md) for more info.
+
+Signature:
+
+```typescript
+userDataDir?: string;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.cdpsession.connection.md b/website/versioned_docs/version-10.0.0/puppeteer.cdpsession.connection.md
new file mode 100644
index 0000000000000..e4ca749950ade
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.cdpsession.connection.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CDPSession](./puppeteer.cdpsession.md) > [connection](./puppeteer.cdpsession.connection.md)
+
+## CDPSession.connection() method
+
+Signature:
+
+```typescript
+connection(): Connection;
+```
+Returns:
+
+[Connection](./puppeteer.connection.md)
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.cdpsession.detach.md b/website/versioned_docs/version-10.0.0/puppeteer.cdpsession.detach.md
new file mode 100644
index 0000000000000..b90f2c6210b0c
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.cdpsession.detach.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CDPSession](./puppeteer.cdpsession.md) > [detach](./puppeteer.cdpsession.detach.md)
+
+## CDPSession.detach() method
+
+Detaches the cdpSession from the target. Once detached, the cdpSession object won't emit any events and can't be used to send messages.
+
+Signature:
+
+```typescript
+detach(): Promise;
+```
+Returns:
+
+Promise<void>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.cdpsession.md b/website/versioned_docs/version-10.0.0/puppeteer.cdpsession.md
new file mode 100644
index 0000000000000..4bac2335af9a8
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.cdpsession.md
@@ -0,0 +1,46 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CDPSession](./puppeteer.cdpsession.md)
+
+## CDPSession class
+
+The `CDPSession` instances are used to talk raw Chrome Devtools Protocol.
+
+Signature:
+
+```typescript
+export declare class CDPSession extends EventEmitter
+```
+Extends: [EventEmitter](./puppeteer.eventemitter.md)
+
+## Remarks
+
+Protocol methods can be called with [CDPSession.send()](./puppeteer.cdpsession.send.md) method and protocol events can be subscribed to with `CDPSession.on` method.
+
+Useful links: [DevTools Protocol Viewer](https://chromedevtools.github.io/devtools-protocol/) and [Getting Started with DevTools Protocol](https://github.com/aslushnikov/getting-started-with-cdp/blob/master/README.md).
+
+The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `CDPSession` class.
+
+## Example
+
+
+```js
+const client = await page.target().createCDPSession();
+await client.send('Animation.enable');
+client.on('Animation.animationCreated', () => console.log('Animation created!'));
+const response = await client.send('Animation.getPlaybackRate');
+console.log('playback rate is ' + response.playbackRate);
+await client.send('Animation.setPlaybackRate', {
+ playbackRate: response.playbackRate / 2
+});
+
+```
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [connection()](./puppeteer.cdpsession.connection.md) | | |
+| [detach()](./puppeteer.cdpsession.detach.md) | | Detaches the cdpSession from the target. Once detached, the cdpSession object won't emit any events and can't be used to send messages. |
+| [send(method, paramArgs)](./puppeteer.cdpsession.send.md) | | |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.cdpsession.send.md b/website/versioned_docs/version-10.0.0/puppeteer.cdpsession.send.md
new file mode 100644
index 0000000000000..317d01c999368
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.cdpsession.send.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CDPSession](./puppeteer.cdpsession.md) > [send](./puppeteer.cdpsession.send.md)
+
+## CDPSession.send() method
+
+Signature:
+
+```typescript
+send(method: T, ...paramArgs: ProtocolMapping.Commands[T]['paramsType']): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| method | T | |
+| paramArgs | ProtocolMapping.Commands\[T\]\['paramsType'\] | |
+
+Returns:
+
+Promise<ProtocolMapping.Commands\[T\]\['returnType'\]>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.cdpsessiononmessageobject.error.md b/website/versioned_docs/version-10.0.0/puppeteer.cdpsessiononmessageobject.error.md
new file mode 100644
index 0000000000000..d4583855267c4
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.cdpsessiononmessageobject.error.md
@@ -0,0 +1,14 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CDPSessionOnMessageObject](./puppeteer.cdpsessiononmessageobject.md) > [error](./puppeteer.cdpsessiononmessageobject.error.md)
+
+## CDPSessionOnMessageObject.error property
+
+Signature:
+
+```typescript
+error: {
+ message: string;
+ data: any;
+ };
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.cdpsessiononmessageobject.id.md b/website/versioned_docs/version-10.0.0/puppeteer.cdpsessiononmessageobject.id.md
new file mode 100644
index 0000000000000..3e8d0a561e2eb
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.cdpsessiononmessageobject.id.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CDPSessionOnMessageObject](./puppeteer.cdpsessiononmessageobject.md) > [id](./puppeteer.cdpsessiononmessageobject.id.md)
+
+## CDPSessionOnMessageObject.id property
+
+Signature:
+
+```typescript
+id?: number;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.cdpsessiononmessageobject.md b/website/versioned_docs/version-10.0.0/puppeteer.cdpsessiononmessageobject.md
new file mode 100644
index 0000000000000..d7354b824901b
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.cdpsessiononmessageobject.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CDPSessionOnMessageObject](./puppeteer.cdpsessiononmessageobject.md)
+
+## CDPSessionOnMessageObject interface
+
+
+Signature:
+
+```typescript
+export interface CDPSessionOnMessageObject
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [error](./puppeteer.cdpsessiononmessageobject.error.md) | { message: string; data: any; } | |
+| [id?](./puppeteer.cdpsessiononmessageobject.id.md) | number | (Optional) |
+| [method](./puppeteer.cdpsessiononmessageobject.method.md) | string | |
+| [params](./puppeteer.cdpsessiononmessageobject.params.md) | Record<string, unknown> | |
+| [result?](./puppeteer.cdpsessiononmessageobject.result.md) | any | (Optional) |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.cdpsessiononmessageobject.method.md b/website/versioned_docs/version-10.0.0/puppeteer.cdpsessiononmessageobject.method.md
new file mode 100644
index 0000000000000..c5217865b5699
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.cdpsessiononmessageobject.method.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CDPSessionOnMessageObject](./puppeteer.cdpsessiononmessageobject.md) > [method](./puppeteer.cdpsessiononmessageobject.method.md)
+
+## CDPSessionOnMessageObject.method property
+
+Signature:
+
+```typescript
+method: string;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.cdpsessiononmessageobject.params.md b/website/versioned_docs/version-10.0.0/puppeteer.cdpsessiononmessageobject.params.md
new file mode 100644
index 0000000000000..df6d129a94edd
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.cdpsessiononmessageobject.params.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CDPSessionOnMessageObject](./puppeteer.cdpsessiononmessageobject.md) > [params](./puppeteer.cdpsessiononmessageobject.params.md)
+
+## CDPSessionOnMessageObject.params property
+
+Signature:
+
+```typescript
+params: Record;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.cdpsessiononmessageobject.result.md b/website/versioned_docs/version-10.0.0/puppeteer.cdpsessiononmessageobject.result.md
new file mode 100644
index 0000000000000..c53f73437394f
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.cdpsessiononmessageobject.result.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CDPSessionOnMessageObject](./puppeteer.cdpsessiononmessageobject.md) > [result](./puppeteer.cdpsessiononmessageobject.result.md)
+
+## CDPSessionOnMessageObject.result property
+
+Signature:
+
+```typescript
+result?: any;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.chromereleasechannel.md b/website/versioned_docs/version-10.0.0/puppeteer.chromereleasechannel.md
new file mode 100644
index 0000000000000..c4496235bb470
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.chromereleasechannel.md
@@ -0,0 +1,12 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ChromeReleaseChannel](./puppeteer.chromereleasechannel.md)
+
+## ChromeReleaseChannel type
+
+
+Signature:
+
+```typescript
+export declare type ChromeReleaseChannel = 'chrome' | 'chrome-beta' | 'chrome-canary' | 'chrome-dev';
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.clearcustomqueryhandlers.md b/website/versioned_docs/version-10.0.0/puppeteer.clearcustomqueryhandlers.md
new file mode 100644
index 0000000000000..f6bb8422e4ddd
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.clearcustomqueryhandlers.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [clearCustomQueryHandlers](./puppeteer.clearcustomqueryhandlers.md)
+
+## clearCustomQueryHandlers() function
+
+Clears all registered handlers.
+
+Signature:
+
+```typescript
+export declare function clearCustomQueryHandlers(): void;
+```
+Returns:
+
+void
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.clickoptions.button.md b/website/versioned_docs/version-10.0.0/puppeteer.clickoptions.button.md
new file mode 100644
index 0000000000000..2b9d0629954e0
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.clickoptions.button.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ClickOptions](./puppeteer.clickoptions.md) > [button](./puppeteer.clickoptions.button.md)
+
+## ClickOptions.button property
+
+Signature:
+
+```typescript
+button?: 'left' | 'right' | 'middle';
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.clickoptions.clickcount.md b/website/versioned_docs/version-10.0.0/puppeteer.clickoptions.clickcount.md
new file mode 100644
index 0000000000000..942570833d429
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.clickoptions.clickcount.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ClickOptions](./puppeteer.clickoptions.md) > [clickCount](./puppeteer.clickoptions.clickcount.md)
+
+## ClickOptions.clickCount property
+
+Signature:
+
+```typescript
+clickCount?: number;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.clickoptions.delay.md b/website/versioned_docs/version-10.0.0/puppeteer.clickoptions.delay.md
new file mode 100644
index 0000000000000..c96b46a1a0954
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.clickoptions.delay.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ClickOptions](./puppeteer.clickoptions.md) > [delay](./puppeteer.clickoptions.delay.md)
+
+## ClickOptions.delay property
+
+Time to wait between `mousedown` and `mouseup` in milliseconds.
+
+Signature:
+
+```typescript
+delay?: number;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.clickoptions.md b/website/versioned_docs/version-10.0.0/puppeteer.clickoptions.md
new file mode 100644
index 0000000000000..2ed03e2f64f0e
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.clickoptions.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ClickOptions](./puppeteer.clickoptions.md)
+
+## ClickOptions interface
+
+
+Signature:
+
+```typescript
+export interface ClickOptions
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [button?](./puppeteer.clickoptions.button.md) | 'left' \| 'right' \| 'middle' | (Optional) |
+| [clickCount?](./puppeteer.clickoptions.clickcount.md) | number | (Optional) |
+| [delay?](./puppeteer.clickoptions.delay.md) | number | (Optional) Time to wait between mousedown
and mouseup
in milliseconds. |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.addlistener.md b/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.addlistener.md
new file mode 100644
index 0000000000000..60f0fe3d26b00
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.addlistener.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CommonEventEmitter](./puppeteer.commoneventemitter.md) > [addListener](./puppeteer.commoneventemitter.addlistener.md)
+
+## CommonEventEmitter.addListener() method
+
+Signature:
+
+```typescript
+addListener(event: EventType, handler: Handler): CommonEventEmitter;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| event | [EventType](./puppeteer.eventtype.md) | |
+| handler | [Handler](./puppeteer.handler.md) | |
+
+Returns:
+
+[CommonEventEmitter](./puppeteer.commoneventemitter.md)
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.emit.md b/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.emit.md
new file mode 100644
index 0000000000000..75e72f375d9b9
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.emit.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CommonEventEmitter](./puppeteer.commoneventemitter.md) > [emit](./puppeteer.commoneventemitter.emit.md)
+
+## CommonEventEmitter.emit() method
+
+Signature:
+
+```typescript
+emit(event: EventType, eventData?: unknown): boolean;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| event | [EventType](./puppeteer.eventtype.md) | |
+| eventData | unknown | |
+
+Returns:
+
+boolean
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.listenercount.md b/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.listenercount.md
new file mode 100644
index 0000000000000..2e68d936cdc46
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.listenercount.md
@@ -0,0 +1,22 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CommonEventEmitter](./puppeteer.commoneventemitter.md) > [listenerCount](./puppeteer.commoneventemitter.listenercount.md)
+
+## CommonEventEmitter.listenerCount() method
+
+Signature:
+
+```typescript
+listenerCount(event: string): number;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| event | string | |
+
+Returns:
+
+number
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.md b/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.md
new file mode 100644
index 0000000000000..1ec59b257addb
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.md
@@ -0,0 +1,26 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CommonEventEmitter](./puppeteer.commoneventemitter.md)
+
+## CommonEventEmitter interface
+
+
+Signature:
+
+```typescript
+export interface CommonEventEmitter
+```
+
+## Methods
+
+| Method | Description |
+| --- | --- |
+| [addListener(event, handler)](./puppeteer.commoneventemitter.addlistener.md) | |
+| [emit(event, eventData)](./puppeteer.commoneventemitter.emit.md) | |
+| [listenerCount(event)](./puppeteer.commoneventemitter.listenercount.md) | |
+| [off(event, handler)](./puppeteer.commoneventemitter.off.md) | |
+| [on(event, handler)](./puppeteer.commoneventemitter.on.md) | |
+| [once(event, handler)](./puppeteer.commoneventemitter.once.md) | |
+| [removeAllListeners(event)](./puppeteer.commoneventemitter.removealllisteners.md) | |
+| [removeListener(event, handler)](./puppeteer.commoneventemitter.removelistener.md) | |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.off.md b/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.off.md
new file mode 100644
index 0000000000000..bf8f9437ccca5
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.off.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CommonEventEmitter](./puppeteer.commoneventemitter.md) > [off](./puppeteer.commoneventemitter.off.md)
+
+## CommonEventEmitter.off() method
+
+Signature:
+
+```typescript
+off(event: EventType, handler: Handler): CommonEventEmitter;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| event | [EventType](./puppeteer.eventtype.md) | |
+| handler | [Handler](./puppeteer.handler.md) | |
+
+Returns:
+
+[CommonEventEmitter](./puppeteer.commoneventemitter.md)
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.on.md b/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.on.md
new file mode 100644
index 0000000000000..15193a28cf0cc
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.on.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CommonEventEmitter](./puppeteer.commoneventemitter.md) > [on](./puppeteer.commoneventemitter.on.md)
+
+## CommonEventEmitter.on() method
+
+Signature:
+
+```typescript
+on(event: EventType, handler: Handler): CommonEventEmitter;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| event | [EventType](./puppeteer.eventtype.md) | |
+| handler | [Handler](./puppeteer.handler.md) | |
+
+Returns:
+
+[CommonEventEmitter](./puppeteer.commoneventemitter.md)
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.once.md b/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.once.md
new file mode 100644
index 0000000000000..9066bd6aa909b
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.once.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CommonEventEmitter](./puppeteer.commoneventemitter.md) > [once](./puppeteer.commoneventemitter.once.md)
+
+## CommonEventEmitter.once() method
+
+Signature:
+
+```typescript
+once(event: EventType, handler: Handler): CommonEventEmitter;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| event | [EventType](./puppeteer.eventtype.md) | |
+| handler | [Handler](./puppeteer.handler.md) | |
+
+Returns:
+
+[CommonEventEmitter](./puppeteer.commoneventemitter.md)
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.removealllisteners.md b/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.removealllisteners.md
new file mode 100644
index 0000000000000..a52938a09e49b
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.removealllisteners.md
@@ -0,0 +1,22 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CommonEventEmitter](./puppeteer.commoneventemitter.md) > [removeAllListeners](./puppeteer.commoneventemitter.removealllisteners.md)
+
+## CommonEventEmitter.removeAllListeners() method
+
+Signature:
+
+```typescript
+removeAllListeners(event?: EventType): CommonEventEmitter;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| event | [EventType](./puppeteer.eventtype.md) | |
+
+Returns:
+
+[CommonEventEmitter](./puppeteer.commoneventemitter.md)
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.removelistener.md b/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.removelistener.md
new file mode 100644
index 0000000000000..9053478c28f73
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.commoneventemitter.removelistener.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CommonEventEmitter](./puppeteer.commoneventemitter.md) > [removeListener](./puppeteer.commoneventemitter.removelistener.md)
+
+## CommonEventEmitter.removeListener() method
+
+Signature:
+
+```typescript
+removeListener(event: EventType, handler: Handler): CommonEventEmitter;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| event | [EventType](./puppeteer.eventtype.md) | |
+| handler | [Handler](./puppeteer.handler.md) | |
+
+Returns:
+
+[CommonEventEmitter](./puppeteer.commoneventemitter.md)
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connect.md b/website/versioned_docs/version-10.0.0/puppeteer.connect.md
new file mode 100644
index 0000000000000..57b13bca920f7
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connect.md
@@ -0,0 +1,29 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [connect](./puppeteer.connect.md)
+
+## connect() function
+
+This method attaches Puppeteer to an existing browser instance.
+
+Signature:
+
+```typescript
+export declare function connect(options: ConnectOptions): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| options | [ConnectOptions](./puppeteer.connectoptions.md) | Set of configurable options to set on the browser. |
+
+Returns:
+
+Promise<[Browser](./puppeteer.browser.md)>
+
+Promise which resolves to browser instance.
+
+## Remarks
+
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connection._callbacks.md b/website/versioned_docs/version-10.0.0/puppeteer.connection._callbacks.md
new file mode 100644
index 0000000000000..69922d9e55b04
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connection._callbacks.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Connection](./puppeteer.connection.md) > [\_callbacks](./puppeteer.connection._callbacks.md)
+
+## Connection.\_callbacks property
+
+Signature:
+
+```typescript
+_callbacks: Map;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connection._closed.md b/website/versioned_docs/version-10.0.0/puppeteer.connection._closed.md
new file mode 100644
index 0000000000000..7c5aa0625ff71
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connection._closed.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Connection](./puppeteer.connection.md) > [\_closed](./puppeteer.connection._closed.md)
+
+## Connection.\_closed property
+
+Signature:
+
+```typescript
+_closed: boolean;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connection._constructor_.md b/website/versioned_docs/version-10.0.0/puppeteer.connection._constructor_.md
new file mode 100644
index 0000000000000..b5539c1239daa
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connection._constructor_.md
@@ -0,0 +1,22 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Connection](./puppeteer.connection.md) > [(constructor)](./puppeteer.connection._constructor_.md)
+
+## Connection.(constructor)
+
+Constructs a new instance of the `Connection` class
+
+Signature:
+
+```typescript
+constructor(url: string, transport: ConnectionTransport, delay?: number);
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| url | string | |
+| transport | [ConnectionTransport](./puppeteer.connectiontransport.md) | |
+| delay | number | |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connection._delay.md b/website/versioned_docs/version-10.0.0/puppeteer.connection._delay.md
new file mode 100644
index 0000000000000..14839d6208248
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connection._delay.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Connection](./puppeteer.connection.md) > [\_delay](./puppeteer.connection._delay.md)
+
+## Connection.\_delay property
+
+Signature:
+
+```typescript
+_delay: number;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connection._lastid.md b/website/versioned_docs/version-10.0.0/puppeteer.connection._lastid.md
new file mode 100644
index 0000000000000..66c8641b67b89
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connection._lastid.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Connection](./puppeteer.connection.md) > [\_lastId](./puppeteer.connection._lastid.md)
+
+## Connection.\_lastId property
+
+Signature:
+
+```typescript
+_lastId: number;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connection._onclose.md b/website/versioned_docs/version-10.0.0/puppeteer.connection._onclose.md
new file mode 100644
index 0000000000000..31d6153ec2859
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connection._onclose.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Connection](./puppeteer.connection.md) > [\_onClose](./puppeteer.connection._onclose.md)
+
+## Connection.\_onClose() method
+
+Signature:
+
+```typescript
+_onClose(): void;
+```
+Returns:
+
+void
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connection._onmessage.md b/website/versioned_docs/version-10.0.0/puppeteer.connection._onmessage.md
new file mode 100644
index 0000000000000..7dca08b4da3d3
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connection._onmessage.md
@@ -0,0 +1,22 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Connection](./puppeteer.connection.md) > [\_onMessage](./puppeteer.connection._onmessage.md)
+
+## Connection.\_onMessage() method
+
+Signature:
+
+```typescript
+_onMessage(message: string): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| message | string | |
+
+Returns:
+
+Promise<void>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connection._rawsend.md b/website/versioned_docs/version-10.0.0/puppeteer.connection._rawsend.md
new file mode 100644
index 0000000000000..6a83d3104b996
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connection._rawsend.md
@@ -0,0 +1,22 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Connection](./puppeteer.connection.md) > [\_rawSend](./puppeteer.connection._rawsend.md)
+
+## Connection.\_rawSend() method
+
+Signature:
+
+```typescript
+_rawSend(message: Record): number;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| message | Record<string, unknown> | |
+
+Returns:
+
+number
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connection._sessions.md b/website/versioned_docs/version-10.0.0/puppeteer.connection._sessions.md
new file mode 100644
index 0000000000000..de2d752218d8f
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connection._sessions.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Connection](./puppeteer.connection.md) > [\_sessions](./puppeteer.connection._sessions.md)
+
+## Connection.\_sessions property
+
+Signature:
+
+```typescript
+_sessions: Map;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connection._transport.md b/website/versioned_docs/version-10.0.0/puppeteer.connection._transport.md
new file mode 100644
index 0000000000000..c8692e633ea9a
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connection._transport.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Connection](./puppeteer.connection.md) > [\_transport](./puppeteer.connection._transport.md)
+
+## Connection.\_transport property
+
+Signature:
+
+```typescript
+_transport: ConnectionTransport;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connection._url.md b/website/versioned_docs/version-10.0.0/puppeteer.connection._url.md
new file mode 100644
index 0000000000000..fc5bce5806268
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connection._url.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Connection](./puppeteer.connection.md) > [\_url](./puppeteer.connection._url.md)
+
+## Connection.\_url property
+
+Signature:
+
+```typescript
+_url: string;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connection.createsession.md b/website/versioned_docs/version-10.0.0/puppeteer.connection.createsession.md
new file mode 100644
index 0000000000000..19ccedbe794a4
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connection.createsession.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Connection](./puppeteer.connection.md) > [createSession](./puppeteer.connection.createsession.md)
+
+## Connection.createSession() method
+
+Signature:
+
+```typescript
+createSession(targetInfo: Protocol.Target.TargetInfo): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| targetInfo | Protocol.Target.TargetInfo | The target info |
+
+Returns:
+
+Promise<[CDPSession](./puppeteer.cdpsession.md)>
+
+The CDP session that is created
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connection.dispose.md b/website/versioned_docs/version-10.0.0/puppeteer.connection.dispose.md
new file mode 100644
index 0000000000000..07d8e9b585a8a
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connection.dispose.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Connection](./puppeteer.connection.md) > [dispose](./puppeteer.connection.dispose.md)
+
+## Connection.dispose() method
+
+Signature:
+
+```typescript
+dispose(): void;
+```
+Returns:
+
+void
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connection.fromsession.md b/website/versioned_docs/version-10.0.0/puppeteer.connection.fromsession.md
new file mode 100644
index 0000000000000..8663934a5fd47
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connection.fromsession.md
@@ -0,0 +1,22 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Connection](./puppeteer.connection.md) > [fromSession](./puppeteer.connection.fromsession.md)
+
+## Connection.fromSession() method
+
+Signature:
+
+```typescript
+static fromSession(session: CDPSession): Connection;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| session | [CDPSession](./puppeteer.cdpsession.md) | |
+
+Returns:
+
+[Connection](./puppeteer.connection.md)
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connection.md b/website/versioned_docs/version-10.0.0/puppeteer.connection.md
new file mode 100644
index 0000000000000..385faea9236df
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connection.md
@@ -0,0 +1,46 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Connection](./puppeteer.connection.md)
+
+## Connection class
+
+
+Signature:
+
+```typescript
+export declare class Connection extends EventEmitter
+```
+Extends: [EventEmitter](./puppeteer.eventemitter.md)
+
+## Constructors
+
+| Constructor | Modifiers | Description |
+| --- | --- | --- |
+| [(constructor)(url, transport, delay)](./puppeteer.connection._constructor_.md) | | Constructs a new instance of the Connection
class |
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [\_callbacks](./puppeteer.connection._callbacks.md) | | Map<number, [ConnectionCallback](./puppeteer.connectioncallback.md)> | |
+| [\_closed](./puppeteer.connection._closed.md) | | boolean | |
+| [\_delay](./puppeteer.connection._delay.md) | | number | |
+| [\_lastId](./puppeteer.connection._lastid.md) | | number | |
+| [\_sessions](./puppeteer.connection._sessions.md) | | Map<string, [CDPSession](./puppeteer.cdpsession.md)> | |
+| [\_transport](./puppeteer.connection._transport.md) | | [ConnectionTransport](./puppeteer.connectiontransport.md) | |
+| [\_url](./puppeteer.connection._url.md) | | string | |
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [\_onClose()](./puppeteer.connection._onclose.md) | | |
+| [\_onMessage(message)](./puppeteer.connection._onmessage.md) | | |
+| [\_rawSend(message)](./puppeteer.connection._rawsend.md) | | |
+| [createSession(targetInfo)](./puppeteer.connection.createsession.md) | | |
+| [dispose()](./puppeteer.connection.dispose.md) | | |
+| [fromSession(session)](./puppeteer.connection.fromsession.md) | static
| |
+| [send(method, paramArgs)](./puppeteer.connection.send.md) | | |
+| [session(sessionId)](./puppeteer.connection.session.md) | | |
+| [url()](./puppeteer.connection.url.md) | | |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connection.send.md b/website/versioned_docs/version-10.0.0/puppeteer.connection.send.md
new file mode 100644
index 0000000000000..b5c4185416e10
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connection.send.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Connection](./puppeteer.connection.md) > [send](./puppeteer.connection.send.md)
+
+## Connection.send() method
+
+Signature:
+
+```typescript
+send(method: T, ...paramArgs: ProtocolMapping.Commands[T]['paramsType']): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| method | T | |
+| paramArgs | ProtocolMapping.Commands\[T\]\['paramsType'\] | |
+
+Returns:
+
+Promise<ProtocolMapping.Commands\[T\]\['returnType'\]>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connection.session.md b/website/versioned_docs/version-10.0.0/puppeteer.connection.session.md
new file mode 100644
index 0000000000000..901679b7aab4f
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connection.session.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Connection](./puppeteer.connection.md) > [session](./puppeteer.connection.session.md)
+
+## Connection.session() method
+
+Signature:
+
+```typescript
+session(sessionId: string): CDPSession | null;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| sessionId | string | The session id |
+
+Returns:
+
+[CDPSession](./puppeteer.cdpsession.md) \| null
+
+The current CDP session if it exists
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connection.url.md b/website/versioned_docs/version-10.0.0/puppeteer.connection.url.md
new file mode 100644
index 0000000000000..90bd6c6155ac8
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connection.url.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Connection](./puppeteer.connection.md) > [url](./puppeteer.connection.url.md)
+
+## Connection.url() method
+
+Signature:
+
+```typescript
+url(): string;
+```
+Returns:
+
+string
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connectioncallback.error.md b/website/versioned_docs/version-10.0.0/puppeteer.connectioncallback.error.md
new file mode 100644
index 0000000000000..e5488d211fcbc
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connectioncallback.error.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConnectionCallback](./puppeteer.connectioncallback.md) > [error](./puppeteer.connectioncallback.error.md)
+
+## ConnectionCallback.error property
+
+Signature:
+
+```typescript
+error: Error;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connectioncallback.md b/website/versioned_docs/version-10.0.0/puppeteer.connectioncallback.md
new file mode 100644
index 0000000000000..ca5120494456e
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connectioncallback.md
@@ -0,0 +1,22 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConnectionCallback](./puppeteer.connectioncallback.md)
+
+## ConnectionCallback interface
+
+
+Signature:
+
+```typescript
+export interface ConnectionCallback
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [error](./puppeteer.connectioncallback.error.md) | Error | |
+| [method](./puppeteer.connectioncallback.method.md) | string | |
+| [reject](./puppeteer.connectioncallback.reject.md) | Function | |
+| [resolve](./puppeteer.connectioncallback.resolve.md) | Function | |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connectioncallback.method.md b/website/versioned_docs/version-10.0.0/puppeteer.connectioncallback.method.md
new file mode 100644
index 0000000000000..1cb9db85245df
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connectioncallback.method.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConnectionCallback](./puppeteer.connectioncallback.md) > [method](./puppeteer.connectioncallback.method.md)
+
+## ConnectionCallback.method property
+
+Signature:
+
+```typescript
+method: string;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connectioncallback.reject.md b/website/versioned_docs/version-10.0.0/puppeteer.connectioncallback.reject.md
new file mode 100644
index 0000000000000..2810ebcbe28a1
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connectioncallback.reject.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConnectionCallback](./puppeteer.connectioncallback.md) > [reject](./puppeteer.connectioncallback.reject.md)
+
+## ConnectionCallback.reject property
+
+Signature:
+
+```typescript
+reject: Function;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connectioncallback.resolve.md b/website/versioned_docs/version-10.0.0/puppeteer.connectioncallback.resolve.md
new file mode 100644
index 0000000000000..41dd33698e9ed
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connectioncallback.resolve.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConnectionCallback](./puppeteer.connectioncallback.md) > [resolve](./puppeteer.connectioncallback.resolve.md)
+
+## ConnectionCallback.resolve property
+
+Signature:
+
+```typescript
+resolve: Function;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connectiontransport.close.md b/website/versioned_docs/version-10.0.0/puppeteer.connectiontransport.close.md
new file mode 100644
index 0000000000000..8985544adf2b2
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connectiontransport.close.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConnectionTransport](./puppeteer.connectiontransport.md) > [close](./puppeteer.connectiontransport.close.md)
+
+## ConnectionTransport.close() method
+
+Signature:
+
+```typescript
+close(): any;
+```
+Returns:
+
+any
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connectiontransport.md b/website/versioned_docs/version-10.0.0/puppeteer.connectiontransport.md
new file mode 100644
index 0000000000000..9c918538ce6e7
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connectiontransport.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConnectionTransport](./puppeteer.connectiontransport.md)
+
+## ConnectionTransport interface
+
+
+Signature:
+
+```typescript
+export interface ConnectionTransport
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [onclose?](./puppeteer.connectiontransport.onclose.md) | () => void | (Optional) |
+| [onmessage?](./puppeteer.connectiontransport.onmessage.md) | (message: string) => void | (Optional) |
+
+## Methods
+
+| Method | Description |
+| --- | --- |
+| [close()](./puppeteer.connectiontransport.close.md) | |
+| [send(string)](./puppeteer.connectiontransport.send.md) | |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connectiontransport.onclose.md b/website/versioned_docs/version-10.0.0/puppeteer.connectiontransport.onclose.md
new file mode 100644
index 0000000000000..7c87797c4cadb
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connectiontransport.onclose.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConnectionTransport](./puppeteer.connectiontransport.md) > [onclose](./puppeteer.connectiontransport.onclose.md)
+
+## ConnectionTransport.onclose property
+
+Signature:
+
+```typescript
+onclose?: () => void;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connectiontransport.onmessage.md b/website/versioned_docs/version-10.0.0/puppeteer.connectiontransport.onmessage.md
new file mode 100644
index 0000000000000..7af1a876c772f
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connectiontransport.onmessage.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConnectionTransport](./puppeteer.connectiontransport.md) > [onmessage](./puppeteer.connectiontransport.onmessage.md)
+
+## ConnectionTransport.onmessage property
+
+Signature:
+
+```typescript
+onmessage?: (message: string) => void;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connectiontransport.send.md b/website/versioned_docs/version-10.0.0/puppeteer.connectiontransport.send.md
new file mode 100644
index 0000000000000..0a87f13a8c9ea
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connectiontransport.send.md
@@ -0,0 +1,22 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConnectionTransport](./puppeteer.connectiontransport.md) > [send](./puppeteer.connectiontransport.send.md)
+
+## ConnectionTransport.send() method
+
+Signature:
+
+```typescript
+send(string: any): any;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| string | any | |
+
+Returns:
+
+any
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connectoptions.browserurl.md b/website/versioned_docs/version-10.0.0/puppeteer.connectoptions.browserurl.md
new file mode 100644
index 0000000000000..987e5214302f1
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connectoptions.browserurl.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConnectOptions](./puppeteer.connectoptions.md) > [browserURL](./puppeteer.connectoptions.browserurl.md)
+
+## ConnectOptions.browserURL property
+
+Signature:
+
+```typescript
+browserURL?: string;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connectoptions.browserwsendpoint.md b/website/versioned_docs/version-10.0.0/puppeteer.connectoptions.browserwsendpoint.md
new file mode 100644
index 0000000000000..2b79ea3c5b723
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connectoptions.browserwsendpoint.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConnectOptions](./puppeteer.connectoptions.md) > [browserWSEndpoint](./puppeteer.connectoptions.browserwsendpoint.md)
+
+## ConnectOptions.browserWSEndpoint property
+
+Signature:
+
+```typescript
+browserWSEndpoint?: string;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connectoptions.md b/website/versioned_docs/version-10.0.0/puppeteer.connectoptions.md
new file mode 100644
index 0000000000000..4c465dda01ba0
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connectoptions.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConnectOptions](./puppeteer.connectoptions.md)
+
+## ConnectOptions interface
+
+
+Signature:
+
+```typescript
+export interface ConnectOptions extends BrowserConnectOptions
+```
+Extends: [BrowserConnectOptions](./puppeteer.browserconnectoptions.md)
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [browserURL?](./puppeteer.connectoptions.browserurl.md) | string | (Optional) |
+| [browserWSEndpoint?](./puppeteer.connectoptions.browserwsendpoint.md) | string | (Optional) |
+| [product?](./puppeteer.connectoptions.product.md) | [Product](./puppeteer.product.md) | (Optional) |
+| [transport?](./puppeteer.connectoptions.transport.md) | [ConnectionTransport](./puppeteer.connectiontransport.md) | (Optional) |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connectoptions.product.md b/website/versioned_docs/version-10.0.0/puppeteer.connectoptions.product.md
new file mode 100644
index 0000000000000..b828ce7ba05eb
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connectoptions.product.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConnectOptions](./puppeteer.connectoptions.md) > [product](./puppeteer.connectoptions.product.md)
+
+## ConnectOptions.product property
+
+Signature:
+
+```typescript
+product?: Product;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.connectoptions.transport.md b/website/versioned_docs/version-10.0.0/puppeteer.connectoptions.transport.md
new file mode 100644
index 0000000000000..50a40a1e14827
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.connectoptions.transport.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConnectOptions](./puppeteer.connectoptions.md) > [transport](./puppeteer.connectoptions.transport.md)
+
+## ConnectOptions.transport property
+
+Signature:
+
+```typescript
+transport?: ConnectionTransport;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.consolemessage._constructor_.md b/website/versioned_docs/version-10.0.0/puppeteer.consolemessage._constructor_.md
new file mode 100644
index 0000000000000..11614f1bac14c
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.consolemessage._constructor_.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConsoleMessage](./puppeteer.consolemessage.md) > [(constructor)](./puppeteer.consolemessage._constructor_.md)
+
+## ConsoleMessage.(constructor)
+
+Constructs a new instance of the `ConsoleMessage` class
+
+Signature:
+
+```typescript
+constructor(type: ConsoleMessageType, text: string, args: JSHandle[], stackTraceLocations: ConsoleMessageLocation[]);
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| type | [ConsoleMessageType](./puppeteer.consolemessagetype.md) | |
+| text | string | |
+| args | [JSHandle](./puppeteer.jshandle.md)\[\] | |
+| stackTraceLocations | [ConsoleMessageLocation](./puppeteer.consolemessagelocation.md)\[\] | |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.consolemessage.args.md b/website/versioned_docs/version-10.0.0/puppeteer.consolemessage.args.md
new file mode 100644
index 0000000000000..a346e18d4f2de
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.consolemessage.args.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConsoleMessage](./puppeteer.consolemessage.md) > [args](./puppeteer.consolemessage.args.md)
+
+## ConsoleMessage.args() method
+
+Signature:
+
+```typescript
+args(): JSHandle[];
+```
+Returns:
+
+[JSHandle](./puppeteer.jshandle.md)\[\]
+
+An array of arguments passed to the console.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.consolemessage.location.md b/website/versioned_docs/version-10.0.0/puppeteer.consolemessage.location.md
new file mode 100644
index 0000000000000..1eac0154d58a1
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.consolemessage.location.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConsoleMessage](./puppeteer.consolemessage.md) > [location](./puppeteer.consolemessage.location.md)
+
+## ConsoleMessage.location() method
+
+Signature:
+
+```typescript
+location(): ConsoleMessageLocation;
+```
+Returns:
+
+[ConsoleMessageLocation](./puppeteer.consolemessagelocation.md)
+
+The location of the console message.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.consolemessage.md b/website/versioned_docs/version-10.0.0/puppeteer.consolemessage.md
new file mode 100644
index 0000000000000..e56e5bd20930f
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.consolemessage.md
@@ -0,0 +1,30 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConsoleMessage](./puppeteer.consolemessage.md)
+
+## ConsoleMessage class
+
+ConsoleMessage objects are dispatched by page via the 'console' event.
+
+Signature:
+
+```typescript
+export declare class ConsoleMessage
+```
+
+## Constructors
+
+| Constructor | Modifiers | Description |
+| --- | --- | --- |
+| [(constructor)(type, text, args, stackTraceLocations)](./puppeteer.consolemessage._constructor_.md) | | Constructs a new instance of the ConsoleMessage
class |
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [args()](./puppeteer.consolemessage.args.md) | | |
+| [location()](./puppeteer.consolemessage.location.md) | | |
+| [stackTrace()](./puppeteer.consolemessage.stacktrace.md) | | |
+| [text()](./puppeteer.consolemessage.text.md) | | |
+| [type()](./puppeteer.consolemessage.type.md) | | |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.consolemessage.stacktrace.md b/website/versioned_docs/version-10.0.0/puppeteer.consolemessage.stacktrace.md
new file mode 100644
index 0000000000000..b326e6c7bb6b6
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.consolemessage.stacktrace.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConsoleMessage](./puppeteer.consolemessage.md) > [stackTrace](./puppeteer.consolemessage.stacktrace.md)
+
+## ConsoleMessage.stackTrace() method
+
+Signature:
+
+```typescript
+stackTrace(): ConsoleMessageLocation[];
+```
+Returns:
+
+[ConsoleMessageLocation](./puppeteer.consolemessagelocation.md)\[\]
+
+The array of locations on the stack of the console message.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.consolemessage.text.md b/website/versioned_docs/version-10.0.0/puppeteer.consolemessage.text.md
new file mode 100644
index 0000000000000..6b4edb5ca63fe
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.consolemessage.text.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConsoleMessage](./puppeteer.consolemessage.md) > [text](./puppeteer.consolemessage.text.md)
+
+## ConsoleMessage.text() method
+
+Signature:
+
+```typescript
+text(): string;
+```
+Returns:
+
+string
+
+The text of the console message.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.consolemessage.type.md b/website/versioned_docs/version-10.0.0/puppeteer.consolemessage.type.md
new file mode 100644
index 0000000000000..685b61957c70d
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.consolemessage.type.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConsoleMessage](./puppeteer.consolemessage.md) > [type](./puppeteer.consolemessage.type.md)
+
+## ConsoleMessage.type() method
+
+Signature:
+
+```typescript
+type(): ConsoleMessageType;
+```
+Returns:
+
+[ConsoleMessageType](./puppeteer.consolemessagetype.md)
+
+The type of the console message.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.consolemessagelocation.columnnumber.md b/website/versioned_docs/version-10.0.0/puppeteer.consolemessagelocation.columnnumber.md
new file mode 100644
index 0000000000000..23a6b3d20a078
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.consolemessagelocation.columnnumber.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConsoleMessageLocation](./puppeteer.consolemessagelocation.md) > [columnNumber](./puppeteer.consolemessagelocation.columnnumber.md)
+
+## ConsoleMessageLocation.columnNumber property
+
+0-based column number in the resource if known or `undefined` otherwise.
+
+Signature:
+
+```typescript
+columnNumber?: number;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.consolemessagelocation.linenumber.md b/website/versioned_docs/version-10.0.0/puppeteer.consolemessagelocation.linenumber.md
new file mode 100644
index 0000000000000..cdc1fd4579231
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.consolemessagelocation.linenumber.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConsoleMessageLocation](./puppeteer.consolemessagelocation.md) > [lineNumber](./puppeteer.consolemessagelocation.linenumber.md)
+
+## ConsoleMessageLocation.lineNumber property
+
+0-based line number in the resource if known or `undefined` otherwise.
+
+Signature:
+
+```typescript
+lineNumber?: number;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.consolemessagelocation.md b/website/versioned_docs/version-10.0.0/puppeteer.consolemessagelocation.md
new file mode 100644
index 0000000000000..b6f032160a651
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.consolemessagelocation.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConsoleMessageLocation](./puppeteer.consolemessagelocation.md)
+
+## ConsoleMessageLocation interface
+
+
+Signature:
+
+```typescript
+export interface ConsoleMessageLocation
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [columnNumber?](./puppeteer.consolemessagelocation.columnnumber.md) | number | (Optional) 0-based column number in the resource if known or undefined
otherwise. |
+| [lineNumber?](./puppeteer.consolemessagelocation.linenumber.md) | number | (Optional) 0-based line number in the resource if known or undefined
otherwise. |
+| [url?](./puppeteer.consolemessagelocation.url.md) | string | (Optional) URL of the resource if known or undefined
otherwise. |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.consolemessagelocation.url.md b/website/versioned_docs/version-10.0.0/puppeteer.consolemessagelocation.url.md
new file mode 100644
index 0000000000000..fe30226eb6ac4
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.consolemessagelocation.url.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConsoleMessageLocation](./puppeteer.consolemessagelocation.md) > [url](./puppeteer.consolemessagelocation.url.md)
+
+## ConsoleMessageLocation.url property
+
+URL of the resource if known or `undefined` otherwise.
+
+Signature:
+
+```typescript
+url?: string;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.consolemessagetype.md b/website/versioned_docs/version-10.0.0/puppeteer.consolemessagetype.md
new file mode 100644
index 0000000000000..d9a3e883d7de5
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.consolemessagetype.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ConsoleMessageType](./puppeteer.consolemessagetype.md)
+
+## ConsoleMessageType type
+
+The supported types for console messages.
+
+Signature:
+
+```typescript
+export declare type ConsoleMessageType = 'log' | 'debug' | 'info' | 'error' | 'warning' | 'dir' | 'dirxml' | 'table' | 'trace' | 'clear' | 'startGroup' | 'startGroupCollapsed' | 'endGroup' | 'assert' | 'profile' | 'profileEnd' | 'count' | 'timeEnd' | 'verbose';
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.continuerequestoverrides.headers.md b/website/versioned_docs/version-10.0.0/puppeteer.continuerequestoverrides.headers.md
new file mode 100644
index 0000000000000..85a062bb0b3eb
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.continuerequestoverrides.headers.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ContinueRequestOverrides](./puppeteer.continuerequestoverrides.md) > [headers](./puppeteer.continuerequestoverrides.headers.md)
+
+## ContinueRequestOverrides.headers property
+
+Signature:
+
+```typescript
+headers?: Record;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.continuerequestoverrides.md b/website/versioned_docs/version-10.0.0/puppeteer.continuerequestoverrides.md
new file mode 100644
index 0000000000000..38e7a2decdbe6
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.continuerequestoverrides.md
@@ -0,0 +1,22 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ContinueRequestOverrides](./puppeteer.continuerequestoverrides.md)
+
+## ContinueRequestOverrides interface
+
+
+Signature:
+
+```typescript
+export interface ContinueRequestOverrides
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [headers?](./puppeteer.continuerequestoverrides.headers.md) | Record<string, string> | (Optional) |
+| [method?](./puppeteer.continuerequestoverrides.method.md) | string | (Optional) |
+| [postData?](./puppeteer.continuerequestoverrides.postdata.md) | string | (Optional) |
+| [url?](./puppeteer.continuerequestoverrides.url.md) | string | (Optional) If set, the request URL will change. This is not a redirect. |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.continuerequestoverrides.method.md b/website/versioned_docs/version-10.0.0/puppeteer.continuerequestoverrides.method.md
new file mode 100644
index 0000000000000..ef17b396e1be6
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.continuerequestoverrides.method.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ContinueRequestOverrides](./puppeteer.continuerequestoverrides.md) > [method](./puppeteer.continuerequestoverrides.method.md)
+
+## ContinueRequestOverrides.method property
+
+Signature:
+
+```typescript
+method?: string;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.continuerequestoverrides.postdata.md b/website/versioned_docs/version-10.0.0/puppeteer.continuerequestoverrides.postdata.md
new file mode 100644
index 0000000000000..7d88853284c7b
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.continuerequestoverrides.postdata.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ContinueRequestOverrides](./puppeteer.continuerequestoverrides.md) > [postData](./puppeteer.continuerequestoverrides.postdata.md)
+
+## ContinueRequestOverrides.postData property
+
+Signature:
+
+```typescript
+postData?: string;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.continuerequestoverrides.url.md b/website/versioned_docs/version-10.0.0/puppeteer.continuerequestoverrides.url.md
new file mode 100644
index 0000000000000..53ec6d6d87073
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.continuerequestoverrides.url.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ContinueRequestOverrides](./puppeteer.continuerequestoverrides.md) > [url](./puppeteer.continuerequestoverrides.url.md)
+
+## ContinueRequestOverrides.url property
+
+If set, the request URL will change. This is not a redirect.
+
+Signature:
+
+```typescript
+url?: string;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.coverage._constructor_.md b/website/versioned_docs/version-10.0.0/puppeteer.coverage._constructor_.md
new file mode 100644
index 0000000000000..727461d119001
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.coverage._constructor_.md
@@ -0,0 +1,20 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Coverage](./puppeteer.coverage.md) > [(constructor)](./puppeteer.coverage._constructor_.md)
+
+## Coverage.(constructor)
+
+Constructs a new instance of the `Coverage` class
+
+Signature:
+
+```typescript
+constructor(client: CDPSession);
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| client | [CDPSession](./puppeteer.cdpsession.md) | |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.coverage.md b/website/versioned_docs/version-10.0.0/puppeteer.coverage.md
new file mode 100644
index 0000000000000..a5a1a57bbfc4a
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.coverage.md
@@ -0,0 +1,62 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Coverage](./puppeteer.coverage.md)
+
+## Coverage class
+
+The Coverage class provides methods to gathers information about parts of JavaScript and CSS that were used by the page.
+
+Signature:
+
+```typescript
+export declare class Coverage
+```
+
+## Remarks
+
+To output coverage in a form consumable by [Istanbul](https://github.com/istanbuljs), see [puppeteer-to-istanbul](https://github.com/istanbuljs/puppeteer-to-istanbul).
+
+## Example
+
+An example of using JavaScript and CSS coverage to get percentage of initially executed code:
+
+```js
+// Enable both JavaScript and CSS coverage
+await Promise.all([
+ page.coverage.startJSCoverage(),
+ page.coverage.startCSSCoverage()
+]);
+// Navigate to page
+await page.goto('https://example.com');
+// Disable both JavaScript and CSS coverage
+const [jsCoverage, cssCoverage] = await Promise.all([
+ page.coverage.stopJSCoverage(),
+ page.coverage.stopCSSCoverage(),
+]);
+let totalBytes = 0;
+let usedBytes = 0;
+const coverage = [...jsCoverage, ...cssCoverage];
+for (const entry of coverage) {
+ totalBytes += entry.text.length;
+ for (const range of entry.ranges)
+ usedBytes += range.end - range.start - 1;
+}
+console.log(`Bytes used: ${usedBytes / totalBytes * 100}%`);
+
+```
+
+## Constructors
+
+| Constructor | Modifiers | Description |
+| --- | --- | --- |
+| [(constructor)(client)](./puppeteer.coverage._constructor_.md) | | Constructs a new instance of the Coverage
class |
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [startCSSCoverage(options)](./puppeteer.coverage.startcsscoverage.md) | | |
+| [startJSCoverage(options)](./puppeteer.coverage.startjscoverage.md) | | |
+| [stopCSSCoverage()](./puppeteer.coverage.stopcsscoverage.md) | | |
+| [stopJSCoverage()](./puppeteer.coverage.stopjscoverage.md) | | |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.coverage.startcsscoverage.md b/website/versioned_docs/version-10.0.0/puppeteer.coverage.startcsscoverage.md
new file mode 100644
index 0000000000000..2a5996d097be2
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.coverage.startcsscoverage.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Coverage](./puppeteer.coverage.md) > [startCSSCoverage](./puppeteer.coverage.startcsscoverage.md)
+
+## Coverage.startCSSCoverage() method
+
+Signature:
+
+```typescript
+startCSSCoverage(options?: CSSCoverageOptions): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| options | [CSSCoverageOptions](./puppeteer.csscoverageoptions.md) | Set of configurable options for coverage, defaults to resetOnNavigation : true
|
+
+Returns:
+
+Promise<void>
+
+Promise that resolves when coverage is started.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.coverage.startjscoverage.md b/website/versioned_docs/version-10.0.0/puppeteer.coverage.startjscoverage.md
new file mode 100644
index 0000000000000..b4e195f3036f8
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.coverage.startjscoverage.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Coverage](./puppeteer.coverage.md) > [startJSCoverage](./puppeteer.coverage.startjscoverage.md)
+
+## Coverage.startJSCoverage() method
+
+Signature:
+
+```typescript
+startJSCoverage(options?: JSCoverageOptions): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| options | [JSCoverageOptions](./puppeteer.jscoverageoptions.md) | Set of configurable options for coverage defaults to resetOnNavigation : true, reportAnonymousScripts : false
|
+
+Returns:
+
+Promise<void>
+
+Promise that resolves when coverage is started.
+
+## Remarks
+
+Anonymous scripts are ones that don't have an associated url. These are scripts that are dynamically created on the page using `eval` or `new Function`. If `reportAnonymousScripts` is set to `true`, anonymous scripts will have `__puppeteer_evaluation_script__` as their URL.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.coverage.stopcsscoverage.md b/website/versioned_docs/version-10.0.0/puppeteer.coverage.stopcsscoverage.md
new file mode 100644
index 0000000000000..e926fd00624e1
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.coverage.stopcsscoverage.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Coverage](./puppeteer.coverage.md) > [stopCSSCoverage](./puppeteer.coverage.stopcsscoverage.md)
+
+## Coverage.stopCSSCoverage() method
+
+Signature:
+
+```typescript
+stopCSSCoverage(): Promise;
+```
+Returns:
+
+Promise<[CoverageEntry](./puppeteer.coverageentry.md)\[\]>
+
+Promise that resolves to the array of coverage reports for all stylesheets.
+
+## Remarks
+
+CSS Coverage doesn't include dynamically injected style tags without sourceURLs.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.coverage.stopjscoverage.md b/website/versioned_docs/version-10.0.0/puppeteer.coverage.stopjscoverage.md
new file mode 100644
index 0000000000000..9a66b035e48f6
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.coverage.stopjscoverage.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Coverage](./puppeteer.coverage.md) > [stopJSCoverage](./puppeteer.coverage.stopjscoverage.md)
+
+## Coverage.stopJSCoverage() method
+
+Signature:
+
+```typescript
+stopJSCoverage(): Promise;
+```
+Returns:
+
+Promise<[CoverageEntry](./puppeteer.coverageentry.md)\[\]>
+
+Promise that resolves to the array of coverage reports for all scripts.
+
+## Remarks
+
+JavaScript Coverage doesn't include anonymous scripts by default. However, scripts with sourceURLs are reported.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.coverageentry.md b/website/versioned_docs/version-10.0.0/puppeteer.coverageentry.md
new file mode 100644
index 0000000000000..2991f0d539fe9
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.coverageentry.md
@@ -0,0 +1,22 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CoverageEntry](./puppeteer.coverageentry.md)
+
+## CoverageEntry interface
+
+The CoverageEntry class represents one entry of the coverage report.
+
+Signature:
+
+```typescript
+export interface CoverageEntry
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [ranges](./puppeteer.coverageentry.ranges.md) | Array<{ start: number; end: number; }> | The covered range as start and end positions. |
+| [text](./puppeteer.coverageentry.text.md) | string | The content of the style sheet or script. |
+| [url](./puppeteer.coverageentry.url.md) | string | The URL of the style sheet or script. |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.coverageentry.ranges.md b/website/versioned_docs/version-10.0.0/puppeteer.coverageentry.ranges.md
new file mode 100644
index 0000000000000..8a0ad196125d4
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.coverageentry.ranges.md
@@ -0,0 +1,16 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CoverageEntry](./puppeteer.coverageentry.md) > [ranges](./puppeteer.coverageentry.ranges.md)
+
+## CoverageEntry.ranges property
+
+The covered range as start and end positions.
+
+Signature:
+
+```typescript
+ranges: Array<{
+ start: number;
+ end: number;
+ }>;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.coverageentry.text.md b/website/versioned_docs/version-10.0.0/puppeteer.coverageentry.text.md
new file mode 100644
index 0000000000000..a5d3ddc629a66
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.coverageentry.text.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CoverageEntry](./puppeteer.coverageentry.md) > [text](./puppeteer.coverageentry.text.md)
+
+## CoverageEntry.text property
+
+The content of the style sheet or script.
+
+Signature:
+
+```typescript
+text: string;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.coverageentry.url.md b/website/versioned_docs/version-10.0.0/puppeteer.coverageentry.url.md
new file mode 100644
index 0000000000000..9f33d73053bb5
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.coverageentry.url.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CoverageEntry](./puppeteer.coverageentry.md) > [url](./puppeteer.coverageentry.url.md)
+
+## CoverageEntry.url property
+
+The URL of the style sheet or script.
+
+Signature:
+
+```typescript
+url: string;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.credentials.md b/website/versioned_docs/version-10.0.0/puppeteer.credentials.md
new file mode 100644
index 0000000000000..106acd7596317
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.credentials.md
@@ -0,0 +1,20 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Credentials](./puppeteer.credentials.md)
+
+## Credentials interface
+
+
+Signature:
+
+```typescript
+export interface Credentials
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [password](./puppeteer.credentials.password.md) | string | |
+| [username](./puppeteer.credentials.username.md) | string | |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.credentials.password.md b/website/versioned_docs/version-10.0.0/puppeteer.credentials.password.md
new file mode 100644
index 0000000000000..988017ac966c1
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.credentials.password.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Credentials](./puppeteer.credentials.md) > [password](./puppeteer.credentials.password.md)
+
+## Credentials.password property
+
+Signature:
+
+```typescript
+password: string;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.credentials.username.md b/website/versioned_docs/version-10.0.0/puppeteer.credentials.username.md
new file mode 100644
index 0000000000000..1cf2011cd6d43
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.credentials.username.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Credentials](./puppeteer.credentials.md) > [username](./puppeteer.credentials.username.md)
+
+## Credentials.username property
+
+Signature:
+
+```typescript
+username: string;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._client.md b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._client.md
new file mode 100644
index 0000000000000..1c0e553db1c67
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._client.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CSSCoverage](./puppeteer.csscoverage.md) > [\_client](./puppeteer.csscoverage._client.md)
+
+## CSSCoverage.\_client property
+
+Signature:
+
+```typescript
+_client: CDPSession;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._constructor_.md b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._constructor_.md
new file mode 100644
index 0000000000000..1511bc6704d9c
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._constructor_.md
@@ -0,0 +1,20 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CSSCoverage](./puppeteer.csscoverage.md) > [(constructor)](./puppeteer.csscoverage._constructor_.md)
+
+## CSSCoverage.(constructor)
+
+Constructs a new instance of the `CSSCoverage` class
+
+Signature:
+
+```typescript
+constructor(client: CDPSession);
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| client | [CDPSession](./puppeteer.cdpsession.md) | |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._enabled.md b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._enabled.md
new file mode 100644
index 0000000000000..2bcf040238771
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._enabled.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CSSCoverage](./puppeteer.csscoverage.md) > [\_enabled](./puppeteer.csscoverage._enabled.md)
+
+## CSSCoverage.\_enabled property
+
+Signature:
+
+```typescript
+_enabled: boolean;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._eventlisteners.md b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._eventlisteners.md
new file mode 100644
index 0000000000000..6d37743663bc8
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._eventlisteners.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CSSCoverage](./puppeteer.csscoverage.md) > [\_eventListeners](./puppeteer.csscoverage._eventlisteners.md)
+
+## CSSCoverage.\_eventListeners property
+
+Signature:
+
+```typescript
+_eventListeners: PuppeteerEventListener[];
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._onexecutioncontextscleared.md b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._onexecutioncontextscleared.md
new file mode 100644
index 0000000000000..70210cc2b837f
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._onexecutioncontextscleared.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CSSCoverage](./puppeteer.csscoverage.md) > [\_onExecutionContextsCleared](./puppeteer.csscoverage._onexecutioncontextscleared.md)
+
+## CSSCoverage.\_onExecutionContextsCleared() method
+
+Signature:
+
+```typescript
+_onExecutionContextsCleared(): void;
+```
+Returns:
+
+void
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._onstylesheet.md b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._onstylesheet.md
new file mode 100644
index 0000000000000..311e6e2fc2824
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._onstylesheet.md
@@ -0,0 +1,22 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CSSCoverage](./puppeteer.csscoverage.md) > [\_onStyleSheet](./puppeteer.csscoverage._onstylesheet.md)
+
+## CSSCoverage.\_onStyleSheet() method
+
+Signature:
+
+```typescript
+_onStyleSheet(event: Protocol.CSS.StyleSheetAddedEvent): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| event | Protocol.CSS.StyleSheetAddedEvent | |
+
+Returns:
+
+Promise<void>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._reportanonymousscripts.md b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._reportanonymousscripts.md
new file mode 100644
index 0000000000000..d34910e683ed3
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._reportanonymousscripts.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CSSCoverage](./puppeteer.csscoverage.md) > [\_reportAnonymousScripts](./puppeteer.csscoverage._reportanonymousscripts.md)
+
+## CSSCoverage.\_reportAnonymousScripts property
+
+Signature:
+
+```typescript
+_reportAnonymousScripts: boolean;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._resetonnavigation.md b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._resetonnavigation.md
new file mode 100644
index 0000000000000..ecb994ca071b7
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._resetonnavigation.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CSSCoverage](./puppeteer.csscoverage.md) > [\_resetOnNavigation](./puppeteer.csscoverage._resetonnavigation.md)
+
+## CSSCoverage.\_resetOnNavigation property
+
+Signature:
+
+```typescript
+_resetOnNavigation: boolean;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._stylesheetsources.md b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._stylesheetsources.md
new file mode 100644
index 0000000000000..5044bc3a39544
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._stylesheetsources.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CSSCoverage](./puppeteer.csscoverage.md) > [\_stylesheetSources](./puppeteer.csscoverage._stylesheetsources.md)
+
+## CSSCoverage.\_stylesheetSources property
+
+Signature:
+
+```typescript
+_stylesheetSources: Map;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._stylesheeturls.md b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._stylesheeturls.md
new file mode 100644
index 0000000000000..2ea0794bfa80e
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage._stylesheeturls.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CSSCoverage](./puppeteer.csscoverage.md) > [\_stylesheetURLs](./puppeteer.csscoverage._stylesheeturls.md)
+
+## CSSCoverage.\_stylesheetURLs property
+
+Signature:
+
+```typescript
+_stylesheetURLs: Map;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.csscoverage.md b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage.md
new file mode 100644
index 0000000000000..f76249c2f1283
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage.md
@@ -0,0 +1,40 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CSSCoverage](./puppeteer.csscoverage.md)
+
+## CSSCoverage class
+
+
+Signature:
+
+```typescript
+export declare class CSSCoverage
+```
+
+## Constructors
+
+| Constructor | Modifiers | Description |
+| --- | --- | --- |
+| [(constructor)(client)](./puppeteer.csscoverage._constructor_.md) | | Constructs a new instance of the CSSCoverage
class |
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [\_client](./puppeteer.csscoverage._client.md) | | [CDPSession](./puppeteer.cdpsession.md) | |
+| [\_enabled](./puppeteer.csscoverage._enabled.md) | | boolean | |
+| [\_eventListeners](./puppeteer.csscoverage._eventlisteners.md) | | [PuppeteerEventListener](./puppeteer.puppeteereventlistener.md)\[\] | |
+| [\_reportAnonymousScripts](./puppeteer.csscoverage._reportanonymousscripts.md) | | boolean | |
+| [\_resetOnNavigation](./puppeteer.csscoverage._resetonnavigation.md) | | boolean | |
+| [\_stylesheetSources](./puppeteer.csscoverage._stylesheetsources.md) | | Map<string, string> | |
+| [\_stylesheetURLs](./puppeteer.csscoverage._stylesheeturls.md) | | Map<string, string> | |
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [\_onExecutionContextsCleared()](./puppeteer.csscoverage._onexecutioncontextscleared.md) | | |
+| [\_onStyleSheet(event)](./puppeteer.csscoverage._onstylesheet.md) | | |
+| [start(options)](./puppeteer.csscoverage.start.md) | | |
+| [stop()](./puppeteer.csscoverage.stop.md) | | |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.csscoverage.start.md b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage.start.md
new file mode 100644
index 0000000000000..dc986e9465fef
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage.start.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CSSCoverage](./puppeteer.csscoverage.md) > [start](./puppeteer.csscoverage.start.md)
+
+## CSSCoverage.start() method
+
+Signature:
+
+```typescript
+start(options?: {
+ resetOnNavigation?: boolean;
+ }): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| options | { resetOnNavigation?: boolean; } | |
+
+Returns:
+
+Promise<void>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.csscoverage.stop.md b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage.stop.md
new file mode 100644
index 0000000000000..d08b2c6945a51
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.csscoverage.stop.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CSSCoverage](./puppeteer.csscoverage.md) > [stop](./puppeteer.csscoverage.stop.md)
+
+## CSSCoverage.stop() method
+
+Signature:
+
+```typescript
+stop(): Promise;
+```
+Returns:
+
+Promise<[CoverageEntry](./puppeteer.coverageentry.md)\[\]>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.csscoverageoptions.md b/website/versioned_docs/version-10.0.0/puppeteer.csscoverageoptions.md
new file mode 100644
index 0000000000000..699d2f04a7532
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.csscoverageoptions.md
@@ -0,0 +1,20 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CSSCoverageOptions](./puppeteer.csscoverageoptions.md)
+
+## CSSCoverageOptions interface
+
+Set of configurable options for CSS coverage.
+
+Signature:
+
+```typescript
+export interface CSSCoverageOptions
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [resetOnNavigation?](./puppeteer.csscoverageoptions.resetonnavigation.md) | boolean | (Optional) Whether to reset coverage on every navigation. |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.csscoverageoptions.resetonnavigation.md b/website/versioned_docs/version-10.0.0/puppeteer.csscoverageoptions.resetonnavigation.md
new file mode 100644
index 0000000000000..079ffe47c50d0
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.csscoverageoptions.resetonnavigation.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CSSCoverageOptions](./puppeteer.csscoverageoptions.md) > [resetOnNavigation](./puppeteer.csscoverageoptions.resetonnavigation.md)
+
+## CSSCoverageOptions.resetOnNavigation property
+
+Whether to reset coverage on every navigation.
+
+Signature:
+
+```typescript
+resetOnNavigation?: boolean;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.customerror._constructor_.md b/website/versioned_docs/version-10.0.0/puppeteer.customerror._constructor_.md
new file mode 100644
index 0000000000000..1d0a6b107b291
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.customerror._constructor_.md
@@ -0,0 +1,20 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CustomError](./puppeteer.customerror.md) > [(constructor)](./puppeteer.customerror._constructor_.md)
+
+## CustomError.(constructor)
+
+Constructs a new instance of the `CustomError` class
+
+Signature:
+
+```typescript
+constructor(message: string);
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| message | string | |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.customerror.md b/website/versioned_docs/version-10.0.0/puppeteer.customerror.md
new file mode 100644
index 0000000000000..6ff0b16151f02
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.customerror.md
@@ -0,0 +1,20 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CustomError](./puppeteer.customerror.md)
+
+## CustomError class
+
+
+Signature:
+
+```typescript
+export declare class CustomError extends Error
+```
+Extends: Error
+
+## Constructors
+
+| Constructor | Modifiers | Description |
+| --- | --- | --- |
+| [(constructor)(message)](./puppeteer.customerror._constructor_.md) | | Constructs a new instance of the CustomError
class |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.customqueryhandler.md b/website/versioned_docs/version-10.0.0/puppeteer.customqueryhandler.md
new file mode 100644
index 0000000000000..6aa1aa93e719d
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.customqueryhandler.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CustomQueryHandler](./puppeteer.customqueryhandler.md)
+
+## CustomQueryHandler interface
+
+Contains two functions `queryOne` and `queryAll` that can be [registered](./puppeteer.puppeteer.registercustomqueryhandler.md) as alternative querying strategies. The functions `queryOne` and `queryAll` are executed in the page context. `queryOne` should take an `Element` and a selector string as argument and return a single `Element` or `null` if no element is found. `queryAll` takes the same arguments but should instead return a `NodeListOf` or `Array` with all the elements that match the given query selector.
+
+Signature:
+
+```typescript
+export interface CustomQueryHandler
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [queryAll?](./puppeteer.customqueryhandler.queryall.md) | (element: Element \| Document, selector: string) => Element\[\] \| NodeListOf<Element> | (Optional) |
+| [queryOne?](./puppeteer.customqueryhandler.queryone.md) | (element: Element \| Document, selector: string) => Element \| null | (Optional) |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.customqueryhandler.queryall.md b/website/versioned_docs/version-10.0.0/puppeteer.customqueryhandler.queryall.md
new file mode 100644
index 0000000000000..71b35ca8ca86f
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.customqueryhandler.queryall.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CustomQueryHandler](./puppeteer.customqueryhandler.md) > [queryAll](./puppeteer.customqueryhandler.queryall.md)
+
+## CustomQueryHandler.queryAll property
+
+Signature:
+
+```typescript
+queryAll?: (element: Element | Document, selector: string) => Element[] | NodeListOf;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.customqueryhandler.queryone.md b/website/versioned_docs/version-10.0.0/puppeteer.customqueryhandler.queryone.md
new file mode 100644
index 0000000000000..7f00edadd0764
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.customqueryhandler.queryone.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [CustomQueryHandler](./puppeteer.customqueryhandler.md) > [queryOne](./puppeteer.customqueryhandler.queryone.md)
+
+## CustomQueryHandler.queryOne property
+
+Signature:
+
+```typescript
+queryOne?: (element: Element | Document, selector: string) => Element | null;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.customqueryhandlernames.md b/website/versioned_docs/version-10.0.0/puppeteer.customqueryhandlernames.md
new file mode 100644
index 0000000000000..d077b98b76f9c
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.customqueryhandlernames.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [customQueryHandlerNames](./puppeteer.customqueryhandlernames.md)
+
+## customQueryHandlerNames() function
+
+Signature:
+
+```typescript
+export declare function customQueryHandlerNames(): string[];
+```
+Returns:
+
+string\[\]
+
+a list with the names of all registered custom query handlers.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.device.md b/website/versioned_docs/version-10.0.0/puppeteer.device.md
new file mode 100644
index 0000000000000..1382b587225fc
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.device.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Device](./puppeteer.device.md)
+
+## Device interface
+
+
+Signature:
+
+```typescript
+export interface Device
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [name](./puppeteer.device.name.md) | string | |
+| [userAgent](./puppeteer.device.useragent.md) | string | |
+| [viewport](./puppeteer.device.viewport.md) | { width: number; height: number; deviceScaleFactor: number; isMobile: boolean; hasTouch: boolean; isLandscape: boolean; } | |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.device.name.md b/website/versioned_docs/version-10.0.0/puppeteer.device.name.md
new file mode 100644
index 0000000000000..6d044886b48bf
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.device.name.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Device](./puppeteer.device.md) > [name](./puppeteer.device.name.md)
+
+## Device.name property
+
+Signature:
+
+```typescript
+name: string;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.device.useragent.md b/website/versioned_docs/version-10.0.0/puppeteer.device.useragent.md
new file mode 100644
index 0000000000000..1d79e8f6093e4
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.device.useragent.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Device](./puppeteer.device.md) > [userAgent](./puppeteer.device.useragent.md)
+
+## Device.userAgent property
+
+Signature:
+
+```typescript
+userAgent: string;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.device.viewport.md b/website/versioned_docs/version-10.0.0/puppeteer.device.viewport.md
new file mode 100644
index 0000000000000..9e16753edc343
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.device.viewport.md
@@ -0,0 +1,18 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Device](./puppeteer.device.md) > [viewport](./puppeteer.device.viewport.md)
+
+## Device.viewport property
+
+Signature:
+
+```typescript
+viewport: {
+ width: number;
+ height: number;
+ deviceScaleFactor: number;
+ isMobile: boolean;
+ hasTouch: boolean;
+ isLandscape: boolean;
+ };
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.devices.md b/website/versioned_docs/version-10.0.0/puppeteer.devices.md
new file mode 100644
index 0000000000000..97e3587397f80
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.devices.md
@@ -0,0 +1,16 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [devices](./puppeteer.devices.md)
+
+## devices variable
+
+Signature:
+
+```typescript
+devices: DevicesMap
+```
+
+## Remarks
+
+A list of devices to be used with `page.emulate(options)`. Actual list of devices can be found in [src/common/DeviceDescriptors.ts](https://github.com/puppeteer/puppeteer/blob/main/src/common/DeviceDescriptors.ts).
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.devicesmap.md b/website/versioned_docs/version-10.0.0/puppeteer.devicesmap.md
new file mode 100644
index 0000000000000..fb1afe4a2ad4c
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.devicesmap.md
@@ -0,0 +1,16 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [DevicesMap](./puppeteer.devicesmap.md)
+
+## DevicesMap type
+
+
+Signature:
+
+```typescript
+export declare type DevicesMap = {
+ [name: string]: Device;
+};
+```
+References: [Device](./puppeteer.device.md)
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.dialog.accept.md b/website/versioned_docs/version-10.0.0/puppeteer.dialog.accept.md
new file mode 100644
index 0000000000000..cf7386371f7be
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.dialog.accept.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Dialog](./puppeteer.dialog.md) > [accept](./puppeteer.dialog.accept.md)
+
+## Dialog.accept() method
+
+Signature:
+
+```typescript
+accept(promptText?: string): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| promptText | string | optional text that will be entered in the dialog prompt. Has no effect if the dialog's type is not prompt
. |
+
+Returns:
+
+Promise<void>
+
+A promise that resolves when the dialog has been accepted.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.dialog.defaultvalue.md b/website/versioned_docs/version-10.0.0/puppeteer.dialog.defaultvalue.md
new file mode 100644
index 0000000000000..fe83eecc105bb
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.dialog.defaultvalue.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Dialog](./puppeteer.dialog.md) > [defaultValue](./puppeteer.dialog.defaultvalue.md)
+
+## Dialog.defaultValue() method
+
+Signature:
+
+```typescript
+defaultValue(): string;
+```
+Returns:
+
+string
+
+The default value of the prompt, or an empty string if the dialog is not a `prompt`.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.dialog.dismiss.md b/website/versioned_docs/version-10.0.0/puppeteer.dialog.dismiss.md
new file mode 100644
index 0000000000000..900c286305b2d
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.dialog.dismiss.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Dialog](./puppeteer.dialog.md) > [dismiss](./puppeteer.dialog.dismiss.md)
+
+## Dialog.dismiss() method
+
+Signature:
+
+```typescript
+dismiss(): Promise;
+```
+Returns:
+
+Promise<void>
+
+A promise which will resolve once the dialog has been dismissed
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.dialog.md b/website/versioned_docs/version-10.0.0/puppeteer.dialog.md
new file mode 100644
index 0000000000000..13c9c277c6ae1
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.dialog.md
@@ -0,0 +1,47 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Dialog](./puppeteer.dialog.md)
+
+## Dialog class
+
+Dialog instances are dispatched by the [Page](./puppeteer.page.md) via the `dialog` event.
+
+Signature:
+
+```typescript
+export declare class Dialog
+```
+
+## Remarks
+
+The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `Dialog` class.
+
+## Example
+
+
+```js
+const puppeteer = require('puppeteer');
+
+(async () => {
+ const browser = await puppeteer.launch();
+ const page = await browser.newPage();
+ page.on('dialog', async dialog => {
+ console.log(dialog.message());
+ await dialog.dismiss();
+ await browser.close();
+ });
+ page.evaluate(() => alert('1'));
+})();
+
+```
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [accept(promptText)](./puppeteer.dialog.accept.md) | | |
+| [defaultValue()](./puppeteer.dialog.defaultvalue.md) | | |
+| [dismiss()](./puppeteer.dialog.dismiss.md) | | |
+| [message()](./puppeteer.dialog.message.md) | | |
+| [type()](./puppeteer.dialog.type.md) | | |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.dialog.message.md b/website/versioned_docs/version-10.0.0/puppeteer.dialog.message.md
new file mode 100644
index 0000000000000..e725b67f68f15
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.dialog.message.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Dialog](./puppeteer.dialog.md) > [message](./puppeteer.dialog.message.md)
+
+## Dialog.message() method
+
+Signature:
+
+```typescript
+message(): string;
+```
+Returns:
+
+string
+
+The message displayed in the dialog.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.dialog.type.md b/website/versioned_docs/version-10.0.0/puppeteer.dialog.type.md
new file mode 100644
index 0000000000000..07e789bd326c4
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.dialog.type.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [Dialog](./puppeteer.dialog.md) > [type](./puppeteer.dialog.type.md)
+
+## Dialog.type() method
+
+Signature:
+
+```typescript
+type(): Protocol.Page.DialogType;
+```
+Returns:
+
+Protocol.Page.DialogType
+
+The type of the dialog.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle._.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle._.md
new file mode 100644
index 0000000000000..80479eebc6edc
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle._.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [$](./puppeteer.elementhandle._.md)
+
+## ElementHandle.$() method
+
+Runs `element.querySelector` within the page. If no element matches the selector, the return value resolves to `null`.
+
+Signature:
+
+```typescript
+$(selector: string): Promise | null>;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| selector | string | |
+
+Returns:
+
+Promise<[ElementHandle](./puppeteer.elementhandle.md)<T> \| null>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.__.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.__.md
new file mode 100644
index 0000000000000..06986b9d0f5c4
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.__.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [$$](./puppeteer.elementhandle.__.md)
+
+## ElementHandle.$$() method
+
+Runs `element.querySelectorAll` within the page. If no elements match the selector, the return value resolves to `[]`.
+
+Signature:
+
+```typescript
+$$(selector: string): Promise>>;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| selector | string | |
+
+Returns:
+
+Promise<Array<[ElementHandle](./puppeteer.elementhandle.md)<T>>>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.__eval.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.__eval.md
new file mode 100644
index 0000000000000..a93ca0025efc0
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.__eval.md
@@ -0,0 +1,49 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [$$eval](./puppeteer.elementhandle.__eval.md)
+
+## ElementHandle.$$eval() method
+
+This method runs `document.querySelectorAll` within the element and passes it as the first argument to `pageFunction`. If there's no element matching `selector`, the method throws an error.
+
+If `pageFunction` returns a Promise, then `frame.$$eval` would wait for the promise to resolve and return its value.
+
+Signature:
+
+```typescript
+$$eval(selector: string, pageFunction: (elements: Element[], ...args: unknown[]) => ReturnType | Promise, ...args: SerializableOrJSHandle[]): Promise>;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| selector | string | |
+| pageFunction | (elements: Element\[\], ...args: unknown\[\]) => ReturnType \| Promise<ReturnType> | |
+| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)\[\] | |
+
+Returns:
+
+Promise<[WrapElementHandle](./puppeteer.wrapelementhandle.md)<ReturnType>>
+
+## Example 1
+
+
+```html
+
+
+
+
+
+```
+
+## Example 2
+
+
+```js
+const feedHandle = await page.$('.feed');
+expect(await feedHandle.$$eval('.tweet', nodes => nodes.map(n => n.innerText)))
+ .toEqual(['Hello!', 'Hi!']);
+
+```
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle._eval.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle._eval.md
new file mode 100644
index 0000000000000..9c05c4488d886
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle._eval.md
@@ -0,0 +1,38 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [$eval](./puppeteer.elementhandle._eval.md)
+
+## ElementHandle.$eval() method
+
+This method runs `document.querySelector` within the element and passes it as the first argument to `pageFunction`. If there's no element matching `selector`, the method throws an error.
+
+If `pageFunction` returns a Promise, then `frame.$eval` would wait for the promise to resolve and return its value.
+
+Signature:
+
+```typescript
+$eval(selector: string, pageFunction: (element: Element, ...args: unknown[]) => ReturnType | Promise, ...args: SerializableOrJSHandle[]): Promise>;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| selector | string | |
+| pageFunction | (element: Element, ...args: unknown\[\]) => ReturnType \| Promise<ReturnType> | |
+| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)\[\] | |
+
+Returns:
+
+Promise<[WrapElementHandle](./puppeteer.wrapelementhandle.md)<ReturnType>>
+
+## Example
+
+
+```js
+const tweetHandle = await page.$('.tweet');
+expect(await tweetHandle.$eval('.like', node => node.innerText)).toBe('100');
+expect(await tweetHandle.$eval('.retweets', node => node.innerText)).toBe('10');
+
+```
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle._x.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle._x.md
new file mode 100644
index 0000000000000..db94315bb944d
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle._x.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [$x](./puppeteer.elementhandle._x.md)
+
+## ElementHandle.$x() method
+
+The method evaluates the XPath expression relative to the elementHandle. If there are no such elements, the method will resolve to an empty array.
+
+Signature:
+
+```typescript
+$x(expression: string): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| expression | string | Expression to [evaluate](https://developer.mozilla.org/en-US/docs/Web/API/Document/evaluate) |
+
+Returns:
+
+Promise<[ElementHandle](./puppeteer.elementhandle.md)\[\]>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.aselement.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.aselement.md
new file mode 100644
index 0000000000000..0df27995230f3
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.aselement.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [asElement](./puppeteer.elementhandle.aselement.md)
+
+## ElementHandle.asElement() method
+
+Signature:
+
+```typescript
+asElement(): ElementHandle | null;
+```
+Returns:
+
+[ElementHandle](./puppeteer.elementhandle.md)<ElementType> \| null
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.boundingbox.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.boundingbox.md
new file mode 100644
index 0000000000000..eda3aaebded22
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.boundingbox.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [boundingBox](./puppeteer.elementhandle.boundingbox.md)
+
+## ElementHandle.boundingBox() method
+
+This method returns the bounding box of the element (relative to the main frame), or `null` if the element is not visible.
+
+Signature:
+
+```typescript
+boundingBox(): Promise;
+```
+Returns:
+
+Promise<[BoundingBox](./puppeteer.boundingbox.md) \| null>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.boxmodel.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.boxmodel.md
new file mode 100644
index 0000000000000..0e1a6679fcb77
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.boxmodel.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [boxModel](./puppeteer.elementhandle.boxmodel.md)
+
+## ElementHandle.boxModel() method
+
+This method returns boxes of the element, or `null` if the element is not visible.
+
+Signature:
+
+```typescript
+boxModel(): Promise;
+```
+Returns:
+
+Promise<[BoxModel](./puppeteer.boxmodel.md) \| null>
+
+## Remarks
+
+Boxes are represented as an array of points; Each Point is an object `{x, y}`. Box points are sorted clock-wise.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.click.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.click.md
new file mode 100644
index 0000000000000..23a2d94a11b89
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.click.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [click](./puppeteer.elementhandle.click.md)
+
+## ElementHandle.click() method
+
+This method scrolls element into view if needed, and then uses [Page.mouse](./puppeteer.page.mouse.md) to click in the center of the element. If the element is detached from DOM, the method throws an error.
+
+Signature:
+
+```typescript
+click(options?: ClickOptions): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| options | [ClickOptions](./puppeteer.clickoptions.md) | |
+
+Returns:
+
+Promise<void>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.clickablepoint.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.clickablepoint.md
new file mode 100644
index 0000000000000..b12f9217a1b9c
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.clickablepoint.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [clickablePoint](./puppeteer.elementhandle.clickablepoint.md)
+
+## ElementHandle.clickablePoint() method
+
+Signature:
+
+```typescript
+clickablePoint(): Promise;
+```
+Returns:
+
+Promise<[Point](./puppeteer.point.md)>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.contentframe.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.contentframe.md
new file mode 100644
index 0000000000000..186e012652ea9
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.contentframe.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [contentFrame](./puppeteer.elementhandle.contentframe.md)
+
+## ElementHandle.contentFrame() method
+
+Resolves to the content frame for element handles referencing iframe nodes, or null otherwise
+
+Signature:
+
+```typescript
+contentFrame(): Promise ;
+```
+Returns:
+
+Promise<[Frame](./puppeteer.frame.md) \| null>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.drag.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.drag.md
new file mode 100644
index 0000000000000..7e16df4e21e4b
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.drag.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [drag](./puppeteer.elementhandle.drag.md)
+
+## ElementHandle.drag() method
+
+This method creates and captures a dragevent from the element.
+
+Signature:
+
+```typescript
+drag(target: Point): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| target | [Point](./puppeteer.point.md) | |
+
+Returns:
+
+Promise<Protocol.Input.DragData>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.draganddrop.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.draganddrop.md
new file mode 100644
index 0000000000000..80c69427199d6
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.draganddrop.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [dragAndDrop](./puppeteer.elementhandle.draganddrop.md)
+
+## ElementHandle.dragAndDrop() method
+
+This method triggers a dragenter, dragover, and drop on the element.
+
+Signature:
+
+```typescript
+dragAndDrop(target: ElementHandle, options?: {
+ delay: number;
+ }): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| target | [ElementHandle](./puppeteer.elementhandle.md) | |
+| options | { delay: number; } | |
+
+Returns:
+
+Promise<void>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.dragenter.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.dragenter.md
new file mode 100644
index 0000000000000..f54d33b134f1f
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.dragenter.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [dragEnter](./puppeteer.elementhandle.dragenter.md)
+
+## ElementHandle.dragEnter() method
+
+This method creates a `dragenter` event on the element.
+
+Signature:
+
+```typescript
+dragEnter(data?: Protocol.Input.DragData): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| data | Protocol.Input.DragData | |
+
+Returns:
+
+Promise<void>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.dragover.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.dragover.md
new file mode 100644
index 0000000000000..73566c1606296
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.dragover.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [dragOver](./puppeteer.elementhandle.dragover.md)
+
+## ElementHandle.dragOver() method
+
+This method creates a `dragover` event on the element.
+
+Signature:
+
+```typescript
+dragOver(data?: Protocol.Input.DragData): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| data | Protocol.Input.DragData | |
+
+Returns:
+
+Promise<void>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.drop.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.drop.md
new file mode 100644
index 0000000000000..b6470e42a7d89
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.drop.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [drop](./puppeteer.elementhandle.drop.md)
+
+## ElementHandle.drop() method
+
+This method triggers a drop on the element.
+
+Signature:
+
+```typescript
+drop(data?: Protocol.Input.DragData): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| data | Protocol.Input.DragData | |
+
+Returns:
+
+Promise<void>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.focus.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.focus.md
new file mode 100644
index 0000000000000..4509b290a9036
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.focus.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [focus](./puppeteer.elementhandle.focus.md)
+
+## ElementHandle.focus() method
+
+Calls [focus](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) on the element.
+
+Signature:
+
+```typescript
+focus(): Promise;
+```
+Returns:
+
+Promise<void>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.hover.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.hover.md
new file mode 100644
index 0000000000000..30943ba1147ea
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.hover.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [hover](./puppeteer.elementhandle.hover.md)
+
+## ElementHandle.hover() method
+
+This method scrolls element into view if needed, and then uses [Page.mouse](./puppeteer.page.mouse.md) to hover over the center of the element. If the element is detached from DOM, the method throws an error.
+
+Signature:
+
+```typescript
+hover(): Promise;
+```
+Returns:
+
+Promise<void>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.isintersectingviewport.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.isintersectingviewport.md
new file mode 100644
index 0000000000000..4621338b8c070
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.isintersectingviewport.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [isIntersectingViewport](./puppeteer.elementhandle.isintersectingviewport.md)
+
+## ElementHandle.isIntersectingViewport() method
+
+Resolves to true if the element is visible in the current viewport.
+
+Signature:
+
+```typescript
+isIntersectingViewport(): Promise;
+```
+Returns:
+
+Promise<boolean>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.md
new file mode 100644
index 0000000000000..8670c0793373b
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.md
@@ -0,0 +1,63 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md)
+
+## ElementHandle class
+
+ElementHandle represents an in-page DOM element.
+
+Signature:
+
+```typescript
+export declare class ElementHandle extends JSHandle
+```
+Extends: [JSHandle](./puppeteer.jshandle.md)<ElementType>
+
+## Remarks
+
+ElementHandles can be created with the [Page.$()](./puppeteer.page._.md) method.
+
+```js
+const puppeteer = require('puppeteer');
+
+(async () => {
+ const browser = await puppeteer.launch();
+ const page = await browser.newPage();
+ await page.goto('https://example.com');
+ const hrefElement = await page.$('a');
+ await hrefElement.click();
+ // ...
+})();
+
+```
+ElementHandle prevents the DOM element from being garbage-collected unless the handle is [disposed](./puppeteer.jshandle.dispose.md). ElementHandles are auto-disposed when their origin frame gets navigated.
+
+ElementHandle instances can be used as arguments in [Page.$eval()](./puppeteer.page._eval.md) and [Page.evaluate()](./puppeteer.page.evaluate.md) methods.
+
+If you're using TypeScript, ElementHandle takes a generic argument that denotes the type of element the handle is holding within. For example, if you have a handle to a `` element, you can type it as `ElementHandle` and you get some nicer type checks.
+
+The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `ElementHandle` class.
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [$(selector)](./puppeteer.elementhandle._.md) | | Runs element.querySelector
within the page. If no element matches the selector, the return value resolves to null
. |
+| [$$(selector)](./puppeteer.elementhandle.__.md) | | Runs element.querySelectorAll
within the page. If no elements match the selector, the return value resolves to []
. |
+| [$$eval(selector, pageFunction, args)](./puppeteer.elementhandle.__eval.md) | | This method runs document.querySelectorAll
within the element and passes it as the first argument to pageFunction
. If there's no element matching selector
, the method throws an error.If pageFunction
returns a Promise, then frame.$$eval
would wait for the promise to resolve and return its value. |
+| [$eval(selector, pageFunction, args)](./puppeteer.elementhandle._eval.md) | | This method runs document.querySelector
within the element and passes it as the first argument to pageFunction
. If there's no element matching selector
, the method throws an error.If pageFunction
returns a Promise, then frame.$eval
would wait for the promise to resolve and return its value. |
+| [$x(expression)](./puppeteer.elementhandle._x.md) | | The method evaluates the XPath expression relative to the elementHandle. If there are no such elements, the method will resolve to an empty array. |
+| [asElement()](./puppeteer.elementhandle.aselement.md) | | |
+| [boundingBox()](./puppeteer.elementhandle.boundingbox.md) | | This method returns the bounding box of the element (relative to the main frame), or null
if the element is not visible. |
+| [boxModel()](./puppeteer.elementhandle.boxmodel.md) | | This method returns boxes of the element, or null
if the element is not visible. |
+| [click(options)](./puppeteer.elementhandle.click.md) | | This method scrolls element into view if needed, and then uses [Page.mouse](./puppeteer.page.mouse.md) to click in the center of the element. If the element is detached from DOM, the method throws an error. |
+| [contentFrame()](./puppeteer.elementhandle.contentframe.md) | | Resolves to the content frame for element handles referencing iframe nodes, or null otherwise |
+| [focus()](./puppeteer.elementhandle.focus.md) | | Calls [focus](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) on the element. |
+| [hover()](./puppeteer.elementhandle.hover.md) | | This method scrolls element into view if needed, and then uses [Page.mouse](./puppeteer.page.mouse.md) to hover over the center of the element. If the element is detached from DOM, the method throws an error. |
+| [isIntersectingViewport()](./puppeteer.elementhandle.isintersectingviewport.md) | | Resolves to true if the element is visible in the current viewport. |
+| [press(key, options)](./puppeteer.elementhandle.press.md) | | Focuses the element, and then uses [Keyboard.down()](./puppeteer.keyboard.down.md) and [Keyboard.up()](./puppeteer.keyboard.up.md). |
+| [screenshot(options)](./puppeteer.elementhandle.screenshot.md) | | This method scrolls element into view if needed, and then uses [Page.screenshot()](./puppeteer.page.screenshot.md) to take a screenshot of the element. If the element is detached from DOM, the method throws an error. |
+| [select(values)](./puppeteer.elementhandle.select.md) | | Triggers a change
and input
event once all the provided options have been selected. If there's no <select>
element matching selector
, the method throws an error. |
+| [tap()](./puppeteer.elementhandle.tap.md) | | This method scrolls element into view if needed, and then uses [Touchscreen.tap()](./puppeteer.touchscreen.tap.md) to tap in the center of the element. If the element is detached from DOM, the method throws an error. |
+| [type(text, options)](./puppeteer.elementhandle.type.md) | | Focuses the element, and then sends a keydown
, keypress
/input
, and keyup
event for each character in the text.To press a special key, like Control
or ArrowDown
, use [ElementHandle.press()](./puppeteer.elementhandle.press.md). |
+| [uploadFile(filePaths)](./puppeteer.elementhandle.uploadfile.md) | | This method expects elementHandle
to point to an [input element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input). |
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.press.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.press.md
new file mode 100644
index 0000000000000..e49749d64090e
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.press.md
@@ -0,0 +1,31 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [press](./puppeteer.elementhandle.press.md)
+
+## ElementHandle.press() method
+
+Focuses the element, and then uses [Keyboard.down()](./puppeteer.keyboard.down.md) and [Keyboard.up()](./puppeteer.keyboard.up.md).
+
+Signature:
+
+```typescript
+press(key: KeyInput, options?: PressOptions): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| key | [KeyInput](./puppeteer.keyinput.md) | Name of key to press, such as ArrowLeft
. See [KeyInput](./puppeteer.keyinput.md) for a list of all key names. |
+| options | [PressOptions](./puppeteer.pressoptions.md) | |
+
+Returns:
+
+Promise<void>
+
+## Remarks
+
+If `key` is a single character and no modifier keys besides `Shift` are being held down, a `keypress`/`input` event will also be generated. The `text` option can be specified to force an input event to be generated.
+
+\*\*NOTE\*\* Modifier keys DO affect `elementHandle.press`. Holding down `Shift` will type the text in upper case.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.screenshot.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.screenshot.md
new file mode 100644
index 0000000000000..b1e3d90c01d39
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.screenshot.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [screenshot](./puppeteer.elementhandle.screenshot.md)
+
+## ElementHandle.screenshot() method
+
+This method scrolls element into view if needed, and then uses [Page.screenshot()](./puppeteer.page.screenshot.md) to take a screenshot of the element. If the element is detached from DOM, the method throws an error.
+
+Signature:
+
+```typescript
+screenshot(options?: {}): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| options | {} | |
+
+Returns:
+
+Promise<string \| Buffer \| void>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.select.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.select.md
new file mode 100644
index 0000000000000..481f58f796182
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.select.md
@@ -0,0 +1,33 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [select](./puppeteer.elementhandle.select.md)
+
+## ElementHandle.select() method
+
+Triggers a `change` and `input` event once all the provided options have been selected. If there's no `` element matching `selector`, the method throws an error.
+
+Signature:
+
+```typescript
+select(...values: string[]): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| values | string\[\] | Values of options to select. If the <select>
has the multiple
attribute, all values are considered, otherwise only the first one is taken into account. |
+
+Returns:
+
+Promise<string\[\]>
+
+## Example
+
+
+```js
+handle.select('blue'); // single selection
+handle.select('red', 'green', 'blue'); // multiple selections
+
+```
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.tap.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.tap.md
new file mode 100644
index 0000000000000..a694242045e3f
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.tap.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [tap](./puppeteer.elementhandle.tap.md)
+
+## ElementHandle.tap() method
+
+This method scrolls element into view if needed, and then uses [Touchscreen.tap()](./puppeteer.touchscreen.tap.md) to tap in the center of the element. If the element is detached from DOM, the method throws an error.
+
+Signature:
+
+```typescript
+tap(): Promise;
+```
+Returns:
+
+Promise<void>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.type.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.type.md
new file mode 100644
index 0000000000000..3a64609b67ad5
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.type.md
@@ -0,0 +1,49 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [type](./puppeteer.elementhandle.type.md)
+
+## ElementHandle.type() method
+
+Focuses the element, and then sends a `keydown`, `keypress`/`input`, and `keyup` event for each character in the text.
+
+To press a special key, like `Control` or `ArrowDown`, use [ElementHandle.press()](./puppeteer.elementhandle.press.md).
+
+Signature:
+
+```typescript
+type(text: string, options?: {
+ delay: number;
+ }): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| text | string | |
+| options | { delay: number; } | |
+
+Returns:
+
+Promise<void>
+
+## Example 1
+
+
+```js
+await elementHandle.type('Hello'); // Types instantly
+await elementHandle.type('World', {delay: 100}); // Types slower, like a user
+
+```
+
+## Example 2
+
+An example of typing into a text field and then submitting the form:
+
+```js
+const elementHandle = await page.$('input');
+await elementHandle.type('some text');
+await elementHandle.press('Enter');
+
+```
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.uploadfile.md b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.uploadfile.md
new file mode 100644
index 0000000000000..a4d1b4cd02ebe
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.elementhandle.uploadfile.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ElementHandle](./puppeteer.elementhandle.md) > [uploadFile](./puppeteer.elementhandle.uploadfile.md)
+
+## ElementHandle.uploadFile() method
+
+This method expects `elementHandle` to point to an [input element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input).
+
+Signature:
+
+```typescript
+uploadFile(...filePaths: string[]): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| filePaths | string\[\] | Sets the value of the file input to these paths. If some of the filePaths
are relative paths, then they are resolved relative to the [current working directory](https://nodejs.org/api/process.html#process_process_cwd) |
+
+Returns:
+
+Promise<void>
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.errorcode.md b/website/versioned_docs/version-10.0.0/puppeteer.errorcode.md
new file mode 100644
index 0000000000000..5ab270c54289d
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.errorcode.md
@@ -0,0 +1,12 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ErrorCode](./puppeteer.errorcode.md)
+
+## ErrorCode type
+
+
+Signature:
+
+```typescript
+export declare type ErrorCode = 'aborted' | 'accessdenied' | 'addressunreachable' | 'blockedbyclient' | 'blockedbyresponse' | 'connectionaborted' | 'connectionclosed' | 'connectionfailed' | 'connectionrefused' | 'connectionreset' | 'internetdisconnected' | 'namenotresolved' | 'timedout' | 'failed';
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.errors.md b/website/versioned_docs/version-10.0.0/puppeteer.errors.md
new file mode 100644
index 0000000000000..15c99b8764d86
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.errors.md
@@ -0,0 +1,12 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [errors](./puppeteer.errors.md)
+
+## errors variable
+
+
+Signature:
+
+```typescript
+errors: PuppeteerErrors
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.evaluatefn.md b/website/versioned_docs/version-10.0.0/puppeteer.evaluatefn.md
new file mode 100644
index 0000000000000..23f80a3476061
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.evaluatefn.md
@@ -0,0 +1,12 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [EvaluateFn](./puppeteer.evaluatefn.md)
+
+## EvaluateFn type
+
+
+Signature:
+
+```typescript
+export declare type EvaluateFn = string | ((arg1: T, ...args: any[]) => any);
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.evaluatefnreturntype.md b/website/versioned_docs/version-10.0.0/puppeteer.evaluatefnreturntype.md
new file mode 100644
index 0000000000000..6829af5ad0385
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.evaluatefnreturntype.md
@@ -0,0 +1,14 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [EvaluateFnReturnType](./puppeteer.evaluatefnreturntype.md)
+
+## EvaluateFnReturnType type
+
+
+Signature:
+
+```typescript
+export declare type EvaluateFnReturnType = T extends (...args: any[]) => infer R ? R : any;
+```
+References: [EvaluateFn](./puppeteer.evaluatefn.md)
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.evaluatehandlefn.md b/website/versioned_docs/version-10.0.0/puppeteer.evaluatehandlefn.md
new file mode 100644
index 0000000000000..22191cfef5c83
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.evaluatehandlefn.md
@@ -0,0 +1,12 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [EvaluateHandleFn](./puppeteer.evaluatehandlefn.md)
+
+## EvaluateHandleFn type
+
+
+Signature:
+
+```typescript
+export declare type EvaluateHandleFn = string | ((...args: any[]) => any);
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.evaluation_script_url.md b/website/versioned_docs/version-10.0.0/puppeteer.evaluation_script_url.md
new file mode 100644
index 0000000000000..ab5912e6c2e03
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.evaluation_script_url.md
@@ -0,0 +1,12 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [EVALUATION\_SCRIPT\_URL](./puppeteer.evaluation_script_url.md)
+
+## EVALUATION\_SCRIPT\_URL variable
+
+
+Signature:
+
+```typescript
+EVALUATION_SCRIPT_URL = "__puppeteer_evaluation_script__"
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.addlistener.md b/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.addlistener.md
new file mode 100644
index 0000000000000..d492b3d88960d
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.addlistener.md
@@ -0,0 +1,30 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [EventEmitter](./puppeteer.eventemitter.md) > [addListener](./puppeteer.eventemitter.addlistener.md)
+
+## EventEmitter.addListener() method
+
+> Warning: This API is now obsolete.
+>
+> please use [EventEmitter.on()](./puppeteer.eventemitter.on.md) instead.
+>
+
+Add an event listener.
+
+Signature:
+
+```typescript
+addListener(event: EventType, handler: Handler): EventEmitter;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| event | [EventType](./puppeteer.eventtype.md) | |
+| handler | [Handler](./puppeteer.handler.md) | |
+
+Returns:
+
+[EventEmitter](./puppeteer.eventemitter.md)
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.emit.md b/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.emit.md
new file mode 100644
index 0000000000000..1db43ac72701c
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.emit.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [EventEmitter](./puppeteer.eventemitter.md) > [emit](./puppeteer.eventemitter.emit.md)
+
+## EventEmitter.emit() method
+
+Emit an event and call any associated listeners.
+
+Signature:
+
+```typescript
+emit(event: EventType, eventData?: unknown): boolean;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| event | [EventType](./puppeteer.eventtype.md) | the event you'd like to emit |
+| eventData | unknown | any data you'd like to emit with the event |
+
+Returns:
+
+boolean
+
+`true` if there are any listeners, `false` if there are not.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.listenercount.md b/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.listenercount.md
new file mode 100644
index 0000000000000..6b6d7792b01eb
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.listenercount.md
@@ -0,0 +1,26 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [EventEmitter](./puppeteer.eventemitter.md) > [listenerCount](./puppeteer.eventemitter.listenercount.md)
+
+## EventEmitter.listenerCount() method
+
+Gets the number of listeners for a given event.
+
+Signature:
+
+```typescript
+listenerCount(event: EventType): number;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| event | [EventType](./puppeteer.eventtype.md) | the event to get the listener count for |
+
+Returns:
+
+number
+
+the number of listeners bound to the given event
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.md b/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.md
new file mode 100644
index 0000000000000..634d9a569edf2
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.md
@@ -0,0 +1,34 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [EventEmitter](./puppeteer.eventemitter.md)
+
+## EventEmitter class
+
+The EventEmitter class that many Puppeteer classes extend.
+
+Signature:
+
+```typescript
+export declare class EventEmitter implements CommonEventEmitter
+```
+Implements: [CommonEventEmitter](./puppeteer.commoneventemitter.md)
+
+## Remarks
+
+This allows you to listen to events that Puppeteer classes fire and act accordingly. Therefore you'll mostly use [on](./puppeteer.eventemitter.on.md) and [off](./puppeteer.eventemitter.off.md) to bind and unbind to event listeners.
+
+The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `EventEmitter` class.
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [addListener(event, handler)](./puppeteer.eventemitter.addlistener.md) | | Add an event listener. |
+| [emit(event, eventData)](./puppeteer.eventemitter.emit.md) | | Emit an event and call any associated listeners. |
+| [listenerCount(event)](./puppeteer.eventemitter.listenercount.md) | | Gets the number of listeners for a given event. |
+| [off(event, handler)](./puppeteer.eventemitter.off.md) | | Remove an event listener from firing. |
+| [on(event, handler)](./puppeteer.eventemitter.on.md) | | Bind an event listener to fire when an event occurs. |
+| [once(event, handler)](./puppeteer.eventemitter.once.md) | | Like on
but the listener will only be fired once and then it will be removed. |
+| [removeAllListeners(event)](./puppeteer.eventemitter.removealllisteners.md) | | Removes all listeners. If given an event argument, it will remove only listeners for that event. |
+| [removeListener(event, handler)](./puppeteer.eventemitter.removelistener.md) | | Remove an event listener. |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.off.md b/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.off.md
new file mode 100644
index 0000000000000..71578a24dcd91
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.off.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [EventEmitter](./puppeteer.eventemitter.md) > [off](./puppeteer.eventemitter.off.md)
+
+## EventEmitter.off() method
+
+Remove an event listener from firing.
+
+Signature:
+
+```typescript
+off(event: EventType, handler: Handler): EventEmitter;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| event | [EventType](./puppeteer.eventtype.md) | the event type you'd like to stop listening to. |
+| handler | [Handler](./puppeteer.handler.md) | the function that should be removed. |
+
+Returns:
+
+[EventEmitter](./puppeteer.eventemitter.md)
+
+`this` to enable you to chain method calls.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.on.md b/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.on.md
new file mode 100644
index 0000000000000..d6bcf47a58c24
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.on.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [EventEmitter](./puppeteer.eventemitter.md) > [on](./puppeteer.eventemitter.on.md)
+
+## EventEmitter.on() method
+
+Bind an event listener to fire when an event occurs.
+
+Signature:
+
+```typescript
+on(event: EventType, handler: Handler): EventEmitter;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| event | [EventType](./puppeteer.eventtype.md) | the event type you'd like to listen to. Can be a string or symbol. |
+| handler | [Handler](./puppeteer.handler.md) | the function to be called when the event occurs. |
+
+Returns:
+
+[EventEmitter](./puppeteer.eventemitter.md)
+
+`this` to enable you to chain method calls.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.once.md b/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.once.md
new file mode 100644
index 0000000000000..39cd7dd483ee4
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.once.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [EventEmitter](./puppeteer.eventemitter.md) > [once](./puppeteer.eventemitter.once.md)
+
+## EventEmitter.once() method
+
+Like `on` but the listener will only be fired once and then it will be removed.
+
+Signature:
+
+```typescript
+once(event: EventType, handler: Handler): EventEmitter;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| event | [EventType](./puppeteer.eventtype.md) | the event you'd like to listen to |
+| handler | [Handler](./puppeteer.handler.md) | the handler function to run when the event occurs |
+
+Returns:
+
+[EventEmitter](./puppeteer.eventemitter.md)
+
+`this` to enable you to chain method calls.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.removealllisteners.md b/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.removealllisteners.md
new file mode 100644
index 0000000000000..d03627c97070f
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.removealllisteners.md
@@ -0,0 +1,26 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [EventEmitter](./puppeteer.eventemitter.md) > [removeAllListeners](./puppeteer.eventemitter.removealllisteners.md)
+
+## EventEmitter.removeAllListeners() method
+
+Removes all listeners. If given an event argument, it will remove only listeners for that event.
+
+Signature:
+
+```typescript
+removeAllListeners(event?: EventType): EventEmitter;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| event | [EventType](./puppeteer.eventtype.md) | the event to remove listeners for. |
+
+Returns:
+
+[EventEmitter](./puppeteer.eventemitter.md)
+
+`this` to enable you to chain method calls.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.removelistener.md b/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.removelistener.md
new file mode 100644
index 0000000000000..c388eb33002d0
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.eventemitter.removelistener.md
@@ -0,0 +1,30 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [EventEmitter](./puppeteer.eventemitter.md) > [removeListener](./puppeteer.eventemitter.removelistener.md)
+
+## EventEmitter.removeListener() method
+
+> Warning: This API is now obsolete.
+>
+> please use [EventEmitter.off()](./puppeteer.eventemitter.off.md) instead.
+>
+
+Remove an event listener.
+
+Signature:
+
+```typescript
+removeListener(event: EventType, handler: Handler): EventEmitter;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| event | [EventType](./puppeteer.eventtype.md) | |
+| handler | [Handler](./puppeteer.handler.md) | |
+
+Returns:
+
+[EventEmitter](./puppeteer.eventemitter.md)
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.eventtype.md b/website/versioned_docs/version-10.0.0/puppeteer.eventtype.md
new file mode 100644
index 0000000000000..81a51014baaee
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.eventtype.md
@@ -0,0 +1,12 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [EventType](./puppeteer.eventtype.md)
+
+## EventType type
+
+
+Signature:
+
+```typescript
+export declare type EventType = string | symbol;
+```
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.executioncontext.evaluate.md b/website/versioned_docs/version-10.0.0/puppeteer.executioncontext.evaluate.md
new file mode 100644
index 0000000000000..e1e532814ab63
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.executioncontext.evaluate.md
@@ -0,0 +1,64 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ExecutionContext](./puppeteer.executioncontext.md) > [evaluate](./puppeteer.executioncontext.evaluate.md)
+
+## ExecutionContext.evaluate() method
+
+Signature:
+
+```typescript
+evaluate(pageFunction: Function | string, ...args: unknown[]): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| pageFunction | Function \| string | a function to be evaluated in the executionContext
|
+| args | unknown\[\] | argument to pass to the page function |
+
+Returns:
+
+Promise<ReturnType>
+
+A promise that resolves to the return value of the given function.
+
+## Remarks
+
+If the function passed to the `executionContext.evaluate` returns a Promise, then `executionContext.evaluate` would wait for the promise to resolve and return its value. If the function passed to the `executionContext.evaluate` returns a non-serializable value, then `executionContext.evaluate` resolves to `undefined`. DevTools Protocol also supports transferring some additional values that are not serializable by `JSON`: `-0`, `NaN`, `Infinity`, `-Infinity`, and bigint literals.
+
+## Example 1
+
+
+```js
+const executionContext = await page.mainFrame().executionContext();
+const result = await executionContext.evaluate(() => Promise.resolve(8 * 7))* ;
+console.log(result); // prints "56"
+
+```
+
+## Example 2
+
+A string can also be passed in instead of a function.
+
+```js
+console.log(await executionContext.evaluate('1 + 2')); // prints "3"
+
+```
+
+## Example 3
+
+[JSHandle](./puppeteer.jshandle.md) instances can be passed as arguments to the `executionContext.* evaluate`:
+
+```js
+const oneHandle = await executionContext.evaluateHandle(() => 1);
+const twoHandle = await executionContext.evaluateHandle(() => 2);
+const result = await executionContext.evaluate(
+ (a, b) => a + b, oneHandle, * twoHandle
+);
+await oneHandle.dispose();
+await twoHandle.dispose();
+console.log(result); // prints '3'.
+
+```
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.executioncontext.evaluatehandle.md b/website/versioned_docs/version-10.0.0/puppeteer.executioncontext.evaluatehandle.md
new file mode 100644
index 0000000000000..e8c9e3d4da891
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.executioncontext.evaluatehandle.md
@@ -0,0 +1,62 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ExecutionContext](./puppeteer.executioncontext.md) > [evaluateHandle](./puppeteer.executioncontext.evaluatehandle.md)
+
+## ExecutionContext.evaluateHandle() method
+
+Signature:
+
+```typescript
+evaluateHandle(pageFunction: EvaluateHandleFn, ...args: SerializableOrJSHandle[]): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| pageFunction | [EvaluateHandleFn](./puppeteer.evaluatehandlefn.md) | a function to be evaluated in the executionContext
|
+| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)\[\] | argument to pass to the page function |
+
+Returns:
+
+Promise<HandleType>
+
+A promise that resolves to the return value of the given function as an in-page object (a [JSHandle](./puppeteer.jshandle.md)).
+
+## Remarks
+
+The only difference between `executionContext.evaluate` and `executionContext.evaluateHandle` is that `executionContext.evaluateHandle` returns an in-page object (a [JSHandle](./puppeteer.jshandle.md)). If the function passed to the `executionContext.evaluateHandle` returns a Promise, then `executionContext.evaluateHandle` would wait for the promise to resolve and return its value.
+
+## Example 1
+
+
+```js
+const context = await page.mainFrame().executionContext();
+const aHandle = await context.evaluateHandle(() => Promise.resolve(self));
+aHandle; // Handle for the global object.
+
+```
+
+## Example 2
+
+A string can also be passed in instead of a function.
+
+```js
+// Handle for the '3' * object.
+const aHandle = await context.evaluateHandle('1 + 2');
+
+```
+
+## Example 3
+
+JSHandle instances can be passed as arguments to the `executionContext.* evaluateHandle`:
+
+```js
+const aHandle = await context.evaluateHandle(() => document.body);
+const resultHandle = await context.evaluateHandle(body => body.innerHTML, * aHandle);
+console.log(await resultHandle.jsonValue()); // prints body's innerHTML
+await aHandle.dispose();
+await resultHandle.dispose();
+
+```
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.executioncontext.frame.md b/website/versioned_docs/version-10.0.0/puppeteer.executioncontext.frame.md
new file mode 100644
index 0000000000000..be7d61d40b0f8
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.executioncontext.frame.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ExecutionContext](./puppeteer.executioncontext.md) > [frame](./puppeteer.executioncontext.frame.md)
+
+## ExecutionContext.frame() method
+
+Signature:
+
+```typescript
+frame(): Frame | null;
+```
+Returns:
+
+[Frame](./puppeteer.frame.md) \| null
+
+The frame associated with this execution context.
+
+## Remarks
+
+Not every execution context is associated with a frame. For example, workers and extensions have execution contexts that are not associated with frames.
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.executioncontext.md b/website/versioned_docs/version-10.0.0/puppeteer.executioncontext.md
new file mode 100644
index 0000000000000..88699a5d32d77
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.executioncontext.md
@@ -0,0 +1,29 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ExecutionContext](./puppeteer.executioncontext.md)
+
+## ExecutionContext class
+
+This class represents a context for JavaScript execution. A \[Page\] might have many execution contexts: - each [frame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe) has "default" execution context that is always created after frame is attached to DOM. This context is returned by the [Frame.executionContext()](./puppeteer.frame.executioncontext.md) method. - [Extension](https://developer.chrome.com/extensions)'s content scripts create additional execution contexts.
+
+Besides pages, execution contexts can be found in [workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API).
+
+Signature:
+
+```typescript
+export declare class ExecutionContext
+```
+
+## Remarks
+
+The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `ExecutionContext` class.
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [evaluate(pageFunction, args)](./puppeteer.executioncontext.evaluate.md) | | |
+| [evaluateHandle(pageFunction, args)](./puppeteer.executioncontext.evaluatehandle.md) | | |
+| [frame()](./puppeteer.executioncontext.frame.md) | | |
+| [queryObjects(prototypeHandle)](./puppeteer.executioncontext.queryobjects.md) | | This method iterates the JavaScript heap and finds all the objects with the given prototype. |
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.executioncontext.queryobjects.md b/website/versioned_docs/version-10.0.0/puppeteer.executioncontext.queryobjects.md
new file mode 100644
index 0000000000000..abeea9c340f53
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.executioncontext.queryobjects.md
@@ -0,0 +1,46 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [ExecutionContext](./puppeteer.executioncontext.md) > [queryObjects](./puppeteer.executioncontext.queryobjects.md)
+
+## ExecutionContext.queryObjects() method
+
+This method iterates the JavaScript heap and finds all the objects with the given prototype.
+
+Signature:
+
+```typescript
+queryObjects(prototypeHandle: JSHandle): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| prototypeHandle | [JSHandle](./puppeteer.jshandle.md) | a handle to the object prototype |
+
+Returns:
+
+Promise<[JSHandle](./puppeteer.jshandle.md)>
+
+A handle to an array of objects with the given prototype.
+
+## Remarks
+
+
+## Example
+
+
+```js
+// Create a Map object
+await page.evaluate(() => window.map = new Map());
+// Get a handle to the Map object prototype
+const mapPrototype = await page.evaluateHandle(() => Map.prototype);
+// Query all map instances into an array
+const mapInstances = await page.queryObjects(mapPrototype);
+// Count amount of map objects in heap
+const count = await page.evaluate(maps => maps.length, mapInstances);
+await mapInstances.dispose();
+await mapPrototype.dispose();
+
+```
+
diff --git a/website/versioned_docs/version-10.0.0/puppeteer.filechooser.accept.md b/website/versioned_docs/version-10.0.0/puppeteer.filechooser.accept.md
new file mode 100644
index 0000000000000..173ec7c659fe8
--- /dev/null
+++ b/website/versioned_docs/version-10.0.0/puppeteer.filechooser.accept.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [puppeteer](./puppeteer.md) > [FileChooser](./puppeteer.filechooser.md) > [accept](./puppeteer.filechooser.accept.md)
+
+## FileChooser.accept() method
+
+Accept the file chooser request with given paths.
+
+Signature:
+
+```typescript
+accept(filePaths: string[]): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| filePaths | string\[\] | If some of the