Bug 1632710 - [puppeteer] vendor v3.1.0 r=remote-protocol-reviewers,whimboo,jgraham
authorMaja Frydrychowicz <mjzffr@gmail.com>
Fri, 05 Jun 2020 18:53:38 +0000
changeset 534188 dadc7312128e70919054924ab980f4344c504da2
parent 534187 b57a835e980a2ff920e9b805e60df9f2221d0d56
child 534189 969dfa0cd82e9ad8322233ad8c27d6c1a80008f4
push id37483
push userapavel@mozilla.com
push dateFri, 05 Jun 2020 21:40:11 +0000
treeherdermozilla-central@dadc7312128e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersremote-protocol-reviewers, whimboo, jgraham
bugs1632710
milestone79.0a1
first release with
nightly linux32
dadc7312128e / 79.0a1 / 20200605214011 / files
nightly linux64
dadc7312128e / 79.0a1 / 20200605214011 / files
nightly mac
dadc7312128e / 79.0a1 / 20200605214011 / files
nightly win32
dadc7312128e / 79.0a1 / 20200605214011 / files
nightly win64
dadc7312128e / 79.0a1 / 20200605214011 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1632710 - [puppeteer] vendor v3.1.0 r=remote-protocol-reviewers,whimboo,jgraham This requires a custom mocha reporter under puppeteer/ and changes in output parsing. Differential Revision: https://phabricator.services.mozilla.com/D77625
.hgignore
remote/doc/PuppeteerVendor.md
remote/doc/Testing.md
remote/mach_commands.py
remote/puppeteer-expected.json
remote/test/puppeteer/.appveyor.yml
remote/test/puppeteer/.ci/node10/Dockerfile.linux
remote/test/puppeteer/.ci/node12/Dockerfile.linux
remote/test/puppeteer/.ci/node8/Dockerfile.linux
remote/test/puppeteer/.cirrus.yml
remote/test/puppeteer/.eslintignore
remote/test/puppeteer/.eslintrc.js
remote/test/puppeteer/.npmignore
remote/test/puppeteer/.travis.yml
remote/test/puppeteer/CONTRIBUTING.md
remote/test/puppeteer/README.md
remote/test/puppeteer/docs/api.md
remote/test/puppeteer/docs/troubleshooting.md
remote/test/puppeteer/examples/README.md
remote/test/puppeteer/examples/block-images.js
remote/test/puppeteer/examples/cross-browser.js
remote/test/puppeteer/examples/custom-event.js
remote/test/puppeteer/examples/detect-sniff.js
remote/test/puppeteer/examples/pdf.js
remote/test/puppeteer/examples/proxy.js
remote/test/puppeteer/examples/screenshot-fullpage.js
remote/test/puppeteer/examples/screenshot.js
remote/test/puppeteer/examples/search.js
remote/test/puppeteer/index.js
remote/test/puppeteer/install.js
remote/test/puppeteer/json-mocha-reporter.js
remote/test/puppeteer/lib/.eslintrc.js
remote/test/puppeteer/lib/Accessibility.js
remote/test/puppeteer/lib/Browser.js
remote/test/puppeteer/lib/BrowserFetcher.js
remote/test/puppeteer/lib/Connection.js
remote/test/puppeteer/lib/Coverage.js
remote/test/puppeteer/lib/DOMWorld.js
remote/test/puppeteer/lib/DeviceDescriptors.js
remote/test/puppeteer/lib/Dialog.js
remote/test/puppeteer/lib/EmulationManager.js
remote/test/puppeteer/lib/Errors.js
remote/test/puppeteer/lib/Events.js
remote/test/puppeteer/lib/ExecutionContext.js
remote/test/puppeteer/lib/FrameManager.js
remote/test/puppeteer/lib/Input.js
remote/test/puppeteer/lib/JSHandle.js
remote/test/puppeteer/lib/Launcher.js
remote/test/puppeteer/lib/LifecycleWatcher.js
remote/test/puppeteer/lib/Multimap.js
remote/test/puppeteer/lib/NetworkManager.js
remote/test/puppeteer/lib/Page.js
remote/test/puppeteer/lib/PipeTransport.js
remote/test/puppeteer/lib/Puppeteer.js
remote/test/puppeteer/lib/Target.js
remote/test/puppeteer/lib/TaskQueue.js
remote/test/puppeteer/lib/TimeoutSettings.js
remote/test/puppeteer/lib/Tracing.js
remote/test/puppeteer/lib/USKeyboardLayout.js
remote/test/puppeteer/lib/WebSocketTransport.js
remote/test/puppeteer/lib/Worker.js
remote/test/puppeteer/lib/api.js
remote/test/puppeteer/lib/externs.d.ts
remote/test/puppeteer/lib/helper.js
remote/test/puppeteer/mocha-config/base.js
remote/test/puppeteer/mocha-config/coverage-tests.js
remote/test/puppeteer/mocha-config/doclint-tests.js
remote/test/puppeteer/mocha-config/puppeteer-unit-tests.js
remote/test/puppeteer/moz.yaml
remote/test/puppeteer/package.json
remote/test/puppeteer/prettier.config.js
remote/test/puppeteer/scripts/test-install.sh
remote/test/puppeteer/src/.eslintrc.js
remote/test/puppeteer/src/Accessibility.ts
remote/test/puppeteer/src/Browser.ts
remote/test/puppeteer/src/BrowserFetcher.ts
remote/test/puppeteer/src/Connection.ts
remote/test/puppeteer/src/ConnectionTransport.ts
remote/test/puppeteer/src/ConsoleMessage.ts
remote/test/puppeteer/src/Coverage.ts
remote/test/puppeteer/src/DOMWorld.ts
remote/test/puppeteer/src/DeviceDescriptors.ts
remote/test/puppeteer/src/Dialog.ts
remote/test/puppeteer/src/EmulationManager.ts
remote/test/puppeteer/src/Errors.ts
remote/test/puppeteer/src/Events.ts
remote/test/puppeteer/src/ExecutionContext.ts
remote/test/puppeteer/src/FileChooser.ts
remote/test/puppeteer/src/FrameManager.ts
remote/test/puppeteer/src/Input.ts
remote/test/puppeteer/src/JSHandle.ts
remote/test/puppeteer/src/Launcher.ts
remote/test/puppeteer/src/LifecycleWatcher.ts
remote/test/puppeteer/src/NetworkManager.ts
remote/test/puppeteer/src/Page.ts
remote/test/puppeteer/src/PipeTransport.ts
remote/test/puppeteer/src/Puppeteer.ts
remote/test/puppeteer/src/PuppeteerViewport.ts
remote/test/puppeteer/src/QueryHandler.ts
remote/test/puppeteer/src/Request.ts
remote/test/puppeteer/src/Response.ts
remote/test/puppeteer/src/SecurityDetails.ts
remote/test/puppeteer/src/Target.ts
remote/test/puppeteer/src/TimeoutSettings.ts
remote/test/puppeteer/src/Tracing.ts
remote/test/puppeteer/src/USKeyboardLayout.ts
remote/test/puppeteer/src/WebSocketTransport.ts
remote/test/puppeteer/src/Worker.ts
remote/test/puppeteer/src/api.ts
remote/test/puppeteer/src/helper.ts
remote/test/puppeteer/src/launcher/BrowserRunner.ts
remote/test/puppeteer/src/launcher/LaunchOptions.ts
remote/test/puppeteer/src/protocol.d.ts
remote/test/puppeteer/test/CDPSession.spec.js
remote/test/puppeteer/test/README.md
remote/test/puppeteer/test/accessibility.spec.js
remote/test/puppeteer/test/assert-coverage-test.js
remote/test/puppeteer/test/assets/es6/es6import.js
remote/test/puppeteer/test/assets/es6/es6module.js
remote/test/puppeteer/test/assets/es6/es6pathimport.js
remote/test/puppeteer/test/assets/firefox-75.0a1.en-US.linux-x86_64.tar.bz2
remote/test/puppeteer/test/assets/injectedfile.js
remote/test/puppeteer/test/assets/input/mouse-helper.js
remote/test/puppeteer/test/assets/serviceworkers/fetch/sw.js
remote/test/puppeteer/test/assets/simple-extension/content-script.js
remote/test/puppeteer/test/assets/worker/worker.js
remote/test/puppeteer/test/browser.spec.js
remote/test/puppeteer/test/browsercontext.spec.js
remote/test/puppeteer/test/chromiumonly.spec.js
remote/test/puppeteer/test/click.spec.js
remote/test/puppeteer/test/cookies.spec.js
remote/test/puppeteer/test/coverage-utils.js
remote/test/puppeteer/test/coverage.spec.js
remote/test/puppeteer/test/defaultbrowsercontext.spec.js
remote/test/puppeteer/test/dialog.spec.js
remote/test/puppeteer/test/elementhandle.spec.js
remote/test/puppeteer/test/emulation.spec.js
remote/test/puppeteer/test/evaluation.spec.js
remote/test/puppeteer/test/fixtures.spec.js
remote/test/puppeteer/test/fixtures/closeme.js
remote/test/puppeteer/test/fixtures/dumpio.js
remote/test/puppeteer/test/frame.spec.js
remote/test/puppeteer/test/golden-utils.js
remote/test/puppeteer/test/headful.spec.js
remote/test/puppeteer/test/ignorehttpserrors.spec.js
remote/test/puppeteer/test/input.spec.js
remote/test/puppeteer/test/jshandle.spec.js
remote/test/puppeteer/test/keyboard.spec.js
remote/test/puppeteer/test/launcher.spec.js
remote/test/puppeteer/test/mocha-utils.js
remote/test/puppeteer/test/mouse.spec.js
remote/test/puppeteer/test/navigation.spec.js
remote/test/puppeteer/test/network.spec.js
remote/test/puppeteer/test/oopif.spec.js
remote/test/puppeteer/test/page.spec.js
remote/test/puppeteer/test/puppeteer.spec.js
remote/test/puppeteer/test/queryselector.spec.js
remote/test/puppeteer/test/requestinterception.spec.js
remote/test/puppeteer/test/run_static_server.js
remote/test/puppeteer/test/screenshot.spec.js
remote/test/puppeteer/test/target.spec.js
remote/test/puppeteer/test/test.js
remote/test/puppeteer/test/touchscreen.spec.js
remote/test/puppeteer/test/tracing.spec.js
remote/test/puppeteer/test/utils.js
remote/test/puppeteer/test/waittask.spec.js
remote/test/puppeteer/test/worker.spec.js
remote/test/puppeteer/tsconfig.json
remote/test/puppeteer/typescript-if-required.js
remote/test/puppeteer/utils/ESTreeWalker.js
remote/test/puppeteer/utils/apply_next_version.js
remote/test/puppeteer/utils/bisect.js
remote/test/puppeteer/utils/browser/README.md
remote/test/puppeteer/utils/browser/WebSocket.js
remote/test/puppeteer/utils/browser/test.js
remote/test/puppeteer/utils/check_availability.js
remote/test/puppeteer/utils/doclint/README.md
remote/test/puppeteer/utils/doclint/Source.js
remote/test/puppeteer/utils/doclint/check_public_api/Documentation.js
remote/test/puppeteer/utils/doclint/check_public_api/JSBuilder.js
remote/test/puppeteer/utils/doclint/check_public_api/MDBuilder.js
remote/test/puppeteer/utils/doclint/check_public_api/index.js
remote/test/puppeteer/utils/doclint/check_public_api/test/public-api.spec.js
remote/test/puppeteer/utils/doclint/check_public_api/test/test.js
remote/test/puppeteer/utils/doclint/cli.js
remote/test/puppeteer/utils/doclint/generate_types/index.js
remote/test/puppeteer/utils/doclint/preprocessor/index.js
remote/test/puppeteer/utils/doclint/preprocessor/preprocessor.spec.js
remote/test/puppeteer/utils/doclint/preprocessor/test.js
remote/test/puppeteer/utils/fetch_devices.js
remote/test/puppeteer/utils/flakiness-dashboard/FlakinessDashboard.js
remote/test/puppeteer/utils/flakiness-dashboard/index.js
remote/test/puppeteer/utils/prepare_puppeteer_core.js
remote/test/puppeteer/utils/protocol-types-generator/index.js
remote/test/puppeteer/utils/testrunner/.npmignore
remote/test/puppeteer/utils/testrunner/LICENSE
remote/test/puppeteer/utils/testrunner/Matchers.js
remote/test/puppeteer/utils/testrunner/Multimap.js
remote/test/puppeteer/utils/testrunner/README.md
remote/test/puppeteer/utils/testrunner/Reporter.js
remote/test/puppeteer/utils/testrunner/TestRunner.js
remote/test/puppeteer/utils/testrunner/examples/fail.js
remote/test/puppeteer/utils/testrunner/examples/hookfail.js
remote/test/puppeteer/utils/testrunner/examples/hooktimeout.js
remote/test/puppeteer/utils/testrunner/examples/timeout.js
remote/test/puppeteer/utils/testrunner/examples/unhandledpromiserejection.js
remote/test/puppeteer/utils/testrunner/index.js
remote/test/puppeteer/utils/testrunner/package.json
remote/test/puppeteer/utils/testrunner/test/test.js
remote/test/puppeteer/utils/testrunner/test/testrunner.spec.js
remote/test/puppeteer/utils/testserver/cert.pem
remote/test/puppeteer/utils/testserver/index.js
remote/test/puppeteer/utils/testserver/key.pem
--- a/.hgignore
+++ b/.hgignore
@@ -101,17 +101,20 @@ compile_commands\.json
 # Ignore node_modules directories in devtools
 ^devtools/.*/node_modules/
 
 # Ignore node_module directories and npm artifacts
 ^remote/test/puppeteer/package-lock.json
 ^remote/test/puppeteer/node_modules/
 ^remote/test/puppeteer/.local-chromium/
 ^remote/test/puppeteer/.local-firefox/
-^remote/test/puppeteer/experimental
+^remote/test/puppeteer/experimental/
+^remote/test/puppeteer/lib/
+^remote/test/puppeteer/test/output-firefox
+^remote/test/puppeteer/test/output-chromium
 
 # git checkout of libstagefright
 ^media/libstagefright/android$
 
 # Tag files generated by GNU Global
 (^|/)GTAGS$
 (^|/)GRTAGS$
 (^|/)GSYMS$
--- a/remote/doc/PuppeteerVendor.md
+++ b/remote/doc/PuppeteerVendor.md
@@ -1,24 +1,14 @@
 Vendoring Puppeteer
 ===================
 
 As mentioned in the chapter on [Testing], we run the full [Puppeteer
 test suite] on try.  These tests are vendored in central under
 _remote/test/puppeteer/_ and we have a script to pull in the most
 recent changes.
 
-We currently use [a fork of Puppeteer] with a small number of tweaks
-and workaround to make them run against Firefox.  These changes
-will be upstreamed to Puppeteer when we feel more comfortable about
-compatibility with Chrome.
-
 To update the vendored copy of Puppeteer under _remote/test/puppeteer/_:
 
 	% ./mach remote vendor-puppeteer
 
-This will replace the current checkout with a fresh copy from
-the predefined remote, which currently is the `firefox` branch on
-https://github.com/andreastt/puppeteer.git.  You can override which
-remote and commit to use.
-
 [Testing]: ./Testing.html
 [Puppeteer test suite]: https://github.com/GoogleChrome/puppeteer/tree/master/test
--- a/remote/doc/Testing.md
+++ b/remote/doc/Testing.md
@@ -104,65 +104,34 @@ original `add_task()`.
 [CDP client]: https://github.com/cyrus-and/chrome-remote-interface
 
 
 Puppeteer tests
 ---------------
 
 In addition to our own Firefox-specific tests, we run the upstream
 [Puppeteer test suite] against our implementation to [track progress]
-towards achieving full [Puppeteer support] in Firefox.
+towards achieving full [Puppeteer support] in Firefox. The tests are written
+in the behaviour-driven testing framework [Mocha].
 
-These tests are vendored under _remote/test/puppeteer/_ and are
+Puppeteer tests are vendored under _remote/test/puppeteer/_ and are
 run locally like this:
 
-	% ./mach test remote/test/puppeteer/test
+	% ./mach puppeteer-test
 
-On try they appear under the `remote(pup)` symbol, but because they’re
-a Tier-3 class test job they’re not automatically scheduled.
-To schedule the tests, look for `source-test-remote-puppeteer` in
-`./mach try fuzzy`.
+You can also run them against Chrome as:
 
-The tests are written in the behaviour-driven testing framework
-[Mocha], which doesn’t support selecting tests by file path like
-other harnesses.  It does however come with a myriad of flags to
-narrow down the selection a bit: some of the most useful ones include
-[`--grep`], [`--ignore`], and [`--file`].
+  % ./mach puppeteer-test --product=chrome --subset
 
-Perhaps the most useful trick is to rename the `describe()` or
-`it()` functions to `fdescribe()` and `fit()`, respectively, in
-order to run a specific subset of tests.  This does however force
-you to edit the test files manually.
-
-A real-world example:
+`--subset` disables a check for missing or skipped tests in our log parsing.
+This check is typically not relevant when running against Chrome.
 
-```diff
-diff --git a/remote/test/puppeteer/test/frame.spec.js b/remote/test/puppeteer/test/frame.spec.js
-index 58e57934a7b8..0531d49d7a12 100644
---- a/remote/test/puppeteer/test/frame.spec.js
-+++ b/remote/test/puppeteer/test/frame.spec.js
-@@ -48,7 +48,7 @@ module.exports.addTests = function({testRunner, expect}) {
-     });
-   });
+To schedule the tests on try, look for `source-test-remote-puppeteer` in
+`./mach try fuzzy`. On try they appear under the `remote(pup)` symbol.
 
--  describe('Frame.evaluateHandle', function() {
-+  fdescribe('Frame.evaluateHandle', function() {
-     it('should work', async({page, server}) => {
-       await page.goto(server.EMPTY_PAGE);
-       const mainFrame = page.mainFrame();
-@@ -58,7 +58,7 @@ module.exports.addTests = function({testRunner, expect}) {
-   });
+Test expectation metadata is collected in _remote/puppeteer-expected.json_
+via log parsing and a custom Mocha reporter under
+_remote/test/puppeteer/json-mocha-reporter.js_
 
-   describe('Frame.evaluate', function() {
--    it('should throw for detached frames', async({page, server}) => {
-+    fit('should throw for detached frames', async({page, server}) => {
-       const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
-       await utils.detachFrame(page, 'frame1');
-       let error = null;
-```
-
-[Puppeteer test suite]: https://github.com/GoogleChrome/puppeteer/tree/master/test
-[track progress]: https://docs.google.com/spreadsheets/d/1GZ4yO2-NGD6kbsT7aMlUPUpUqTaASpqNxJGKhOQ-_BM/edit#gid=0
+[Puppeteer test suite]: https://github.com/puppeteer/puppeteer/blob/master/test/README.md
+[track progress]: https://puppeteer.github.io/ispuppeteerfirefoxready/
 [Puppeteer support]: https://bugzilla.mozilla.org/show_bug.cgi?id=puppeteer
 [Mocha]: https://mochajs.org/
-[`--grep`]: https://mochajs.org/#-grep-regexp-g-regexp
-[`--ignore`]: https://mochajs.org/#-ignore-filedirectoryglob
-[`--file`]: https://mochajs.org/#-file-filedirectoryglob
--- a/remote/mach_commands.py
+++ b/remote/mach_commands.py
@@ -7,17 +7,16 @@ from __future__ import (
     print_function,
     unicode_literals,
 )
 
 import argparse
 import json
 import sys
 import os
-import re
 import tempfile
 import shutil
 import subprocess
 from collections import OrderedDict
 
 from six import iteritems
 
 from mach.decorators import (
@@ -167,76 +166,101 @@ def npm(*args, **kwargs):
     if not kwargs.get("wait", True):
         return p
 
     wait_proc(p, cmd="npm", exit_on_fail=kwargs.get("exit_on_fail", True))
 
     return p.returncode
 
 
-def wait_proc(p, cmd=None, exit_on_fail=True):
+def wait_proc(p, cmd=None, exit_on_fail=True, output_timeout=None):
     try:
-        p.run()
+        p.run(outputTimeout=output_timeout)
         p.wait()
+        if p.timedOut:
+            # In some cases, we wait longer for a mocha timeout
+            print("Timed out after {} seconds of no output".format(output_timeout))
     finally:
         p.kill()
     if exit_on_fail and p.returncode > 0:
         msg = ("%s: exit code %s" % (cmd, p.returncode) if cmd
                else "exit code %s" % p.returncode)
         exit(p.returncode, msg)
 
 
 class MochaOutputHandler(object):
     def __init__(self, logger, expected):
         self.logger = logger
-        self.test_name_re = re.compile(r"\d+\) \[\s*(\w+)\s*\] (.*) \(.*\)")
-        self.control_re = re.compile("\x1b\\[[0-9]*m")
         self.proc = None
         self.test_results = OrderedDict()
         self.expected = expected
 
         self.has_unexpected = False
         self.logger.suite_start([], name="puppeteer-tests")
         self.status_map = {
             "CRASHED": "CRASH",
             "OK": "PASS",
             "TERMINATED": "CRASH",
-            "TIME": "TIMEOUT",
+            "pass": "PASS",
+            "fail": "FAIL",
+            "pending": "SKIP"
         }
 
     @property
     def pid(self):
         return self.proc and self.proc.pid
 
     def __call__(self, line):
-        line_text = self.control_re.subn("", line)[0]
-        m = self.test_name_re.match(line_text)
-        if m:
-            status, test_name = m.groups()
-            status = self.status_map.get(status, status)
+        event = None
+        try:
+            if line.startswith('[') and line.endswith(']'):
+                event = json.loads(line)
+            self.process_event(event)
+        except ValueError:
+            pass
+        finally:
+            self.logger.process_output(self.pid, line, command="npm")
+
+    def process_event(self, event):
+        if isinstance(event, list) and len(event) > 1:
+            status = self.status_map.get(event[0])
+            test_start = event[0] == 'test-start'
+            if not status and not test_start:
+                return
+            test_info = event[1]
+            test_name = test_info.get("fullTitle", "")
+            test_path = test_info.get("file", "")
+            test_err = test_info.get("err")
+            if status == "FAIL" and test_err:
+                if "timeout" in test_err.lower():
+                    status = "TIMEOUT"
+            if test_name and test_path:
+                test_name = "{} ({})".format(test_name, os.path.basename(test_path))
+            if test_start:
+                self.logger.test_start(test_name)
+                return
             # mozlog doesn't really allow unexpected skip,
-            # so if a test is disabled just expect that
+            # so if a test is disabled just expect that.
+            # Also, mocha doesn't log test-start for skipped tests
             if status == "SKIP":
+                self.logger.test_start(test_name)
                 expected = ["SKIP"]
             else:
                 expected = self.expected.get(test_name, ["PASS"])
             known_intermittent = expected[1:]
             expected_status = expected[0]
 
             self.test_results[test_name] = status
-
-            self.logger.test_start(test_name)
             self.logger.test_end(test_name,
                                  status=status,
                                  expected=expected_status,
                                  known_intermittent=known_intermittent)
 
             if status not in expected:
                 self.has_unexpected = True
-        self.logger.process_output(self.pid, line, command="npm")
 
     def new_expected(self):
         new_expected = OrderedDict()
         for test_name, status in iteritems(self.test_results):
             if test_name not in self.expected:
                 new_status = [status]
             else:
                 if status in self.expected[test_name]:
@@ -310,26 +334,39 @@ class PuppeteerRunner(MozbuildObject):
           Indicates only a subset of tests are being run, so we should
           skip the check for missing results
         """
         setup()
 
         binary = params.get("binary") or self.get_binary_path()
         product = params.get("product", "firefox")
 
-        env = {"DUMPIO": "1"}
+        env = {
+            # Print browser process ouptut
+            "DUMPIO": "1",
+            # Checked by Puppeteer's custom mocha config
+            "CI": "1",
+            # Causes some tests to be skipped due to assumptions about install
+            "PUPPETEER_ALT_INSTALL": "1"
+        }
         extra_options = {}
         for k, v in params.get("extra_launcher_options", {}).items():
             extra_options[k] = json.loads(v)
 
+        # Override upstream defaults: no retries, shorter timeout
+        mocha_options = [
+            "--reporter", "./json-mocha-reporter.js",
+            "--retries", "0",
+            "--fullTrace",
+            "--timeout", "15000"
+        ]
         if product == "firefox":
             env["BINARY"] = binary
-            command = ["run", "funit", "--", "--verbose"]
-        elif product == "chrome":
-            command = ["run", "unit", "--", "--verbose"]
+            env["PUPPETEER_PRODUCT"] = "firefox"
+        command = ["run", "unit", "--"] + mocha_options
 
         if params.get("jobs"):
             env["PPTR_PARALLEL_TESTS"] = str(params["jobs"])
         env["HEADLESS"] = str(params.get("headless", False))
 
         prefs = {}
         for k, v in params.get("extra_prefs", {}).items():
             prefs[k] = mozprofile.Preferences.cast(v)
@@ -348,17 +385,19 @@ class PuppeteerRunner(MozbuildObject):
         else:
             expected_data = {}
 
         output_handler = MochaOutputHandler(logger, expected_data)
         proc = npm(*command, cwd=self.puppeteerdir, env=env,
                    processOutputLine=output_handler, wait=False)
         output_handler.proc = proc
 
-        wait_proc(proc, "npm", exit_on_fail=False)
+        # Puppeteer unit tests don't always clean-up child processes in case of
+        # failure, so use an output_timeout as a fallback
+        wait_proc(proc, "npm", output_timeout=60, exit_on_fail=False)
 
         output_handler.after_end(params.get("subset", False))
 
         # Non-zero return codes are non-fatal for now since we have some
         # issues with unresolved promises that shouldn't otherwise block
         # running the tests
         if proc.returncode != 0:
             logger.warning("npm exited with code %s" % proc.returncode)
@@ -502,20 +541,34 @@ class PuppeteerTest(MachCommandBase):
             logger.info(e.help())
             exit(1)
         except Exception as e:
             exit(EX_SOFTWARE, e)
 
     def install_puppeteer(self, product):
         setup()
         env = {}
+        from mozversioncontrol import get_repository_object
+        repo = get_repository_object(self.topsrcdir)
+        puppeteer_dir = os.path.join("remote", "test", "puppeteer")
+        src_dir = os.path.join(puppeteer_dir, "src")
+        changed_files = False
+        for f in repo.get_changed_files():
+            if f.startswith(src_dir):
+                changed_files = True
+                break
+
         if product != "chrome":
-            env["PUPPETEER_SKIP_CHROMIUM_DOWNLOAD"] = "1"
+            env["PUPPETEER_SKIP_DOWNLOAD"] = "1"
+        lib_dir = os.path.join(self.topsrcdir, puppeteer_dir, "lib")
+        if changed_files and os.path.isdir(lib_dir):
+            # clobber lib to force `tsc compile` step
+            shutil.rmtree(lib_dir)
         npm("install",
-            cwd=os.path.join(self.topsrcdir, "remote", "test", "puppeteer"),
+            cwd=os.path.join(self.topsrcdir, puppeteer_dir),
             env=env)
 
 
 def exit(code, error=None):
     if error is not None:
         if isinstance(error, Exception):
             import traceback
             traceback.print_exc()
--- a/remote/puppeteer-expected.json
+++ b/remote/puppeteer-expected.json
@@ -1,1930 +1,1910 @@
 {
-  "Firefox Browser Page Accessibility should work": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Accessibility should report uninteresting nodes": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Accessibility roledescription": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Accessibility orientation": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Accessibility autocomplete": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Accessibility multiselectable": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Accessibility keyshortcuts": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Accessibility filtering children of leaf nodes should not report text nodes inside controls": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Accessibility filtering children of leaf nodes rich text editable fields should have children": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Accessibility filtering children of leaf nodes rich text editable fields with role should have children": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Accessibility filtering children of leaf nodes non editable textbox with role and tabIndex and label should not have children": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Accessibility filtering children of leaf nodes checkbox with and tabIndex and label should not have children": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Accessibility filtering children of leaf nodes checkbox without label should not have children": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Accessibility filtering children of leaf nodes root option should work a button": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Accessibility filtering children of leaf nodes root option should work an input": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Accessibility filtering children of leaf nodes root option should work a menu": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Accessibility filtering children of leaf nodes root option should return null when the element is no longer in DOM": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Accessibility filtering children of leaf nodes root option should support the interestingOnly option": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Browser.version should return whether we are in headless": [
+  "Accessibility should work (accessibility.spec.js)": [
+    "FAIL"
+  ],
+  "Accessibility should report uninteresting nodes (accessibility.spec.js)": [
+    "FAIL"
+  ],
+  "Accessibility roledescription (accessibility.spec.js)": [
+    "FAIL"
+  ],
+  "Accessibility orientation (accessibility.spec.js)": [
+    "FAIL"
+  ],
+  "Accessibility autocomplete (accessibility.spec.js)": [
+    "FAIL"
+  ],
+  "Accessibility multiselectable (accessibility.spec.js)": [
+    "FAIL"
+  ],
+  "Accessibility keyshortcuts (accessibility.spec.js)": [
+    "FAIL"
+  ],
+  "Accessibility filtering children of leaf nodes should not report text nodes inside controls (accessibility.spec.js)": [
+    "FAIL"
+  ],
+  "Accessibility filtering children of leaf nodes rich text editable fields should have children (accessibility.spec.js)": [
+    "FAIL"
+  ],
+  "Accessibility filtering children of leaf nodes rich text editable fields with role should have children (accessibility.spec.js)": [
+    "FAIL"
+  ],
+  "Accessibility filtering children of leaf nodes non editable textbox with role and tabIndex and label should not have children (accessibility.spec.js)": [
+    "FAIL"
+  ],
+  "Accessibility filtering children of leaf nodes checkbox with and tabIndex and label should not have children (accessibility.spec.js)": [
+    "FAIL"
+  ],
+  "Accessibility filtering children of leaf nodes checkbox without label should not have children (accessibility.spec.js)": [
+    "FAIL"
+  ],
+  "Accessibility filtering children of leaf nodes plaintext contenteditable plain text field with role should not have children (accessibility.spec.js)": [
+    "FAIL"
+  ],
+  "Accessibility filtering children of leaf nodes plaintext contenteditable plain text field without role should not have content (accessibility.spec.js)": [
+    "FAIL"
+  ],
+  "Accessibility filtering children of leaf nodes plaintext contenteditable plain text field with tabindex and without role should not have content (accessibility.spec.js)": [
+    "FAIL"
+  ],
+  "Accessibility filtering children of leaf nodes root option should work a button (accessibility.spec.js)": [
+    "FAIL"
+  ],
+  "Accessibility filtering children of leaf nodes root option should work an input (accessibility.spec.js)": [
+    "FAIL"
+  ],
+  "Accessibility filtering children of leaf nodes root option should work a menu (accessibility.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Browser.userAgent should include WebKit": [
-    "PASS"
-  ],
-  "Firefox Browser Page Browser.target should return browser target": [
+  "Accessibility filtering children of leaf nodes root option should return null when the element is no longer in DOM (accessibility.spec.js)": [
+    "FAIL"
+  ],
+  "Accessibility filtering children of leaf nodes root option should support the interestingOnly option (accessibility.spec.js)": [
+    "FAIL"
+  ],
+  "Browser specs Browser.version should return whether we are in headless (browser.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Browser.process should return child_process instance": [
-    "PASS"
-  ],
-  "Firefox Browser Page Browser.process should not return child_process for remote browser": [
-    "PASS"
-  ],
-  "Firefox Browser Page Browser.isConnected should set the browser connected state": [
+  "Browser specs Browser.userAgent should include WebKit (browser.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.click should click the button": [
+  "Browser specs Browser.target should return browser target (browser.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.click should click svg": [
+  "Browser specs Browser.process should return child_process instance (browser.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.click should click the button if window.Node is removed": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.click should click on a span with an inline element inside": [
+  "Browser specs Browser.process should not return child_process for remote browser (browser.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.click should not throw UnhandledPromiseRejection when page closes": [
+  "Browser specs Browser.isConnected should set the browser connected state (browser.spec.js)": [
+    "PASS"
+  ],
+  "BrowserContext should have default context (browsercontext.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.click should click the button after navigation": [
+  "BrowserContext should create new incognito context (browsercontext.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.click should click with disabled javascript": [
+  "BrowserContext should close all belonging targets once closing context (browsercontext.spec.js)": [
+    "PASS"
+  ],
+  "BrowserContext window.open should use parent tab context (browsercontext.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.click should click when one of inline box children is outside of viewport": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.click should select the text by triple clicking": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.click should click offscreen buttons": [
+  "BrowserContext should fire target events (browsercontext.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.click should click wrapped links": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.click should click on checkbox input and toggle": [
+  "BrowserContext should wait for a target (browsercontext.spec.js)": [
+    "TIMEOUT"
+  ],
+  "BrowserContext should timeout waiting for a non-existent target (browsercontext.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.click should click on checkbox label and toggle": [
+  "BrowserContext should isolate localStorage and cookies (browsercontext.spec.js)": [
+    "FAIL"
+  ],
+  "BrowserContext should work across sessions (browsercontext.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.click should fail to click a missing button": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.click should not hang with touch-enabled viewports": [
+  "Page.click should click the button (click.spec.js)": [
+    "PASS", "FAIL"
+  ],
+  "Page.click should click svg (click.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.click should scroll and click the button": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.click should double click the button": [
+  "Page.click should click the button if window.Node is removed (click.spec.js)": [
+    "FAIL"
+  ],
+  "Page.click should click on a span with an inline element inside (click.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.click should click a partially obscured button": [
+  "Page.click should not throw UnhandledPromiseRejection when page closes (click.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.click should click a rotated button": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.click should fire contextmenu event on right click": [
+  "Page.click should click the button after navigation  (click.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.click should click links which cause navigation": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.click should click the button inside an iframe": [
+  "Page.click should click with disabled javascript (click.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.click should click the button with fixed position inside an iframe": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.click should click the button with deviceScaleFactor set": [
+  "Page.click should click when one of inline box children is outside of viewport (click.spec.js)": [
+    "PASS"
+  ],
+  "Page.click should select the text by triple clicking (click.spec.js)": [
+    "PASS"
+  ],
+  "Page.click should click offscreen buttons (click.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.cookies should return no cookies in pristine browser context": [
+  "Page.click should click wrapped links (click.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.cookies should get a cookie": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.cookies should properly report httpOnly cookie": [
+  "Page.click should click on checkbox input and toggle (click.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.cookies should properly report \"Strict\" sameSite cookie": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.cookies should properly report \"Lax\" sameSite cookie": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.cookies should get multiple cookies": [
+  "Page.click should click on checkbox label and toggle (click.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.cookies should get cookies from multiple urls": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.setCookie should work": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.setCookie should isolate cookies in browser contexts": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.setCookie should set multiple cookies": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.setCookie should have |expires| set to |-1| for session cookies": [
+  "Page.click should fail to click a missing button (click.spec.js)": [
+    "PASS"
+  ],
+  "Page.click should not hang with touch-enabled viewports (click.spec.js)": [
+    "PASS"
+  ],
+  "Page.click should scroll and click the button (click.spec.js)": [
+    "PASS"
+  ],
+  "Page.click should double click the button (click.spec.js)": [
+    "PASS"
+  ],
+  "Page.click should click a partially obscured button (click.spec.js)": [
+    "PASS"
+  ],
+  "Page.click should click a rotated button (click.spec.js)": [
+    "PASS"
+  ],
+  "Page.click should fire contextmenu event on right click (click.spec.js)": [
+    "PASS"
+  ],
+  "Page.click should click links which cause navigation (click.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.setCookie should set cookie with reasonable defaults": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.setCookie should set a cookie with a path": [
+  "Page.click should click the button inside an iframe (click.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.setCookie should not set a cookie on a blank page": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.setCookie should not set a cookie with blank page URL": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.setCookie should not set a cookie on a data URL page": [
+  "Page.click should click the button with fixed position inside an iframe (click.spec.js)": [
+    "SKIP"
+  ],
+  "Page.click should click the button with deviceScaleFactor set (click.spec.js)": [
+    "FAIL"
+  ],
+  "Cookie specs Page.cookies should return no cookies in pristine browser context (cookies.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.setCookie should default to setting secure cookie for HTTPS websites": [
+  "Cookie specs Page.cookies should get a cookie (cookies.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.setCookie should be able to set unsecure cookie for HTTP website": [
+  "Cookie specs Page.cookies should properly report httpOnly cookie (cookies.spec.js)": [
+    "PASS"
+  ],
+  "Cookie specs Page.cookies should properly report \"Strict\" sameSite cookie (cookies.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.setCookie should set a cookie on a different domain": [
+  "Cookie specs Page.cookies should properly report \"Lax\" sameSite cookie (cookies.spec.js)": [
+    "PASS"
+  ],
+  "Cookie specs Page.cookies should get multiple cookies (cookies.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.setCookie should set cookies from a frame": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.deleteCookie should work": [
+  "Cookie specs Page.cookies should get cookies from multiple urls (cookies.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.Events.Dialog should fire": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.Events.Dialog should allow accepting prompts": [
+  "Cookie specs Page.setCookie should work (cookies.spec.js)": [
+    "FAIL"
+  ],
+  "Cookie specs Page.setCookie should isolate cookies in browser contexts (cookies.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.Events.Dialog should dismiss the prompt": [
+  "Cookie specs Page.setCookie should set multiple cookies (cookies.spec.js)": [
+    "FAIL"
+  ],
+  "Cookie specs Page.setCookie should have |expires| set to |-1| for session cookies (cookies.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page ElementHandle.boundingBox should work": [
-    "FAIL"
-  ],
-  "Firefox Browser Page ElementHandle.boundingBox should handle nested frames": [
+  "Cookie specs Page.setCookie should set cookie with reasonable defaults (cookies.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page ElementHandle.boundingBox should return null for invisible elements": [
+  "Cookie specs Page.setCookie should set a cookie with a path (cookies.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page ElementHandle.boundingBox should force a layout": [
+  "Cookie specs Page.setCookie should not set a cookie on a blank page (cookies.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page ElementHandle.boundingBox should work with SVG nodes": [
+  "Cookie specs Page.setCookie should not set a cookie with blank page URL (cookies.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page ElementHandle.boxModel should work": [
-    "FAIL"
-  ],
-  "Firefox Browser Page ElementHandle.boxModel should return null for invisible elements": [
+  "Cookie specs Page.setCookie should not set a cookie on a data URL page (cookies.spec.js)": [
+    "PASS"
+  ],
+  "Cookie specs Page.setCookie should default to setting secure cookie for HTTPS websites (cookies.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page ElementHandle.contentFrame should work": [
-    "FAIL"
-  ],
-  "Firefox Browser Page ElementHandle.click should work": [
-    "PASS"
-  ],
-  "Firefox Browser Page ElementHandle.click should work for Shadow DOM v1": [
-    "PASS"
-  ],
-  "Firefox Browser Page ElementHandle.click should work for TextNodes": [
-    "PASS"
-  ],
-  "Firefox Browser Page ElementHandle.click should throw for detached nodes": [
-    "PASS"
-  ],
-  "Firefox Browser Page ElementHandle.click should throw for hidden nodes": [
-    "PASS"
-  ],
-  "Firefox Browser Page ElementHandle.click should throw for recursively hidden nodes": [
-    "PASS"
-  ],
-  "Firefox Browser Page ElementHandle.click should throw for <br> elements": [
-    "PASS"
-  ],
-  "Firefox Browser Page ElementHandle.hover should work": [
-    "FAIL",
+  "Cookie specs Page.setCookie should be able to set unsecure cookie for HTTP website (cookies.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page ElementHandle.isIntersectingViewport should work": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.viewport should get the proper viewport size": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.viewport should support mobile emulation": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.viewport should support touch emulation": [
+  "Cookie specs Page.setCookie should set a cookie on a different domain (cookies.spec.js)": [
+    "FAIL"
+  ],
+  "Cookie specs Page.setCookie should set cookies from a frame (cookies.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.viewport should be detectable by Modernizr": [
+  "Cookie specs Page.setCookie should set secure same-site cookies from a frame (cookies.spec.js)": [
+    "FAIL"
+  ],
+  "Cookie specs Page.deleteCookie should work (cookies.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.viewport should detect touch when applying viewport with touches": [
+  "DefaultBrowserContext page.cookies() should work (defaultbrowsercontext.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.viewport should support landscape emulation": [
+  "DefaultBrowserContext page.setCookie() should work (defaultbrowsercontext.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.emulate should work": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.emulate should support clicking": [
-    "PASS",
+  "DefaultBrowserContext page.deleteCookie() should work (defaultbrowsercontext.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.emulateMedia should be an alias for Page.emulateMediaType": [
+  "Page.Events.Dialog should fire (dialog.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.emulateMediaType should work": [
+  "Page.Events.Dialog should allow accepting prompts (dialog.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.emulateMediaType should throw in case of bad argument": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.emulateMediaFeatures should work": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.emulateMediaFeatures should throw in case of bad argument": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.emulateTimezone should work": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.emulateTimezone should throw for invalid timezone IDs": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.evaluate should work": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.evaluate should transfer BigInt": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.evaluate should transfer NaN": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.evaluate should transfer -0": [
+  "Page.Events.Dialog should dismiss the prompt (dialog.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.evaluate should transfer Infinity": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.evaluate should transfer -Infinity": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.evaluate should transfer arrays": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.evaluate should transfer arrays as arrays, not objects": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.evaluate should modify global environment": [
+  "ElementHandle specs ElementHandle.boundingBox should work (elementhandle.spec.js)": [
+    "FAIL"
+  ],
+  "ElementHandle specs ElementHandle.boundingBox should handle nested frames (elementhandle.spec.js)": [
+    "FAIL"
+  ],
+  "ElementHandle specs ElementHandle.boundingBox should return null for invisible elements (elementhandle.spec.js)": [
+    "FAIL"
+  ],
+  "ElementHandle specs ElementHandle.boundingBox should force a layout (elementhandle.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.evaluate should evaluate in the page context": [
+  "ElementHandle specs ElementHandle.boundingBox should work with SVG nodes (elementhandle.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.evaluate should return undefined for objects with symbols": [
+  "ElementHandle specs ElementHandle.boxModel should work (elementhandle.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.evaluate should work with function shorthands": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.evaluate should work with unicode chars": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.evaluate should throw when evaluation triggers reload": [
+  "ElementHandle specs ElementHandle.boxModel should return null for invisible elements (elementhandle.spec.js)": [
+    "FAIL"
+  ],
+  "ElementHandle specs ElementHandle.contentFrame should work (elementhandle.spec.js)": [
     "TIMEOUT"
   ],
-  "Firefox Browser Page Page.evaluate should await promise": [
+  "ElementHandle specs ElementHandle.click should work (elementhandle.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.evaluate should work right after framenavigated": [
+  "ElementHandle specs ElementHandle.click should work for Shadow DOM v1 (elementhandle.spec.js)": [
+    "PASS"
+  ],
+  "ElementHandle specs ElementHandle.click should work for TextNodes (elementhandle.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.evaluate should work from-inside an exposed function": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.evaluate should reject promise with exception": [
+  "ElementHandle specs ElementHandle.click should throw for detached nodes (elementhandle.spec.js)": [
+    "PASS"
+  ],
+  "ElementHandle specs ElementHandle.click should throw for hidden nodes (elementhandle.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.evaluate should support thrown strings as error messages": [
+  "ElementHandle specs ElementHandle.click should throw for recursively hidden nodes (elementhandle.spec.js)": [
+    "PASS"
+  ],
+  "ElementHandle specs ElementHandle.click should throw for <br> elements (elementhandle.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.evaluate should support thrown numbers as error messages": [
+  "ElementHandle specs ElementHandle.hover should work (elementhandle.spec.js)": [
+    "FAIL", "PASS"
+  ],
+  "ElementHandle specs ElementHandle.isIntersectingViewport should work (elementhandle.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.evaluate should return complex objects": [
+  "ElementHandle specs Custom queries should register and unregister (elementhandle.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.evaluate should return BigInt": [
+  "ElementHandle specs Custom queries should throw with invalid query names (elementhandle.spec.js)": [
+    "PASS"
+  ],
+  "ElementHandle specs Custom queries should work for multiple elements (elementhandle.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.evaluate should return NaN": [
+  "ElementHandle specs Custom queries should eval correctly (elementhandle.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.evaluate should return -0": [
+  "ElementHandle specs Custom queries should wait correctly with waitForSelector (elementhandle.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.evaluate should return Infinity": [
+  "ElementHandle specs Custom queries should wait correctly with waitFor (elementhandle.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.evaluate should return -Infinity": [
+  "Emulation Page.viewport should get the proper viewport size (emulation.spec.js)": [
+    "PASS"
+  ],
+  "Emulation Page.viewport should support mobile emulation (emulation.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.evaluate should accept \"undefined\" as one of multiple parameters": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.evaluate should properly serialize null fields": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.evaluate should return undefined for non-serializable objects": [
+  "Emulation Page.viewport should support touch emulation (emulation.spec.js)": [
+    "FAIL"
+  ],
+  "Emulation Page.viewport should be detectable by Modernizr (emulation.spec.js)": [
+    "FAIL"
+  ],
+  "Emulation Page.viewport should detect touch when applying viewport with touches (emulation.spec.js)": [
+    "FAIL"
+  ],
+  "Emulation Page.viewport should support landscape emulation (emulation.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.evaluate should fail for circular object": [
+  "Emulation Page.emulate should work (emulation.spec.js)": [
+    "PASS"
+  ],
+  "Emulation Page.emulate should support clicking (emulation.spec.js)": [
+    "PASS", "FAIL"
+  ],
+  "Emulation Page.emulateMedia [deprecated] should work (emulation.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.evaluate should be able to throw a tricky error": [
+  "Emulation Page.emulateMedia [deprecated] should throw in case of bad argument (emulation.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.evaluate should accept a string": [
+  "Emulation Page.emulateMediaType should work (emulation.spec.js)": [
+    "FAIL"
+  ],
+  "Emulation Page.emulateMediaType should throw in case of bad argument (emulation.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.evaluate should accept a string with semi colons": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.evaluate should accept a string with comments": [
+  "Emulation Page.emulateMediaFeatures should work (emulation.spec.js)": [
+    "FAIL"
+  ],
+  "Emulation Page.emulateMediaFeatures should throw in case of bad argument (emulation.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.evaluate should accept element handle as an argument": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.evaluate should throw if underlying element was disposed": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.evaluate should throw if elementHandles are from other frames": [
+  "Emulation Page.emulateTimezone should work (emulation.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.evaluate should simulate a user gesture": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.evaluate should throw a nice error after a navigation": [
+  "Emulation Page.emulateTimezone should throw for invalid timezone IDs (emulation.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.evaluate should not throw an error when evaluation does a navigation": [
+  "Evaluation specs Page.evaluate should work (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should transfer BigInt (evaluation.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.evaluate should transfer 100Mb of data from page to node.js": [
+  "Evaluation specs Page.evaluate should transfer NaN (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should transfer -0 (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should transfer Infinity (evaluation.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.evaluate should throw error with detailed information on exception inside promise": [
+  "Evaluation specs Page.evaluate should transfer -Infinity (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should transfer arrays (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should transfer arrays as arrays, not objects (evaluation.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.evaluateOnNewDocument should evaluate before anything else on the page": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.evaluateOnNewDocument should work with CSP": [
+  "Evaluation specs Page.evaluate should modify global environment (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should evaluate in the page context (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should return undefined for objects with symbols (evaluation.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Frame.evaluate should have different execution contexts": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Frame.evaluate should have correct execution contexts": [
+  "Evaluation specs Page.evaluate should work with function shorthands (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should work with unicode chars (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should throw when evaluation triggers reload (evaluation.spec.js)": [
+    "TIMEOUT"
+  ],
+  "Evaluation specs Page.evaluate should await promise (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should work right after framenavigated (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should work from-inside an exposed function (evaluation.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Frame.evaluate should execute after cross-site navigation": [
+  "Evaluation specs Page.evaluate should reject promise with exception (evaluation.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Frame.executionContext should work": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Frame.evaluateHandle should work": [
+  "Evaluation specs Page.evaluate should support thrown strings as error messages (evaluation.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Frame.evaluate should throw for detached frames": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Frame Management should handle nested frames": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Frame Management should send events when frames are manipulated dynamically": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Frame Management should send \"framenavigated\" when navigating on anchor URLs": [
-    "TIMEOUT"
-  ],
-  "Firefox Browser Page Frame Management should persist mainFrame on cross-process navigation": [
+  "Evaluation specs Page.evaluate should support thrown numbers as error messages (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should return complex objects (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should return BigInt (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should return NaN (evaluation.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Frame Management should not send attach/detach events for main frame": [
+  "Evaluation specs Page.evaluate should return -0 (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should return Infinity (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should return -Infinity (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should accept \"undefined\" as one of multiple parameters (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should properly serialize null fields (evaluation.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Frame Management should detach child frames on navigation": [
+  "Evaluation specs Page.evaluate should return undefined for non-serializable objects (evaluation.spec.js)": [
+    "FAIL"
+  ],
+  "Evaluation specs Page.evaluate should fail for circular object (evaluation.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Frame Management should support framesets": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Frame Management should report frame from-inside shadow DOM": [
+  "Evaluation specs Page.evaluate should be able to throw a tricky error (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should accept a string (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should accept a string with semi colons (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should accept a string with comments (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should accept element handle as an argument (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should throw if underlying element was disposed (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should throw if elementHandles are from other frames (evaluation.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Frame Management should report frame.name()": [
+  "Evaluation specs Page.evaluate should simulate a user gesture (evaluation.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Frame Management should report frame.parent()": [
+  "Evaluation specs Page.evaluate should throw a nice error after a navigation (evaluation.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Frame Management should report different frame instance when frame re-attaches": [
+  "Evaluation specs Page.evaluate should not throw an error when evaluation does a navigation (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should transfer 100Mb of data from page to node.js (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluate should throw error with detailed information on exception inside promise  (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Evaluation specs Page.evaluateOnNewDocument should evaluate before anything else on the page (evaluation.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page input should upload the file": [
+  "Evaluation specs Page.evaluateOnNewDocument should work with CSP (evaluation.spec.js)": [
+    "FAIL"
+  ],
+  "Evaluation specs Frame.evaluate should have different execution contexts (evaluation.spec.js)": [
+    "FAIL"
+  ],
+  "Evaluation specs Frame.evaluate should have correct execution contexts (evaluation.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.waitForFileChooser should work when file input is attached to DOM": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.waitForFileChooser should work when file input is not attached to DOM": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.waitForFileChooser should respect timeout": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.waitForFileChooser should respect default timeout when there is no custom timeout": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.waitForFileChooser should prioritize exact timeout over default timeout": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.waitForFileChooser should work with no timeout": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.waitForFileChooser should return the same file chooser when there are many watchdogs simultaneously": [
-    "SKIP"
-  ],
-  "Firefox Browser Page FileChooser.accept should accept single file": [
-    "SKIP"
-  ],
-  "Firefox Browser Page FileChooser.accept should be able to read selected file": [
-    "SKIP"
-  ],
-  "Firefox Browser Page FileChooser.accept should be able to reset selected files with empty file list": [
-    "SKIP"
-  ],
-  "Firefox Browser Page FileChooser.accept should not accept multiple files for single-file input": [
-    "SKIP"
-  ],
-  "Firefox Browser Page FileChooser.accept should fail when accepting file chooser twice": [
-    "SKIP"
-  ],
-  "Firefox Browser Page FileChooser.cancel should cancel dialog": [
-    "SKIP"
-  ],
-  "Firefox Browser Page FileChooser.cancel should fail when canceling file chooser twice": [
-    "SKIP"
-  ],
-  "Firefox Browser Page FileChooser.isMultiple should work for single file pick": [
-    "SKIP"
-  ],
-  "Firefox Browser Page FileChooser.isMultiple should work for \"multiple\"": [
-    "SKIP"
-  ],
-  "Firefox Browser Page FileChooser.isMultiple should work for \"webkitdirectory\"": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.evaluateHandle should work": [
+  "Evaluation specs Frame.evaluate should execute after cross-site navigation (evaluation.spec.js)": [
+    "PASS"
+  ],
+  "Fixtures dumpio option should work with pipe option  (fixtures.spec.js)": [
+    "TIMEOUT"
+  ],
+  "Fixtures should dump browser process stderr (fixtures.spec.js)": [
+    "PASS"
+  ],
+  "Fixtures should close the browser when the node process closes (fixtures.spec.js)": [
+    "PASS"
+  ],
+  "Frame specs Frame.executionContext should work (frame.spec.js)": [
+    "FAIL"
+  ],
+  "Frame specs Frame.evaluateHandle should work (frame.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.evaluateHandle should accept object handle as an argument": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.evaluateHandle should accept object handle to primitive types": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.evaluateHandle should warn on nested object handles": [
+  "Frame specs Frame.evaluate should throw for detached frames (frame.spec.js)": [
+    "FAIL"
+  ],
+  "Frame specs Frame Management should handle nested frames (frame.spec.js)": [
+    "FAIL"
+  ],
+  "Frame specs Frame Management should send events when frames are manipulated dynamically (frame.spec.js)": [
+    "FAIL"
+  ],
+  "Frame specs Frame Management should send \"framenavigated\" when navigating on anchor URLs (frame.spec.js)": [
+    "TIMEOUT"
+  ],
+  "Frame specs Frame Management should persist mainFrame on cross-process navigation (frame.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.evaluateHandle should accept object handle to unserializable value": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.evaluateHandle should use the same JS wrappers": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.evaluateHandle should work with primitives": [
-    "PASS"
-  ],
-  "Firefox Browser Page JSHandle.getProperty should work": [
+  "Frame specs Frame Management should not send attach/detach events for main frame (frame.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page JSHandle.jsonValue should work": [
-    "PASS"
-  ],
-  "Firefox Browser Page JSHandle.jsonValue should not work with dates": [
+  "Frame specs Frame Management should detach child frames on navigation (frame.spec.js)": [
+    "FAIL"
+  ],
+  "Frame specs Frame Management should support framesets (frame.spec.js)": [
+    "FAIL"
+  ],
+  "Frame specs Frame Management should report frame from-inside shadow DOM (frame.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page JSHandle.jsonValue should throw for circular objects": [
-    "PASS"
-  ],
-  "Firefox Browser Page JSHandle.getProperties should work": [
-    "PASS"
-  ],
-  "Firefox Browser Page JSHandle.getProperties should return even non-own properties": [
-    "PASS"
-  ],
-  "Firefox Browser Page JSHandle.asElement should work": [
-    "PASS"
-  ],
-  "Firefox Browser Page JSHandle.asElement should return null for non-elements": [
-    "PASS"
-  ],
-  "Firefox Browser Page JSHandle.asElement should return ElementHandle for TextNodes": [
+  "Frame specs Frame Management should report frame.name() (frame.spec.js)": [
+    "FAIL"
+  ],
+  "Frame specs Frame Management should report frame.parent() (frame.spec.js)": [
+    "FAIL"
+  ],
+  "Frame specs Frame Management should report different frame instance when frame re-attaches (frame.spec.js)": [
+    "FAIL"
+  ],
+  "ignoreHTTPSErrors should work (ignorehttpserrors.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page JSHandle.asElement should work with nullified Node": [
-    "PASS"
-  ],
-  "Firefox Browser Page JSHandle.toString should work for primitives": [
-    "PASS"
-  ],
-  "Firefox Browser Page JSHandle.toString should work for complicated objects": [
-    "PASS"
-  ],
-  "Firefox Browser Page JSHandle.toString should work with different subtypes": [
+  "ignoreHTTPSErrors should work with request interception (ignorehttpserrors.spec.js)": [
+    "FAIL"
+  ],
+  "ignoreHTTPSErrors should work with mixed content (ignorehttpserrors.spec.js)": [
+    "FAIL"
+  ],
+  "ignoreHTTPSErrors Response.securityDetails should work (ignorehttpserrors.spec.js)": [
+    "FAIL"
+  ],
+  "ignoreHTTPSErrors Response.securityDetails should be |null| for non-secure requests (ignorehttpserrors.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Keyboard should type into a textarea": [
-    "PASS"
-  ],
-  "Firefox Browser Page Keyboard should press the metaKey": [
-    "PASS"
-  ],
-  "Firefox Browser Page Keyboard should move with the arrow keys": [
-    "PASS"
-  ],
-  "Firefox Browser Page Keyboard should send a character with ElementHandle.press": [
-    "PASS"
-  ],
-  "Firefox Browser Page Keyboard ElementHandle.press should support |text| option": [
+  "ignoreHTTPSErrors Response.securityDetails Network redirects should report SecurityDetails (ignorehttpserrors.spec.js)": [
+    "PASS", "FAIL"
+  ],
+  "input tests input should upload the file (input.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Keyboard should send a character with sendCharacter": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Keyboard should report shiftKey": [
-    "PASS",
-    "FAIL"
-  ],
-  "Firefox Browser Page Keyboard should report multiple modifiers": [
+  "input tests Page.waitForFileChooser should work when file input is attached to DOM (input.spec.js)": [
+    "TIMEOUT"
+  ],
+  "JSHandle Page.evaluateHandle should work (jshandle.spec.js)": [
+    "PASS"
+  ],
+  "JSHandle Page.evaluateHandle should accept object handle as an argument (jshandle.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Keyboard should send proper codes while typing": [
+  "JSHandle Page.evaluateHandle should accept object handle to primitive types (jshandle.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Keyboard should send proper codes while typing with shift": [
+  "JSHandle Page.evaluateHandle should warn on nested object handles (jshandle.spec.js)": [
+    "PASS"
+  ],
+  "JSHandle Page.evaluateHandle should accept object handle to unserializable value (jshandle.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Keyboard should not type canceled events": [
+  "JSHandle Page.evaluateHandle should use the same JS wrappers (jshandle.spec.js)": [
+    "PASS"
+  ],
+  "JSHandle Page.evaluateHandle should work with primitives (jshandle.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Keyboard should specify repeat property": [
+  "JSHandle JSHandle.getProperty should work (jshandle.spec.js)": [
+    "PASS"
+  ],
+  "JSHandle JSHandle.jsonValue should work (jshandle.spec.js)": [
+    "PASS"
+  ],
+  "JSHandle JSHandle.jsonValue should not work with dates (jshandle.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Keyboard should type all kinds of characters": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Keyboard should specify location": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Keyboard should throw on unknown keys": [
+  "JSHandle JSHandle.jsonValue should throw for circular objects (jshandle.spec.js)": [
+    "PASS"
+  ],
+  "JSHandle JSHandle.getProperties should work (jshandle.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Keyboard should type emoji": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Keyboard should type emoji into an iframe": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Keyboard should press the meta key": [
+  "JSHandle JSHandle.getProperties should return even non-own properties (jshandle.spec.js)": [
+    "PASS"
+  ],
+  "JSHandle JSHandle.asElement should work (jshandle.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Mouse should click the document": [
+  "JSHandle JSHandle.asElement should return null for non-elements (jshandle.spec.js)": [
+    "PASS"
+  ],
+  "JSHandle JSHandle.asElement should return ElementHandle for TextNodes (jshandle.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Mouse should resize the textarea": [
-    "FAIL",
+  "JSHandle JSHandle.asElement should work with nullified Node (jshandle.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Mouse should select the text with mouse": [
-    "FAIL",
+  "JSHandle JSHandle.toString should work for primitives (jshandle.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Mouse should trigger hover state": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Mouse should trigger hover state with removed window.Node": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Mouse should set modifier keys on click": [
+  "JSHandle JSHandle.toString should work for complicated objects (jshandle.spec.js)": [
+    "PASS"
+  ],
+  "JSHandle JSHandle.toString should work with different subtypes (jshandle.spec.js)": [
+    "PASS"
+  ],
+  "Keyboard should type into a textarea (keyboard.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Mouse should tween mouse movement": [
+  "Keyboard should press the metaKey (keyboard.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Mouse should work with mobile viewports and cross process navigations": [
+  "Keyboard should move with the arrow keys (keyboard.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.goto should work": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.goto should work with anchor navigation": [
-    "TIMEOUT"
-  ],
-  "Firefox Browser Page Page.goto should work with redirects": [
+  "Keyboard should send a character with ElementHandle.press (keyboard.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.goto should navigate to about:blank": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.goto should return response when page changes its URL after load": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.goto should work with subframes return 204": [
+  "Keyboard ElementHandle.press should support |text| option (keyboard.spec.js)": [
+    "FAIL"
+  ],
+  "Keyboard should send a character with sendCharacter (keyboard.spec.js)": [
+    "FAIL"
+  ],
+  "Keyboard should report shiftKey (keyboard.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.goto should fail when server returns 204": [
-    "TIMEOUT"
-  ],
-  "Firefox Browser Page Page.goto should navigate to empty page with domcontentloaded": [
+  "Keyboard should report multiple modifiers (keyboard.spec.js)": [
+    "PASS"
+  ],
+  "Keyboard should send proper codes while typing (keyboard.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.goto should work when page calls history API in beforeunload": [
+  "Keyboard should send proper codes while typing with shift (keyboard.spec.js)": [
+    "PASS"
+  ],
+  "Keyboard should not type canceled events (keyboard.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.goto should navigate to empty page with networkidle0": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.goto should navigate to empty page with networkidle2": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.goto should fail when navigating to bad url": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.goto should fail when navigating to bad SSL": [
+  "Keyboard should specify repeat property (keyboard.spec.js)": [
+    "FAIL"
+  ],
+  "Keyboard should type all kinds of characters (keyboard.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.goto should fail when navigating to bad SSL after redirects": [
+  "Keyboard should specify location (keyboard.spec.js)": [
+    "FAIL"
+  ],
+  "Keyboard should throw on unknown keys (keyboard.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.goto should throw if networkidle is passed as an option": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.goto should fail when main resources failed to load": [
+  "Keyboard should type emoji (keyboard.spec.js)": [
+    "FAIL"
+  ],
+  "Keyboard should type emoji into an iframe (keyboard.spec.js)": [
+    "FAIL"
+  ],
+  "Keyboard should press the meta key (keyboard.spec.js)": [
+    "FAIL"
+  ],
+  "Launcher specs Puppeteer BrowserFetcher should download and extract chrome linux binary (launcher.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.goto should fail when exceeding maximum navigation timeout": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.goto should fail when exceeding default maximum navigation timeout": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.goto should fail when exceeding default maximum timeout": [
+  "Launcher specs Puppeteer Browser.disconnect should reject navigation when browser closes (launcher.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.goto should prioritize default navigation timeout over default timeout": [
+  "Launcher specs Puppeteer Browser.disconnect should reject waitForSelector when browser closes (launcher.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.goto should disable timeout when its set to 0": [
+  "Launcher specs Puppeteer Browser.close should terminate network waiters (launcher.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.goto should work when navigating to valid url": [
+  "Launcher specs Puppeteer Puppeteer.launch should reject all promises when browser is closed (launcher.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.goto should work when navigating to data url": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.goto should work when navigating to 404": [
+  "Launcher specs Puppeteer Puppeteer.launch should reject if executable path is invalid (launcher.spec.js)": [
+    "PASS"
+  ],
+  "Launcher specs Puppeteer Puppeteer.launch userDataDir option (launcher.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.goto should return last response in redirect chain": [
+  "Launcher specs Puppeteer Puppeteer.launch userDataDir argument (launcher.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.goto should wait for network idle to succeed navigation": [
+  "Launcher specs Puppeteer Puppeteer.launch userDataDir option should restore state (launcher.spec.js)": [
+    "PASS"
+  ],
+  "Launcher specs Puppeteer Puppeteer.launch userDataDir option should restore cookies (launcher.spec.js)": [
     "SKIP"
   ],
-  "Firefox Browser Page Page.goto should not leak listeners during navigation": [
+  "Launcher specs Puppeteer Puppeteer.launch should return the default arguments (launcher.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.goto should not leak listeners during bad navigation": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.goto should not leak listeners during navigation of 11 pages": [
+  "Launcher specs Puppeteer Puppeteer.launch should report the correct product (launcher.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.goto should navigate to dataURL and fire dataURL requests": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.goto should navigate to URL with hash and fire requests without hash": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.goto should work with self requesting page": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.goto should fail when navigating and show the url at the error message": [
+  "Launcher specs Puppeteer Puppeteer.launch should work with no default arguments (launcher.spec.js)": [
+    "FAIL", "PASS"
+  ],
+  "Launcher specs Puppeteer Puppeteer.launch should filter out ignored default arguments (launcher.spec.js)": [
+    "FAIL", "PASS"
+  ],
+  "Launcher specs Puppeteer Puppeteer.launch should have default URL when launching browser (launcher.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.goto should send referer": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.waitForNavigation should work": [
-    "FAIL", "PASS"
-  ],
-  "Firefox Browser Page Page.waitForNavigation should work with both domcontentloaded and load": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.waitForNavigation should work with clicking on anchor links": [
-    "TIMEOUT",
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.waitForNavigation should work with history.pushState()": [
-    "FAIL",
+  "Launcher specs Puppeteer Puppeteer.launch should have custom URL when launching browser (launcher.spec.js)": [
     "TIMEOUT"
   ],
-  "Firefox Browser Page Page.waitForNavigation should work with history.replaceState()": [
-    "FAIL",
-    "TIMEOUT"
-  ],
-  "Firefox Browser Page Page.waitForNavigation should work with DOM history.back()/history.forward()": [
-    "FAIL",
-    "TIMEOUT"
-  ],
-  "Firefox Browser Page Page.waitForNavigation should work when subframe issues window.stop()": [
+  "Launcher specs Puppeteer Puppeteer.launch should set the default viewport (launcher.spec.js)": [
+    "PASS"
+  ],
+  "Launcher specs Puppeteer Puppeteer.launch should disable the default viewport (launcher.spec.js)": [
+    "PASS"
+  ],
+  "Launcher specs Puppeteer Puppeteer.launch should take fullPage screenshots when defaultViewport is null (launcher.spec.js)": [
+    "PASS"
+  ],
+  "Launcher specs Puppeteer Puppeteer.launch should be able to launch Chrome (launcher.spec.js)": [
     "SKIP"
   ],
-  "Firefox Browser Page Page.goBack should work": [
+  "Launcher specs Puppeteer Puppeteer.launch falls back to launching chrome if there is an unknown product but logs a warning (launcher.spec.js)": [
+    "PASS"
+  ],
+  "Launcher specs Puppeteer Puppeteer.launch should be able to launch Firefox (launcher.spec.js)": [
+    "FAIL"
+  ],
+  "Launcher specs Puppeteer Puppeteer.connect should be able to connect multiple times to the same browser (launcher.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.goBack should work with HistoryAPI": [
+  "Launcher specs Puppeteer Puppeteer.connect should be able to close remote browser (launcher.spec.js)": [
+    "PASS"
+  ],
+  "Launcher specs Puppeteer Puppeteer.connect should support ignoreHTTPSErrors option (launcher.spec.js)": [
+    "PASS"
+  ],
+  "Launcher specs Puppeteer Puppeteer.connect should be able to reconnect to a disconnected browser (launcher.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Frame.goto should navigate subframes": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Frame.goto should reject when frame detaches": [
+  "Launcher specs Puppeteer Puppeteer.connect should be able to connect to the same page simultaneously (launcher.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Frame.goto should return matching responses": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Frame.waitForNavigation should work": [
+  "Launcher specs Puppeteer Puppeteer.executablePath should work (launcher.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Frame.waitForNavigation should fail when frame detaches": [
+  "Launcher specs Top-level requires should require top-level Errors (launcher.spec.js)": [
+    "PASS"
+  ],
+  "Launcher specs Top-level requires should require top-level DeviceDescriptors (launcher.spec.js)": [
+    "PASS"
+  ],
+  "Launcher specs Browser target events should work (launcher.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.reload should work": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.Events.Request should fire for navigation requests": [
+  "Launcher specs Browser.Events.disconnected should be emitted when: browser gets closed, disconnected or underlying websocket gets closed (launcher.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.Events.Request should fire for iframes": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.Events.Request should fire for fetches": [
-    "PASS"
-  ],
-  "Firefox Browser Page Request.frame should work for main frame navigation request": [
+  "Mouse should click the document (mouse.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Request.frame should work for subframe navigation request": [
+  "Mouse should resize the textarea (mouse.spec.js)": [
+    "FAIL", "PASS"
+  ],
+  "Mouse should select the text with mouse (mouse.spec.js)": [
+    "FAIL", "PASS"
+  ],
+  "Mouse should trigger hover state (mouse.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Request.frame should work for fetch requests": [
+  "Mouse should trigger hover state with removed window.Node (mouse.spec.js)": [
+    "FAIL"
+  ],
+  "Mouse should set modifier keys on click (mouse.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Request.headers should work": [
+  "Mouse should tween mouse movement (mouse.spec.js)": [
+    "FAIL"
+  ],
+  "Mouse should work with mobile viewports and cross process navigations (mouse.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Response.headers should work": [
-    "PASS"
-  ],
-  "Firefox Browser Page Response.fromCache should return |false| for non-cached content": [
+  "navigation Page.goto should work (navigation.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Response.fromCache should work": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Response.fromServiceWorker should return |false| for non-service-worker content": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Response.fromServiceWorker Response.fromServiceWorker": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Request.postData should work": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Request.postData should be |undefined| when there is no post data": [
+  "navigation Page.goto should work with anchor navigation (navigation.spec.js)": [
+    "TIMEOUT"
+  ],
+  "navigation Page.goto should work with redirects (navigation.spec.js)": [
+    "PASS"
+  ],
+  "navigation Page.goto should navigate to about:blank (navigation.spec.js)": [
+    "PASS"
+  ],
+  "navigation Page.goto should return response when page changes its URL after load (navigation.spec.js)": [
+    "PASS"
+  ],
+  "navigation Page.goto should work with subframes return 204 (navigation.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Response.text should work": [
-    "TIMEOUT"
-  ],
-  "Firefox Browser Page Response.text should return uncompressed text": [
+  "navigation Page.goto should fail when server returns 204 (navigation.spec.js)": [
     "TIMEOUT"
   ],
-  "Firefox Browser Page Response.text should throw when requesting body of redirected response": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Response.text should wait until response completes": [
+  "navigation Page.goto should navigate to empty page with domcontentloaded (navigation.spec.js)": [
+    "PASS"
+  ],
+  "navigation Page.goto should work when page calls history API in beforeunload (navigation.spec.js)": [
+    "PASS"
+  ],
+  "navigation Page.goto should navigate to empty page with networkidle0 (navigation.spec.js)": [
     "TIMEOUT"
   ],
-  "Firefox Browser Page Response.json should work": [
+  "navigation Page.goto should navigate to empty page with networkidle2 (navigation.spec.js)": [
     "TIMEOUT"
   ],
-  "Firefox Browser Page Response.buffer should work": [
-    "TIMEOUT"
-  ],
-  "Firefox Browser Page Response.buffer should work with compression": [
-    "TIMEOUT"
-  ],
-  "Firefox Browser Page Response.statusText should work": [
-    "PASS"
-  ],
-  "Firefox Browser Page Network Events Page.Events.Request": [
-    "PASS"
-  ],
-  "Firefox Browser Page Network Events Page.Events.Response": [
-    "PASS"
-  ],
-  "Firefox Browser Page Network Events Page.Events.RequestFailed": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Network Events Page.Events.RequestFinished": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Network Events should fire events in proper order": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Network Events should support redirects": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Request.isNavigationRequest should work": [
+  "navigation Page.goto should fail when navigating to bad url (navigation.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Request.isNavigationRequest should work with request interception": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Request.isNavigationRequest should work when navigating to image": [
+  "navigation Page.goto should fail when navigating to bad SSL (navigation.spec.js)": [
+    "PASS"
+  ],
+  "navigation Page.goto should fail when navigating to bad SSL after redirects (navigation.spec.js)": [
+    "PASS"
+  ],
+  "navigation Page.goto should throw if networkidle is passed as an option (navigation.spec.js)": [
+    "PASS"
+  ],
+  "navigation Page.goto should fail when main resources failed to load (navigation.spec.js)": [
+    "PASS"
+  ],
+  "navigation Page.goto should fail when exceeding maximum navigation timeout (navigation.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.setExtraHTTPHeaders should work": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.setExtraHTTPHeaders should throw for non-string header values": [
+  "navigation Page.goto should fail when exceeding default maximum navigation timeout (navigation.spec.js)": [
+    "PASS"
+  ],
+  "navigation Page.goto should fail when exceeding default maximum timeout (navigation.spec.js)": [
+    "PASS"
+  ],
+  "navigation Page.goto should prioritize default navigation timeout over default timeout (navigation.spec.js)": [
+    "PASS"
+  ],
+  "navigation Page.goto should disable timeout when its set to 0 (navigation.spec.js)": [
+    "PASS"
+  ],
+  "navigation Page.goto should work when navigating to valid url (navigation.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.authenticate should work": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.authenticate should fail if wrong credentials": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.authenticate should allow disable authentication": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.setRequestInterception should intercept": [
+  "navigation Page.goto should work when navigating to data url (navigation.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.setRequestInterception should work when POST is redirected with 302": [
+  "navigation Page.goto should work when navigating to 404 (navigation.spec.js)": [
+    "PASS"
+  ],
+  "navigation Page.goto should return last response in redirect chain (navigation.spec.js)": [
+    "PASS"
+  ],
+  "navigation Page.goto should wait for network idle to succeed navigation (navigation.spec.js)": [
+    "TIMEOUT"
+  ],
+  "navigation Page.goto should not leak listeners during navigation (navigation.spec.js)": [
+    "PASS"
+  ],
+  "navigation Page.goto should not leak listeners during bad navigation (navigation.spec.js)": [
+    "PASS"
+  ],
+  "navigation Page.goto should not leak listeners during navigation of 11 pages (navigation.spec.js)": [
+    "PASS"
+  ],
+  "navigation Page.goto should navigate to dataURL and fire dataURL requests (navigation.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.setRequestInterception should work when header manipulation headers with redirect": [
+  "navigation Page.goto should navigate to URL with hash and fire requests without hash (navigation.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.setRequestInterception should be able to remove headers": [
+  "navigation Page.goto should work with self requesting page (navigation.spec.js)": [
+    "PASS"
+  ],
+  "navigation Page.goto should fail when navigating and show the url at the error message (navigation.spec.js)": [
+    "PASS"
+  ],
+  "navigation Page.goto should send referer (navigation.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.setRequestInterception should contain referer header": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.setRequestInterception should properly return navigation response when URL has cookies": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.setRequestInterception should stop intercepting": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.setRequestInterception should show custom HTTP headers": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.setRequestInterception should work with redirect inside sync XHR": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.setRequestInterception should work with custom referer headers": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.setRequestInterception should be abortable": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.setRequestInterception should be abortable with custom error codes": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.setRequestInterception should send referer": [
+  "navigation Page.waitForNavigation should work (navigation.spec.js)": [
+    "PASS", "FAIL"
+  ],
+  "navigation Page.waitForNavigation should work with both domcontentloaded and load (navigation.spec.js)": [
+    "PASS"
+  ],
+  "navigation Page.waitForNavigation should work with clicking on anchor links (navigation.spec.js)": [
+    "TIMEOUT", "FAIL"
+  ],
+  "navigation Page.waitForNavigation should work with history.pushState() (navigation.spec.js)": [
+    "FAIL", "TIMEOUT"
+  ],
+  "navigation Page.waitForNavigation should work with history.replaceState() (navigation.spec.js)": [
+    "FAIL", "TIMEOUT"
+  ],
+  "navigation Page.waitForNavigation should work with DOM history.back()/history.forward() (navigation.spec.js)": [
+    "FAIL", "TIMEOUT"
+  ],
+  "navigation Page.waitForNavigation should work when subframe issues window.stop() (navigation.spec.js)": [
+    "TIMEOUT"
+  ],
+  "navigation Page.goBack should work (navigation.spec.js)": [
+    "PASS"
+  ],
+  "navigation Page.goBack should work with HistoryAPI (navigation.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.setRequestInterception should fail navigation when aborting main resource": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.setRequestInterception should work with redirects": [
+  "navigation Frame.goto should navigate subframes (navigation.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.setRequestInterception should work with redirects for subresources": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.setRequestInterception should be able to abort redirects": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.setRequestInterception should work with equal requests": [
+  "navigation Frame.goto should reject when frame detaches (navigation.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.setRequestInterception should navigate to dataURL and fire dataURL requests": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.setRequestInterception should be able to fetch dataURL and fire dataURL requests": [
+  "navigation Frame.goto should return matching responses (navigation.spec.js)": [
+    "TIMEOUT"
+  ],
+  "navigation Frame.waitForNavigation should work (navigation.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.setRequestInterception should navigate to URL with hash and and fire requests without hash": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.setRequestInterception should work with encoded server": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.setRequestInterception should work with badly encoded server": [
+  "navigation Frame.waitForNavigation should fail when frame detaches (navigation.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.setRequestInterception should work with encoded server - 2": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.setRequestInterception should not throw \"Invalid Interception Id\" if the request was cancelled": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.setRequestInterception should throw if interception is not enabled": [
+  "navigation Page.reload should work (navigation.spec.js)": [
+    "PASS"
+  ],
+  "network Page.Events.Request should fire for navigation requests (network.spec.js)": [
+    "PASS"
+  ],
+  "network Page.Events.Request should fire for iframes (network.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.setRequestInterception should work with file URLs": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Request.continue should work": [
+  "network Page.Events.Request should fire for fetches (network.spec.js)": [
+    "PASS"
+  ],
+  "network Request.frame should work for main frame navigation request (network.spec.js)": [
+    "PASS"
+  ],
+  "network Request.frame should work for subframe navigation request (network.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Request.continue should amend HTTP headers": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Request.continue should redirect in a way non-observable to page": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Request.continue should amend method": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Request.continue should amend post data": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Request.continue should amend both post data and method on navigation": [
+  "network Request.frame should work for fetch requests (network.spec.js)": [
+    "PASS"
+  ],
+  "network Request.headers should work (network.spec.js)": [
+    "PASS"
+  ],
+  "network Response.headers should work (network.spec.js)": [
+    "PASS"
+  ],
+  "network Response.fromCache should return |false| for non-cached content (network.spec.js)": [
+    "PASS"
+  ],
+  "network Response.fromCache should work (network.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Request.respond should work": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Request.respond should work with status code 422": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Request.respond should redirect": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Request.respond should allow mocking binary responses": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Request.respond should stringify intercepted request response headers": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.close should reject all promises when page is closed": [
+  "network Response.fromServiceWorker should return |false| for non-service-worker content (network.spec.js)": [
+    "PASS"
+  ],
+  "network Response.fromServiceWorker Response.fromServiceWorker (network.spec.js)": [
+    "TIMEOUT"
+  ],
+  "network Request.postData should work (network.spec.js)": [
+    "FAIL"
+  ],
+  "network Request.postData should be |undefined| when there is no post data (network.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.close should not be visible in browser.pages": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.close should run beforeunload if asked for": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.close should *not* run beforeunload by default": [
+  "network Response.text should work (network.spec.js)": [
+    "TIMEOUT"
+  ],
+  "network Response.text should return uncompressed text (network.spec.js)": [
+    "TIMEOUT"
+  ],
+  "network Response.text should throw when requesting body of redirected response (network.spec.js)": [
+    "FAIL"
+  ],
+  "network Response.text should wait until response completes (network.spec.js)": [
     "TIMEOUT"
   ],
-  "Firefox Browser Page Page.close should set the page close state": [
+  "network Response.json should work (network.spec.js)": [
+    "TIMEOUT"
+  ],
+  "network Response.buffer should work (network.spec.js)": [
+    "TIMEOUT"
+  ],
+  "network Response.buffer should work with compression (network.spec.js)": [
+    "TIMEOUT"
+  ],
+  "network Response.statusText should work (network.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.close should terminate network waiters": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.Events.Load should fire when expected": [
+  "network Network Events Page.Events.Request (network.spec.js)": [
+    "FAIL", "PASS"
+  ],
+  "network Network Events Page.Events.Response (network.spec.js)": [
+    "PASS", "FAIL"
+  ],
+  "network Network Events Page.Events.RequestFailed (network.spec.js)": [
+    "FAIL"
+  ],
+  "network Network Events Page.Events.RequestFinished (network.spec.js)": [
+    "FAIL"
+  ],
+  "network Network Events should fire events in proper order (network.spec.js)": [
+    "FAIL"
+  ],
+  "network Network Events should support redirects (network.spec.js)": [
+    "FAIL"
+  ],
+  "network Request.isNavigationRequest should work (network.spec.js)": [
+    "FAIL"
+  ],
+  "network Request.isNavigationRequest should work with request interception (network.spec.js)": [
+    "FAIL"
+  ],
+  "network Request.isNavigationRequest should work when navigating to image (network.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Async stacks should work": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.Events.error should throw when page crashes": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.Events.Popup should work": [
+  "network Page.setExtraHTTPHeaders should work (network.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.Events.Popup should work with noopener": [
-    "TIMEOUT"
-  ],
-  "Firefox Browser Page Page.Events.Popup should work with clicking target=_blank": [
-    "TIMEOUT"
-  ],
-  "Firefox Browser Page Page.Events.Popup should work with fake-clicking target=_blank and rel=noopener": [
+  "network Page.setExtraHTTPHeaders should throw for non-string header values (network.spec.js)": [
+    "PASS"
+  ],
+  "network Page.authenticate should work (network.spec.js)": [
     "TIMEOUT"
   ],
-  "Firefox Browser Page Page.Events.Popup should work with clicking target=_blank and rel=noopener": [
-    "TIMEOUT"
-  ],
-  "Firefox Browser Page BrowserContext.overridePermissions should be prompt by default": [
-    "PASS"
-  ],
-  "Firefox Browser Page BrowserContext.overridePermissions should deny permission when not listed": [
+  "network Page.authenticate should fail if wrong credentials (network.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page BrowserContext.overridePermissions should fail when bad permission is given": [
-    "PASS"
-  ],
-  "Firefox Browser Page BrowserContext.overridePermissions should grant permission when listed": [
-    "FAIL"
-  ],
-  "Firefox Browser Page BrowserContext.overridePermissions should reset permissions": [
-    "FAIL"
-  ],
-  "Firefox Browser Page BrowserContext.overridePermissions should trigger permission onchange": [
-    "FAIL"
-  ],
-  "Firefox Browser Page BrowserContext.overridePermissions should isolate permissions between browser contexs": [
+  "network Page.authenticate should allow disable authentication (network.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.setGeolocation should work": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.setGeolocation should throw when invalid longitude": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.setOfflineMode should work": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.setOfflineMode should emulate navigator.onLine": [
-    "SKIP"
-  ],
-  "Firefox Browser Page ExecutionContext.queryObjects should work": [
-    "SKIP"
-  ],
-  "Firefox Browser Page ExecutionContext.queryObjects should work for non-blank page": [
-    "SKIP"
-  ],
-  "Firefox Browser Page ExecutionContext.queryObjects should fail for disposed handles": [
-    "SKIP"
-  ],
-  "Firefox Browser Page ExecutionContext.queryObjects should fail primitive values as prototypes": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.Events.Console should work": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.Events.Console should work for different console API calls": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.Events.Console should not fail for window object": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.Events.Console should trigger correct Log": [
+  "Page Page.close should reject all promises when page is closed (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.close should not be visible in browser.pages (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.close should run beforeunload if asked for (page.spec.js)": [
+    "TIMEOUT"
+  ],
+  "Page Page.close should *not* run beforeunload by default (page.spec.js)": [
+    "TIMEOUT"
+  ],
+  "Page Page.close should set the page close state (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.close should terminate network waiters (page.spec.js)": [
+    "TIMEOUT"
+  ],
+  "Page Page.Events.Load should fire when expected (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Async stacks should work (page.spec.js)": [
+    "TIMEOUT"
+  ],
+  "Page Page.Events.error should throw when page crashes (page.spec.js)": [
+    "TIMEOUT"
+  ],
+  "Page Page.Events.Popup should work (page.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.Events.Console should have location when fetch fails": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.Events.Console should have location for console API calls": [
+  "Page Page.Events.Popup should work with noopener (page.spec.js)": [
+    "TIMEOUT"
+  ],
+  "Page Page.Events.Popup should work with clicking target=_blank (page.spec.js)": [
+    "TIMEOUT"
+  ],
+  "Page Page.Events.Popup should work with fake-clicking target=_blank and rel=noopener (page.spec.js)": [
+    "TIMEOUT"
+  ],
+  "Page Page.Events.Popup should work with clicking target=_blank and rel=noopener (page.spec.js)": [
+    "TIMEOUT"
+  ],
+  "Page BrowserContext.overridePermissions should be prompt by default (page.spec.js)": [
+    "PASS"
+  ],
+  "Page BrowserContext.overridePermissions should deny permission when not listed (page.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.Events.Console should not throw when there are console messages in detached iframes": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.Events.DOMContentLoaded should fire when expected": [
+  "Page BrowserContext.overridePermissions should fail when bad permission is given (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.metrics should get metrics from a page": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.metrics metrics event fired on console.timeStamp": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.waitForRequest should work": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.waitForRequest should work with predicate": [
+  "Page BrowserContext.overridePermissions should grant permission when listed (page.spec.js)": [
+    "FAIL"
+  ],
+  "Page BrowserContext.overridePermissions should reset permissions (page.spec.js)": [
+    "FAIL"
+  ],
+  "Page BrowserContext.overridePermissions should trigger permission onchange (page.spec.js)": [
+    "FAIL"
+  ],
+  "Page BrowserContext.overridePermissions should isolate permissions between browser contexs (page.spec.js)": [
+    "FAIL"
+  ],
+  "Page Page.setGeolocation should work (page.spec.js)": [
+    "FAIL"
+  ],
+  "Page Page.setGeolocation should throw when invalid longitude (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.waitForRequest should respect timeout": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.waitForRequest should respect default timeout": [
+  "Page Page.setOfflineMode should work (page.spec.js)": [
+    "FAIL"
+  ],
+  "Page Page.setOfflineMode should emulate navigator.onLine (page.spec.js)": [
+    "FAIL"
+  ],
+  "Page ExecutionContext.queryObjects should work (page.spec.js)": [
+    "FAIL"
+  ],
+  "Page ExecutionContext.queryObjects should work for non-blank page (page.spec.js)": [
+    "FAIL"
+  ],
+  "Page ExecutionContext.queryObjects should fail for disposed handles (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.waitForRequest should work with no timeout": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.waitForResponse should work": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.waitForResponse should respect timeout": [
+  "Page ExecutionContext.queryObjects should fail primitive values as prototypes (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.waitForResponse should respect default timeout": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.waitForResponse should work with predicate": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.waitForResponse should work with no timeout": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.exposeFunction should work": [
+  "Page Page.Events.Console should work (page.spec.js)": [
+    "FAIL"
+  ],
+  "Page Page.Events.Console should work for different console API calls (page.spec.js)": [
+    "FAIL"
+  ],
+  "Page Page.Events.Console should not fail for window object (page.spec.js)": [
+    "FAIL"
+  ],
+  "Page Page.Events.Console should trigger correct Log (page.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.exposeFunction should throw exception in page context": [
+  "Page Page.Events.Console should have location when fetch fails (page.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.exposeFunction should support throwing \"null\"": [
+  "Page Page.Events.Console should have location for console API calls (page.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.exposeFunction should be callable from-inside evaluateOnNewDocument": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.exposeFunction should survive navigation": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.exposeFunction should await returned promise": [
+  "Page Page.Events.Console should not throw when there are console messages in detached iframes (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.Events.DOMContentLoaded should fire when expected (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.metrics should get metrics from a page (page.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.exposeFunction should work on frames": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.exposeFunction should work on frames before navigation": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.exposeFunction should work with complex objects": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.Events.PageError should fire": [
+  "Page Page.metrics metrics event fired on console.timeStamp (page.spec.js)": [
     "TIMEOUT"
   ],
-  "Firefox Browser Page Page.setUserAgent should work": [
+  "Page Page.waitForRequest should work (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.waitForRequest should work with predicate (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.waitForRequest should respect timeout (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.waitForRequest should respect default timeout (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.setUserAgent should work for subframes": [
+  "Page Page.waitForRequest should work with no timeout (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.setUserAgent should emulate device user-agent": [
+  "Page Page.waitForResponse should work (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.waitForResponse should respect timeout (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.setContent should work": [
+  "Page Page.waitForResponse should respect default timeout (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.setContent should work with doctype": [
+  "Page Page.waitForResponse should work with predicate (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.setContent should work with HTML 4 doctype": [
+  "Page Page.waitForResponse should work with no timeout (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.setContent should respect timeout": [
-    "PASS",
+  "Page Page.exposeFunction should work (page.spec.js)": [
+    "FAIL"
+  ],
+  "Page Page.exposeFunction should throw exception in page context (page.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.setContent should respect default navigation timeout": [
-    "PASS",
+  "Page Page.exposeFunction should support throwing \"null\" (page.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.setContent should await resources to load": [
-    "PASS",
+  "Page Page.exposeFunction should be callable from-inside evaluateOnNewDocument (page.spec.js)": [
+    "FAIL"
+  ],
+  "Page Page.exposeFunction should survive navigation (page.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.setContent should work fast enough": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.setContent should work with tricky content": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.setContent should work with accents": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.setContent should work with emojis": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.setContent should work with newline": [
+  "Page Page.exposeFunction should await returned promise (page.spec.js)": [
+    "FAIL"
+  ],
+  "Page Page.exposeFunction should work on frames (page.spec.js)": [
+    "FAIL"
+  ],
+  "Page Page.exposeFunction should work on frames before navigation (page.spec.js)": [
+    "FAIL"
+  ],
+  "Page Page.exposeFunction should work with complex objects (page.spec.js)": [
+    "FAIL"
+  ],
+  "Page Page.Events.PageError should fire (page.spec.js)": [
+    "TIMEOUT"
+  ],
+  "Page Page.setUserAgent should work (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.setBypassCSP should bypass CSP meta tag": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.setBypassCSP should bypass CSP header": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.setBypassCSP should bypass after cross-process navigation": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.setBypassCSP should bypass CSP in iframes as well": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.addScriptTag should throw an error if no options are provided": [
+  "Page Page.setUserAgent should work for subframes (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.setUserAgent should emulate device user-agent (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.addScriptTag should work with a url": [
+  "Page Page.setContent should work (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.setContent should work with doctype (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.setContent should work with HTML 4 doctype (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.addScriptTag should work with a url and type=module": [
+  "Page Page.setContent should respect timeout (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.addScriptTag should work with a path and type=module": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.addScriptTag should work with a content and type=module": [
+  "Page Page.setContent should respect default navigation timeout (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.addScriptTag should throw an error if loading from url fail": [
+  "Page Page.setContent should await resources to load (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.addScriptTag should work with a path": [
+  "Page Page.setContent should work fast enough (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.addScriptTag should include sourcemap when path is provided": [
+  "Page Page.setContent should work with tricky content (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.addScriptTag should work with content": [
+  "Page Page.setContent should work with accents (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.setContent should work with emojis (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.setContent should work with newline (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.addScriptTag should throw when added with content to the CSP page": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.addScriptTag should throw when added with URL to the CSP page": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.addStyleTag should throw an error if no options are provided": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.addStyleTag should work with a url": [
+  "Page Page.setBypassCSP should bypass CSP meta tag (page.spec.js)": [
+    "FAIL"
+  ],
+  "Page Page.setBypassCSP should bypass CSP header (page.spec.js)": [
+    "FAIL"
+  ],
+  "Page Page.setBypassCSP should bypass after cross-process navigation (page.spec.js)": [
+    "FAIL"
+  ],
+  "Page Page.setBypassCSP should bypass CSP in iframes as well (page.spec.js)": [
+    "FAIL"
+  ],
+  "Page Page.addScriptTag should throw an error if no options are provided (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.addStyleTag should throw an error if loading from url fail": [
+  "Page Page.addScriptTag should work with a url (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.addScriptTag should work with a url and type=module (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.addStyleTag should work with a path": [
+  "Page Page.addScriptTag should work with a path and type=module (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.addScriptTag should work with a content and type=module (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.addStyleTag should include sourcemap when path is provided": [
+  "Page Page.addScriptTag should throw an error if loading from url fail (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.addScriptTag should work with a path (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.addStyleTag should work with content": [
+  "Page Page.addScriptTag should include sourcemap when path is provided (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.addStyleTag should throw when added with content to the CSP page": [
+  "Page Page.addScriptTag should work with content (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.addScriptTag should throw when added with content to the CSP page (page.spec.js)": [
     "SKIP"
   ],
-  "Firefox Browser Page Page.addStyleTag should throw when added with URL to the CSP page": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.url should work": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.setJavaScriptEnabled should work": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.setCacheEnabled should enable or disable the cache based on the state passed": [
+  "Page Page.addScriptTag should throw when added with URL to the CSP page (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.setCacheEnabled should stay disabled when toggling request interception on/off": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.pdf should be able to save file": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.title should return the page title": [
+  "Page Page.addStyleTag should throw an error if no options are provided (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.select should select single option": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.select should select only first option": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.select should not throw when select causes navigation": [
+  "Page Page.addStyleTag should work with a url (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.select should select multiple options": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.select should respect event bubbling": [
+  "Page Page.addStyleTag should throw an error if loading from url fail (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.select should throw when element is not a <select>": [
+  "Page Page.addStyleTag should work with a path (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.select should return [] on no matched values": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.select should return an array of matched values": [
+  "Page Page.addStyleTag should include sourcemap when path is provided (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.select should return an array of one element when multiple is not set": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.select should return [] on no values": [
+  "Page Page.addStyleTag should work with content (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.select should deselect all options when passed no values for a multiple select": [
+  "Page Page.addStyleTag should throw when added with content to the CSP page (page.spec.js)": [
+    "TIMEOUT"
+  ],
+  "Page Page.addStyleTag should throw when added with URL to the CSP page (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.select should deselect all options when passed no values for a select without multiple": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.select should throw if passed in non-strings": [
+  "Page Page.url should work (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.select should work when re-defining top-level Event class": [
+  "Page Page.setJavaScriptEnabled should work (page.spec.js)": [
+    "FAIL"
+  ],
+  "Page Page.setCacheEnabled should enable or disable the cache based on the state passed (page.spec.js)": [
+    "FAIL"
+  ],
+  "Page Page.setCacheEnabled should stay disabled when toggling request interception on/off (page.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.Events.Close should work with window.close": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Page.Events.Close should work with page.close": [
+  "Page printing to PDF can print to PDF and save to file (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.title should return the page title (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.browser should return the correct browser instance": [
+  "Page Page.select should select single option (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.browserContext should return the correct browser instance": [
+  "Page Page.select should select only first option (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.screenshot should work": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.screenshot should clip rect": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.screenshot should clip elements to the viewport": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.screenshot should run in parallel": [
+  "Page Page.select should not throw when select causes navigation (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.select should select multiple options (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.select should respect event bubbling (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.screenshot should take fullPage screenshots": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.screenshot should run in parallel in multiple pages": [
+  "Page Page.select should throw when element is not a <select> (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.select should return [] on no matched values (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.select should return an array of matched values (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.select should return an array of one element when multiple is not set (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.screenshot should allow transparency": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.screenshot should render white background on jpeg file": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.screenshot should work with odd clip size on Retina displays": [
+  "Page Page.select should return [] on no values (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.screenshot should return base64": [
-    "FAIL"
-  ],
-  "Firefox Browser Page ElementHandle.screenshot should work": [
+  "Page Page.select should deselect all options when passed no values for a multiple select (page.spec.js)": [
+    "PASS"
+  ],
+  "Page Page.select should deselect all options when passed no values for a select without multiple (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page ElementHandle.screenshot should take into account padding and border": [
-    "PASS"
-  ],
-  "Firefox Browser Page ElementHandle.screenshot should capture full element when larger than viewport": [
+  "Page Page.select should throw if passed in non-strings (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page ElementHandle.screenshot should scroll element into view": [
-    "PASS"
-  ],
-  "Firefox Browser Page ElementHandle.screenshot should work with a rotated element": [
+  "Page Page.select should work when re-defining top-level Event class (page.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page ElementHandle.screenshot should fail to screenshot a detached element": [
+  "Page Page.Events.Close should work with window.close (page.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page ElementHandle.screenshot should not hang with zero width/height element": [
+  "Page Page.Events.Close should work with page.close (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page ElementHandle.screenshot should work for an element with fractional dimensions": [
+  "Page Page.browser should return the correct browser instance (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page ElementHandle.screenshot should work for an element with an offset": [
-    "FAIL"
-  ],
-  "Firefox Browser Page Page.$eval should work": [
+  "Page Page.browserContext should return the correct browser instance (page.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.$eval should accept arguments": [
+  "querySelector Page.$eval should work (queryselector.spec.js)": [
+    "PASS"
+  ],
+  "querySelector Page.$eval should accept arguments (queryselector.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.$eval should accept ElementHandles as arguments": [
+  "querySelector Page.$eval should accept ElementHandles as arguments (queryselector.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.$eval should throw error if no element is found": [
+  "querySelector Page.$eval should throw error if no element is found (queryselector.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.$$eval should work": [
+  "querySelector Page.$$eval should work (queryselector.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.$ should query existing element": [
+  "querySelector Page.$ should query existing element (queryselector.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.$ should return null for non-existing element": [
+  "querySelector Page.$ should return null for non-existing element (queryselector.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.$$ should query existing elements": [
+  "querySelector Page.$$ should query existing elements (queryselector.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.$$ should return empty array if nothing is found": [
+  "querySelector Page.$$ should return empty array if nothing is found (queryselector.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Path.$x should query existing element": [
+  "querySelector Path.$x should query existing element (queryselector.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Path.$x should return empty array for non-existing element": [
-    "PASS"
-  ],
-  "Firefox Browser Page Path.$x should return multiple elements": [
+  "querySelector Path.$x should return empty array for non-existing element (queryselector.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page ElementHandle.$ should query existing element": [
+  "querySelector Path.$x should return multiple elements (queryselector.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page ElementHandle.$ should return null for non-existing element": [
+  "querySelector ElementHandle.$ should query existing element (queryselector.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page ElementHandle.$eval should work": [
+  "querySelector ElementHandle.$ should return null for non-existing element (queryselector.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page ElementHandle.$eval should retrieve content from subtree": [
-    "PASS"
-  ],
-  "Firefox Browser Page ElementHandle.$eval should throw in case of missing selector": [
+  "querySelector ElementHandle.$eval should work (queryselector.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page ElementHandle.$$eval should work": [
+  "querySelector ElementHandle.$eval should retrieve content from subtree (queryselector.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page ElementHandle.$$eval should retrieve content from subtree": [
+  "querySelector ElementHandle.$eval should throw in case of missing selector (queryselector.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page ElementHandle.$$eval should not throw in case of missing selector": [
+  "querySelector ElementHandle.$$eval should work (queryselector.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page ElementHandle.$$ should query existing elements": [
-    "PASS"
-  ],
-  "Firefox Browser Page ElementHandle.$$ should return empty array for non-existing elements": [
+  "querySelector ElementHandle.$$eval should retrieve content from subtree (queryselector.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page ElementHandle.$x should query existing element": [
+  "querySelector ElementHandle.$$eval should not throw in case of missing selector (queryselector.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page ElementHandle.$x should return null for non-existing element": [
+  "querySelector ElementHandle.$$ should query existing elements (queryselector.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Target Browser.targets should return all of the targets": [
+  "querySelector ElementHandle.$$ should return empty array for non-existing elements (queryselector.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Target Browser.pages should return all of the pages": [
+  "querySelector ElementHandle.$x should query existing element (queryselector.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Target should contain browser target": [
+  "querySelector ElementHandle.$x should return null for non-existing element (queryselector.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Target should be able to use the default page in the browser": [
-    "PASS"
-  ],
-  "Firefox Browser Page Target should report when a new page is created and closed": [
+  "request interception Page.setRequestInterception should intercept (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Page.setRequestInterception should work when POST is redirected with 302 (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Page.setRequestInterception should work when header manipulation headers with redirect (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Page.setRequestInterception should be able to remove headers (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Page.setRequestInterception should contain referer header (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Page.setRequestInterception should properly return navigation response when URL has cookies (requestinterception.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Target should report when a service worker is created and destroyed": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Target should create a worker from a service worker": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Target should create a worker from a shared worker": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Target should report when a target url changes": [
-    "TIMEOUT"
-  ],
-  "Firefox Browser Page Target should not report uninitialized pages": [
+  "request interception Page.setRequestInterception should stop intercepting (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Page.setRequestInterception should show custom HTTP headers (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Page.setRequestInterception should work with redirect inside sync XHR (requestinterception.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Target should not crash while redirecting if original request was missed": [
+  "request interception Page.setRequestInterception should work with custom referer headers (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Page.setRequestInterception should be abortable (requestinterception.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Target should have an opener": [
+  "request interception Page.setRequestInterception should be abortable with custom error codes (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Page.setRequestInterception should send referer (requestinterception.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Browser.waitForTarget should wait for a target": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Browser.waitForTarget should timeout waiting for a non-existent target": [
-    "PASS"
-  ],
-  "Firefox Browser Page Touchscreen should tap the button": [
+  "request interception Page.setRequestInterception should fail navigation when aborting main resource (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Page.setRequestInterception should work with redirects (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Page.setRequestInterception should work with redirects for subresources (requestinterception.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Touchscreen should report touches": [
+  "request interception Page.setRequestInterception should be able to abort redirects (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Page.setRequestInterception should work with equal requests (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Page.setRequestInterception should navigate to dataURL and fire dataURL requests (requestinterception.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Page.waitFor should wait for selector": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.waitFor should wait for an xpath": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.waitFor should not allow you to select an element with single slash xpath": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.waitFor should timeout": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.waitFor should work with multiline body": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.waitFor should wait for predicate": [
-    "PASS"
-  ],
-  "Firefox Browser Page Page.waitFor should throw when unknown type": [
+  "request interception Page.setRequestInterception should be able to fetch dataURL and fire dataURL requests (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Page.setRequestInterception should navigate to URL with hash and and fire requests without hash (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Page.setRequestInterception should work with encoded server (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Page.setRequestInterception should work with badly encoded server (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Page.setRequestInterception should work with encoded server - 2 (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Page.setRequestInterception should not throw \"Invalid Interception Id\" if the request was cancelled (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Page.setRequestInterception should throw if interception is not enabled (requestinterception.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Page.waitFor should wait for predicate with arguments": [
-    "PASS"
-  ],
-  "Firefox Browser Page Frame.waitForFunction should accept a string": [
-    "PASS"
-  ],
-  "Firefox Browser Page Frame.waitForFunction should work when resolved right before execution context disposal": [
-    "TIMEOUT"
-  ],
-  "Firefox Browser Page Frame.waitForFunction should poll on interval": [
-    "PASS"
-  ],
-  "Firefox Browser Page Frame.waitForFunction should poll on mutation": [
-    "PASS"
-  ],
-  "Firefox Browser Page Frame.waitForFunction should poll on raf": [
-    "PASS"
-  ],
-  "Firefox Browser Page Frame.waitForFunction should work with strict CSP policy": [
+  "request interception Page.setRequestInterception should work with file URLs (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Request.continue should work (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Request.continue should amend HTTP headers (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Request.continue should redirect in a way non-observable to page (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Request.continue should amend method (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Request.continue should amend post data (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Request.continue should amend both post data and method on navigation (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Request.respond should work (requestinterception.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Frame.waitForFunction should throw on bad polling value": [
-    "PASS"
-  ],
-  "Firefox Browser Page Frame.waitForFunction should throw negative polling interval": [
-    "PASS"
-  ],
-  "Firefox Browser Page Frame.waitForFunction should return the success value as a JSHandle": [
+  "request interception Request.respond should work with status code 422 (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Request.respond should redirect (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Request.respond should allow mocking binary responses (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "request interception Request.respond should stringify intercepted request response headers (requestinterception.spec.js)": [
+    "FAIL"
+  ],
+  "Screenshots Page.screenshot should work (screenshot.spec.js)": [
+    "FAIL"
+  ],
+  "Screenshots Page.screenshot should clip rect (screenshot.spec.js)": [
+    "FAIL"
+  ],
+  "Screenshots Page.screenshot should clip elements to the viewport (screenshot.spec.js)": [
+    "FAIL"
+  ],
+  "Screenshots Page.screenshot should run in parallel (screenshot.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Frame.waitForFunction should return the window as a success value": [
-    "PASS"
-  ],
-  "Firefox Browser Page Frame.waitForFunction should accept ElementHandle arguments": [
-    "PASS"
-  ],
-  "Firefox Browser Page Frame.waitForFunction should respect timeout": [
-    "PASS"
-  ],
-  "Firefox Browser Page Frame.waitForFunction should respect default timeout": [
-    "PASS"
-  ],
-  "Firefox Browser Page Frame.waitForFunction should disable timeout when its set to 0": [
+  "Screenshots Page.screenshot should take fullPage screenshots (screenshot.spec.js)": [
+    "FAIL"
+  ],
+  "Screenshots Page.screenshot should run in parallel in multiple pages (screenshot.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Frame.waitForFunction should survive cross-process navigation": [
-    "PASS"
-  ],
-  "Firefox Browser Page Frame.waitForFunction should survive navigations": [
-    "PASS"
-  ],
-  "Firefox Browser Page Frame.waitForSelector should immediately resolve promise if node exists": [
-    "PASS"
-  ],
-  "Firefox Browser Page Frame.waitForSelector should work with removed MutationObserver": [
+  "Screenshots Page.screenshot should allow transparency (screenshot.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Frame.waitForSelector should resolve promise when node is added": [
+  "Screenshots Page.screenshot should render white background on jpeg file (screenshot.spec.js)": [
+    "FAIL"
+  ],
+  "Screenshots Page.screenshot should work with odd clip size on Retina displays (screenshot.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Frame.waitForSelector should work when node is added through innerHTML": [
-    "PASS"
-  ],
-  "Firefox Browser Page Frame.waitForSelector Page.waitForSelector is shortcut for main frame": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Frame.waitForSelector should run in specified frame": [
-    "PASS",
-    "FAIL"
-  ],
-  "Firefox Browser Page Frame.waitForSelector should throw when frame is detached": [
-    "PASS",
+  "Screenshots Page.screenshot should return base64 (screenshot.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Frame.waitForSelector should survive cross-process navigation": [
+  "Screenshots ElementHandle.screenshot should work (screenshot.spec.js)": [
+    "PASS"
+  ],
+  "Screenshots ElementHandle.screenshot should take into account padding and border (screenshot.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Frame.waitForSelector should wait for visible": [
+  "Screenshots ElementHandle.screenshot should capture full element when larger than viewport (screenshot.spec.js)": [
+    "PASS"
+  ],
+  "Screenshots ElementHandle.screenshot should scroll element into view (screenshot.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Frame.waitForSelector should wait for visible recursively": [
+  "Screenshots ElementHandle.screenshot should work with a rotated element (screenshot.spec.js)": [
+    "FAIL"
+  ],
+  "Screenshots ElementHandle.screenshot should fail to screenshot a detached element (screenshot.spec.js)": [
+    "FAIL"
+  ],
+  "Screenshots ElementHandle.screenshot should not hang with zero width/height element (screenshot.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Frame.waitForSelector hidden should wait for visibility: hidden": [
-    "PASS"
-  ],
-  "Firefox Browser Page Frame.waitForSelector hidden should wait for display: none": [
+  "Screenshots ElementHandle.screenshot should work for an element with fractional dimensions (screenshot.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Frame.waitForSelector hidden should wait for removal": [
-    "PASS"
-  ],
-  "Firefox Browser Page Frame.waitForSelector should return null if waiting to hide non-existing element": [
+  "Screenshots ElementHandle.screenshot should work for an element with an offset (screenshot.spec.js)": [
+    "FAIL"
+  ],
+  "Target Browser.targets should return all of the targets (target.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Frame.waitForSelector should respect timeout": [
+  "Target Browser.pages should return all of the pages (target.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Frame.waitForSelector should have an error message specifically for awaiting an element to be hidden": [
+  "Target should contain browser target (target.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Frame.waitForSelector should respond to node attribute mutation": [
+  "Target should be able to use the default page in the browser (target.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Frame.waitForSelector should return the element handle": [
-    "PASS"
-  ],
-  "Firefox Browser Page Frame.waitForSelector should have correct stack trace for timeout": [
-    "PASS"
-  ],
-  "Firefox Browser Page Frame.waitForXPath should support some fancy xpath": [
-    "PASS"
-  ],
-  "Firefox Browser Page Frame.waitForXPath should respect timeout": [
-    "PASS"
-  ],
-  "Firefox Browser Page Frame.waitForXPath should run in specified frame": [
+  "Target should report when a new page is created and closed (target.spec.js)": [
+    "FAIL"
+  ],
+  "Target should report when a service worker is created and destroyed (target.spec.js)": [
+    "TIMEOUT"
+  ],
+  "Target should create a worker from a service worker (target.spec.js)": [
+    "TIMEOUT"
+  ],
+  "Target should create a worker from a shared worker (target.spec.js)": [
+    "TIMEOUT"
+  ],
+  "Target should report when a target url changes (target.spec.js)": [
+    "TIMEOUT"
+  ],
+  "Target should not report uninitialized pages (target.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Frame.waitForXPath should throw when frame is detached": [
+  "Target should not crash while redirecting if original request was missed (target.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser Page Frame.waitForXPath hidden should wait for display: none": [
+  "Target should have an opener (target.spec.js)": [
+    "TIMEOUT", "FAIL"
+  ],
+  "Target Browser.waitForTarget should wait for a target (target.spec.js)": [
+    "TIMEOUT"
+  ],
+  "Target Browser.waitForTarget should timeout waiting for a non-existent target (target.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Frame.waitForXPath should return the element handle": [
+  "Touchscreen should tap the button (touchscreen.spec.js)": [
+    "FAIL"
+  ],
+  "Touchscreen should report touches (touchscreen.spec.js)": [
+    "FAIL"
+  ],
+  "waittask specs Page.waitFor should wait for selector (waittask.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Frame.waitForXPath should allow you to select a text node": [
+  "waittask specs Page.waitFor should wait for an xpath (waittask.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Frame.waitForXPath should allow you to select an element with single slash": [
+  "waittask specs Page.waitFor should not allow you to select an element with single slash xpath (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Page.waitFor should timeout (waittask.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser Page Workers Page.workers": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Workers should emit created and destroyed events": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Workers should report console logs": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Workers should have JSHandles for console logs": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Workers should have an execution context": [
-    "SKIP"
-  ],
-  "Firefox Browser Page Workers should report errors": [
-    "SKIP"
-  ],
-  "Firefox Browser BrowserContext should have default context": [
-    "FAIL"
-  ],
-  "Firefox Browser BrowserContext should create new incognito context": [
+  "waittask specs Page.waitFor should work with multiline body (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Page.waitFor should wait for predicate (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Page.waitFor should throw when unknown type (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Page.waitFor should wait for predicate with arguments (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Frame.waitForFunction should accept a string (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Frame.waitForFunction should work when resolved right before execution context disposal (waittask.spec.js)": [
+    "TIMEOUT"
+  ],
+  "waittask specs Frame.waitForFunction should poll on interval (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Frame.waitForFunction should poll on mutation (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Frame.waitForFunction should poll on raf (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Frame.waitForFunction should work with strict CSP policy (waittask.spec.js)": [
     "FAIL"
   ],
-  "Firefox Browser BrowserContext should close all belonging targets once closing context": [
-    "FAIL"
-  ],
-  "Firefox Browser BrowserContext window.open should use parent tab context": [
-    "FAIL"
-  ],
-  "Firefox Browser BrowserContext should fire target events": [
-    "FAIL"
-  ],
-  "Firefox Browser BrowserContext should wait for a target": [
-    "SKIP"
-  ],
-  "Firefox Browser BrowserContext should timeout waiting for a non-existent target": [
-    "PASS"
-  ],
-  "Firefox Browser BrowserContext should isolate localStorage and cookies": [
-    "FAIL"
-  ],
-  "Firefox Browser BrowserContext should work across sessions": [
-    "FAIL"
-  ],
-  "Firefox ignoreHTTPSErrors Response.securityDetails should work": [
+  "waittask specs Frame.waitForFunction should throw on bad polling value (waittask.spec.js)": [
     "PASS"
   ],
-  "Firefox ignoreHTTPSErrors Response.securityDetails should be |null| for non-secure requests": [
+  "waittask specs Frame.waitForFunction should throw negative polling interval (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Frame.waitForFunction should return the success value as a JSHandle (waittask.spec.js)": [
     "PASS"
   ],
-  "Firefox ignoreHTTPSErrors Response.securityDetails Network redirects should report SecurityDetails": [
+  "waittask specs Frame.waitForFunction should return the window as a success value (waittask.spec.js)": [
     "PASS"
   ],
-  "Firefox ignoreHTTPSErrors should work": [
+  "waittask specs Frame.waitForFunction should accept ElementHandle arguments (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Frame.waitForFunction should respect timeout (waittask.spec.js)": [
     "PASS"
   ],
-  "Firefox ignoreHTTPSErrors should work with request interception": [
-    "FAIL"
-  ],
-  "Firefox ignoreHTTPSErrors should work with mixed content": [
-    "FAIL"
-  ],
-  "Firefox DefaultBrowserContext page.cookies() should work": [
-    "FAIL"
-  ],
-  "Firefox DefaultBrowserContext page.setCookie() should work": [
+  "waittask specs Frame.waitForFunction should respect default timeout (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Frame.waitForFunction should disable timeout when its set to 0 (waittask.spec.js)": [
     "PASS"
   ],
-  "Firefox DefaultBrowserContext page.deleteCookie() should work": [
+  "waittask specs Frame.waitForFunction should survive cross-process navigation (waittask.spec.js)": [
     "PASS"
   ],
-  "Firefox Puppeteer BrowserFetcher should download and extract linux binary": [
+  "waittask specs Frame.waitForFunction should survive navigations (waittask.spec.js)": [
     "PASS"
   ],
-  "Firefox Puppeteer Browser.disconnect should reject navigation when browser closes": [
+  "waittask specs Frame.waitForSelector should immediately resolve promise if node exists (waittask.spec.js)": [
     "PASS"
   ],
-  "Firefox Puppeteer Browser.disconnect should reject waitForSelector when browser closes": [
-    "SKIP"
-  ],
-  "Firefox Puppeteer Browser.close should terminate network waiters": [
-    "SKIP"
-  ],
-  "Firefox Puppeteer Puppeteer.launch should reject all promises when browser is closed": [
-    "SKIP"
-  ],
-  "Firefox Puppeteer Puppeteer.launch should reject if executable path is invalid": [
+  "waittask specs Frame.waitForSelector should work with removed MutationObserver (waittask.spec.js)": [
+    "FAIL"
+  ],
+  "waittask specs Frame.waitForSelector should resolve promise when node is added (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Frame.waitForSelector should work when node is added through innerHTML (waittask.spec.js)": [
     "PASS"
   ],
-  "Firefox Puppeteer Puppeteer.launch userDataDir option": [
-    "SKIP"
-  ],
-  "Firefox Puppeteer Puppeteer.launch userDataDir argument": [
-    "SKIP"
-  ],
-  "Firefox Puppeteer Puppeteer.launch userDataDir option should restore state": [
-    "SKIP"
-  ],
-  "Firefox Puppeteer Puppeteer.launch userDataDir option should restore cookies": [
-    "SKIP"
-  ],
-  "Firefox Puppeteer Puppeteer.launch should return the default arguments": [
+  "waittask specs Frame.waitForSelector Page.waitForSelector is shortcut for main frame (waittask.spec.js)": [
+    "FAIL"
+  ],
+  "waittask specs Frame.waitForSelector should run in specified frame (waittask.spec.js)": [
+    "FAIL"
+  ],
+  "waittask specs Frame.waitForSelector should throw when frame is detached (waittask.spec.js)": [
+    "FAIL"
+  ],
+  "waittask specs Frame.waitForSelector should survive cross-process navigation (waittask.spec.js)": [
     "PASS"
   ],
-  "Firefox Puppeteer Puppeteer.launch should report the correct product": [
+  "waittask specs Frame.waitForSelector should wait for visible (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Frame.waitForSelector should wait for visible recursively (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Frame.waitForSelector hidden should wait for visibility: hidden (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Frame.waitForSelector hidden should wait for display: none (waittask.spec.js)": [
     "PASS"
   ],
-  "Firefox Puppeteer Puppeteer.launch should work with no default arguments": [
-    "SKIP"
-  ],
-  "Firefox Puppeteer Puppeteer.launch should filter out ignored default arguments": [
-    "SKIP"
-  ],
-  "Firefox Puppeteer Puppeteer.launch should have default URL when launching browser": [
-    "SKIP"
-  ],
-  "Firefox Puppeteer Puppeteer.launch should have custom URL when launching browser": [
-    "SKIP"
-  ],
-  "Firefox Puppeteer Puppeteer.launch should set the default viewport": [
-    "SKIP"
-  ],
-  "Firefox Puppeteer Puppeteer.launch should disable the default viewport": [
-    "SKIP"
-  ],
-  "Firefox Puppeteer Puppeteer.launch should take fullPage screenshots when defaultViewport is null": [
-    "SKIP"
-  ],
-  "Firefox Puppeteer Puppeteer.connect should be able to connect multiple times to the same browser": [
-    "SKIP"
-  ],
-  "Firefox Puppeteer Puppeteer.connect should be able to close remote browser": [
-    "SKIP"
-  ],
-  "Firefox Puppeteer Puppeteer.connect should support ignoreHTTPSErrors option": [
-    "SKIP"
-  ],
-  "Firefox Puppeteer Puppeteer.connect should be able to reconnect to a disconnected browser": [
-    "SKIP"
-  ],
-  "Firefox Puppeteer Puppeteer.connect should be able to connect to the same page simultaneously": [
-    "SKIP"
-  ],
-  "Firefox Puppeteer Puppeteer.executablePath should work": [
-    "SKIP"
-  ],
-  "Firefox Top-level requires should require top-level Errors": [
+  "waittask specs Frame.waitForSelector hidden should wait for removal (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Frame.waitForSelector should return null if waiting to hide non-existing element (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Frame.waitForSelector should respect timeout (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Frame.waitForSelector should have an error message specifically for awaiting an element to be hidden (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Frame.waitForSelector should respond to node attribute mutation (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Frame.waitForSelector should return the element handle (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Frame.waitForSelector should have correct stack trace for timeout (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Frame.waitForXPath should support some fancy xpath (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Frame.waitForXPath should respect timeout (waittask.spec.js)": [
     "PASS"
   ],
-  "Firefox Top-level requires should require top-level DeviceDescriptors": [
+  "waittask specs Frame.waitForXPath should run in specified frame (waittask.spec.js)": [
+    "FAIL"
+  ],
+  "waittask specs Frame.waitForXPath should throw when frame is detached (waittask.spec.js)": [
+    "FAIL"
+  ],
+  "waittask specs Frame.waitForXPath hidden should wait for display: none (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Frame.waitForXPath should return the element handle (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Frame.waitForXPath should allow you to select a text node (waittask.spec.js)": [
+    "PASS"
+  ],
+  "waittask specs Frame.waitForXPath should allow you to select an element with single slash (waittask.spec.js)": [
     "PASS"
   ],
-  "Firefox Browser target events should work": [
-    "SKIP"
-  ],
-  "Firefox Browser.Events.disconnected should be emitted when: browser gets closed, disconnected or underlying websocket gets closed": [
-    "SKIP"
-  ],
-  "Firefox Fixtures dumpio option should work with pipe option": [
-    "SKIP"
-  ],
-  "Firefox Fixtures should dump browser process stderr": [
-    "SKIP"
-  ],
-  "Firefox Fixtures should close the browser when the node process closes": [
-    "SKIP"
+  "Workers Page.workers (worker.spec.js)": [
+    "TIMEOUT"
+  ],
+  "Workers should emit created and destroyed events (worker.spec.js)": [
+    "TIMEOUT"
+  ],
+  "Workers should report console logs (worker.spec.js)": [
+    "FAIL"
+  ],
+  "Workers should have JSHandles for console logs (worker.spec.js)": [
+    "FAIL"
+  ],
+  "Workers should have an execution context (worker.spec.js)": [
+    "TIMEOUT"
+  ],
+  "Workers should report errors (worker.spec.js)": [
+    "TIMEOUT"
   ]
 }
deleted file mode 100644
--- a/remote/test/puppeteer/.appveyor.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-environment:
-  matrix:
-    - nodejs_version: "8.16.0"
-      FLAKINESS_DASHBOARD_NAME: Appveyor Chromium (Win + node8)
-  FLAKINESS_DASHBOARD_PASSWORD:
-    secure: g66jP+j6C+hkXLutBV9fdxB5fRJgcQQzy93SgQzXUmcCl/RjkJwnzyHvX0xfCVnv
-
-build: off
-
-install:
-  - ps: $env:FLAKINESS_DASHBOARD_BUILD_URL="https://ci.appveyor.com/project/aslushnikov/puppeteer/builds/$env:APPVEYOR_BUILD_ID/job/$env:APPVEYOR_JOB_ID"
-  - ps: Install-Product node $env:nodejs_version
-  - npm install
-  - if "%nodejs_version%" == "8.16.0" (
-      npm run lint &&
-      npm run coverage  &&
-      npm run test-doclint &&
-      npm run test-types
-    )
--- a/remote/test/puppeteer/.ci/node10/Dockerfile.linux
+++ b/remote/test/puppeteer/.ci/node10/Dockerfile.linux
@@ -1,13 +1,13 @@
 FROM node:10
 
 RUN apt-get update && \
     apt-get -y install xvfb gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 \
-      libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 \
+      libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 \
       libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 \
       libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 \
       libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget && \
     rm -rf /var/lib/apt/lists/*
 
 # Add user so we don't need --no-sandbox.
 RUN groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser \
     && mkdir -p /home/pptruser/Downloads \
--- a/remote/test/puppeteer/.ci/node12/Dockerfile.linux
+++ b/remote/test/puppeteer/.ci/node12/Dockerfile.linux
@@ -1,13 +1,13 @@
 FROM node:12
 
 RUN apt-get update && \
     apt-get -y install xvfb gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 \
-      libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 \
+      libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 \
       libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 \
       libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 \
       libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget && \
     rm -rf /var/lib/apt/lists/*
 
 # Add user so we don't need --no-sandbox.
 RUN groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser \
     && mkdir -p /home/pptruser/Downloads \
deleted file mode 100644
--- a/remote/test/puppeteer/.ci/node8/Dockerfile.linux
+++ /dev/null
@@ -1,17 +0,0 @@
-FROM node:8.11.3
-
-RUN apt-get update && \
-    apt-get -y install xvfb gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 \
-      libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 \
-      libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 \
-      libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 \
-      libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget && \
-    rm -rf /var/lib/apt/lists/*
-
-# Add user so we don't need --no-sandbox.
-RUN groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser \
-    && mkdir -p /home/pptruser/Downloads \
-    && chown -R pptruser:pptruser /home/pptruser
-
-# Run everything after as non-privileged user.
-USER pptruser
deleted file mode 100644
--- a/remote/test/puppeteer/.cirrus.yml
+++ /dev/null
@@ -1,47 +0,0 @@
-env:
-  DISPLAY: :99.0
-  FLAKINESS_DASHBOARD_PASSWORD: ENCRYPTED[b3e207db5d153b543f219d3c3b9123d8321834b783b9e45ac7d380e026ab3a56398bde51b521ac5859e7e45cb95d0992]
-  FLAKINESS_DASHBOARD_NAME: Cirrus ${CIRRUS_TASK_NAME}
-  FLAKINESS_DASHBOARD_BUILD_URL: https://cirrus-ci.com/task/${CIRRUS_TASK_ID}
-
-task:
-  matrix:
-    - name: Chromium (node8 + linux)
-      container:
-        dockerfile: .ci/node8/Dockerfile.linux
-    - name: Chromium (node10 + linux)
-      container:
-        dockerfile: .ci/node10/Dockerfile.linux
-    - name: Chromium (node12 + linux)
-      container:
-        dockerfile: .ci/node12/Dockerfile.linux
-  xvfb_start_background_script: Xvfb :99 -ac -screen 0 1024x768x24
-  install_script: npm install --unsafe-perm
-  lint_script: npm run lint
-  coverage_script: npm run coverage
-  test_doclint_script: npm run test-doclint
-  test_types_script: npm run test-types
-
-task:
-  matrix:
-    - name: Firefox Juggler (node8 + linux)
-      container:
-        dockerfile: .ci/node8/Dockerfile.linux
-      xvfb_start_background_script: Xvfb :99 -ac -screen 0 1024x768x24
-  install_script: npm install --unsafe-perm && cd experimental/puppeteer-firefox && npm install --unsafe-perm
-  test_script: npm run fjunit
-
-task:
-  osx_instance:
-    image: high-sierra-base
-  name: Chromium (node8 + macOS)
-  env:
-    HOMEBREW_NO_AUTO_UPDATE: 1
-  node_install_script:
-    - brew install node@8
-    - brew link --force node@8
-  install_script: npm install --unsafe-perm
-  lint_script: npm run lint
-  coverage_script: npm run coverage
-  test_doclint_script: npm run test-doclint
-  test_types_script: npm run test-types
--- a/remote/test/puppeteer/.eslintignore
+++ b/remote/test/puppeteer/.eslintignore
@@ -1,9 +1,14 @@
 test/assets/modernizr.js
 third_party/*
 utils/browser/puppeteer-web.js
 utils/doclint/check_public_api/test/
-utils/testrunner/examples/
 node6/*
 node6-test/*
-node6-testrunner/*
 experimental/
+lib/
+src/externs.d.ts
+src/protocol.d.ts
+/index.d.ts
+# We ignore this file because it uses ES imports which we don't yet use
+# in the Puppeteer src, so it trips up the ESLint-TypeScript parser.
+utils/doclint/generate_types/test/test.ts
--- a/remote/test/puppeteer/.eslintrc.js
+++ b/remote/test/puppeteer/.eslintrc.js
@@ -1,60 +1,46 @@
 module.exports = {
     "root": true,
-
     "env": {
         "node": true,
         "es6": true
     },
 
-    "parserOptions": {
-        "ecmaVersion": 9
-    },
+    "parser": "@typescript-eslint/parser",
+
+    "plugins": [
+        "mocha",
+        "@typescript-eslint",
+        "unicorn"
+    ],
 
-    /**
-     * ESLint rules
-     *
-     * All available rules: http://eslint.org/docs/rules/
-     *
-     * Rules take the following form:
-     *   "rule-name", [severity, { opts }]
-     * Severity: 2 == error, 1 == warning, 0 == off.
-     */
+    "extends": [
+        "plugin:prettier/recommended"
+    ],
+
     "rules": {
-        /**
-         * Enforced rules
-         */
-
-
+        // Error if files are not formatted with Prettier correctly.
+        "prettier/prettier": 2,
         // syntax preferences
         "quotes": [2, "single", {
             "avoidEscape": true,
             "allowTemplateLiterals": true
         }],
-        "semi": 2,
-        "no-extra-semi": 2,
-        "comma-style": [2, "last"],
-        "wrap-iife": [2, "inside"],
         "spaced-comment": [2, "always", {
             "markers": ["*"]
         }],
         "eqeqeq": [2],
-        "arrow-body-style": [2, "as-needed"],
         "accessor-pairs": [2, {
             "getWithoutSet": false,
             "setWithoutGet": false
         }],
-        "brace-style": [2, "1tbs", {"allowSingleLine": true}],
-        "curly": [2, "multi-or-nest", "consistent"],
         "new-parens": 2,
         "func-call-spacing": 2,
-        "arrow-parens": [2, "as-needed"],
         "prefer-const": 2,
-        "quote-props": [2, "consistent"],
 
         // anti-patterns
         "no-var": 2,
         "no-with": 2,
         "no-multi-str": 2,
         "no-caller": 2,
         "no-implied-eval": 2,
         "no-labels": 2,
@@ -73,40 +59,37 @@ module.exports = {
         "valid-typeof": 2,
         "no-unused-vars": [2, { "args": "none", "vars": "local", "varsIgnorePattern": "([fx]?describe|[fx]?it|beforeAll|beforeEach|afterAll|afterEach)" }],
         "no-implicit-globals": [2],
 
         // es2015 features
         "require-yield": 2,
         "template-curly-spacing": [2, "never"],
 
-        // spacing details
-        "space-infix-ops": 2,
-        "space-in-parens": [2, "never"],
-        "space-before-function-paren": [2, "never"],
-        "no-whitespace-before-property": 2,
-        "keyword-spacing": [2, {
-            "overrides": {
-                "if": {"after": true},
-                "else": {"after": true},
-                "for": {"after": true},
-                "while": {"after": true},
-                "do": {"after": true},
-                "switch": {"after": true},
-                "return": {"after": true}
+        // ensure we don't have any it.only or describe.only in prod
+        "mocha/no-exclusive-tests": "error",
+
+        // enforce the variable in a catch block is named error
+        "unicorn/catch-error-name": "error"
+    },
+    "overrides": [
+        {
+            "files": ["*.ts"],
+            "extends": [
+                'plugin:@typescript-eslint/eslint-recommended',
+                'plugin:@typescript-eslint/recommended',
+            ],
+            "rules": {
+                "no-unused-vars": 0,
+                "@typescript-eslint/no-unused-vars": 2,
+                "semi": 0,
+                "@typescript-eslint/semi": 2,
+                "@typescript-eslint/no-empty-function": 0,
+                "@typescript-eslint/no-use-before-define": 0,
+                // We know it's bad and use it very sparingly but it's needed :(
+                "@typescript-eslint/ban-ts-ignore": 0,
+                "@typescript-eslint/array-type": [2, {
+                    "default": "array-simple"
+                }]
             }
-        }],
-        "arrow-spacing": [2, {
-            "after": true,
-            "before": true
-        }],
-
-        // file whitespace
-        "no-multiple-empty-lines": [2, {"max": 2}],
-        "no-mixed-spaces-and-tabs": 2,
-        "no-trailing-spaces": 2,
-        "linebreak-style": [ process.platform === "win32" ? 0 : 2, "unix" ],
-        "indent": [2, 2, { "SwitchCase": 1, "CallExpression": {"arguments": 2}, "MemberExpression": 2 }],
-        "key-spacing": [2, {
-            "beforeColon": false
-        }]
-    }
+        }
+    ]
 };
deleted file mode 100644
--- a/remote/test/puppeteer/.npmignore
+++ /dev/null
@@ -1,44 +0,0 @@
-.appveyor.yml
-.gitattributes
-
-# no longer generated, but old checkouts might still have it
-node6
-
-# exclude all tests
-test
-utils/node6-transform
-
-# exclude internal type definitions
-/lib/externs.d.ts
-
-# repeats from .gitignore
-node_modules
-.local-chromium
-.dev_profile*
-.DS_Store
-*.swp
-*.pyc
-.vscode
-package-lock.json
-/node6/test
-/node6/utils
-/test
-/utils
-/docs
-yarn.lock
-
-# other
-/.ci
-/examples
-.appveyour.yml
-.cirrus.yml
-.editorconfig
-.eslintignore
-.eslintrc.js
-.travis.yml
-README.md
-tsconfig.json
-experimental
-
-# exclude types, see https://github.com/puppeteer/puppeteer/issues/3878
-/index.d.ts
--- a/remote/test/puppeteer/.travis.yml
+++ b/remote/test/puppeteer/.travis.yml
@@ -1,49 +1,82 @@
 language: node_js
-dist: trusty
-addons:
-  apt:
-    packages:
-      # This is required to run new chrome on old trusty
-      - libnss3
+services: xvfb
+
+jobs:
+  include:
+    - os: "osx"
+      name: 'Unit tests: macOS/Chromium'
+      node_js: "10.19.0"
+      env:
+        - CHROMIUM=true
+      before_install:
+        - PUPPETEER_PRODUCT=firefox npm install
+      script:
+        - ls .local-chromium .local-firefox
+        - npm run tsc
+        - travis_retry npm run unit
+
+    - os: "windows"
+      name: 'Unit tests: Windows/Chromium'
+      node_js: "10.19.0"
+      env:
+        - CHROMIUM=true
+      before_install:
+        - PUPPETEER_PRODUCT=firefox npm install
+      script:
+        - ls .local-chromium .local-firefox
+        - npm run tsc
+        - travis_retry npm run unit
+
+    # Runs unit tests on Linux + Chromium
+    - node_js: "10.19.0"
+      name: 'Unit tests [with coverage]: Linux/Chromium'
+      env:
+        - CHROMIUM=true
+      before_install:
+        - PUPPETEER_PRODUCT=firefox npm install
+      script:
+        - travis_retry npm run unit-with-coverage
+        - npm run assert-unit-coverage
+
+    - node_js: "12.16.3"
+      name: 'Unit tests [Node 12]: Linux/Chromium'
+      env:
+        - CHROMIUM=true
+      before_install:
+        - PUPPETEER_PRODUCT=firefox npm install
+      script:
+        - travis_retry npm run unit
+
+    - node_js: "14.2.0"
+      name: 'Unit tests [Node 14]: Linux/Chromium'
+      env:
+        - CHROMIUM=true
+      before_install:
+        - PUPPETEER_PRODUCT=firefox npm install
+      script:
+        - travis_retry npm run unit
+
+    # This bot runs all the extra checks that aren't the main Puppeteer unit tests
+    - node_js: "10.19.0"
+      name: 'Extra tests: Linux/Chromium'
+      env:
+        - CHROMIUM=true
+      script:
+        - npm run compare-protocol-d-ts
+        - npm run test-install
+        - npm run lint
+        - npm run test-doclint
+        - npm run test-types
+
+    # Runs unit tests on Linux + Firefox
+    - node_js: "10.19.0"
+      name: 'Unit tests: Linux/Firefox'
+      env:
+        - FIREFOX=true
+      before_install:
+        - PUPPETEER_PRODUCT=firefox npm install
+      script:
+        - travis_retry npm run funit
+
 notifications:
   email: false
-cache:
-  directories:
-    - node_modules
-# allow headful tests
-before_install:
-  - "sysctl kernel.unprivileged_userns_clone=1"
-  - "export DISPLAY=:99.0"
-  - "sh -e /etc/init.d/xvfb start"
-script:
-  - 'if [ "$NODE8" = "true" ]; then npm run lint; fi'
-  - 'if [ "$NODE8" = "true" ]; then npm run coverage; fi'
-  - 'if [ "$FIREFOX" = "true" ]; then cd experimental/puppeteer-firefox && npm i && cd ../..; fi'
-  - 'if [ "$FIREFOX" = "true" ]; then npm run fjunit; fi'
-  - 'if [ "$NODE8" = "true" ]; then npm run test-doclint; fi'
-  - 'if [ "$NODE8" = "true" ]; then npm run test-types; fi'
-  - 'if [ "$NODE8" = "true" ]; then npm run bundle; fi'
-  - 'if [ "$NODE8" = "true" ]; then npm run unit-bundle; fi'
-jobs:
-  include:
-    - node_js: "8.16.0"
-      env:
-        - NODE8=true
-        - FLAKINESS_DASHBOARD_NAME="Travis Chromium (node8 + linux)"
-        - FLAKINESS_DASHBOARD_BUILD_URL="${TRAVIS_JOB_WEB_URL}"
-    - node_js: "8.16.0"
-      env:
-        - FIREFOX=true
-        - FLAKINESS_DASHBOARD_NAME="Travis Firefox (node8 + linux)"
-        - FLAKINESS_DASHBOARD_BUILD_URL="${TRAVIS_JOB_WEB_URL}"
-before_deploy: "npm run apply-next-version"
-deploy:
-  provider: npm
-  email: aslushnikov@gmail.com
-  api_key:
-    secure: Ng8o2KwJf90XCBNgUKK3jRZnwtdBSJatjYNmZBERJEqBWFTadFAp1NdhxZaqjnuG8aFYaH5bRJdL+EQBYUksVCbrv/gcaXeEFkwsfPfVX1QXGqu7NnZmtme2hbxppLQ7dEJ8hz2Z9K4vehqVOxmLabxvoupOumxEQMLCphVHh2FOmsm/S5JrRZqZ4V9k76eIc0/PiyfXNMdx5WTZjHbIRDIHRy9nqOXjFp2Rx3PMa3uU2fS8mTshYEYs151TA6e6VdHjqmBwEQC/M5tXbDlLCMNUr4JBtLTcL4OipNYjzkwD1N2xYlbSRqtvqqF4ifdvFhoI65a31GinlMC7Z/SH1Zy+d+/z3Mo7D63eYcsJVnsg9OYxTFy2piUntr0JqTBHtQoe/CvGxJmkcVt+H6YSkcBibSG9s9tG3qpAD5wBCFqqOYnfClX+YZziEd+Hngd9inxAf87qdvgVIZ5tPD2dygtE+te2/qoEHtvccv/HuS8MxNj5iKwlP7JaBPM6uAkazYqZP2R99I2ph9gNOEVuQLtk+3+OIdb8HWrEKUrJBgKhdKY1dvcKYElI+D8NRlyzrr6BnZfudACuAt2EtfKpfJ3mL+iRMFdBJ3ntLt93xBrB+j4z3pD0iWZcg1g3I742PFzQEHzyd/DDTP1yRTUoJeQWwoQRJyNO1m6Qk4wx77c=
-  on:
-    branch: master
-    condition: "$NODE8 = true"
-  skip_cleanup: true
-  tag: next
--- a/remote/test/puppeteer/CONTRIBUTING.md
+++ b/remote/test/puppeteer/CONTRIBUTING.md
@@ -1,14 +1,15 @@
 <!-- gen:toc -->
 - [How to Contribute](#how-to-contribute)
   * [Contributor License Agreement](#contributor-license-agreement)
   * [Getting Code](#getting-code)
   * [Code reviews](#code-reviews)
   * [Code Style](#code-style)
+  * [TypeScript guidelines](#typescript-guidelines)
   * [API guidelines](#api-guidelines)
   * [Commit Messages](#commit-messages)
   * [Writing Documentation](#writing-documentation)
   * [Adding New Dependencies](#adding-new-dependencies)
   * [Running & Writing Tests](#running--writing-tests)
   * [Public API Coverage](#public-api-coverage)
   * [Debugging Puppeteer](#debugging-puppeteer)
 - [For Project Maintainers](#for-project-maintainers)
@@ -58,26 +59,37 @@ npm run unit
 
 All submissions, including submissions by project members, require review. We
 use GitHub pull requests for this purpose. Consult
 [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
 information on using pull requests.
 
 ## Code Style
 
-- Coding style is fully defined in [.eslintrc](https://github.com/puppeteer/puppeteer/blob/master/.eslintrc.js)
-- Code should be annotated with [closure annotations](https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler).
-- Comments should be generally avoided. If the code would not be understood without comments, consider re-writing the code to make it self-explanatory.
+- Coding style is fully defined in [`.eslintrc`](https://github.com/puppeteer/puppeteer/blob/master/.eslintrc.js) and we automatically format our code with [Prettier](https://prettier.io).
+- It's recommended to set-up Prettier into your editor, or you can run `npm run eslint-fix` to automatically format any files.
+- If you're working in a JS file, code should be annotated with [closure annotations](https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler).
+- If you're working in a TS file, you should explicitly type all variables and return types. You'll get ESLint warnings if you don't so if you're not sure use them as guidelines, and feel free to ask us for help!
 
-To run code linter, use:
+To run ESLint, use:
 
 ```bash
-npm run lint
+npm run eslint
 ```
 
+You can check your code (both JS & TS) type-checks by running:
+
+```bash
+npm run tsc
+```
+
+## TypeScript guidelines
+
+- Try to avoid the use of `any` when possible. Consider `unknown` as a better alternative. You are able to use `any` if needbe, but it will generate an ESLint warning.
+
 ## API guidelines
 
 When authoring new API methods, consider the following:
 
 - Expose as little information as needed. When in doubt, don’t expose new information.
 - Methods are used in favor of getters/setters.
   - The only exception is namespaces, e.g. `page.keyboard` and `page.coverage`
 - All string literals must be small case. This includes event names and option values.
@@ -140,48 +152,34 @@ For all dependencies (both installation 
 
 A barrier for introducing new installation dependencies is especially high:
 - **Do not add** installation dependency unless it's critical to project success.
 
 ## Running & Writing Tests
 
 - Every feature should be accompanied by a test.
 - Every public api event/method should be accompanied by a test.
-- Tests should be *hermetic*. Tests should not depend on external services.
+- Tests should not depend on external services.
 - Tests should work on all three platforms: Mac, Linux and Win. This is especially important for screenshot tests.
 
-Puppeteer tests are located in [`test/test.js`](https://github.com/puppeteer/puppeteer/blob/master/test/test.js)
-and are written with a [TestRunner](https://github.com/puppeteer/puppeteer/tree/master/utils/testrunner) framework.
+Puppeteer tests are located in the test directory ([`test`](https://github.com/puppeteer/puppeteer/blob/master/test/) and are written using Mocha. See [`test/README.md`](https://github.com/puppeteer/puppeteer/blob/master/test/) for more details.
+
 Despite being named 'unit', these are integration tests, making sure public API methods and events work as expected.
 
 - To run all tests:
 
 ```bash
 npm run unit
 ```
 
-- To run tests in parallel, use `-j` flag:
-
-```bash
-npm run unit -- -j 4
-```
-
-- To run tests in "verbose" mode or to stop testrunner on first failure:
-
-```bash
-npm run unit -- --verbose
-npm run unit -- --break-on-failure
-```
-
-- To run a specific test, substitute the `it` with `fit` (mnemonic rule: '*focus it*'):
+- To run a specific test, substitute the `it` with `it.only`:
 
 ```js
   ...
-  // Using "fit" to run specific test
-  fit('should work', async function({server, page}) {
+  it.only('should work', async function({server, page}) {
     const response = await page.goto(server.EMPTY_PAGE);
     expect(response.ok).toBe(true);
   });
 ```
 
 - To disable a specific test, substitute the `it` with `xit` (mnemonic rule: '*cross it*'):
 
 ```js
@@ -194,41 +192,29 @@ npm run unit -- --break-on-failure
 ```
 
 - To run tests in non-headless mode:
 
 ```bash
 HEADLESS=false npm run unit
 ```
 
+- To run Firefox tests, firstly ensure you have Firefox installed locally (you only need to do this once, not on every test run) and then you can run the tests:
+
+```bash
+PUPPETEER_PRODUCT=firefox node install.js
+PUPPETEER_PRODUCT=firefox npm run unit
+```
+
 - To run tests with custom browser executable:
 
 ```bash
 BINARY=<path-to-executable> npm run unit
 ```
 
-- To run tests in slow-mode:
-
-```bash
-HEADLESS=false SLOW_MO=500 npm run unit
-```
-
-- To run tests with additional Launcher options:
-
-```bash
-EXTRA_LAUNCH_OPTIONS='{"args": ["--user-data-dir=some/path"], "handleSIGINT": true}' npm run unit
-```
-
-
-- To debug a test, "focus" a test first and then run:
-
-```bash
-node --inspect-brk test/test.js
-```
-
 ## Public API Coverage
 
 Every public API method or event should be called at least once in tests. To ensure this, there's a `coverage` command which tracks calls to public API and reports back if some methods/events were not called.
 
 Run coverage:
 
 ```bash
 npm run coverage
@@ -242,52 +228,45 @@ See [Debugging Tips](README.md#debugging
 
 ## Releasing to npm
 
 Releasing to npm consists of the following phases:
 
 1. Source Code: mark a release.
     1. Bump `package.json` version following the SEMVER rules.
     2. Run `npm run doc` to update the docs accordingly.
-    3. Update the “Releases per Chromium Version” list in [`docs/api.md`](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md) to include the new version.
+    3. Update the “Releases per Chromium Version” list in [`docs/api.md`](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md) to include the new version. Note: only do this when the Chrome revision is different from the previous release.
     4. Send a PR titled `'chore: mark version vXXX.YYY.ZZZ'` ([example](https://github.com/puppeteer/puppeteer/pull/5078)).
     5. Make sure the PR passes **all checks**.
         - **WHY**: there are linters in place that help to avoid unnecessary errors, e.g. [like this](https://github.com/puppeteer/puppeteer/pull/2446)
     6. Merge the PR.
     7. Once merged, publish the release notes using [GitHub's “draft new release tag” option](https://github.com/puppeteer/puppeteer/releases/new).
         - **NOTE**: tag names are prefixed with `'v'`, e.g. for version `1.4.0` the tag is `v1.4.0`.
         - For the “raw notes” section, use `git log --pretty="%h - %s" v2.0.0..HEAD`.
 2. Publish `puppeteer` to npm.
     1. On your local machine, pull from [upstream](https://github.com/puppeteer/puppeteer) and make sure the last commit is the one just merged.
     2. Run `git status` and make sure there are no untracked files.
         - **WHY**: this is to avoid adding unnecessary files to the npm package.
-    3. Run `npm install` to make sure the latest `lib/protocol.d.ts` is generated.
-    4. Run [`npx pkgfiles`](https://www.npmjs.com/package/pkgfiles) to make sure you don't publish anything unnecessary.
-    5. Run `npm publish`. This publishes the `puppeteer` package.
+    3. Run [`npx pkgfiles`](https://www.npmjs.com/package/pkgfiles) to make sure you don't publish anything unnecessary.
+    4. Run `npm publish`. This publishes the `puppeteer` package.
 3. Publish `puppeteer-core` to npm.
     1. Run `./utils/prepare_puppeteer_core.js`. The script changes the name inside `package.json` to `puppeteer-core`.
     2. Run `npm publish`. This publishes the `puppeteer-core` package.
     3. Run `git reset --hard` to reset the changes to `package.json`.
 4. Source Code: mark post-release.
     1. Bump `package.json` version to `-post` version, run `npm run doc` to update the “released APIs” section at the top of `docs/api.md` accordingly, and send a PR titled `'chore: bump version to vXXX.YYY.ZZZ-post'` ([example](https://github.com/puppeteer/puppeteer/commit/d02440d1eac98028e29f4e1cf55413062a259156))
         - **NOTE**: no other commits should be landed in-between release commit and bump commit.
 
 ## Updating npm dist tags
 
-For both `puppeteer` and `puppeteer-core` we maintain the following npm tags:
-
-- `chrome-*` tags, e.g. `chrome-75` and so on. These tags match the Puppeteer version that corresponds to the `chrome-*` release.
-- `chrome-stable` tag. This tag points to the Puppeteer version that works with the current Chrome stable release.
+For both `puppeteer` and `puppeteer-core` we maintain `chrome-*` npm dist tags, e.g. `chrome-75` and so on. These tags match the Puppeteer version that corresponds to the `chrome-*` release.
 
 These tags are updated on every Puppeteer release.
 
-> **NOTE**: due to Chrome's rolling release, we take [omahaproxy's linux stable version](https://omahaproxy.appspot.com/) as *stable*.
-
 Managing tags 101:
 
 ```bash
-# list tags
+# List tags
 $ npm dist-tag ls puppeteer
-# Removing a tag
-$ npm dist-tag rm puppeteer-core chrome-stable
-# Adding a tag
-$ npm dist-tag add puppeteer-core@1.13.0 chrome-stable
+# Add tags
+$ npm dist-tag add puppeteer@3.0.0 chrome-81
+$ npm dist-tag add puppeteer-core@3.0.0 chrome-81
 ```
--- a/remote/test/puppeteer/README.md
+++ b/remote/test/puppeteer/README.md
@@ -1,17 +1,17 @@
 # Puppeteer
 
 <!-- [START badges] -->
-[![Linux Build Status](https://img.shields.io/travis/com/puppeteer/puppeteer/master.svg)](https://travis-ci.com/puppeteer/puppeteer) [![Windows Build Status](https://img.shields.io/appveyor/ci/mathiasbynens/puppeteer/master.svg?logo=appveyor)](https://ci.appveyor.com/project/mathiasbynens/puppeteer/branch/master) [![Build Status](https://api.cirrus-ci.com/github/puppeteer/puppeteer.svg)](https://cirrus-ci.com/github/puppeteer/puppeteer) [![npm puppeteer package](https://img.shields.io/npm/v/puppeteer.svg)](https://npmjs.org/package/puppeteer) [![Issue resolution status](https://isitmaintained.com/badge/resolution/puppeteer/puppeteer.svg)](https://github.com/puppeteer/puppeteer/issues)
+[![Build status](https://img.shields.io/travis/com/puppeteer/puppeteer/master.svg)](https://travis-ci.com/puppeteer/puppeteer) [![npm puppeteer package](https://img.shields.io/npm/v/puppeteer.svg)](https://npmjs.org/package/puppeteer) [![Issue resolution status](https://isitmaintained.com/badge/resolution/puppeteer/puppeteer.svg)](https://github.com/puppeteer/puppeteer/issues)
 <!-- [END badges] -->
 
 <img src="https://user-images.githubusercontent.com/10379601/29446482-04f7036a-841f-11e7-9872-91d1fc2ea683.png" height="200" align="right">
 
-###### [API](https://github.com/puppeteer/puppeteer/blob/v2.0.0/docs/api.md) | [FAQ](#faq) | [Contributing](https://github.com/puppeteer/puppeteer/blob/master/CONTRIBUTING.md) | [Troubleshooting](https://github.com/puppeteer/puppeteer/blob/master/docs/troubleshooting.md)
+###### [API](https://github.com/puppeteer/puppeteer/blob/v3.1.0/docs/api.md) | [FAQ](#faq) | [Contributing](https://github.com/puppeteer/puppeteer/blob/master/CONTRIBUTING.md) | [Troubleshooting](https://github.com/puppeteer/puppeteer/blob/master/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.
 
 <!-- [START usecases] -->
 ###### 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:
 
@@ -32,43 +32,43 @@ Give it a spin: https://try-puppeteer.ap
 
 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, see [Environment variables](https://github.com/puppeteer/puppeteer/blob/v2.0.0/docs/api.md#environment-variables).
+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/v3.1.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 Chromium by default.
+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/master/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. All subsequent versions rely on
-Node 8.9.0+. All examples below use async/await which is only supported in Node v7.6.0 or greater.
+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/v2.0.0/docs/api.md#).
+of `Browser`, open pages, and then manipulate them with [Puppeteer's API](https://github.com/puppeteer/puppeteer/blob/v3.1.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');
 
@@ -83,17 +83,17 @@ const puppeteer = require('puppeteer');
 ```
 
 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/v2.0.0/docs/api.md#pagesetviewportviewport).
+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/v3.1.0/docs/api.md#pagesetviewportviewport).
 
 **Example** - create a PDF.
 
 Save file as **hn.js**
 
 ```js
 const puppeteer = require('puppeteer');
 
@@ -108,17 +108,17 @@ const puppeteer = require('puppeteer');
 ```
 
 Execute script on the command line
 
 ```bash
 node hn.js
 ```
 
-See [`Page.pdf()`](https://github.com/puppeteer/puppeteer/blob/v2.0.0/docs/api.md#pagepdfoptions) for more information about creating pdfs.
+See [`Page.pdf()`](https://github.com/puppeteer/puppeteer/blob/v3.1.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');
 
@@ -143,137 +143,149 @@ const puppeteer = require('puppeteer');
 ```
 
 Execute script on the command line
 
 ```bash
 node get-dimensions.js
 ```
 
-See [`Page.evaluate()`](https://github.com/puppeteer/puppeteer/blob/v2.0.0/docs/api.md#pageevaluatepagefunction-args) for more information on `evaluate` and related methods like `evaluateOnNewDocument` and `exposeFunction`.
+See [`Page.evaluate()`](https://github.com/puppeteer/puppeteer/blob/v3.1.0/docs/api.md#pageevaluatepagefunction-args) for more information on `evaluate` and related methods like `evaluateOnNewDocument` and `exposeFunction`.
 
 <!-- [END getstarted] -->
 
 <!-- [START runtimesettings] -->
 ## 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/v2.0.0/docs/api.md#puppeteerlaunchoptions) when launching a browser:
+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/v3.1.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'});
 ```
 
-See [`Puppeteer.launch()`](https://github.com/puppeteer/puppeteer/blob/v2.0.0/docs/api.md#puppeteerlaunchoptions) for more information.
+You can also use Puppeteer with Firefox Nightly (experimental support). See [`Puppeteer.launch()`](https://github.com/puppeteer/puppeteer/blob/v3.1.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 Chromium user profile which it **cleans up on every run**.
+Puppeteer creates its own browser user profile which it **cleans up on every run**.
 
 <!-- [END runtimesettings] -->
 
 ## Resources
 
-- [API Documentation](https://github.com/puppeteer/puppeteer/blob/v2.0.0/docs/api.md)
+- [API Documentation](https://github.com/puppeteer/puppeteer/blob/v3.1.0/docs/api.md)
 - [Examples](https://github.com/puppeteer/puppeteer/tree/master/examples/)
 - [Community list of Puppeteer resources](https://github.com/transitive-bullshit/awesome-puppeteer)
 
 
 <!-- [START debugging] -->
 
 ## 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`:
 
-        const browser = await puppeteer.launch({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.
 
-        const browser = await puppeteer.launch({
-          headless: false,
-          slowMo: 250 // slow down by 250ms
-        });
+    ```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()`:
 
-        page.on('console', msg => console.log('PAGE LOG:', msg.text()));
+    ```js
+    page.on('console', msg => console.log('PAGE LOG:', msg.text()));
 
-        await page.evaluate(() => console.log(`url is ${location.href}`));
+    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:
 
-        `const browser = await puppeteer.launch({devtools: true});`
+      ```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:
 
-      `await page.evaluate(() => {debugger;});`
+      ```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/puppeteerLabs/ndb)
+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`)
@@ -291,16 +303,25 @@ Check out [contributing guide](https://g
 
 # 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/master/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/v3.1.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!
@@ -340,16 +361,28 @@ For example, in order to drive Chrome 71
 ```bash
 npm install puppeteer-core@chrome-71
 ```
 
 #### Q: Which Chromium version does Puppeteer use?
 
 Look for `chromium_revision` in [package.json](https://github.com/puppeteer/puppeteer/blob/master/package.json). 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_revision` in [package.json](https://github.com/puppeteer/puppeteer/blob/master/package.json) 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?
@@ -370,17 +403,17 @@ 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/v2.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.)
+* 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/v3.1.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/master/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:
--- a/remote/test/puppeteer/docs/api.md
+++ b/remote/test/puppeteer/docs/api.md
@@ -1,16 +1,19 @@
 
-# Puppeteer API <!-- GEN:version -->Tip-Of-Tree<!-- GEN:stop-->
+# Puppeteer API <!-- GEN:version -->v3.1.0<!-- GEN:stop-->
 <!-- GEN:empty-if-release --><!-- GEN:stop -->
 
 - Interactive Documentation: https://pptr.dev
 - API Translations: [中文|Chinese](https://zhaoqize.github.io/puppeteer-api-zh_CN/#/)
 - Troubleshooting: [troubleshooting.md](https://github.com/puppeteer/puppeteer/blob/master/docs/troubleshooting.md)
 - Releases per Chromium Version:
+  * Chromium 83.0.4103.0 - [Puppeteer v3.1.0](https://github.com/puppeteer/puppeteer/blob/v3.1.0/docs/api.md)
+  * Chromium 81.0.4044.0 - [Puppeteer v3.0.0](https://github.com/puppeteer/puppeteer/blob/v3.0.0/docs/api.md)
+  * Chromium 80.0.3987.0 - [Puppeteer v2.1.0](https://github.com/puppeteer/puppeteer/blob/v2.1.0/docs/api.md)
   * Chromium 79.0.3942.0 - [Puppeteer v2.0.0](https://github.com/puppeteer/puppeteer/blob/v2.0.0/docs/api.md)
   * Chromium 78.0.3882.0 - [Puppeteer v1.20.0](https://github.com/puppeteer/puppeteer/blob/v1.20.0/docs/api.md)
   * Chromium 77.0.3803.0 - [Puppeteer v1.19.0](https://github.com/puppeteer/puppeteer/blob/v1.19.0/docs/api.md)
   * Chromium 76.0.3803.0 - [Puppeteer v1.17.0](https://github.com/puppeteer/puppeteer/blob/v1.17.0/docs/api.md)
   * Chromium 75.0.3765.0 - [Puppeteer v1.15.0](https://github.com/puppeteer/puppeteer/blob/v1.15.0/docs/api.md)
   * Chromium 74.0.3723.0 - [Puppeteer v1.13.0](https://github.com/puppeteer/puppeteer/blob/v1.13.0/docs/api.md)
   * Chromium 73.0.3679.0 - [Puppeteer v1.12.2](https://github.com/puppeteer/puppeteer/blob/v1.12.2/docs/api.md)
   * [All releases](https://github.com/puppeteer/puppeteer/releases)
@@ -30,18 +33,20 @@
   * [puppeteer.devices](#puppeteerdevices)
   * [puppeteer.errors](#puppeteererrors)
   * [puppeteer.executablePath()](#puppeteerexecutablepath)
   * [puppeteer.launch([options])](#puppeteerlaunchoptions)
   * [puppeteer.product](#puppeteerproduct)
 - [class: BrowserFetcher](#class-browserfetcher)
   * [browserFetcher.canDownload(revision)](#browserfetchercandownloadrevision)
   * [browserFetcher.download(revision[, progressCallback])](#browserfetcherdownloadrevision-progresscallback)
+  * [browserFetcher.host()](#browserfetcherhost)
   * [browserFetcher.localRevisions()](#browserfetcherlocalrevisions)
   * [browserFetcher.platform()](#browserfetcherplatform)
+  * [browserFetcher.product()](#browserfetcherproduct)
   * [browserFetcher.remove(revision)](#browserfetcherremoverevision)
   * [browserFetcher.revisionInfo(revision)](#browserfetcherrevisioninforevision)
 - [class: Browser](#class-browser)
   * [event: 'disconnected'](#event-disconnected)
   * [event: 'targetchanged'](#event-targetchanged)
   * [event: 'targetcreated'](#event-targetcreated)
   * [event: 'targetdestroyed'](#event-targetdestroyed)
   * [browser.browserContexts()](#browserbrowsercontexts)
@@ -302,16 +307,17 @@
   * [response.securityDetails()](#responsesecuritydetails)
   * [response.status()](#responsestatus)
   * [response.statusText()](#responsestatustext)
   * [response.text()](#responsetext)
   * [response.url()](#responseurl)
 - [class: SecurityDetails](#class-securitydetails)
   * [securityDetails.issuer()](#securitydetailsissuer)
   * [securityDetails.protocol()](#securitydetailsprotocol)
+  * [securityDetails.subjectAlternativeNames()](#securitydetailssubjectalternativenames)
   * [securityDetails.subjectName()](#securitydetailssubjectname)
   * [securityDetails.validFrom()](#securitydetailsvalidfrom)
   * [securityDetails.validTo()](#securitydetailsvalidto)
 - [class: Target](#class-target)
   * [target.browser()](#targetbrowser)
   * [target.browserContext()](#targetbrowsercontext)
   * [target.createCDPSession()](#targetcreatecdpsession)
   * [target.opener()](#targetopener)
@@ -365,16 +371,17 @@ To sum up, the only differences between 
 - `puppeteer-core` doesn't automatically download Chromium when installed.
 - `puppeteer-core` ignores all `PUPPETEER_*` env variables.
 
 In most cases, you'll be fine using the `puppeteer` package.
 
 However, you should use `puppeteer-core` if:
 - you're building another end-user product or library atop of DevTools protocol. For example, one might build a PDF generator using `puppeteer-core` and write a custom `install.js` script that downloads [`headless_shell`](https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md) instead of Chromium to save disk space.
 - you're bundling Puppeteer to use in Chrome Extension / browser with the DevTools protocol where downloading an additional Chromium binary is unnecessary.
+- you're building a set of tools where `puppeteer-core` is one of the ingredients and you want to postpone `install.js` script execution until Chromium is about to be used.
 
 When using `puppeteer-core`, remember to change the *include* line:
 
 ```js
 const puppeteer = require('puppeteer-core');
 ```
 
 You will then need to call [`puppeteer.connect([options])`](#puppeteerconnectoptions) or [`puppeteer.launch([options])`](#puppeteerlaunchoptions) with an explicit `executablePath` option.
@@ -384,17 +391,17 @@ You will then need to call [`puppeteer.c
 Puppeteer looks for certain [environment variables](https://en.wikipedia.org/wiki/Environment_variable) to aid its operations.
 If Puppeteer doesn't find them in the environment during the installation step, a lowercased variant of these variables will be used from the [npm config](https://docs.npmjs.com/cli/config).
 
 - `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` - defines HTTP proxy settings that are used to download and run Chromium.
 - `PUPPETEER_SKIP_CHROMIUM_DOWNLOAD` - do not download bundled Chromium during installation step.
 - `PUPPETEER_DOWNLOAD_HOST` - overwrite URL prefix that is used to download Chromium. Note: this includes protocol and might even include path prefix. Defaults to `https://storage.googleapis.com`.
 - `PUPPETEER_CHROMIUM_REVISION` - specify a certain version of Chromium you'd like Puppeteer to use. See [puppeteer.launch([options])](#puppeteerlaunchoptions) on how executable path is inferred. **BEWARE**: Puppeteer is only [guaranteed to work](https://github.com/puppeteer/puppeteer/#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy) with the bundled Chromium, use at your own risk.
 - `PUPPETEER_EXECUTABLE_PATH` - specify an executable path to be used in `puppeteer.launch`. See [puppeteer.launch([options])](#puppeteerlaunchoptions) on how the executable path is inferred. **BEWARE**: Puppeteer is only [guaranteed to work](https://github.com/puppeteer/puppeteer/#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy) with the bundled Chromium, use at your own risk.
-- `PUPPETEER_PRODUCT` - specify which browser you'd like Puppeteer to use. Must be one of `chrome` or `firefox`. Setting `product` programmatically in [puppeteer.launch([options])](#puppeteerlaunchoptions) supercedes this environment variable. The product is exposed in [`puppeteer.product`](#puppeteerproduct)
+- `PUPPETEER_PRODUCT` - specify which browser you'd like Puppeteer to use. Must be one of `chrome` or `firefox`. This can also be used during installation to fetch the recommended browser binary. Setting `product` programmatically in [puppeteer.launch([options])](#puppeteerlaunchoptions) supersedes this environment variable. The product is exposed in [`puppeteer.product`](#puppeteerproduct)
 
 > **NOTE** PUPPETEER_* env variables are not accounted for in the [`puppeteer-core`](https://www.npmjs.com/package/puppeteer-core) package.
 
 
 ### Working with Chrome Extensions
 
 Puppeteer can be used for testing Chrome Extensions.
 
@@ -448,42 +455,44 @@ const puppeteer = require('puppeteer');
     - `width` <[number]> page width in pixels.
     - `height` <[number]> page height in pixels.
     - `deviceScaleFactor` <[number]> Specify device scale factor (can be thought of as dpr). Defaults to `1`.
     - `isMobile` <[boolean]> Whether the `meta viewport` tag is taken into account. Defaults to `false`.
     - `hasTouch`<[boolean]> Specifies if viewport supports touch events. Defaults to `false`
     - `isLandscape` <[boolean]> Specifies if viewport is in landscape mode. Defaults to `false`.
   - `slowMo` <[number]> Slows down Puppeteer operations by the specified amount of milliseconds. Useful so that you can see what is going on.
   - `transport` <[ConnectionTransport]> **Experimental** Specify a custom transport object for Puppeteer to use.
+  - `product` <[string]> Possible values are: `chrome`, `firefox`. Defaults to `chrome`.
 - returns: <[Promise]<[Browser]>>
 
-This methods attaches Puppeteer to an existing Chromium instance.
+This methods attaches Puppeteer to an existing browser instance.
 
 #### puppeteer.createBrowserFetcher([options])
 - `options` <[Object]>
-  - `host` <[string]> A download host to be used. Defaults to `https://storage.googleapis.com`.
-  - `path` <[string]> A path for the downloads folder. Defaults to `<root>/.local-chromium`, where `<root>` is puppeteer's package root.
-  - `platform` <[string]> Possible values are: `mac`, `win32`, `win64`, `linux`. Defaults to the current platform.
+  - `host` <[string]> A download host to be used. Defaults to `https://storage.googleapis.com`. If the `product` is `firefox`, this defaults to `https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central`.
+  - `path` <[string]> A path for the downloads folder. Defaults to `<root>/.local-chromium`, where `<root>` is puppeteer's package root. If the `product` is `firefox`, this defaults to `<root>/.local-firefox`.
+  - `platform` <"linux"|"mac"|"win32"|"win64"> [string] for the current platform. Possible values are: `mac`, `win32`, `win64`, `linux`. Defaults to the current platform.
+  - `product` <"chrome"|"firefox"> [string] for the product to run. Possible values are: `chrome`, `firefox`. Defaults to `chrome`.
 - returns: <[BrowserFetcher]>
 
 #### puppeteer.defaultArgs([options])
 - `options` <[Object]>  Set of configurable options to set on the browser. Can have the following fields:
   - `headless` <[boolean]> Whether to run browser in [headless mode](https://developers.google.com/web/updates/2017/04/headless-chrome). Defaults to `true` unless the `devtools` option is `true`.
   - `args` <[Array]<[string]>> Additional arguments to pass to the browser instance. The list of Chromium flags can be found [here](http://peter.sh/experiments/chromium-command-line-switches/).
   - `userDataDir` <[string]> Path to a [User Data Directory](https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md).
   - `devtools` <[boolean]> Whether to auto-open a DevTools panel for each tab. If this option is `true`, the `headless` option will be set `false`.
 - returns: <[Array]<[string]>>
 
 The default flags that Chromium will be launched with.
 
 #### puppeteer.devices
 - returns: <[Object]>
 
 Returns a list of devices to be used with [`page.emulate(options)`](#pageemulateoptions). Actual list of
-devices can be found in [lib/DeviceDescriptors.js](https://github.com/puppeteer/puppeteer/blob/master/lib/DeviceDescriptors.js).
+devices can be found in [src/DeviceDescriptors.js](https://github.com/puppeteer/puppeteer/blob/master/src/DeviceDescriptors.ts).
 
 ```js
 const puppeteer = require('puppeteer');
 const iPhone = puppeteer.devices['iPhone 6'];
 
 (async () => {
   const browser = await puppeteer.launch();
   const page = await browser.newPage();
@@ -515,27 +524,27 @@ try {
     // Do something if this is a timeout.
   }
 }
 ```
 
 > **NOTE** The old way (Puppeteer versions <= v1.14.0) errors can be obtained with `require('puppeteer/Errors')`.
 
 #### puppeteer.executablePath()
-- returns: <[string]> A path where Puppeteer expects to find bundled Chromium. Chromium might not exist there if the download was skipped with [`PUPPETEER_SKIP_CHROMIUM_DOWNLOAD`](#environment-variables).
+- returns: <[string]> A path where Puppeteer expects to find the bundled browser. The browser binary might not be there if the download was skipped with [`PUPPETEER_SKIP_DOWNLOAD`](#environment-variables).
 
 > **NOTE** `puppeteer.executablePath()` is affected by the `PUPPETEER_EXECUTABLE_PATH` and `PUPPETEER_CHROMIUM_REVISION` env variables. See [Environment Variables](#environment-variables) for details.
 
 
 #### puppeteer.launch([options])
 - `options` <[Object]>  Set of configurable options to set on the browser. Can have the following fields:
   - `product` <[string]> Which browser to launch. At this time, this is either `chrome` or `firefox`. See also `PUPPETEER_PRODUCT`.
   - `ignoreHTTPSErrors` <[boolean]> Whether to ignore HTTPS errors during navigation. Defaults to `false`.
   - `headless` <[boolean]> Whether to run browser in [headless mode](https://developers.google.com/web/updates/2017/04/headless-chrome). Defaults to `true` unless the `devtools` option is `true`.
-  - `executablePath` <[string]> Path to a browser executable to run instead of the bundled Chromium. If `executablePath` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). **BEWARE**: Puppeteer is only [guaranteed to work](https://github.compuppeteer/puppeteer/#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy) with the bundled Chromium, use at your own risk.
+  - `executablePath` <[string]> Path to a browser executable to run instead of the bundled Chromium. If `executablePath` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). **BEWARE**: Puppeteer is only [guaranteed to work](https://github.com/puppeteer/puppeteer/#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy) with the bundled Chromium, use at your own risk.
   - `slowMo` <[number]> Slows down Puppeteer operations by the specified amount of milliseconds. Useful so that you can see what is going on.
   - `defaultViewport` <?[Object]> Sets a consistent viewport for each page. Defaults to an 800x600 viewport. `null` disables the default viewport.
     - `width` <[number]> page width in pixels.
     - `height` <[number]> page height in pixels.
     - `deviceScaleFactor` <[number]> Specify device scale factor (can be thought of as dpr). Defaults to `1`.
     - `isMobile` <[boolean]> Whether the `meta viewport` tag is taken into account. Defaults to `false`.
     - `hasTouch`<[boolean]> Specifies if viewport supports touch events. Defaults to `false`
     - `isLandscape` <[boolean]> Specifies if viewport is in landscape mode. Defaults to `false`.
@@ -565,27 +574,29 @@ const browser = await puppeteer.launch({
 >
 > If Google Chrome (rather than Chromium) is preferred, a [Chrome Canary](https://www.google.com/chrome/browser/canary.html) or [Dev Channel](https://www.chromium.org/getting-involved/dev-channel) build is suggested.
 >
 > In [puppeteer.launch([options])](#puppeteerlaunchoptions) above, any mention of Chromium also applies to Chrome.
 >
 > 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/+/lkgr/docs/chromium_browser_vs_google_chrome.md) describes some differences for Linux users.
 
 #### puppeteer.product
-- returns: <[string]> returns the name of the browser that is under automation ("chrome" or "firefox")
+- returns: <[string]> returns the name of the browser that is under automation (`"chrome"` or `"firefox"`)
 
 The product is set by the `PUPPETEER_PRODUCT` environment variable or the `product` option in [puppeteer.launch([options])](#puppeteerlaunchoptions) and defaults to `chrome`. Firefox support is experimental.
 
 
 ### class: BrowserFetcher
 
-BrowserFetcher can download and manage different versions of Chromium.
+BrowserFetcher can download and manage different versions of Chromium and Firefox.
 
 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"`.
+
 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})
 ```
@@ -608,34 +619,44 @@ The method initiates a HEAD request to c
   - `revision` <[string]> the revision the info was created from
   - `folderPath` <[string]> path to the extracted revision folder
   - `executablePath` <[string]> path to the revision executable
   - `url` <[string]> URL this revision can be downloaded from
   - `local` <[boolean]> whether the revision is locally available on disk
 
 The method initiates a GET request to download the revision from the host.
 
+#### browserFetcher.host()
+- returns: <[string]> The download host being used.
+
 #### browserFetcher.localRevisions()
-- returns: <[Promise]<[Array]<[string]>>> A list of all revisions available locally on disk.
+- returns: <[Promise]<[Array]<[string]>>> A list of all revisions (for the current `product`) available locally on disk.
 
 #### browserFetcher.platform()
 - returns: <[string]> One of `mac`, `linux`, `win32` or `win64`.
 
+#### browserFetcher.product()
+- returns: <[string]> One of `chrome` or `firefox`.
+
 #### browserFetcher.remove(revision)
-- `revision` <[string]> a revision to remove. The method will throw if the revision has not been downloaded.
+- `revision` <[string]> a revision to remove for the current `product`. The method will throw if the revision has not been downloaded.
 - returns: <[Promise]> Resolves when the revision has been removed.
 
 #### browserFetcher.revisionInfo(revision)
 - `revision` <[string]> a revision to get info for.
 - returns: <[Object]>
   - `revision` <[string]> the revision the info was created from
   - `folderPath` <[string]> path to the extracted revision folder
   - `executablePath` <[string]> path to the revision executable
   - `url` <[string]> URL this revision can be downloaded from
   - `local` <[boolean]> whether the revision is locally available on disk
+  - `product` <[string]> one of `chrome` or `firefox`
+
+> **NOTE** Many BrowserFetcher methods, like `remove` and `revisionInfo`
+> are affected by the choice of `product`. See [puppeteer.createBrowserFetcher([options])](#puppeteercreatebrowserfetcheroptions).
 
 ### class: Browser
 
 * extends: [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
 
 A Browser is created when Puppeteer connects to a Chromium instance, either through [`puppeteer.launch`](#puppeteerlaunchoptions) or [`puppeteer.connect`](#puppeteerconnectoptions).
 
 An example of using a [Browser] to create a [Page]:
@@ -1105,17 +1126,21 @@ Shortcut for [page.mainFrame().$$(select
 - returns: <[Promise]<[Serializable]>> Promise which resolves to the return value of `pageFunction`
 
 This method runs `Array.from(document.querySelectorAll(selector))` within the page and passes it as the first argument to `pageFunction`.
 
 If `pageFunction` returns a [Promise], then `page.$$eval` would wait for the promise to resolve and return its value.
 
 Examples:
 ```js
-const divsCounts = await page.$$eval('div', divs => divs.length);
+const divCount = await page.$$eval('div', divs => divs.length);
+```
+
+```js
+const options = await page.$$eval('div > span.options', options => options.map(option => option.textContent));
 ```
 
 #### page.$eval(selector, pageFunction[, ...args])
 - `selector` <[string]> A [selector] to query page for
 - `pageFunction` <[function]\([Element]\)> Function to be evaluated in browser context
 - `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
 - returns: <[Promise]<[Serializable]>> Promise which resolves to the return value of `pageFunction`
 
@@ -1291,82 +1316,82 @@ const iPhone = puppeteer.devices['iPhone
   const page = await browser.newPage();
   await page.emulate(iPhone);
   await page.goto('https://www.google.com');
   // other actions...
   await browser.close();
 })();
 ```
 
-List of all available devices is available in the source code: [DeviceDescriptors.js](https://github.com/puppeteer/puppeteer/blob/master/lib/DeviceDescriptors.js).
+List of all available devices is available in the source code: [src/DeviceDescriptors.ts](https://github.com/puppeteer/puppeteer/blob/master/src/DeviceDescriptors.ts).
 
 #### page.emulateMedia(type)
 - `type` <?[string]> Changes the CSS media type of the page. The only allowed values are `'screen'`, `'print'` and `null`. Passing `null` disables CSS media emulation.
 - returns: <[Promise]>
 
 **Note:** This method is deprecated, and only kept around as an alias for backwards compatibility. Use [`page.emulateMediaType(type)`](#pageemulatemediatypetype) instead.
 
 #### page.emulateMediaFeatures(features)
 - `features` <?[Array]<[Object]>> Given an array of media feature objects, emulates CSS media features on the page. Each media feature object must have the following properties:
   - `name` <[string]> The CSS media feature name. Supported names are `'prefers-colors-scheme'` and `'prefers-reduced-motion'`.
   - `value` <[string]> The value for the given CSS media feature.
 - returns: <[Promise]>
 
 ```js
 await page.emulateMediaFeatures([{ name: 'prefers-color-scheme', value: 'dark' }]);
-await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches));
+await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches);
 // → true
-await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches));
+await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches);
 // → false
-await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches));
+await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches);
 // → false
 
 await page.emulateMediaFeatures([{ name: 'prefers-reduced-motion', value: 'reduce' }]);
-await page.evaluate(() => matchMedia('(prefers-reduced-motion: reduce)').matches));
+await page.evaluate(() => matchMedia('(prefers-reduced-motion: reduce)').matches);
 // → true
-await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches));
+await page.evaluate(() => matchMedia('(prefers-reduced-motion: no-preference)').matches);
 // → false
 
 await page.emulateMediaFeatures([
   { name: 'prefers-color-scheme', value: 'dark' },
   { name: 'prefers-reduced-motion', value: 'reduce' },
 ]);
-await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches));
+await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches);
 // → true
-await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches));
+await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches);
 // → false
-await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches));
+await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches);
 // → false
-await page.evaluate(() => matchMedia('(prefers-reduced-motion: reduce)').matches));
+await page.evaluate(() => matchMedia('(prefers-reduced-motion: reduce)').matches);
 // → true
-await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches));
+await page.evaluate(() => matchMedia('(prefers-reduced-motion: no-preference)').matches);
 // → false
 ```
 
 #### page.emulateMediaType(type)
 - `type` <?[string]> Changes the CSS media type of the page. The only allowed values are `'screen'`, `'print'` and `null`. Passing `null` disables CSS media emulation.
 - returns: <[Promise]>
 
 ```js
-await page.evaluate(() => matchMedia('screen').matches));
+await page.evaluate(() => matchMedia('screen').matches);
 // → true
-await page.evaluate(() => matchMedia('print').matches));
-// → true
+await page.evaluate(() => matchMedia('print').matches);
+// → false
 
 await page.emulateMediaType('print');
-await page.evaluate(() => matchMedia('screen').matches));
+await page.evaluate(() => matchMedia('screen').matches);
 // → false
-await page.evaluate(() => matchMedia('print').matches));
+await page.evaluate(() => matchMedia('print').matches);
 // → true
 
 await page.emulateMediaType(null);
-await page.evaluate(() => matchMedia('screen').matches));
+await page.evaluate(() => matchMedia('screen').matches);
 // → true
-await page.evaluate(() => matchMedia('print').matches));
-// → true
+await page.evaluate(() => matchMedia('print').matches);
+// → false
 ```
 
 #### page.emulateTimezone(timezoneId)
 - `timezoneId` <?[string]> Changes the timezone of the page. See [ICU’s `metaZones.txt`](https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1) for a list of supported timezone IDs. Passing `null` disables timezone emulation.
 - returns: <[Promise]>
 
 #### page.evaluate(pageFunction[, ...args])
 - `pageFunction` <[function]|[string]> Function to be evaluated in the page context
@@ -1526,44 +1551,44 @@ If there's no element matching `selector
 Shortcut for [page.mainFrame().focus(selector)](#framefocusselector).
 
 #### page.frames()
 - returns: <[Array]<[Frame]>> An array of all frames attached to the page.
 
 #### page.goBack([options])
 - `options` <[Object]> Navigation parameters which might have the following properties:
   - `timeout` <[number]> Maximum navigation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
-  - `waitUntil` <[string]|[Array]<[string]>> When to consider navigation succeeded, defaults to `load`. Given an array of event strings, navigation is considered to be successful after all events have been fired. Events can be either:
+  - `waitUntil` <"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array<PuppeteerLifeCycleMethod>> When to consider navigation succeeded, defaults to `load`. Given an array of event strings, navigation is considered to be successful after all events have been fired. Events can be either:
     - `load` - consider navigation to be finished when the `load` event is fired.
     - `domcontentloaded` - consider navigation to be finished when the `DOMContentLoaded` event is fired.
     - `networkidle0` - consider navigation to be finished when there are no more than 0 network connections for at least `500` ms.
     - `networkidle2` - consider navigation to be finished when there are no more than 2 network connections for at least `500` ms.
 - returns: <[Promise]<?[Response]>> Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect. If
 can not go back, resolves to `null`.
 
 Navigate to the previous page in history.
 
 #### page.goForward([options])
 - `options` <[Object]> Navigation parameters which might have the following properties:
   - `timeout` <[number]> Maximum navigation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
-  - `waitUntil` <[string]|[Array]<[string]>> When to consider navigation succeeded, defaults to `load`. Given an array of event strings, navigation is considered to be successful after all events have been fired. Events can be either:
+  - `waitUntil` <"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array<PuppeteerLifeCycleMethod>> When to consider navigation succeeded, defaults to `load`. Given an array of event strings, navigation is considered to be successful after all events have been fired. Events can be either:
     - `load` - consider navigation to be finished when the `load` event is fired.
     - `domcontentloaded` - consider navigation to be finished when the `DOMContentLoaded` event is fired.
     - `networkidle0` - consider navigation to be finished when there are no more than 0 network connections for at least `500` ms.
     - `networkidle2` - consider navigation to be finished when there are no more than 2 network connections for at least `500` ms.
 - returns: <[Promise]<?[Response]>> Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect. If
 can not go forward, resolves to `null`.
 
 Navigate to the next page in history.
 
 #### page.goto(url[, options])
 - `url` <[string]> URL to navigate page to. The url should include scheme, e.g. `https://`.
 - `options` <[Object]> Navigation parameters which might have the following properties:
   - `timeout` <[number]> Maximum navigation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
-  - `waitUntil` <[string]|[Array]<[string]>> When to consider navigation succeeded, defaults to `load`. Given an array of event strings, navigation is considered to be successful after all events have been fired. Events can be either:
+  - `waitUntil` <"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array<PuppeteerLifeCycleMethod>> When to consider navigation succeeded, defaults to `load`. Given an array of event strings, navigation is considered to be successful after all events have been fired. Events can be either:
     - `load` - consider navigation to be finished when the `load` event is fired.
     - `domcontentloaded` - consider navigation to be finished when the `DOMContentLoaded` event is fired.
     - `networkidle0` - consider navigation to be finished when there are no more than 0 network connections for at least `500` ms.
     - `networkidle2` - consider navigation to be finished when there are no more than 2 network connections for at least `500` ms.
   - `referer` <[string]> Referer header value. If provided it will take preference over the referer header value set by [page.setExtraHTTPHeaders()](#pagesetextrahttpheadersheaders).
 - returns: <[Promise]<?[Response]>> Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect.
 
 `page.goto` will throw an error if:
@@ -1650,23 +1675,23 @@ Page is guaranteed to have a main frame 
     - `right` <[string]|[number]> Right margin, accepts values labeled with units.
     - `bottom` <[string]|[number]> Bottom margin, accepts values labeled with units.
     - `left` <[string]|[number]> Left margin, accepts values labeled with units.
   - `preferCSSPageSize` <[boolean]> Give any CSS `@page` size declared in the page priority over what is declared in `width` and `height` or `format` options. Defaults to `false`, which will scale the content to fit the paper size.
 - returns: <[Promise]<[Buffer]>> Promise which resolves with PDF buffer.
 
 > **NOTE** Generating a pdf is currently only supported in Chrome headless.
 
-`page.pdf()` generates a pdf of the page with `print` css media. To generate a pdf with `screen` media, call [page.emulateMedia('screen')](#pageemulatemediamediatype) before calling `page.pdf()`:
+`page.pdf()` generates a pdf of the page with `print` css media. To generate a pdf with `screen` media, call [page.emulateMediaType('screen')](#pageemulatemediatypetype) before calling `page.pdf()`:
 
 > **NOTE** By default, `page.pdf()` generates a pdf with modified colors for printing. Use the [`-webkit-print-color-adjust`](https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-print-color-adjust) property to force rendering of exact colors.
 
 ```js
 // Generates a PDF with 'screen' media type.
-await page.emulateMedia('screen');
+await page.emulateMediaType('screen');
 await page.pdf({path: 'page.pdf'});
 ```
 
 The `width`, `height`, and `margin` options accept values labeled with units. Unlabeled values are treated as pixels.
 
 A few examples:
 - `page.pdf({width: 100})` - prints with width set to 100 pixels
 - `page.pdf({width: '100px'})` - prints with width set to 100 pixels
@@ -1714,17 +1739,17 @@ await mapInstances.dispose();
 await mapPrototype.dispose();
 ```
 
 Shortcut for [page.mainFrame().executionContext().queryObjects(prototypeHandle)](#executioncontextqueryobjectsprototypehandle).
 
 #### page.reload([options])
 - `options` <[Object]> Navigation parameters which might have the following properties:
   - `timeout` <[number]> Maximum navigation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
-  - `waitUntil` <[string]|[Array]<[string]>> When to consider navigation succeeded, defaults to `load`. Given an array of event strings, navigation is considered to be successful after all events have been fired. Events can be either:
+  - `waitUntil` <"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array<PuppeteerLifeCycleMethod>> When to consider navigation succeeded, defaults to `load`. Given an array of event strings, navigation is considered to be successful after all events have been fired. Events can be either:
     - `load` - consider navigation to be finished when the `load` event is fired.
     - `domcontentloaded` - consider navigation to be finished when the `DOMContentLoaded` event is fired.
     - `networkidle0` - consider navigation to be finished when there are no more than 0 network connections for at least `500` ms.
     - `networkidle2` - consider navigation to be finished when there are no more than 2 network connections for at least `500` ms.
 - returns: <[Promise]<[Response]>> Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect.
 
 #### page.screenshot([options])
 - `options` <[Object]> Options object which might have the following properties:
@@ -1772,17 +1797,17 @@ that `page.setBypassCSP` should be calle
 - returns: <[Promise]>
 
 Toggles ignoring cache for each request based on the enabled state. By default, caching is enabled.
 
 #### page.setContent(html[, options])
 - `html` <[string]> HTML markup to assign to the page.
 - `options` <[Object]> Parameters which might have the following properties:
   - `timeout` <[number]> Maximum time in milliseconds for resources to load, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
-  - `waitUntil` <[string]|[Array]<[string]>> When to consider setting markup succeeded, defaults to `load`. Given an array of event strings, setting content is considered to be successful after all events have been fired. Events can be either:
+  - `waitUntil` <"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array<PuppeteerLifeCycleMethod>> When to consider setting markup succeeded, defaults to `load`. Given an array of event strings, setting content is considered to be successful after all events have been fired. Events can be either:
     - `load` - consider setting content to be finished when the `load` event is fired.
     - `domcontentloaded` - consider setting content to be finished when the `DOMContentLoaded` event is fired.
     - `networkidle0` - consider setting content to be finished when there are no more than 0 network connections for at least `500` ms.
     - `networkidle2` - consider setting content to be finished when there are no more than 2 network connections for at least `500` ms.
 - returns: <[Promise]>
 
 #### page.setCookie(...cookies)
 - `...cookies` <...[Object]>
@@ -2073,17 +2098,17 @@ const selector = '.foo';
 await page.waitForFunction(selector => !!document.querySelector(selector), {}, selector);
 ```
 
 Shortcut for [page.mainFrame().waitForFunction(pageFunction[, options[, ...args]])](#framewaitforfunctionpagefunction-options-args).
 
 #### page.waitForNavigation([options])
 - `options` <[Object]> Navigation parameters which might have the following properties:
   - `timeout` <[number]> Maximum navigation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
-  - `waitUntil` <[string]|[Array]<[string]>> When to consider navigation succeeded, defaults to `load`. Given an array of event strings, navigation is considered to be successful after all events have been fired. Events can be either:
+  - `waitUntil` <"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array<PuppeteerLifeCycleMethod>> When to consider navigation succeeded, defaults to `load`. Given an array of event strings, navigation is considered to be successful after all events have been fired. Events can be either:
     - `load` - consider navigation to be finished when the `load` event is fired.
     - `domcontentloaded` - consider navigation to be finished when the `DOMContentLoaded` event is fired.
     - `networkidle0` - consider navigation to be finished when there are no more than 0 network connections for at least `500` ms.
     - `networkidle2` - consider navigation to be finished when there are no more than 2 network connections for at least `500` ms.
 - returns: <[Promise]<?[Response]>> Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect. In case of navigation to a different anchor or navigation due to History API usage, the navigation will resolve with `null`.
 
 This resolves when the page navigates to a new URL or reloads. It is useful for when you run code
 which will indirectly cause the page to navigate. Consider this example:
@@ -2776,17 +2801,17 @@ Returns promise that resolves to the fra
 
 This method fetches an element with `selector` and focuses it.
 If there's no element matching `selector`, the method throws an error.
 
 #### frame.goto(url[, options])
 - `url` <[string]> URL to navigate frame to. The url should include scheme, e.g. `https://`.
 - `options` <[Object]> Navigation parameters which might have the following properties:
   - `timeout` <[number]> Maximum navigation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
-  - `waitUntil` <[string]|[Array]<[string]>> When to consider navigation succeeded, defaults to `load`. Given an array of event strings, navigation is considered to be successful after all events have been fired. Events can be either:
+  - `waitUntil` <"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array<PuppeteerLifeCycleMethod>> When to consider navigation succeeded, defaults to `load`. Given an array of event strings, navigation is considered to be successful after all events have been fired. Events can be either:
     - `load` - consider navigation to be finished when the `load` event is fired.
     - `domcontentloaded` - consider navigation to be finished when the `DOMContentLoaded` event is fired.
     - `networkidle0` - consider navigation to be finished when there are no more than 0 network connections for at least `500` ms.
     - `networkidle2` - consider navigation to be finished when there are no more than 2 network connections for at least `500` ms.
   - `referer` <[string]> Referer header value. If provided it will take preference over the referer header value set by [page.setExtraHTTPHeaders()](#pagesetextrahttpheadersheaders).
 - returns: <[Promise]<?[Response]>> Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect.
 
 `frame.goto` will throw an error if:
@@ -2839,17 +2864,17 @@ If there's no `<select>` element matchin
 frame.select('select#colors', 'blue'); // single selection
 frame.select('select#colors', 'red', 'green', 'blue'); // multiple selections
 ```
 
 #### frame.setContent(html[, options])
 - `html` <[string]> HTML markup to assign to the page.
 - `options` <[Object]> Parameters which might have the following properties:
   - `timeout` <[number]> Maximum time in milliseconds for resources to load, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
-  - `waitUntil` <[string]|[Array]<[string]>> When to consider setting markup succeeded, defaults to `load`. Given an array of event strings, setting content is considered to be successful after all events have been fired. Events can be either:
+  - `waitUntil` <"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array<PuppeteerLifeCycleMethod>> When to consider setting markup succeeded, defaults to `load`. Given an array of event strings, setting content is considered to be successful after all events have been fired. Events can be either:
     - `load` - consider setting content to be finished when the `load` event is fired.
     - `domcontentloaded` - consider setting content to be finished when the `DOMContentLoaded` event is fired.
     - `networkidle0` - consider setting content to be finished when there are no more than 0 network connections for at least `500` ms.
     - `networkidle2` - consider setting content to be finished when there are no more than 2 network connections for at least `500` ms.
 - returns: <[Promise]>
 
 #### frame.tap(selector)
 - `selector` <[string]> A [selector] to search for element to tap. If there are multiple elements satisfying the selector, the first will be tapped.
@@ -2940,17 +2965,17 @@ To pass arguments from node.js to the pr
 ```js
 const selector = '.foo';
 await page.waitForFunction(selector => !!document.querySelector(selector), {}, selector);
 ```
 
 #### frame.waitForNavigation([options])
 - `options` <[Object]> Navigation parameters which might have the following properties:
   - `timeout` <[number]> Maximum navigation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
-  - `waitUntil` <[string]|[Array]<[string]>> When to consider navigation succeeded, defaults to `load`. Given an array of event strings, navigation is considered to be successful after all events have been fired. Events can be either:
+  - `waitUntil` <"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array<PuppeteerLifeCycleMethod>> When to consider navigation succeeded, defaults to `load`. Given an array of event strings, navigation is considered to be successful after all events have been fired. Events can be either:
     - `load` - consider navigation to be finished when the `load` event is fired.
     - `domcontentloaded` - consider navigation to be finished when the `DOMContentLoaded` event is fired.
     - `networkidle0` - consider navigation to be finished when there are no more than 0 network connections for at least `500` ms.
     - `networkidle2` - consider navigation to be finished when there are no more than 2 network connections for at least `500` ms.
 - returns: <[Promise]<?[Response]>> Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect. In case of navigation to a different anchor or navigation due to History API usage, the navigation will resolve with `null`.
 
 This resolves when the frame navigates to a new URL. It is useful for when you run code
 which will indirectly cause the frame to navigate. Consider this example:
@@ -3701,16 +3726,19 @@ Contains the URL of the response.
 [SecurityDetails] class represents the security details when response was received over the secure connection.
 
 #### securityDetails.issuer()
 - returns: <[string]> A string with the name of issuer of the certificate.
 
 #### securityDetails.protocol()
 - returns: <[string]> String with the security protocol, eg. "TLS 1.2".
 
+#### securityDetails.subjectAlternativeNames()
+- returns: <[Array]<[string]>> Returns the list of SANs (subject alternative names) of the certificate.
+
 #### securityDetails.subjectName()
 - returns: <[string]> Name of the subject to which the certificate was issued to.
 
 #### securityDetails.validFrom()
 - returns: <[number]> [UnixTime] stating the start of validity of the certificate.
 
 #### securityDetails.validTo()
 - returns: <[number]> [UnixTime] stating the end of validity of the certificate.
@@ -3872,17 +3900,17 @@ TimeoutError is emitted whenever certain
 [Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array "Array"
 [Body]: #class-body  "Body"
 [BrowserContext]: #class-browsercontext  "BrowserContext"
 [BrowserFetcher]: #class-browserfetcher  "BrowserFetcher"
 [Browser]: #class-browser  "Browser"
 [Buffer]: https://nodejs.org/api/buffer.html#buffer_class_buffer "Buffer"
 [CDPSession]: #class-cdpsession  "CDPSession"
 [ChildProcess]: https://nodejs.org/api/child_process.html "ChildProcess"
-[ConnectionTransport]: ../lib/WebSocketTransport.js "ConnectionTransport"
+[ConnectionTransport]: ../src/WebSocketTransport.js "ConnectionTransport"
 [ConsoleMessage]: #class-consolemessage "ConsoleMessage"
 [Coverage]: #class-coverage "Coverage"
 [Dialog]: #class-dialog "Dialog"
 [ElementHandle]: #class-elementhandle "ElementHandle"
 [Element]: https://developer.mozilla.org/en-US/docs/Web/API/element "Element"
 [Error]: https://nodejs.org/api/errors.html#errors_class_error "Error"
 [ExecutionContext]: #class-executioncontext "ExecutionContext"
 [FileChooser]: #class-filechooser "FileChooser"
@@ -3898,17 +3926,17 @@ TimeoutError is emitted whenever certain
 [Response]: #class-response  "Response"
 [SecurityDetails]: #class-securitydetails "SecurityDetails"
 [Serializable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Description "Serializable"
 [Target]: #class-target "Target"
 [TimeoutError]: #class-timeouterror "TimeoutError"
 [Touchscreen]: #class-touchscreen "Touchscreen"
 [Tracing]: #class-tracing "Tracing"
 [UIEvent.detail]: https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail "UIEvent.detail"
-[USKeyboardLayout]: ../lib/USKeyboardLayout.js "USKeyboardLayout"
+[USKeyboardLayout]: ../src/USKeyboardLayout.ts "USKeyboardLayout"
 [UnixTime]: https://en.wikipedia.org/wiki/Unix_time "Unix Time"
 [Worker]: #class-worker "Worker"
 [boolean]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type "Boolean"
 [function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function "Function"
 [iterator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols "Iterator"
 [number]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type "Number"
 [origin]: https://developer.mozilla.org/en-US/docs/Glossary/Origin "Origin"
 [selector]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors "selector"
--- a/remote/test/puppeteer/docs/troubleshooting.md
+++ b/remote/test/puppeteer/docs/troubleshooting.md
@@ -40,83 +40,84 @@ const browser = await puppeteer.launch({
 
 Make sure all the necessary dependencies are installed. You can run `ldd chrome | grep not` on a Linux
 machine to check which dependencies are missing. The common ones are provided below.
 
 <details>
 <summary>Debian (e.g. Ubuntu) Dependencies</summary>
 
 ```
+ca-certificates
+fonts-liberation
 gconf-service
+libappindicator1
 libasound2
+libatk-bridge2.0-0
 libatk1.0-0
-libatk-bridge2.0-0
 libc6
 libcairo2
 libcups2
 libdbus-1-3
 libexpat1
 libfontconfig1
+libgbm1
 libgcc1
 libgconf-2-4
 libgdk-pixbuf2.0-0
 libglib2.0-0
 libgtk-3-0
 libnspr4
+libnss3
 libpango-1.0-0
 libpangocairo-1.0-0
 libstdc++6
 libx11-6
 libx11-xcb1
 libxcb1
 libxcomposite1
 libxcursor1
 libxdamage1
 libxext6
 libxfixes3
 libxi6
 libxrandr2
 libxrender1
 libxss1
 libxtst6
-ca-certificates
-fonts-liberation
-libappindicator1
-libnss3
 lsb-release
+wget
 xdg-utils
-wget
 ```
 </details>
 
 <details>
 <summary>CentOS Dependencies</summary>
 
 ```
-pango.x86_64
+alsa-lib.x86_64
+atk.x86_64
+cups-libs.x86_64
+GConf2.x86_64
+gtk3.x86_64
+ipa-gothic-fonts
 libXcomposite.x86_64
 libXcursor.x86_64
 libXdamage.x86_64
 libXext.x86_64
 libXi.x86_64
-libXtst.x86_64
-cups-libs.x86_64
+libXrandr.x86_64
 libXScrnSaver.x86_64
-libXrandr.x86_64
-GConf2.x86_64
-alsa-lib.x86_64
-atk.x86_64
-gtk3.x86_64
-ipa-gothic-fonts
+libXtst.x86_64
+pango.x86_64
 xorg-x11-fonts-100dpi
 xorg-x11-fonts-75dpi
+xorg-x11-fonts-cyrillic
+xorg-x11-fonts-misc
+xorg-x11-fonts-Type1
 xorg-x11-utils
-xorg-x11-fonts-cyrillic
-xorg-x11-fonts-Type1
-xorg-x11-fonts-misc
 ```
 
 After installing dependencies you need to update nss library using this command
 
 ```
 yum update nss -y
 ```
 </details>
@@ -181,43 +182,30 @@ export CHROME_DEVEL_SANDBOX=/usr/local/s
 ```
 
 
 ## Running Puppeteer on Travis CI
 
 > 👋 We run our tests for Puppeteer on Travis CI - see our [`.travis.yml`](https://github.com/puppeteer/puppeteer/blob/master/.travis.yml) for reference.
 
 Tips-n-tricks:
-- The `libnss3` package must be installed in order to run Chromium on Ubuntu Trusty
-- [user namespace cloning](http://man7.org/linux/man-pages/man7/user_namespaces.7.html) should be enabled to support
-  proper sandboxing
-- [xvfb](https://en.wikipedia.org/wiki/Xvfb) should be launched in order to run Chromium in non-headless mode (e.g. to test Chrome Extensions)
+- [xvfb](https://en.wikipedia.org/wiki/Xvfb) service should be launched in order to run Chromium in non-headless mode
+- Runs on Xenial Linux on Travis by default
+- Runs `npm install` by default
+- `node_modules` is cached by default
 
-To sum up, your `.travis.yml` might look like this:
+`.travis.yml` might look like this:
 
 ```yml
 language: node_js
-dist: trusty
-addons:
-  apt:
-    packages:
-      # This is required to run new chrome on old trusty
-      - libnss3
-notifications:
-  email: false
-cache:
-  directories:
-    - node_modules
-# allow headful tests
-before_install:
-  # Enable user namespace cloning
-  - "sysctl kernel.unprivileged_userns_clone=1"
-  # Launch XVFB
-  - "export DISPLAY=:99.0"
-  - "sh -e /etc/init.d/xvfb start"
+node_js: node
+services: xvfb
+
+script:
+  - npm run test
 ```
 
 ## Running Puppeteer on CircleCI
 
 Running Puppeteer smoothly on CircleCI requires the following steps:
 
 1. Start with a [NodeJS
    image](https://circleci.com/docs/2.0/circleci-images/#nodejs) in your config
@@ -244,32 +232,34 @@ 1. Lastly, if you’re using Puppeteer through Jest, then you may encounter an
       at ChildProcess.spawn (internal/child_process.js:394:11)
    ```
    This is likely caused by Jest autodetecting the number of processes on the
    entire machine (`36`) rather than the number allowed to your container
    (`2`). To fix this, set `jest --maxWorkers=2` in your test command.
 
 ## Running Puppeteer in Docker
 
-> 👋 We use [Cirrus Ci](https://cirrus-ci.org/) to run our tests for Puppeteer in a Docker container - see our [`Dockerfile.linux`](https://github.com/puppeteer/puppeteer/blob/master/.ci/node8/Dockerfile.linux) for reference.
+> 👋 We use [Cirrus Ci](https://cirrus-ci.org/) to run our tests for Puppeteer in a Docker container - see our [`Dockerfile.linux`](https://github.com/puppeteer/puppeteer/blob/master/.ci/node10/Dockerfile.linux) for reference.
 
 Getting headless Chrome up and running in Docker can be tricky.
 The bundled Chromium that Puppeteer installs is missing the necessary
 shared library dependencies.
 
 To fix, you'll need to install the missing dependencies and the
 latest Chromium package in your Dockerfile:
 
 ```Dockerfile
 FROM node:10-slim
 
 # Install latest chrome dev package and fonts to support major charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others)
 # Note: this installs the necessary libs to make the bundled version of Chromium that Puppeteer
 # installs, work.
-RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
+RUN apt-get update \
+    && apt-get install -y wget gnupg \
+    && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
     && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
     && apt-get update \
     && apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf \
       --no-install-recommends \
     && rm -rf /var/lib/apt/lists/*
 
 # If running Docker >= 1.13.0 use docker run's --init arg to reap zombie processes, otherwise
 # uncomment the following lines to have `dumb-init` as PID 1
@@ -333,41 +323,34 @@ RUN apk add --no-cache \
       ca-certificates \
       ttf-freefont \
       nodejs \
       yarn
 
 ...
 
 # Tell Puppeteer to skip installing Chrome. We'll be using the installed package.
-ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true
+ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
+    PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
 
 # Puppeteer v1.19.0 works with Chromium 77.
 RUN yarn add puppeteer@1.19.0
 
 # Add user so we don't need --no-sandbox.
 RUN addgroup -S pptruser && adduser -S -g pptruser pptruser \
     && mkdir -p /home/pptruser/Downloads /app \
     && chown -R pptruser:pptruser /home/pptruser \
     && chown -R pptruser:pptruser /app
 
 # Run everything after as non-privileged user.
 USER pptruser
 
 ...
 ```
 
-And when launching Chrome, be sure to use the `chromium-browser` executable:
-
-```js
-const browser = await puppeteer.launch({
-  executablePath: '/usr/bin/chromium-browser'
-});
-```
-
 #### Tips
 
 By default, Docker runs a container with a `/dev/shm` shared memory space 64MB.
 This is [typically too small](https://github.com/c0b/chrome-in-docker/issues/1) for Chrome
 and will cause Chrome to crash when rendering large pages. To fix, run the container with
 `docker run --shm-size=1gb` to increase the size of `/dev/shm`. Since Chrome 65, this is no
 longer necessary. Instead, launch the browser with the `--disable-dev-shm-usage` flag:
 
@@ -393,19 +376,19 @@ properly in some cases (e.g. in Docker).
 ### Running Puppeteer on Google App Engine
 
 The Node.js runtime of the [App Engine standard environment](https://cloud.google.com/appengine/docs/standard/nodejs/) comes with all system packages needed to run Headless Chrome.
 
 To use `puppeteer`, simply list the module as a dependency in your `package.json` and deploy to Google App Engine. Read more about using `puppeteer` on App Engine by following [the official tutorial](https://cloud.google.com/appengine/docs/standard/nodejs/using-headless-chrome-with-puppeteer).
 
 ### Running Puppeteer on Google Cloud Functions
 
-The Node.js 8 runtime of [Google Cloud Functions](https://cloud.google.com/functions/docs/) comes with all system packages needed to run Headless Chrome.
+The Node.js 10 runtime of [Google Cloud Functions](https://cloud.google.com/functions/docs/) comes with all system packages needed to run Headless Chrome.
 
-To use `puppeteer`, simply list the module as a dependency in your `package.json` and deploy your function to Google Cloud Functions using the `nodejs8` runtime.
+To use `puppeteer`, simply list the module as a dependency in your `package.json` and deploy your function to Google Cloud Functions using the `nodejs10` runtime.
 
 ### Running Puppeteer on Heroku
 
 Running Puppeteer on Heroku requires some additional dependencies that aren't included on the Linux box that Heroku spins up for you. To add the dependencies on deploy, add the Puppeteer Heroku buildpack to the list of buildpacks for your app under Settings > Buildpacks.
 
 The url for the buildpack is https://github.com/jontewks/puppeteer-heroku-buildpack
 
 Ensure that you're using `'--no-sandbox'` mode when launching Puppeteer. This can be done by passing it as an argument to your `.launch()` call: `puppeteer.launch({ args: ['--no-sandbox'] });`.
--- a/remote/test/puppeteer/examples/README.md
+++ b/remote/test/puppeteer/examples/README.md
@@ -27,11 +27,12 @@ More complex and use case driven example
 ## Testing
 
 - [angular-puppeteer-demo](https://github.com/Quramy/angular-puppeteer-demo) - Demo repository explaining how to use Puppeteer in Karma.
 - [mocha-headless-chrome](https://github.com/direct-adv-interfaces/mocha-headless-chrome) - Tool which runs client-side **mocha** tests in the command line through headless Chrome.
 - [puppeteer-to-istanbul-example](https://github.com/bcoe/puppeteer-to-istanbul-example) - Demo repository demonstrating how to output Puppeteer coverage in Istanbul format.
 - [jest-puppeteer](https://github.com/smooth-code/jest-puppeteer) - (almost) Zero configuration tool for setting up and running Jest and Puppeteer easily. Also includes an assertion library for Puppeteer.
 - [puppeteer-har](https://github.com/Everettss/puppeteer-har) - Generate HAR file with puppeteer.
 - [puppetry](https://puppetry.app/) - A desktop app to build Puppeteer/Jest driven tests without coding.
+- [cucumber-puppeteer-example](https://github.com/mlampedx/cucumber-puppeteer-example) - Example repository demonstrating how to use Puppeeteer and Cucumber for integration testing.
 
 ## Services
 - [Checkly](https://checklyhq.com) - Monitoring SaaS that uses Puppeteer to check availability and correctness of web pages and apps.
--- a/remote/test/puppeteer/examples/block-images.js
+++ b/remote/test/puppeteer/examples/block-images.js
@@ -13,24 +13,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 'use strict';
 
 const puppeteer = require('puppeteer');
 
-(async() => {
+(async () => {
   const browser = await puppeteer.launch();
   const page = await browser.newPage();
   await page.setRequestInterception(true);
-  page.on('request', request => {
-    if (request.resourceType() === 'image')
-      request.abort();
-    else
-      request.continue();
+  page.on('request', (request) => {
+    if (request.resourceType() === 'image') request.abort();
+    else request.continue();
   });
   await page.goto('https://news.google.com/news/');
-  await page.screenshot({path: 'news.png', fullPage: true});
+  await page.screenshot({ path: 'news.png', fullPage: true });
 
   await browser.close();
 })();
-
new file mode 100644
--- /dev/null
+++ b/remote/test/puppeteer/examples/cross-browser.js
@@ -0,0 +1,48 @@
+const puppeteer = require('puppeteer');
+
+/**
+ * To have Puppeteer fetch a Firefox binary for you, first run:
+ *
+ *  PUPPETEER_PRODUCT=firefox npm install
+ *
+ * To get additional logging about which browser binary is executed,
+ * run this example as:
+ *
+ *   DEBUG=puppeteer:launcher NODE_PATH=../ node examples/cross-browser.js
+ *
+ * You can set a custom binary with the `executablePath` launcher option.
+ *
+ *
+ */
+
+const firefoxOptions = {
+  product: 'firefox',
+  extraPrefsFirefox: {
+    // Enable additional Firefox logging from its protocol implementation
+    // 'remote.log.level': 'Trace',
+  },
+  // Make browser logs visible
+  dumpio: true,
+};
+
+(async () => {
+  const browser = await puppeteer.launch(firefoxOptions);
+
+  const page = await browser.newPage();
+  console.log(await browser.version());
+
+  await page.goto('https://news.ycombinator.com/');
+
+  // Extract articles from the page.
+  const resultsSelector = '.storylink';
+  const links = await page.evaluate((resultsSelector) => {
+    const anchors = Array.from(document.querySelectorAll(resultsSelector));
+    return anchors.map((anchor) => {
+      const title = anchor.textContent.trim();
+      return `${title} - ${anchor.href}`;
+    });
+  }, resultsSelector);
+  console.log(links.join('\n'));
+
+  await browser.close();
+})();
--- a/remote/test/puppeteer/examples/custom-event.js
+++ b/remote/test/puppeteer/examples/custom-event.js
@@ -13,36 +13,38 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 'use strict';
 
 const puppeteer = require('puppeteer');
 
-(async() => {
+(async () => {
   const browser = await puppeteer.launch();
   const page = await browser.newPage();
 
   // Define a window.onCustomEvent function on the page.
-  await page.exposeFunction('onCustomEvent', e => {
+  await page.exposeFunction('onCustomEvent', (e) => {
     console.log(`${e.type} fired`, e.detail || '');
   });
 
   /**
    * Attach an event listener to page to capture a custom event on page load/navigation.
    * @param {string} type Event name.
    * @return {!Promise}
    */
   function listenFor(type) {
-    return page.evaluateOnNewDocument(type => {
-      document.addEventListener(type, e => {
-        window.onCustomEvent({type, detail: e.detail});
+    return page.evaluateOnNewDocument((type) => {
+      document.addEventListener(type, (e) => {
+        window.onCustomEvent({ type, detail: e.detail });
       });
     }, type);
   }
 
   await listenFor('app-ready'); // Listen for "app-ready" custom event on page load.
 
-  await page.goto('https://www.chromestatus.com/features', {waitUntil: 'networkidle0'});
+  await page.goto('https://www.chromestatus.com/features', {
+    waitUntil: 'networkidle0',
+  });
 
   await browser.close();
 })();
--- a/remote/test/puppeteer/examples/detect-sniff.js
+++ b/remote/test/puppeteer/examples/detect-sniff.js
@@ -17,28 +17,28 @@
 'use strict';
 
 const puppeteer = require('puppeteer');
 
 function sniffDetector() {
   const userAgent = window.navigator.userAgent;
   const platform = window.navigator.platform;
 
-  window.navigator.__defineGetter__('userAgent', function() {
+  window.navigator.__defineGetter__('userAgent', function () {
     window.navigator.sniffed = true;
     return userAgent;
   });
 
-  window.navigator.__defineGetter__('platform', function() {
+  window.navigator.__defineGetter__('platform', function () {
     window.navigator.sniffed = true;
     return platform;
   });
 }
 
-(async() => {
+(async () => {
   const browser = await puppeteer.launch();
   const page = await browser.newPage();
   await page.evaluateOnNewDocument(sniffDetector);
-  await page.goto('https://www.google.com', {waitUntil: 'networkidle2'});
+  await page.goto('https://www.google.com', { waitUntil: 'networkidle2' });
   console.log('Sniffed: ' + (await page.evaluate(() => !!navigator.sniffed)));
 
   await browser.close();
 })();
--- a/remote/test/puppeteer/examples/pdf.js
+++ b/remote/test/puppeteer/examples/pdf.js
@@ -13,21 +13,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 'use strict';
 
 const puppeteer = require('puppeteer');
 
-(async() => {
+(async () => {
   const browser = await puppeteer.launch();
   const page = await browser.newPage();
-  await page.goto('https://news.ycombinator.com', {waitUntil: 'networkidle2'});
+  await page.goto('https://news.ycombinator.com', {
+    waitUntil: 'networkidle2',
+  });
   // page.pdf() is currently supported only in headless mode.
   // @see https://bugs.chromium.org/p/chromium/issues/detail?id=753118
   await page.pdf({
     path: 'hn.pdf',
-    format: 'letter'
+    format: 'letter',
   });
 
   await browser.close();
 })();
--- a/remote/test/puppeteer/examples/proxy.js
+++ b/remote/test/puppeteer/examples/proxy.js
@@ -13,23 +13,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 'use strict';
 
 const puppeteer = require('puppeteer');
 
-(async() => {
+(async () => {
   const browser = await puppeteer.launch({
     // Launch chromium using a proxy server on port 9876.
     // More on proxying:
     //    https://www.chromium.org/developers/design-documents/network-settings
     args: [
       '--proxy-server=127.0.0.1:9876',
       // Use proxy for localhost URLs
       '--proxy-bypass-list=<-loopback>',
-    ]
+    ],
   });
   const page = await browser.newPage();
   await page.goto('https://google.com');
   await browser.close();
 })();
--- a/remote/test/puppeteer/examples/screenshot-fullpage.js
+++ b/remote/test/puppeteer/examples/screenshot-fullpage.js
@@ -14,16 +14,16 @@
  * limitations under the License.
  */
 
 'use strict';
 
 const puppeteer = require('puppeteer');
 const devices = require('puppeteer/DeviceDescriptors');
 
-(async() => {
+(async () => {
   const browser = await puppeteer.launch();
   const page = await browser.newPage();
   await page.emulate(devices['iPhone 6']);
   await page.goto('https://www.nytimes.com/');
-  await page.screenshot({path: 'full.png', fullPage: true});
+  await page.screenshot({ path: 'full.png', fullPage: true });
   await browser.close();
 })();
--- a/remote/test/puppeteer/examples/screenshot.js
+++ b/remote/test/puppeteer/examples/screenshot.js
@@ -13,15 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 'use strict';
 
 const puppeteer = require('puppeteer');
 
-(async() => {
+(async () => {
   const browser = await puppeteer.launch();
   const page = await browser.newPage();
   await page.goto('http://example.com');
-  await page.screenshot({path: 'example.png'});
+  await page.screenshot({ path: 'example.png' });
   await browser.close();
 })();
--- a/remote/test/puppeteer/examples/search.js
+++ b/remote/test/puppeteer/examples/search.js
@@ -18,17 +18,17 @@
  * @fileoverview Search developers.google.com/web for articles tagged
  * "Headless Chrome" and scrape results from the results page.
  */
 
 'use strict';
 
 const puppeteer = require('puppeteer');
 
-(async() => {
+(async () => {
   const browser = await puppeteer.launch();
   const page = await browser.newPage();
 
   await page.goto('https://developers.google.com/web/');
 
   // Type into search box.
   await page.type('#searchbox input', 'Headless Chrome');
 
@@ -37,19 +37,19 @@ const puppeteer = require('puppeteer');
   await page.waitForSelector(allResultsSelector);
   await page.click(allResultsSelector);
 
   // Wait for the results page to load and display the results.
   const resultsSelector = '.gsc-results .gsc-thumbnail-inside a.gs-title';
   await page.waitForSelector(resultsSelector);
 
   // Extract the results from the page.
-  const links = await page.evaluate(resultsSelector => {
+  const links = await page.evaluate((resultsSelector) => {
     const anchors = Array.from(document.querySelectorAll(resultsSelector));
-    return anchors.map(anchor => {
+    return anchors.map((anchor) => {
       const title = anchor.textContent.split('|')[0].trim();
       return `${title} - ${anchor.href}`;
     });
   }, resultsSelector);
   console.log(links.join('\n'));
 
   await browser.close();
 })();
--- a/remote/test/puppeteer/index.js
+++ b/remote/test/puppeteer/index.js
@@ -9,30 +9,45 @@
  *
  * 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 {helper} = require('./lib/helper');
+const { helper } = require('./lib/helper');
 const api = require('./lib/api');
+const { Page } = require('./lib/Page');
 for (const className in api) {
-  // Puppeteer-web excludes certain classes from bundle, e.g. BrowserFetcher.
   if (typeof api[className] === 'function')
     helper.installAsyncStackHooks(api[className]);
 }
 
-// If node does not support async await, use the compiled version.
-const Puppeteer = require('./lib/Puppeteer');
+// Expose alias for deprecated method.
+Page.prototype.emulateMedia = Page.prototype.emulateMediaType;
+
+const { Puppeteer } = require('./lib/Puppeteer');
 const packageJson = require('./package.json');
-const preferredRevision = packageJson.puppeteer.chromium_revision;
+let preferredRevision = packageJson.puppeteer.chromium_revision;
 const isPuppeteerCore = packageJson.name === 'puppeteer-core';
+// puppeteer-core ignores environment variables
+const product = isPuppeteerCore
+  ? undefined
+  : process.env.PUPPETEER_PRODUCT ||
+    process.env.npm_config_puppeteer_product ||
+    process.env.npm_package_config_puppeteer_product;
+if (!isPuppeteerCore && product === 'firefox')
+  preferredRevision = packageJson.puppeteer.firefox_revision;
 
-const puppeteer = new Puppeteer(__dirname, preferredRevision, isPuppeteerCore);
+const puppeteer = new Puppeteer(
+  __dirname,
+  preferredRevision,
+  isPuppeteerCore,
+  product
+);
 
 // The introspection in `Helper.installAsyncStackHooks` references `Puppeteer._launcher`
 // before the Puppeteer ctor is called, such that an invalid Launcher is selected at import,
 // so we reset it.
 puppeteer._lazyLauncher = undefined;
 
 module.exports = puppeteer;
--- a/remote/test/puppeteer/install.js
+++ b/remote/test/puppeteer/install.js
@@ -9,119 +9,234 @@
  *
  * 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.
  */
 
-// puppeteer-core should not install anything.
-if (require('./package.json').name === 'puppeteer-core')
-  return;
+/**
+ * This file is part of public API.
+ *
+ * By default, the `puppeteer` package runs this script during the installation
+ * process unless one of the env flags is provided.
+ * `puppeteer-core` package doesn't include this step at all. However, it's
+ * still possible to install a supported browser using this script when
+ * necessary.
+ */
+
+const compileTypeScriptIfRequired = require('./typescript-if-required');
+
+const firefoxVersions =
+  'https://product-details.mozilla.org/1.0/firefox_versions.json';
+const supportedProducts = {
+  chrome: 'Chromium',
+  firefox: 'Firefox Nightly',
+};
 
-if (process.env.PUPPETEER_SKIP_CHROMIUM_DOWNLOAD) {
-  logPolitely('**INFO** Skipping Chromium download. "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" environment variable was found.');
-  return;
-}
-if (process.env.NPM_CONFIG_PUPPETEER_SKIP_CHROMIUM_DOWNLOAD || process.env.npm_config_puppeteer_skip_chromium_download) {
-  logPolitely('**INFO** Skipping Chromium download. "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" was set in npm config.');
-  return;
-}
-if (process.env.NPM_PACKAGE_CONFIG_PUPPETEER_SKIP_CHROMIUM_DOWNLOAD || process.env.npm_package_config_puppeteer_skip_chromium_download) {
-  logPolitely('**INFO** Skipping Chromium download. "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" was set in project config.');
-  return;
-}
+async function download() {
+  await compileTypeScriptIfRequired();
 
-const downloadHost = process.env.PUPPETEER_DOWNLOAD_HOST || process.env.npm_config_puppeteer_download_host || process.env.npm_package_config_puppeteer_download_host;
-
-const puppeteer = require('./index');
-const browserFetcher = puppeteer.createBrowserFetcher({ host: downloadHost });
-
-const revision = process.env.PUPPETEER_CHROMIUM_REVISION || process.env.npm_config_puppeteer_chromium_revision || process.env.npm_package_config_puppeteer_chromium_revision
-  || require('./package.json').puppeteer.chromium_revision;
+  const downloadHost =
+    process.env.PUPPETEER_DOWNLOAD_HOST ||
+    process.env.npm_config_puppeteer_download_host ||
+    process.env.npm_package_config_puppeteer_download_host;
+  const puppeteer = require('./index');
+  const product =
+    process.env.PUPPETEER_PRODUCT ||
+    process.env.npm_config_puppeteer_product ||
+    process.env.npm_package_config_puppeteer_product ||
+    'chrome';
+  const browserFetcher = puppeteer.createBrowserFetcher({
+    product,
+    host: downloadHost,
+  });
+  const revision = await getRevision();
+  await fetchBinary(revision);
 
-const revisionInfo = browserFetcher.revisionInfo(revision);
-
-// Do nothing if the revision is already downloaded.
-if (revisionInfo.local) {
-  generateProtocolTypesIfNecessary(false /* updated */);
-  return;
-}
+  function getRevision() {
+    if (product === 'chrome') {
+      return (
+        process.env.PUPPETEER_CHROMIUM_REVISION ||
+        process.env.npm_config_puppeteer_chromium_revision ||
+        process.env.npm_package_config_puppeteer_chromium_revision ||
+        require('./package.json').puppeteer.chromium_revision
+      );
+    } else if (product === 'firefox') {
+      puppeteer._preferredRevision = require('./package.json').puppeteer.firefox_revision;
+      return getFirefoxNightlyVersion(browserFetcher.host()).catch((error) => {
+        console.error(error);
+        process.exit(1);
+      });
+    } else {
+      throw new Error(`Unsupported product ${product}`);
+    }
+  }
 
-// Override current environment proxy settings with npm configuration, if any.
-const NPM_HTTPS_PROXY = process.env.npm_config_https_proxy || process.env.npm_config_proxy;
-const NPM_HTTP_PROXY = process.env.npm_config_http_proxy || process.env.npm_config_proxy;
-const NPM_NO_PROXY = process.env.npm_config_no_proxy;
+  function fetchBinary(revision) {
+    const revisionInfo = browserFetcher.revisionInfo(revision);
+
+    // Do nothing if the revision is already downloaded.
+    if (revisionInfo.local) {
+      logPolitely(
+        `${supportedProducts[product]} is already in ${revisionInfo.folderPath}; skipping download.`
+      );
+      return;
+    }
 
-if (NPM_HTTPS_PROXY)
-  process.env.HTTPS_PROXY = NPM_HTTPS_PROXY;
-if (NPM_HTTP_PROXY)
-  process.env.HTTP_PROXY = NPM_HTTP_PROXY;
-if (NPM_NO_PROXY)
-  process.env.NO_PROXY = NPM_NO_PROXY;
+    // Override current environment proxy settings with npm configuration, if any.
+    const NPM_HTTPS_PROXY =
+      process.env.npm_config_https_proxy || process.env.npm_config_proxy;
+    const NPM_HTTP_PROXY =
+      process.env.npm_config_http_proxy || process.env.npm_config_proxy;
+    const NPM_NO_PROXY = process.env.npm_config_no_proxy;
 
-browserFetcher.download(revisionInfo.revision, onProgress)
-    .then(() => browserFetcher.localRevisions())
-    .then(onSuccess)
-    .catch(onError);
+    if (NPM_HTTPS_PROXY) process.env.HTTPS_PROXY = NPM_HTTPS_PROXY;
+    if (NPM_HTTP_PROXY) process.env.HTTP_PROXY = NPM_HTTP_PROXY;
+    if (NPM_NO_PROXY) process.env.NO_PROXY = NPM_NO_PROXY;
 
-/**
- * @param {!Array<string>}
- * @return {!Promise}
- */
-function onSuccess(localRevisions) {
-  logPolitely('Chromium downloaded to ' + revisionInfo.folderPath);
-  localRevisions = localRevisions.filter(revision => revision !== revisionInfo.revision);
-  // Remove previous chromium revisions.
-  const cleanupOldVersions = localRevisions.map(revision => browserFetcher.remove(revision));
-  return Promise.all([...cleanupOldVersions, generateProtocolTypesIfNecessary(true /* updated */)]);
-}
+    /**
+     * @param {!Array<string>}
+     * @return {!Promise}
+     */
+    function onSuccess(localRevisions) {
+      logPolitely(
+        `${supportedProducts[product]} (${revisionInfo.revision}) downloaded to ${revisionInfo.folderPath}`
+      );
+      localRevisions = localRevisions.filter(
+        (revision) => revision !== revisionInfo.revision
+      );
+      const cleanupOldVersions = localRevisions.map((revision) =>
+        browserFetcher.remove(revision)
+      );
+      Promise.all([...cleanupOldVersions]);
+    }
 
-/**
- * @param {!Error} error
- */
-function onError(error) {
-  console.error(`ERROR: Failed to download Chromium r${revision}! Set "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" env variable to skip download.`);
-  console.error(error);
-  process.exit(1);
-}
+    /**
+     * @param {!Error} error
+     */
+    function onError(error) {
+      console.error(
+        `ERROR: Failed to set up ${supportedProducts[product]} r${revision}! Set "PUPPETEER_SKIP_DOWNLOAD" env variable to skip download.`
+      );
+      console.error(error);
+      process.exit(1);
+    }
 
-let progressBar = null;
-let lastDownloadedBytes = 0;
-function onProgress(downloadedBytes, totalBytes) {
-  if (!progressBar) {
-    const ProgressBar = require('progress');
-    progressBar = new ProgressBar(`Downloading Chromium r${revision} - ${toMegabytes(totalBytes)} [:bar] :percent :etas `, {
-      complete: '=',
-      incomplete: ' ',
-      width: 20,
-      total: totalBytes,
-    });
+    let progressBar = null;
+    let lastDownloadedBytes = 0;
+    function onProgress(downloadedBytes, totalBytes) {
+      if (!progressBar) {
+        const ProgressBar = require('progress');
+        progressBar = new ProgressBar(
+          `Downloading ${
+            supportedProducts[product]
+          } r${revision} - ${toMegabytes(totalBytes)} [:bar] :percent :etas `,
+          {
+            complete: '=',
+            incomplete: ' ',
+            width: 20,
+            total: totalBytes,
+          }
+        );
+      }
+      const delta = downloadedBytes - lastDownloadedBytes;
+      lastDownloadedBytes = downloadedBytes;
+      progressBar.tick(delta);
+    }
+
+    return browserFetcher
+      .download(revisionInfo.revision, onProgress)
+      .then(() => browserFetcher.localRevisions())
+      .then(onSuccess)
+      .catch(onError);
   }
-  const delta = downloadedBytes - lastDownloadedBytes;
-  lastDownloadedBytes = downloadedBytes;
-  progressBar.tick(delta);
-}
+
+  function toMegabytes(bytes) {
+    const mb = bytes / 1024 / 1024;
+    return `${Math.round(mb * 10) / 10} Mb`;
+  }
 
-function toMegabytes(bytes) {
-  const mb = bytes / 1024 / 1024;
-  return `${Math.round(mb * 10) / 10} Mb`;
-}
-
-function generateProtocolTypesIfNecessary(updated) {
-  const fs = require('fs');
-  const path = require('path');
-  if (!fs.existsSync(path.join(__dirname, 'utils', 'protocol-types-generator')))
-    return;
-  if (!updated && fs.existsSync(path.join(__dirname, 'lib', 'protocol.d.ts')))
-    return;
-  return require('./utils/protocol-types-generator');
+  function getFirefoxNightlyVersion(host) {
+    const https = require('https');
+    const promise = new Promise((resolve, reject) => {
+      let data = '';
+      logPolitely(`Requesting latest Firefox Nightly version from ${host}`);
+      https
+        .get(firefoxVersions, (r) => {
+          if (r.statusCode >= 400)
+            return reject(new Error(`Got status code ${r.statusCode}`));
+          r.on('data', (chunk) => {
+            data += chunk;
+          });
+          r.on('end', () => {
+            try {
+              const versions = JSON.parse(data);
+              return resolve(versions.FIREFOX_NIGHTLY);
+            } catch {
+              return reject(new Error('Firefox version not found'));
+            }
+          });
+        })
+        .on('error', reject);
+    });
+    return promise;
+  }
 }
 
 function logPolitely(toBeLogged) {
   const logLevel = process.env.npm_config_loglevel;
   const logLevelDisplay = ['silent', 'error', 'warn'].indexOf(logLevel) > -1;
 
-  if (!logLevelDisplay)
-    console.log(toBeLogged);
+  if (!logLevelDisplay) console.log(toBeLogged);
 }
 
+if (process.env.PUPPETEER_SKIP_DOWNLOAD) {
+  logPolitely(
+    '**INFO** Skipping browser download. "PUPPETEER_SKIP_DOWNLOAD" environment variable was found.'
+  );
+  return;
+}
+if (
+  process.env.NPM_CONFIG_PUPPETEER_SKIP_DOWNLOAD ||
+  process.env.npm_config_puppeteer_skip_download
+) {
+  logPolitely(
+    '**INFO** Skipping browser download. "PUPPETEER_SKIP_DOWNLOAD" was set in npm config.'
+  );
+  return;
+}
+if (
+  process.env.NPM_PACKAGE_CONFIG_PUPPETEER_SKIP_DOWNLOAD ||
+  process.env.npm_package_config_puppeteer_skip_download
+) {
+  logPolitely(
+    '**INFO** Skipping browser download. "PUPPETEER_SKIP_DOWNLOAD" was set in project config.'
+  );
+  return;
+}
+if (process.env.PUPPETEER_SKIP_CHROMIUM_DOWNLOAD) {
+  logPolitely(
+    '**INFO** Skipping browser download. "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" environment variable was found.'
+  );
+  return;
+}
+if (
+  process.env.NPM_CONFIG_PUPPETEER_SKIP_CHROMIUM_DOWNLOAD ||
+  process.env.npm_config_puppeteer_skip_chromium_download
+) {
+  logPolitely(
+    '**INFO** Skipping browser download. "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" was set in npm config.'
+  );
+  return;
+}
+if (
+  process.env.NPM_PACKAGE_CONFIG_PUPPETEER_SKIP_CHROMIUM_DOWNLOAD ||
+  process.env.npm_package_config_puppeteer_skip_chromium_download
+) {
+  logPolitely(
+    '**INFO** Skipping browser download. "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" was set in project config.'
+  );
+  return;
+}
+
+download();
new file mode 100644
--- /dev/null
+++ b/remote/test/puppeteer/json-mocha-reporter.js
@@ -0,0 +1,68 @@
+const mocha = require('mocha');
+module.exports = JSONExtra;
+
+const constants = mocha.Runner.constants;
+
+/*
+
+This is a copy of
+https://github.com/mochajs/mocha/blob/master/lib/reporters/json-stream.js
+with more event hooks. mocha does not support extending reporters or using
+multiple reporters so a custom reporter is needed and it must be local
+to the project.
+
+*/
+
+function JSONExtra(runner, options) {
+  mocha.reporters.Base.call(this, runner, options);
+  const self = this;
+
+  runner.once(constants.EVENT_RUN_BEGIN, function () {
+    writeEvent(['start', { total: runner.total }]);
+  });
+
+  runner.on(constants.EVENT_TEST_PASS, function (test) {
+    writeEvent(['pass', clean(test)]);
+  });
+
+  runner.on(constants.EVENT_TEST_FAIL, function (test, err) {
+    test = clean(test);
+    test.err = err.message;
+    test.stack = err.stack || null;
+    writeEvent(['fail', test]);
+  });
+
+  runner.once(constants.EVENT_RUN_END, function () {
+    writeEvent(['end', self.stats]);
+  });
+
+  runner.on(constants.EVENT_TEST_BEGIN, function (test) {
+    writeEvent(['test-start', clean(test)]);
+  });
+
+  runner.on(constants.EVENT_TEST_PENDING, function (test) {
+    writeEvent(['pending', clean(test)]);
+  });
+}
+
+function writeEvent(event) {
+  process.stdout.write(JSON.stringify(event) + '\n');
+}
+
+/**
+ * Returns an object literal representation of `test`
+ * free of cyclic properties, etc.
+ *
+ * @private
+ * @param {Test} test - Instance used as data source.
+ * @return {Object} object containing pared-down test instance data
+ */
+function clean(test) {
+  return {
+    title: test.title,
+    fullTitle: test.fullTitle(),
+    file: test.file,
+    duration: test.duration,
+    currentRetry: test.currentRetry(),
+  };
+}
deleted file mode 100644
--- a/remote/test/puppeteer/lib/Accessibility.js
+++ /dev/null
@@ -1,425 +0,0 @@
-/**
- * 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.
- */
-
-/**
- * @typedef {Object} SerializedAXNode
- * @property {string} role
- *
- * @property {string=} name
- * @property {string|number=} value
- * @property {string=} description
- *
- * @property {string=} keyshortcuts
- * @property {string=} roledescription
- * @property {string=} valuetext
- *
- * @property {boolean=} disabled
- * @property {boolean=} expanded
- * @property {boolean=} focused
- * @property {boolean=} modal
- * @property {boolean=} multiline
- * @property {boolean=} multiselectable
- * @property {boolean=} readonly
- * @property {boolean=} required
- * @property {boolean=} selected
- *
- * @property {boolean|"mixed"=} checked
- * @property {boolean|"mixed"=} pressed
- *
- * @property {number=} level
- * @property {number=} valuemin
- * @property {number=} valuemax
- *
- * @property {string=} autocomplete
- * @property {string=} haspopup
- * @property {string=} invalid
- * @property {string=} orientation
- *
- * @property {Array<SerializedAXNode>=} children
- */
-
-class Accessibility {
-  /**
-   * @param {!Puppeteer.CDPSession} client
-   */
-  constructor(client) {
-    this._client = client;
-  }
-
-  /**
-   * @param {{interestingOnly?: boolean, root?: ?Puppeteer.ElementHandle}=} options
-   * @return {!Promise<!SerializedAXNode>}
-   */
-  async snapshot(options = {}) {
-    const {
-      interestingOnly = true,
-      root = null,
-    } = options;
-    const {nodes} = await this._client.send('Accessibility.getFullAXTree');
-    let backendNodeId = null;
-    if (root) {
-      const {node} = await this._client.send('DOM.describeNode', {objectId: root._remoteObject.objectId});
-      backendNodeId = node.backendNodeId;
-    }
-    const defaultRoot = AXNode.createTree(nodes);
-    let needle = defaultRoot;
-    if (backendNodeId) {
-      needle = defaultRoot.find(node => node._payload.backendDOMNodeId === backendNodeId);
-      if (!needle)
-        return null;
-    }
-    if (!interestingOnly)
-      return serializeTree(needle)[0];
-
-    /** @type {!Set<!AXNode>} */
-    const interestingNodes = new Set();
-    collectInterestingNodes(interestingNodes, defaultRoot, false);
-    if (!interestingNodes.has(needle))
-      return null;
-    return serializeTree(needle, interestingNodes)[0];
-  }
-}
-
-/**
- * @param {!Set<!AXNode>} collection
- * @param {!AXNode} node
- * @param {boolean} insideControl
- */
-function collectInterestingNodes(collection, node, insideControl) {
-  if (node.isInteresting(insideControl))
-    collection.add(node);
-  if (node.isLeafNode())
-    return;
-  insideControl = insideControl || node.isControl();
-  for (const child of node._children)
-    collectInterestingNodes(collection, child, insideControl);
-}
-
-/**
- * @param {!AXNode} node
- * @param {!Set<!AXNode>=} whitelistedNodes
- * @return {!Array<!SerializedAXNode>}
- */
-function serializeTree(node, whitelistedNodes) {
-  /** @type {!Array<!SerializedAXNode>} */
-  const children = [];
-  for (const child of node._children)
-    children.push(...serializeTree(child, whitelistedNodes));
-
-  if (whitelistedNodes && !whitelistedNodes.has(node))
-    return children;
-
-  const serializedNode = node.serialize();
-  if (children.length)
-    serializedNode.children = children;
-  return [serializedNode];
-}
-
-
-class AXNode {
-  /**
-   * @param {!Protocol.Accessibility.AXNode} payload
-   */
-  constructor(payload) {
-    this._payload = payload;
-
-    /** @type {!Array<!AXNode>} */
-    this._children = [];
-
-    this._richlyEditable = false;
-    this._editable = false;
-    this._focusable = false;
-    this._expanded = false;
-    this._hidden = false;
-    this._name = this._payload.name ? this._payload.name.value : '';
-    this._role = this._payload.role ? this._payload.role.value : 'Unknown';
-    this._cachedHasFocusableChild;
-
-    for (const property of this._payload.properties || []) {
-      if (property.name === 'editable') {
-        this._richlyEditable = property.value.value === 'richtext';
-        this._editable = true;
-      }
-      if (property.name === 'focusable')
-        this._focusable = property.value.value;
-      if (property.name === 'expanded')
-        this._expanded = property.value.value;
-      if (property.name === 'hidden')
-        this._hidden = property.value.value;
-    }
-  }
-
-  /**
-   * @return {boolean}
-   */
-  _isPlainTextField() {
-    if (this._richlyEditable)
-      return false;
-    if (this._editable)
-      return true;
-    return this._role === 'textbox' || this._role === 'ComboBox' || this._role === 'searchbox';
-  }
-
-  /**
-   * @return {boolean}
-   */
-  _isTextOnlyObject() {
-    const role = this._role;
-    return (role === 'LineBreak' || role === 'text' ||
-            role === 'InlineTextBox');
-  }
-
-  /**
-   * @return {boolean}
-   */
-  _hasFocusableChild() {
-    if (this._cachedHasFocusableChild === undefined) {
-      this._cachedHasFocusableChild = false;
-      for (const child of this._children) {
-        if (child._focusable || child._hasFocusableChild()) {
-          this._cachedHasFocusableChild = true;
-          break;
-        }
-      }
-    }
-    return this._cachedHasFocusableChild;
-  }
-
-  /**
-   * @param {function(AXNode):boolean} predicate
-   * @return {?AXNode}
-   */
-  find(predicate) {
-    if (predicate(this))
-      return this;
-    for (const child of this._children) {
-      const result = child.find(predicate);
-      if (result)
-        return result;
-    }
-    return null;
-  }
-
-  /**
-   * @return {boolean}
-   */
-  isLeafNode() {
-    if (!this._children.length)
-      return true;
-
-    // These types of objects may have children that we use as internal
-    // implementation details, but we want to expose them as leaves to platform
-    // accessibility APIs because screen readers might be confused if they find
-    // any children.
-    if (this._isPlainTextField() || this._isTextOnlyObject())
-      return true;
-
-    // Roles whose children are only presentational according to the ARIA and
-    // HTML5 Specs should be hidden from screen readers.
-    // (Note that whilst ARIA buttons can have only presentational children, HTML5
-    // buttons are allowed to have content.)
-    switch (this._role) {
-      case 'doc-cover':
-      case 'graphics-symbol':
-      case 'img':
-      case 'Meter':
-      case 'scrollbar':
-      case 'slider':
-      case 'separator':
-      case 'progressbar':
-        return true;
-      default:
-        break;
-    }
-
-    // Here and below: Android heuristics
-    if (this._hasFocusableChild())
-      return false;
-    if (this._focusable && this._name)
-      return true;
-    if (this._role === 'heading' && this._name)
-      return true;
-    return false;
-  }
-
-  /**
-   * @return {boolean}
-   */
-  isControl() {
-    switch (this._role) {
-      case 'button':
-      case 'checkbox':
-      case 'ColorWell':
-      case 'combobox':
-      case 'DisclosureTriangle':
-      case 'listbox':
-      case 'menu':
-      case 'menubar':
-      case 'menuitem':
-      case 'menuitemcheckbox':
-      case 'menuitemradio':
-      case 'radio':
-      case 'scrollbar':
-      case 'searchbox':
-      case 'slider':
-      case 'spinbutton':
-      case 'switch':
-      case 'tab':
-      case 'textbox':
-      case 'tree':
-        return true;
-      default:
-        return false;
-    }
-  }
-
-  /**
-   * @param {boolean} insideControl
-   * @return {boolean}
-   */
-  isInteresting(insideControl) {
-    const role = this._role;
-    if (role === 'Ignored' || this._hidden)
-      return false;
-
-    if (this._focusable || this._richlyEditable)
-      return true;
-
-    // If it's not focusable but has a control role, then it's interesting.
-    if (this.isControl())
-      return true;
-
-    // A non focusable child of a control is not interesting
-    if (insideControl)
-      return false;
-
-    return this.isLeafNode() && !!this._name;
-  }
-
-  /**
-   * @return {!SerializedAXNode}
-   */
-  serialize() {
-    /** @type {!Map<string, number|string|boolean>} */
-    const properties = new Map();
-    for (const property of this._payload.properties || [])
-      properties.set(property.name.toLowerCase(), property.value.value);
-    if (this._payload.name)
-      properties.set('name', this._payload.name.value);
-    if (this._payload.value)
-      properties.set('value', this._payload.value.value);
-    if (this._payload.description)
-      properties.set('description', this._payload.description.value);
-
-    /** @type {SerializedAXNode} */
-    const node = {
-      role: this._role
-    };
-
-    /** @type {!Array<keyof SerializedAXNode>} */
-    const userStringProperties = [
-      'name',
-      'value',
-      'description',
-      'keyshortcuts',
-      'roledescription',
-      'valuetext',
-    ];
-    for (const userStringProperty of userStringProperties) {
-      if (!properties.has(userStringProperty))
-        continue;
-      node[userStringProperty] = properties.get(userStringProperty);
-    }
-
-    /** @type {!Array<keyof SerializedAXNode>} */
-    const booleanProperties = [
-      'disabled',
-      'expanded',
-      'focused',
-      'modal',
-      'multiline',
-      'multiselectable',
-      'readonly',
-      'required',
-      'selected',
-    ];
-    for (const booleanProperty of booleanProperties) {
-      // WebArea's treat focus differently than other nodes. They report whether their frame  has focus,
-      // not whether focus is specifically on the root node.
-      if (booleanProperty === 'focused' && this._role === 'WebArea')
-        continue;
-      const value = properties.get(booleanProperty);
-      if (!value)
-        continue;
-      node[booleanProperty] = value;
-    }
-
-    /** @type {!Array<keyof SerializedAXNode>} */
-    const tristateProperties = [
-      'checked',
-      'pressed',
-    ];
-    for (const tristateProperty of tristateProperties) {
-      if (!properties.has(tristateProperty))
-        continue;
-      const value = properties.get(tristateProperty);
-      node[tristateProperty] = value === 'mixed' ? 'mixed' : value === 'true' ? true : false;
-    }
-    /** @type {!Array<keyof SerializedAXNode>} */
-    const numericalProperties = [
-      'level',
-      'valuemax',
-      'valuemin',
-    ];
-    for (const numericalProperty of numericalProperties) {
-      if (!properties.has(numericalProperty))
-        continue;
-      node[numericalProperty] = properties.get(numericalProperty);
-    }
-    /** @type {!Array<keyof SerializedAXNode>} */
-    const tokenProperties = [
-      'autocomplete',
-      'haspopup',
-      'invalid',
-      'orientation',
-    ];
-    for (const tokenProperty of tokenProperties) {
-      const value = properties.get(tokenProperty);
-      if (!value || value === 'false')
-        continue;
-      node[tokenProperty] = value;
-    }
-    return node;
-  }
-
-  /**
-   * @param {!Array<!Protocol.Accessibility.AXNode>} payloads
-   * @return {!AXNode}
-   */
-  static createTree(payloads) {
-    /** @type {!Map<string, !AXNode>} */
-    const nodeById = new Map();
-    for (const payload of payloads)
-      nodeById.set(payload.nodeId, new AXNode(payload));
-    for (const node of nodeById.values()) {
-      for (const childId of node._payload.childIds || [])
-        node._children.push(nodeById.get(childId));
-    }
-    return nodeById.values().next().value;
-  }
-}
-
-module.exports = {Accessibility};
deleted file mode 100644
--- a/remote/test/puppeteer/lib/Browser.js
+++ /dev/null
@@ -1,383 +0,0 @@
-/**
- * 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 { helper, assert } = require('./helper');
-const {Target} = require('./Target');
-const EventEmitter = require('events');
-const {TaskQueue} = require('./TaskQueue');
-const {Events} = require('./Events');
-
-class Browser extends EventEmitter {
-  /**
-   * @param {!Puppeteer.Connection} connection
-   * @param {!Array<string>} contextIds
-   * @param {boolean} ignoreHTTPSErrors
-   * @param {?Puppeteer.Viewport} defaultViewport
-   * @param {?Puppeteer.ChildProcess} process
-   * @param {function()=} closeCallback
-   */
-  static async create(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback) {
-    const browser = new Browser(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback);
-    await connection.send('Target.setDiscoverTargets', {discover: true});
-    return browser;
-  }
-
-  /**
-   * @param {!Puppeteer.Connection} connection
-   * @param {!Array<string>} contextIds
-   * @param {boolean} ignoreHTTPSErrors
-   * @param {?Puppeteer.Viewport} defaultViewport
-   * @param {?Puppeteer.ChildProcess} process
-   * @param {(function():Promise)=} closeCallback
-   */
-  constructor(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback) {
-    super();
-    this._ignoreHTTPSErrors = ignoreHTTPSErrors;
-    this._defaultViewport = defaultViewport;
-    this._process = process;
-    this._screenshotTaskQueue = new TaskQueue();
-    this._connection = connection;
-    this._closeCallback = closeCallback || new Function();
-
-    this._defaultContext = new BrowserContext(this._connection, this, null);
-    /** @type {Map<string, BrowserContext>} */
-    this._contexts = new Map();
-    for (const contextId of contextIds)
-      this._contexts.set(contextId, new BrowserContext(this._connection, this, contextId));
-
-    /** @type {Map<string, Target>} */
-    this._targets = new Map();
-    this._connection.on(Events.Connection.Disconnected, () => this.emit(Events.Browser.Disconnected));
-    this._connection.on('Target.targetCreated', this._targetCreated.bind(this));
-    this._connection.on('Target.targetDestroyed', this._targetDestroyed.bind(this));
-    this._connection.on('Target.targetInfoChanged', this._targetInfoChanged.bind(this));
-  }
-
-  /**
-   * @return {?Puppeteer.ChildProcess}
-   */
-  process() {
-    return this._process;
-  }
-
-  /**
-   * @return {!Promise<!BrowserContext>}
-   */
-  async createIncognitoBrowserContext() {
-    const {browserContextId} = await this._connection.send('Target.createBrowserContext');
-    const context = new BrowserContext(this._connection, this, browserContextId);
-    this._contexts.set(browserContextId, context);
-    return context;
-  }
-
-  /**
-   * @return {!Array<!BrowserContext>}
-   */
-  browserContexts() {
-    return [this._defaultContext, ...Array.from(this._contexts.values())];
-  }
-
-  /**
-   * @return {!BrowserContext}
-   */
-  defaultBrowserContext() {
-    return this._defaultContext;
-  }
-
-  /**
-   * @param {?string} contextId
-   */
-  async _disposeContext(contextId) {
-    await this._connection.send('Target.disposeBrowserContext', {browserContextId: contextId || undefined});
-    this._contexts.delete(contextId);
-  }
-
-  /**
-   * @param {!Protocol.Target.targetCreatedPayload} event
-   */
-  async _targetCreated(event) {
-    const targetInfo = event.targetInfo;
-    const {browserContextId} = targetInfo;
-    const context = (browserContextId && this._contexts.has(browserContextId)) ? this._contexts.get(browserContextId) : this._defaultContext;
-
-    const target = new Target(targetInfo, context, () => this._connection.createSession(targetInfo), this._ignoreHTTPSErrors, this._defaultViewport, this._screenshotTaskQueue);
-    assert(!this._targets.has(event.targetInfo.targetId), 'Target should not exist before targetCreated');
-    this._targets.set(event.targetInfo.targetId, target);
-
-    if (await target._initializedPromise) {
-      this.emit(Events.Browser.TargetCreated, target);
-      context.emit(Events.BrowserContext.TargetCreated, target);
-    }
-  }
-
-  /**
-   * @param {{targetId: string}} event
-   */
-  async _targetDestroyed(event) {
-    const target = this._targets.get(event.targetId);
-    target._initializedCallback(false);
-    this._targets.delete(event.targetId);
-    target._closedCallback();
-    if (await target._initializedPromise) {
-      this.emit(Events.Browser.TargetDestroyed, target);
-      target.browserContext().emit(Events.BrowserContext.TargetDestroyed, target);
-    }
-  }
-
-  /**
-   * @param {!Protocol.Target.targetInfoChangedPayload} event
-   */
-  _targetInfoChanged(event) {
-    const target = this._targets.get(event.targetInfo.targetId);
-    assert(target, 'target should exist before targetInfoChanged');
-    const previousURL = target.url();
-    const wasInitialized = target._isInitialized;
-    target._targetInfoChanged(event.targetInfo);
-    if (wasInitialized && previousURL !== target.url()) {
-      this.emit(Events.Browser.TargetChanged, target);
-      target.browserContext().emit(Events.BrowserContext.TargetChanged, target);
-    }
-  }
-
-  /**
-   * @return {string}
-   */
-  wsEndpoint() {
-    return this._connection.url();
-  }
-
-  /**
-   * @return {!Promise<!Puppeteer.Page>}
-   */
-  async newPage() {
-    return this._defaultContext.newPage();
-  }
-
-  /**
-   * @param {?string} contextId
-   * @return {!Promise<!Puppeteer.Page>}
-   */
-  async _createPageInContext(contextId) {
-    const {targetId} = await this._connection.send('Target.createTarget', {url: 'about:blank', browserContextId: contextId || undefined});
-    const target = await this._targets.get(targetId);
-    assert(await target._initializedPromise, 'Failed to create target for page');
-    const page = await target.page();
-    return page;
-  }
-
-  /**
-   * @return {!Array<!Target>}
-   */
-  targets() {
-    return Array.from(this._targets.values()).filter(target => target._isInitialized);
-  }
-
-  /**
-   * @return {!Target}
-   */
-  target() {
-    return this.targets().find(target => target.type() === 'browser');
-  }
-
-  /**
-   * @param {function(!Target):boolean} predicate
-   * @param {{timeout?: number}=} options
-   * @return {!Promise<!Target>}
-   */
-  async waitForTarget(predicate, options = {}) {
-    const {
-      timeout = 30000
-    } = options;
-    const existingTarget = this.targets().find(predicate);
-    if (existingTarget)
-      return existingTarget;
-    let resolve;
-    const targetPromise = new Promise(x => resolve = x);
-    this.on(Events.Browser.TargetCreated, check);
-    this.on(Events.Browser.TargetChanged, check);
-    try {
-      if (!timeout)
-        return await targetPromise;
-      return await helper.waitWithTimeout(targetPromise, 'target', timeout);
-    } finally {
-      this.removeListener(Events.Browser.TargetCreated, check);
-      this.removeListener(Events.Browser.TargetChanged, check);
-    }
-
-    /**
-     * @param {!Target} target
-     */
-    function check(target) {
-      if (predicate(target))
-        resolve(target);
-    }
-  }
-
-  /**
-   * @return {!Promise<!Array<!Puppeteer.Page>>}
-   */
-  async pages() {
-    const contextPages = await Promise.all(this.browserContexts().map(context => context.pages()));
-    // Flatten array.
-    return contextPages.reduce((acc, x) => acc.concat(x), []);
-  }
-
-  /**
-   * @return {!Promise<string>}
-   */
-  async version() {
-    const version = await this._getVersion();
-    return version.product;
-  }
-
-  /**
-   * @return {!Promise<string>}
-   */
-  async userAgent() {
-    const version = await this._getVersion();
-    return version.userAgent;
-  }
-
-  async close() {
-    await this._closeCallback.call(null);
-    this.disconnect();
-  }
-
-  disconnect() {
-    this._connection.dispose();
-  }
-
-  /**
-   * @return {boolean}
-   */
-  isConnected() {
-    return !this._connection._closed;
-  }
-
-  /**
-   * @return {!Promise<!Object>}
-   */
-  _getVersion() {
-    return this._connection.send('Browser.getVersion');
-  }
-}
-
-class BrowserContext extends EventEmitter {
-  /**
-   * @param {!Puppeteer.Connection} connection
-   * @param {!Browser} browser
-   * @param {?string} contextId
-   */
-  constructor(connection, browser, contextId) {
-    super();
-    this._connection = connection;
-    this._browser = browser;
-    this._id = contextId;
-  }
-
-  /**
-   * @return {!Array<!Target>} target
-   */
-  targets() {
-    return this._browser.targets().filter(target => target.browserContext() === this);
-  }
-
-  /**
-   * @param {function(!Target):boolean} predicate
-   * @param {{timeout?: number}=} options
-   * @return {!Promise<!Target>}
-   */
-  waitForTarget(predicate, options) {
-    return this._browser.waitForTarget(target => target.browserContext() === this && predicate(target), options);
-  }
-
-  /**
-   * @return {!Promise<!Array<!Puppeteer.Page>>}
-   */
-  async pages() {
-    const pages = await Promise.all(
-        this.targets()
-            .filter(target => target.type() === 'page')
-            .map(target => target.page())
-    );
-    return pages.filter(page => !!page);
-  }
-
-  /**
-   * @return {boolean}
-   */
-  isIncognito() {
-    return !!this._id;
-  }
-
-  /**
-   * @param {string} origin
-   * @param {!Array<string>} permissions
-   */
-  async overridePermissions(origin, permissions) {
-    const webPermissionToProtocol = new Map([
-      ['geolocation', 'geolocation'],
-      ['midi', 'midi'],
-      ['notifications', 'notifications'],
-      ['push', 'push'],
-      ['camera', 'videoCapture'],
-      ['microphone', 'audioCapture'],
-      ['background-sync', 'backgroundSync'],
-      ['ambient-light-sensor', 'sensors'],
-      ['accelerometer', 'sensors'],
-      ['gyroscope', 'sensors'],
-      ['magnetometer', 'sensors'],
-      ['accessibility-events', 'accessibilityEvents'],
-      ['clipboard-read', 'clipboardRead'],
-      ['clipboard-write', 'clipboardWrite'],
-      ['payment-handler', 'paymentHandler'],
-      // chrome-specific permissions we have.
-      ['midi-sysex', 'midiSysex'],
-    ]);
-    permissions = permissions.map(permission => {
-      const protocolPermission = webPermissionToProtocol.get(permission);
-      if (!protocolPermission)
-        throw new Error('Unknown permission: ' + permission);
-      return protocolPermission;
-    });
-    await this._connection.send('Browser.grantPermissions', {origin, browserContextId: this._id || undefined, permissions});
-  }
-
-  async clearPermissionOverrides() {
-    await this._connection.send('Browser.resetPermissions', {browserContextId: this._id || undefined});
-  }
-
-  /**
-   * @return {!Promise<!Puppeteer.Page>}
-   */
-  newPage() {
-    return this._browser._createPageInContext(this._id);
-  }
-
-  /**
-   * @return {!Browser}
-   */
-  browser() {
-    return this._browser;
-  }
-
-  async close() {
-    assert(this._id, 'Non-incognito profiles cannot be closed!');
-    await this._browser._disposeContext(this._id);
-  }
-}
-
-module.exports = {Browser, BrowserContext};
deleted file mode 100644
--- a/remote/test/puppeteer/lib/BrowserFetcher.js
+++ /dev/null
@@ -1,320 +0,0 @@
-/**
- * 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 os = require('os');
-const fs = require('fs');
-const path = require('path');
-const util = require('util');
-const extract = require('extract-zip');
-const URL = require('url');
-const {helper, assert} = require('./helper');
-const removeRecursive = require('rimraf');
-// @ts-ignore
-const ProxyAgent = require('https-proxy-agent');
-// @ts-ignore
-const getProxyForUrl = require('proxy-from-env').getProxyForUrl;
-
-const DEFAULT_DOWNLOAD_HOST = 'https://storage.googleapis.com';
-
-const supportedPlatforms = ['mac', 'linux', 'win32', 'win64'];
-const downloadURLs = {
-  linux: '%s/chromium-browser-snapshots/Linux_x64/%d/%s.zip',
-  mac: '%s/chromium-browser-snapshots/Mac/%d/%s.zip',
-  win32: '%s/chromium-browser-snapshots/Win/%d/%s.zip',
-  win64: '%s/chromium-browser-snapshots/Win_x64/%d/%s.zip',
-};
-
-/**
- * @param {string} platform
- * @param {string} revision
- * @return {string}
- */
-function archiveName(platform, revision) {
-  if (platform === 'linux')
-    return 'chrome-linux';
-  if (platform === 'mac')
-    return 'chrome-mac';
-  if (platform === 'win32' || platform === 'win64') {
-    // Windows archive name changed at r591479.
-    return parseInt(revision, 10) > 591479 ? 'chrome-win' : 'chrome-win32';
-  }
-  return null;
-}
-
-/**
- * @param {string} platform
- * @param {string} host
- * @param {string} revision
- * @return {string}
- */
-function downloadURL(platform, host, revision) {
-  return util.format(downloadURLs[platform], host, revision, archiveName(platform, revision));
-}
-
-const readdirAsync = helper.promisify(fs.readdir.bind(fs));
-const mkdirAsync = helper.promisify(fs.mkdir.bind(fs));
-const unlinkAsync = helper.promisify(fs.unlink.bind(fs));
-const chmodAsync = helper.promisify(fs.chmod.bind(fs));
-
-function existsAsync(filePath) {
-  let fulfill = null;
-  const promise = new Promise(x => fulfill = x);
-  fs.access(filePath, err => fulfill(!err));
-  return promise;
-}
-
-class BrowserFetcher {
-  /**
-   * @param {string} projectRoot
-   * @param {!BrowserFetcher.Options=} options
-   */
-  constructor(projectRoot, options = {}) {
-    this._downloadsFolder = options.path || path.join(projectRoot, '.local-chromium');
-    this._downloadHost = options.host || DEFAULT_DOWNLOAD_HOST;
-    this._platform = options.platform || '';
-    if (!this._platform) {
-      const platform = os.platform();
-      if (platform === 'darwin')
-        this._platform = 'mac';
-      else if (platform === 'linux')
-        this._platform = 'linux';
-      else if (platform === 'win32')
-        this._platform = os.arch() === 'x64' ? 'win64' : 'win32';
-      assert(this._platform, 'Unsupported platform: ' + os.platform());
-    }
-    assert(supportedPlatforms.includes(this._platform), 'Unsupported platform: ' + this._platform);
-  }
-
-  /**
-   * @return {string}
-   */
-  platform() {
-    return this._platform;
-  }
-
-  /**
-   * @param {string} revision
-   * @return {!Promise<boolean>}
-   */
-  canDownload(revision) {
-    const url = downloadURL(this._platform, this._downloadHost, revision);
-    let resolve;
-    const promise = new Promise(x => resolve = x);
-    const request = httpRequest(url, 'HEAD', response => {
-      resolve(response.statusCode === 200);
-    });
-    request.on('error', error => {
-      console.error(error);
-      resolve(false);
-    });
-    return promise;
-  }
-
-  /**
-   * @param {string} revision
-   * @param {?function(number, number):void} progressCallback
-   * @return {!Promise<!BrowserFetcher.RevisionInfo>}
-   */
-  async download(revision, progressCallback) {
-    const url = downloadURL(this._platform, this._downloadHost, revision);
-    const zipPath = path.join(this._downloadsFolder, `download-${this._platform}-${revision}.zip`);
-    const folderPath = this._getFolderPath(revision);
-    if (await existsAsync(folderPath))
-      return this.revisionInfo(revision);
-    if (!(await existsAsync(this._downloadsFolder)))
-      await mkdirAsync(this._downloadsFolder);
-    try {
-      await downloadFile(url, zipPath, progressCallback);
-      await extractZip(zipPath, folderPath);
-    } finally {
-      if (await existsAsync(zipPath))
-        await unlinkAsync(zipPath);
-    }
-    const revisionInfo = this.revisionInfo(revision);
-    if (revisionInfo)
-      await chmodAsync(revisionInfo.executablePath, 0o755);
-    return revisionInfo;
-  }
-
-  /**
-   * @return {!Promise<!Array<string>>}
-   */
-  async localRevisions() {
-    if (!await existsAsync(this._downloadsFolder))
-      return [];
-    const fileNames = await readdirAsync(this._downloadsFolder);
-    return fileNames.map(fileName => parseFolderPath(fileName)).filter(entry => entry && entry.platform === this._platform).map(entry => entry.revision);
-  }
-
-  /**
-   * @param {string} revision
-   */
-  async remove(revision) {
-    const folderPath = this._getFolderPath(revision);
-    assert(await existsAsync(folderPath), `Failed to remove: revision ${revision} is not downloaded`);
-    await new Promise(fulfill => removeRecursive(folderPath, fulfill));
-  }
-
-  /**
-   * @param {string} revision
-   * @return {!BrowserFetcher.RevisionInfo}
-   */
-  revisionInfo(revision) {
-    const folderPath = this._getFolderPath(revision);
-    let executablePath = '';
-    if (this._platform === 'mac')
-      executablePath = path.join(folderPath, archiveName(this._platform, revision), 'Chromium.app', 'Contents', 'MacOS', 'Chromium');
-    else if (this._platform === 'linux')
-      executablePath = path.join(folderPath, archiveName(this._platform, revision), 'chrome');
-    else if (this._platform === 'win32' || this._platform === 'win64')
-      executablePath = path.join(folderPath, archiveName(this._platform, revision), 'chrome.exe');
-    else
-      throw new Error('Unsupported platform: ' + this._platform);
-    const url = downloadURL(this._platform, this._downloadHost, revision);
-    const local = fs.existsSync(folderPath);
-    return {revision, executablePath, folderPath, local, url};
-  }
-
-  /**
-   * @param {string} revision
-   * @return {string}
-   */
-  _getFolderPath(revision) {
-    return path.join(this._downloadsFolder, this._platform + '-' + revision);
-  }
-}
-
-module.exports = BrowserFetcher;
-
-/**
- * @param {string} folderPath
- * @return {?{platform: string, revision: string}}
- */
-function parseFolderPath(folderPath) {
-  const name = path.basename(folderPath);
-  const splits = name.split('-');
-  if (splits.length !== 2)
-    return null;
-  const [platform, revision] = splits;
-  if (!supportedPlatforms.includes(platform))
-    return null;
-  return {platform, revision};
-}
-
-/**
- * @param {string} url
- * @param {string} destinationPath
- * @param {?function(number, number):void} progressCallback
- * @return {!Promise}
- */
-function downloadFile(url, destinationPath, progressCallback) {
-  let fulfill, reject;
-  let downloadedBytes = 0;
-  let totalBytes = 0;
-
-  const promise = new Promise((x, y) => { fulfill = x; reject = y; });
-
-  const request = httpRequest(url, 'GET', response => {
-    if (response.statusCode !== 200) {
-      const error = new Error(`Download failed: server returned code ${response.statusCode}. URL: ${url}`);
-      // consume response data to free up memory
-      response.resume();
-      reject(error);
-      return;
-    }
-    const file = fs.createWriteStream(destinationPath);
-    file.on('finish', () => fulfill());
-    file.on('error', error => reject(error));
-    response.pipe(file);
-    totalBytes = parseInt(/** @type {string} */ (response.headers['content-length']), 10);
-    if (progressCallback)
-      response.on('data', onData);
-  });
-  request.on('error', error => reject(error));
-  return promise;
-
-  function onData(chunk) {
-    downloadedBytes += chunk.length;
-    progressCallback(downloadedBytes, totalBytes);
-  }
-}
-
-/**
- * @param {string} zipPath
- * @param {string} folderPath
- * @return {!Promise<?Error>}
- */
-function extractZip(zipPath, folderPath) {
-  return new Promise((fulfill, reject) => extract(zipPath, {dir: folderPath}, err => {
-    if (err)
-      reject(err);
-    else
-      fulfill();
-  }));
-}
-
-function httpRequest(url, method, response) {
-  /** @type {Object} */
-  let options = URL.parse(url);
-  options.method = method;
-
-  const proxyURL = getProxyForUrl(url);
-  if (proxyURL) {
-    if (url.startsWith('http:')) {
-      const proxy = URL.parse(proxyURL);
-      options = {
-        path: options.href,
-        host: proxy.hostname,
-        port: proxy.port,
-      };
-    } else {
-      /** @type {Object} */
-      const parsedProxyURL = URL.parse(proxyURL);
-      parsedProxyURL.secureProxy = parsedProxyURL.protocol === 'https:';
-
-      options.agent = new ProxyAgent(parsedProxyURL);
-      options.rejectUnauthorized = false;
-    }
-  }
-
-  const requestCallback = res => {
-    if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location)
-      httpRequest(res.headers.location, method, response);
-    else
-      response(res);
-  };
-  const request = options.protocol === 'https:' ?
-    require('https').request(options, requestCallback) :
-    require('http').request(options, requestCallback);
-  request.end();
-  return request;
-}
-
-/**
- * @typedef {Object} BrowserFetcher.Options
- * @property {string=} platform
- * @property {string=} path
- * @property {string=} host
- */
-
-/**
- * @typedef {Object} BrowserFetcher.RevisionInfo
- * @property {string} folderPath
- * @property {string} executablePath
- * @property {string} url
- * @property {boolean} local
- * @property {string} revision
- */
deleted file mode 100644
--- a/remote/test/puppeteer/lib/Connection.js
+++ /dev/null
@@ -1,242 +0,0 @@
-/**
- * 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 {assert} = require('./helper');
-const {Events} = require('./Events');
-const debugProtocol = require('debug')('puppeteer:protocol');
-const EventEmitter = require('events');
-
-class Connection extends EventEmitter {
-  /**
-   * @param {string} url
-   * @param {!Puppeteer.ConnectionTransport} transport
-   * @param {number=} delay
-   */
-  constructor(url, transport, delay = 0) {
-    super();
-    this._url = url;
-    this._lastId = 0;
-    /** @type {!Map<number, {resolve: function, reject: function, error: !Error, method: string}>}*/
-    this._callbacks = new Map();
-    this._delay = delay;
-
-    this._transport = transport;
-    this._transport.onmessage = this._onMessage.bind(this);
-    this._transport.onclose = this._onClose.bind(this);
-    /** @type {!Map<string, !CDPSession>}*/
-    this._sessions = new Map();
-    this._closed = false;
-  }
-
-  /**
-   * @param {!CDPSession} session
-   * @return {!Connection}
-   */
-  static fromSession(session) {
-    return session._connection;
-  }
-
-  /**
-   * @param {string} sessionId
-   * @return {?CDPSession}
-   */
-  session(sessionId) {
-    return this._sessions.get(sessionId) || null;
-  }
-
-  /**
-   * @return {string}
-   */
-  url() {
-    return this._url;
-  }
-
-  /**
-   * @param {string} method
-   * @param {!Object=} params
-   * @return {!Promise<?Object>}
-   */
-  send(method, params = {}) {
-    const id = this._rawSend({method, params});
-    return new Promise((resolve, reject) => {
-      this._callbacks.set(id, {resolve, reject, error: new Error(), method});
-    });
-  }
-
-  /**
-   * @param {*} message
-   * @return {number}
-   */
-  _rawSend(message) {
-    const id = ++this._lastId;
-    message = JSON.stringify(Object.assign({}, message, {id}));
-    debugProtocol('SEND ► ' + message);
-    this._transport.send(message);
-    return id;
-  }
-
-  /**
-   * @param {string} message
-   */
-  async _onMessage(message) {
-    if (this._delay)
-      await new Promise(f => setTimeout(f, this._delay));
-    debugProtocol('◀ RECV ' + message);
-    const object = JSON.parse(message);
-    if (object.method === 'Target.attachedToTarget') {
-      const sessionId = object.params.sessionId;
-      const session = new CDPSession(this, object.params.targetInfo.type, sessionId);
-      this._sessions.set(sessionId, session);
-    } else if (object.method === 'Target.detachedFromTarget') {
-      const session = this._sessions.get(object.params.sessionId);
-      if (session) {
-        session._onClosed();
-        this._sessions.delete(object.params.sessionId);
-      }
-    }
-    if (object.sessionId) {
-      const session = this._sessions.get(object.sessionId);
-      if (session)
-        session._onMessage(object);
-    } else if (object.id) {
-      const callback = this._callbacks.get(object.id);
-      // Callbacks could be all rejected if someone has called `.dispose()`.
-      if (callback) {
-        this._callbacks.delete(object.id);
-        if (object.error)
-          callback.reject(createProtocolError(callback.error, callback.method, object));
-        else
-          callback.resolve(object.result);
-      }
-    } else {
-      this.emit(object.method, object.params);
-    }
-  }
-
-  _onClose() {
-    if (this._closed)
-      return;
-    this._closed = true;
-    this._transport.onmessage = null;
-    this._transport.onclose = null;
-    for (const callback of this._callbacks.values())
-      callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`));
-    this._callbacks.clear();
-    for (const session of this._sessions.values())
-      session._onClosed();
-    this._sessions.clear();
-    this.emit(Events.Connection.Disconnected);
-  }
-
-  dispose() {
-    this._onClose();
-    this._transport.close();
-  }
-
-  /**
-   * @param {Protocol.Target.TargetInfo} targetInfo
-   * @return {!Promise<!CDPSession>}
-   */
-  async createSession(targetInfo) {
-    const {sessionId} = await this.send('Target.attachToTarget', {targetId: targetInfo.targetId, flatten: true});
-    return this._sessions.get(sessionId);
-  }
-}
-
-class CDPSession extends EventEmitter {
-  /**
-   * @param {!Connection} connection
-   * @param {string} targetType
-   * @param {string} sessionId
-   */
-  constructor(connection, targetType, sessionId) {
-    super();
-    /** @type {!Map<number, {resolve: function, reject: function, error: !Error, method: string}>}*/
-    this._callbacks = new Map();
-    this._connection = connection;
-    this._targetType = targetType;
-    this._sessionId = sessionId;
-  }
-
-  /**
-   * @param {string} method
-   * @param {!Object=} params
-   * @return {!Promise<?Object>}
-   */
-  send(method, params = {}) {
-    if (!this._connection)
-      return Promise.reject(new Error(`Protocol error (${method}): Session closed. Most likely the ${this._targetType} has been closed.`));
-    const id = this._connection._rawSend({sessionId: this._sessionId, method, params});
-    return new Promise((resolve, reject) => {
-      this._callbacks.set(id, {resolve, reject, error: new Error(), method});
-    });
-  }
-
-  /**
-   * @param {{id?: number, method: string, params: Object, error: {message: string, data: any}, result?: *}} object
-   */
-  _onMessage(object) {
-    if (object.id && this._callbacks.has(object.id)) {
-      const callback = this._callbacks.get(object.id);
-      this._callbacks.delete(object.id);
-      if (object.error)
-        callback.reject(createProtocolError(callback.error, callback.method, object));
-      else
-        callback.resolve(object.result);
-    } else {
-      assert(!object.id);
-      this.emit(object.method, object.params);
-    }
-  }
-
-  async detach() {
-    if (!this._connection)
-      throw new Error(`Session already detached. Most likely the ${this._targetType} has been closed.`);
-    await this._connection.send('Target.detachFromTarget',  {sessionId: this._sessionId});
-  }
-
-  _onClosed() {
-    for (const callback of this._callbacks.values())
-      callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`));
-    this._callbacks.clear();
-    this._connection = null;
-    this.emit(Events.CDPSession.Disconnected);
-  }
-}
-
-/**
- * @param {!Error} error
- * @param {string} method
- * @param {{error: {message: string, data: any}}} object
- * @return {!Error}
- */
-function createProtocolError(error, method, object) {
-  let message = `Protocol error (${method}): ${object.error.message}`;
-  if ('data' in object.error)
-    message += ` ${object.error.data}`;
-  return rewriteError(error, message);
-}
-
-/**
- * @param {!Error} error
- * @param {string} message
- * @return {!Error}
- */
-function rewriteError(error, message) {
-  error.message = message;
-  return error;
-}
-
-module.exports = {Connection, CDPSession};
deleted file mode 100644
--- a/remote/test/puppeteer/lib/Coverage.js
+++ /dev/null
@@ -1,313 +0,0 @@
-/**
- * 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 {helper, debugError, assert} = require('./helper');
-
-const {EVALUATION_SCRIPT_URL} = require('./ExecutionContext');
-
-/**
- * @typedef {Object} CoverageEntry
- * @property {string} url
- * @property {string} text
- * @property {!Array<!{start: number, end: number}>} ranges
- */
-
-class Coverage {
-  /**
-   * @param {!Puppeteer.CDPSession} client
-   */
-  constructor(client) {
-    this._jsCoverage = new JSCoverage(client);
-    this._cssCoverage = new CSSCoverage(client);
-  }
-
-  /**
-   * @param {!{resetOnNavigation?: boolean, reportAnonymousScripts?: boolean}} options
-   */
-  async startJSCoverage(options) {
-    return await this._jsCoverage.start(options);
-  }
-
-  /**
-   * @return {!Promise<!Array<!CoverageEntry>>}
-   */
-  async stopJSCoverage() {
-    return await this._jsCoverage.stop();
-  }
-
-  /**
-   * @param {{resetOnNavigation?: boolean}=} options
-   */
-  async startCSSCoverage(options) {
-    return await this._cssCoverage.start(options);
-  }
-
-  /**
-   * @return {!Promise<!Array<!CoverageEntry>>}
-   */
-  async stopCSSCoverage() {
-    return await this._cssCoverage.stop();
-  }
-}
-
-module.exports = {Coverage};
-
-class JSCoverage {
-  /**
-   * @param {!Puppeteer.CDPSession} client
-   */
-  constructor(client) {
-    this._client = client;
-    this._enabled = false;
-    this._scriptURLs = new Map();
-    this._scriptSources = new Map();
-    this._eventListeners = [];
-    this._resetOnNavigation = false;
-  }
-
-  /**
-   * @param {!{resetOnNavigation?: boolean, reportAnonymousScripts?: boolean}} options
-   */
-  async start(options = {}) {
-    assert(!this._enabled, 'JSCoverage is already enabled');
-    const {
-      resetOnNavigation = true,
-      reportAnonymousScripts = false
-    } = options;
-    this._resetOnNavigation = resetOnNavigation;
-    this._reportAnonymousScripts = reportAnonymousScripts;
-    this._enabled = true;
-    this._scriptURLs.clear();
-    this._scriptSources.clear();
-    this._eventListeners = [
-      helper.addEventListener(this._client, 'Debugger.scriptParsed', this._onScriptParsed.bind(this)),
-      helper.addEventListener(this._client, 'Runtime.executionContextsCleared', this._onExecutionContextsCleared.bind(this)),
-    ];
-    await Promise.all([
-      this._client.send('Profiler.enable'),
-      this._client.send('Profiler.startPreciseCoverage', {callCount: false, detailed: true}),
-      this._client.send('Debugger.enable'),
-      this._client.send('Debugger.setSkipAllPauses', {skip: true})
-    ]);
-  }
-
-  _onExecutionContextsCleared() {
-    if (!this._resetOnNavigation)
-      return;
-    this._scriptURLs.clear();
-    this._scriptSources.clear();
-  }
-
-  /**
-   * @param {!Protocol.Debugger.scriptParsedPayload} event
-   */
-  async _onScriptParsed(event) {
-    // Ignore puppeteer-injected scripts
-    if (event.url === EVALUATION_SCRIPT_URL)
-      return;
-    // Ignore other anonymous scripts unless the reportAnonymousScripts option is true.
-    if (!event.url && !this._reportAnonymousScripts)
-      return;
-    try {
-      const response = await this._client.send('Debugger.getScriptSource', {scriptId: event.scriptId});
-      this._scriptURLs.set(event.scriptId, event.url);
-      this._scriptSources.set(event.scriptId, response.scriptSource);
-    } catch (e) {
-      // This might happen if the page has already navigated away.
-      debugError(e);
-    }
-  }
-
-  /**
-   * @return {!Promise<!Array<!CoverageEntry>>}
-   */
-  async stop() {
-    assert(this._enabled, 'JSCoverage is not enabled');
-    this._enabled = false;
-    const [profileResponse] = await Promise.all([
-      this._client.send('Profiler.takePreciseCoverage'),
-      this._client.send('Pro