[
  {
    "path": ".circleci/config.yml",
    "content": "version: 2.1\n\norbs:\n  codecov: codecov/codecov@1.0.5\n\nworkflows:\n  main:\n    jobs:\n      - lint\n      - mypy\n      - test_36\n      - test_37\n      - test_38\n      - test_39\n      - test_310\n\n\njobs:\n  test_36:\n    docker:\n      - image: circleci/python:3.6\n    environment:\n      TOXENV: py36\n      PYTEST_ADDOPTS: -n 8 --junitxml=/tmp/tests/pytest/results.xml --cov=./\n    steps: &step_template\n      - checkout\n      - restore_cache:\n          keys:\n            - poetry_deps_{{checksum \"poetry.lock\"}}\n      - run:\n          name: Install headless Chrome dependencies\n          # chrome headless libs, see\n          # https://github.com/puppeteer/puppeteer/blob/master/docs/troubleshooting.md#chrome-headless-doesnt-launch-on-unix\n          command: |\n            sudo apt install -yq \\\n              ca-certificates fonts-liberation libasound2 libatk1.0-0 \\\n              libcairo2 libcups2 libdbus-1-3 libgdk-pixbuf2.0-0 \\\n              libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 \\\n              libpangocairo-1.0-0 libx11-xcb1 libxcomposite1 libxcursor1 \\\n              libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \\\n              lsb-release xdg-utils wget \n      - run:\n          name: Install tox\n          command: pip install tox\n      - run:\n          name: Run tests\n          command: tox\n      - save_cache:\n          key: poetry_deps_{{checksum \"poetry.lock\"}}\n          paths: ~/.cache/pypoetry/\n      - store_test_results:\n          path: /tmp/tests/\n      # this step will simply fail for other jobs\n      - codecov/upload:\n          file: ./pytest-cov.pth\n\n  test_37:\n    docker:\n      - image: circleci/python:3.7\n    environment:\n      TOXENV: py37\n      PYTEST_ADDOPTS: &pytest_default -n 8 --junitxml=/tmp/tests/pytest/results.xml\n    steps: *step_template\n\n  test_38:\n    docker:\n      - image: circleci/python:3.8\n    environment:\n      TOXENV: py38\n      PYTEST_ADDOPTS: *pytest_default\n    steps: *step_template\n\n  test_39:\n    docker:\n      - image: circleci/python:3.9\n    environment:\n      TOXENV: py39\n      PYTEST_ADDOPTS: *pytest_default\n    steps: *step_template\n\n  test_310:\n    docker:\n      - image: circleci/python:3.10-rc\n    environment:\n      TOXENV: py310\n      PYTEST_ADDOPTS: *pytest_default\n    steps: *step_template\n\n  mypy:\n    docker:\n      - image: circleci/python:3.6\n    environment:\n      TOXENV: mypy\n      MYPY_JUNIT_XML_PATH: /tmp/tests/mypy/results.xml\n    steps:\n      - checkout\n      - run:\n          name: Install tox\n          command: pip install tox\n      - run:\n          name: Check typing\n          command: tox\n      - store_test_results:\n          path: /tmp/tests\n\n\n  lint:\n    docker:\n      - image: circleci/python:3.6\n    environment:\n      TOXENV: flake8\n    steps:\n      - checkout\n      - run:\n          name: Install tox\n          command: pip install tox\n      - run:\n          name: Check code style\n          command: tox\n\n"
  },
  {
    "path": ".coveragerc",
    "content": "[run]\nomit=setup.py\nsource=pyppeteer,tests\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# Virtualenv\nenv/\nvenv/\nbin/\ninclude/\nlib/\nlib64\nlib64/\nman/\npyvenv.cfg\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\n.doit.db.*\n.mypy_cache\nnosetests.xml\ncoverage.xml\n*,cover\n.hypothesis/\n.pytest_cache/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# pyenv python configuration file\n.python-version\n\n# pycharm file\n.idea/\n\n###### direnv ######\n.direnv\n.envrc\n\n###### zsh-autoenv ######\n.autoenv.zsh\n.autoenv_leave.zsh\n\n# test files\ntrace.json\n"
  },
  {
    "path": ".noserc",
    "content": "[nosetests]\nlogging-level=INFO\n# no-path-adjustment=true\n# with-coverage=true\n# cover-package=pyppeteer\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v2.4.0\n    hooks:\n      - id: trailing-whitespace\n      - id: end-of-file-fixer\n      - id: check-yaml\n      - id: check-toml\n      - id: check-builtin-literals\n      - id: debug-statements\n      - id: check-added-large-files\n  - repo: https://github.com/asottile/seed-isort-config\n    rev: v2.1.1\n    hooks:\n      - id: seed-isort-config\n        # if we don't specify these the seeder will intermittently include\n        # these as 'known third parties' which messes with our diffs\n        args: ['--application-directories', './pyppeteer:./tests']\n  - repo: https://github.com/timothycrosley/isort\n    rev: 4.3.21\n    hooks:\n      - id: isort\n        additional_dependencies: [toml]\n  - repo: https://github.com/psf/black\n    rev: stable\n    hooks:\n      - id: black\n        language_version: python3\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "History\n=======\n\n## Version 2.0.0\n\n* Bump pyee version, which removes support for Python 3.7\n* Bumped included browser version to revision 1181205. It may not match the base p*u*ppeteer version, but at least it runs\n* Fix invalid escape sequence (#453)\n* Fix deprecated asyncio wait in page.py (#451)\n* Remove version_info. `version` is still present and can be parsed if necessary\n\n## Version 1.0.2\n\n* Fix circular import as result of 1.0.1 (#344)\n\n## Version 1.0.1\n\n* don't configure logging ourselves (#343)\n* make logging better for chromium downloader\n\n## Version 1.0.0\n\n* hotfix: websockets 10 for python 3.10 (#321, #327)\n  * removes support for python 3.6./\n* remove `Page.craete`\n\n## Version 0.2.6\n\n* Change build backend to poetry-core (allows for faster PEP517 package building) #262 @fabaff\n* Chromium download fixes (file not found) #245 @mborsetti\n* Do not try to set an exception on finished futures #216 @polyfloyd\n* Add HTTPException to caught exceptions in launch #293 @raymondguo-db\n* Fix encoding error #226 @aleksei140888\n* support websockets 9.0 #252 @mborsetti\n* Fix tqdm exception when NO_PROGRESS_BAR is True #224 @mborsetti\n* fix(browser): Clean up coroutine Browser._targetCreated() #271 @H--o-I\n\n## Version 0.2.5\n\n* Match package version and \\_\\_version__ (🤦‍♂️)\n* Use `importlib_metadata` so this isn't a problem in the future\n\n## Version 0.2.4\n\n* Update `pyee` dependency breaking build failures on NixOS + Fedora packaging systems (#207)\n\n## Version 0.2.3\n\n* Hotfix: random freezes from sending stdout to PIPE instead of DEVNULL\n* Fix `tests` package being installed for no reason\n\n## Version 0.0.26\n\n* Add `$PYPPETEER_NO_PROGRESS_BAR` environment variable\n* `pyppeteer.defaultArgs` now accepts that help infer chromium command-line flags.\n* `pyppeteer.launch()` argument `ignoreDefaultArgs` now accepts a list of flags to ignore.\n* `Page.type()` now supports typing emoji\n* `Page.pdf()` accepts a new argument `preferCSSPageSize`\n* Add new option `defaultViewport` to `launch()` and `connect()`\n* Add `BrowserContext.pages()` method\n\n## Version 0.0.25 (2018-09-27)\n\n* Fix miss-spelled methods and functions\n  * Change `Browser.isIncognite` to `Browser.isIncognito`\n  * Change `Browser.createIncogniteBrowserContext` to `Browser.createIncognitoBrowserContext`\n  * Change `chromium_excutable` to `chromium_executable`\n  * Remove `craete` function in `page.py`\n\n## Version 0.0.24 (2018-09-12)\n\nCatch up puppeteer v1.6.0\n\n* Add `ElementHandle.isIntersectingViewport()`\n* Add `reportAnonymousScript` option to `Coverage.startJSCoverage()`\n* Add `Page.waitForRequest` and `Page.waitForResponse` methods\n* Now possible to attach to extension background pages with `Target.page()`\n* Improved reliability of clicking with `Page.click()` and `ElementHandle.click()`\n\n## Version 0.0.23 (2018-09-10)\n\nCatch up puppeteer v1.5.0\n\n* Add `BrowserContext` class\n* Add `Worker` class\n* Change `CDPSession.send` to a normal function which returns awaitable value\n* Add `Page.isClosed` method\n* Add `ElementHandle.querySelectorAllEval` and `ElementHandle.JJeval`\n* Add `Target.opener`\n* Add `Request.isNavigationRequest`\n\n## Version 0.0.22 (2018-09-06)\n\nCatch up puppeteer v1.4.0\n\n* Add `pyppeteer.DEBUG` variable\n* Add `Page.browser`\n* Add `Target.browser`\n* Add `ElementHandle.querySelectorEval` and `ElementHandle.Jeval`\n* Add `runBeforeUnload` option to `Page.close` method\n* Change `Page.querySelectorEval` to raise `ElementHandleError` when element which matches `selector` is not found\n* Report 'Log' domain entries as 'console' events\n* Fix `Page.goto` to return response when page pushes new state\n* (OS X) Suppress long log when extracting chromium\n\n\n## Version 0.0.21 (2018-08-21)\n\nCatch up puppeteer v1.3.0\n\n* Add `pyppeteer-install` command\n* Add `autoClose` option to `launch` function\n* Add `loop` option to `launch` function (experimental)\n* Add `Page.setBypassCSP` method\n* `Page.tracing.stop` returns result data\n* Rename `documentloaded` to `domcontentloaded` on `waitUntil` option\n* Fix `slowMo` option\n* Fix anchor navigation\n* Fix to return response via redirects\n* Continue to find WS URL while process is alive\n\n\n## Version 0.0.20 (2018-08-11)\n\n* Run on msys/cygwin, anyway\n* Raise error correctly when connection failed (PR#91)\n* Change browser download location and temporary user data directory to:\n    * If `$PYPPETEER_HOME` environment variable is defined, use this location\n    * Otherwise, use platform dependent locations, based on [appdirs](https://pypi.org/project/appdirs/):\n        * `'C:\\Users\\<username>\\AppData\\Local\\pyppeteer'` (Windows)\n        * `'/Users/<username>/Library/Application Support/pyppeteer'` (OS X)\n        * `'/home/<username>/.local/share/pyppeteer'` (Linux)\n            * or in `'$XDG_DATA_HOME/pyppeteer'` if `$XDG_DATA_HOME` is defined\n\n* Introduce `$PYPPETEER_CHROMIUM_REVISION`\n* Introduce `$PYPPETEER_HOME`\n* Add `logLevel` option to `launch` and `connect` functions\n* Add page `close` event\n* Add `ElementHandle.boxModel` method\n* Add an option to disable timeout for `waitFor` functions\n\n\n## Version 0.0.19 (2018-07-05)\n\nCatch up puppeteer v1.2.0\n\n* Add `ElementHandle.contentFrame` method\n* Add `Request.redirectChain` method\n* `Page.addScriptTag` accepts a new option `type`\n\n\n## Version 0.0.18 (2018-07-04)\n\nCatch up puppeteer v1.1.1\n\n* Add `Page.waitForXPath` and `Frame.waitForXPath`\n* `Page.waitFor` accepts xpath string which starts with `//`\n* Add `Response.fromCache` and `Response.fromServiceWorker`\n* Add `SecurityDetails` class and `response.securityDetails`\n* Add `Page.setCacheEnabled` method\n* Add `ExecutionContext.frame`\n* Add `dumpio` option to `launch` function\n* Add `slowMo` option to `connect` function\n* `launcher.connect` can be access from package top\n  * `from pyppeteer import connect` is now valid\n* Add `Frame.evaluateHandle`\n* Add `Page.Events.DOMContentLoaded`\n\n\n## Version 0.0.17 (2018-04-02)\n\n* Mark as alpha\n\n* Gracefully terminate browser process\n* `Request.method` and `Request.postData` return `None` if no data\n* Change `Target.url` and `Target.type` to properties\n* Change `Dialog.message` and `Dialog.defaultValue` to properties\n* Fix: properly emit `Browser.targetChanged` events\n* Fix: properly emit `Browser.targetDestroyed` events\n\n\n## Version 0.0.16 (2018-03-23)\n\n* BugFix: Skip SIGHUP option on windows (windows does not support this signal)\n\n\n## Version 0.0.15 (2018-03-22)\n\nCatch up puppeteer v1.0.0\n\n* Support `raf` and `mutation` polling for `waitFor*` methods\n* Add `Page.coverage` to support JS and CSS coverage\n* Add XPath support with `Page.xpath`, `Frame.xpath`, and `ElementHandle.xpath`\n* Add `Target.createCDPSession` to work with raw Devtools Protocol\n* Change `Frame.executionContext` from property to coroutine\n* Add `ignoreDefaultArgs` option to `pyppeteer.launch`\n* Add `handleSIGINT`/`handleSIGTERM`/`handleSIGHUP` options to `pyppeteer.launch`\n* Add `Page.setDefaultNavigationTimeout` method\n* `Page.waitFor*` methods accept `JSHandle` as argument\n* Implement `Frame.content` and `Frame.setContent` methods\n* `page.tracing.start` accepts custom tracing categories option\n* Add `Browser.process` property\n* Add `Request.frame` property\n\n\n## Version 0.0.14 (2018-03-14)\n\n* Read WS endpoint from web interface instead of stdout\n* Pass environment variables of python process to chrome by default\n* Do not limit size of websocket frames\n\n* BugFix:\n    * `Keyboard.type`\n    * `Page.Events.Metrics`\n\n## Version 0.0.13 (2018-03-10)\n\nCatch up puppeteer v0.13.0\n\n* `pyppeteer.launch()` is now **coroutine**\n* Implement `connect` function\n* `PYPPETEER_DOWNLOAD_HOST` env variable specifies host part of URL to download chromium\n* Rename `setRequestInterceptionEnable` to `setRequestInterception`\n* Rename `Page.getMetrics` to `Page.metrics`\n* Implement `Browser.pages` to access all pages\n    * Add `Target` class and some new method on Browser\n* Add `ElementHandle.querySelector` and `ElementHandle.querySelectorAll`\n* Refactor NavigatorWatcher\n    * add `documentloaded`, `networkidle0`, and `networkidle2` options\n* `Request.abort` accepts error code\n* `addScriptTag` and `addStyleTag` return `ElementHandle`\n* Add `force_expr` option to `evaluate` method\n* `Page.select` returns selected values\n* Add `pyppeteer.version` and `pyppeteer.version_info`\n\n* BugFix:\n    * Do not change original options dictionary\n    * `Page.frames`\n    * `Page.queryObjects`\n    * `Page.exposeFunction`\n    * Request interception\n    * Console API\n    * websocket error on closing browser (#24)\n\n## Version 0.0.12 (2018-03-01)\n\n* BugFix (#33)\n\n## Version 0.0.11 (2018-03-01)\n\nCatch up puppeteer v0.12.0\n\n* Remove `ElementHandle.evaluate`\n* Remove `ElementHandle.attribute`\n* Deprecate `Page.plainText`\n* Deprecate `Page.injectFile`\n* Add `Page.querySelectorAllEval`\n* Add `Page.select` and `Page.type`\n* Add `ElementHandle.boundingBox` and `ElementHandle.screenshot`\n* Add `ElementHandle.focus`, `ElementHandle.type`, and `ElementHandle.press`\n* Add `getMetrics` method\n* Add `offlineMode`\n\n## Version 0.0.10 (2018-02-27)\n\n* Enable to import `launch` from package root\n* Change `browser.close` to coroutine function\n* Catch up puppeteer v0.11.0\n\n### Version 0.0.9 (2017-09-09)\n\n* Delete temporary user data directory when browser closed\n* Fix bug to fail extracting zip on mac\n\n### Version 0.0.8 (2017-09-03)\n\n* Change chromium revision\n* Support steps option of `Mouse.move()`\n* Experimentally supports python 3.5 by py-backwards\n\n### Version 0.0.7 (2017-09-03)\n\n* Catch up puppeteer v0.10.2\n    * Add `Page.querySelectorEval` (`Page.$eval` in puppeteer)\n    * Deprecate `ElementHandle.attribute`\n    * Add `Touchscreen` class and implement `Page.tap` and `ElementHandle.tap`\n\n### Version 0.0.6 (2017-09-02)\n\n* Accept keyword arguments for options\n* Faster polling on `waitFor*` functions\n* Fix bugs\n\n### Version 0.0.5 (2017-08-30)\n\n* Implement pdf printing\n* Implement `waitFor*` functions\n\n### Version 0.0.4 (2017-08-30)\n\n* Register PyPI\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contribution guidelines\n\nContributions are welcome as long as they follow core rule of the project:\n\nThe API of pyppeteer should [__match the API of puppeteer__](https://github.com/puppeteer/puppeteer) as closely as possible without sacrificing python too much.\nie keep public API keywords such as method names, arguments, class names etc. as they are in puppeteer version.\n\nOther than that the contributions should remain as pythonic as possible and pass linting and code tests.\n\nChanges worthy of a changelog entry should get one - simply follow the existing format in CHANGELOG.md\n\n## Maintainers - creating a release\n\n - Make sure all relevant changes have been recorded in the changelog\n - Ensure that code is properly tested\n - Bump the version in `pyproject.toml`, then tag the release in git\n   - ex: `git tag -a 2.0.0rc1 -m \"pypi release\"`\n - Run `poetry build`\n - Run `poetry publish`\n"
  },
  {
    "path": "LICENSE",
    "content": "\nMIT License\n\nCopyright (c) 2017, Hiroyuki Takagi\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nThis software includes the work that is distributed in the Apache License 2.0.\n"
  },
  {
    "path": "README.md",
    "content": "### Attention: This repo is unmaintained and has been outside of minor changes for a long time. Please consider [playwright-python](https://github.com/microsoft/playwright-python) as an alternative. \nIf you are interested in maintaining this, please contact [me](https://github.com/Mattwmaster58)\n\npyppeteer\n==========\n\n[![PyPI](https://img.shields.io/pypi/v/pyppeteer.svg)](https://pypi.python.org/pypi/pyppeteer)\n[![PyPI version](https://img.shields.io/pypi/pyversions/pyppeteer.svg)](https://pypi.python.org/pypi/pyppeteer)\n[![Documentation](https://img.shields.io/badge/docs-latest-brightgreen.svg)](https://pyppeteer.github.io/pyppeteer/)\n[![CircleCI](https://circleci.com/gh/pyppeteer/pyppeteer.svg?style=shield)](https://circleci.com/gh/pyppeteer/pyppeteer)\n[![codecov](https://codecov.io/gh/pyppeteer/pyppeteer/branch/dev/graph/badge.svg)](https://codecov.io/gh/pyppeteer/pyppeteer)\n\n_Note: this is a continuation of the [pyppeteer project](https://github.com/miyakogi/pyppeteer)_\n\nUnofficial Python port of [puppeteer](https://github.com/GoogleChrome/puppeteer) JavaScript (headless) chrome/chromium browser automation library.\n\n* Free software: MIT license (including the work distributed under the Apache 2.0 license)\n* Documentation: https://pyppeteer.github.io/pyppeteer/\n\n## Installation\n\npyppeteer requires Python >= 3.8\n\nInstall with `pip` from PyPI:\n\n```\npip install pyppeteer\n```\n\nOr install the latest version from [this github repo](https://github.com/pyppeteer/pyppeteer/):\n\n```\npip install -U git+https://github.com/pyppeteer/pyppeteer@dev\n```\n\n## Usage\n\n> **Note**: When you run pyppeteer for the first time, it downloads the latest version of Chromium (~150MB) if it is not found on your system. If you don't prefer this behavior, ensure that a suitable Chrome binary is installed. One way to do this is to run `pyppeteer-install` command before prior to using this library.\n\nFull documentation can be found [here](https://pyppeteer.github.io/pyppeteer/reference.html). [Puppeteer's documentation](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#) and [its troubleshooting guide](https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md) are also great resources for pyppeteer users.\n\n### Examples\n\nOpen web page and take a screenshot:\n```py\nimport asyncio\nfrom pyppeteer import launch\n\nasync def main():\n    browser = await launch()\n    page = await browser.newPage()\n    await page.goto('https://example.com')\n    await page.screenshot({'path': 'example.png'})\n    await browser.close()\n\nasyncio.get_event_loop().run_until_complete(main())\n```\n\nEvaluate javascript on a page:\n```py\nimport asyncio\nfrom pyppeteer import launch\n\nasync def main():\n    browser = await launch()\n    page = await browser.newPage()\n    await page.goto('https://example.com')\n    await page.screenshot({'path': 'example.png'})\n\n    dimensions = await page.evaluate('''() => {\n        return {\n            width: document.documentElement.clientWidth,\n            height: document.documentElement.clientHeight,\n            deviceScaleFactor: window.devicePixelRatio,\n        }\n    }''')\n\n    print(dimensions)\n    # >>> {'width': 800, 'height': 600, 'deviceScaleFactor': 1}\n    await browser.close()\n\nasyncio.get_event_loop().run_until_complete(main())\n```\n\n## Differences between puppeteer and pyppeteer\n\npyppeteer strives to replicate the puppeteer API as close as possible, however, fundamental differences between Javascript and Python make this difficult to do precisely. More information on specifics can be found in the [documentation](https://pyppeteer.github.io/pyppeteer/reference.html).\n\n### Keyword arguments for options\n\npuppeteer uses an object for passing options to functions/methods. pyppeteer methods/functions accept both dictionary (python equivalent to JavaScript's objects) and keyword arguments for options.\n\nDictionary style options (similar to puppeteer):\n\n```python\nbrowser = await launch({'headless': True})\n```\n\nKeyword argument style options (more pythonic, isn't it?):\n\n```python\nbrowser = await launch(headless=True)\n```\n\n### Element selector method names\n\nIn python, `$` is not a valid identifier. The equivalent methods to Puppeteer's `$`, `$$`, and `$x` methods are listed below, along with some shorthand methods for your convenience:\n\n| puppeteer | pyppeteer              | pyppeteer shorthand |\n|-----------|-------------------------|----------------------|\n| Page.$()  | Page.querySelector()    | Page.J()             |\n| Page.$$() | Page.querySelectorAll() | Page.JJ()            |\n| Page.$x() | Page.xpath()            | Page.Jx()            |\n\n### Arguments of `Page.evaluate()` and `Page.querySelectorEval()`\n\npuppeteer's version of `evaluate()` takes a JavaScript function or a string representation of a JavaScript expression. pyppeteer takes string representation of JavaScript expression or function. pyppeteer will try to automatically detect if the string is function or expression, but it will fail sometimes. If an expression is erroneously treated as function and an error is raised, try setting `force_expr` to `True`, to force pyppeteer to treat the string as expression.\n\n### Examples:\n\nGet a page's `textContent`:\n\n```python\ncontent = await page.evaluate('document.body.textContent', force_expr=True)\n```\n\nGet an element's `textContent`:\n\n```python\nelement = await page.querySelector('h1')\ntitle = await page.evaluate('(element) => element.textContent', element)\n```\n\n## Roadmap\n\nSee [projects](https://github.com/pyppeteer/pyppeteer/projects)\n\n## Credits\n\n###### This package was created with [Cookiecutter](https://github.com/audreyr/cookiecutter) and the [audreyr/cookiecutter-pypackage](https://github.com/audreyr/cookiecutter-pypackage) project template.\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = _build\n\n# User-friendly check for sphinx-build\nifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)\n$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)\nendif\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n# the i18n builder cannot share the environment and doctrees with the others\nI18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n\n.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext\n\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html       to make standalone HTML files\"\n\t@echo \"  dirhtml    to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml to make a single large HTML file\"\n\t@echo \"  pickle     to make pickle files\"\n\t@echo \"  json       to make JSON files\"\n\t@echo \"  htmlhelp   to make HTML files and a HTML help project\"\n\t@echo \"  qthelp     to make HTML files and a qthelp project\"\n\t@echo \"  devhelp    to make HTML files and a Devhelp project\"\n\t@echo \"  epub       to make an epub\"\n\t@echo \"  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf   to make LaTeX files and run them through pdflatex\"\n\t@echo \"  latexpdfja to make LaTeX files and run them through platex/dvipdfmx\"\n\t@echo \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  texinfo    to make Texinfo files\"\n\t@echo \"  info       to make Texinfo files and run them through makeinfo\"\n\t@echo \"  gettext    to make PO message catalogs\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\n\t@echo \"  xml        to make Docutils-native XML files\"\n\t@echo \"  pseudoxml  to make pseudoxml-XML files for display purposes\"\n\t@echo \"  linkcheck  to check all external links for integrity\"\n\t@echo \"  doctest    to run all doctests embedded in the documentation (if enabled)\"\n\nclean:\n\trm -rf $(BUILDDIR)/*\n\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/pyppeteer.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/pyppeteer.qhc\"\n\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/pyppeteer\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyppeteer\"\n\t@echo \"# devhelp\"\n\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\nlatexpdfja:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through platex and dvipdfmx...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\ntexinfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo\n\t@echo \"Build finished. The Texinfo files are in $(BUILDDIR)/texinfo.\"\n\t@echo \"Run \\`make' in that directory to run these through makeinfo\" \\\n\t      \"(use \\`make info' here to do that automatically).\"\n\ninfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo \"Running Texinfo files through makeinfo...\"\n\tmake -C $(BUILDDIR)/texinfo info\n\t@echo \"makeinfo finished; the Info files are in $(BUILDDIR)/texinfo.\"\n\ngettext:\n\t$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale\n\t@echo\n\t@echo \"Build finished. The message catalogs are in $(BUILDDIR)/locale.\"\n\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n\nxml:\n\t$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml\n\t@echo\n\t@echo \"Build finished. The XML files are in $(BUILDDIR)/xml.\"\n\npseudoxml:\n\t$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml\n\t@echo\n\t@echo \"Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml.\"\n"
  },
  {
    "path": "docs/_static/custom.css",
    "content": "h1.logo {\n  font-family: \"Raleway\";\n  font-weight: 500;\n}\n\na.headerlink {\n  color: rgba(0, 0, 0, 0.1);\n}\n\ndiv.sphinxsidebarwrapper p.blurb {\n  font-family: Lato, sans-serif;\n}\n\ndiv.sphinxsidebar li.toctree-l1 {\n  font-family: Lato, sans-serif;\n}\n\nbody {\n  background-color: #fafafa\n}\n\n.search-btn {\n  padding: 0 1em;\n  font-family: Lato, sans-serif;\n  font-weight: normal;\n  line-height: normal;\n  align-self: stretch;\n}\n"
  },
  {
    "path": "docs/_templates/layout.html",
    "content": "{% extends 'alabaster/layout.html' %}\n{% block extrahead %}\n  <!-- font -->\n  <link href='https://fonts.googleapis.com/css?family=Raleway:500' rel='stylesheet' type='text/css'>\n  <link href='https://fonts.googleapis.com/css?family=Lato:400,400italic' rel='stylesheet' type='text/css'>\n  <link href='https://fonts.googleapis.com/css?family=Noto+Serif:400,400italic,700,700italic' rel='stylesheet' type='text/css'>\n  <link href='https://fonts.googleapis.com/css?family=Ubuntu+Mono:400,400italic,700,700italic' rel='stylesheet' type='text/css'>\n  <!-- Style -->\n  {{ super() }}\n{% endblock %}\n"
  },
  {
    "path": "docs/changes.md",
    "content": ".. mdinclude:: ../CHANGES.md\n"
  },
  {
    "path": "docs/conf.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# pyppeteer documentation build configuration file, created by\n# sphinx-quickstart on Tue Jul  9 22:26:36 2013.\n#\n# This file is execfile()d with the current directory set to its\n# containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\nimport sys\nimport os\n\n# If extensions (or modules to document with autodoc) are in another\n# directory, add these directories to sys.path here. If the directory is\n# relative to the documentation root, use os.path.abspath to make it\n# absolute, like shown here.\n# sys.path.insert(0, os.path.abspath('.'))\n\n# Get the project root dir, which is the parent dir of this\ncwd = os.getcwd()\nproject_root = os.path.dirname(cwd)\n\n# Insert the project root dir as the first element in the PYTHONPATH.\n# This lets us ensure that the source package is imported, and that its\n# version is used.\nsys.path.insert(0, project_root)\n\nimport pyppeteer\n\n# -- General configuration ---------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n# needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.\nextensions = [\n    'sphinx.ext.autodoc',\n    'sphinx.ext.githubpages',\n    'sphinx.ext.viewcode',\n    # 'sphinx_autodoc_typehints',\n    'sphinxcontrib.asyncio',\n    'm2r',\n]\n\nprimary_domain = 'py'\ndefault_role = 'py:obj'\n# autodoc_member_order = 'bysource'\n# include class' and __init__'s docstring\n# autoclass_content = 'both'\n# autodoc_docstring_signature = False\nautodoc_default_flags = ['show-inheritance']\n\nsuppress_warnings = ['image.nonlocal_uri']\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\n\n# The suffix(es) of source filenames.\n# You can specify multiple suffix as a list of string:\nsource_suffix = ['.rst', '.md']\n\n# The encoding of source files.\n# source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# General information about the project.\nproject = 'Pyppeteer'\ncopyright = \"2017, Hiroyuki Takagi\"\n\n# The version info for the project you're documenting, acts as replacement\n# for |version| and |release|, also used in various other places throughout\n# the built documents.\n#\n# The short X.Y version.\nversion = pyppeteer.__version__\n# The full version, including alpha/beta/rc tags.\nrelease = pyppeteer.__version__\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n# language = None\n\n# There are two options for replacing |today|: either, you set today to\n# some non-false value, then it is used:\n# today = ''\n# Else, today_fmt is used as the format for a strftime call.\n# today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = ['_build']\n\n# The reST default role (used for this markup: `text`) to use for all\n# documents.\n# default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n# add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n# add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n# show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# A list of ignored prefixes for module index sorting.\n# modindex_common_prefix = []\n\n# If true, keep warnings as \"system message\" paragraphs in the built\n# documents.\n# keep_warnings = False\n\n\n# -- Options for HTML output -------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\nhtml_theme = 'alabaster'\n\n# Theme options are theme-specific and customize the look and feel of a\n# theme further.  For a list of options available for each theme, see the\n# documentation.\nhtml_theme_options = {\n    'description': ('Headless chrome/chromium automation library '\n                    '(unofficial port of puppeteer)'),\n    'github_user': 'miyakogi',\n    'github_repo': 'pyppeteer',\n    'github_banner': True,\n    'github_type': 'mark',\n    'github_count': False,\n    'font_family': '\"Charis SIL\", \"Noto Serif\", serif',\n    'head_font_family': 'Lato, sans-serif',\n    'code_font_family': '\"Code new roman\", \"Ubuntu Mono\", monospace',\n    'code_font_size': '1rem',\n}\n\n# Add any paths that contain custom themes here, relative to this directory.\n# html_theme_path = []\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n# html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as\n# html_title.\n# html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the\n# top of the sidebar.\n# html_logo = None\n\n# The name of an image file (within the static path) to use as favicon\n# of the docs.  This file should be a Windows icon file (.ico) being\n# 16x16 or 32x32 pixels large.\n# html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets)\n# here, relative to this directory. They are copied after the builtin\n# static files, so a file named \"default.css\" will overwrite the builtin\n# \"default.css\".\nhtml_static_path = ['_static']\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page\n# bottom, using the given strftime format.\n# html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n# html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\nhtml_sidebars = {\n    '**': [\n        'about.html',\n        'navigation.html',\n        'relations.html',\n        'searchbox.html',\n    ]\n}\n\n# Additional templates that should be rendered to pages, maps page names\n# to template names.\n# html_additional_pages = {}\n\n# If false, no module index is generated.\n# html_domain_indices = True\n\n# If false, no index is generated.\n# html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n# html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\n# html_show_sourcelink = True\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer.\n# Default is True.\n# html_show_sphinx = True\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer.\n# Default is True.\n# html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages\n# will contain a <link> tag referring to it.  The value of this option\n# must be the base URL from which the finished HTML is served.\n# html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n# html_file_suffix = None\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = 'pyppeteerdoc'\n\n# -- Options for LaTeX output ------------------------------------------\n\nlatex_elements = {\n    # The paper size ('letterpaper' or 'a4paper').\n    # 'papersize': 'letterpaper',\n\n    # The font size ('10pt', '11pt' or '12pt').\n    # 'pointsize': '10pt',\n\n    # Additional stuff for the LaTeX preamble.\n    # 'preamble': '',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title, author, documentclass\n# [howto/manual]).\nlatex_documents = [\n    ('index', 'pyppeteer.tex',\n     'pyppeteer Documentation',\n     'Hiroyuki Takagi', 'manual'),\n]\n\n# The name of an image file (relative to this directory) to place at\n# the top of the title page.\n# latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings\n# are parts, not chapters.\n# latex_use_parts = False\n\n# If true, show page references after internal links.\n# latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n# latex_show_urls = False\n\n# Documents to append as an appendix to all manuals.\n# latex_appendices = []\n\n# If false, no module index is generated.\n# latex_domain_indices = True\n\n\n# -- Options for manual page output ------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    ('index', 'pyppeteer',\n     'pyppeteer Documentation',\n     ['Hiroyuki Takagi'], 1)\n]\n\n# If true, show URL addresses after external links.\n# man_show_urls = False\n\n\n# -- Options for Texinfo output ----------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n    ('index', 'pyppeteer',\n     'pyppeteer Documentation',\n     'Hiroyuki Takagi',\n     'pyppeteer',\n     'One line description of project.',\n     'Miscellaneous'),\n]\n\n# Documents to append as an appendix to all manuals.\n# texinfo_appendices = []\n\n# If false, no module index is generated.\n# texinfo_domain_indices = True\n\n# How to display URL addresses: 'footnote', 'no', or 'inline'.\n# texinfo_show_urls = 'footnote'\n\n# If true, do not generate a @detailmenu in the \"Top\" node's menu.\n# texinfo_no_detailmenu = False\n"
  },
  {
    "path": "docs/index.md",
    "content": "Pyppeteer's documentation\n=========================\n\n.. mdinclude:: ../README.md\n\n\nContents\n--------\n\n.. toctree::\n   :maxdepth: 2\n\n   reference\n   changes\n\nIndices and tables\n==================\n\n* :ref:`genindex`\n* :ref:`modindex`\n* :ref:`search`\n"
  },
  {
    "path": "docs/make.bat",
    "content": "@ECHO OFF\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-build\n)\nset BUILDDIR=_build\nset ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .\nset I18NSPHINXOPTS=%SPHINXOPTS% .\nif NOT \"%PAPER%\" == \"\" (\n\tset ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%\n\tset I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%\n)\n\nif \"%1\" == \"\" goto help\n\nif \"%1\" == \"help\" (\n\t:help\n\techo.Please use `make ^<target^>` where ^<target^> is one of\n\techo.  html       to make standalone HTML files\n\techo.  dirhtml    to make HTML files named index.html in directories\n\techo.  singlehtml to make a single large HTML file\n\techo.  pickle     to make pickle files\n\techo.  json       to make JSON files\n\techo.  htmlhelp   to make HTML files and a HTML help project\n\techo.  qthelp     to make HTML files and a qthelp project\n\techo.  devhelp    to make HTML files and a Devhelp project\n\techo.  epub       to make an epub\n\techo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\n\techo.  text       to make text files\n\techo.  man        to make manual pages\n\techo.  texinfo    to make Texinfo files\n\techo.  gettext    to make PO message catalogs\n\techo.  changes    to make an overview over all changed/added/deprecated items\n\techo.  xml        to make Docutils-native XML files\n\techo.  pseudoxml  to make pseudoxml-XML files for display purposes\n\techo.  linkcheck  to check all external links for integrity\n\techo.  doctest    to run all doctests embedded in the documentation if enabled\n\tgoto end\n)\n\nif \"%1\" == \"clean\" (\n\tfor /d %%i in (%BUILDDIR%\\*) do rmdir /q /s %%i\n\tdel /q /s %BUILDDIR%\\*\n\tgoto end\n)\n\n\n%SPHINXBUILD% 2> nul\nif errorlevel 9009 (\n\techo.\n\techo.The 'sphinx-build' command was not found. Make sure you have Sphinx\n\techo.installed, then set the SPHINXBUILD environment variable to point\n\techo.to the full path of the 'sphinx-build' executable. Alternatively you\n\techo.may add the Sphinx directory to PATH.\n\techo.\n\techo.If you don't have Sphinx installed, grab it from\n\techo.http://sphinx-doc.org/\n\texit /b 1\n)\n\nif \"%1\" == \"html\" (\n\t%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/html.\n\tgoto end\n)\n\nif \"%1\" == \"dirhtml\" (\n\t%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.\n\tgoto end\n)\n\nif \"%1\" == \"singlehtml\" (\n\t%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.\n\tgoto end\n)\n\nif \"%1\" == \"pickle\" (\n\t%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the pickle files.\n\tgoto end\n)\n\nif \"%1\" == \"json\" (\n\t%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the JSON files.\n\tgoto end\n)\n\nif \"%1\" == \"htmlhelp\" (\n\t%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run HTML Help Workshop with the ^\n.hhp project file in %BUILDDIR%/htmlhelp.\n\tgoto end\n)\n\nif \"%1\" == \"qthelp\" (\n\t%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run \"qcollectiongenerator\" with the ^\n.qhcp project file in %BUILDDIR%/qthelp, like this:\n\techo.^> qcollectiongenerator %BUILDDIR%\\qthelp\\pyppeteer.qhcp\n\techo.To view the help file:\n\techo.^> assistant -collectionFile %BUILDDIR%\\qthelp\\pyppeteer.ghc\n\tgoto end\n)\n\nif \"%1\" == \"devhelp\" (\n\t%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished.\n\tgoto end\n)\n\nif \"%1\" == \"epub\" (\n\t%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The epub file is in %BUILDDIR%/epub.\n\tgoto end\n)\n\nif \"%1\" == \"latex\" (\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; the LaTeX files are in %BUILDDIR%/latex.\n\tgoto end\n)\n\nif \"%1\" == \"latexpdf\" (\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\n\tcd %BUILDDIR%/latex\n\tmake all-pdf\n\tcd %BUILDDIR%/..\n\techo.\n\techo.Build finished; the PDF files are in %BUILDDIR%/latex.\n\tgoto end\n)\n\nif \"%1\" == \"latexpdfja\" (\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\n\tcd %BUILDDIR%/latex\n\tmake all-pdf-ja\n\tcd %BUILDDIR%/..\n\techo.\n\techo.Build finished; the PDF files are in %BUILDDIR%/latex.\n\tgoto end\n)\n\nif \"%1\" == \"text\" (\n\t%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The text files are in %BUILDDIR%/text.\n\tgoto end\n)\n\nif \"%1\" == \"man\" (\n\t%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The manual pages are in %BUILDDIR%/man.\n\tgoto end\n)\n\nif \"%1\" == \"texinfo\" (\n\t%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.\n\tgoto end\n)\n\nif \"%1\" == \"gettext\" (\n\t%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The message catalogs are in %BUILDDIR%/locale.\n\tgoto end\n)\n\nif \"%1\" == \"changes\" (\n\t%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.The overview file is in %BUILDDIR%/changes.\n\tgoto end\n)\n\nif \"%1\" == \"linkcheck\" (\n\t%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Link check complete; look for any errors in the above output ^\nor in %BUILDDIR%/linkcheck/output.txt.\n\tgoto end\n)\n\nif \"%1\" == \"doctest\" (\n\t%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Testing of doctests in the sources finished, look at the ^\nresults in %BUILDDIR%/doctest/output.txt.\n\tgoto end\n)\n\nif \"%1\" == \"xml\" (\n\t%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The XML files are in %BUILDDIR%/xml.\n\tgoto end\n)\n\nif \"%1\" == \"pseudoxml\" (\n\t%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.\n\tgoto end\n)\n\n:end\n"
  },
  {
    "path": "docs/reference.md",
    "content": "API Reference\n=============\n\nCommands\n--------\n\n* ``pyppeteer-install``: Download and install chromium for pyppeteer.\n\nEnvironment Variables\n---------------------\n\n* ``$PYPPETEER_HOME``: Specify the directory to be used by pyppeteer.\n  Pyppeteer uses this directory for extracting downloaded Chromium, and for\n  making temporary user data directory.\n  Default location depends on platform:\n  * Windows: `C:\\Users\\<username>\\AppData\\Local\\pyppeteer`\n  * OS X: `/Users/<username>/Library/Application Support/pyppeteer`\n  * Linux: `/home/<username>/.local/share/pyppeteer`\n    * or in `$XDG_DATA_HOME/pyppeteer` if `$XDG_DATA_HOME` is defined.\n\n  Details see [appdirs](https://pypi.org/project/appdirs/)'s `user_data_dir`.\n\n* ``$PYPPETEER_DOWNLOAD_HOST``: Overwrite host part of URL that is used to\n  download Chromium. Defaults to ``https://storage.googleapis.com``.\n\n* ``$PYPPETEER_CHROMIUM_REVISION``: Specify a certain version of chromium you'd\n  like pyppeteer to use. Default value can be checked by\n  ``pyppeteer.__chromium_revision__``.\n\n* ``$PYPPETEER_NO_PROGRESS_BAR``: Suppress showing progress bar in chromium\n  download process. Acceptable values are ``1`` or ``true`` (case-insensitive).\n\n\nPyppeteer Main Module\n---------------------\n\n.. currentmodule:: pyppeteer\n\n.. autofunction:: launch\n.. autofunction:: connect\n.. autofunction:: defaultArgs\n.. autofunction:: executablePath\n\nBrowser Class\n-------------\n\n.. currentmodule:: pyppeteer.browser\n\n.. autoclass:: pyppeteer.browser.Browser\n   :members:\n   :exclude-members: create\n\nBrowserContext Class\n--------------------\n\n.. currentmodule:: pyppeteer.browser\n\n.. autoclass:: pyppeteer.browser.BrowserContext\n   :members:\n\nPage Class\n----------\n\n.. currentmodule:: pyppeteer.page\n\n.. autoclass:: pyppeteer.page.Page\n   :members:\n   :exclude-members: create\n\nWorker Class\n------------\n\n.. currentmodule:: pyppeteer.worker\n\n.. autoclass:: pyppeteer.worker.Worker\n   :members:\n\nKeyboard Class\n--------------\n\n.. currentmodule:: pyppeteer.input\n\n.. autoclass:: pyppeteer.input.Keyboard\n   :members:\n\nMouse Class\n-----------\n\n.. currentmodule:: pyppeteer.input\n\n.. autoclass:: pyppeteer.input.Mouse\n   :members:\n\nTracing Class\n-------------\n\n.. currentmodule:: pyppeteer.tracing\n\n.. autoclass:: pyppeteer.tracing.Tracing\n   :members:\n\nDialog Class\n------------\n\n.. currentmodule:: pyppeteer.dialog\n\n.. autoclass:: pyppeteer.dialog.Dialog\n   :members:\n\nConsoleMessage Class\n--------------------\n\n.. currentmodule:: pyppeteer.page\n\n.. autoclass:: pyppeteer.page.ConsoleMessage\n   :members:\n\nFrame Class\n-----------\n\n.. currentmodule:: pyppeteer.frame\n\n.. autoclass:: pyppeteer.frame_manager.Frame\n   :members:\n\nExecutionContext Class\n----------------------\n\n.. currentmodule:: pyppeteer.execution_context\n\n.. autoclass:: pyppeteer.execution_context.ExecutionContext\n   :members:\n\nJSHandle Class\n--------------\n\n.. autoclass:: pyppeteer.execution_context.JSHandle\n   :members:\n\nElementHandle Class\n-------------------\n\n.. currentmodule:: pyppeteer.element_handle\n\n.. autoclass:: pyppeteer.element_handle.ElementHandle\n   :members:\n\nRequest Class\n-------------\n\n.. currentmodule:: pyppeteer.network_manager\n\n.. autoclass:: pyppeteer.network_manager.Request\n   :members:\n\nResponse Class\n--------------\n\n.. currentmodule:: pyppeteer.network_manager\n\n.. autoclass:: pyppeteer.network_manager.Response\n   :members:\n\nTarget Class\n------------\n\n.. currentmodule:: pyppeteer.target\n\n.. autoclass:: pyppeteer.target.Target\n   :members:\n\nCDPSession Class\n----------------\n\n.. currentmodule:: pyppeteer.connection\n\n.. autoclass:: pyppeteer.connection.CDPSession\n   :members:\n\nCoverage Class\n--------------\n\n.. currentmodule:: pyppeteer.coverage\n\n.. autoclass:: pyppeteer.coverage.Coverage\n   :members:\n\nDebugging\n---------\n\nFor debugging, you can set `logLevel` option to `logging.DEBUG` for\n:func:`pyppeteer.launcher.launch` and :func:`pyppeteer.launcher.connect`\nfunctions. However, this option prints too many logs including SEND/RECV\nmessages of pyppeteer. In order to only show suppressed error messages, you\nshould set ``pyppeteer.DEBUG`` to ``True``.\n\nExample:\n\n```python\nimport asyncio\nimport pyppeteer\nfrom pyppeteer import launch\n\npyppeteer.DEBUG = True  # print suppressed errors as error log\n\nasync def main():\n    browser = await launch()\n    ...  # do something\n\nasyncio.get_event_loop().run_until_complete(main())\n```\n"
  },
  {
    "path": "docs/server.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nfrom os import path\nimport subprocess\n\nfrom livereload import Server\nfrom livereload import watcher\n\nwatcher.pyinotify = None  # disable pyinotify\n\ndocsdir = path.dirname(path.abspath(__file__))\nbuilddir = path.join(docsdir, '_build')\nbuild_cmd = [\n    'sphinx-build', '-q', '-j', 'auto', '-b', 'html',\n    '-d', path.join(builddir, 'doctrees'),\n    docsdir, path.join(builddir, 'html'),\n]\n\n\ndef cmd() -> None:\n    print('=== Sphinx Build Start ===')\n    subprocess.run(build_cmd, cwd=docsdir)\n    print('=== Sphinx Build done ===')\n\n\n# subprocess.run(['make', 'clean'], cwd=docsdir)\ncmd()\nserver = Server()\n\n\ndef docs(p: str) -> str:\n    return path.join(docsdir, p)\n\n\n# Watch documents\nserver.watch(docs('*.py'), cmd, delay=1)\nserver.watch(docs('*.md'), cmd, delay=1)\nserver.watch(docs('../*.md'), cmd, delay=1)\nserver.watch(docs('*.md'), cmd, delay=1)\nserver.watch(docs('*/*.md'), cmd, delay=1)\nserver.watch(docs('*/*/*.md'), cmd, delay=1)\n\n# Watch template/style\nserver.watch(docs('_templates/*.html'), cmd, delay=1)\nserver.watch(docs('_static/*.css'), cmd, delay=1)\nserver.watch(docs('_static/*.js'), cmd, delay=1)\n\n# Watch package\nserver.watch(docs('../pyppeteer/*.py'), cmd, delay=1)\nserver.watch(docs('../pyppeteer/*/*.py'), cmd, delay=1)\nserver.watch(docs('../pyppeteer/*/*/*.py'), cmd, delay=1)\n\nserver.serve(port=8889, root=docs('_build/html'), debug=True, restart_delay=1)\n"
  },
  {
    "path": "pyppeteer/__init__.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"Meta data for pyppeteer.\"\"\"\n\nimport logging\nimport os\n\nfrom appdirs import AppDirs\n\nfrom importlib.metadata import version\n\ntry:\n    __version__ = version(__name__)\nexcept Exception:\n    __version__ = None\n\n\n# old chrome version panic upon launching - this one may not match the base puppeteer version, but at least it launches\n__chromium_revision__ = '1181205'\n__base_puppeteer_version__ = 'v1.6.0'\n__pyppeteer_home__ = os.environ.get('PYPPETEER_HOME', AppDirs('pyppeteer').user_data_dir)  # type: str\nDEBUG = False\n\nfrom pyppeteer.launcher import connect, executablePath, launch, defaultArgs  # noqa: E402; noqa: E402\n\nversion = __version__\n\n__all__ = [\n    'connect',\n    'launch',\n    'executablePath',\n    'defaultArgs',\n    'version',\n]\n"
  },
  {
    "path": "pyppeteer/browser.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"Browser module.\"\"\"\n\nimport logging\nfrom subprocess import Popen\nfrom types import SimpleNamespace\nfrom typing import Any, Awaitable, Callable, Dict, List, Optional\n\nfrom pyee import EventEmitter\n\nfrom pyppeteer.connection import Connection\nfrom pyppeteer.errors import BrowserError\nfrom pyppeteer.page import Page\nfrom pyppeteer.target import Target\n\nlogger = logging.getLogger(__name__)\n\n\nclass Browser(EventEmitter):\n    \"\"\"Browser class.\n\n    A Browser object is created when pyppeteer connects to chrome, either\n    through :func:`~pyppeteer.launcher.launch` or\n    :func:`~pyppeteer.launcher.connect`.\n    \"\"\"\n\n    Events = SimpleNamespace(\n        TargetCreated='targetcreated',\n        TargetDestroyed='targetdestroyed',\n        TargetChanged='targetchanged',\n        Disconnected='disconnected',\n    )\n\n    def __init__(self, connection: Connection, contextIds: List[str],\n                 ignoreHTTPSErrors: bool, defaultViewport: Optional[Dict],\n                 process: Optional[Popen] = None,\n                 closeCallback: Callable[[], Awaitable[None]] = None,\n                 **kwargs: Any) -> None:\n        super().__init__()\n        self._ignoreHTTPSErrors = ignoreHTTPSErrors\n        self._defaultViewport = defaultViewport\n        self._process = process\n        self._screenshotTaskQueue: List = []\n        self._connection = connection\n        loop = self._connection._loop\n\n        def _dummy_callback() -> Awaitable[None]:\n            fut = loop.create_future()\n            fut.set_result(None)\n            return fut\n\n        if closeCallback:\n            self._closeCallback = closeCallback\n        else:\n            self._closeCallback = _dummy_callback\n\n        self._defaultContext = BrowserContext(self, None)\n        self._contexts: Dict[str, BrowserContext] = dict()\n        for contextId in contextIds:\n            self._contexts[contextId] = BrowserContext(self, contextId)\n\n        self._targets: Dict[str, Target] = dict()\n        self._connection.setClosedCallback(\n            lambda: self.emit(Browser.Events.Disconnected)\n        )\n        self._connection.on(\n            'Target.targetCreated',\n            lambda event: loop.create_task(self._targetCreated(event)),\n        )\n        self._connection.on(\n            'Target.targetDestroyed',\n            lambda event: loop.create_task(self._targetDestroyed(event)),\n        )\n        self._connection.on(\n            'Target.targetInfoChanged',\n            lambda event: loop.create_task(self._targetInfoChanged(event)),\n        )\n\n    @property\n    def process(self) -> Optional[Popen]:\n        \"\"\"Return process of this browser.\n\n        If browser instance is created by :func:`pyppeteer.launcher.connect`,\n        return ``None``.\n        \"\"\"\n        return self._process\n\n    async def createIncogniteBrowserContext(self) -> 'BrowserContext':\n        \"\"\"[Deprecated] Miss spelled method.\n\n        Use :meth:`createIncognitoBrowserContext` method instead.\n        \"\"\"\n        logger.warning(\n            'createIncogniteBrowserContext is deprecated. '\n            'Use createIncognitoBrowserContext instead.'\n        )\n        return await self.createIncognitoBrowserContext()\n\n    async def createIncognitoBrowserContext(self) -> 'BrowserContext':\n        \"\"\"Create a new incognito browser context.\n\n        This won't share cookies/cache with other browser contexts.\n\n        .. code::\n\n            browser = await launch()\n            # Create a new incognito browser context.\n            context = await browser.createIncognitoBrowserContext()\n            # Create a new page in a pristine context.\n            page = await context.newPage()\n            # Do stuff\n            await page.goto('https://example.com')\n            ...\n        \"\"\"\n        obj = await self._connection.send('Target.createBrowserContext')\n        browserContextId = obj['browserContextId']\n        context = BrowserContext(self, browserContextId)  # noqa: E501\n        self._contexts[browserContextId] = context\n        return context\n\n    @property\n    def browserContexts(self) -> List['BrowserContext']:\n        \"\"\"Return a list of all open browser contexts.\n\n        In a newly created browser, this will return a single instance of\n        ``[BrowserContext]``\n        \"\"\"\n        return [self._defaultContext] + [context for context in self._contexts.values()]  # noqa: E501\n\n    async def _disposeContext(self, contextId: str) -> None:\n        await self._connection.send('Target.disposeBrowserContext', {\n            'browserContextId': contextId,\n        })\n        self._contexts.pop(contextId, None)\n\n    @staticmethod\n    async def create(connection: Connection, contextIds: List[str],\n                     ignoreHTTPSErrors: bool, defaultViewport: Optional[Dict],\n                     process: Optional[Popen] = None,\n                     closeCallback: Callable[[], Awaitable[None]] = None,\n                     **kwargs: Any) -> 'Browser':\n        \"\"\"Create browser object.\"\"\"\n        browser = Browser(connection, contextIds, ignoreHTTPSErrors,\n                          defaultViewport, process, closeCallback)\n        await connection.send('Target.setDiscoverTargets', {'discover': True})\n        return browser\n\n    async def _targetCreated(self, event: Dict) -> None:\n        targetInfo = event['targetInfo']\n        browserContextId = targetInfo.get('browserContextId')\n\n        if browserContextId and browserContextId in self._contexts:\n            context = self._contexts[browserContextId]\n        else:\n            context = self._defaultContext\n\n        target = Target(\n            targetInfo,\n            context,\n            lambda: self._connection.createSession(targetInfo),\n            self._ignoreHTTPSErrors,\n            self._defaultViewport,\n            self._screenshotTaskQueue,\n            self._connection._loop,\n        )\n        if targetInfo['targetId'] in self._targets:\n            raise BrowserError('target should not exist before create.')\n        self._targets[targetInfo['targetId']] = target\n        if await target._initializedPromise:\n            self.emit(Browser.Events.TargetCreated, target)\n            context.emit(BrowserContext.Events.TargetCreated, target)\n\n    async def _targetDestroyed(self, event: Dict) -> None:\n        target = self._targets[event['targetId']]\n        del self._targets[event['targetId']]\n        target._closedCallback()\n        if await target._initializedPromise:\n            self.emit(Browser.Events.TargetDestroyed, target)\n            target.browserContext.emit(BrowserContext.Events.TargetDestroyed, target)  # noqa: E501\n        target._initializedCallback(False)\n\n    async def _targetInfoChanged(self, event: Dict) -> None:\n        target = self._targets.get(event['targetInfo']['targetId'])\n        if not target:\n            raise BrowserError('target should exist before targetInfoChanged')\n        previousURL = target.url\n        wasInitialized = target._isInitialized\n        target._targetInfoChanged(event['targetInfo'])\n        if wasInitialized and previousURL != target.url:\n            self.emit(Browser.Events.TargetChanged, target)\n            target.browserContext.emit(BrowserContext.Events.TargetChanged, target)  # noqa: E501\n\n    @property\n    def wsEndpoint(self) -> str:\n        \"\"\"Return websocket end point url.\"\"\"\n        return self._connection.url\n\n    async def newPage(self) -> Page:\n        \"\"\"Make new page on this browser and return its object.\"\"\"\n        return await self._defaultContext.newPage()\n\n    async def _createPageInContext(self, contextId: Optional[str]) -> Page:\n        options = {'url': 'about:blank'}\n        if contextId:\n            options['browserContextId'] = contextId\n\n        targetId = (await self._connection.send(\n            'Target.createTarget', options)).get('targetId')\n        target = self._targets.get(targetId)\n        if target is None:\n            raise BrowserError('Failed to create target for page.')\n        if not await target._initializedPromise:\n            raise BrowserError('Failed to create target for page.')\n        page = await target.page()\n        if page is None:\n            raise BrowserError('Failed to create page.')\n        return page\n\n    def targets(self) -> List[Target]:\n        \"\"\"Get a list of all active targets inside the browser.\n\n        In case of multiple browser contexts, the method will return a list\n        with all the targets in all browser contexts.\n        \"\"\"\n        return [target for target in self._targets.values()\n                if target._isInitialized]\n\n    async def pages(self) -> List[Page]:\n        \"\"\"Get all pages of this browser.\n\n        Non visible pages, such as ``\"background_page\"``, will not be listed\n        here. You can find then using :meth:`pyppeteer.target.Target.page`.\n\n        In case of multiple browser contexts, this method will return a list\n        with all the pages in all browser contexts.\n        \"\"\"\n        # Using asyncio.gather is better for performance\n        pages: List[Page] = list()\n        for context in self.browserContexts:\n            pages.extend(await context.pages())\n        return pages\n\n    async def version(self) -> str:\n        \"\"\"Get version of the browser.\"\"\"\n        version = await self._getVersion()\n        return version['product']\n\n    async def userAgent(self) -> str:\n        \"\"\"Return browser's original user agent.\n\n        .. note::\n            Pages can override browser user agent with\n            :meth:`pyppeteer.page.Page.setUserAgent`.\n        \"\"\"\n        version = await self._getVersion()\n        return version.get('userAgent', '')\n\n    async def close(self) -> None:\n        \"\"\"Close connections and terminate browser process.\"\"\"\n        await self._closeCallback()  # Launcher.killChrome()\n\n    async def disconnect(self) -> None:\n        \"\"\"Disconnect browser.\"\"\"\n        await self._connection.dispose()\n        for target in self._targets.values():\n            if not target._isInitialized:\n                target._initializedCallback(False)\n\n    def _getVersion(self) -> Awaitable:\n        return self._connection.send('Browser.getVersion')\n\n\nclass BrowserContext(EventEmitter):\n    \"\"\"BrowserContext provides multiple independent browser sessions.\n\n    When a browser is launched, it has a single BrowserContext used by default.\n    The method `browser.newPage()` creates a page in the default browser\n    context.\n\n    If a page opens another page, e.g. with a ``window.open`` call, the popup\n    will belong to the parent page's browser context.\n\n    Pyppeteer allows creation of \"incognito\" browser context with\n    ``browser.createIncognitoBrowserContext()`` method.\n    \"incognito\" browser contexts don't write any browser data to disk.\n\n    .. code::\n\n        # Create new incognito browser context\n        context = await browser.createIncognitoBrowserContext()\n        # Create a new page inside context\n        page = await context.newPage()\n        # ... do stuff with page ...\n        await page.goto('https://example.com')\n        # Dispose context once it's no longer needed\n        await context.close()\n    \"\"\"\n\n    Events = SimpleNamespace(\n        TargetCreated='targetcreated',\n        TargetDestroyed='targetdestroyed',\n        TargetChanged='targetchanged',\n    )\n\n    def __init__(self, browser: Browser, contextId: Optional[str]) -> None:\n        super().__init__()\n        self._browser = browser\n        self._id = contextId\n\n    def targets(self) -> List[Target]:\n        \"\"\"Return a list of all active targets inside the browser context.\"\"\"\n        targets = []\n        for target in self._browser.targets():\n            if target.browserContext == self:\n                targets.append(target)\n        return targets\n\n    async def pages(self) -> List[Page]:\n        \"\"\"Return list of all open pages.\n\n        Non-visible pages, such as ``\"background_page\"``, will not be listed\n        here. You can find them using :meth:`pyppeteer.target.Target.page`.\n        \"\"\"\n        # Using asyncio.gather is better for performance\n        pages = []\n        for target in self.targets():\n            if target.type == 'page':\n                page = await target.page()\n                if page:\n                    pages.append(page)\n        return pages\n\n    def isIncognite(self) -> bool:\n        \"\"\"[Deprecated] Miss spelled method.\n\n        Use :meth:`isIncognito` method instead.\n        \"\"\"\n        logger.warning(\n            'isIncognite is deprecated. '\n            'Use isIncognito instead.'\n        )\n        return self.isIncognito()\n\n    def isIncognito(self) -> bool:\n        \"\"\"Return whether BrowserContext is incognito.\n\n        The default browser context is the only non-incognito browser context.\n\n        .. note::\n            The default browser context cannot be closed.\n        \"\"\"\n        return bool(self._id)\n\n    async def newPage(self) -> Page:\n        \"\"\"Create a new page in the browser context.\"\"\"\n        return await self._browser._createPageInContext(self._id)\n\n    @property\n    def browser(self) -> Browser:\n        \"\"\"Return the browser this browser context belongs to.\"\"\"\n        return self._browser\n\n    async def close(self) -> None:\n        \"\"\"Close the browser context.\n\n        All the targets that belongs to the browser context will be closed.\n\n        .. note::\n            Only incognito browser context can be closed.\n        \"\"\"\n        if self._id is None:\n            raise BrowserError('Non-incognito profile cannot be closed')\n        await self._browser._disposeContext(self._id)\n"
  },
  {
    "path": "pyppeteer/chromium_downloader.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"Chromium download module.\"\"\"\n\nimport logging\nimport os\nimport stat\nimport sys\nfrom io import BytesIO\nfrom pathlib import Path\nfrom zipfile import ZipFile\n\nimport certifi\nimport urllib3\nfrom pyppeteer import __chromium_revision__, __pyppeteer_home__\nfrom tqdm import tqdm\n\nlogger = logging.getLogger(__name__)\n# add our own stream handler - we want some output here\nhandler = logging.StreamHandler()\nhandler.setFormatter(fmt=logging.Formatter(fmt=\"[{levelname}] {msg}\", style=\"{\"))\nhandler.setLevel(logging.INFO)\nlogger.setLevel(logging.INFO)\nlogger.addHandler(handler)\n\nDOWNLOADS_FOLDER = Path(__pyppeteer_home__) / 'local-chromium'\nDEFAULT_DOWNLOAD_HOST = 'https://storage.googleapis.com'\nDOWNLOAD_HOST = os.environ.get('PYPPETEER_DOWNLOAD_HOST', DEFAULT_DOWNLOAD_HOST)\nBASE_URL = f'{DOWNLOAD_HOST}/chromium-browser-snapshots'\n\nREVISION = os.environ.get('PYPPETEER_CHROMIUM_REVISION', __chromium_revision__)\n\nNO_PROGRESS_BAR = os.environ.get('PYPPETEER_NO_PROGRESS_BAR', '')\nif NO_PROGRESS_BAR.lower() in ('1', 'true'):\n    NO_PROGRESS_BAR = True  # type: ignore\n\nwindowsArchive = 'chrome-win'\n\ndownloadURLs = {\n    'linux': f'{BASE_URL}/Linux_x64/{REVISION}/chrome-linux.zip',\n    'mac': f'{BASE_URL}/Mac/{REVISION}/chrome-mac.zip',\n    'win32': f'{BASE_URL}/Win/{REVISION}/{windowsArchive}.zip',\n    'win64': f'{BASE_URL}/Win_x64/{REVISION}/{windowsArchive}.zip',\n}\n\nchromiumExecutable = {\n    'linux': DOWNLOADS_FOLDER / REVISION / 'chrome-linux' / 'chrome',\n    'mac': (DOWNLOADS_FOLDER / REVISION / 'chrome-mac' / 'Chromium.app' / 'Contents' / 'MacOS' / 'Chromium'),\n    'win32': DOWNLOADS_FOLDER / REVISION / windowsArchive / 'chrome.exe',\n    'win64': DOWNLOADS_FOLDER / REVISION / windowsArchive / 'chrome.exe',\n}\n\n\ndef current_platform() -> str:\n    \"\"\"Get current platform name by short string.\"\"\"\n    if sys.platform.startswith('linux'):\n        return 'linux'\n    elif sys.platform.startswith('darwin'):\n        return 'mac'\n    elif sys.platform.startswith('win') or sys.platform.startswith('msys') or sys.platform.startswith('cyg'):\n        if sys.maxsize > 2 ** 31 - 1:\n            return 'win64'\n        return 'win32'\n    raise OSError('Unsupported platform: ' + sys.platform)\n\n\ndef get_url() -> str:\n    \"\"\"Get chromium download url.\"\"\"\n    return downloadURLs[current_platform()]\n\n\ndef download_zip(url: str) -> BytesIO:\n    \"\"\"Download data from url.\"\"\"\n    logger.info('Starting Chromium download.')\n\n    with urllib3.PoolManager(cert_reqs='CERT_REQUIRED', ca_certs=certifi.where()) as http:\n        # Get data from url.\n        # set preload_content=False means using stream later.\n        r = http.request('GET', url, preload_content=False)\n        if r.status >= 400:\n            raise OSError(f'Chromium downloadable not found at {url}: ' f'Received {r.data.decode()}.\\n')\n\n        # 10 * 1024\n        _data = BytesIO()\n        if NO_PROGRESS_BAR:\n            for chunk in r.stream(10240):\n                _data.write(chunk)\n        else:\n            try:\n                total_length = int(r.headers['content-length'])\n            except (KeyError, ValueError, AttributeError):\n                total_length = 0\n            process_bar = tqdm(total=total_length, unit_scale=True, unit='b')\n            for chunk in r.stream(10240):\n                _data.write(chunk)\n                process_bar.update(len(chunk))\n            process_bar.close()\n\n    return _data\n\n\ndef extract_zip(data: BytesIO, path: Path) -> None:\n    \"\"\"Extract zipped data to path.\"\"\"\n    # On mac zipfile module cannot extract correctly, so use unzip instead.\n    logger.info('Beginning extraction')\n    if current_platform() == 'mac':\n        import subprocess\n        import shutil\n\n        zip_path = path / 'chrome.zip'\n        if not path.exists():\n            path.mkdir(parents=True)\n        with zip_path.open('wb') as f:\n            f.write(data.getvalue())\n        if not shutil.which('unzip'):\n            raise OSError('Failed to automatically extract chromium.' f'Please unzip {zip_path} manually.')\n        proc = subprocess.run(\n            ['unzip', str(zip_path)], cwd=str(path), stdout=subprocess.PIPE, stderr=subprocess.STDOUT,\n        )\n        if proc.returncode != 0:\n            logger.error(proc.stdout.decode())\n            raise OSError(f'Failed to unzip {zip_path}.')\n        if chromium_executable().exists() and zip_path.exists():\n            zip_path.unlink()\n    else:\n        with ZipFile(data) as zf:\n            zf.extractall(str(path))\n    exec_path = chromium_executable()\n    if not exec_path.exists():\n        raise IOError('Failed to extract chromium.')\n    exec_path.chmod(exec_path.stat().st_mode | stat.S_IXOTH | stat.S_IXGRP | stat.S_IXUSR)\n    logger.info(f'Chromium extracted to: {path}')\n\n\ndef download_chromium() -> None:\n    \"\"\"Download and extract chromium.\"\"\"\n    extract_zip(download_zip(get_url()), DOWNLOADS_FOLDER / REVISION)\n\n\ndef chromium_executable() -> Path:\n    \"\"\"Get path of the chromium executable.\"\"\"\n    return chromiumExecutable[current_platform()]\n\n\ndef check_chromium() -> bool:\n    \"\"\"Check if chromium is placed at correct path.\"\"\"\n    return chromium_executable().exists()\n"
  },
  {
    "path": "pyppeteer/command.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"Commands for Pyppeteer.\"\"\"\n\nimport logging\n\nfrom pyppeteer.chromium_downloader import check_chromium, download_chromium\n\n\ndef install() -> None:\n    \"\"\"Download chromium if not install.\"\"\"\n    if not check_chromium():\n        download_chromium()\n    else:\n        logging.getLogger(__name__).warning('chromium is already installed.')\n"
  },
  {
    "path": "pyppeteer/connection.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"Connection/Session management module.\"\"\"\n\nimport asyncio\nimport json\nimport logging\nfrom typing import Awaitable, Callable, Dict, Union, TYPE_CHECKING\n\nfrom pyee import EventEmitter\nimport websockets\nfrom websockets.legacy.client import connect as ws_connect\n\nfrom pyppeteer.errors import NetworkError\n\nif TYPE_CHECKING:\n    from typing import Optional  # noqa: F401\n\nlogger = logging.getLogger(__name__)\nlogger_connection = logging.getLogger(__name__ + '.Connection')\nlogger_session = logging.getLogger(__name__ + '.CDPSession')\n\n\nclass Connection(EventEmitter):\n    \"\"\"Connection management class.\"\"\"\n\n    def __init__(self, url: str, loop: asyncio.AbstractEventLoop,\n                 delay: int = 0) -> None:\n        \"\"\"Make connection.\n\n        :arg str url: WebSocket url to connect devtool.\n        :arg int delay: delay to wait before processing received messages.\n        \"\"\"\n        super().__init__()\n        self._url = url\n        self._lastId = 0\n        self._callbacks: Dict[int, asyncio.Future] = dict()\n        self._delay = delay / 1000\n        self._loop = loop\n        self._sessions: Dict[str, CDPSession] = dict()\n        self.connection: CDPSession\n        self._connected = False\n        self._ws = ws_connect(self._url, max_size=None, loop=self._loop, ping_interval=None, ping_timeout=None)\n        self._recv_fut = self._loop.create_task(self._recv_loop())\n        self._closeCallback: Optional[Callable[[], None]] = None\n\n    @property\n    def url(self) -> str:\n        \"\"\"Get connected WebSocket url.\"\"\"\n        return self._url\n\n    async def _recv_loop(self) -> None:\n        async with self._ws as connection:\n            self._connected = True\n            self.connection = connection\n            while self._connected:\n                try:\n                    resp = await self.connection.recv()\n                    if resp:\n                        await self._on_message(resp)\n                except (websockets.ConnectionClosed, ConnectionResetError):\n                    logger.info('connection closed')\n                    break\n                await asyncio.sleep(0)\n        if self._connected:\n            self._loop.create_task(self.dispose())\n\n    async def _async_send(self, msg: str, callback_id: int) -> None:\n        while not self._connected:\n            await asyncio.sleep(self._delay)\n        try:\n            await self.connection.send(msg)\n        except websockets.ConnectionClosed:\n            logger.error('connection unexpectedly closed')\n            callback = self._callbacks.get(callback_id, None)\n            if callback and not callback.done():\n                callback.set_result(None)\n                await self.dispose()\n\n    def send(self, method: str, params: dict = None) -> Awaitable:\n        \"\"\"Send message via the connection.\"\"\"\n        # Detect connection availability from the second transmission\n        if self._lastId and not self._connected:\n            raise ConnectionError('Connection is closed')\n        if params is None:\n            params = dict()\n        self._lastId += 1\n        _id = self._lastId\n        msg = json.dumps(dict(\n            id=_id,\n            method=method,\n            params=params,\n        ))\n        logger_connection.debug(f'SEND: {msg}')\n        self._loop.create_task(self._async_send(msg, _id))\n        callback = self._loop.create_future()\n        self._callbacks[_id] = callback\n        callback.error: Exception = NetworkError()  # type: ignore\n        callback.method: str = method  # type: ignore\n        return callback\n\n    def _on_response(self, msg: dict) -> None:\n        callback = self._callbacks.pop(msg.get('id', -1))\n        if msg.get('error'):\n            callback.set_exception(\n                _createProtocolError(\n                    callback.error,  # type: ignore\n                    callback.method,  # type: ignore\n                    msg\n                )\n            )\n        else:\n            callback.set_result(msg.get('result'))\n\n    def _on_query(self, msg: dict) -> None:\n        params = msg.get('params', {})\n        method = msg.get('method', '')\n        sessionId = params.get('sessionId')\n        if method == 'Target.receivedMessageFromTarget':\n            session = self._sessions.get(sessionId)\n            if session:\n                session._on_message(params.get('message'))\n        elif method == 'Target.detachedFromTarget':\n            session = self._sessions.get(sessionId)\n            if session:\n                session._on_closed()\n                del self._sessions[sessionId]\n        else:\n            self.emit(method, params)\n\n    def setClosedCallback(self, callback: Callable[[], None]) -> None:\n        \"\"\"Set closed callback.\"\"\"\n        self._closeCallback = callback\n\n    async def _on_message(self, message: str) -> None:\n        await asyncio.sleep(self._delay)\n        logger_connection.debug(f'RECV: {message}')\n        msg = json.loads(message)\n        if msg.get('id') in self._callbacks:\n            self._on_response(msg)\n        else:\n            self._on_query(msg)\n\n    async def _on_close(self) -> None:\n        if self._closeCallback:\n            self._closeCallback()\n            self._closeCallback = None\n\n        for cb in self._callbacks.values():\n            cb.set_exception(_rewriteError(\n                cb.error,  # type: ignore\n                f'Protocol error {cb.method}: Target closed.',  # type: ignore\n            ))\n        self._callbacks.clear()\n\n        for session in self._sessions.values():\n            session._on_closed()\n        self._sessions.clear()\n\n        # close connection\n        if hasattr(self, 'connection'):  # may not have connection\n            await self.connection.close()\n        if not self._recv_fut.done():\n            self._recv_fut.cancel()\n\n    async def dispose(self) -> None:\n        \"\"\"Close all connection.\"\"\"\n        self._connected = False\n        await self._on_close()\n\n    async def createSession(self, targetInfo: Dict) -> 'CDPSession':\n        \"\"\"Create new session.\"\"\"\n        resp = await self.send(\n            'Target.attachToTarget',\n            {'targetId': targetInfo['targetId']}\n        )\n        sessionId = resp.get('sessionId')\n        session = CDPSession(self, targetInfo['type'], sessionId, self._loop)\n        self._sessions[sessionId] = session\n        return session\n\n\nclass CDPSession(EventEmitter):\n    \"\"\"Chrome Devtools Protocol Session.\n\n    The :class:`CDPSession` instances are used to talk raw Chrome Devtools\n    Protocol:\n\n    * protocol methods can be called with :meth:`send` method.\n    * protocol events can be subscribed to with :meth:`on` method.\n\n    Documentation on DevTools Protocol can be found\n    `here <https://chromedevtools.github.io/devtools-protocol/>`__.\n    \"\"\"\n\n    def __init__(self, connection: Union[Connection, 'CDPSession'],\n                 targetType: str, sessionId: str,\n                 loop: asyncio.AbstractEventLoop) -> None:\n        \"\"\"Make new session.\"\"\"\n        super().__init__()\n        self._lastId = 0\n        self._callbacks: Dict[int, asyncio.Future] = {}\n        self._connection: Optional[Connection] = connection\n        self._targetType = targetType\n        self._sessionId = sessionId\n        self._sessions: Dict[str, CDPSession] = dict()\n        self._loop = loop\n\n    def send(self, method: str, params: dict = None) -> Awaitable:\n        \"\"\"Send message to the connected session.\n\n        :arg str method: Protocol method name.\n        :arg dict params: Optional method parameters.\n        \"\"\"\n        if not self._connection:\n            raise NetworkError(\n                f'Protocol Error ({method}): Session closed. Most likely the '\n                f'{self._targetType} has been closed.'\n            )\n        self._lastId += 1\n        _id = self._lastId\n        msg = json.dumps(dict(id=_id, method=method, params=params))\n        logger_session.debug(f'SEND: {msg}')\n\n        callback = self._loop.create_future()\n        self._callbacks[_id] = callback\n        callback.error: Exception = NetworkError()  # type: ignore\n        callback.method: str = method  # type: ignore\n        try:\n            self._connection.send('Target.sendMessageToTarget', {\n                'sessionId': self._sessionId,\n                'message': msg,\n            })\n        except Exception as e:\n            # The response from target might have been already dispatched\n            if _id in self._callbacks:\n                _callback = self._callbacks[_id]\n                del self._callbacks[_id]\n                _callback.set_exception(_rewriteError(\n                    _callback.error,  # type: ignore\n                    e.args[0],\n                ))\n        return callback\n\n    def _on_message(self, msg: str) -> None:  # noqa: C901\n        logger_session.debug(f'RECV: {msg}')\n        obj = json.loads(msg)\n        _id = obj.get('id')\n        if _id:\n            callback = self._callbacks.get(_id)\n            if callback:\n                del self._callbacks[_id]\n                if obj.get('error'):\n                    callback.set_exception(_createProtocolError(\n                        callback.error,  # type: ignore\n                        callback.method,  # type: ignore\n                        obj,\n                    ))\n                else:\n                    result = obj.get('result')\n                    if callback and not callback.done():\n                        callback.set_result(result)\n        else:\n            params = obj.get('params', {})\n            if obj.get('method') == 'Target.receivedMessageFromTarget':\n                session = self._sessions.get(params.get('sessionId'))\n                if session:\n                    session._on_message(params.get('message'))\n            elif obj.get('method') == 'Target.detachFromTarget':\n                sessionId = params.get('sessionId')\n                session = self._sessions.get(sessionId)\n                if session:\n                    session._on_closed()\n                    del self._sessions[sessionId]\n            self.emit(obj.get('method'), obj.get('params'))\n\n    async def detach(self) -> None:\n        \"\"\"Detach session from target.\n\n        Once detached, session won't emit any events and can't be used to send\n        messages.\n        \"\"\"\n        if not self._connection:\n            raise NetworkError('Connection already closed.')\n        await self._connection.send('Target.detachFromTarget',\n                                    {'sessionId': self._sessionId})\n\n    def _on_closed(self) -> None:\n        for cb in self._callbacks.values():\n            cb.set_exception(_rewriteError(\n                cb.error,  # type: ignore\n                f'Protocol error {cb.method}: Target closed.',  # type: ignore\n            ))\n        self._callbacks.clear()\n        self._connection = None\n\n    def _createSession(self, targetType: str, sessionId: str) -> 'CDPSession':\n        session = CDPSession(self, targetType, sessionId, self._loop)\n        self._sessions[sessionId] = session\n        return session\n\n\ndef _createProtocolError(error: Exception, method: str, obj: Dict\n                         ) -> Exception:\n    message = f'Protocol error ({method}): {obj[\"error\"][\"message\"]}'\n    if 'data' in obj['error']:\n        message += f' {obj[\"error\"][\"data\"]}'\n    return _rewriteError(error, message)\n\n\ndef _rewriteError(error: Exception, message: str) -> Exception:\n    error.args = (message, )\n    return error\n"
  },
  {
    "path": "pyppeteer/coverage.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"Coverage module.\"\"\"\n\nfrom functools import cmp_to_key\nimport logging\nfrom typing import Any, Dict, List\n\nfrom pyppeteer import helper\nfrom pyppeteer.connection import CDPSession\nfrom pyppeteer.errors import PageError\nfrom pyppeteer.execution_context import EVALUATION_SCRIPT_URL\nfrom pyppeteer.helper import debugError\nfrom pyppeteer.util import merge_dict\n\nlogger = logging.getLogger(__name__)\n\n\nclass Coverage(object):\n    \"\"\"Coverage class.\n\n    Coverage gathers information about parts of JavaScript and CSS that were\n    used by the page.\n\n    An example of using JavaScript and CSS coverage to get percentage of\n    initially executed code::\n\n        # Enable both JavaScript and CSS coverage\n        await page.coverage.startJSCoverage()\n        await page.coverage.startCSSCoverage()\n\n        # Navigate to page\n        await page.goto('https://example.com')\n        # Disable JS and CSS coverage and get results\n        jsCoverage = await page.coverage.stopJSCoverage()\n        cssCoverage = await page.coverage.stopCSSCoverage()\n        totalBytes = 0\n        usedBytes = 0\n        coverage = jsCoverage + cssCoverage\n        for entry in coverage:\n            totalBytes += len(entry['text'])\n            for range in entry['ranges']:\n                usedBytes += range['end'] - range['start'] - 1\n\n        print('Bytes used: {}%'.format(usedBytes / totalBytes * 100))\n    \"\"\"\n\n    def __init__(self, client: CDPSession) -> None:\n        self._jsCoverage = JSCoverage(client)\n        self._cssCoverage = CSSCoverage(client)\n\n    async def startJSCoverage(self, options: Dict = None, **kwargs: Any\n                              ) -> None:\n        \"\"\"Start JS coverage measurement.\n\n        Available options are:\n\n        * ``resetOnNavigation`` (bool): Whether to reset coverage on every\n          navigation. Defaults to ``True``.\n        * ``reportAnonymousScript`` (bool): Whether anonymous script generated\n          by the page should be reported. Defaults to ``False``.\n\n        .. note::\n            Anonymous scripts are ones that don't have an associated url. These\n            are scripts that are dynamically created on the page using ``eval``\n            of ``new Function``. If ``reportAnonymousScript`` is set to\n            ``True``, anonymous scripts will have\n            ``__pyppeteer_evaluation_script__`` as their url.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n        await self._jsCoverage.start(options)\n\n    async def stopJSCoverage(self) -> List:\n        \"\"\"Stop JS coverage measurement and get result.\n\n        Return list of coverage reports for all scripts. Each report includes:\n\n        * ``url`` (str): Script url.\n        * ``text`` (str): Script content.\n        * ``ranges`` (List[Dict]): Script ranges that were executed. Ranges are\n          sorted and non-overlapping.\n\n          * ``start`` (int): A start offset in text, inclusive.\n          * ``end`` (int): An end offset in text, exclusive.\n\n        .. note::\n           JavaScript coverage doesn't include anonymous scripts by default.\n           However, scripts with sourceURLs are reported.\n        \"\"\"\n        return await self._jsCoverage.stop()\n\n    async def startCSSCoverage(self, options: Dict = None, **kwargs: Any\n                               ) -> None:\n        \"\"\"Start CSS coverage measurement.\n\n        Available options are:\n\n        * ``resetOnNavigation`` (bool): Whether to reset coverage on every\n          navigation. Defaults to ``True``.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n        await self._cssCoverage.start(options)\n\n    async def stopCSSCoverage(self) -> List:\n        \"\"\"Stop CSS coverage measurement and get result.\n\n        Return list of coverage reports for all non-anonymous scripts. Each\n        report includes:\n\n        * ``url`` (str): StyleSheet url.\n        * ``text`` (str): StyleSheet content.\n        * ``ranges`` (List[Dict]): StyleSheet ranges that were executed. Ranges\n          are sorted and non-overlapping.\n\n          * ``start`` (int): A start offset in text, inclusive.\n          * ``end`` (int): An end offset in text, exclusive.\n\n        .. note::\n           CSS coverage doesn't include dynamically injected style tags without\n           sourceURLs (but currently includes... to be fixed).\n        \"\"\"\n        return await self._cssCoverage.stop()\n\n\nclass JSCoverage(object):\n    \"\"\"JavaScript Coverage class.\"\"\"\n\n    def __init__(self, client: CDPSession) -> None:\n        self._client = client\n        self._enabled = False\n        self._scriptURLs: Dict = dict()\n        self._scriptSources: Dict = dict()\n        self._eventListeners: List = list()\n        self._resetOnNavigation = False\n\n    async def start(self, options: Dict = None, **kwargs: Any) -> None:\n        \"\"\"Start coverage measurement.\"\"\"\n        options = merge_dict(options, kwargs)\n        if self._enabled:\n            raise PageError('JSCoverage is always enabled.')\n        self._resetOnNavigation = (True if 'resetOnNavigation' not in options\n                                   else bool(options['resetOnNavigation']))\n        self._reportAnonymousScript = bool(options.get('reportAnonymousScript'))  # noqa: E501\n        self._enabled = True\n        self._scriptURLs.clear()\n        self._scriptSources.clear()\n        self._eventListeners = [\n            helper.addEventListener(\n                self._client, 'Debugger.scriptParsed',\n                lambda e: self._client._loop.create_task(\n                    self._onScriptParsed(e))),\n            helper.addEventListener(\n                self._client, 'Runtime.executionContextsCleared',\n                self._onExecutionContextsCleared),\n        ]\n        await self._client.send('Profiler.enable')\n        await self._client.send('Profiler.startPreciseCoverage',\n                                {'callCount': False, 'detailed': True})\n        await self._client.send('Debugger.enable')\n        await self._client.send('Debugger.setSkipAllPauses', {'skip': True})\n\n    def _onExecutionContextsCleared(self, event: Dict) -> None:\n        if not self._resetOnNavigation:\n            return\n        self._scriptURLs.clear()\n        self._scriptSources.clear()\n\n    async def _onScriptParsed(self, event: Dict) -> None:\n        # Ignore pyppeteer-injected scripts\n        if event.get('url') == EVALUATION_SCRIPT_URL:\n            return\n        # Ignore other anonymous scripts unless the reportAnonymousScript\n        # option is True\n        if not event.get('url') and not self._reportAnonymousScript:\n            return\n\n        scriptId = event.get('scriptId')\n        url = event.get('url')\n        if not url and self._reportAnonymousScript:\n            url = f'debugger://VM{scriptId}'\n        try:\n            response = await self._client.send(\n                'Debugger.getScriptSource',\n                {'scriptId': scriptId}\n            )\n            self._scriptURLs[scriptId] = url\n            self._scriptSources[scriptId] = response.get('scriptSource')\n        except Exception as e:\n            # This might happen if the page has already navigated away.\n            debugError(logger, e)\n\n    async def stop(self) -> List:\n        \"\"\"Stop coverage measurement and return results.\"\"\"\n        if not self._enabled:\n            raise PageError('JSCoverage is not enabled.')\n        self._enabled = False\n\n        result = await self._client.send('Profiler.takePreciseCoverage')\n        await self._client.send('Profiler.stopPreciseCoverage')\n        await self._client.send('Profiler.disable')\n        await self._client.send('Debugger.disable')\n        helper.removeEventListeners(self._eventListeners)\n\n        coverage: List = []\n        for entry in result.get('result', []):\n            url = self._scriptURLs.get(entry.get('scriptId'))\n            text = self._scriptSources.get(entry.get('scriptId'))\n            if text is None or url is None:\n                continue\n            flattenRanges: List = []\n            for func in entry.get('functions', []):\n                flattenRanges.extend(func.get('ranges', []))\n            ranges = convertToDisjointRanges(flattenRanges)\n            coverage.append({'url': url, 'ranges': ranges, 'text': text})\n        return coverage\n\n\nclass CSSCoverage(object):\n    \"\"\"CSS Coverage class.\"\"\"\n\n    def __init__(self, client: CDPSession) -> None:\n        self._client = client\n        self._enabled = False\n        self._stylesheetURLs: Dict = dict()\n        self._stylesheetSources: Dict = dict()\n        self._eventListeners: List = []\n        self._resetOnNavigation = False\n\n    async def start(self, options: Dict = None, **kwargs: Any) -> None:\n        \"\"\"Start coverage measurement.\"\"\"\n        options = merge_dict(options, kwargs)\n        if self._enabled:\n            raise PageError('CSSCoverage is already enabled.')\n        self._resetOnNavigation = (True if 'resetOnNavigation' not in options\n                                   else bool(options['resetOnNavigation']))\n        self._enabled = True\n        self._stylesheetURLs.clear()\n        self._stylesheetSources.clear()\n        self._eventListeners = [\n            helper.addEventListener(\n                self._client, 'CSS.styleSheetAdded',\n                lambda e: self._client._loop.create_task(\n                    self._onStyleSheet(e))),\n            helper.addEventListener(\n                self._client, 'Runtime.executionContextsCleared',\n                self._onExecutionContextsCleared),\n        ]\n        await self._client.send('DOM.enable')\n        await self._client.send('CSS.enable')\n        await self._client.send('CSS.startRuleUsageTracking')\n\n    def _onExecutionContextsCleared(self, event: Dict) -> None:\n        if not self._resetOnNavigation:\n            return\n        self._stylesheetURLs.clear()\n        self._stylesheetSources.clear()\n\n    async def _onStyleSheet(self, event: Dict) -> None:\n        header = event.get('header', {})\n        # Ignore anonymous scripts\n        if not header.get('sourceURL'):\n            return\n        try:\n            response = await self._client.send(\n                'CSS.getStyleSheetText',\n                {'styleSheetId': header['styleSheetId']}\n            )\n            self._stylesheetURLs[header['styleSheetId']] = header['sourceURL']\n            self._stylesheetSources[header['styleSheetId']] = response['text']\n        except Exception as e:\n            # This might happen if the page has already navigated away.\n            debugError(logger, e)\n\n    async def stop(self) -> List:\n        \"\"\"Stop coverage measurement and return results.\"\"\"\n        if not self._enabled:\n            raise PageError('CSSCoverage is not enabled.')\n        self._enabled = False\n        result = await self._client.send('CSS.stopRuleUsageTracking')\n        await self._client.send('CSS.disable')\n        await self._client.send('DOM.disable')\n        helper.removeEventListeners(self._eventListeners)\n\n        # aggregate by styleSheetId\n        styleSheetIdToCoverage: Dict = {}\n        for entry in result['ruleUsage']:\n            ranges = styleSheetIdToCoverage.get(entry['styleSheetId'])\n            if not ranges:\n                ranges = []\n                styleSheetIdToCoverage[entry['styleSheetId']] = ranges\n            ranges.append({\n                'startOffset': entry['startOffset'],\n                'endOffset': entry['endOffset'],\n                'count': 1 if entry['used'] else 0\n            })\n\n        coverage = []\n        for styleSheetId in self._stylesheetURLs:\n            url = self._stylesheetURLs.get(styleSheetId)\n            text = self._stylesheetSources.get(styleSheetId)\n            ranges = convertToDisjointRanges(\n                styleSheetIdToCoverage.get(styleSheetId, [])\n            )\n            coverage.append({'url': url, 'ranges': ranges, 'text': text})\n\n        return coverage\n\n\ndef convertToDisjointRanges(nestedRanges: List[Any]  # noqa: C901\n                            ) -> List[Any]:\n    \"\"\"Convert ranges.\"\"\"\n    points: List = []\n    for nested_range in nestedRanges:\n        points.append({'offset': nested_range['startOffset'], 'type': 0,\n                       'range': nested_range})\n        points.append({'offset': nested_range['endOffset'], 'type': 1,\n                       'range': nested_range})\n\n    # Sort points to form a valid parenthesis sequence.\n    def _sort_func(a: Dict, b: Dict) -> int:\n        # Sort with increasing offsets.\n        if a['offset'] != b['offset']:\n            return a['offset'] - b['offset']\n        # All \"end\" points should go before \"start\" points.\n        if a['type'] != b['type']:\n            return b['type'] - a['type']\n        aLength = a['range']['endOffset'] - a['range']['startOffset']\n        bLength = b['range']['endOffset'] - b['range']['startOffset']\n        # For two \"start\" points, the one with longer range goes first.\n        if a['type'] == 0:\n            return bLength - aLength\n        # For two \"end\" points, the one with shorter range goes first.\n        return aLength - bLength\n\n    points.sort(key=cmp_to_key(_sort_func))\n\n    hitCountStack: List[int] = []\n    results: List[Dict] = []\n    lastOffset = 0\n    # Run scanning line to intersect all ranges.\n    for point in points:\n        if (hitCountStack and\n                lastOffset < point['offset'] and\n                hitCountStack[len(hitCountStack) - 1] > 0):\n            lastResult = results[-1] if results else None\n            if lastResult and lastResult['end'] == lastOffset:\n                lastResult['end'] = point['offset']\n            else:\n                results.append({'start': lastOffset, 'end': point['offset']})\n        lastOffset = point['offset']\n        if point['type'] == 0:\n            hitCountStack.append(point['range']['count'])\n        else:\n            hitCountStack.pop()\n    # Filter out empty ranges.\n    return [range for range in results if range['end'] - range['start'] > 1]\n"
  },
  {
    "path": "pyppeteer/dialog.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"Dialog module.\"\"\"\n\nfrom types import SimpleNamespace\n\nfrom pyppeteer.connection import CDPSession\n\n\nclass Dialog(object):\n    \"\"\"Dialog class.\n\n    Dialog objects are dispatched by page via the ``dialog`` event.\n\n    An example of using ``Dialog`` class:\n\n    .. code::\n\n        browser = await launch()\n        page = await browser.newPage()\n\n        async def close_dialog(dialog):\n            print(dialog.message)\n            await dialog.dismiss()\n            await browser.close()\n\n        page.on(\n            'dialog',\n            lambda dialog: asyncio.ensure_future(close_dialog(dialog))\n        )\n        await page.evaluate('() => alert(\"1\")')\n    \"\"\"\n\n    Type = SimpleNamespace(\n        Alert='alert',\n        BeforeUnload='beforeunload',\n        Confirm='confirm',\n        Prompt='prompt',\n    )\n\n    def __init__(self, client: CDPSession, type: str, message: str,\n                 defaultValue: str = '') -> None:\n        self._client = client\n        self._type = type\n        self._message = message\n        self._handled = False\n        self._defaultValue = defaultValue\n\n    @property\n    def type(self) -> str:\n        \"\"\"Get dialog type.\n\n        One of ``alert``, ``beforeunload``, ``confirm``, or ``prompt``.\n        \"\"\"\n        return self._type\n\n    @property\n    def message(self) -> str:\n        \"\"\"Get dialog message.\"\"\"\n        return self._message\n\n    @property\n    def defaultValue(self) -> str:\n        \"\"\"If dialog is prompt, get default prompt value.\n\n        If dialog is not prompt, return empty string (``''``).\n        \"\"\"\n        return self._defaultValue\n\n    async def accept(self, promptText: str = '') -> None:\n        \"\"\"Accept the dialog.\n\n        * ``promptText`` (str): A text to enter in prompt. If the dialog's type\n          is not prompt, this does not cause any effect.\n        \"\"\"\n        self._handled = True\n        await self._client.send('Page.handleJavaScriptDialog', {\n            'accept': True,\n            'promptText': promptText,\n        })\n\n    async def dismiss(self) -> None:\n        \"\"\"Dismiss the dialog.\"\"\"\n        self._handled = True\n        await self._client.send('Page.handleJavaScriptDialog', {\n            'accept': False,\n        })\n"
  },
  {
    "path": "pyppeteer/element_handle.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"Element handle module.\"\"\"\n\nimport copy\nimport logging\nimport math\nimport os.path\nfrom typing import Any, Dict, List, Optional, TYPE_CHECKING\n\nfrom pyppeteer.connection import CDPSession\nfrom pyppeteer.execution_context import ExecutionContext, JSHandle\nfrom pyppeteer.errors import ElementHandleError, NetworkError\nfrom pyppeteer.helper import debugError\nfrom pyppeteer.util import merge_dict\n\nif TYPE_CHECKING:\n    from pyppeteer.frame_manager import Frame, FrameManager  # noqa: F401\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass ElementHandle(JSHandle):\n    \"\"\"ElementHandle class.\n\n    This class represents an in-page DOM element. ElementHandle can be created\n    by the :meth:`pyppeteer.page.Page.querySelector` method.\n\n    ElementHandle prevents DOM element from garbage collection unless the\n    handle is disposed. ElementHandles are automatically disposed when their\n    origin frame gets navigated.\n\n    ElementHandle isinstance can be used as arguments in\n    :meth:`pyppeteer.page.Page.querySelectorEval` and\n    :meth:`pyppeteer.page.Page.evaluate` methods.\n    \"\"\"\n\n    def __init__(self, context: ExecutionContext, client: CDPSession,\n                 remoteObject: dict, page: Any,\n                 frameManager: 'FrameManager') -> None:\n        super().__init__(context, client, remoteObject)\n        self._client = client\n        self._remoteObject = remoteObject\n        self._page = page\n        self._frameManager = frameManager\n        self._disposed = False\n\n    def asElement(self) -> 'ElementHandle':\n        \"\"\"Return this ElementHandle.\"\"\"\n        return self\n\n    async def contentFrame(self) -> Optional['Frame']:\n        \"\"\"Return the content frame for the element handle.\n\n        Return ``None`` if this handle is not referencing iframe.\n        \"\"\"\n        nodeInfo = await self._client.send('DOM.describeNode', {\n            'objectId': self._remoteObject.get('objectId'),\n        })\n        node_obj = nodeInfo.get('node', {})\n        if not isinstance(node_obj.get('frameId'), str):\n            return None\n        return self._frameManager.frame(node_obj['frameId'])\n\n    async def _scrollIntoViewIfNeeded(self) -> None:\n        error = await self.executionContext.evaluate('''\n            async (element, pageJavascriptEnabled) => {\n                if (!element.isConnected)\n                    return 'Node is detached from document';\n                if (element.nodeType !== Node.ELEMENT_NODE)\n                    return 'Node is not of type HTMLElement';\n                // force-scroll if page's javascript is disabled.\n                if (!pageJavascriptEnabled) {\n                    element.scrollIntoView({\n                        block: 'center',\n                        inline: 'center',\n                        behavior: 'instant',\n                    });\n                    return false;\n                }\n                const visibleRatio = await new Promise(resolve => {\n                    const observer = new IntersectionObserver(entries => {\n                        resolve(entries[0].intersectionRatio);\n                        observer.disconnect();\n                    });\n                    observer.observe(element);\n                });\n                if (visibleRatio !== 1.0)\n                    element.scrollIntoView({\n                        block: 'center',\n                        inline: 'center',\n                        behavior: 'instant',\n                    });\n                return false;\n            }''', self, self._page._javascriptEnabled)\n        if error:\n            raise ElementHandleError(error)\n\n    async def _clickablePoint(self) -> Dict[str, float]:  # noqa: C901\n        result = None\n        try:\n            result = await self._client.send('DOM.getContentQuads', {\n                'objectId': self._remoteObject.get('objectId'),\n            })\n        except Exception as e:\n            debugError(logger, e)\n\n        if not result or not result.get('quads'):\n            raise ElementHandleError(\n                'Node is either not visible or not an HTMLElement')\n\n        quads = []\n        for _quad in result.get('quads'):\n            _q = self._fromProtocolQuad(_quad)\n            if _computeQuadArea(_q) > 1:\n                quads.append(_q)\n        if not quads:\n            raise ElementHandleError(\n                'Node is either not visible or not an HTMLElement')\n\n        quad = quads[0]\n        x = 0\n        y = 0\n        for point in quad:\n            x += point['x']\n            y += point['y']\n        return {'x': x / 4, 'y': y / 4}\n\n    async def _getBoxModel(self) -> Optional[Dict]:\n        try:\n            result: Optional[Dict] = await self._client.send(\n                'DOM.getBoxModel',\n                {'objectId': self._remoteObject.get('objectId')},\n            )\n        except NetworkError as e:\n            debugError(logger, e)\n            result = None\n        return result\n\n    def _fromProtocolQuad(self, quad: List[int]) -> List[Dict[str, int]]:\n        return [\n            {'x': quad[0], 'y': quad[1]},\n            {'x': quad[2], 'y': quad[3]},\n            {'x': quad[4], 'y': quad[5]},\n            {'x': quad[6], 'y': quad[7]},\n        ]\n\n    async def hover(self) -> None:\n        \"\"\"Move mouse over to center of this element.\n\n        If needed, this method scrolls element into view. If this element is\n        detached from DOM tree, the method raises an ``ElementHandleError``.\n        \"\"\"\n        await self._scrollIntoViewIfNeeded()\n        obj = await self._clickablePoint()\n        x = obj.get('x', 0)\n        y = obj.get('y', 0)\n        await self._page.mouse.move(x, y)\n\n    async def click(self, options: dict = None, **kwargs: Any) -> None:\n        \"\"\"Click the center of this element.\n\n        If needed, this method scrolls element into view. If the element is\n        detached from DOM, the method raises ``ElementHandleError``.\n\n        ``options`` can contain the following fields:\n\n        * ``button`` (str): ``left``, ``right``, of ``middle``, defaults to\n          ``left``.\n        * ``clickCount`` (int): Defaults to 1.\n        * ``delay`` (int|float): Time to wait between ``mousedown`` and\n          ``mouseup`` in milliseconds. Defaults to 0.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n        await self._scrollIntoViewIfNeeded()\n        obj = await self._clickablePoint()\n        x = obj.get('x', 0)\n        y = obj.get('y', 0)\n        await self._page.mouse.click(x, y, options)\n\n    async def uploadFile(self, *filePaths: str) -> dict:\n        \"\"\"Upload files.\"\"\"\n        files = [os.path.abspath(p) for p in filePaths]\n        objectId = self._remoteObject.get('objectId')\n        return await self._client.send(\n            'DOM.setFileInputFiles',\n            {'objectId': objectId, 'files': files}\n        )\n\n    async def tap(self) -> None:\n        \"\"\"Tap the center of this element.\n\n        If needed, this method scrolls element into view. If the element is\n        detached from DOM, the method raises ``ElementHandleError``.\n        \"\"\"\n        await self._scrollIntoViewIfNeeded()\n        center = await self._clickablePoint()\n        x = center.get('x', 0)\n        y = center.get('y', 0)\n        await self._page.touchscreen.tap(x, y)\n\n    async def focus(self) -> None:\n        \"\"\"Focus on this element.\"\"\"\n        await self.executionContext.evaluate(\n            'element => element.focus()', self)\n\n    async def type(self, text: str, options: Dict = None, **kwargs: Any\n                   ) -> None:\n        \"\"\"Focus the element and then type text.\n\n        Details see :meth:`pyppeteer.input.Keyboard.type` method.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n        await self.focus()\n        await self._page.keyboard.type(text, options)\n\n    async def press(self, key: str, options: Dict = None, **kwargs: Any\n                    ) -> None:\n        \"\"\"Press ``key`` onto the element.\n\n        This method focuses the element, and then uses\n        :meth:`pyppeteer.input.keyboard.down` and\n        :meth:`pyppeteer.input.keyboard.up`.\n\n        :arg str key: Name of key to press, such as ``ArrowLeft``.\n\n        This method accepts the following options:\n\n        * ``text`` (str): If specified, generates an input event with this\n          text.\n        * ``delay`` (int|float): Time to wait between ``keydown`` and\n          ``keyup``. Defaults to 0.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n        await self.focus()\n        await self._page.keyboard.press(key, options)\n\n    async def boundingBox(self) -> Optional[Dict[str, float]]:\n        \"\"\"Return bounding box of this element.\n\n        If the element is not visible, return ``None``.\n\n        This method returns dictionary of bounding box, which contains:\n\n        * ``x`` (int): The X coordinate of the element in pixels.\n        * ``y`` (int): The Y coordinate of the element in pixels.\n        * ``width`` (int): The width of the element in pixels.\n        * ``height`` (int): The height of the element in pixels.\n        \"\"\"\n        result = await self._getBoxModel()\n\n        if not result:\n            return None\n\n        quad = result['model']['border']\n        x = min(quad[0], quad[2], quad[4], quad[6])\n        y = min(quad[1], quad[3], quad[5], quad[7])\n        width = max(quad[0], quad[2], quad[4], quad[6]) - x\n        height = max(quad[1], quad[3], quad[5], quad[7]) - y\n        return {'x': x, 'y': y, 'width': width, 'height': height}\n\n    async def boxModel(self) -> Optional[Dict]:\n        \"\"\"Return boxes of element.\n\n        Return ``None`` if element is not visible. Boxes are represented as an\n        list of points; each Point is a dictionary ``{x, y}``. Box points are\n        sorted clock-wise.\n\n        Returned value is a dictionary with the following fields:\n\n        * ``content`` (List[Dict]): Content box.\n        * ``padding`` (List[Dict]): Padding box.\n        * ``border`` (List[Dict]): Border box.\n        * ``margin`` (List[Dict]): Margin box.\n        * ``width`` (int): Element's width.\n        * ``height`` (int): Element's height.\n        \"\"\"\n        result = await self._getBoxModel()\n\n        if not result:\n            return None\n\n        model = result.get('model', {})\n        return {\n            'content': self._fromProtocolQuad(model.get('content')),\n            'padding': self._fromProtocolQuad(model.get('padding')),\n            'border': self._fromProtocolQuad(model.get('border')),\n            'margin': self._fromProtocolQuad(model.get('margin')),\n            'width': model.get('width'),\n            'height': model.get('height'),\n        }\n\n    async def screenshot(self, options: Dict = None, **kwargs: Any) -> bytes:\n        \"\"\"Take a screenshot of this element.\n\n        If the element is detached from DOM, this method raises an\n        ``ElementHandleError``.\n\n        Available options are same as :meth:`pyppeteer.page.Page.screenshot`.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n\n        needsViewportReset = False\n        boundingBox = await self.boundingBox()\n        if not boundingBox:\n            raise ElementHandleError(\n                'Node is either not visible or not an HTMLElement')\n\n        original_viewport = copy.deepcopy(self._page.viewport)\n\n        if (boundingBox['width'] > original_viewport['width'] or\n                boundingBox['height'] > original_viewport['height']):\n            newViewport = {\n                'width': max(\n                    original_viewport['width'],\n                    math.ceil(boundingBox['width'])\n                ),\n                'height': max(\n                    original_viewport['height'],\n                    math.ceil(boundingBox['height'])\n                ),\n            }\n            new_viewport = copy.deepcopy(original_viewport)\n            new_viewport.update(newViewport)\n            await self._page.setViewport(new_viewport)\n            needsViewportReset = True\n\n        await self._scrollIntoViewIfNeeded()\n        boundingBox = await self.boundingBox()\n        if not boundingBox:\n            raise ElementHandleError(\n                'Node is either not visible or not an HTMLElement')\n\n        _obj = await self._client.send('Page.getLayoutMetrics')\n        pageX = _obj['layoutViewport']['pageX']\n        pageY = _obj['layoutViewport']['pageY']\n\n        clip = {}\n        clip.update(boundingBox)\n        clip['x'] = clip['x'] + pageX\n        clip['y'] = clip['y'] + pageY\n        opt = {'clip': clip}\n        opt.update(options)\n        imageData = await self._page.screenshot(opt)\n\n        if needsViewportReset:\n            await self._page.setViewport(original_viewport)\n\n        return imageData\n\n    async def querySelector(self, selector: str) -> Optional['ElementHandle']:\n        \"\"\"Return first element which matches ``selector`` under this element.\n\n        If no element matches the ``selector``, returns ``None``.\n        \"\"\"\n        handle = await self.executionContext.evaluateHandle(\n            '(element, selector) => element.querySelector(selector)',\n            self, selector,\n        )\n        element = handle.asElement()\n        if element:\n            return element\n        await handle.dispose()\n        return None\n\n    async def querySelectorAll(self, selector: str) -> List['ElementHandle']:\n        \"\"\"Return all elements which match ``selector`` under this element.\n\n        If no element matches the ``selector``, returns empty list (``[]``).\n        \"\"\"\n        arrayHandle = await self.executionContext.evaluateHandle(\n            '(element, selector) => element.querySelectorAll(selector)',\n            self, selector,\n        )\n        properties = await arrayHandle.getProperties()\n        await arrayHandle.dispose()\n        result = []\n        for prop in properties.values():\n            elementHandle = prop.asElement()\n            if elementHandle:\n                result.append(elementHandle)\n        return result  # type: ignore\n\n    async def querySelectorEval(self, selector: str, pageFunction: str,\n                                *args: Any) -> Any:\n        \"\"\"Run ``Page.querySelectorEval`` within the element.\n\n        This method runs ``document.querySelector`` within the element and\n        passes it as the first argument to ``pageFunction``. If there is no\n        element matching ``selector``, the method raises\n        ``ElementHandleError``.\n\n        If ``pageFunction`` returns a promise, then wait for the promise to\n        resolve and return its value.\n\n        ``ElementHandle.Jeval`` is a shortcut of this method.\n\n        Example:\n\n        .. code:: python\n\n            tweetHandle = await page.querySelector('.tweet')\n            assert (await tweetHandle.querySelectorEval('.like', 'node => node.innerText')) == 100\n            assert (await tweetHandle.Jeval('.retweets', 'node => node.innerText')) == 10\n        \"\"\"  # noqa: E501\n        elementHandle = await self.querySelector(selector)\n        if not elementHandle:\n            raise ElementHandleError(\n                f'Error: failed to find element matching selector \"{selector}\"'\n            )\n        result = await self.executionContext.evaluate(\n            pageFunction, elementHandle, *args)\n        await elementHandle.dispose()\n        return result\n\n    async def querySelectorAllEval(self, selector: str, pageFunction: str,\n                                   *args: Any) -> Any:\n        \"\"\"Run ``Page.querySelectorAllEval`` within the element.\n\n        This method runs ``Array.from(document.querySelectorAll)`` within the\n        element and passes it as the first argument to ``pageFunction``. If\n        there is no element matching ``selector``, the method raises\n        ``ElementHandleError``.\n\n        If ``pageFunction`` returns a promise, then wait for the promise to\n        resolve and return its value.\n\n        Example:\n\n        .. code:: html\n\n            <div class=\"feed\">\n                <div class=\"tweet\">Hello!</div>\n                <div class=\"tweet\">Hi!</div>\n            </div>\n\n        .. code:: python\n\n            feedHandle = await page.J('.feed')\n            assert (await feedHandle.JJeval('.tweet', '(nodes => nodes.map(n => n.innerText))')) == ['Hello!', 'Hi!']\n        \"\"\"  # noqa: E501\n        arrayHandle = await self.executionContext.evaluateHandle(\n            '(element, selector) => Array.from(element.querySelectorAll(selector))',  # noqa: E501\n            self, selector\n        )\n        result = await self.executionContext.evaluate(\n            pageFunction, arrayHandle, *args)\n        await arrayHandle.dispose()\n        return result\n\n    #: alias to :meth:`querySelector`\n    J = querySelector\n    #: alias to :meth:`querySelectorAll`\n    JJ = querySelectorAll\n    #: alias to :meth:`querySelectorEval`\n    Jeval = querySelectorEval\n    #: alias to :meth:`querySelectorAllEval`\n    JJeval = querySelectorAllEval\n\n    async def xpath(self, expression: str) -> List['ElementHandle']:\n        \"\"\"Evaluate the XPath expression relative to this elementHandle.\n\n        If there are no such elements, return an empty list.\n\n        :arg str expression: XPath string to be evaluated.\n        \"\"\"\n        arrayHandle = await self.executionContext.evaluateHandle(\n            '''(element, expression) => {\n                const document = element.ownerDocument || element;\n                const iterator = document.evaluate(expression, element, null,\n                    XPathResult.ORDERED_NODE_ITERATOR_TYPE);\n                const array = [];\n                let item;\n                while ((item = iterator.iterateNext()))\n                    array.push(item);\n                return array;\n\n            }''', self, expression)\n        properties = await arrayHandle.getProperties()\n        await arrayHandle.dispose()\n        result = []\n        for property in properties.values():\n            elementHandle = property.asElement()\n            if elementHandle:\n                result.append(elementHandle)\n        return result\n\n    #: alias to :meth:`xpath`\n    Jx = xpath\n\n    async def isIntersectingViewport(self) -> bool:\n        \"\"\"Return ``True`` if the element is visible in the viewport.\"\"\"\n        return await self.executionContext.evaluate('''async element => {\n            const visibleRatio = await new Promise(resolve => {\n                const observer = new IntersectionObserver(entries => {\n                    resolve(entries[0].intersectionRatio);\n                    observer.disconnect();\n                });\n                observer.observe(element);\n            });\n            return visibleRatio > 0;\n        }''', self)\n\n\ndef _computeQuadArea(quad: List[Dict]) -> float:\n    area = 0\n    for i, _ in enumerate(quad):\n        p1 = quad[i]\n        p2 = quad[(i + 1) % len(quad)]\n        area += (p1['x'] * p2['y'] - p2['x'] * p1['y']) / 2\n    return area\n"
  },
  {
    "path": "pyppeteer/emulation_manager.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"Emulation Manager module.\"\"\"\n\nfrom pyppeteer import helper\nfrom pyppeteer.connection import CDPSession\n\n\nclass EmulationManager(object):\n    \"\"\"EmulationManager class.\"\"\"\n\n    def __init__(self, client: CDPSession) -> None:\n        \"\"\"Make new emulation manager.\"\"\"\n        self._client = client\n        self._emulatingMobile = False\n        self._hasTouch = False\n\n    async def emulateViewport(self, viewport: dict) -> bool:\n        \"\"\"Evaluate viewport.\"\"\"\n        options = dict()\n        mobile = viewport.get('isMobile', False)\n        options['mobile'] = mobile\n        if 'width' in viewport:\n            options['width'] = helper.get_positive_int(viewport, 'width')\n        if 'height' in viewport:\n            options['height'] = helper.get_positive_int(viewport, 'height')\n\n        options['deviceScaleFactor'] = viewport.get('deviceScaleFactor', 1)\n        if viewport.get('isLandscape'):\n            options['screenOrientation'] = {'angle': 90,\n                                            'type': 'landscapePrimary'}\n        else:\n            options['screenOrientation'] = {'angle': 0,\n                                            'type': 'portraitPrimary'}\n        hasTouch = viewport.get('hasTouch', False)\n\n        await self._client.send('Emulation.setDeviceMetricsOverride', options)\n        await self._client.send('Emulation.setTouchEmulationEnabled', {\n            'enabled': hasTouch,\n            'configuration': 'mobile' if mobile else 'desktop'\n        })\n\n        reloadNeeded = (self._emulatingMobile != mobile or\n                        self._hasTouch != hasTouch)\n\n        self._emulatingMobile = mobile\n        self._hasTouch = hasTouch\n        return reloadNeeded\n"
  },
  {
    "path": "pyppeteer/errors.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"Exceptions for pyppeteer package.\"\"\"\n\nimport asyncio\n\n\nclass PyppeteerError(Exception):  # noqa: D204\n    \"\"\"Base exception for pyppeteer.\"\"\"\n    pass\n\n\nclass BrowserError(PyppeteerError):  # noqa: D204\n    \"\"\"Exception raised from browser.\"\"\"\n    pass\n\n\nclass ElementHandleError(PyppeteerError):  # noqa: D204\n    \"\"\"ElementHandle related exception.\"\"\"\n    pass\n\n\nclass NetworkError(PyppeteerError):  # noqa: D204\n    \"\"\"Network/Protocol related exception.\"\"\"\n    pass\n\n\nclass PageError(PyppeteerError):  # noqa: D204\n    \"\"\"Page/Frame related exception.\"\"\"\n    pass\n\n\nclass TimeoutError(asyncio.TimeoutError):  # noqa: D204\n    \"\"\"Timeout Error class.\"\"\"\n    pass\n"
  },
  {
    "path": "pyppeteer/execution_context.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"Execution Context Module.\"\"\"\n\nimport logging\nimport math\nimport re\nfrom typing import Any, Dict, Optional, TYPE_CHECKING\n\nfrom pyppeteer import helper\nfrom pyppeteer.connection import CDPSession\nfrom pyppeteer.errors import ElementHandleError, NetworkError\nfrom pyppeteer.helper import debugError\n\nif TYPE_CHECKING:\n    from pyppeteer.element_handle import ElementHandle  # noqa: F401\n    from pyppeteer.frame_manager import Frame  # noqa: F401\n\nlogger = logging.getLogger(__name__)\n\nEVALUATION_SCRIPT_URL = '__pyppeteer_evaluation_script__'\nSOURCE_URL_REGEX = re.compile(\n    r'^[\\040\\t]*//[@#] sourceURL=\\s*(\\S*?)\\s*$',\n    re.MULTILINE,\n)\n\n\nclass ExecutionContext(object):\n    \"\"\"Execution Context class.\"\"\"\n\n    def __init__(self, client: CDPSession, contextPayload: Dict,\n                 objectHandleFactory: Any, frame: 'Frame' = None) -> None:\n        self._client = client\n        self._frame = frame\n        self._contextId = contextPayload.get('id')\n\n        auxData = contextPayload.get('auxData', {'isDefault': False})\n        self._isDefault = bool(auxData.get('isDefault'))\n        self._objectHandleFactory = objectHandleFactory\n\n    @property\n    def frame(self) -> Optional['Frame']:\n        \"\"\"Return frame associated with this execution context.\"\"\"\n        return self._frame\n\n    async def evaluate(self, pageFunction: str, *args: Any,\n                       force_expr: bool = False) -> Any:\n        \"\"\"Execute ``pageFunction`` on this context.\n\n        Details see :meth:`pyppeteer.page.Page.evaluate`.\n        \"\"\"\n        handle = await self.evaluateHandle(\n            pageFunction, *args, force_expr=force_expr)\n        try:\n            result = await handle.jsonValue()\n        except NetworkError as e:\n            if 'Object reference chain is too long' in e.args[0]:\n                return\n            if 'Object couldn\\'t be returned by value' in e.args[0]:\n                return\n            raise\n        await handle.dispose()\n        return result\n\n    async def evaluateHandle(self, pageFunction: str, *args: Any,  # noqa: C901\n                             force_expr: bool = False) -> 'JSHandle':\n        \"\"\"Execute ``pageFunction`` on this context.\n\n        Details see :meth:`pyppeteer.page.Page.evaluateHandle`.\n        \"\"\"\n        suffix = f'//# sourceURL={EVALUATION_SCRIPT_URL}'\n\n        if force_expr or (not args and not helper.is_jsfunc(pageFunction)):\n            try:\n                if SOURCE_URL_REGEX.match(pageFunction):\n                    expressionWithSourceUrl = pageFunction\n                else:\n                    expressionWithSourceUrl = f'{pageFunction}\\n{suffix}'\n                _obj = await self._client.send('Runtime.evaluate', {\n                    'expression': expressionWithSourceUrl,\n                    'contextId': self._contextId,\n                    'returnByValue': False,\n                    'awaitPromise': True,\n                    'userGesture': True,\n                })\n            except Exception as e:\n                _rewriteError(e)\n\n            exceptionDetails = _obj.get('exceptionDetails')\n            if exceptionDetails:\n                raise ElementHandleError(\n                    'Evaluation failed: {}'.format(\n                        helper.getExceptionMessage(exceptionDetails)))\n            remoteObject = _obj.get('result')\n            return self._objectHandleFactory(remoteObject)\n\n        try:\n            _obj = await self._client.send('Runtime.callFunctionOn', {\n                'functionDeclaration': f'{pageFunction}\\n{suffix}\\n',\n                'executionContextId': self._contextId,\n                'arguments': [self._convertArgument(arg) for arg in args],\n                'returnByValue': False,\n                'awaitPromise': True,\n                'userGesture': True,\n            })\n        except Exception as e:\n            _rewriteError(e)\n\n        exceptionDetails = _obj.get('exceptionDetails')\n        if exceptionDetails:\n            raise ElementHandleError('Evaluation failed: {}'.format(\n                helper.getExceptionMessage(exceptionDetails)))\n        remoteObject = _obj.get('result')\n        return self._objectHandleFactory(remoteObject)\n\n    def _convertArgument(self, arg: Any) -> Dict:  # noqa: C901\n        if arg == math.inf:\n            return {'unserializableValue': 'Infinity'}\n        if arg == -math.inf:\n            return {'unserializableValue': '-Infinity'}\n        objectHandle = arg if isinstance(arg, JSHandle) else None\n        if objectHandle:\n            if objectHandle._context != self:\n                raise ElementHandleError('JSHandles can be evaluated only in the context they were created!')  # noqa: E501\n            if objectHandle._disposed:\n                raise ElementHandleError('JSHandle is disposed!')\n            if objectHandle._remoteObject.get('unserializableValue'):\n                return {'unserializableValue': objectHandle._remoteObject.get('unserializableValue')}  # noqa: E501\n            if not objectHandle._remoteObject.get('objectId'):\n                return {'value': objectHandle._remoteObject.get('value')}\n            return {'objectId': objectHandle._remoteObject.get('objectId')}\n        return {'value': arg}\n\n    async def queryObjects(self, prototypeHandle: 'JSHandle') -> 'JSHandle':\n        \"\"\"Send query.\n\n        Details see :meth:`pyppeteer.page.Page.queryObjects`.\n        \"\"\"\n        if prototypeHandle._disposed:\n            raise ElementHandleError('Prototype JSHandle is disposed!')\n        if not prototypeHandle._remoteObject.get('objectId'):\n            raise ElementHandleError(\n                'Prototype JSHandle must not be referencing primitive value')\n        response = await self._client.send('Runtime.queryObjects', {\n            'prototypeObjectId': prototypeHandle._remoteObject['objectId'],\n        })\n        return self._objectHandleFactory(response.get('objects'))\n\n\nclass JSHandle(object):\n    \"\"\"JSHandle class.\n\n    JSHandle represents an in-page JavaScript object. JSHandle can be created\n    with the :meth:`~pyppeteer.page.Page.evaluateHandle` method.\n    \"\"\"\n\n    def __init__(self, context: ExecutionContext, client: CDPSession,\n                 remoteObject: Dict) -> None:\n        self._context = context\n        self._client = client\n        self._remoteObject = remoteObject\n        self._disposed = False\n\n    @property\n    def executionContext(self) -> ExecutionContext:\n        \"\"\"Get execution context of this handle.\"\"\"\n        return self._context\n\n    async def getProperty(self, propertyName: str) -> 'JSHandle':\n        \"\"\"Get property value of ``propertyName``.\"\"\"\n        objectHandle = await self._context.evaluateHandle(\n            '''(object, propertyName) => {\n                const result = {__proto__: null};\n                result[propertyName] = object[propertyName];\n                return result;\n            }''', self, propertyName)\n        properties = await objectHandle.getProperties()\n        result = properties[propertyName]\n        await objectHandle.dispose()\n        return result\n\n    async def getProperties(self) -> Dict[str, 'JSHandle']:\n        \"\"\"Get all properties of this handle.\"\"\"\n        response = await self._client.send('Runtime.getProperties', {\n            'objectId': self._remoteObject.get('objectId', ''),\n            'ownProperties': True,\n        })\n        result = dict()\n        for prop in response['result']:\n            if not prop.get('enumerable'):\n                continue\n            result[prop.get('name')] = self._context._objectHandleFactory(\n                prop.get('value'))\n        return result\n\n    async def jsonValue(self) -> Dict:\n        \"\"\"Get Jsonized value of this object.\"\"\"\n        objectId = self._remoteObject.get('objectId')\n        if objectId:\n            response = await self._client.send('Runtime.callFunctionOn', {\n                'functionDeclaration': 'function() { return this; }',\n                'objectId': objectId,\n                'returnByValue': True,\n                'awaitPromise': True,\n            })\n            return helper.valueFromRemoteObject(response['result'])\n        return helper.valueFromRemoteObject(self._remoteObject)\n\n    def asElement(self) -> Optional['ElementHandle']:\n        \"\"\"Return either null or the object handle itself.\"\"\"\n        return None\n\n    async def dispose(self) -> None:\n        \"\"\"Stop referencing the handle.\"\"\"\n        if self._disposed:\n            return\n        self._disposed = True\n        try:\n            await helper.releaseObject(self._client, self._remoteObject)\n        except Exception as e:\n            debugError(logger, e)\n\n    def toString(self) -> str:\n        \"\"\"Get string representation.\"\"\"\n        if self._remoteObject.get('objectId'):\n            _type = (self._remoteObject.get('subtype') or\n                     self._remoteObject.get('type'))\n            return f'JSHandle@{_type}'\n        return 'JSHandle:{}'.format(\n            helper.valueFromRemoteObject(self._remoteObject))\n\n\ndef _rewriteError(error: Exception) -> None:\n    if error.args[0].endswith('Cannot find context with specified id'):\n        msg = 'Execution context was destroyed, most likely because of a navigation.'  # noqa: E501\n        raise type(error)(msg)\n    raise error\n"
  },
  {
    "path": "pyppeteer/frame_manager.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"Frame Manager module.\"\"\"\n\nimport asyncio\nfrom collections import OrderedDict\nimport logging\nfrom types import SimpleNamespace\nfrom typing import Any, Awaitable, Dict, Generator, List, Optional, Set, Union\n\nfrom pyee import EventEmitter\n\nfrom pyppeteer import helper\nfrom pyppeteer.connection import CDPSession\nfrom pyppeteer.element_handle import ElementHandle\nfrom pyppeteer.errors import NetworkError\nfrom pyppeteer.execution_context import ExecutionContext, JSHandle\nfrom pyppeteer.errors import ElementHandleError, PageError, TimeoutError\nfrom pyppeteer.util import merge_dict\n\nlogger = logging.getLogger(__name__)\n\n\nclass FrameManager(EventEmitter):\n    \"\"\"FrameManager class.\"\"\"\n\n    Events = SimpleNamespace(\n        FrameAttached='frameattached',\n        FrameNavigated='framenavigated',\n        FrameDetached='framedetached',\n        LifecycleEvent='lifecycleevent',\n        FrameNavigatedWithinDocument='framenavigatedwithindocument',\n    )\n\n    def __init__(self, client: CDPSession, frameTree: Dict, page: Any) -> None:\n        \"\"\"Make new frame manager.\"\"\"\n        super().__init__()\n        self._client = client\n        self._page = page\n        self._frames: OrderedDict[str, Frame] = OrderedDict()\n        self._mainFrame: Optional[Frame] = None\n        self._contextIdToContext: Dict[str, ExecutionContext] = dict()\n\n        client.on('Page.frameAttached',\n                  lambda event: self._onFrameAttached(\n                      event.get('frameId', ''), event.get('parentFrameId', ''))\n                  )\n        client.on('Page.frameNavigated',\n                  lambda event: self._onFrameNavigated(event.get('frame')))\n        client.on('Page.navigatedWithinDocument',\n                  lambda event: self._onFrameNavigatedWithinDocument(\n                      event.get('frameId'), event.get('url')\n                  ))\n        client.on('Page.frameDetached',\n                  lambda event: self._onFrameDetached(event.get('frameId')))\n        client.on('Page.frameStoppedLoading',\n                  lambda event: self._onFrameStoppedLoading(\n                      event.get('frameId')\n                  ))\n        client.on('Runtime.executionContextCreated',\n                  lambda event: self._onExecutionContextCreated(\n                      event.get('context')))\n        client.on('Runtime.executionContextDestroyed',\n                  lambda event: self._onExecutionContextDestroyed(\n                      event.get('executionContextId')))\n        client.on('Runtime.executionContextsCleared',\n                  lambda event: self._onExecutionContextsCleared())\n        client.on('Page.lifecycleEvent',\n                  lambda event: self._onLifecycleEvent(event))\n\n        self._handleFrameTree(frameTree)\n\n    def _onLifecycleEvent(self, event: Dict) -> None:\n        frame = self._frames.get(event['frameId'])\n        if not frame:\n            return\n        frame._onLifecycleEvent(event['loaderId'], event['name'])\n        self.emit(FrameManager.Events.LifecycleEvent, frame)\n\n    def _onFrameStoppedLoading(self, frameId: str) -> None:\n        frame = self._frames.get(frameId)\n        if not frame:\n            return\n        frame._onLoadingStopped()\n        self.emit(FrameManager.Events.LifecycleEvent, frame)\n\n    def _handleFrameTree(self, frameTree: Dict) -> None:\n        frame = frameTree['frame']\n        if 'parentId' in frame:\n            self._onFrameAttached(\n                frame['id'],\n                frame['parentId'],\n            )\n        self._onFrameNavigated(frame)\n        if 'childFrames' not in frameTree:\n            return\n        for child in frameTree['childFrames']:\n            self._handleFrameTree(child)\n\n    @property\n    def mainFrame(self) -> Optional['Frame']:\n        \"\"\"Return main frame.\"\"\"\n        return self._mainFrame\n\n    def frames(self) -> List['Frame']:\n        \"\"\"Return all frames.\"\"\"\n        return list(self._frames.values())\n\n    def frame(self, frameId: str) -> Optional['Frame']:\n        \"\"\"Return :class:`Frame` of ``frameId``.\"\"\"\n        return self._frames.get(frameId)\n\n    def _onFrameAttached(self, frameId: str, parentFrameId: str) -> None:\n        if frameId in self._frames:\n            return\n        parentFrame = self._frames.get(parentFrameId)\n        frame = Frame(self._client, parentFrame, frameId)\n        self._frames[frameId] = frame\n        self.emit(FrameManager.Events.FrameAttached, frame)\n\n    def _onFrameNavigated(self, framePayload: dict) -> None:\n        isMainFrame = not framePayload.get('parentId')\n        if isMainFrame:\n            frame = self._mainFrame\n        else:\n            frame = self._frames.get(framePayload.get('id', ''))\n        if not (isMainFrame or frame):\n            raise PageError('We either navigate top level or have old version '\n                            'of the navigated frame')\n\n        # Detach all child frames first.\n        if frame:\n            for child in frame.childFrames:\n                self._removeFramesRecursively(child)\n\n        # Update or create main frame.\n        _id = framePayload.get('id', '')\n        if isMainFrame:\n            if frame:\n                # Update frame id to retain frame identity on cross-process navigation.  # noqa: E501\n                self._frames.pop(frame._id, None)\n                frame._id = _id\n            else:\n                # Initial main frame navigation.\n                frame = Frame(self._client, None, _id)\n            self._frames[_id] = frame\n            self._mainFrame = frame\n\n        # Update frame payload.\n        frame._navigated(framePayload)  # type: ignore\n        self.emit(FrameManager.Events.FrameNavigated, frame)\n\n    def _onFrameNavigatedWithinDocument(self, frameId: str, url: str) -> None:\n        frame = self._frames.get(frameId)\n        if not frame:\n            return\n        frame._navigatedWithinDocument(url)\n        self.emit(FrameManager.Events.FrameNavigatedWithinDocument, frame)\n        self.emit(FrameManager.Events.FrameNavigated, frame)\n\n    def _onFrameDetached(self, frameId: str) -> None:\n        frame = self._frames.get(frameId)\n        if frame:\n            self._removeFramesRecursively(frame)\n\n    def _onExecutionContextCreated(self, contextPayload: Dict) -> None:\n        if (contextPayload.get('auxData') and\n                contextPayload['auxData'].get('frameId')):\n            frameId = contextPayload['auxData']['frameId']\n        else:\n            frameId = None\n\n        frame = self._frames.get(frameId)\n\n        def _createJSHandle(obj: Dict) -> JSHandle:\n            context = self.executionContextById(contextPayload['id'])\n            return self.createJSHandle(context, obj)\n\n        context = ExecutionContext(\n            self._client,\n            contextPayload,\n            _createJSHandle,\n            frame,\n        )\n        self._contextIdToContext[contextPayload['id']] = context\n\n        if frame:\n            frame._addExecutionContext(context)\n\n    def _onExecutionContextDestroyed(self, executionContextId: str) -> None:\n        context = self._contextIdToContext.get(executionContextId)\n        if not context:\n            return\n        del self._contextIdToContext[executionContextId]\n\n        frame = context.frame\n        if frame:\n            frame._removeExecutionContext(context)\n\n    def _onExecutionContextsCleared(self) -> None:\n        for context in self._contextIdToContext.values():\n            frame = context.frame\n            if frame:\n                frame._removeExecutionContext(context)\n        self._contextIdToContext.clear()\n\n    def executionContextById(self, contextId: str) -> ExecutionContext:\n        \"\"\"Get stored ``ExecutionContext`` by ``id``.\"\"\"\n        context = self._contextIdToContext.get(contextId)\n        if not context:\n            raise ElementHandleError(\n                f'INTERNAL ERROR: missing context with id = {contextId}'\n            )\n        return context\n\n    def createJSHandle(self, context: ExecutionContext,\n                       remoteObject: Dict = None) -> JSHandle:\n        \"\"\"Create JS handle associated to the context id and remote object.\"\"\"\n        if remoteObject is None:\n            remoteObject = dict()\n        if remoteObject.get('subtype') == 'node':\n            return ElementHandle(context, self._client, remoteObject,\n                                 self._page, self)\n        return JSHandle(context, self._client, remoteObject)\n\n    def _removeFramesRecursively(self, frame: 'Frame') -> None:\n        for child in frame.childFrames:\n            self._removeFramesRecursively(child)\n        frame._detach()\n        self._frames.pop(frame._id, None)\n        self.emit(FrameManager.Events.FrameDetached, frame)\n\n\nclass Frame(object):\n    \"\"\"Frame class.\n\n    Frame objects can be obtained via :attr:`pyppeteer.page.Page.mainFrame`.\n    \"\"\"\n\n    def __init__(self, client: CDPSession, parentFrame: Optional['Frame'],\n                 frameId: str) -> None:\n        self._client = client\n        self._parentFrame = parentFrame\n        self._url = ''\n        self._detached = False\n        self._id = frameId\n\n        self._documentPromise: Optional[ElementHandle] = None\n        self._contextResolveCallback = lambda _: None\n        self._setDefaultContext(None)\n\n        self._waitTasks: Set[WaitTask] = set()  # maybe list\n        self._loaderId = ''\n        self._lifecycleEvents: Set[str] = set()\n        self._childFrames: Set[Frame] = set()  # maybe list\n        if self._parentFrame:\n            self._parentFrame._childFrames.add(self)\n\n    def _addExecutionContext(self, context: ExecutionContext) -> None:\n        if context._isDefault:\n            self._setDefaultContext(context)\n\n    def _removeExecutionContext(self, context: ExecutionContext) -> None:\n        if context._isDefault:\n            self._setDefaultContext(None)\n\n    def _setDefaultContext(self, context: Optional[ExecutionContext]) -> None:\n        if context is not None:\n            self._contextResolveCallback(context)  # type: ignore\n            self._contextResolveCallback = lambda _: None\n            for waitTask in self._waitTasks:\n                self._client._loop.create_task(waitTask.rerun())\n        else:\n            self._documentPromise = None\n            self._contextPromise = self._client._loop.create_future()\n            self._contextResolveCallback = (\n                lambda _context: self._contextPromise.set_result(_context)\n            )\n\n    async def executionContext(self) -> Optional[ExecutionContext]:\n        \"\"\"Return execution context of this frame.\n\n        Return :class:`~pyppeteer.execution_context.ExecutionContext`\n        associated to this frame.\n        \"\"\"\n        return await self._contextPromise\n\n    async def evaluateHandle(self, pageFunction: str, *args: Any) -> JSHandle:\n        \"\"\"Execute function on this frame.\n\n        Details see :meth:`pyppeteer.page.Page.evaluateHandle`.\n        \"\"\"\n        context = await self.executionContext()\n        if context is None:\n            raise PageError('this frame has no context.')\n        return await context.evaluateHandle(pageFunction, *args)\n\n    async def evaluate(self, pageFunction: str, *args: Any,\n                       force_expr: bool = False) -> Any:\n        \"\"\"Evaluate pageFunction on this frame.\n\n        Details see :meth:`pyppeteer.page.Page.evaluate`.\n        \"\"\"\n        context = await self.executionContext()\n        if context is None:\n            raise ElementHandleError('ExecutionContext is None.')\n        return await context.evaluate(\n            pageFunction, *args, force_expr=force_expr)\n\n    async def querySelector(self, selector: str) -> Optional[ElementHandle]:\n        \"\"\"Get element which matches `selector` string.\n\n        Details see :meth:`pyppeteer.page.Page.querySelector`.\n        \"\"\"\n        document = await self._document()\n        value = await document.querySelector(selector)\n        return value\n\n    async def _document(self) -> ElementHandle:\n        if self._documentPromise:\n            return self._documentPromise\n        context = await self.executionContext()\n        if context is None:\n            raise PageError('No context exists.')\n        document = (await context.evaluateHandle('document')).asElement()\n        self._documentPromise = document\n        if document is None:\n            raise PageError('Could not find `document`.')\n        return document\n\n    async def xpath(self, expression: str) -> List[ElementHandle]:\n        \"\"\"Evaluate the XPath expression.\n\n        If there are no such elements in this frame, return an empty list.\n\n        :arg str expression: XPath string to be evaluated.\n        \"\"\"\n        document = await self._document()\n        value = await document.xpath(expression)\n        return value\n\n    async def querySelectorEval(self, selector: str, pageFunction: str,\n                                *args: Any) -> Any:\n        \"\"\"Execute function on element which matches selector.\n\n        Details see :meth:`pyppeteer.page.Page.querySelectorEval`.\n        \"\"\"\n        document = await self._document()\n        return await document.querySelectorEval(selector, pageFunction, *args)\n\n    async def querySelectorAllEval(self, selector: str, pageFunction: str,\n                                   *args: Any) -> Optional[Dict]:\n        \"\"\"Execute function on all elements which matches selector.\n\n        Details see :meth:`pyppeteer.page.Page.querySelectorAllEval`.\n        \"\"\"\n        document = await self._document()\n        value = await document.JJeval(selector, pageFunction, *args)\n        return value\n\n    async def querySelectorAll(self, selector: str) -> List[ElementHandle]:\n        \"\"\"Get all elements which matches `selector`.\n\n        Details see :meth:`pyppeteer.page.Page.querySelectorAll`.\n        \"\"\"\n        document = await self._document()\n        value = await document.querySelectorAll(selector)\n        return value\n\n    #: Alias to :meth:`querySelector`\n    J = querySelector\n    #: Alias to :meth:`xpath`\n    Jx = xpath\n    #: Alias to :meth:`querySelectorEval`\n    Jeval = querySelectorEval\n    #: Alias to :meth:`querySelectorAll`\n    JJ = querySelectorAll\n    #: Alias to :meth:`querySelectorAllEval`\n    JJeval = querySelectorAllEval\n\n    async def content(self) -> str:\n        \"\"\"Get the whole HTML contents of the page.\"\"\"\n        return await self.evaluate('''\n() => {\n  let retVal = '';\n  if (document.doctype)\n    retVal = new XMLSerializer().serializeToString(document.doctype);\n  if (document.documentElement)\n    retVal += document.documentElement.outerHTML;\n  return retVal;\n}\n        '''.strip())\n\n    async def setContent(self, html: str) -> None:\n        \"\"\"Set content to this page.\"\"\"\n        func = '''\nfunction(html) {\n  document.open();\n  document.write(html);\n  document.close();\n}\n'''\n        await self.evaluate(func, html)\n\n    @property\n    def name(self) -> str:\n        \"\"\"Get frame name.\"\"\"\n        return self.__dict__.get('_name', '')\n\n    @property\n    def url(self) -> str:\n        \"\"\"Get url of the frame.\"\"\"\n        return self._url\n\n    @property\n    def parentFrame(self) -> Optional['Frame']:\n        \"\"\"Get parent frame.\n\n        If this frame is main frame or detached frame, return ``None``.\n        \"\"\"\n        return self._parentFrame\n\n    @property\n    def childFrames(self) -> List['Frame']:\n        \"\"\"Get child frames.\"\"\"\n        return list(self._childFrames)\n\n    def isDetached(self) -> bool:\n        \"\"\"Return ``True`` if this frame is detached.\n\n        Otherwise return ``False``.\n        \"\"\"\n        return self._detached\n\n    async def injectFile(self, filePath: str) -> str:\n        \"\"\"[Deprecated] Inject file to the frame.\"\"\"\n        logger.warning('`injectFile` method is deprecated.'\n                       ' Use `addScriptTag` method instead.')\n        with open(filePath) as f:\n            contents = f.read()\n        contents += '/* # sourceURL= {} */'.format(filePath.replace('\\n', ''))\n        return await self.evaluate(contents)\n\n    async def addScriptTag(self, options: Dict) -> ElementHandle:  # noqa: C901\n        \"\"\"Add script tag to this frame.\n\n        Details see :meth:`pyppeteer.page.Page.addScriptTag`.\n        \"\"\"\n        context = await self.executionContext()\n        if context is None:\n            raise ElementHandleError('ExecutionContext is None.')\n\n        addScriptUrl = '''\n        async function addScriptUrl(url, type) {\n            const script = document.createElement('script');\n            script.src = url;\n            if (type)\n                script.type = type;\n            const promise = new Promise((res, rej) => {\n                script.onload = res;\n                script.onerror = rej;\n            });\n            document.head.appendChild(script);\n            await promise;\n            return script;\n        }'''\n\n        addScriptContent = '''\n        function addScriptContent(content, type = 'text/javascript') {\n            const script = document.createElement('script');\n            script.type = type;\n            script.text = content;\n            let error = null;\n            script.onerror = e => error = e;\n            document.head.appendChild(script);\n            if (error)\n                throw error;\n            return script;\n        }'''\n\n        if isinstance(options.get('url'), str):\n            url = options['url']\n            args = [addScriptUrl, url]\n            if 'type' in options:\n                args.append(options['type'])\n            try:\n                return (await context.evaluateHandle(*args)  # type: ignore\n                        ).asElement()\n            except ElementHandleError as e:\n                raise PageError(f'Loading script from {url} failed') from e\n\n        if isinstance(options.get('path'), str):\n            with open(options['path']) as f:\n                contents = f.read()\n            contents = contents + '//# sourceURL={}'.format(\n                options['path'].replace('\\n', ''))\n            args = [addScriptContent, contents]\n            if 'type' in options:\n                args.append(options['type'])\n            return (await context.evaluateHandle(*args)  # type: ignore\n                    ).asElement()\n\n        if isinstance(options.get('content'), str):\n            args = [addScriptContent, options['content']]\n            if 'type' in options:\n                args.append(options['type'])\n            return (await context.evaluateHandle(*args)  # type: ignore\n                    ).asElement()\n\n        raise ValueError(\n            'Provide an object with a `url`, `path` or `content` property')\n\n    async def addStyleTag(self, options: Dict) -> ElementHandle:\n        \"\"\"Add style tag to this frame.\n\n        Details see :meth:`pyppeteer.page.Page.addStyleTag`.\n        \"\"\"\n        context = await self.executionContext()\n        if context is None:\n            raise ElementHandleError('ExecutionContext is None.')\n\n        addStyleUrl = '''\n        async function (url) {\n            const link = document.createElement('link');\n            link.rel = 'stylesheet';\n            link.href = url;\n            const promise = new Promise((res, rej) => {\n                link.onload = res;\n                link.onerror = rej;\n            });\n            document.head.appendChild(link);\n            await promise;\n            return link;\n        }'''\n\n        addStyleContent = '''\n        async function (content) {\n            const style = document.createElement('style');\n            style.type = 'text/css';\n            style.appendChild(document.createTextNode(content));\n            const promise = new Promise((res, rej) => {\n                style.onload = res;\n                style.onerror = rej;\n            });\n            document.head.appendChild(style);\n            await promise;\n            return style;\n        }'''\n\n        if isinstance(options.get('url'), str):\n            url = options['url']\n            try:\n                return (await context.evaluateHandle(  # type: ignore\n                    addStyleUrl, url)).asElement()\n            except ElementHandleError as e:\n                raise PageError(f'Loading style from {url} failed') from e\n\n        if isinstance(options.get('path'), str):\n            with open(options['path']) as f:\n                contents = f.read()\n            contents = contents + '/*# sourceURL={}*/'.format(\n                options['path'].replace('\\n', ''))\n            return (await context.evaluateHandle(  # type: ignore\n                addStyleContent, contents)).asElement()\n\n        if isinstance(options.get('content'), str):\n            return (await context.evaluateHandle(  # type: ignore\n                addStyleContent, options['content'])).asElement()\n\n        raise ValueError(\n            'Provide an object with a `url`, `path` or `content` property')\n\n    async def click(self, selector: str, options: dict = None, **kwargs: Any\n                    ) -> None:\n        \"\"\"Click element which matches ``selector``.\n\n        Details see :meth:`pyppeteer.page.Page.click`.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n        handle = await self.J(selector)\n        if not handle:\n            raise PageError('No node found for selector: ' + selector)\n        await handle.click(options)\n        await handle.dispose()\n\n    async def focus(self, selector: str) -> None:\n        \"\"\"Focus element which matches ``selector``.\n\n        Details see :meth:`pyppeteer.page.Page.focus`.\n        \"\"\"\n        handle = await self.J(selector)\n        if not handle:\n            raise PageError('No node found for selector: ' + selector)\n        await self.evaluate('element => element.focus()', handle)\n        await handle.dispose()\n\n    async def hover(self, selector: str) -> None:\n        \"\"\"Mouse hover the element which matches ``selector``.\n\n        Details see :meth:`pyppeteer.page.Page.hover`.\n        \"\"\"\n        handle = await self.J(selector)\n        if not handle:\n            raise PageError('No node found for selector: ' + selector)\n        await handle.hover()\n        await handle.dispose()\n\n    async def select(self, selector: str, *values: str) -> List[str]:\n        \"\"\"Select options and return selected values.\n\n        Details see :meth:`pyppeteer.page.Page.select`.\n        \"\"\"\n        for value in values:\n            if not isinstance(value, str):\n                raise TypeError(\n                    'Values must be string. '\n                    f'Found {value} of type {type(value)}'\n                )\n        return await self.querySelectorEval(  # type: ignore\n            selector, '''\n(element, values) => {\n    if (element.nodeName.toLowerCase() !== 'select')\n        throw new Error('Element is not a <select> element.');\n\n    const options = Array.from(element.options);\n    element.value = undefined;\n    for (const option of options) {\n        option.selected = values.includes(option.value);\n        if (option.selected && !element.multiple)\n            break;\n    }\n\n    element.dispatchEvent(new Event('input', { 'bubbles': true }));\n    element.dispatchEvent(new Event('change', { 'bubbles': true }));\n    return options.filter(option => option.selected).map(options => options.value)\n}\n        ''', values)  # noqa: E501\n\n    async def tap(self, selector: str) -> None:\n        \"\"\"Tap the element which matches the ``selector``.\n\n        Details see :meth:`pyppeteer.page.Page.tap`.\n        \"\"\"\n        handle = await self.J(selector)\n        if not handle:\n            raise PageError('No node found for selector: ' + selector)\n        await handle.tap()\n        await handle.dispose()\n\n    async def type(self, selector: str, text: str, options: dict = None,\n                   **kwargs: Any) -> None:\n        \"\"\"Type ``text`` on the element which matches ``selector``.\n\n        Details see :meth:`pyppeteer.page.Page.type`.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n        handle = await self.querySelector(selector)\n        if handle is None:\n            raise PageError('Cannot find {} on this page'.format(selector))\n        await handle.type(text, options)\n        await handle.dispose()\n\n    def waitFor(self, selectorOrFunctionOrTimeout: Union[str, int, float],\n                options: dict = None, *args: Any, **kwargs: Any\n                ) -> Union[Awaitable, 'WaitTask']:\n        \"\"\"Wait until `selectorOrFunctionOrTimeout`.\n\n        Details see :meth:`pyppeteer.page.Page.waitFor`.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n        if isinstance(selectorOrFunctionOrTimeout, (int, float)):\n            fut: Awaitable[None] = self._client._loop.create_task(\n                asyncio.sleep(selectorOrFunctionOrTimeout / 1000))\n            return fut\n        if not isinstance(selectorOrFunctionOrTimeout, str):\n            fut = self._client._loop.create_future()\n            fut.set_exception(TypeError(\n                'Unsupported target type: ' +\n                str(type(selectorOrFunctionOrTimeout))\n            ))\n            return fut\n\n        if args or helper.is_jsfunc(selectorOrFunctionOrTimeout):\n            return self.waitForFunction(\n                selectorOrFunctionOrTimeout, options, *args)\n        if selectorOrFunctionOrTimeout.startswith('//'):\n            return self.waitForXPath(selectorOrFunctionOrTimeout, options)\n        return self.waitForSelector(selectorOrFunctionOrTimeout, options)\n\n    def waitForSelector(self, selector: str, options: dict = None,\n                        **kwargs: Any) -> 'WaitTask':\n        \"\"\"Wait until element which matches ``selector`` appears on page.\n\n        Details see :meth:`pyppeteer.page.Page.waitForSelector`.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n        return self._waitForSelectorOrXPath(selector, False, options)\n\n    def waitForXPath(self, xpath: str, options: dict = None,\n                     **kwargs: Any) -> 'WaitTask':\n        \"\"\"Wait until element which matches ``xpath`` appears on page.\n\n        Details see :meth:`pyppeteer.page.Page.waitForXPath`.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n        return self._waitForSelectorOrXPath(xpath, True, options)\n\n    def waitForFunction(self, pageFunction: str, options: dict = None,\n                        *args: Any, **kwargs: Any) -> 'WaitTask':\n        \"\"\"Wait until the function completes.\n\n        Details see :meth:`pyppeteer.page.Page.waitForFunction`.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n        timeout = options.get('timeout',  30000)  # msec\n        polling = options.get('polling', 'raf')\n        return WaitTask(self, pageFunction, 'function', polling, timeout,\n                        self._client._loop, *args)\n\n    def _waitForSelectorOrXPath(self, selectorOrXPath: str, isXPath: bool,\n                                options: dict = None, **kwargs: Any\n                                ) -> 'WaitTask':\n        options = merge_dict(options, kwargs)\n        timeout = options.get('timeout', 30000)\n        waitForVisible = bool(options.get('visible'))\n        waitForHidden = bool(options.get('hidden'))\n        polling = 'raf' if waitForHidden or waitForVisible else 'mutation'\n        title = '{} \"{}\"{}'.format(\n            'XPath' if isXPath else 'selector',\n            selectorOrXPath,\n            ' to be hidden' if waitForHidden else '',\n        )\n\n        predicate = '''\n(selectorOrXPath, isXPath, waitForVisible, waitForHidden) => {\n    const node = isXPath\n        ? document.evaluate(selectorOrXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue\n        : document.querySelector(selectorOrXPath);\n    if (!node)\n        return waitForHidden;\n    if (!waitForVisible && !waitForHidden)\n        return node;\n    const element = /** @type {Element} */ (node.nodeType === Node.TEXT_NODE ? node.parentElement : node);\n\n    const style = window.getComputedStyle(element);\n    const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox();\n    const success = (waitForVisible === isVisible || waitForHidden === !isVisible)\n    return success ? node : null\n\n    function hasVisibleBoundingBox() {\n        const rect = element.getBoundingClientRect();\n        return !!(rect.top || rect.bottom || rect.width || rect.height);\n    }\n}\n        '''  # noqa: E501\n\n        return WaitTask(\n            self,\n            predicate,\n            title,\n            polling,\n            timeout,\n            self._client._loop,\n            selectorOrXPath,\n            isXPath,\n            waitForVisible,\n            waitForHidden,\n        )\n\n    async def title(self) -> str:\n        \"\"\"Get title of the frame.\"\"\"\n        return await self.evaluate('() => document.title')\n\n    def _navigated(self, framePayload: dict) -> None:\n        self._name = framePayload.get('name', '')\n        self._navigationURL = framePayload.get('url', '')\n        self._url = framePayload.get('url', '')\n\n    def _navigatedWithinDocument(self, url: str) -> None:\n        self._url = url\n\n    def _onLifecycleEvent(self, loaderId: str, name: str) -> None:\n        if name == 'init':\n            self._loaderId = loaderId\n            self._lifecycleEvents.clear()\n        else:\n            self._lifecycleEvents.add(name)\n\n    def _onLoadingStopped(self) -> None:\n        self._lifecycleEvents.add('DOMContentLoaded')\n        self._lifecycleEvents.add('load')\n\n    def _detach(self) -> None:\n        for waitTask in self._waitTasks:\n            waitTask.terminate(\n                PageError('waitForFunction failed: frame got detached.'))\n        self._detached = True\n        if self._parentFrame:\n            self._parentFrame._childFrames.remove(self)\n        self._parentFrame = None\n\n\nclass WaitTask(object):\n    \"\"\"WaitTask class.\n\n    Instance of this class is awaitable.\n    \"\"\"\n\n    def __init__(self, frame: Frame, predicateBody: str,  # noqa: C901\n                 title: str, polling: Union[str, int], timeout: float,\n                 loop: asyncio.AbstractEventLoop, *args: Any) -> None:\n        if isinstance(polling, str):\n            if polling not in ['raf', 'mutation']:\n                raise ValueError(f'Unknown polling: {polling}')\n        elif isinstance(polling, (int, float)):\n            if polling <= 0:\n                raise ValueError(\n                    f'Cannot poll with non-positive interval: {polling}'\n                )\n        else:\n            raise ValueError(f'Unknown polling option: {polling}')\n\n        self._frame = frame\n        self._polling = polling\n        self._timeout = timeout\n        self._loop = loop\n        if args or helper.is_jsfunc(predicateBody):\n            self._predicateBody = f'return ({predicateBody})(...args)'\n        else:\n            self._predicateBody = f'return {predicateBody}'\n        self._args = args\n        self._runCount = 0\n        self._terminated = False\n        self._timeoutError = False\n        frame._waitTasks.add(self)\n\n        self.promise = self._loop.create_future()\n\n        async def timer(timeout: Union[int, float]) -> None:\n            await asyncio.sleep(timeout / 1000)\n            self._timeoutError = True\n            self.terminate(TimeoutError(\n                f'Waiting for {title} failed: timeout {timeout}ms exceeds.'\n            ))\n\n        if timeout:\n            self._timeoutTimer = self._loop.create_task(timer(self._timeout))\n        self._runningTask = self._loop.create_task(self.rerun())\n\n    def __await__(self) -> Generator:\n        \"\"\"Make this class **awaitable**.\"\"\"\n        result = yield from self.promise\n        if isinstance(result, Exception):\n            raise result\n        return result\n\n    def terminate(self, error: Exception) -> None:\n        \"\"\"Terminate this task.\"\"\"\n        self._terminated = True\n        if not self.promise.done():\n            self.promise.set_result(error)\n        self._cleanup()\n\n    async def rerun(self) -> None:  # noqa: C901\n        \"\"\"Start polling.\"\"\"\n        runCount = self._runCount = self._runCount + 1\n        success: Optional[JSHandle] = None\n        error = None\n\n        try:\n            context = await self._frame.executionContext()\n            if context is None:\n                raise PageError('No execution context.')\n            success = await context.evaluateHandle(\n                waitForPredicatePageFunction,\n                self._predicateBody,\n                self._polling,\n                self._timeout,\n                *self._args,\n            )\n        except Exception as e:\n            error = e\n\n        if self.promise.done():\n            return\n\n        if self._terminated or runCount != self._runCount:\n            if success:\n                await success.dispose()\n            return\n\n        # Add try/except referring to puppeteer.\n        try:\n            if not error and success and (\n                    await self._frame.evaluate('s => !s', success)):\n                await success.dispose()\n                return\n        except NetworkError:\n            if success is not None:\n                await success.dispose()\n            return\n\n        # page is navigated and context is destroyed.\n        # Try again in the new execution context.\n        if (isinstance(error, NetworkError) and\n                'Execution context was destroyed' in error.args[0]):\n            return\n\n        # Try again in the new execution context.\n        if (isinstance(error, NetworkError) and\n                'Cannot find context with specified id' in error.args[0]):\n            return\n\n        if error:\n            self.promise.set_exception(error)\n        else:\n            self.promise.set_result(success)\n\n        self._cleanup()\n\n    def _cleanup(self) -> None:\n        if self._timeout and not self._timeoutError:\n            self._timeoutTimer.cancel()\n        self._frame._waitTasks.remove(self)\n\n\nwaitForPredicatePageFunction = \"\"\"\nasync function waitForPredicatePageFunction(predicateBody, polling, timeout, ...args) {\n  const predicate = new Function('...args', predicateBody);\n  let timedOut = false;\n  if (timeout)\n    setTimeout(() => timedOut = true, timeout);\n  if (polling === 'raf')\n    return await pollRaf();\n  if (polling === 'mutation')\n    return await pollMutation();\n  if (typeof polling === 'number')\n    return await pollInterval(polling);\n\n  /**\n   * @return {!Promise<*>}\n   */\n  function pollMutation() {\n    const success = predicate.apply(null, args);\n    if (success)\n      return Promise.resolve(success);\n\n    let fulfill;\n    const result = new Promise(x => fulfill = x);\n    const observer = new MutationObserver(mutations => {\n      if (timedOut) {\n        observer.disconnect();\n        fulfill();\n      }\n      const success = predicate.apply(null, args);\n      if (success) {\n        observer.disconnect();\n        fulfill(success);\n      }\n    });\n    observer.observe(document, {\n      childList: true,\n      subtree: true,\n      attributes: true\n    });\n    return result;\n  }\n\n  /**\n   * @return {!Promise<*>}\n   */\n  function pollRaf() {\n    let fulfill;\n    const result = new Promise(x => fulfill = x);\n    onRaf();\n    return result;\n\n    function onRaf() {\n      if (timedOut) {\n        fulfill();\n        return;\n      }\n      const success = predicate.apply(null, args);\n      if (success)\n        fulfill(success);\n      else\n        requestAnimationFrame(onRaf);\n    }\n  }\n\n  /**\n   * @param {number} pollInterval\n   * @return {!Promise<*>}\n   */\n  function pollInterval(pollInterval) {\n    let fulfill;\n    const result = new Promise(x => fulfill = x);\n    onTimeout();\n    return result;\n\n    function onTimeout() {\n      if (timedOut) {\n        fulfill();\n        return;\n      }\n      const success = predicate.apply(null, args);\n      if (success)\n        fulfill(success);\n      else\n        setTimeout(onTimeout, pollInterval);\n    }\n  }\n}\n\"\"\"  # noqa: E501\n"
  },
  {
    "path": "pyppeteer/helper.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"Helper functions.\"\"\"\n\nimport asyncio\nimport json\nimport logging\nimport math\nfrom typing import Any, Awaitable, Callable, Dict, List\n\nfrom pyee import EventEmitter\n\nimport pyppeteer\nfrom pyppeteer.connection import CDPSession\nfrom pyppeteer.errors import ElementHandleError, TimeoutError\n\nlogger = logging.getLogger(__name__)\n\n\ndef debugError(_logger: logging.Logger, msg: Any) -> None:\n    \"\"\"Log error messages.\"\"\"\n    if pyppeteer.DEBUG:\n        _logger.error(msg)\n    else:\n        _logger.debug(msg)\n\n\ndef evaluationString(fun: str, *args: Any) -> str:\n    \"\"\"Convert function and arguments to str.\"\"\"\n    _args = ', '.join([\n        json.dumps('undefined' if arg is None else arg) for arg in args\n    ])\n    expr = f'({fun})({_args})'\n    return expr\n\n\ndef getExceptionMessage(exceptionDetails: dict) -> str:\n    \"\"\"Get exception message from `exceptionDetails` object.\"\"\"\n    exception = exceptionDetails.get('exception')\n    if exception:\n        return exception.get('description') or exception.get('value')\n    message = exceptionDetails.get('text', '')\n    stackTrace = exceptionDetails.get('stackTrace', dict())\n    if stackTrace:\n        for callframe in stackTrace.get('callFrames'):\n            location = (\n                str(callframe.get('url', '')) + ':' +\n                str(callframe.get('lineNumber', '')) + ':' +\n                str(callframe.get('columnNumber'))\n            )\n            functionName = callframe.get('functionName', '<anonymous>')\n            message = message + f'\\n    at {functionName} ({location})'\n    return message\n\n\ndef addEventListener(emitter: EventEmitter, eventName: str, handler: Callable\n                     ) -> Dict[str, Any]:\n    \"\"\"Add handler to the emitter and return emitter/handler.\"\"\"\n    emitter.on(eventName, handler)\n    return {'emitter': emitter, 'eventName': eventName, 'handler': handler}\n\n\ndef removeEventListeners(listeners: List[dict]) -> None:\n    \"\"\"Remove listeners from emitter.\"\"\"\n    for listener in listeners:\n        emitter = listener['emitter']\n        eventName = listener['eventName']\n        handler = listener['handler']\n        emitter.remove_listener(eventName, handler)\n    listeners.clear()\n\n\nunserializableValueMap = {\n    '-0': -0,\n    'NaN': None,\n    None: None,\n    'Infinity': math.inf,\n    '-Infinity': -math.inf,\n}\n\n\ndef valueFromRemoteObject(remoteObject: Dict) -> Any:\n    \"\"\"Serialize value of remote object.\"\"\"\n    if remoteObject.get('objectId'):\n        raise ElementHandleError('Cannot extract value when objectId is given')\n    value = remoteObject.get('unserializableValue')\n    if value:\n        if value == '-0':\n            return -0\n        elif value == 'NaN':\n            return None\n        elif value == 'Infinity':\n            return math.inf\n        elif value == '-Infinity':\n            return -math.inf\n        else:\n            raise ElementHandleError(\n                'Unsupported unserializable value: {}'.format(value))\n    return remoteObject.get('value')\n\n\ndef releaseObject(client: CDPSession, remoteObject: dict\n                  ) -> Awaitable:\n    \"\"\"Release remote object.\"\"\"\n    objectId = remoteObject.get('objectId')\n    fut_none = client._loop.create_future()\n    fut_none.set_result(None)\n    if not objectId:\n        return fut_none\n    try:\n        return client.send('Runtime.releaseObject', {\n            'objectId': objectId\n        })\n    except Exception as e:\n        # Exceptions might happen in case of a page been navigated or closed.\n        # Swallow these since they are harmless and we don't leak anything in this case.  # noqa\n        debugError(logger, e)\n    return fut_none\n\n\ndef waitForEvent(emitter: EventEmitter, eventName: str,  # noqa: C901\n                 predicate: Callable[[Any], bool], timeout: float,\n                 loop: asyncio.AbstractEventLoop) -> Awaitable:\n    \"\"\"Wait for an event emitted from the emitter.\"\"\"\n    promise = loop.create_future()\n\n    def resolveCallback(target: Any) -> None:\n        promise.set_result(target)\n\n    def rejectCallback(exception: Exception) -> None:\n        promise.set_exception(exception)\n\n    async def timeoutTimer() -> None:\n        await asyncio.sleep(timeout / 1000)\n        rejectCallback(\n            TimeoutError('Timeout exceeded while waiting for event'))\n\n    def _listener(target: Any) -> None:\n        if not predicate(target):\n            return\n        cleanup()\n        resolveCallback(target)\n\n    listener = addEventListener(emitter, eventName, _listener)\n    if timeout:\n        eventTimeout = loop.create_task(timeoutTimer())\n\n    def cleanup() -> None:\n        removeEventListeners([listener])\n        if timeout:\n            eventTimeout.cancel()\n\n    return promise\n\n\ndef get_positive_int(obj: dict, name: str) -> int:\n    \"\"\"Get and check the value of name in obj is positive integer.\"\"\"\n    value = obj[name]\n    if not isinstance(value, int):\n        raise TypeError(\n            f'{name} must be integer: {type(value)}')\n    elif value < 0:\n        raise ValueError(\n            f'{name} must be positive integer: {value}')\n    return value\n\n\ndef is_jsfunc(func: str) -> bool:  # not in puppeteer\n    \"\"\"Heuristically check function or expression.\"\"\"\n    func = func.strip()\n    if func.startswith('function') or func.startswith('async '):\n        return True\n    elif '=>' in func:\n        return True\n    return False\n"
  },
  {
    "path": "pyppeteer/input.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"Keyboard and Mouse module.\"\"\"\n\nimport asyncio\nfrom typing import Any, Dict, TYPE_CHECKING\n\nfrom pyppeteer.connection import CDPSession\nfrom pyppeteer.errors import PyppeteerError\nfrom pyppeteer.us_keyboard_layout import keyDefinitions\nfrom pyppeteer.util import merge_dict\n\nif TYPE_CHECKING:\n    from typing import Set  # noqa: F401\n\n\nclass Keyboard(object):\n    \"\"\"Keyboard class provides as api for managing a virtual keyboard.\n\n    The high level api is :meth:`type`, which takes raw characters and\n    generate proper keydown, keypress/input, and keyup events on your page.\n\n    For finer control, you can use :meth:`down`, :meth:`up`, and\n    :meth:`sendCharacter` to manually fire events as if they were generated\n    from a real keyboard.\n\n    An example of holding down ``Shift`` in order to select and delete some\n    text:\n\n    .. code::\n\n        await page.keyboard.type('Hello, World!')\n        await page.keyboard.press('ArrowLeft')\n\n        await page.keyboard.down('Shift')\n        for i in ' World':\n            await page.keyboard.press('ArrowLeft')\n        await page.keyboard.up('Shift')\n\n        await page.keyboard.press('Backspace')\n        # Result text will end up saying 'Hello!'.\n\n    An example of pressing ``A``:\n\n    .. code::\n\n        await page.keyboard.down('Shift')\n        await page.keyboard.press('KeyA')\n        await page.keyboard.up('Shift')\n    \"\"\"\n\n    def __init__(self, client: CDPSession) -> None:\n        self._client = client\n        self._modifiers = 0\n        self._pressedKeys: Set[str] = set()\n\n    async def down(self, key: str, options: dict = None, **kwargs: Any\n                   ) -> None:\n        \"\"\"Dispatch a ``keydown`` event with ``key``.\n\n        If ``key`` is a single character and no modifier keys besides ``Shift``\n        are being held down, and a ``keypress``/``input`` event will also\n        generated. The ``text`` option can be specified to force an ``input``\n        event to be generated.\n\n        If ``key`` is a modifier key, like ``Shift``, ``Meta``, or ``Alt``,\n        subsequent key presses will be sent with that modifier active. To\n        release the modifier key, use :meth:`up` method.\n\n        :arg str key: Name of key to press, such as ``ArrowLeft``.\n        :arg dict options: Option can have ``text`` field, and if this option\n            specified, generate an input event with this text.\n\n        .. note::\n            Modifier keys DO influence :meth:`down`. Holding down ``shift``\n            will type the text in upper case.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n\n        description = self._keyDescriptionForString(key)\n        autoRepeat = description['code'] in self._pressedKeys\n        self._pressedKeys.add(description['code'])\n        self._modifiers |= self._modifierBit(description['key'])\n\n        text = options.get('text')\n        if text is None:\n            text = description['text']\n\n        await self._client.send('Input.dispatchKeyEvent', {\n            'type': 'keyDown' if text else 'rawKeyDown',\n            'modifiers': self._modifiers,\n            'windowsVirtualKeyCode': description['keyCode'],\n            'code': description['code'],\n            'key': description['key'],\n            'text': text,\n            'unmodifiedText': text,\n            'autoRepeat': autoRepeat,\n            'location': description['location'],\n            'isKeypad': description['location'] == 3,\n        })\n\n    def _modifierBit(self, key: str) -> int:\n        if key == 'Alt':\n            return 1\n        if key == 'Control':\n            return 2\n        if key == 'Meta':\n            return 4\n        if key == 'Shift':\n            return 8\n        return 0\n\n    def _keyDescriptionForString(self, keyString: str) -> Dict:  # noqa: C901\n        shift = self._modifiers & 8\n        description = {\n            'key': '',\n            'keyCode': 0,\n            'code': '',\n            'text': '',\n            'location': 0,\n        }\n\n        definition: Dict = keyDefinitions.get(keyString)  # type: ignore\n        if not definition:\n            raise PyppeteerError(f'Unknown key: {keyString}')\n\n        if 'key' in definition:\n            description['key'] = definition['key']\n        if shift and definition.get('shiftKey'):\n            description['key'] = definition['shiftKey']\n\n        if 'keyCode' in definition:\n            description['keyCode'] = definition['keyCode']\n        if shift and definition.get('shiftKeyCode'):\n            description['keyCode'] = definition['shiftKeyCode']\n\n        if 'code' in definition:\n            description['code'] = definition['code']\n\n        if 'location' in definition:\n            description['location'] = definition['location']\n\n        if len(description['key']) == 1:  # type: ignore\n            description['text'] = description['key']\n\n        if 'text' in definition:\n            description['text'] = definition['text']\n        if shift and definition.get('shiftText'):\n            description['text'] = definition['shiftText']\n\n        if self._modifiers & ~8:\n            description['text'] = ''\n\n        return description\n\n    async def up(self, key: str) -> None:\n        \"\"\"Dispatch a ``keyup`` event of the ``key``.\n\n        :arg str key: Name of key to release, such as ``ArrowLeft``.\n        \"\"\"\n        description = self._keyDescriptionForString(key)\n\n        self._modifiers &= ~self._modifierBit(description['key'])\n        if description['code'] in self._pressedKeys:\n            self._pressedKeys.remove(description['code'])\n        await self._client.send('Input.dispatchKeyEvent', {\n            'type': 'keyUp',\n            'modifiers': self._modifiers,\n            'key': description['key'],\n            'windowsVirtualKeyCode': description['keyCode'],\n            'code': description['code'],\n            'location': description['location'],\n        })\n\n    async def sendCharacter(self, char: str) -> None:\n        \"\"\"Send character into the page.\n\n        This method dispatches a ``keypress`` and ``input`` event. This does\n        not send a ``keydown`` or ``keyup`` event.\n\n        .. note::\n            Modifier keys DO NOT effect :meth:`sendCharacter`. Holding down\n            ``shift`` will not type the text in upper case.\n        \"\"\"\n        await self._client.send('Input.insertText', {'text': char})\n\n    async def type(self, text: str, options: Dict = None, **kwargs: Any\n                   ) -> None:\n        \"\"\"Type characters into a focused element.\n\n        This method sends ``keydown``, ``keypress``/``input``, and ``keyup``\n        event for each character in the ``text``.\n\n        To press a special key, like ``Control`` or ``ArrowDown``, use\n        :meth:`press` method.\n\n        :arg str text: Text to type into a focused element.\n        :arg dict options: Options can have ``delay`` (int|float) field, which\n          specifies time to wait between key presses in milliseconds. Defaults\n          to 0.\n\n        .. note::\n            Modifier keys DO NOT effect :meth:`type`. Holding down ``shift``\n            will not type the text in upper case.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n        delay = options.get('delay', 0)\n        for char in text:\n            if char in keyDefinitions:\n                await self.press(char, {'delay': delay})\n            else:\n                await self.sendCharacter(char)\n            if delay:\n                await asyncio.sleep(delay / 1000)\n\n    async def press(self, key: str, options: Dict = None, **kwargs: Any\n                    ) -> None:\n        \"\"\"Press ``key``.\n\n        If ``key`` is a single character and no modifier keys besides\n        ``Shift`` are being held down, a ``keypress``/``input`` event will also\n        generated. The ``text`` option can be specified to force an input event\n        to be generated.\n\n        :arg str key: Name of key to press, such as ``ArrowLeft``.\n\n        This method accepts the following options:\n\n        * ``text`` (str): If specified, generates an input event with this\n          text.\n        * ``delay`` (int|float): Time to wait between ``keydown`` and\n          ``keyup``. Defaults to 0.\n\n        .. note::\n            Modifier keys DO effect :meth:`press`. Holding down ``Shift`` will\n            type the text in upper case.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n\n        await self.down(key, options)\n        if 'delay' in options:\n            await asyncio.sleep(options['delay'] / 1000)\n        await self.up(key)\n\n\nclass Mouse(object):\n    \"\"\"Mouse class.\n\n    The :class:`Mouse` operates in main-frame CSS pixels relative to the\n    top-left corner of the viewport.\n    \"\"\"\n\n    def __init__(self, client: CDPSession, keyboard: Keyboard) -> None:\n        self._client = client\n        self._keyboard = keyboard\n        self._x = 0.0\n        self._y = 0.0\n        self._button = 'none'\n\n    async def move(self, x: float, y: float, options: dict = None,\n                   **kwargs: Any) -> None:\n        \"\"\"Move mouse cursor (dispatches a ``mousemove`` event).\n\n        Options can accepts ``steps`` (int) field. If this ``steps`` option\n        specified, Sends intermediate ``mousemove`` events. Defaults to 1.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n        fromX = self._x\n        fromY = self._y\n        self._x = x\n        self._y = y\n        steps = options.get('steps', 1)\n        for i in range(1, steps + 1):\n            x = round(fromX + (self._x - fromX) * (i / steps))\n            y = round(fromY + (self._y - fromY) * (i / steps))\n            await self._client.send('Input.dispatchMouseEvent', {\n                'type': 'mouseMoved',\n                'button': self._button,\n                'x': x,\n                'y': y,\n                'modifiers': self._keyboard._modifiers,\n            })\n\n    async def click(self, x: float, y: float, options: dict = None,\n                    **kwargs: Any) -> None:\n        \"\"\"Click button at (``x``, ``y``).\n\n        Shortcut to :meth:`move`, :meth:`down`, and :meth:`up`.\n\n        This method accepts the following options:\n\n        * ``button`` (str): ``left``, ``right``, or ``middle``, defaults to\n          ``left``.\n        * ``clickCount`` (int): defaults to 1.\n        * ``delay`` (int|float): Time to wait between ``mousedown`` and\n          ``mouseup`` in milliseconds. Defaults to 0.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n        await self.move(x, y)\n        await self.down(options)\n        if options and options.get('delay'):\n            await asyncio.sleep(options.get('delay', 0) / 1000)\n        await self.up(options)\n\n    async def down(self, options: dict = None, **kwargs: Any) -> None:\n        \"\"\"Press down button (dispatches ``mousedown`` event).\n\n        This method accepts the following options:\n\n        * ``button`` (str): ``left``, ``right``, or ``middle``, defaults to\n          ``left``.\n        * ``clickCount`` (int): defaults to 1.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n        self._button = options.get('button', 'left')\n        await self._client.send('Input.dispatchMouseEvent', {\n            'type': 'mousePressed',\n            'button': self._button,\n            'x': self._x,\n            'y': self._y,\n            'modifiers': self._keyboard._modifiers,\n            'clickCount': options.get('clickCount') or 1,\n        })\n\n    async def up(self, options: dict = None, **kwargs: Any) -> None:\n        \"\"\"Release pressed button (dispatches ``mouseup`` event).\n\n        This method accepts the following options:\n\n        * ``button`` (str): ``left``, ``right``, or ``middle``, defaults to\n          ``left``.\n        * ``clickCount`` (int): defaults to 1.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n        self._button = 'none'\n        await self._client.send('Input.dispatchMouseEvent', {\n            'type': 'mouseReleased',\n            'button': options.get('button', 'left'),\n            'x': self._x,\n            'y': self._y,\n            'modifiers': self._keyboard._modifiers,\n            'clickCount': options.get('clickCount') or 1,\n        })\n\n\nclass Touchscreen(object):\n    \"\"\"Touchscreen class.\"\"\"\n\n    def __init__(self, client: CDPSession, keyboard: Keyboard) -> None:\n        \"\"\"Make new touchscreen object.\"\"\"\n        self._client = client\n        self._keyboard = keyboard\n\n    async def tap(self, x: float, y: float) -> None:\n        \"\"\"Tap (``x``, ``y``).\n\n        Dispatches a ``touchstart`` and ``touchend`` event.\n        \"\"\"\n        touchPoints = [{'x': round(x), 'y': round(y)}]\n        await self._client.send('Input.dispatchTouchEvent', {\n            'type': 'touchStart',\n            'touchPoints': touchPoints,\n            'modifiers': self._keyboard._modifiers,\n        })\n        await self._client.send('Input.dispatchTouchEvent', {\n            'type': 'touchEnd',\n            'touchPoints': [],\n            'modifiers': self._keyboard._modifiers,\n        })\n"
  },
  {
    "path": "pyppeteer/launcher.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"Chromium process launcher module.\"\"\"\n\nimport asyncio\nimport atexit\nfrom copy import copy\nimport json\nfrom urllib.request import urlopen\nfrom urllib.error import URLError\nfrom http.client import HTTPException\nimport logging\nimport os\nimport os.path\nfrom pathlib import Path\nimport shutil\nimport signal\nimport subprocess\nimport sys\nimport tempfile\nimport time\nfrom typing import Any, Dict, List, TYPE_CHECKING\n\nfrom pyppeteer import __pyppeteer_home__\nfrom pyppeteer.browser import Browser\nfrom pyppeteer.connection import Connection\nfrom pyppeteer.chromium_downloader import current_platform\nfrom pyppeteer.errors import BrowserError\nfrom pyppeteer.helper import addEventListener, debugError, removeEventListeners\nfrom pyppeteer.target import Target\nfrom pyppeteer.util import check_chromium, chromium_executable\nfrom pyppeteer.util import download_chromium, merge_dict, get_free_port\n\nif TYPE_CHECKING:\n    from typing import Optional  # noqa: F401\n\nlogger = logging.getLogger(__name__)\n\npyppeteer_home = Path(__pyppeteer_home__)\nCHROME_PROFILE_PATH = pyppeteer_home / '.dev_profile'\n\nDEFAULT_ARGS = [\n    '--disable-background-networking',\n    '--disable-background-timer-throttling',\n    '--disable-breakpad',\n    '--disable-browser-side-navigation',\n    '--disable-client-side-phishing-detection',\n    '--disable-default-apps',\n    '--disable-dev-shm-usage',\n    '--disable-extensions',\n    '--disable-features=site-per-process',\n    '--disable-hang-monitor',\n    '--disable-popup-blocking',\n    '--disable-prompt-on-repost',\n    '--disable-sync',\n    '--disable-translate',\n    '--metrics-recording-only',\n    '--no-first-run',\n    '--safebrowsing-disable-auto-update',\n    '--enable-automation',\n    '--password-store=basic',\n    '--use-mock-keychain',\n]\n\n\nclass Launcher(object):\n    \"\"\"Chrome process launcher class.\"\"\"\n\n    def __init__(self, options: Dict[str, Any] = None,  # noqa: C901\n                 **kwargs: Any) -> None:\n        \"\"\"Make new launcher.\"\"\"\n        options = merge_dict(options, kwargs)\n\n        self.port = get_free_port()\n        self.url = f'http://127.0.0.1:{self.port}'\n        self._loop = options.get('loop', asyncio.get_event_loop())\n        self.chromeClosed = True\n\n        ignoreDefaultArgs = options.get('ignoreDefaultArgs', False)\n        args: List[str] = options.get('args', list())\n        self.dumpio = options.get('dumpio', False)\n        executablePath = options.get('executablePath')\n        self.env = options.get('env')\n        self.handleSIGINT = options.get('handleSIGINT', True)\n        self.handleSIGTERM = options.get('handleSIGTERM', True)\n        self.handleSIGHUP = options.get('handleSIGHUP', True)\n        self.ignoreHTTPSErrors = options.get('ignoreHTTPSErrors', False)\n        self.defaultViewport = options.get('defaultViewport', {'width': 800, 'height': 600})  # noqa: E501\n        self.slowMo = options.get('slowMo', 0)\n        self.timeout = options.get('timeout', 30000)\n        self.autoClose = options.get('autoClose', True)\n\n        logLevel = options.get('logLevel')\n        if logLevel:\n            logging.getLogger('pyppeteer').setLevel(logLevel)\n\n        self.chromeArguments: List[str] = list()\n        if not ignoreDefaultArgs:\n            self.chromeArguments.extend(defaultArgs(options))\n        elif isinstance(ignoreDefaultArgs, list):\n            self.chromeArguments.extend(filter(lambda arg: arg not in ignoreDefaultArgs, defaultArgs(options), ))\n        else:\n            self.chromeArguments.extend(args)\n\n        self.temporaryUserDataDir: Optional[str] = None\n\n        if not any(arg for arg in self.chromeArguments if arg.startswith('--remote-debugging-')):\n            self.chromeArguments.append(f'--remote-debugging-port={self.port}')\n\n        if not any(arg for arg in self.chromeArguments if arg.startswith('--user-data-dir')):\n            if not CHROME_PROFILE_PATH.exists():\n                CHROME_PROFILE_PATH.mkdir(parents=True)\n            self.temporaryUserDataDir = tempfile.mkdtemp(dir=str(CHROME_PROFILE_PATH))  # noqa: E501\n            self.chromeArguments.append(f'--user-data-dir={self.temporaryUserDataDir}')  # noqa: E501\n\n        self.chromeExecutable = executablePath\n        if not self.chromeExecutable:\n            if not check_chromium():\n                download_chromium()\n            self.chromeExecutable = str(chromium_executable())\n\n        self.cmd = [self.chromeExecutable] + self.chromeArguments\n\n    def _cleanup_tmp_user_data_dir(self) -> None:\n        for retry in range(100):\n            if self.temporaryUserDataDir and os.path.exists(self.temporaryUserDataDir):\n                shutil.rmtree(self.temporaryUserDataDir, ignore_errors=True)\n                if os.path.exists(self.temporaryUserDataDir):\n                    time.sleep(0.01)\n            else:\n                break\n        else:\n            raise IOError('Unable to remove Temporary User Data')\n\n    async def launch(self) -> Browser:  # noqa: C901\n        \"\"\"Start chrome process and return `Browser` object.\"\"\"\n        self.chromeClosed = False\n        self.connection: Optional[Connection] = None\n\n        options = dict()\n        options['env'] = self.env\n        if not self.dumpio:\n            # discard stdout, it's never read in any case.\n            options['stdout'] = subprocess.DEVNULL\n            options['stderr'] = subprocess.STDOUT\n\n        self.proc = subprocess.Popen(  # type: ignore\n            self.cmd, **options, )\n\n        def _close_process(*args: Any, **kwargs: Any) -> None:\n            if not self.chromeClosed:\n                self._loop.run_until_complete(self.killChrome())\n\n        # don't forget to close browser process\n        if self.autoClose:\n            atexit.register(_close_process)\n        if self.handleSIGINT:\n            signal.signal(signal.SIGINT, _close_process)\n        if self.handleSIGTERM:\n            signal.signal(signal.SIGTERM, _close_process)\n        if not sys.platform.startswith('win'):\n            # SIGHUP is not defined on windows\n            if self.handleSIGHUP:\n                signal.signal(signal.SIGHUP, _close_process)\n\n        connectionDelay = self.slowMo\n        self.browserWSEndpoint = get_ws_endpoint(self.url)\n        logger.info(f'Browser listening on: {self.browserWSEndpoint}')\n        self.connection = Connection(self.browserWSEndpoint, self._loop, connectionDelay, )\n        browser = await Browser.create(self.connection, [], self.ignoreHTTPSErrors, self.defaultViewport, self.proc,\n                                       self.killChrome)\n        await self.ensureInitialPage(browser)\n        return browser\n\n    async def ensureInitialPage(self, browser: Browser) -> None:\n        \"\"\"Wait for initial page target to be created.\"\"\"\n        for target in browser.targets():\n            if target.type == 'page':\n                return\n\n        initialPagePromise = self._loop.create_future()\n\n        def initialPageCallback() -> None:\n            initialPagePromise.set_result(True)\n\n        def check_target(target: Target) -> None:\n            if target.type == 'page':\n                initialPageCallback()\n\n        listeners = [addEventListener(browser, 'targetcreated', check_target)]\n        await initialPagePromise\n        removeEventListeners(listeners)\n\n    def waitForChromeToClose(self) -> None:\n        \"\"\"Terminate chrome.\"\"\"\n        if self.proc.poll() is None and not self.chromeClosed:\n            self.chromeClosed = True\n            try:\n                self.proc.terminate()\n                self.proc.wait()\n            except Exception:\n                # browser process may be already closed\n                pass\n\n    async def killChrome(self) -> None:\n        \"\"\"Terminate chromium process.\"\"\"\n        logger.info('terminate chrome process...')\n        if self.connection and self.connection._connected:\n            try:\n                await self.connection.send('Browser.close')\n                await self.connection.dispose()\n            except Exception as e:\n                # ignore errors on browser termination process\n                debugError(logger, e)\n        if self.temporaryUserDataDir and os.path.exists(self.temporaryUserDataDir):  # noqa: E501\n            # Force kill chrome only when using temporary userDataDir\n            self.waitForChromeToClose()\n            self._cleanup_tmp_user_data_dir()\n\n\ndef get_ws_endpoint(url) -> str:\n    url = url + '/json/version'\n    timeout = time.time() + 30\n    while (True):\n        if time.time() > timeout:\n            raise BrowserError('Browser closed unexpectedly:\\n')\n        try:\n            with urlopen(url) as f:\n                data = json.loads(f.read().decode())\n            break\n        except (URLError, HTTPException):\n            pass\n        time.sleep(0.1)\n\n    return data['webSocketDebuggerUrl']\n\n\nasync def launch(options: dict = None, **kwargs: Any) -> Browser:\n    \"\"\"Start chrome process and return :class:`~pyppeteer.browser.Browser`.\n    This function is a shortcut to :meth:`Launcher(options, **kwargs).launch`.\n    Available options are:\n    * ``ignoreHTTPSErrors`` (bool): Whether to ignore HTTPS errors. Defaults to\n      ``False``.\n    * ``headless`` (bool): Whether to run browser in headless mode. Defaults to\n      ``True`` unless ``appMode`` or ``devtools`` options is ``True``.\n    * ``executablePath`` (str): Path to a Chromium or Chrome executable to run\n      instead of default bundled Chromium.\n    * ``slowMo`` (int|float): Slow down pyppeteer operations by the specified\n      amount of milliseconds.\n    * ``defaultViewport`` (dict): Set a consistent viewport for each page.\n      Defaults to an 800x600 viewport. ``None`` disables default viewport.\n      * ``width`` (int): page width in pixels.\n      * ``height`` (int): page height in pixels.\n      * ``deviceScaleFactor`` (int|float): Specify device scale factor (can be\n        thought as dpr). Defaults to ``1``.\n      * ``isMobile`` (bool): Whether the ``meta viewport`` tag is taken into\n        account. Defaults to ``False``.\n      * ``hasTouch`` (bool): Specify if viewport supports touch events.\n        Defaults to ``False``.\n      * ``isLandscape`` (bool): Specify if viewport is in landscape mode.\n        Defaults to ``False``.\n    * ``args`` (List[str]): Additional arguments (flags) to pass to the browser\n      process.\n    * ``ignoreDefaultArgs`` (bool or List[str]): If ``True``, do not use\n      :func:`~pyppeteer.defaultArgs`. If list is given, then filter out given\n      default arguments. Dangerous option; use with care. Defaults to\n      ``False``.\n    * ``handleSIGINT`` (bool): Close the browser process on Ctrl+C. Defaults to\n      ``True``.\n    * ``handleSIGTERM`` (bool): Close the browser process on SIGTERM. Defaults\n      to ``True``.\n    * ``handleSIGHUP`` (bool): Close the browser process on SIGHUP. Defaults to\n      ``True``.\n    * ``dumpio`` (bool): Whether to pipe the browser process stdout and stderr\n      into ``process.stdout`` and ``process.stderr``. Defaults to ``False``.\n    * ``userDataDir`` (str): Path to a user data directory.\n    * ``env`` (dict): Specify environment variables that will be visible to the\n      browser. Defaults to same as python process.\n    * ``devtools`` (bool): Whether to auto-open a DevTools panel for each tab.\n      If this option is ``True``, the ``headless`` option will be set\n      ``False``.\n    * ``logLevel`` (int|str): Log level to print logs. Defaults to same as the\n      root logger.\n    * ``autoClose`` (bool): Automatically close browser process when script\n      completed. Defaults to ``True``.\n    * ``loop`` (asyncio.AbstractEventLoop): Event loop (**experimental**).\n    * ``appMode`` (bool): Deprecated.\n    This function combines 3 steps:\n    1. Infer a set of flags to launch chromium with using\n       :func:`~pyppeteer.defaultArgs`.\n    2. Launch browser and start managing its process according to the\n       ``executablePath``, ``handleSIGINT``, ``dumpio``, and other options.\n    3. Create an instance of :class:`~pyppeteer.browser.Browser` class and\n       initialize it with ``defaultViewport``, ``slowMo``, and\n       ``ignoreHTTPSErrors``.\n    ``ignoreDefaultArgs`` option can be used to customize behavior on the (1)\n    step. For example, to filter out ``--mute-audio`` from default arguments:\n    .. code::\n        browser = await launch(ignoreDefaultArgs=['--mute-audio'])\n    .. note::\n        Pyppeteer can also be used to control the Chrome browser, but it works\n        best with the version of Chromium it is bundled with. There is no\n        guarantee it will work with any other version. Use ``executablePath``\n        option with extreme caution.\n    \"\"\"\n    return await Launcher(options, **kwargs).launch()\n\n\nasync def connect(options: dict = None, **kwargs: Any) -> Browser:\n    \"\"\"Connect to the existing chrome.\n    ``browserWSEndpoint`` or ``browserURL`` option is necessary to connect to\n    the chrome. The format of ``browserWSEndpoint`` is\n    ``ws://${host}:${port}/devtools/browser/<id>`` and format of ``browserURL``\n    is ``http://127.0.0.1:9222```.\n    The value of ``browserWSEndpoint`` can get by :attr:`~pyppeteer.browser.Browser.wsEndpoint`.\n    Available options are:\n    * ``browserWSEndpoint`` (str): A browser websocket endpoint to connect to.\n    * ``browserURL`` (str): A browser URL to connect to.\n    * ``ignoreHTTPSErrors`` (bool): Whether to ignore HTTPS errors. Defaults to\n      ``False``.\n    * ``defaultViewport`` (dict): Set a consistent viewport for each page.\n      Defaults to an 800x600 viewport. ``None`` disables default viewport.\n      * ``width`` (int): page width in pixels.\n      * ``height`` (int): page height in pixels.\n      * ``deviceScaleFactor`` (int|float): Specify device scale factor (can be\n        thought as dpr). Defaults to ``1``.\n      * ``isMobile`` (bool): Whether the ``meta viewport`` tag is taken into\n        account. Defaults to ``False``.\n      * ``hasTouch`` (bool): Specify if viewport supports touch events.\n        Defaults to ``False``.\n      * ``isLandscape`` (bool): Specify if viewport is in landscape mode.\n        Defaults to ``False``.\n    * ``slowMo`` (int|float): Slow down pyppeteer's by the specified amount of\n      milliseconds.\n    * ``logLevel`` (int|str): Log level to print logs. Defaults to same as the\n      root logger.\n    * ``loop`` (asyncio.AbstractEventLoop): Event loop (**experimental**).\n    \"\"\"\n    options = merge_dict(options, kwargs)\n    logLevel = options.get('logLevel')\n    if logLevel:\n        logging.getLogger('pyppeteer').setLevel(logLevel)\n\n    browserWSEndpoint = options.get('browserWSEndpoint')\n    if not browserWSEndpoint:\n        browserURL = options.get('browserURL')\n        if not browserURL:\n            raise BrowserError('Need `browserWSEndpoint` or `browserURL` option.')\n        browserWSEndpoint = get_ws_endpoint(browserURL)\n    connectionDelay = options.get('slowMo', 0)\n    connection = Connection(browserWSEndpoint, options.get('loop', asyncio.get_event_loop()), connectionDelay)\n    browserContextIds = (await connection.send('Target.getBrowserContexts')).get('browserContextIds', [])\n    ignoreHTTPSErrors = bool(options.get('ignoreHTTPSErrors', False))\n    defaultViewport = options.get('defaultViewport', {'width': 800, 'height': 600})\n    return await Browser.create(connection, browserContextIds, ignoreHTTPSErrors, defaultViewport, None,\n                                lambda: connection.send('Browser.close'))\n\n\ndef executablePath() -> str:\n    \"\"\"Get executable path of default chromium.\"\"\"\n    return str(chromium_executable())\n\n\ndef defaultArgs(options: Dict = None, **kwargs: Any) -> List[str]:  # noqa: C901,E501\n    \"\"\"Get the default flags the chromium will be launched with.\n    ``options`` or keyword arguments are set of configurable options to set on\n    the browser. Can have the following fields:\n    * ``headless`` (bool): Whether to run browser in headless mode. Defaults to\n      ``True`` unless the ``devtools`` option is ``True``.\n    * ``args`` (List[str]): Additional arguments to pass to the browser\n      instance. The list of chromium flags can be found\n      `here <http://peter.sh/experiments/chromium-command-line-switches/>`__.\n    * ``userDataDir`` (str): Path to a User Data Directory.\n    * ``devtools`` (bool): Whether to auto-open DevTools panel for each tab. If\n      this option is ``True``, the ``headless`` option will be set ``False``.\n    \"\"\"\n    options = merge_dict(options, kwargs)\n    devtools = options.get('devtools', False)\n    headless = options.get('headless', not devtools)\n    args = options.get('args', list())\n    userDataDir = options.get('userDataDir')\n    chromeArguments = copy(DEFAULT_ARGS)\n\n    if userDataDir:\n        chromeArguments.append(f'--user-data-dir={userDataDir}')\n    if devtools:\n        chromeArguments.append('--auto-open-devtools-for-tabs')\n    if headless:\n        chromeArguments.extend(('--headless', '--hide-scrollbars', '--mute-audio',))\n        if current_platform().startswith('win'):\n            chromeArguments.append('--disable-gpu')\n\n    if all(map(lambda arg: arg.startswith('-'), args)):  # type: ignore\n        chromeArguments.append('about:blank')\n    chromeArguments.extend(args)\n\n    return chromeArguments\n"
  },
  {
    "path": "pyppeteer/multimap.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"Multimap module.\"\"\"\n\nfrom collections import OrderedDict\nfrom typing import Any, List, Optional\n\n\nclass Multimap(object):\n    \"\"\"Multimap class.\"\"\"\n\n    def __init__(self) -> None:\n        \"\"\"Make new multimap.\"\"\"\n        # maybe defaultdict(set) is better\n        self._map: OrderedDict[Optional[str], List[Any]] = OrderedDict()\n\n    def set(self, key: Optional[str], value: Any) -> None:\n        \"\"\"Set value.\"\"\"\n        _set = self._map.get(key)\n        if not _set:\n            _set = list()\n            self._map[key] = _set\n        if value not in _set:\n            _set.append(value)\n\n    def get(self, key: Optional[str]) -> List[Any]:\n        \"\"\"Get values.\"\"\"\n        return self._map.get(key, list())\n\n    def has(self, key: Optional[str]) -> bool:\n        \"\"\"Check key is in this map.\"\"\"\n        return key in self._map\n\n    def hasValue(self, key: Optional[str], value: Any) -> bool:\n        \"\"\"Check value is in this map.\"\"\"\n        _set = self._map.get(key, list())\n        return value in _set\n\n    def size(self) -> int:\n        \"\"\"Length of this map.\"\"\"\n        return len(self._map)\n\n    def delete(self, key: Optional[str], value: Any) -> bool:\n        \"\"\"Delete value from key.\"\"\"\n        values = self.get(key)\n        result = value in values\n        if result:\n            values.remove(value)\n        if len(values) == 0:\n            self._map.pop(key)\n        return result\n\n    def deleteAll(self, key: Optional[str]) -> None:\n        \"\"\"Delete all value of the key.\"\"\"\n        self._map.pop(key, None)\n\n    def firstValue(self, key: Optional[str]) -> Any:\n        \"\"\"Get first value of the key.\"\"\"\n        _set = self._map.get(key)\n        if not _set:\n            return None\n        return _set[0]\n\n    def firstKey(self) -> Optional[str]:\n        \"\"\"Get first key.\"\"\"\n        return next(iter(self._map.keys()))\n\n    def valuesArray(self) -> List[Any]:\n        \"\"\"Get all values as list.\"\"\"\n        result: List[Any] = list()\n        for values in self._map.values():\n            result.extend(values)\n        return result\n\n    def clear(self) -> None:\n        \"\"\"Clear all entries of this map.\"\"\"\n        self._map.clear()\n"
  },
  {
    "path": "pyppeteer/navigator_watcher.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"Navigator Watcher module.\"\"\"\n\nimport asyncio\nimport concurrent.futures\nfrom typing import Any, Awaitable, Dict, List, Union\n\nfrom pyppeteer import helper\nfrom pyppeteer.errors import TimeoutError\nfrom pyppeteer.frame_manager import FrameManager, Frame\nfrom pyppeteer.util import merge_dict\n\n\nclass NavigatorWatcher:\n    \"\"\"NavigatorWatcher class.\"\"\"\n\n    def __init__(self, frameManager: FrameManager, frame: Frame, timeout: int,\n                 options: Dict = None, **kwargs: Any) -> None:\n        \"\"\"Make new navigator watcher.\"\"\"\n        options = merge_dict(options, kwargs)\n        self._validate_options(options)\n        self._frameManager = frameManager\n        self._frame = frame\n        self._initialLoaderId = frame._loaderId\n        self._timeout = timeout\n        self._hasSameDocumentNavigation = False\n        self._eventListeners = [\n            helper.addEventListener(\n                self._frameManager,\n                FrameManager.Events.LifecycleEvent,\n                self._checkLifecycleComplete,\n            ),\n            helper.addEventListener(\n                self._frameManager,\n                FrameManager.Events.FrameNavigatedWithinDocument,\n                self._navigatedWithinDocument,\n            ),\n            helper.addEventListener(\n                self._frameManager,\n                FrameManager.Events.FrameDetached,\n                self._checkLifecycleComplete,\n            ),\n        ]\n        self._loop = self._frameManager._client._loop\n        self._lifecycleCompletePromise = self._loop.create_future()\n\n        self._navigationPromise = self._loop.create_task(asyncio.wait([\n            self._lifecycleCompletePromise,\n            self._createTimeoutPromise(),\n        ], return_when=concurrent.futures.FIRST_COMPLETED))\n        self._navigationPromise.add_done_callback(\n            lambda fut: self._cleanup())\n\n    def _validate_options(self, options: Dict) -> None:  # noqa: C901\n        if 'networkIdleTimeout' in options:\n            raise ValueError(\n                '`networkIdleTimeout` option is no longer supported.')\n        if 'networkIdleInflight' in options:\n            raise ValueError(\n                '`networkIdleInflight` option is no longer supported.')\n        if options.get('waitUntil') == 'networkidle':\n            raise ValueError(\n                '`networkidle` option is no logner supported. '\n                'Use `networkidle2` instead.')\n        if options.get('waitUntil') == 'documentloaded':\n            import logging\n            logging.getLogger(__name__).warning(\n                '`documentloaded` option is no longer supported. '\n                'Use `domcontentloaded` instead.')\n        _waitUntil = options.get('waitUntil', 'load')\n        if isinstance(_waitUntil, list):\n            waitUntil = _waitUntil\n        elif isinstance(_waitUntil, str):\n            waitUntil = [_waitUntil]\n        else:\n            raise TypeError(\n                '`waitUntil` option should be str or list of str, '\n                f'but got type {type(_waitUntil)}'\n            )\n        self._expectedLifecycle: List[str] = []\n        for value in waitUntil:\n            protocolEvent = pyppeteerToProtocolLifecycle.get(value)\n            if protocolEvent is None:\n                raise ValueError(\n                    f'Unknown value for options.waitUntil: {value}')\n            self._expectedLifecycle.append(protocolEvent)\n\n    def _createTimeoutPromise(self) -> Awaitable[None]:\n        self._maximumTimer = self._loop.create_future()\n        if self._timeout:\n            errorMessage = f'Navigation Timeout Exceeded: {self._timeout} ms exceeded.'  # noqa: E501\n\n            async def _timeout_func() -> None:\n                await asyncio.sleep(self._timeout / 1000)\n                self._maximumTimer.set_exception(TimeoutError(errorMessage))\n\n            self._timeout_timer: Union[asyncio.Task, asyncio.Future] = self._loop.create_task(_timeout_func())  # noqa: E501\n        else:\n            self._timeout_timer = self._loop.create_future()\n        return self._maximumTimer\n\n    def navigationPromise(self) -> Any:\n        \"\"\"Return navigation promise.\"\"\"\n        return self._navigationPromise\n\n    def _navigatedWithinDocument(self, frame: Frame = None) -> None:\n        if frame != self._frame:\n            return\n        self._hasSameDocumentNavigation = True\n        self._checkLifecycleComplete()\n\n    def _checkLifecycleComplete(self, frame: Frame = None) -> None:\n        if (self._frame._loaderId == self._initialLoaderId and\n                not self._hasSameDocumentNavigation):\n            return\n        if not self._checkLifecycle(self._frame, self._expectedLifecycle):\n            return\n\n        if not self._lifecycleCompletePromise.done():\n            self._lifecycleCompletePromise.set_result(None)\n\n    def _checkLifecycle(self, frame: Frame, expectedLifecycle: List[str]\n                        ) -> bool:\n        for event in expectedLifecycle:\n            if event not in frame._lifecycleEvents:\n                return False\n        for child in frame.childFrames:\n            if not self._checkLifecycle(child, expectedLifecycle):\n                return False\n        return True\n\n    def cancel(self) -> None:\n        \"\"\"Cancel navigation.\"\"\"\n        self._cleanup()\n\n    def _cleanup(self) -> None:\n        helper.removeEventListeners(self._eventListeners)\n        self._lifecycleCompletePromise.cancel()\n        self._maximumTimer.cancel()\n        self._timeout_timer.cancel()\n\n\npyppeteerToProtocolLifecycle = {\n    'load': 'load',\n    'domcontentloaded': 'DOMContentLoaded',\n    'documentloaded': 'DOMContentLoaded',\n    'networkidle0': 'networkIdle',\n    'networkidle2': 'networkAlmostIdle',\n}\n"
  },
  {
    "path": "pyppeteer/network_manager.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"Network Manager module.\"\"\"\n\nimport asyncio\nimport base64\nfrom collections import OrderedDict\nimport copy\nimport json\nimport logging\nfrom types import SimpleNamespace\nfrom typing import Awaitable, Dict, List, Optional, Union, TYPE_CHECKING\nfrom urllib.parse import unquote\n\nfrom pyee import EventEmitter\n\nfrom pyppeteer.connection import CDPSession\nfrom pyppeteer.errors import NetworkError\nfrom pyppeteer.frame_manager import FrameManager, Frame\nfrom pyppeteer.helper import debugError\nfrom pyppeteer.multimap import Multimap\n\nif TYPE_CHECKING:\n    from typing import Set  # noqa: F401\n\nlogger = logging.getLogger(__name__)\n\n\nclass NetworkManager(EventEmitter):\n    \"\"\"NetworkManager class.\"\"\"\n\n    Events = SimpleNamespace(\n        Request='request',\n        Response='response',\n        RequestFailed='requestfailed',\n        RequestFinished='requestfinished',\n    )\n\n    def __init__(self, client: CDPSession, frameManager: FrameManager) -> None:\n        \"\"\"Make new NetworkManager.\"\"\"\n        super().__init__()\n        self._client = client\n        self._frameManager = frameManager\n        self._requestIdToRequest: Dict[Optional[str], Request] = dict()\n        self._requestIdToResponseWillBeSent: Dict[Optional[str], Dict] = dict()\n        self._extraHTTPHeaders: OrderedDict[str, str] = OrderedDict()\n        self._offline: bool = False\n        self._credentials: Optional[Dict[str, str]] = None\n        self._attemptedAuthentications: Set[Optional[str]] = set()\n        self._userRequestInterceptionEnabled = False\n        self._protocolRequestInterceptionEnabled = False\n        self._requestHashToRequestIds = Multimap()\n        self._requestHashToInterceptionIds = Multimap()\n\n        self._client.on(\n            'Network.requestWillBeSent',\n            lambda event: self._client._loop.create_task(\n                self._onRequestWillBeSent(event)\n            ),\n        )\n        self._client.on('Network.requestIntercepted', self._onRequestIntercepted)  # noqa: E501\n        self._client.on('Network.requestServedFromCache', self._onRequestServedFromCache)  # noqa: #501\n        self._client.on('Network.responseReceived', self._onResponseReceived)\n        self._client.on('Network.loadingFinished', self._onLoadingFinished)\n        self._client.on('Network.loadingFailed', self._onLoadingFailed)\n\n    async def authenticate(self, credentials: Dict[str, str]) -> None:\n        \"\"\"Provide credentials for http auth.\"\"\"\n        self._credentials = credentials\n        await self._updateProtocolRequestInterception()\n\n    async def setExtraHTTPHeaders(self, extraHTTPHeaders: Dict[str, str]\n                                  ) -> None:\n        \"\"\"Set extra http headers.\"\"\"\n        self._extraHTTPHeaders = OrderedDict()\n        for k, v in extraHTTPHeaders.items():\n            if not isinstance(v, str):\n                raise TypeError(\n                    f'Expected value of header \"{k}\" to be string, '\n                    f'but {type(v)} is found.')\n            self._extraHTTPHeaders[k.lower()] = v\n        await self._client.send('Network.setExtraHTTPHeaders',\n                                {'headers': self._extraHTTPHeaders})\n\n    def extraHTTPHeaders(self) -> Dict[str, str]:\n        \"\"\"Get extra http headers.\"\"\"\n        return dict(**self._extraHTTPHeaders)\n\n    async def setOfflineMode(self, value: bool) -> None:\n        \"\"\"Change offline mode enable/disable.\"\"\"\n        if self._offline == value:\n            return\n        self._offline = value\n        await self._client.send('Network.emulateNetworkConditions', {\n            'offline': self._offline,\n            'latency': 0,\n            'downloadThroughput': -1,\n            'uploadThroughput': -1,\n        })\n\n    async def setUserAgent(self, userAgent: str) -> None:\n        \"\"\"Set user agent.\"\"\"\n        await self._client.send('Network.setUserAgentOverride',\n                                {'userAgent': userAgent})\n\n    async def setRequestInterception(self, value: bool) -> None:\n        \"\"\"Enable request interception.\"\"\"\n        self._userRequestInterceptionEnabled = value\n        await self._updateProtocolRequestInterception()\n\n    async def _updateProtocolRequestInterception(self) -> None:\n        enabled = (self._userRequestInterceptionEnabled or\n                   bool(self._credentials))\n        if enabled == self._protocolRequestInterceptionEnabled:\n            return\n        self._protocolRequestInterceptionEnabled = enabled\n        patterns = [{'urlPattern': '*'}] if enabled else []\n        await asyncio.gather(\n            self._client.send(\n                'Network.setCacheDisabled',\n                {'cacheDisabled': enabled},\n            ),\n            self._client.send(\n                'Network.setRequestInterception',\n                {'patterns': patterns},\n            )\n        )\n\n    async def _onRequestWillBeSent(self, event: Dict) -> None:\n        if self._protocolRequestInterceptionEnabled:\n            requestHash = generateRequestHash(event.get('request', {}))\n            interceptionId = self._requestHashToInterceptionIds.firstValue(requestHash)  # noqa: E501\n            if interceptionId:\n                self._onRequest(event, interceptionId)\n                self._requestHashToInterceptionIds.delete(requestHash, interceptionId)  # noqa: E501\n            else:\n                self._requestHashToRequestIds.set(requestHash, event.get('requestId'))  # noqa: E501\n                self._requestIdToResponseWillBeSent[event.get('requestId')] = event  # noqa: E501\n            return\n        self._onRequest(event, None)\n\n    async def _send(self, method: str, msg: dict) -> None:\n        try:\n            await self._client.send(method, msg)\n        except Exception as e:\n            debugError(logger, e)\n\n    def _onRequestIntercepted(self, event: dict) -> None:  # noqa: C901\n        if event.get('authChallenge'):\n            response = 'Default'\n            if event['interceptionId'] in self._attemptedAuthentications:\n                response = 'CancelAuth'\n            elif self._credentials:\n                response = 'ProvideCredentials'\n                self._attemptedAuthentications.add(event['interceptionId'])\n            username = getattr(self, '_credentials', {}).get('username')\n            password = getattr(self, '_credentials', {}).get('password')\n\n            self._client._loop.create_task(self._send(\n                'Network.continueInterceptedRequest', {\n                    'interceptionId': event['interceptionId'],\n                    'authChallengeResponse': {\n                        'response': response,\n                        'username': username,\n                        'password': password,\n                    }\n                }\n            ))\n            return\n\n        if (not self._userRequestInterceptionEnabled and\n                self._protocolRequestInterceptionEnabled):\n            self._client._loop.create_task(self._send(\n                'Network.continueInterceptedRequest', {\n                    'interceptionId': event['interceptionId'],\n                }\n            ))\n\n        requestHash = generateRequestHash(event['request'])\n        requestId = self._requestHashToRequestIds.firstValue(requestHash)\n        if requestId:\n            requestWillBeSentEvent = self._requestIdToResponseWillBeSent[requestId]  # noqa: E501\n            self._onRequest(requestWillBeSentEvent, event.get('interceptionId'))  # noqa: E501\n            self._requestHashToRequestIds.delete(requestHash, requestId)\n            self._requestIdToResponseWillBeSent.pop(requestId, None)\n        else:\n            self._requestHashToInterceptionIds.set(requestHash, event['interceptionId'])  # noqa: E501\n\n    def _onRequest(self, event: Dict, interceptionId: Optional[str]) -> None:\n        redirectChain: List[Request] = list()\n        if event.get('redirectResponse'):\n            request = self._requestIdToRequest.get(event['requestId'])\n            if request:\n                redirectResponse = event['redirectResponse']\n                self._handleRequestRedirect(\n                    request,\n                    redirectResponse.get('status'),\n                    redirectResponse.get('headers'),\n                    redirectResponse.get('fromDiskCache'),\n                    redirectResponse.get('fromServiceWorker'),\n                    redirectResponse.get('SecurityDetails'),\n                )\n                redirectChain = request._redirectChain\n\n        isNavigationRequest = bool(\n            event.get('requestId') == event.get('loaderId') and\n            event.get('type') == 'Document'\n        )\n        self._handleRequestStart(\n            event['requestId'],\n            interceptionId,\n            event.get('request', {}).get('url'),\n            isNavigationRequest,\n            event.get('type', ''),\n            event.get('request', {}),\n            event.get('frameId'),\n            redirectChain,\n        )\n\n    def _onRequestServedFromCache(self, event: Dict) -> None:\n        request = self._requestIdToRequest.get(event.get('requestId'))\n        if request:\n            request._fromMemoryCache = True\n\n    def _handleRequestRedirect(self, request: 'Request', redirectStatus: int,\n                               redirectHeaders: Dict, fromDiskCache: bool,\n                               fromServiceWorker: bool,\n                               securityDetails: Dict = None) -> None:\n        response = Response(self._client, request, redirectStatus,\n                            redirectHeaders, fromDiskCache, fromServiceWorker,\n                            securityDetails)\n        request._response = response\n        request._redirectChain.append(request)\n        response._bodyLoadedPromiseFulfill(\n            NetworkError('Response body is unavailable for redirect response')\n        )\n        self._requestIdToRequest.pop(request._requestId, None)\n        self._attemptedAuthentications.discard(request._interceptionId)\n        self.emit(NetworkManager.Events.Response, response)\n        self.emit(NetworkManager.Events.RequestFinished, request)\n\n    def _handleRequestStart(self, requestId: str,\n                            interceptionId: Optional[str], url: str,\n                            isNavigationRequest: bool, resourceType: str,\n                            requestPayload: Dict, frameId: Optional[str],\n                            redirectChain: List['Request']\n                            ) -> None:\n        frame = None\n        if frameId and self._frameManager is not None:\n            frame = self._frameManager.frame(frameId)\n\n        request = Request(self._client, requestId, interceptionId,\n                          isNavigationRequest,\n                          self._userRequestInterceptionEnabled, url,\n                          resourceType, requestPayload, frame, redirectChain)\n        self._requestIdToRequest[requestId] = request\n        self.emit(NetworkManager.Events.Request, request)\n\n    def _onResponseReceived(self, event: dict) -> None:\n        request = self._requestIdToRequest.get(event['requestId'])\n        # FileUpload sends a response without a matching request.\n        if not request:\n            return\n        _resp = event.get('response', {})\n        response = Response(self._client, request,\n                            _resp.get('status', 0),\n                            _resp.get('headers', {}),\n                            _resp.get('fromDiskCache'),\n                            _resp.get('fromServiceWorker'),\n                            _resp.get('securityDetails'))\n        request._response = response\n        self.emit(NetworkManager.Events.Response, response)\n\n    def _onLoadingFinished(self, event: dict) -> None:\n        request = self._requestIdToRequest.get(event['requestId'])\n        # For certain requestIds we never receive requestWillBeSent event.\n        # @see https://crbug.com/750469\n        if not request:\n            return\n        response = request.response\n        if response:\n            response._bodyLoadedPromiseFulfill(None)\n        self._requestIdToRequest.pop(request._requestId, None)\n        self._attemptedAuthentications.discard(request._interceptionId)\n        self.emit(NetworkManager.Events.RequestFinished, request)\n\n    def _onLoadingFailed(self, event: dict) -> None:\n        request = self._requestIdToRequest.get(event['requestId'])\n        # For certain requestIds we never receive requestWillBeSent event.\n        # @see https://crbug.com/750469\n        if not request:\n            return\n        request._failureText = event.get('errorText')\n        response = request.response\n        if response:\n            response._bodyLoadedPromiseFulfill(None)\n        self._requestIdToRequest.pop(request._requestId, None)\n        self._attemptedAuthentications.discard(request._interceptionId)\n        self.emit(NetworkManager.Events.RequestFailed, request)\n\n\nclass Request(object):\n    \"\"\"Request class.\n\n    Whenever the page sends a request, such as for a network resource, the\n    following events are emitted by pyppeteer's page:\n\n    - ``'request'``: emitted when the request is issued by the page.\n    - ``'response'``: emitted when/if the response is received for the request.\n    - ``'requestfinished'``: emitted when the response body is downloaded and\n      the request is complete.\n\n    If request fails at some point, then instead of ``'requestfinished'`` event\n    (and possibly instead of ``'response'`` event), the ``'requestfailed'``\n    event is emitted.\n\n    If request gets a ``'redirect'`` response, the request is successfully\n    finished with the ``'requestfinished'`` event, and a new request is issued\n    to a redirect url.\n    \"\"\"\n\n    def __init__(self, client: CDPSession, requestId: Optional[str],\n                 interceptionId: Optional[str], isNavigationRequest: bool,\n                 allowInterception: bool, url: str, resourceType: str,\n                 payload: dict, frame: Optional[Frame],\n                 redirectChain: List['Request']\n                 ) -> None:\n        self._client = client\n        self._requestId = requestId\n        self._isNavigationRequest = isNavigationRequest\n        self._interceptionId = interceptionId\n        self._allowInterception = allowInterception\n        self._interceptionHandled = False\n        self._response: Optional[Response] = None\n        self._failureText: Optional[str] = None\n\n        self._url = url\n        self._resourceType = resourceType.lower()\n        self._method = payload.get('method')\n        self._postData = payload.get('postData')\n        headers = payload.get('headers', {})\n        self._headers = {k.lower(): v for k, v in headers.items()}\n        self._frame = frame\n        self._redirectChain = redirectChain\n\n        self._fromMemoryCache = False\n\n    @property\n    def url(self) -> str:\n        \"\"\"URL of this request.\"\"\"\n        return self._url\n\n    @property\n    def resourceType(self) -> str:\n        \"\"\"Resource type of this request perceived by the rendering engine.\n\n        ResourceType will be one of the following: ``document``,\n        ``stylesheet``, ``image``, ``media``, ``font``, ``script``,\n        ``texttrack``, ``xhr``, ``fetch``, ``eventsource``, ``websocket``,\n        ``manifest``, ``other``.\n        \"\"\"\n        return self._resourceType\n\n    @property\n    def method(self) -> Optional[str]:\n        \"\"\"Return this request's method (GET, POST, etc.).\"\"\"\n        return self._method\n\n    @property\n    def postData(self) -> Optional[str]:\n        \"\"\"Return post body of this request.\"\"\"\n        return self._postData\n\n    @property\n    def headers(self) -> Dict:\n        \"\"\"Return a dictionary of HTTP headers of this request.\n\n        All header names are lower-case.\n        \"\"\"\n        return self._headers\n\n    @property\n    def response(self) -> Optional['Response']:\n        \"\"\"Return matching :class:`Response` object, or ``None``.\n\n        If the response has not been received, return ``None``.\n        \"\"\"\n        return self._response\n\n    @property\n    def frame(self) -> Optional[Frame]:\n        \"\"\"Return a matching :class:`~pyppeteer.frame_manager.frame` object.\n\n        Return ``None`` if navigating to error page.\n        \"\"\"\n        return self._frame\n\n    def isNavigationRequest(self) -> bool:\n        \"\"\"Whether this request is driving frame's navigation.\"\"\"\n        return self._isNavigationRequest\n\n    @property\n    def redirectChain(self) -> List['Request']:\n        \"\"\"Return chain of requests initiated to fetch a resource.\n\n        * If there are no redirects and request was successful, the chain will\n          be empty.\n        * If a server responds with at least a single redirect, then the chain\n          will contain all the requests that were redirected.\n\n        ``redirectChain`` is shared between all the requests of the same chain.\n        \"\"\"\n        return copy.copy(self._redirectChain)\n\n    def failure(self) -> Optional[Dict]:\n        \"\"\"Return error text.\n\n        Return ``None`` unless this request was failed, as reported by\n        ``requestfailed`` event.\n\n        When request failed, this method return dictionary which has a\n        ``errorText`` field, which contains human-readable error message, e.g.\n        ``'net::ERR_RAILED'``.\n        \"\"\"\n        if not self._failureText:\n            return None\n        return {'errorText': self._failureText}\n\n    async def continue_(self, overrides: Dict = None) -> None:\n        \"\"\"Continue request with optional request overrides.\n\n        To use this method, request interception should be enabled by\n        :meth:`pyppeteer.page.Page.setRequestInterception`. If request\n        interception is not enabled, raise ``NetworkError``.\n\n        ``overrides`` can have the following fields:\n\n        * ``url`` (str): If set, the request url will be changed.\n        * ``method`` (str): If set, change the request method (e.g. ``GET``).\n        * ``postData`` (str): If set, change the post data or request.\n        * ``headers`` (dict): If set, change the request HTTP header.\n        \"\"\"\n        if overrides is None:\n            overrides = {}\n\n        if not self._allowInterception:\n            raise NetworkError('Request interception is not enabled.')\n        if self._interceptionHandled:\n            raise NetworkError('Request is already handled.')\n\n        self._interceptionHandled = True\n        opt = {'interceptionId': self._interceptionId}\n        opt.update(overrides)\n        try:\n            await self._client.send('Network.continueInterceptedRequest', opt)\n        except Exception as e:\n            debugError(logger, e)\n\n    async def respond(self, response: Dict) -> None:  # noqa: C901\n        \"\"\"Fulfills request with given response.\n\n        To use this, request interception should by enabled by\n        :meth:`pyppeteer.page.Page.setRequestInterception`. Request\n        interception is not enabled, raise ``NetworkError``.\n\n        ``response`` is a dictionary which can have the following fields:\n\n        * ``status`` (int): Response status code, defaults to 200.\n        * ``headers`` (dict): Optional response headers.\n        * ``contentType`` (str): If set, equals to setting ``Content-Type``\n          response header.\n        * ``body`` (str|bytes): Optional response body.\n        \"\"\"\n        if self._url.startswith('data:'):\n            return\n        if not self._allowInterception:\n            raise NetworkError('Request interception is not enabled.')\n        if self._interceptionHandled:\n            raise NetworkError('Request is already handled.')\n        self._interceptionHandled = True\n\n        if response.get('body') and isinstance(response['body'], str):\n            responseBody: Optional[bytes] = response['body'].encode('utf-8')\n        else:\n            responseBody = response.get('body')\n\n        responseHeaders = {}\n        if response.get('headers'):\n            for header in response['headers']:\n                responseHeaders[header.lower()] = response['headers'][header]\n        if response.get('contentType'):\n            responseHeaders['content-type'] = response['contentType']\n        if responseBody and 'content-length' not in responseHeaders:\n            responseHeaders['content-length'] = len(responseBody)\n\n        statusCode = response.get('status', 200)\n        statusText = statusTexts.get(statusCode, '')\n        statusLine = f'HTTP/1.1 {statusCode} {statusText}'\n\n        CRLF = '\\r\\n'\n        text = statusLine + CRLF\n        for header in responseHeaders:\n            text = f'{text}{header}: {responseHeaders[header]}{CRLF}'\n        text = text + CRLF\n        responseBuffer = text.encode('utf-8')\n        if responseBody:\n            responseBuffer = responseBuffer + responseBody\n\n        rawResponse = base64.b64encode(responseBuffer).decode('ascii')\n        try:\n            await self._client.send('Network.continueInterceptedRequest', {\n                'interceptionId': self._interceptionId,\n                'rawResponse': rawResponse,\n            })\n        except Exception as e:\n            debugError(logger, e)\n\n    async def abort(self, errorCode: str = 'failed') -> None:\n        \"\"\"Abort request.\n\n        To use this, request interception should be enabled by\n        :meth:`pyppeteer.page.Page.setRequestInterception`.\n        If request interception is not enabled, raise ``NetworkError``.\n\n        ``errorCode`` is an optional error code string. Defaults to ``failed``,\n        could be one of the following:\n\n        - ``aborted``: An operation was aborted (due to user action).\n        - ``accessdenied``: Permission to access a resource, other than the\n          network, was denied.\n        - ``addressunreachable``: The IP address is unreachable. This usually\n          means that there is no route to the specified host or network.\n        - ``blockedbyclient``: The client chose to block the request.\n        - ``blockedbyresponse``: The request failed because the request was\n          delivered along with requirements which are not met\n          ('X-Frame-Options' and 'Content-Security-Policy' ancestor check,\n          for instance).\n        - ``connectionaborted``: A connection timeout as a result of not\n          receiving an ACK for data sent.\n        - ``connectionclosed``: A connection was closed (corresponding to a TCP\n          FIN).\n        - ``connectionfailed``: A connection attempt failed.\n        - ``connectionrefused``: A connection attempt was refused.\n        - ``connectionreset``: A connection was reset (corresponding to a TCP\n          RST).\n        - ``internetdisconnected``: The Internet connection has been lost.\n        - ``namenotresolved``: The host name could not be resolved.\n        - ``timedout``: An operation timed out.\n        - ``failed``: A generic failure occurred.\n        \"\"\"\n        errorReason = errorReasons[errorCode]\n        if not errorReason:\n            raise NetworkError('Unknown error code: {}'.format(errorCode))\n        if not self._allowInterception:\n            raise NetworkError('Request interception is not enabled.')\n        if self._interceptionHandled:\n            raise NetworkError('Request is already handled.')\n        self._interceptionHandled = True\n        try:\n            await self._client.send('Network.continueInterceptedRequest', dict(\n                interceptionId=self._interceptionId,\n                errorReason=errorReason,\n            ))\n        except Exception as e:\n            debugError(logger, e)\n\n\nerrorReasons = {\n    'aborted': 'Aborted',\n    'accessdenied': 'AccessDenied',\n    'addressunreachable': 'AddressUnreachable',\n    'blockedbyclient': 'BlockedByClient',\n    'blockedbyresponse': 'BlockedByResponse',\n    'connectionaborted': 'ConnectionAborted',\n    'connectionclosed': 'ConnectionClosed',\n    'connectionfailed': 'ConnectionFailed',\n    'connectionrefused': 'ConnectionRefused',\n    'connectionreset': 'ConnectionReset',\n    'internetdisconnected': 'InternetDisconnected',\n    'namenotresolved': 'NameNotResolved',\n    'timedout': 'TimedOut',\n    'failed': 'Failed',\n}\n\n\nclass Response(object):\n    \"\"\"Response class represents responses which are received by ``Page``.\"\"\"\n\n    def __init__(self, client: CDPSession, request: Request, status: int,\n                 headers: Dict[str, str], fromDiskCache: bool,\n                 fromServiceWorker: bool, securityDetails: Dict = None\n                 ) -> None:\n        self._client = client\n        self._request = request\n        self._status = status\n        self._contentPromise = self._client._loop.create_future()\n        self._bodyLoadedPromise = self._client._loop.create_future()\n\n        self._url = request.url\n        self._fromDiskCache = fromDiskCache\n        self._fromServiceWorker = fromServiceWorker\n        self._headers = {k.lower(): v for k, v in headers.items()}\n        self._securityDetails: Union[Dict, SecurityDetails] = {}\n        if securityDetails:\n            self._securityDetails = SecurityDetails(\n                securityDetails['subjectName'],\n                securityDetails['issuer'],\n                securityDetails['validFrom'],\n                securityDetails['validTo'],\n                securityDetails['protocol'],\n            )\n\n    def _bodyLoadedPromiseFulfill(self, value: Optional[Exception]) -> None:\n        self._bodyLoadedPromise.set_result(value)\n\n    @property\n    def url(self) -> str:\n        \"\"\"URL of the response.\"\"\"\n        return self._url\n\n    @property\n    def ok(self) -> bool:\n        \"\"\"Return bool whether this request is successful (200-299) or not.\"\"\"\n        return self._status == 0 or 200 <= self._status <= 299\n\n    @property\n    def status(self) -> int:\n        \"\"\"Status code of the response.\"\"\"\n        return self._status\n\n    @property\n    def headers(self) -> Dict:\n        \"\"\"Return dictionary of HTTP headers of this response.\n\n        All header names are lower-case.\n        \"\"\"\n        return self._headers\n\n    @property\n    def securityDetails(self) -> Union[Dict, 'SecurityDetails']:\n        \"\"\"Return security details associated with this response.\n\n        Security details if the response was received over the secure\n        connection, or `None` otherwise.\n        \"\"\"\n        return self._securityDetails\n\n    async def _bufread(self) -> bytes:\n        result = await self._bodyLoadedPromise\n        if isinstance(result, Exception):\n            raise result\n        response = await self._client.send('Network.getResponseBody', {\n            'requestId': self._request._requestId\n        })\n        body = response.get('body', b'')\n        if response.get('base64Encoded'):\n            return base64.b64decode(body)\n        return body\n\n    def buffer(self) -> Awaitable[bytes]:\n        \"\"\"Return awaitable which resolves to bytes with response body.\"\"\"\n        if not self._contentPromise.done():\n            return self._client._loop.create_task(self._bufread())\n        return self._contentPromise\n\n    async def text(self) -> str:\n        \"\"\"Get text representation of response body.\"\"\"\n        content = await self.buffer()\n        if isinstance(content, str):\n            return content\n        else:\n            return content.decode('utf-8')\n\n    async def json(self) -> dict:\n        \"\"\"Get JSON representation of response body.\"\"\"\n        content = await self.text()\n        return json.loads(content)\n\n    @property\n    def request(self) -> Request:\n        \"\"\"Get matching :class:`Request` object.\"\"\"\n        return self._request\n\n    @property\n    def fromCache(self) -> bool:\n        \"\"\"Return ``True`` if the response was served from cache.\n\n        Here `cache` is either the browser's disk cache or memory cache.\n        \"\"\"\n        return self._fromDiskCache or self._request._fromMemoryCache\n\n    @property\n    def fromServiceWorker(self) -> bool:\n        \"\"\"Return ``True`` if the response was served by a service worker.\"\"\"\n        return self._fromServiceWorker\n\n\ndef generateRequestHash(request: dict) -> str:\n    \"\"\"Generate request hash.\"\"\"\n    normalizedURL = request.get('url', '')\n    try:\n        normalizedURL = unquote(normalizedURL)\n    except Exception:\n        pass\n\n    _hash = {\n        'url': normalizedURL,\n        'method': request.get('method'),\n        'postData': request.get('postData'),\n        'headers': {},\n    }\n\n    if not normalizedURL.startswith('data:'):\n        headers = list(request['headers'].keys())\n        headers.sort()\n        for header in headers:\n            headerValue = request['headers'][header]\n            header = header.lower()\n            if header in [\n                'accept',\n                'referer',\n                'x-devtools-emulate-network-conditions-client-id',\n                'cookie',\n            ]:\n                continue\n            _hash['headers'][header] = headerValue\n    return json.dumps(_hash)\n\n\nclass SecurityDetails(object):\n    \"\"\"Class represents responses which are received by page.\"\"\"\n\n    def __init__(self, subjectName: str, issuer: str, validFrom: int,\n                 validTo: int, protocol: str) -> None:\n        self._subjectName = subjectName\n        self._issuer = issuer\n        self._validFrom = validFrom\n        self._validTo = validTo\n        self._protocol = protocol\n\n    @property\n    def subjectName(self) -> str:\n        \"\"\"Return the subject to which the certificate was issued to.\"\"\"\n        return self._subjectName\n\n    @property\n    def issuer(self) -> str:\n        \"\"\"Return a string with the name of issuer of the certificate.\"\"\"\n        return self._issuer\n\n    @property\n    def validFrom(self) -> int:\n        \"\"\"Return UnixTime of the start of validity of the certificate.\"\"\"\n        return self._validFrom\n\n    @property\n    def validTo(self) -> int:\n        \"\"\"Return UnixTime of the end of validity of the certificate.\"\"\"\n        return self._validTo\n\n    @property\n    def protocol(self) -> str:\n        \"\"\"Return string of with the security protocol, e.g. \"TLS1.2\".\"\"\"\n        return self._protocol\n\n\nstatusTexts = {\n    '100': 'Continue',\n    '101': 'Switching Protocols',\n    '102': 'Processing',\n    '200': 'OK',\n    '201': 'Created',\n    '202': 'Accepted',\n    '203': 'Non-Authoritative Information',\n    '204': 'No Content',\n    '206': 'Partial Content',\n    '207': 'Multi-Status',\n    '208': 'Already Reported',\n    '209': 'IM Used',\n    '300': 'Multiple Choices',\n    '301': 'Moved Permanently',\n    '302': 'Found',\n    '303': 'See Other',\n    '304': 'Not Modified',\n    '305': 'Use Proxy',\n    '306': 'Switch Proxy',\n    '307': 'Temporary Redirect',\n    '308': 'Permanent Redirect',\n    '400': 'Bad Request',\n    '401': 'Unauthorized',\n    '402': 'Payment Required',\n    '403': 'Forbidden',\n    '404': 'Not Found',\n    '405': 'Method Not Allowed',\n    '406': 'Not Acceptable',\n    '407': 'Proxy Authentication Required',\n    '408': 'Request Timeout',\n    '409': 'Conflict',\n    '410': 'Gone',\n    '411': 'Length Required',\n    '412': 'Precondition Failed',\n    '413': 'Payload Too Large',\n    '414': 'URI Too Long',\n    '415': 'Unsupported Media Type',\n    '416': 'Range Not Satisfiable',\n    '417': 'Expectation Failed',\n    '418': 'I\\'m a teapot',\n    '421': 'Misdirected Request',\n    '422': 'Unprocessable Entity',\n    '423': 'Locked',\n    '424': 'Failed Dependency',\n    '426': 'Upgrade Required',\n    '428': 'Precondition Required',\n    '429': 'Too Many Requests',\n    '431': 'Request Header Fields Too Large',\n    '451': 'Unavailable For Legal Reasons',\n    '500': 'Internal Server Error',\n    '501': 'Not Implemented',\n    '502': 'Bad Gateway',\n    '503': 'Service Unavailable',\n    '504': 'Gateway Timeout',\n    '505': 'HTTP Version Not Supported',\n    '506': 'Variant Also Negotiates',\n    '507': 'Insufficient Storage',\n    '508': 'Loop Detected',\n    '510': 'Not Extended',\n    '511': 'Network Authentication Required',\n}\n"
  },
  {
    "path": "pyppeteer/options.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"Options module.\"\"\"\n\nfrom argparse import Namespace\n\nconfig = Namespace()\n"
  },
  {
    "path": "pyppeteer/page.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"Page module.\"\"\"\n\nimport asyncio\nimport base64\nimport json\nimport logging\nimport math\nimport mimetypes\nfrom types import SimpleNamespace\nfrom typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, List, Optional, Union\n\nfrom pyee import EventEmitter\nfrom pyppeteer import helper\nfrom pyppeteer.connection import CDPSession\nfrom pyppeteer.coverage import Coverage\nfrom pyppeteer.dialog import Dialog\nfrom pyppeteer.element_handle import ElementHandle\nfrom pyppeteer.emulation_manager import EmulationManager\nfrom pyppeteer.errors import PageError\nfrom pyppeteer.execution_context import JSHandle  # noqa: F401\nfrom pyppeteer.frame_manager import Frame  # noqa: F401\nfrom pyppeteer.frame_manager import FrameManager\nfrom pyppeteer.helper import debugError\nfrom pyppeteer.input import Keyboard, Mouse, Touchscreen\nfrom pyppeteer.navigator_watcher import NavigatorWatcher\nfrom pyppeteer.network_manager import NetworkManager, Request, Response\nfrom pyppeteer.tracing import Tracing\nfrom pyppeteer.util import merge_dict\nfrom pyppeteer.worker import Worker\n\nif TYPE_CHECKING:\n    from pyppeteer.browser import Browser, Target  # noqa: F401\n\nlogger = logging.getLogger(__name__)\n\n\nclass Page(EventEmitter):\n    \"\"\"Page class.\n\n    This class provides methods to interact with a single tab of chrome. One\n    :class:`~pyppeteer.browser.Browser` object might have multiple Page object.\n\n    The :class:`Page` class emits various :attr:`~Page.Events` which can be\n    handled by using ``on`` or ``once`` method, which is inherited from\n    `pyee <https://pyee.readthedocs.io/en/latest/>`_'s ``EventEmitter`` class.\n    \"\"\"\n\n    #: Available events.\n    Events = SimpleNamespace(\n        Close='close',\n        Console='console',\n        Dialog='dialog',\n        DOMContentLoaded='domcontentloaded',\n        Error='error',\n        PageError='pageerror',\n        Request='request',\n        Response='response',\n        RequestFailed='requestfailed',\n        RequestFinished='requestfinished',\n        FrameAttached='frameattached',\n        FrameDetached='framedetached',\n        FrameNavigated='framenavigated',\n        Load='load',\n        Metrics='metrics',\n        WorkerCreated='workercreated',\n        WorkerDestroyed='workerdestroyed',\n    )\n\n    PaperFormats: Dict[str, Dict[str, float]] = dict(\n        letter={'width': 8.5, 'height': 11},\n        legal={'width': 8.5, 'height': 14},\n        tabloid={'width': 11, 'height': 17},\n        ledger={'width': 17, 'height': 11},\n        a0={'width': 33.1, 'height': 46.8},\n        a1={'width': 23.4, 'height': 33.1},\n        a2={'width': 16.5, 'height': 23.4},\n        a3={'width': 11.7, 'height': 16.5},\n        a4={'width': 8.27, 'height': 11.7},\n        a5={'width': 5.83, 'height': 8.27},\n    )\n\n    @staticmethod\n    async def create(\n        client: CDPSession,\n        target: 'Target',\n        ignoreHTTPSErrors: bool,\n        defaultViewport: Optional[Dict],\n        screenshotTaskQueue: list = None,\n    ) -> 'Page':\n        \"\"\"Async function which makes new page object.\"\"\"\n        await client.send('Page.enable')\n        frameTree = (await client.send('Page.getFrameTree'))['frameTree']\n        page = Page(client, target, frameTree, ignoreHTTPSErrors, screenshotTaskQueue)\n\n        await asyncio.gather(\n            client.send('Target.setAutoAttach', {'autoAttach': True, 'waitForDebuggerOnStart': False}),  # noqa: E501\n            client.send('Page.setLifecycleEventsEnabled', {'enabled': True}),\n            client.send('Network.enable', {}),\n            client.send('Runtime.enable', {}),\n            client.send('Security.enable', {}),\n            client.send('Performance.enable', {}),\n            client.send('Log.enable', {}),\n        )\n        if ignoreHTTPSErrors:\n            await client.send('Security.setIgnoreCertificateErrors', {'ignore': True})\n        if defaultViewport:\n            await page.setViewport(defaultViewport)\n        return page\n\n    def __init__(\n        self,\n        client: CDPSession,\n        target: 'Target',  # noqa: C901\n        frameTree: Dict,\n        ignoreHTTPSErrors: bool,\n        screenshotTaskQueue: list = None,\n    ) -> None:\n        super().__init__()\n        self._closed = False\n        self._client = client\n        self._target = target\n        self._keyboard = Keyboard(client)\n        self._mouse = Mouse(client, self._keyboard)\n        self._touchscreen = Touchscreen(client, self._keyboard)\n        self._frameManager = FrameManager(client, frameTree, self)\n        self._networkManager = NetworkManager(client, self._frameManager)\n        self._emulationManager = EmulationManager(client)\n        self._tracing = Tracing(client)\n        self._pageBindings: Dict[str, Callable[..., Any]] = {}\n        self._ignoreHTTPSErrors = ignoreHTTPSErrors\n        self._defaultNavigationTimeout = 30000  # milliseconds\n        self._javascriptEnabled = True\n        self._coverage = Coverage(client)\n        self._viewport: Optional[Dict] = None\n\n        if screenshotTaskQueue is None:\n            screenshotTaskQueue = []\n        self._screenshotTaskQueue = screenshotTaskQueue\n\n        self._workers: Dict[str, Worker] = {}\n\n        def _onTargetAttached(event: Dict) -> None:\n            targetInfo = event['targetInfo']\n            if targetInfo['type'] != 'worker':\n                # If we don't detach from service workers, they will never die.\n                try:\n                    client.send('Target.detachFromTarget', {'sessionId': event['sessionId'],})\n                except Exception as e:\n                    debugError(logger, e)\n                return\n            sessionId = event['sessionId']\n            session = client._createSession(targetInfo['type'], sessionId)\n            worker = Worker(session, targetInfo['url'], self._addConsoleMessage, self._handleException,)\n            self._workers[sessionId] = worker\n            self.emit(Page.Events.WorkerCreated, worker)\n\n        def _onTargetDetached(event: Dict) -> None:\n            sessionId = event['sessionId']\n            worker = self._workers.get(sessionId)\n            if worker is None:\n                return\n            self.emit(Page.Events.WorkerDestroyed, worker)\n            del self._workers[sessionId]\n\n        client.on('Target.attachedToTarget', _onTargetAttached)\n        client.on('Target.detachedFromTarget', _onTargetDetached)\n\n        _fm = self._frameManager\n        _fm.on(FrameManager.Events.FrameAttached, lambda event: self.emit(Page.Events.FrameAttached, event))\n        _fm.on(FrameManager.Events.FrameDetached, lambda event: self.emit(Page.Events.FrameDetached, event))\n        _fm.on(FrameManager.Events.FrameNavigated, lambda event: self.emit(Page.Events.FrameNavigated, event))\n\n        _nm = self._networkManager\n        _nm.on(NetworkManager.Events.Request, lambda event: self.emit(Page.Events.Request, event))\n        _nm.on(NetworkManager.Events.Response, lambda event: self.emit(Page.Events.Response, event))\n        _nm.on(NetworkManager.Events.RequestFailed, lambda event: self.emit(Page.Events.RequestFailed, event))\n        _nm.on(NetworkManager.Events.RequestFinished, lambda event: self.emit(Page.Events.RequestFinished, event))\n\n        client.on('Page.domContentEventFired', lambda event: self.emit(Page.Events.DOMContentLoaded))\n        client.on('Page.loadEventFired', lambda event: self.emit(Page.Events.Load))\n        client.on('Runtime.consoleAPICalled', lambda event: self._onConsoleAPI(event))\n        client.on('Runtime.bindingCalled', lambda event: self._onBindingCalled(event))\n        client.on('Page.javascriptDialogOpening', lambda event: self._onDialog(event))\n        client.on('Runtime.exceptionThrown', lambda exception: self._handleException(exception.get('exceptionDetails')))\n        client.on('Inspector.targetCrashed', lambda event: self._onTargetCrashed())\n        client.on('Performance.metrics', lambda event: self._emitMetrics(event))\n        client.on('Log.entryAdded', lambda event: self._onLogEntryAdded(event))\n\n        def closed(fut: asyncio.futures.Future) -> None:\n            self.emit(Page.Events.Close)\n            self._closed = True\n\n        self._target._isClosedPromise.add_done_callback(closed)\n\n    @property\n    def target(self) -> 'Target':\n        \"\"\"Return a target this page created from.\"\"\"\n        return self._target\n\n    @property\n    def browser(self) -> 'Browser':\n        \"\"\"Get the browser the page belongs to.\"\"\"\n        return self._target.browser\n\n    def _onTargetCrashed(self, *args: Any, **kwargs: Any) -> None:\n        self.emit('error', PageError('Page crashed!'))\n\n    def _onLogEntryAdded(self, event: Dict) -> None:\n        entry = event.get('entry', {})\n        level = entry.get('level', '')\n        text = entry.get('text', '')\n        args = entry.get('args', [])\n        source = entry.get('source', '')\n        for arg in args:\n            helper.releaseObject(self._client, arg)\n\n        if source != 'worker':\n            self.emit(Page.Events.Console, ConsoleMessage(level, text))\n\n    @property\n    def mainFrame(self) -> Optional['Frame']:\n        \"\"\"Get main :class:`~pyppeteer.frame_manager.Frame` of this page.\"\"\"\n        return self._frameManager._mainFrame\n\n    @property\n    def keyboard(self) -> Keyboard:\n        \"\"\"Get :class:`~pyppeteer.input.Keyboard` object.\"\"\"\n        return self._keyboard\n\n    @property\n    def touchscreen(self) -> Touchscreen:\n        \"\"\"Get :class:`~pyppeteer.input.Touchscreen` object.\"\"\"\n        return self._touchscreen\n\n    @property\n    def coverage(self) -> Coverage:\n        \"\"\"Return :class:`~pyppeteer.coverage.Coverage`.\"\"\"\n        return self._coverage\n\n    async def tap(self, selector: str) -> None:\n        \"\"\"Tap the element which matches the ``selector``.\n\n        :arg str selector: A selector to search element to touch.\n        \"\"\"\n        frame = self.mainFrame\n        if frame is None:\n            raise PageError('no main frame')\n        await frame.tap(selector)\n\n    @property\n    def tracing(self) -> 'Tracing':\n        \"\"\"Get tracing object.\"\"\"\n        return self._tracing\n\n    @property\n    def frames(self) -> List['Frame']:\n        \"\"\"Get all frames of this page.\"\"\"\n        return list(self._frameManager.frames())\n\n    @property\n    def workers(self) -> List[Worker]:\n        \"\"\"Get all workers of this page.\"\"\"\n        return list(self._workers.values())\n\n    async def setRequestInterception(self, value: bool) -> None:\n        \"\"\"Enable/disable request interception.\n\n        Activating request interception enables\n        :class:`~pyppeteer.network_manager.Request` class's\n        :meth:`~pyppeteer.network_manager.Request.abort`,\n        :meth:`~pyppeteer.network_manager.Request.continue_`, and\n        :meth:`~pyppeteer.network_manager.Request.response` methods.\n        This provides the capability to modify network requests that are made\n        by a page.\n\n        Once request interception is enabled, every request will stall unless\n        it's continued, responded or aborted.\n\n        An example of a native request interceptor that aborts all image\n        requests:\n\n        .. code:: python\n\n            browser = await launch()\n            page = await browser.newPage()\n            await page.setRequestInterception(True)\n\n            async def intercept(request):\n                if request.url.endswith('.png') or request.url.endswith('.jpg'):\n                    await request.abort()\n                else:\n                    await request.continue_()\n\n            page.on('request', lambda req: asyncio.ensure_future(intercept(req)))\n            await page.goto('https://example.com')\n            await browser.close()\n        \"\"\"  # noqa: E501\n        return await self._networkManager.setRequestInterception(value)\n\n    async def setOfflineMode(self, enabled: bool) -> None:\n        \"\"\"Set offline mode enable/disable.\"\"\"\n        await self._networkManager.setOfflineMode(enabled)\n\n    def setDefaultNavigationTimeout(self, timeout: int) -> None:\n        \"\"\"Change the default maximum navigation timeout.\n\n        This method changes the default timeout of 30 seconds for the following\n        methods:\n\n        * :meth:`goto`\n        * :meth:`goBack`\n        * :meth:`goForward`\n        * :meth:`reload`\n        * :meth:`waitForNavigation`\n\n        :arg int timeout: Maximum navigation time in milliseconds. Pass ``0``\n                          to disable timeout.\n        \"\"\"\n        self._defaultNavigationTimeout = timeout\n\n    async def _send(self, method: str, msg: dict) -> None:\n        try:\n            await self._client.send(method, msg)\n        except Exception as e:\n            debugError(logger, e)\n\n    def _onCertificateError(self, event: Any) -> None:\n        if not self._ignoreHTTPSErrors:\n            return\n        self._client._loop.create_task(\n            self._send('Security.handleCertificateError', {'eventId': event.get('eventId'), 'action': 'continue'})\n        )\n\n    async def querySelector(self, selector: str) -> Optional[ElementHandle]:\n        \"\"\"Get an Element which matches ``selector``.\n\n        :arg str selector: A selector to search element.\n        :return Optional[ElementHandle]: If element which matches the\n            ``selector`` is found, return its\n            :class:`~pyppeteer.element_handle.ElementHandle`. If not found,\n            returns ``None``.\n        \"\"\"\n        frame = self.mainFrame\n        if not frame:\n            raise PageError('no main frame.')\n        return await frame.querySelector(selector)\n\n    async def evaluateHandle(self, pageFunction: str, *args: Any) -> JSHandle:\n        \"\"\"Execute function on this page.\n\n        Difference between :meth:`~pyppeteer.page.Page.evaluate` and\n        :meth:`~pyppeteer.page.Page.evaluateHandle` is that\n        ``evaluateHandle`` returns JSHandle object (not value).\n\n        :arg str pageFunction: JavaScript function to be executed.\n        \"\"\"\n        if not self.mainFrame:\n            raise PageError('no main frame.')\n        context = await self.mainFrame.executionContext()\n        if not context:\n            raise PageError('No context.')\n        return await context.evaluateHandle(pageFunction, *args)\n\n    async def queryObjects(self, prototypeHandle: JSHandle) -> JSHandle:\n        \"\"\"Iterate js heap and finds all the objects with the handle.\n\n        :arg JSHandle prototypeHandle: JSHandle of prototype object.\n        \"\"\"\n        if not self.mainFrame:\n            raise PageError('no main frame.')\n        context = await self.mainFrame.executionContext()\n        if not context:\n            raise PageError('No context.')\n        return await context.queryObjects(prototypeHandle)\n\n    async def querySelectorEval(self, selector: str, pageFunction: str, *args: Any) -> Any:\n        \"\"\"Execute function with an element which matches ``selector``.\n\n        :arg str selector: A selector to query page for.\n        :arg str pageFunction: String of JavaScript function to be evaluated on\n                               browser. This function takes an element which\n                               matches the selector as a first argument.\n        :arg Any args: Arguments to pass to ``pageFunction``.\n\n        This method raises error if no element matched the ``selector``.\n        \"\"\"\n        frame = self.mainFrame\n        if not frame:\n            raise PageError('no main frame.')\n        return await frame.querySelectorEval(selector, pageFunction, *args)\n\n    async def querySelectorAllEval(self, selector: str, pageFunction: str, *args: Any) -> Any:\n        \"\"\"Execute function with all elements which matches ``selector``.\n\n        :arg str selector: A selector to query page for.\n        :arg str pageFunction: String of JavaScript function to be evaluated on\n                               browser. This function takes Array of the\n                               matched elements as the first argument.\n        :arg Any args: Arguments to pass to ``pageFunction``.\n        \"\"\"\n        frame = self.mainFrame\n        if not frame:\n            raise PageError('no main frame.')\n        return await frame.querySelectorAllEval(selector, pageFunction, *args)\n\n    async def querySelectorAll(self, selector: str) -> List[ElementHandle]:\n        \"\"\"Get all element which matches ``selector`` as a list.\n\n        :arg str selector: A selector to search element.\n        :return List[ElementHandle]: List of\n            :class:`~pyppeteer.element_handle.ElementHandle` which matches the\n            ``selector``. If no element is matched to the ``selector``, return\n            empty list.\n        \"\"\"\n        frame = self.mainFrame\n        if not frame:\n            raise PageError('no main frame.')\n        return await frame.querySelectorAll(selector)\n\n    async def xpath(self, expression: str) -> List[ElementHandle]:\n        \"\"\"Evaluate the XPath expression.\n\n        If there are no such elements in this page, return an empty list.\n\n        :arg str expression: XPath string to be evaluated.\n        \"\"\"\n        frame = self.mainFrame\n        if not frame:\n            raise PageError('no main frame.')\n        return await frame.xpath(expression)\n\n    #: alias to :meth:`querySelector`\n    J = querySelector\n    #: alias to :meth:`querySelectorEval`\n    Jeval = querySelectorEval\n    #: alias to :meth:`querySelectorAll`\n    JJ = querySelectorAll\n    #: alias to :meth:`querySelectorAllEval`\n    JJeval = querySelectorAllEval\n    #: alias to :meth:`xpath`\n    Jx = xpath\n\n    async def cookies(self, *urls: str) -> List[Dict[str, Union[str, int, bool]]]:\n        \"\"\"Get cookies.\n\n        If no URLs are specified, this method returns cookies for the current\n        page URL. If URLs are specified, only cookies for those URLs are\n        returned.\n\n        Returned cookies are list of dictionaries which contain these fields:\n\n        * ``name`` (str)\n        * ``value`` (str)\n        * ``url`` (str)\n        * ``domain`` (str)\n        * ``path`` (str)\n        * ``expires`` (number): Unix time in seconds\n        * ``httpOnly`` (bool)\n        * ``secure`` (bool)\n        * ``session`` (bool)\n        * ``sameSite`` (str): ``'Strict'`` or ``'Lax'``\n        \"\"\"\n        if not urls:\n            urls = (self.url,)\n        resp = await self._client.send('Network.getCookies', {'urls': urls,})\n        return resp.get('cookies', {})\n\n    async def deleteCookie(self, *cookies: dict) -> None:\n        \"\"\"Delete cookie.\n\n        ``cookies`` should be dictionaries which contain these fields:\n\n        * ``name`` (str): **required**\n        * ``url`` (str)\n        * ``domain`` (str)\n        * ``path`` (str)\n        * ``secure`` (bool)\n        \"\"\"\n        pageURL = self.url\n        for cookie in cookies:\n            item = dict(**cookie)\n            if not cookie.get('url') and pageURL.startswith('http'):\n                item['url'] = pageURL\n            await self._client.send('Network.deleteCookies', item)\n\n    async def setCookie(self, *cookies: dict) -> None:\n        \"\"\"Set cookies.\n\n        ``cookies`` should be dictionaries which contain these fields:\n\n        * ``name`` (str): **required**\n        * ``value`` (str): **required**\n        * ``url`` (str)\n        * ``domain`` (str)\n        * ``path`` (str)\n        * ``expires`` (number): Unix time in seconds\n        * ``httpOnly`` (bool)\n        * ``secure`` (bool)\n        * ``sameSite`` (str): ``'Strict'`` or ``'Lax'``\n        \"\"\"\n        pageURL = self.url\n        startsWithHTTP = pageURL.startswith('http')\n        items = []\n        for cookie in cookies:\n            item = dict(**cookie)\n            if 'url' not in item and startsWithHTTP:\n                item['url'] = pageURL\n            if item.get('url') == 'about:blank':\n                name = item.get('name', '')\n                raise PageError(f'Blank page can not have cookie \"{name}\"')\n            if item.get('url', '').startswith('data:'):\n                name = item.get('name', '')\n                raise PageError(f'Data URL page can not have cookie \"{name}\"')\n            items.append(item)\n        await self.deleteCookie(*items)\n        if items:\n            await self._client.send('Network.setCookies', {'cookies': items,})\n\n    async def addScriptTag(self, options: Dict = None, **kwargs: str) -> ElementHandle:\n        \"\"\"Add script tag to this page.\n\n        One of ``url``, ``path`` or ``content`` option is necessary.\n            * ``url`` (string): URL of a script to add.\n            * ``path`` (string): Path to the local JavaScript file to add.\n            * ``content`` (string): JavaScript string to add.\n            * ``type`` (string): Script type. Use ``module`` in order to load a\n              JavaScript ES6 module.\n\n        :return ElementHandle: :class:`~pyppeteer.element_handle.ElementHandle`\n                               of added tag.\n        \"\"\"\n        frame = self.mainFrame\n        if not frame:\n            raise PageError('no main frame.')\n        options = merge_dict(options, kwargs)\n        return await frame.addScriptTag(options)\n\n    async def addStyleTag(self, options: Dict = None, **kwargs: str) -> ElementHandle:\n        \"\"\"Add style or link tag to this page.\n\n        One of ``url``, ``path`` or ``content`` option is necessary.\n            * ``url`` (string): URL of the link tag to add.\n            * ``path`` (string): Path to the local CSS file to add.\n            * ``content`` (string): CSS string to add.\n\n        :return ElementHandle: :class:`~pyppeteer.element_handle.ElementHandle`\n                               of added tag.\n        \"\"\"\n        frame = self.mainFrame\n        if not frame:\n            raise PageError('no main frame.')\n        options = merge_dict(options, kwargs)\n        return await frame.addStyleTag(options)\n\n    async def injectFile(self, filePath: str) -> str:\n        \"\"\"[Deprecated] Inject file to this page.\n\n        This method is deprecated. Use :meth:`addScriptTag` instead.\n        \"\"\"\n        frame = self.mainFrame\n        if not frame:\n            raise PageError('no main frame.')\n        return await frame.injectFile(filePath)\n\n    async def exposeFunction(self, name: str, pyppeteerFunction: Callable[..., Any]) -> None:\n        \"\"\"Add python function to the browser's ``window`` object as ``name``.\n\n        Registered function can be called from chrome process.\n\n        :arg string name: Name of the function on the window object.\n        :arg Callable pyppeteerFunction: Function which will be called on\n                                         python process. This function should\n                                         not be asynchronous function.\n        \"\"\"\n        if self._pageBindings.get(name):\n            raise PageError(f'Failed to add page binding with name {name}: ' f'window[\"{name}\"] already exists!')\n        self._pageBindings[name] = pyppeteerFunction\n\n        addPageBinding = '''\nfunction addPageBinding(bindingName) {\n  const binding = window[bindingName];\n  window[bindingName] = async(...args) => {\n    const me = window[bindingName];\n    let callbacks = me['callbacks'];\n    if (!callbacks) {\n      callbacks = new Map();\n      me['callbacks'] = callbacks;\n    }\n    const seq = (me['lastSeq'] || 0) + 1;\n    me['lastSeq'] = seq;\n    const promise = new Promise(fulfill => callbacks.set(seq, fulfill));\n    binding(JSON.stringify({name: bindingName, seq, args}));\n    return promise;\n  };\n}\n        '''  # noqa: E501\n        expression = helper.evaluationString(addPageBinding, name)\n        await self._client.send('Runtime.addBinding', {'name': name})\n        await self._client.send('Page.addScriptToEvaluateOnNewDocument', {'source': expression})\n\n        async def _evaluate(frame: Frame, expression: str) -> None:\n            try:\n                await frame.evaluate(expression, force_expr=True)\n            except Exception as e:\n                debugError(logger, e)\n\n        loop = asyncio.get_event_loop()\n        await asyncio.wait([loop.create_task(_evaluate(frame, expression)) for frame in self.frames])\n\n    async def authenticate(self, credentials: Dict[str, str]) -> Any:\n        \"\"\"Provide credentials for http authentication.\n\n        ``credentials`` should be ``None`` or dict which has ``username`` and\n        ``password`` field.\n        \"\"\"\n        return await self._networkManager.authenticate(credentials)\n\n    async def setExtraHTTPHeaders(self, headers: Dict[str, str]) -> None:\n        \"\"\"Set extra HTTP headers.\n\n        The extra HTTP headers will be sent with every request the page\n        initiates.\n\n        .. note::\n            ``page.setExtraHTTPHeaders`` does not guarantee the order of\n            headers in the outgoing requests.\n\n        :arg Dict headers: A dictionary containing additional http headers to\n                           be sent with every requests. All header values must\n                           be string.\n        \"\"\"\n        return await self._networkManager.setExtraHTTPHeaders(headers)\n\n    async def setUserAgent(self, userAgent: str) -> None:\n        \"\"\"Set user agent to use in this page.\n\n        :arg str userAgent: Specific user agent to use in this page\n        \"\"\"\n        return await self._networkManager.setUserAgent(userAgent)\n\n    async def metrics(self) -> Dict[str, Any]:\n        \"\"\"Get metrics.\n\n        Returns dictionary containing metrics as key/value pairs:\n\n        * ``Timestamp`` (number): The timestamp when the metrics sample was\n          taken.\n        * ``Documents`` (int): Number of documents in the page.\n        * ``Frames`` (int): Number of frames in the page.\n        * ``JSEventListeners`` (int): Number of events in the page.\n        * ``Nodes`` (int): Number of DOM nodes in the page.\n        * ``LayoutCount`` (int): Total number of full partial page layout.\n        * ``RecalcStyleCount`` (int): Total number of page style\n          recalculations.\n        * ``LayoutDuration`` (int): Combined duration of page duration.\n        * ``RecalcStyleDuration`` (int): Combined duration of all page style\n          recalculations.\n        * ``ScriptDuration`` (int): Combined duration of JavaScript\n          execution.\n        * ``TaskDuration`` (int): Combined duration of all tasks performed by\n          the browser.\n        * ``JSHeapUsedSize`` (float): Used JavaScript heap size.\n        * ``JSHeapTotalSize`` (float): Total JavaScript heap size.\n        \"\"\"\n        response = await self._client.send('Performance.getMetrics')\n        return self._buildMetricsObject(response['metrics'])\n\n    def _emitMetrics(self, event: Dict) -> None:\n        self.emit(\n            Page.Events.Metrics, {'title': event['title'], 'metrics': self._buildMetricsObject(event['metrics']),}\n        )\n\n    def _buildMetricsObject(self, metrics: List) -> Dict[str, Any]:\n        result = {}\n        for metric in metrics or []:\n            if metric['name'] in supportedMetrics:\n                result[metric['name']] = metric['value']\n        return result\n\n    def _handleException(self, exceptionDetails: Dict) -> None:\n        message = helper.getExceptionMessage(exceptionDetails)\n        self.emit(Page.Events.PageError, PageError(message))\n\n    def _onConsoleAPI(self, event: dict) -> None:\n        _id = event['executionContextId']\n        context = self._frameManager.executionContextById(_id)\n        values: List[JSHandle] = []\n        for arg in event.get('args', []):\n            values.append(self._frameManager.createJSHandle(context, arg))\n        self._addConsoleMessage(event['type'], values)\n\n    def _onBindingCalled(self, event: Dict) -> None:\n        obj = json.loads(event['payload'])\n        name = obj['name']\n        seq = obj['seq']\n        args = obj['args']\n        result = self._pageBindings[name](*args)\n\n        deliverResult = '''\n            function deliverResult(name, seq, result) {\n                window[name]['callbacks'].get(seq)(result);\n                window[name]['callbacks'].delete(seq);\n            }\n        '''\n\n        expression = helper.evaluationString(deliverResult, name, seq, result)\n        try:\n            self._client.send('Runtime.evaluate', {'expression': expression, 'contextId': event['executionContextId'],})\n        except Exception as e:\n            helper.debugError(logger, e)\n\n    def _addConsoleMessage(self, type: str, args: List[JSHandle]) -> None:\n        if not self.listeners(Page.Events.Console):\n            for arg in args:\n                self._client._loop.create_task(arg.dispose())\n            return\n\n        textTokens = []\n        for arg in args:\n            remoteObject = arg._remoteObject\n            if remoteObject.get('objectId'):\n                textTokens.append(arg.toString())\n            else:\n                textTokens.append(str(helper.valueFromRemoteObject(remoteObject)))\n\n        message = ConsoleMessage(type, ' '.join(textTokens), args)\n        self.emit(Page.Events.Console, message)\n\n    def _onDialog(self, event: Any) -> None:\n        dialogType = ''\n        _type = event.get('type')\n        if _type == 'alert':\n            dialogType = Dialog.Type.Alert\n        elif _type == 'confirm':\n            dialogType = Dialog.Type.Confirm\n        elif _type == 'prompt':\n            dialogType = Dialog.Type.Prompt\n        elif _type == 'beforeunload':\n            dialogType = Dialog.Type.BeforeUnload\n        dialog = Dialog(self._client, dialogType, event.get('message'), event.get('defaultPrompt'))\n        self.emit(Page.Events.Dialog, dialog)\n\n    @property\n    def url(self) -> str:\n        \"\"\"Get URL of this page.\"\"\"\n        frame = self.mainFrame\n        if not frame:\n            raise PageError('no main frame.')\n        return frame.url\n\n    async def content(self) -> str:\n        \"\"\"Get the full HTML contents of the page.\n\n        Returns HTML including the doctype.\n        \"\"\"\n        frame = self.mainFrame\n        if frame is None:\n            raise PageError('No main frame.')\n        return await frame.content()\n\n    async def setContent(self, html: str) -> None:\n        \"\"\"Set content to this page.\n\n        :arg str html: HTML markup to assign to the page.\n        \"\"\"\n        frame = self.mainFrame\n        if frame is None:\n            raise PageError('No main frame.')\n        await frame.setContent(html)\n\n    async def goto(self, url: str, options: dict = None, **kwargs: Any) -> Optional[Response]:\n        \"\"\"Go to the ``url``.\n\n        :arg string url: URL to navigate page to. The url should include\n                         scheme, e.g. ``https://``.\n\n        Available options are:\n\n        * ``timeout`` (int): Maximum navigation time in milliseconds, defaults\n          to 30 seconds, pass ``0`` to disable timeout. The default value can\n          be changed by using the :meth:`setDefaultNavigationTimeout` method.\n        * ``waitUntil`` (str|List[str]): When to consider navigation succeeded,\n          defaults to ``load``. Given a list of event strings, navigation is\n          considered to be successful after all events have been fired. Events\n          can be either:\n\n          * ``load``: when ``load`` event is fired.\n          * ``domcontentloaded``: when the ``DOMContentLoaded`` event is fired.\n          * ``networkidle0``: when there are no more than 0 network connections\n            for at least 500 ms.\n          * ``networkidle2``: when there are no more than 2 network connections\n            for at least 500 ms.\n\n        The ``Page.goto`` will raise errors if:\n\n        * there's an SSL error (e.g. in case of self-signed certificates)\n        * target URL is invalid\n        * the ``timeout`` is exceeded during navigation\n        * then main resource failed to load\n\n        .. note::\n            :meth:`goto` either raise error or return a main resource response.\n            The only exceptions are navigation to ``about:blank`` or navigation\n            to the same URL with a different hash, which would succeed and\n            return ``None``.\n\n        .. note::\n            Headless mode doesn't support navigation to a PDF document.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n        mainFrame = self._frameManager.mainFrame\n        if mainFrame is None:\n            raise PageError('No main frame.')\n\n        referrer = self._networkManager.extraHTTPHeaders().get('referer', '')\n        requests: Dict[str, Request] = {}\n\n        def set_request(req: Request) -> None:\n            if req.url not in requests:\n                requests[req.url] = req\n\n        eventListeners = [helper.addEventListener(self._networkManager, NetworkManager.Events.Request, set_request,)]\n\n        timeout = options.get('timeout', self._defaultNavigationTimeout)\n        watcher = NavigatorWatcher(self._frameManager, mainFrame, timeout, options)\n\n        result = await self._navigate(url, referrer)\n        if result is not None:\n            raise PageError(result)\n        result = await watcher.navigationPromise()\n        watcher.cancel()\n        helper.removeEventListeners(eventListeners)\n        error = result[0].pop().exception()  # type: ignore\n        if error:\n            raise error\n\n        request = requests.get(mainFrame._navigationURL)\n        return request.response if request else None\n\n    async def _navigate(self, url: str, referrer: str) -> Optional[str]:\n        response = await self._client.send('Page.navigate', {'url': url, 'referrer': referrer})\n        if response.get('errorText'):\n            return f'{response[\"errorText\"]} at {url}'\n        return None\n\n    async def reload(self, options: dict = None, **kwargs: Any) -> Optional[Response]:\n        \"\"\"Reload this page.\n\n        Available options are same as :meth:`goto` method.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n        response = (await asyncio.gather(self.waitForNavigation(options), self._client.send('Page.reload'),))[0]\n        return response\n\n    async def waitForNavigation(self, options: dict = None, **kwargs: Any) -> Optional[Response]:\n        \"\"\"Wait for navigation.\n\n        Available options are same as :meth:`goto` method.\n\n        This returns :class:`~pyppeteer.network_manager.Response` when the page\n        navigates to a new URL or reloads. It is useful for when you run code\n        which will indirectly cause the page to navigate. In case of navigation\n        to a different anchor or navigation due to\n        `History API <https://developer.mozilla.org/en-US/docs/Web/API/History_API>`_\n        usage, the navigation will return ``None``.\n\n        Consider this example:\n\n        .. code::\n\n            navigationPromise = asyncio.ensure_future(page.waitForNavigation())\n            await page.click('a.my-link')  # indirectly cause a navigation\n            await navigationPromise  # wait until navigation finishes\n\n        or,\n\n        .. code::\n\n            await asyncio.wait([\n                page.click('a.my-link'),\n                page.waitForNavigation(),\n            ])\n\n        .. note::\n            Usage of the History API to change the URL is considered a\n            navigation.\n        \"\"\"  # noqa: E501\n        options = merge_dict(options, kwargs)\n        mainFrame = self._frameManager.mainFrame\n        if mainFrame is None:\n            raise PageError('No main frame.')\n        timeout = options.get('timeout', self._defaultNavigationTimeout)\n        watcher = NavigatorWatcher(self._frameManager, mainFrame, timeout, options)\n        responses: Dict[str, Response] = {}\n        listener = helper.addEventListener(\n            self._networkManager,\n            NetworkManager.Events.Response,\n            lambda response: responses.__setitem__(response.url, response),\n        )\n        result = await watcher.navigationPromise()\n        helper.removeEventListeners([listener])\n        error = result[0].pop().exception()\n        if error:\n            raise error\n\n        response = responses.get(self.url, None)\n        return response\n\n    async def waitForRequest(\n        self, urlOrPredicate: Union[str, Callable[[Request], bool]], options: Dict = None, **kwargs: Any  # noqa: E501\n    ) -> Request:\n        \"\"\"Wait for request.\n\n        :arg urlOrPredicate: A URL or function to wait for.\n\n        This method accepts below options:\n\n        * ``timeout`` (int|float): Maximum wait time in milliseconds, defaults\n          to 30 seconds, pass ``0`` to disable the timeout.\n\n        Example:\n\n        .. code::\n\n            firstRequest = await page.waitForRequest('http://example.com/resource')\n            finalRequest = await page.waitForRequest(lambda req: req.url == 'http://example.com' and req.method == 'GET')\n            return firstRequest.url\n        \"\"\"  # noqa: E501\n        options = merge_dict(options, kwargs)\n        timeout = options.get('timeout', 30000)\n\n        def predicate(request: Request) -> bool:\n            if isinstance(urlOrPredicate, str):\n                return urlOrPredicate == request.url\n            if callable(urlOrPredicate):\n                return bool(urlOrPredicate(request))\n            return False\n\n        return await helper.waitForEvent(\n            self._networkManager, NetworkManager.Events.Request, predicate, timeout, self._client._loop,\n        )\n\n    async def waitForResponse(\n        self, urlOrPredicate: Union[str, Callable[[Response], bool]], options: Dict = None, **kwargs: Any  # noqa: E501\n    ) -> Response:\n        \"\"\"Wait for response.\n\n        :arg urlOrPredicate: A URL or function to wait for.\n\n        This method accepts below options:\n\n        * ``timeout`` (int|float): Maximum wait time in milliseconds, defaults\n          to 30 seconds, pass ``0`` to disable the timeout.\n\n        Example:\n\n        .. code::\n\n            firstResponse = await page.waitForResponse('http://example.com/resource')\n            finalResponse = await page.waitForResponse(lambda res: res.url == 'http://example.com' and res.status == 200)\n            return finalResponse.ok\n        \"\"\"  # noqa: E501\n        options = merge_dict(options, kwargs)\n        timeout = options.get('timeout', 30000)\n\n        def predicate(response: Response) -> bool:\n            if isinstance(urlOrPredicate, str):\n                return urlOrPredicate == response.url\n            if callable(urlOrPredicate):\n                return bool(urlOrPredicate(response))\n            return False\n\n        return await helper.waitForEvent(\n            self._networkManager, NetworkManager.Events.Response, predicate, timeout, self._client._loop,\n        )\n\n    async def goBack(self, options: dict = None, **kwargs: Any) -> Optional[Response]:\n        \"\"\"Navigate to the previous page in history.\n\n        Available options are same as :meth:`goto` method.\n\n        If cannot go back, return ``None``.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n        return await self._go(-1, options)\n\n    async def goForward(self, options: dict = None, **kwargs: Any) -> Optional[Response]:\n        \"\"\"Navigate to the next page in history.\n\n        Available options are same as :meth:`goto` method.\n\n        If cannot go forward, return ``None``.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n        return await self._go(+1, options)\n\n    async def _go(self, delta: int, options: dict) -> Optional[Response]:\n        history = await self._client.send('Page.getNavigationHistory')\n        _count = history.get('currentIndex', 0) + delta\n        entries = history.get('entries', [])\n        if len(entries) <= _count:\n            return None\n        entry = entries[_count]\n        response = (\n            await asyncio.gather(\n                self.waitForNavigation(options),\n                self._client.send('Page.navigateToHistoryEntry', {'entryId': entry.get('id')}),\n            )\n        )[0]\n        return response\n\n    async def bringToFront(self) -> None:\n        \"\"\"Bring page to front (activate tab).\"\"\"\n        await self._client.send('Page.bringToFront')\n\n    async def emulate(self, options: dict = None, **kwargs: Any) -> None:\n        \"\"\"Emulate given device metrics and user agent.\n\n        This method is a shortcut for calling two methods:\n\n        * :meth:`setUserAgent`\n        * :meth:`setViewport`\n\n        ``options`` is a dictionary containing these fields:\n\n        * ``viewport`` (dict)\n\n          * ``width`` (int): page width in pixels.\n          * ``height`` (int): page width in pixels.\n          * ``deviceScaleFactor`` (float): Specify device scale factor (can be\n            thought as dpr). Defaults to 1.\n          * ``isMobile`` (bool): Whether the ``meta viewport`` tag is taken\n            into account. Defaults to ``False``.\n          * ``hasTouch`` (bool): Specifies if viewport supports touch events.\n            Defaults to ``False``.\n          * ``isLandscape`` (bool): Specifies if viewport is in landscape mode.\n            Defaults to ``False``.\n\n        * ``userAgent`` (str): user agent string.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n        # TODO: if options does not have viewport or userAgent,\n        # skip its setting.\n        await self.setViewport(options.get('viewport', {}))\n        await self.setUserAgent(options.get('userAgent', ''))\n\n    async def setJavaScriptEnabled(self, enabled: bool) -> None:\n        \"\"\"Set JavaScript enable/disable.\"\"\"\n        if self._javascriptEnabled == enabled:\n            return\n        self._javascriptEnabled = enabled\n        await self._client.send('Emulation.setScriptExecutionDisabled', {'value': not enabled,})\n\n    async def setBypassCSP(self, enabled: bool) -> None:\n        \"\"\"Toggles bypassing page's Content-Security-Policy.\n\n        .. note::\n            CSP bypassing happens at the moment of CSP initialization rather\n            then evaluation. Usually this means that ``page.setBypassCSP``\n            should be called before navigating to the domain.\n        \"\"\"\n        await self._client.send('Page.setBypassCSP', {'enabled': enabled})\n\n    async def emulateMedia(self, mediaType: str = None) -> None:\n        \"\"\"Emulate css media type of the page.\n\n        :arg str mediaType: Changes the CSS media type of the page. The only\n                            allowed values are ``'screen'``, ``'print'``, and\n                            ``None``. Passing ``None`` disables media\n                            emulation.\n        \"\"\"\n        if mediaType not in ['screen', 'print', None, '']:\n            raise ValueError(f'Unsupported media type: {mediaType}')\n        await self._client.send('Emulation.setEmulatedMedia', {'media': mediaType or '',})\n\n    async def setViewport(self, viewport: dict) -> None:\n        \"\"\"Set viewport.\n\n        Available options are:\n            * ``width`` (int): page width in pixel.\n            * ``height`` (int): page height in pixel.\n            * ``deviceScaleFactor`` (float): Default to 1.0.\n            * ``isMobile`` (bool): Default to ``False``.\n            * ``hasTouch`` (bool): Default to ``False``.\n            * ``isLandscape`` (bool): Default to ``False``.\n        \"\"\"\n        needsReload = await self._emulationManager.emulateViewport(viewport)\n        self._viewport = viewport\n        if needsReload:\n            await self.reload()\n\n    @property\n    def viewport(self) -> Optional[Dict]:\n        \"\"\"Get viewport as a dictionary or None.\n\n        Fields of returned dictionary is same as :meth:`setViewport`.\n        \"\"\"\n        return self._viewport\n\n    async def evaluate(self, pageFunction: str, *args: Any, force_expr: bool = False) -> Any:\n        \"\"\"Execute js-function or js-expression on browser and get result.\n\n        :arg str pageFunction: String of js-function/expression to be executed\n                               on the browser.\n        :arg bool force_expr: If True, evaluate `pageFunction` as expression.\n                              If False (default), try to automatically detect\n                              function or expression.\n\n        note: ``force_expr`` option is a keyword only argument.\n        \"\"\"\n        frame = self.mainFrame\n        if frame is None:\n            raise PageError('No main frame.')\n        return await frame.evaluate(pageFunction, *args, force_expr=force_expr)\n\n    async def evaluateOnNewDocument(self, pageFunction: str, *args: str) -> None:\n        \"\"\"Add a JavaScript function to the document.\n\n        This function would be invoked in one of the following scenarios:\n\n        * whenever the page is navigated\n        * whenever the child frame is attached or navigated. In this case, the\n          function is invoked in the context of the newly attached frame.\n        \"\"\"\n        source = helper.evaluationString(pageFunction, *args)\n        await self._client.send('Page.addScriptToEvaluateOnNewDocument', {'source': source,})\n\n    async def setCacheEnabled(self, enabled: bool = True) -> None:\n        \"\"\"Enable/Disable cache for each request.\n\n        By default, caching is enabled.\n        \"\"\"\n        await self._client.send('Network.setCacheDisabled', {'cacheDisabled': not enabled})\n\n    async def screenshot(self, options: dict = None, **kwargs: Any) -> Union[bytes, str]:\n        \"\"\"Take a screen shot.\n\n        The following options are available:\n\n        * ``path`` (str): The file path to save the image to. The screenshot\n          type will be inferred from the file extension.\n        * ``type`` (str): Specify screenshot type, can be either ``jpeg`` or\n          ``png``. Defaults to ``png``.\n        * ``quality`` (int): The quality of the image, between 0-100. Not\n          applicable to ``png`` image.\n        * ``fullPage`` (bool): When true, take a screenshot of the full\n          scrollable page. Defaults to ``False``.\n        * ``clip`` (dict): An object which specifies clipping region of the\n          page. This option should have the following fields:\n\n          * ``x`` (int): x-coordinate of top-left corner of clip area.\n          * ``y`` (int): y-coordinate of top-left corner of clip area.\n          * ``width`` (int): width of clipping area.\n          * ``height`` (int): height of clipping area.\n\n        * ``omitBackground`` (bool): Hide default white background and allow\n          capturing screenshot with transparency.\n        * ``encoding`` (str): The encoding of the image, can be either\n          ``'base64'`` or ``'binary'``. Defaults to ``'binary'``.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n        screenshotType = None\n        if 'type' in options:\n            screenshotType = options['type']\n            if screenshotType not in ['png', 'jpeg']:\n                raise ValueError(f'Unknown type value: {screenshotType}')\n        elif 'path' in options:\n            mimeType, _ = mimetypes.guess_type(options['path'])\n            if mimeType == 'image/png':\n                screenshotType = 'png'\n            elif mimeType == 'image/jpeg':\n                screenshotType = 'jpeg'\n            else:\n                raise ValueError('Unsupported screenshot ' f'mime type: {mimeType}')\n        if not screenshotType:\n            screenshotType = 'png'\n        return await self._screenshotTask(screenshotType, options)\n\n    async def _screenshotTask(self, format: str, options: dict) -> Union[bytes, str]:  # noqa: C901\n        await self._client.send('Target.activateTarget', {'targetId': self._target._targetId,})\n        clip, quality = options.get('clip'), options.get('quality')\n\n        if clip:\n            clip['scale'] = 1\n\n        scale = options.get('scale', 1)\n\n        if options.get('fullPage'):\n            metrics = await self._client.send('Page.getLayoutMetrics')\n            width = math.ceil(metrics['contentSize']['width'])\n            height = math.ceil(metrics['contentSize']['height'])\n\n            # Overwrite clip for full page at all times.\n            clip = dict(x=0, y=0, width=width, height=height, scale=1)\n            if self._viewport is not None:\n                mobile = self._viewport.get('isMobile', False)\n                deviceScaleFactor = self._viewport.get('deviceScaleFactor', scale)\n                landscape = self._viewport.get('isLandscape', False)\n            else:\n                mobile = False\n                deviceScaleFactor = scale\n                landscape = False\n\n            if landscape:\n                screenOrientation = dict(angle=90, type='landscapePrimary')\n            else:\n                screenOrientation = dict(angle=0, type='portraitPrimary')\n            await self._client.send(\n                'Emulation.setDeviceMetricsOverride',\n                {\n                    'mobile': mobile,\n                    'width': width,\n                    'height': height,\n                    'deviceScaleFactor': deviceScaleFactor,\n                    'screenOrientation': screenOrientation,\n                },\n            )\n\n        if options.get('omitBackground'):\n            await self._client.send(\n                'Emulation.setDefaultBackgroundColorOverride', {'color': {'r': 0, 'g': 0, 'b': 0, 'a': 0}},\n            )\n        opt = {'format': format}\n\n        if clip:\n            opt['clip'] = clip\n        if format == 'jpeg' and quality is not None:\n            opt['quality'] = quality\n\n        result = await self._client.send('Page.captureScreenshot', opt)\n\n        if options.get('omitBackground'):\n            await self._client.send('Emulation.setDefaultBackgroundColorOverride')\n\n        if options.get('fullPage') and self._viewport is not None:\n            await self.setViewport(self._viewport)\n\n        if options.get('encoding') == 'base64':\n            buffer = result.get('data', b'')\n        else:\n            buffer = base64.b64decode(result.get('data', b''))\n        _path = options.get('path')\n        if _path:\n            with open(_path, 'wb') as f:\n                f.write(buffer)\n        return buffer\n\n    async def pdf(self, options: dict = None, **kwargs: Any) -> bytes:\n        \"\"\"Generate a pdf of the page.\n\n        Options:\n\n        * ``path`` (str): The file path to save the PDF.\n        * ``scale`` (float): Scale of the webpage rendering, defaults to ``1``.\n        * ``displayHeaderFooter`` (bool): Display header and footer.\n          Defaults to ``False``.\n        * ``headerTemplate`` (str): HTML template for the print header. Should\n          be valid HTML markup with following classes.\n\n          * ``date``: formatted print date\n          * ``title``: document title\n          * ``url``: document location\n          * ``pageNumber``: current page number\n          * ``totalPages``: total pages in the document\n\n        * ``footerTemplate`` (str): HTML template for the print footer. Should\n          use the same template as ``headerTemplate``.\n        * ``printBackground`` (bool): Print background graphics. Defaults to\n          ``False``.\n        * ``landscape`` (bool): Paper orientation. Defaults to ``False``.\n        * ``pageRanges`` (string): Paper ranges to print, e.g., '1-5,8,11-13'.\n          Defaults to empty string, which means all pages.\n        * ``format`` (str): Paper format. If set, takes priority over\n          ``width`` or ``height``. Defaults to ``Letter``.\n        * ``width`` (str): Paper width, accepts values labeled with units.\n        * ``height`` (str): Paper height, accepts values labeled with units.\n        * ``margin`` (dict): Paper margins, defaults to ``None``.\n\n          * ``top`` (str): Top margin, accepts values labeled with units.\n          * ``right`` (str): Right margin, accepts values labeled with units.\n          * ``bottom`` (str): Bottom margin, accepts values labeled with units.\n          * ``left`` (str): Left margin, accepts values labeled with units.\n\n        * ``preferCSSPageSize``: Give any CSS ``@page`` size declared in the\n          page priority over what is declared in ``width`` and ``height`` or\n          ``format`` options. Defaults to ``False``, which will scale the\n          content to fit the paper size.\n\n        :return: Return generated PDF ``bytes`` object.\n\n        .. note::\n            Generating a pdf is currently only supported in headless mode.\n\n        :meth:`pdf` generates a pdf of the page with ``print`` css media. To\n        generate a pdf with ``screen`` media, call\n        ``page.emulateMedia('screen')`` before calling :meth:`pdf`.\n\n        .. note::\n            By default, :meth:`pdf` generates a pdf with modified colors for\n            printing. Use the ``--webkit-print-color-adjust`` property to force\n            rendering of exact colors.\n\n        .. code::\n\n            await page.emulateMedia('screen')\n            await page.pdf({'path': 'page.pdf'})\n\n        The ``width``, ``height``, and ``margin`` options accept values labeled\n        with units. Unlabeled values are treated as pixels.\n\n        A few examples:\n\n        - ``page.pdf({'width': 100})``: prints with width set to 100 pixels.\n        - ``page.pdf({'width': '100px'})``: prints with width set to 100 pixels.\n        - ``page.pdf({'width': '10cm'})``: prints with width set to 100 centimeters.\n\n        All available units are:\n\n        - ``px``: pixel\n        - ``in``: inch\n        - ``cm``: centimeter\n        - ``mm``: millimeter\n\n        The format options are:\n\n        - ``Letter``: 8.5in x 11in\n        - ``Legal``: 8.5in x 14in\n        - ``Tabloid``: 11in x 17in\n        - ``Ledger``: 17in x 11in\n        - ``A0``: 33.1in x 46.8in\n        - ``A1``: 23.4in x 33.1in\n        - ``A2``: 16.5in x 23.4in\n        - ``A3``: 11.7in x 16.5in\n        - ``A4``: 8.27in x 11.7in\n        - ``A5``: 5.83in x 8.27in\n        - ``A6``: 4.13in x 5.83in\n\n        .. note::\n            ``headerTemplate`` and ``footerTemplate`` markup have the following\n            limitations:\n\n            1. Script tags inside templates are not evaluated.\n            2. Page styles are not visible inside templates.\n        \"\"\"  # noqa: E501\n        options = merge_dict(options, kwargs)\n        scale = options.get('scale', 1)\n        displayHeaderFooter = bool(options.get('displayHeaderFooter'))\n        headerTemplate = options.get('headerTemplate', '')\n        footerTemplate = options.get('footerTemplate', '')\n        printBackground = bool(options.get('printBackground'))\n        landscape = bool(options.get('landscape'))\n        pageRanges = options.get('pageRanges', '')\n\n        paperWidth = 8.5\n        paperHeight = 11.0\n        if 'format' in options:\n            fmt = Page.PaperFormats.get(options['format'].lower())\n            if not fmt:\n                raise ValueError('Unknown paper format: ' + options['format'])\n            paperWidth = fmt['width']\n            paperHeight = fmt['height']\n        else:\n            paperWidth = convertPrintParameterToInches(options.get('width')) or paperWidth  # noqa: E501\n            paperHeight = convertPrintParameterToInches(options.get('height')) or paperHeight  # noqa: E501\n\n        marginOptions = options.get('margin', {})\n        marginTop = convertPrintParameterToInches(marginOptions.get('top')) or 0  # noqa: E501\n        marginLeft = convertPrintParameterToInches(marginOptions.get('left')) or 0  # noqa: E501\n        marginBottom = convertPrintParameterToInches(marginOptions.get('bottom')) or 0  # noqa: E501\n        marginRight = convertPrintParameterToInches(marginOptions.get('right')) or 0  # noqa: E501\n        preferCSSPageSize = options.get('preferCSSPageSize', False)\n\n        result = await self._client.send(\n            'Page.printToPDF',\n            dict(\n                landscape=landscape,\n                displayHeaderFooter=displayHeaderFooter,\n                headerTemplate=headerTemplate,\n                footerTemplate=footerTemplate,\n                printBackground=printBackground,\n                scale=scale,\n                paperWidth=paperWidth,\n                paperHeight=paperHeight,\n                marginTop=marginTop,\n                marginBottom=marginBottom,\n                marginLeft=marginLeft,\n                marginRight=marginRight,\n                pageRanges=pageRanges,\n                preferCSSPageSize=preferCSSPageSize,\n            ),\n        )\n        buffer = base64.b64decode(result.get('data', b''))\n        if 'path' in options:\n            with open(options['path'], 'wb') as f:\n                f.write(buffer)\n        return buffer\n\n    async def plainText(self) -> str:\n        \"\"\"[Deprecated] Get page content as plain text.\"\"\"\n        logger.warning('`Page.plainText` is deprecated.')\n        return await self.evaluate('() => document.body.innerText')\n\n    async def title(self) -> str:\n        \"\"\"Get page's title.\"\"\"\n        frame = self.mainFrame\n        if not frame:\n            raise PageError('no main frame.')\n        return await frame.title()\n\n    async def close(self, options: Dict = None, **kwargs: Any) -> None:\n        \"\"\"Close this page.\n\n        Available options:\n\n        * ``runBeforeUnload`` (bool): Defaults to ``False``. Whether to run the\n          `before unload <https://developer.mozilla.org/en-US/docs/Web/Events/beforeunload>`_\n          page handlers.\n\n        By defaults, :meth:`close` **does not** run beforeunload handlers.\n\n        .. note::\n           If ``runBeforeUnload`` is passed as ``True``, a ``beforeunload``\n           dialog might be summoned and should be handled manually via page's\n           ``dialog`` event.\n        \"\"\"  # noqa: E501\n        options = merge_dict(options, kwargs)\n        conn = self._client._connection\n        if conn is None:\n            raise PageError('Protocol Error: Connection Closed. ' 'Most likely the page has been closed.')\n        runBeforeUnload = bool(options.get('runBeforeUnload'))\n        if runBeforeUnload:\n            await self._client.send('Page.close')\n        else:\n            await conn.send('Target.closeTarget', {'targetId': self._target._targetId})\n            await self._target._isClosedPromise\n\n    def isClosed(self) -> bool:\n        \"\"\"Indicate that the page has been closed.\"\"\"\n        return self._closed\n\n    @property\n    def mouse(self) -> Mouse:\n        \"\"\"Get :class:`~pyppeteer.input.Mouse` object.\"\"\"\n        return self._mouse\n\n    async def click(self, selector: str, options: dict = None, **kwargs: Any) -> None:\n        \"\"\"Click element which matches ``selector``.\n\n        This method fetches an element with ``selector``, scrolls it into view\n        if needed, and then uses :attr:`mouse` to click in the center of the\n        element. If there's no element matching ``selector``, the method raises\n        ``PageError``.\n\n        Available options are:\n\n        * ``button`` (str): ``left``, ``right``, or ``middle``, defaults to\n          ``left``.\n        * ``clickCount`` (int): defaults to 1.\n        * ``delay`` (int|float): Time to wait between ``mousedown`` and\n          ``mouseup`` in milliseconds. defaults to 0.\n\n        .. note:: If this method triggers a navigation event and there's a\n            separate :meth:`waitForNavigation`, you may end up with a race\n            condition that yields unexpected results. The correct pattern for\n            click and wait for navigation is the following::\n\n                await asyncio.gather(\n                    page.waitForNavigation(waitOptions),\n                    page.click(selector, clickOptions),\n                )\n        \"\"\"\n        frame = self.mainFrame\n        if frame is None:\n            raise PageError('No main frame.')\n        await frame.click(selector, options, **kwargs)\n\n    async def hover(self, selector: str) -> None:\n        \"\"\"Mouse hover the element which matches ``selector``.\n\n        If no element matched the ``selector``, raise ``PageError``.\n        \"\"\"\n        frame = self.mainFrame\n        if frame is None:\n            raise PageError('No main frame.')\n        await frame.hover(selector)\n\n    async def focus(self, selector: str) -> None:\n        \"\"\"Focus the element which matches ``selector``.\n\n        If no element matched the ``selector``, raise ``PageError``.\n        \"\"\"\n        frame = self.mainFrame\n        if frame is None:\n            raise PageError('No main frame.')\n        await frame.focus(selector)\n\n    async def select(self, selector: str, *values: str) -> List[str]:\n        \"\"\"Select options and return selected values.\n\n        If no element matched the ``selector``, raise ``ElementHandleError``.\n        \"\"\"\n        frame = self.mainFrame\n        if not frame:\n            raise PageError('no main frame.')\n        return await frame.select(selector, *values)\n\n    async def type(self, selector: str, text: str, options: dict = None, **kwargs: Any) -> None:\n        \"\"\"Type ``text`` on the element which matches ``selector``.\n\n        If no element matched the ``selector``, raise ``PageError``.\n\n        Details see :meth:`pyppeteer.input.Keyboard.type`.\n        \"\"\"\n        frame = self.mainFrame\n        if not frame:\n            raise PageError('no main frame.')\n        return await frame.type(selector, text, options, **kwargs)\n\n    def waitFor(\n        self, selectorOrFunctionOrTimeout: Union[str, int, float], options: dict = None, *args: Any, **kwargs: Any\n    ) -> Awaitable:\n        \"\"\"Wait for function, timeout, or element which matches on page.\n\n        This method behaves differently with respect to the first argument:\n\n        * If ``selectorOrFunctionOrTimeout`` is number (int or float), then it\n          is treated as a timeout in milliseconds and this returns future which\n          will be done after the timeout.\n        * If ``selectorOrFunctionOrTimeout`` is a string of JavaScript\n          function, this method is a shortcut to :meth:`waitForFunction`.\n        * If ``selectorOrFunctionOrTimeout`` is a selector string or xpath\n          string, this method is a shortcut to :meth:`waitForSelector` or\n          :meth:`waitForXPath`. If the string starts with ``//``, the string is\n          treated as xpath.\n\n        Pyppeteer tries to automatically detect function or selector, but\n        sometimes miss-detects. If not work as you expected, use\n        :meth:`waitForFunction` or :meth:`waitForSelector` directly.\n\n        :arg selectorOrFunctionOrTimeout: A selector, xpath, or function\n                                          string, or timeout (milliseconds).\n        :arg Any args: Arguments to pass the function.\n        :return: Return awaitable object which resolves to a JSHandle of the\n                 success value.\n\n        Available options: see :meth:`waitForFunction` or\n        :meth:`waitForSelector`\n        \"\"\"\n        frame = self.mainFrame\n        if not frame:\n            raise PageError('no main frame.')\n        return frame.waitFor(selectorOrFunctionOrTimeout, options, *args, **kwargs)\n\n    def waitForSelector(self, selector: str, options: dict = None, **kwargs: Any) -> Awaitable:\n        \"\"\"Wait until element which matches ``selector`` appears on page.\n\n        Wait for the ``selector`` to appear in page. If at the moment of\n        calling the method the ``selector`` already exists, the method will\n        return immediately. If the selector doesn't appear after the\n        ``timeout`` milliseconds of waiting, the function will raise error.\n\n        :arg str selector: A selector of an element to wait for.\n        :return: Return awaitable object which resolves when element specified\n                 by selector string is added to DOM.\n\n        This method accepts the following options:\n\n        * ``visible`` (bool): Wait for element to be present in DOM and to be\n          visible; i.e. to not have ``display: none`` or ``visibility: hidden``\n          CSS properties. Defaults to ``False``.\n        * ``hidden`` (bool): Wait for element to not be found in the DOM or to\n          be hidden, i.e. have ``display: none`` or ``visibility: hidden`` CSS\n          properties. Defaults to ``False``.\n        * ``timeout`` (int|float): Maximum time to wait for in milliseconds.\n          Defaults to 30000 (30 seconds). Pass ``0`` to disable timeout.\n        \"\"\"\n        frame = self.mainFrame\n        if not frame:\n            raise PageError('no main frame.')\n        return frame.waitForSelector(selector, options, **kwargs)\n\n    def waitForXPath(self, xpath: str, options: dict = None, **kwargs: Any) -> Awaitable:\n        \"\"\"Wait until element which matches ``xpath`` appears on page.\n\n        Wait for the ``xpath`` to appear in page. If the moment of calling the\n        method the ``xpath`` already exists, the method will return\n        immediately. If the xpath doesn't appear after ``timeout`` milliseconds\n        of waiting, the function will raise exception.\n\n\n        :arg str xpath: A [xpath] of an element to wait for.\n        :return: Return awaitable object which resolves when element specified\n                 by xpath string is added to DOM.\n\n        Available options are:\n\n        * ``visible`` (bool): wait for element to be present in DOM and to be\n          visible, i.e. to not have ``display: none`` or ``visibility: hidden``\n          CSS properties. Defaults to ``False``.\n        * ``hidden`` (bool): wait for element to not be found in the DOM or to\n          be hidden, i.e. have ``display: none`` or ``visibility: hidden`` CSS\n          properties. Defaults to ``False``.\n        * ``timeout`` (int|float): maximum time to wait for in milliseconds.\n          Defaults to 30000 (30 seconds). Pass ``0`` to disable timeout.\n        \"\"\"\n        frame = self.mainFrame\n        if not frame:\n            raise PageError('no main frame.')\n        return frame.waitForXPath(xpath, options, **kwargs)\n\n    def waitForFunction(self, pageFunction: str, options: dict = None, *args: str, **kwargs: Any) -> Awaitable:\n        \"\"\"Wait until the function completes and returns a truthy value.\n\n        :arg Any args: Arguments to pass to ``pageFunction``.\n        :return: Return awaitable object which resolves when the\n                 ``pageFunction`` returns a truthy value. It resolves to a\n                 :class:`~pyppeteer.execution_context.JSHandle` of the truthy\n                 value.\n\n        This method accepts the following options:\n\n        * ``polling`` (str|number): An interval at which the ``pageFunction``\n          is executed, defaults to ``raf``. If ``polling`` is a number, then\n          it is treated as an interval in milliseconds at which the function\n          would be executed. If ``polling`` is a string, then it can be one of\n          the following values:\n\n          * ``raf``: to constantly execute ``pageFunction`` in\n            ``requestAnimationFrame`` callback. This is the tightest polling\n            mode which is suitable to observe styling changes.\n          * ``mutation``: to execute ``pageFunction`` on every DOM mutation.\n\n        * ``timeout`` (int|float): maximum time to wait for in milliseconds.\n          Defaults to 30000 (30 seconds). Pass ``0`` to disable timeout.\n        \"\"\"\n        frame = self.mainFrame\n        if not frame:\n            raise PageError('no main frame.')\n        return frame.waitForFunction(pageFunction, options, *args, **kwargs)\n\n\nsupportedMetrics = (\n    'Timestamp',\n    'Documents',\n    'Frames',\n    'JSEventListeners',\n    'Nodes',\n    'LayoutCount',\n    'RecalcStyleCount',\n    'LayoutDuration',\n    'RecalcStyleDuration',\n    'ScriptDuration',\n    'TaskDuration',\n    'JSHeapUsedSize',\n    'JSHeapTotalSize',\n)\n\n\nunitToPixels = {'px': 1, 'in': 96, 'cm': 37.8, 'mm': 3.78}\n\n\ndef convertPrintParameterToInches(parameter: Union[None, int, float, str]) -> Optional[float]:\n    \"\"\"Convert print parameter to inches.\"\"\"\n    if parameter is None:\n        return None\n    if isinstance(parameter, (int, float)):\n        pixels = parameter\n    elif isinstance(parameter, str):\n        text = parameter\n        unit = text[-2:].lower()\n        if unit in unitToPixels:\n            valueText = text[:-2]\n        else:\n            unit = 'px'\n            valueText = text\n        try:\n            value = float(valueText)\n        except ValueError:\n            raise ValueError('Failed to parse parameter value: ' + text)\n        pixels = value * unitToPixels[unit]\n    else:\n        raise TypeError('page.pdf() Cannot handle parameter type: ' + str(type(parameter)))\n    return pixels / 96\n\n\nclass ConsoleMessage(object):\n    \"\"\"Console message class.\n\n    ConsoleMessage objects are dispatched by page via the ``console`` event.\n    \"\"\"\n\n    def __init__(self, type: str, text: str, args: List[JSHandle] = None) -> None:\n        #: (str) type of console message\n        self._type = type\n        #: (str) console message string\n        self._text = text\n        #: list of JSHandle\n        self._args = args if args is not None else []\n\n    @property\n    def type(self) -> str:\n        \"\"\"Return type of this message.\"\"\"\n        return self._type\n\n    @property\n    def text(self) -> str:\n        \"\"\"Return text representation of this message.\"\"\"\n        return self._text\n\n    @property\n    def args(self) -> List[JSHandle]:\n        \"\"\"Return list of args (JSHandle) of this message.\"\"\"\n        return self._args\n"
  },
  {
    "path": "pyppeteer/target.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"Target module.\"\"\"\n\nimport asyncio\nfrom typing import Any, Callable, Coroutine, Dict, List, Optional\nfrom typing import TYPE_CHECKING\n\nfrom pyppeteer.connection import CDPSession\nfrom pyppeteer.page import Page\n\nif TYPE_CHECKING:\n    from pyppeteer.browser import Browser, BrowserContext  # noqa: F401\n\n\nclass Target(object):\n    \"\"\"Browser's target class.\"\"\"\n\n    def __init__(self, targetInfo: Dict, browserContext: 'BrowserContext',\n                 sessionFactory: Callable[[], Coroutine[Any, Any, CDPSession]],\n                 ignoreHTTPSErrors: bool, defaultViewport: Optional[Dict],\n                 screenshotTaskQueue: List, loop: asyncio.AbstractEventLoop\n                 ) -> None:\n        self._targetInfo = targetInfo\n        self._browserContext = browserContext\n        self._targetId = targetInfo.get('targetId', '')\n        self._sessionFactory = sessionFactory\n        self._ignoreHTTPSErrors = ignoreHTTPSErrors\n        self._defaultViewport = defaultViewport\n        self._screenshotTaskQueue = screenshotTaskQueue\n        self._loop = loop\n        self._page: Optional[Page] = None\n\n        self._initializedPromise = self._loop.create_future()\n        self._isClosedPromise = self._loop.create_future()\n        self._isInitialized = (self._targetInfo['type'] != 'page'\n                               or self._targetInfo['url'] != '')\n        if self._isInitialized:\n            self._initializedCallback(True)\n\n    def _initializedCallback(self, bl: bool) -> None:\n        # TODO: this may cause error on page close\n        if self._initializedPromise.done():\n            self._initializedPromise = self._loop.create_future()\n        self._initializedPromise.set_result(bl)\n\n    def _closedCallback(self) -> None:\n        self._isClosedPromise.set_result(None)\n\n    async def createCDPSession(self) -> CDPSession:\n        \"\"\"Create a Chrome Devtools Protocol session attached to the target.\"\"\"\n        return await self._sessionFactory()\n\n    async def page(self) -> Optional[Page]:\n        \"\"\"Get page of this target.\n\n        If the target is not of type \"page\" or \"background_page\", return\n        ``None``.\n        \"\"\"\n        if (self._targetInfo['type'] in ['page', 'background_page'] and\n                self._page is None):\n            client = await self._sessionFactory()\n            new_page = await Page.create(\n                client, self,\n                self._ignoreHTTPSErrors,\n                self._defaultViewport,\n                self._screenshotTaskQueue,\n            )\n            self._page = new_page\n            return new_page\n        return self._page\n\n    @property\n    def url(self) -> str:\n        \"\"\"Get url of this target.\"\"\"\n        return self._targetInfo['url']\n\n    @property\n    def type(self) -> str:\n        \"\"\"Get type of this target.\n\n        Type can be ``'page'``, ``'background_page'``, ``'service_worker'``,\n        ``'browser'``, or ``'other'``.\n        \"\"\"\n        _type = self._targetInfo['type']\n        if _type in ['page', 'background_page', 'service_worker', 'browser']:\n            return _type\n        return 'other'\n\n    @property\n    def browser(self) -> 'Browser':\n        \"\"\"Get the browser the target belongs to.\"\"\"\n        return self._browserContext.browser\n\n    @property\n    def browserContext(self) -> 'BrowserContext':\n        \"\"\"Return the browser context the target belongs to.\"\"\"\n        return self._browserContext\n\n    @property\n    def opener(self) -> Optional['Target']:\n        \"\"\"Get the target that opened this target.\n\n        Top-level targets return ``None``.\n        \"\"\"\n        openerId = self._targetInfo.get('openerId')\n        if openerId is None:\n            return None\n        return self.browser._targets.get(openerId)\n\n    def _targetInfoChanged(self, targetInfo: Dict) -> None:\n        self._targetInfo = targetInfo\n\n        if not self._isInitialized and (self._targetInfo['type'] != 'page' or\n                                        self._targetInfo['url'] != ''):\n            self._isInitialized = True\n            self._initializedCallback(True)\n            return\n"
  },
  {
    "path": "pyppeteer/tracing.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"Tracing module.\"\"\"\n\nfrom pathlib import Path\nfrom typing import Any\n\nfrom pyppeteer.connection import CDPSession\nfrom pyppeteer.util import merge_dict\n\n\nclass Tracing(object):\n    \"\"\"Tracing class.\n\n    You can use :meth:`start` and :meth:`stop` to create a trace file which can\n    be opened in Chrome DevTools or\n    `timeline viewer <https://chromedevtools.github.io/timeline-viewer/>`_.\n\n    .. code::\n\n        await page.tracing.start({'path': 'trace.json'})\n        await page.goto('https://www.google.com')\n        await page.tracing.stop()\n    \"\"\"\n\n    def __init__(self, client: CDPSession) -> None:\n        self._client = client\n        self._recording = False\n        self._path = ''\n\n    async def start(self, options: dict = None, **kwargs: Any) -> None:\n        \"\"\"Start tracing.\n\n        Only one trace can be active at a time per browser.\n\n        This method accepts the following options:\n\n        * ``path`` (str): A path to write the trace file to.\n        * ``screenshots`` (bool): Capture screenshots in the trace.\n        * ``categories`` (List[str]): Specify custom categories to use instead\n          of default.\n        \"\"\"\n        options = merge_dict(options, kwargs)\n        defaultCategories = [\n            '-*', 'devtools.timeline', 'v8.execute',\n            'disabled-by-default-devtools.timeline',\n            'disabled-by-default-devtools.timeline.frame', 'toplevel',\n            'blink.console', 'blink.user_timing', 'latencyInfo',\n            'disabled-by-default-devtools.timeline.stack',\n            'disabled-by-default-v8.cpu_profiler',\n            'disabled-by-default-v8.cpu_profiler.hires',\n        ]\n        categoriesArray = options.get('categories', defaultCategories)\n\n        if 'screenshots' in options:\n            categoriesArray.append('disabled-by-default-devtools.screenshot')\n\n        self._path = options.get('path', '')\n        self._recording = True\n        await self._client.send('Tracing.start', {\n            'transferMode': 'ReturnAsStream',\n            'categories': ','.join(categoriesArray),\n        })\n\n    async def stop(self) -> str:\n        \"\"\"Stop tracing.\n\n        :return: trace data as string.\n        \"\"\"\n        contentPromise = self._client._loop.create_future()\n        self._client.once(\n            'Tracing.tracingComplete',\n            lambda event: self._client._loop.create_task(\n                self._readStream(event.get('stream'), self._path)\n            ).add_done_callback(\n                lambda fut: contentPromise.set_result(fut.result())\n            )\n        )\n        await self._client.send('Tracing.end')\n        self._recording = False\n        return await contentPromise\n\n    async def _readStream(self, handle: str, path: str) -> str:\n        # might be better to return as bytes\n        eof = False\n        bufs = []\n        while not eof:\n            response = await self._client.send('IO.read', {'handle': handle})\n            eof = response.get('eof', False)\n            bufs.append(response.get('data', ''))\n        await self._client.send('IO.close', {'handle': handle})\n\n        result = ''.join(bufs)\n        if path:\n            file = Path(path)\n            with file.open('w', encoding='utf-8') as f:\n                f.write(result)\n        return result\n"
  },
  {
    "path": "pyppeteer/us_keyboard_layout.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n# flake8: noqa\n\n\"\"\"US Keyboard Definition.\"\"\"\n\nkeyDefinitions = {\n    '0': {'keyCode': 48, 'key': '0', 'code': 'Digit0'},\n    '1': {'keyCode': 49, 'key': '1', 'code': 'Digit1'},\n    '2': {'keyCode': 50, 'key': '2', 'code': 'Digit2'},\n    '3': {'keyCode': 51, 'key': '3', 'code': 'Digit3'},\n    '4': {'keyCode': 52, 'key': '4', 'code': 'Digit4'},\n    '5': {'keyCode': 53, 'key': '5', 'code': 'Digit5'},\n    '6': {'keyCode': 54, 'key': '6', 'code': 'Digit6'},\n    '7': {'keyCode': 55, 'key': '7', 'code': 'Digit7'},\n    '8': {'keyCode': 56, 'key': '8', 'code': 'Digit8'},\n    '9': {'keyCode': 57, 'key': '9', 'code': 'Digit9'},\n    'Power': {'key': 'Power', 'code': 'Power'},\n    'Eject': {'key': 'Eject', 'code': 'Eject'},\n    'Abort': {'keyCode': 3, 'code': 'Abort', 'key': 'Cancel'},\n    'Help': {'keyCode': 6, 'code': 'Help', 'key': 'Help'},\n    'Backspace': {'keyCode': 8, 'code': 'Backspace', 'key': 'Backspace'},\n    'Tab': {'keyCode': 9, 'code': 'Tab', 'key': 'Tab'},\n    'Numpad5': {'keyCode': 12, 'shiftKeyCode': 101, 'key': 'Clear', 'code': 'Numpad5', 'shiftKey': '5', 'location': 3},\n    'NumpadEnter': {'keyCode': 13, 'code': 'NumpadEnter', 'key': 'Enter', 'text': '\\r', 'location': 3},\n    'Enter': {'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\\r'},\n    '\\r': {'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\\r'},\n    '\\n': {'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\\r'},\n    'ShiftLeft': {'keyCode': 16, 'code': 'ShiftLeft', 'key': 'Shift', 'location': 1},\n    'ShiftRight': {'keyCode': 16, 'code': 'ShiftRight', 'key': 'Shift', 'location': 2},\n    'ControlLeft': {'keyCode': 17, 'code': 'ControlLeft', 'key': 'Control', 'location': 1},\n    'ControlRight': {'keyCode': 17, 'code': 'ControlRight', 'key': 'Control', 'location': 2},\n    'AltLeft': {'keyCode': 18, 'code': 'AltLeft', 'key': 'Alt', 'location': 1},\n    'AltRight': {'keyCode': 18, 'code': 'AltRight', 'key': 'Alt', 'location': 2},\n    'Pause': {'keyCode': 19, 'code': 'Pause', 'key': 'Pause'},\n    'CapsLock': {'keyCode': 20, 'code': 'CapsLock', 'key': 'CapsLock'},\n    'Escape': {'keyCode': 27, 'code': 'Escape', 'key': 'Escape'},\n    'Convert': {'keyCode': 28, 'code': 'Convert', 'key': 'Convert'},\n    'NonConvert': {'keyCode': 29, 'code': 'NonConvert', 'key': 'NonConvert'},\n    'Space': {'keyCode': 32, 'code': 'Space', 'key': ' '},\n    'Numpad9': {'keyCode': 33, 'shiftKeyCode': 105, 'key': 'PageUp', 'code': 'Numpad9', 'shiftKey': '9', 'location': 3},\n    'PageUp': {'keyCode': 33, 'code': 'PageUp', 'key': 'PageUp'},\n    'Numpad3': {'keyCode': 34, 'shiftKeyCode': 99, 'key': 'PageDown', 'code': 'Numpad3', 'shiftKey': '3', 'location': 3},\n    'PageDown': {'keyCode': 34, 'code': 'PageDown', 'key': 'PageDown'},\n    'End': {'keyCode': 35, 'code': 'End', 'key': 'End'},\n    'Numpad1': {'keyCode': 35, 'shiftKeyCode': 97, 'key': 'End', 'code': 'Numpad1', 'shiftKey': '1', 'location': 3},\n    'Home': {'keyCode': 36, 'code': 'Home', 'key': 'Home'},\n    'Numpad7': {'keyCode': 36, 'shiftKeyCode': 103, 'key': 'Home', 'code': 'Numpad7', 'shiftKey': '7', 'location': 3},\n    'ArrowLeft': {'keyCode': 37, 'code': 'ArrowLeft', 'key': 'ArrowLeft'},\n    'Numpad4': {'keyCode': 37, 'shiftKeyCode': 100, 'key': 'ArrowLeft', 'code': 'Numpad4', 'shiftKey': '4', 'location': 3},\n    'Numpad8': {'keyCode': 38, 'shiftKeyCode': 104, 'key': 'ArrowUp', 'code': 'Numpad8', 'shiftKey': '8', 'location': 3},\n    'ArrowUp': {'keyCode': 38, 'code': 'ArrowUp', 'key': 'ArrowUp'},\n    'ArrowRight': {'keyCode': 39, 'code': 'ArrowRight', 'key': 'ArrowRight'},\n    'Numpad6': {'keyCode': 39, 'shiftKeyCode': 102, 'key': 'ArrowRight', 'code': 'Numpad6', 'shiftKey': '6', 'location': 3},\n    'Numpad2': {'keyCode': 40, 'shiftKeyCode': 98, 'key': 'ArrowDown', 'code': 'Numpad2', 'shiftKey': '2', 'location': 3},\n    'ArrowDown': {'keyCode': 40, 'code': 'ArrowDown', 'key': 'ArrowDown'},\n    'Select': {'keyCode': 41, 'code': 'Select', 'key': 'Select'},\n    'Open': {'keyCode': 43, 'code': 'Open', 'key': 'Execute'},\n    'PrintScreen': {'keyCode': 44, 'code': 'PrintScreen', 'key': 'PrintScreen'},\n    'Insert': {'keyCode': 45, 'code': 'Insert', 'key': 'Insert'},\n    'Numpad0': {'keyCode': 45, 'shiftKeyCode': 96, 'key': 'Insert', 'code': 'Numpad0', 'shiftKey': '0', 'location': 3},\n    'Delete': {'keyCode': 46, 'code': 'Delete', 'key': 'Delete'},\n    'NumpadDecimal': {'keyCode': 46, 'shiftKeyCode': 110, 'code': 'NumpadDecimal', 'key': '\\u0000', 'shiftKey': '.', 'location': 3},\n    'Digit0': {'keyCode': 48, 'code': 'Digit0', 'shiftKey': ')', 'key': '0'},\n    'Digit1': {'keyCode': 49, 'code': 'Digit1', 'shiftKey': '!', 'key': '1'},\n    'Digit2': {'keyCode': 50, 'code': 'Digit2', 'shiftKey': '@', 'key': '2'},\n    'Digit3': {'keyCode': 51, 'code': 'Digit3', 'shiftKey': '#', 'key': '3'},\n    'Digit4': {'keyCode': 52, 'code': 'Digit4', 'shiftKey': '$', 'key': '4'},\n    'Digit5': {'keyCode': 53, 'code': 'Digit5', 'shiftKey': '%', 'key': '5'},\n    'Digit6': {'keyCode': 54, 'code': 'Digit6', 'shiftKey': '^', 'key': '6'},\n    'Digit7': {'keyCode': 55, 'code': 'Digit7', 'shiftKey': '&', 'key': '7'},\n    'Digit8': {'keyCode': 56, 'code': 'Digit8', 'shiftKey': '*', 'key': '8'},\n    'Digit9': {'keyCode': 57, 'code': 'Digit9', 'shiftKey': '(', 'key': '9'},\n    'KeyA': {'keyCode': 65, 'code': 'KeyA', 'shiftKey': 'A', 'key': 'a'},\n    'KeyB': {'keyCode': 66, 'code': 'KeyB', 'shiftKey': 'B', 'key': 'b'},\n    'KeyC': {'keyCode': 67, 'code': 'KeyC', 'shiftKey': 'C', 'key': 'c'},\n    'KeyD': {'keyCode': 68, 'code': 'KeyD', 'shiftKey': 'D', 'key': 'd'},\n    'KeyE': {'keyCode': 69, 'code': 'KeyE', 'shiftKey': 'E', 'key': 'e'},\n    'KeyF': {'keyCode': 70, 'code': 'KeyF', 'shiftKey': 'F', 'key': 'f'},\n    'KeyG': {'keyCode': 71, 'code': 'KeyG', 'shiftKey': 'G', 'key': 'g'},\n    'KeyH': {'keyCode': 72, 'code': 'KeyH', 'shiftKey': 'H', 'key': 'h'},\n    'KeyI': {'keyCode': 73, 'code': 'KeyI', 'shiftKey': 'I', 'key': 'i'},\n    'KeyJ': {'keyCode': 74, 'code': 'KeyJ', 'shiftKey': 'J', 'key': 'j'},\n    'KeyK': {'keyCode': 75, 'code': 'KeyK', 'shiftKey': 'K', 'key': 'k'},\n    'KeyL': {'keyCode': 76, 'code': 'KeyL', 'shiftKey': 'L', 'key': 'l'},\n    'KeyM': {'keyCode': 77, 'code': 'KeyM', 'shiftKey': 'M', 'key': 'm'},\n    'KeyN': {'keyCode': 78, 'code': 'KeyN', 'shiftKey': 'N', 'key': 'n'},\n    'KeyO': {'keyCode': 79, 'code': 'KeyO', 'shiftKey': 'O', 'key': 'o'},\n    'KeyP': {'keyCode': 80, 'code': 'KeyP', 'shiftKey': 'P', 'key': 'p'},\n    'KeyQ': {'keyCode': 81, 'code': 'KeyQ', 'shiftKey': 'Q', 'key': 'q'},\n    'KeyR': {'keyCode': 82, 'code': 'KeyR', 'shiftKey': 'R', 'key': 'r'},\n    'KeyS': {'keyCode': 83, 'code': 'KeyS', 'shiftKey': 'S', 'key': 's'},\n    'KeyT': {'keyCode': 84, 'code': 'KeyT', 'shiftKey': 'T', 'key': 't'},\n    'KeyU': {'keyCode': 85, 'code': 'KeyU', 'shiftKey': 'U', 'key': 'u'},\n    'KeyV': {'keyCode': 86, 'code': 'KeyV', 'shiftKey': 'V', 'key': 'v'},\n    'KeyW': {'keyCode': 87, 'code': 'KeyW', 'shiftKey': 'W', 'key': 'w'},\n    'KeyX': {'keyCode': 88, 'code': 'KeyX', 'shiftKey': 'X', 'key': 'x'},\n    'KeyY': {'keyCode': 89, 'code': 'KeyY', 'shiftKey': 'Y', 'key': 'y'},\n    'KeyZ': {'keyCode': 90, 'code': 'KeyZ', 'shiftKey': 'Z', 'key': 'z'},\n    'MetaLeft': {'keyCode': 91, 'code': 'MetaLeft', 'key': 'Meta'},\n    'MetaRight': {'keyCode': 92, 'code': 'MetaRight', 'key': 'Meta'},\n    'ContextMenu': {'keyCode': 93, 'code': 'ContextMenu', 'key': 'ContextMenu'},\n    'NumpadMultiply': {'keyCode': 106, 'code': 'NumpadMultiply', 'key': '*', 'location': 3},\n    'NumpadAdd': {'keyCode': 107, 'code': 'NumpadAdd', 'key': '+', 'location': 3},\n    'NumpadSubtract': {'keyCode': 109, 'code': 'NumpadSubtract', 'key': '-', 'location': 3},\n    'NumpadDivide': {'keyCode': 111, 'code': 'NumpadDivide', 'key': '/', 'location': 3},\n    'F1': {'keyCode': 112, 'code': 'F1', 'key': 'F1'},\n    'F2': {'keyCode': 113, 'code': 'F2', 'key': 'F2'},\n    'F3': {'keyCode': 114, 'code': 'F3', 'key': 'F3'},\n    'F4': {'keyCode': 115, 'code': 'F4', 'key': 'F4'},\n    'F5': {'keyCode': 116, 'code': 'F5', 'key': 'F5'},\n    'F6': {'keyCode': 117, 'code': 'F6', 'key': 'F6'},\n    'F7': {'keyCode': 118, 'code': 'F7', 'key': 'F7'},\n    'F8': {'keyCode': 119, 'code': 'F8', 'key': 'F8'},\n    'F9': {'keyCode': 120, 'code': 'F9', 'key': 'F9'},\n    'F10': {'keyCode': 121, 'code': 'F10', 'key': 'F10'},\n    'F11': {'keyCode': 122, 'code': 'F11', 'key': 'F11'},\n    'F12': {'keyCode': 123, 'code': 'F12', 'key': 'F12'},\n    'F13': {'keyCode': 124, 'code': 'F13', 'key': 'F13'},\n    'F14': {'keyCode': 125, 'code': 'F14', 'key': 'F14'},\n    'F15': {'keyCode': 126, 'code': 'F15', 'key': 'F15'},\n    'F16': {'keyCode': 127, 'code': 'F16', 'key': 'F16'},\n    'F17': {'keyCode': 128, 'code': 'F17', 'key': 'F17'},\n    'F18': {'keyCode': 129, 'code': 'F18', 'key': 'F18'},\n    'F19': {'keyCode': 130, 'code': 'F19', 'key': 'F19'},\n    'F20': {'keyCode': 131, 'code': 'F20', 'key': 'F20'},\n    'F21': {'keyCode': 132, 'code': 'F21', 'key': 'F21'},\n    'F22': {'keyCode': 133, 'code': 'F22', 'key': 'F22'},\n    'F23': {'keyCode': 134, 'code': 'F23', 'key': 'F23'},\n    'F24': {'keyCode': 135, 'code': 'F24', 'key': 'F24'},\n    'NumLock': {'keyCode': 144, 'code': 'NumLock', 'key': 'NumLock'},\n    'ScrollLock': {'keyCode': 145, 'code': 'ScrollLock', 'key': 'ScrollLock'},\n    'AudioVolumeMute': {'keyCode': 173, 'code': 'AudioVolumeMute', 'key': 'AudioVolumeMute'},\n    'AudioVolumeDown': {'keyCode': 174, 'code': 'AudioVolumeDown', 'key': 'AudioVolumeDown'},\n    'AudioVolumeUp': {'keyCode': 175, 'code': 'AudioVolumeUp', 'key': 'AudioVolumeUp'},\n    'MediaTrackNext': {'keyCode': 176, 'code': 'MediaTrackNext', 'key': 'MediaTrackNext'},\n    'MediaTrackPrevious': {'keyCode': 177, 'code': 'MediaTrackPrevious', 'key': 'MediaTrackPrevious'},\n    'MediaStop': {'keyCode': 178, 'code': 'MediaStop', 'key': 'MediaStop'},\n    'MediaPlayPause': {'keyCode': 179, 'code': 'MediaPlayPause', 'key': 'MediaPlayPause'},\n    'Semicolon': {'keyCode': 186, 'code': 'Semicolon', 'shiftKey': ':', 'key': ';'},\n    'Equal': {'keyCode': 187, 'code': 'Equal', 'shiftKey': '+', 'key': '='},\n    'NumpadEqual': {'keyCode': 187, 'code': 'NumpadEqual', 'key': '=', 'location': 3},\n    'Comma': {'keyCode': 188, 'code': 'Comma', 'shiftKey': '<', 'key': ','},\n    'Minus': {'keyCode': 189, 'code': 'Minus', 'shiftKey': '_', 'key': '-'},\n    'Period': {'keyCode': 190, 'code': 'Period', 'shiftKey': '>', 'key': '.'},\n    'Slash': {'keyCode': 191, 'code': 'Slash', 'shiftKey': '?', 'key': '/'},\n    'Backquote': {'keyCode': 192, 'code': 'Backquote', 'shiftKey': '~', 'key': '`'},\n    'BracketLeft': {'keyCode': 219, 'code': 'BracketLeft', 'shiftKey': '{', 'key': '['},\n    'Backslash': {'keyCode': 220, 'code': 'Backslash', 'shiftKey': '|', 'key': '\\\\'},\n    'BracketRight': {'keyCode': 221, 'code': 'BracketRight', 'shiftKey': '}', 'key': ']'},\n    'Quote': {'keyCode': 222, 'code': 'Quote', 'shiftKey': '\"', 'key': '\\''},\n    'AltGraph': {'keyCode': 225, 'code': 'AltGraph', 'key': 'AltGraph'},\n    'Props': {'keyCode': 247, 'code': 'Props', 'key': 'CrSel'},\n    'Cancel': {'keyCode': 3, 'key': 'Cancel', 'code': 'Abort'},\n    'Clear': {'keyCode': 12, 'key': 'Clear', 'code': 'Numpad5', 'location': 3},\n    'Shift': {'keyCode': 16, 'key': 'Shift', 'code': 'ShiftLeft'},\n    'Control': {'keyCode': 17, 'key': 'Control', 'code': 'ControlLeft'},\n    'Alt': {'keyCode': 18, 'key': 'Alt', 'code': 'AltLeft'},\n    'Accept': {'keyCode': 30, 'key': 'Accept'},\n    'ModeChange': {'keyCode': 31, 'key': 'ModeChange'},\n    ' ': {'keyCode': 32, 'key': ' ', 'code': 'Space'},\n    'Print': {'keyCode': 42, 'key': 'Print'},\n    'Execute': {'keyCode': 43, 'key': 'Execute', 'code': 'Open'},\n    '\\u0000': {'keyCode': 46, 'key': '\\u0000', 'code': 'NumpadDecimal', 'location': 3},\n    'a': {'keyCode': 65, 'key': 'a', 'code': 'KeyA'},\n    'b': {'keyCode': 66, 'key': 'b', 'code': 'KeyB'},\n    'c': {'keyCode': 67, 'key': 'c', 'code': 'KeyC'},\n    'd': {'keyCode': 68, 'key': 'd', 'code': 'KeyD'},\n    'e': {'keyCode': 69, 'key': 'e', 'code': 'KeyE'},\n    'f': {'keyCode': 70, 'key': 'f', 'code': 'KeyF'},\n    'g': {'keyCode': 71, 'key': 'g', 'code': 'KeyG'},\n    'h': {'keyCode': 72, 'key': 'h', 'code': 'KeyH'},\n    'i': {'keyCode': 73, 'key': 'i', 'code': 'KeyI'},\n    'j': {'keyCode': 74, 'key': 'j', 'code': 'KeyJ'},\n    'k': {'keyCode': 75, 'key': 'k', 'code': 'KeyK'},\n    'l': {'keyCode': 76, 'key': 'l', 'code': 'KeyL'},\n    'm': {'keyCode': 77, 'key': 'm', 'code': 'KeyM'},\n    'n': {'keyCode': 78, 'key': 'n', 'code': 'KeyN'},\n    'o': {'keyCode': 79, 'key': 'o', 'code': 'KeyO'},\n    'p': {'keyCode': 80, 'key': 'p', 'code': 'KeyP'},\n    'q': {'keyCode': 81, 'key': 'q', 'code': 'KeyQ'},\n    'r': {'keyCode': 82, 'key': 'r', 'code': 'KeyR'},\n    's': {'keyCode': 83, 'key': 's', 'code': 'KeyS'},\n    't': {'keyCode': 84, 'key': 't', 'code': 'KeyT'},\n    'u': {'keyCode': 85, 'key': 'u', 'code': 'KeyU'},\n    'v': {'keyCode': 86, 'key': 'v', 'code': 'KeyV'},\n    'w': {'keyCode': 87, 'key': 'w', 'code': 'KeyW'},\n    'x': {'keyCode': 88, 'key': 'x', 'code': 'KeyX'},\n    'y': {'keyCode': 89, 'key': 'y', 'code': 'KeyY'},\n    'z': {'keyCode': 90, 'key': 'z', 'code': 'KeyZ'},\n    'Meta': {'keyCode': 91, 'key': 'Meta', 'code': 'MetaLeft'},\n    '*': {'keyCode': 106, 'key': '*', 'code': 'NumpadMultiply', 'location': 3},\n    '+': {'keyCode': 107, 'key': '+', 'code': 'NumpadAdd', 'location': 3},\n    '-': {'keyCode': 109, 'key': '-', 'code': 'NumpadSubtract', 'location': 3},\n    '/': {'keyCode': 111, 'key': '/', 'code': 'NumpadDivide', 'location': 3},\n    ';': {'keyCode': 186, 'key': ';', 'code': 'Semicolon'},\n    '=': {'keyCode': 187, 'key': '=', 'code': 'Equal'},\n    ',': {'keyCode': 188, 'key': ',', 'code': 'Comma'},\n    '.': {'keyCode': 190, 'key': '.', 'code': 'Period'},\n    '`': {'keyCode': 192, 'key': '`', 'code': 'Backquote'},\n    '[': {'keyCode': 219, 'key': '[', 'code': 'BracketLeft'},\n    '\\\\': {'keyCode': 220, 'key': '\\\\', 'code': 'Backslash'},\n    ']': {'keyCode': 221, 'key': ']', 'code': 'BracketRight'},\n    '\\'': {'keyCode': 222, 'key': '\\'', 'code': 'Quote'},\n    'Attn': {'keyCode': 246, 'key': 'Attn'},\n    'CrSel': {'keyCode': 247, 'key': 'CrSel', 'code': 'Props'},\n    'ExSel': {'keyCode': 248, 'key': 'ExSel'},\n    'EraseEof': {'keyCode': 249, 'key': 'EraseEof'},\n    'Play': {'keyCode': 250, 'key': 'Play'},\n    'ZoomOut': {'keyCode': 251, 'key': 'ZoomOut'},\n    ')': {'keyCode': 48, 'key': ')', 'code': 'Digit0'},\n    '!': {'keyCode': 49, 'key': '!', 'code': 'Digit1'},\n    '@': {'keyCode': 50, 'key': '@', 'code': 'Digit2'},\n    '#': {'keyCode': 51, 'key': '#', 'code': 'Digit3'},\n    '$': {'keyCode': 52, 'key': '$', 'code': 'Digit4'},\n    '%': {'keyCode': 53, 'key': '%', 'code': 'Digit5'},\n    '^': {'keyCode': 54, 'key': '^', 'code': 'Digit6'},\n    '&': {'keyCode': 55, 'key': '&', 'code': 'Digit7'},\n    '(': {'keyCode': 57, 'key': '(', 'code': 'Digit9'},\n    'A': {'keyCode': 65, 'key': 'A', 'code': 'KeyA'},\n    'B': {'keyCode': 66, 'key': 'B', 'code': 'KeyB'},\n    'C': {'keyCode': 67, 'key': 'C', 'code': 'KeyC'},\n    'D': {'keyCode': 68, 'key': 'D', 'code': 'KeyD'},\n    'E': {'keyCode': 69, 'key': 'E', 'code': 'KeyE'},\n    'F': {'keyCode': 70, 'key': 'F', 'code': 'KeyF'},\n    'G': {'keyCode': 71, 'key': 'G', 'code': 'KeyG'},\n    'H': {'keyCode': 72, 'key': 'H', 'code': 'KeyH'},\n    'I': {'keyCode': 73, 'key': 'I', 'code': 'KeyI'},\n    'J': {'keyCode': 74, 'key': 'J', 'code': 'KeyJ'},\n    'K': {'keyCode': 75, 'key': 'K', 'code': 'KeyK'},\n    'L': {'keyCode': 76, 'key': 'L', 'code': 'KeyL'},\n    'M': {'keyCode': 77, 'key': 'M', 'code': 'KeyM'},\n    'N': {'keyCode': 78, 'key': 'N', 'code': 'KeyN'},\n    'O': {'keyCode': 79, 'key': 'O', 'code': 'KeyO'},\n    'P': {'keyCode': 80, 'key': 'P', 'code': 'KeyP'},\n    'Q': {'keyCode': 81, 'key': 'Q', 'code': 'KeyQ'},\n    'R': {'keyCode': 82, 'key': 'R', 'code': 'KeyR'},\n    'S': {'keyCode': 83, 'key': 'S', 'code': 'KeyS'},\n    'T': {'keyCode': 84, 'key': 'T', 'code': 'KeyT'},\n    'U': {'keyCode': 85, 'key': 'U', 'code': 'KeyU'},\n    'V': {'keyCode': 86, 'key': 'V', 'code': 'KeyV'},\n    'W': {'keyCode': 87, 'key': 'W', 'code': 'KeyW'},\n    'X': {'keyCode': 88, 'key': 'X', 'code': 'KeyX'},\n    'Y': {'keyCode': 89, 'key': 'Y', 'code': 'KeyY'},\n    'Z': {'keyCode': 90, 'key': 'Z', 'code': 'KeyZ'},\n    ':': {'keyCode': 186, 'key': ':', 'code': 'Semicolon'},\n    '<': {'keyCode': 188, 'key': '<', 'code': 'Comma'},\n    '_': {'keyCode': 189, 'key': '_', 'code': 'Minus'},\n    '>': {'keyCode': 190, 'key': '>', 'code': 'Period'},\n    '?': {'keyCode': 191, 'key': '?', 'code': 'Slash'},\n    '~': {'keyCode': 192, 'key': '~', 'code': 'Backquote'},\n    '{': {'keyCode': 219, 'key': '{', 'code': 'BracketLeft'},\n    '|': {'keyCode': 220, 'key': '|', 'code': 'Backslash'},\n    '}': {'keyCode': 221, 'key': '}', 'code': 'BracketRight'},\n    '\"': {'keyCode': 222, 'key': '\"', 'code': 'Quote'},\n}\n"
  },
  {
    "path": "pyppeteer/util.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"Utility functions.\"\"\"\n\nimport gc\nimport socket\nfrom typing import Dict, Optional\n\nfrom pyppeteer.chromium_downloader import check_chromium, chromium_executable\nfrom pyppeteer.chromium_downloader import download_chromium\n\n__all__ = [\n    'check_chromium',\n    'chromium_executable',\n    'download_chromium',\n    'get_free_port',\n    'merge_dict',\n]\n\n\ndef get_free_port() -> int:\n    \"\"\"Get free port.\"\"\"\n    sock = socket.socket()\n    sock.bind(('localhost', 0))\n    port = sock.getsockname()[1]\n    sock.close()\n    del sock\n    gc.collect()\n    return port\n\n\ndef merge_dict(dict1: Optional[Dict], dict2: Optional[Dict]) -> Dict:\n    \"\"\"Merge two dictionaries into new one.\"\"\"\n    new_dict = {}\n    if dict1:\n        new_dict.update(dict1)\n    if dict2:\n        new_dict.update(dict2)\n    return new_dict\n"
  },
  {
    "path": "pyppeteer/worker.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"Worker module.\"\"\"\n\nimport logging\nfrom typing import Any, Callable, Dict, List, TYPE_CHECKING\n\nfrom pyee import EventEmitter\n\nfrom pyppeteer.execution_context import ExecutionContext, JSHandle\nfrom pyppeteer.helper import debugError\n\nif TYPE_CHECKING:\n    from pyppeteer.connection import CDPSession  # noqa: F401\n\nlogger = logging.getLogger(__name__)\n\n\nclass Worker(EventEmitter):\n    \"\"\"The Worker class represents a WebWorker.\n\n    The events `workercreated` and `workerdestroyed` are emitted on the page\n    object to signal the worker lifecycle.\n\n    .. code::\n\n        page.on('workercreated', lambda worker: print('Worker created:', worker.url))\n    \"\"\"  # noqa: E501\n\n    def __init__(self, client: 'CDPSession', url: str,  # noqa: C901\n                 consoleAPICalled: Callable[[str, List[JSHandle]], None],\n                 exceptionThrown: Callable[[Dict], None]\n                 ) -> None:\n        super().__init__()\n        self._client = client\n        self._url = url\n        self._loop = client._loop\n        self._executionContextPromise = self._loop.create_future()\n\n        def jsHandleFactory(remoteObject: Dict) -> JSHandle:\n            return None  # type: ignore\n\n        def onExecutionContentCreated(event: Dict) -> None:\n            nonlocal jsHandleFactory\n\n            def jsHandleFactory(remoteObject: Dict) -> JSHandle:\n                return JSHandle(executionContext, client, remoteObject)\n\n            executionContext = ExecutionContext(\n                client, event['context'], jsHandleFactory)\n            self._executionContextCallback(executionContext)\n\n        self._client.on('Runtime.executionContextCreated',\n                        onExecutionContentCreated)\n        try:\n            # This might fail if the target is closed before we receive all\n            # execution contexts.\n            self._client.send('Runtime.enable', {})\n        except Exception as e:\n            debugError(logger, e)\n\n        def onConsoleAPICalled(event: Dict) -> None:\n            args: List[JSHandle] = []\n            for arg in event.get('args', []):\n                args.append(jsHandleFactory(arg))\n            consoleAPICalled(event['type'], args)\n\n        self._client.on('Runtime.consoleAPICalled', onConsoleAPICalled)\n        self._client.on(\n            'Runtime.exceptionThrown',\n            lambda exception: exceptionThrown(exception['exceptionDetails']),\n        )\n\n    def _executionContextCallback(self, value: ExecutionContext) -> None:\n        self._executionContextPromise.set_result(value)\n\n    @property\n    def url(self) -> str:\n        \"\"\"Return URL.\"\"\"\n        return self._url\n\n    async def executionContext(self) -> ExecutionContext:\n        \"\"\"Return ExecutionContext.\"\"\"\n        return await self._executionContextPromise\n\n    async def evaluate(self, pageFunction: str, *args: Any) -> Any:\n        \"\"\"Evaluate ``pageFunction`` with ``args``.\n\n        Shortcut for ``(await worker.executionContext).evaluate(pageFunction, *args)``.\n        \"\"\"  # noqa: E501\n        return await (await self._executionContextPromise).evaluate(\n            pageFunction, *args)\n\n    async def evaluateHandle(self, pageFunction: str, *args: Any) -> JSHandle:\n        \"\"\"Evaluate ``pageFunction`` with ``args`` and return :class:`~pyppeteer.execution_context.JSHandle`.\n\n        Shortcut for ``(await worker.executionContext).evaluateHandle(pageFunction, *args)``.\n        \"\"\"  # noqa: E501\n        return await (await self._executionContextPromise).evaluateHandle(\n            pageFunction, *args)\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[tool.poetry]\nname = \"pyppeteer\"\nversion = \"2.0.0\"\ndescription = \"Headless chrome/chromium automation library (unofficial port of puppeteer)\"\nreadme = 'README.md'\nlicense = \"MIT\"\nhomepage = \"https://github.com/pyppeteer/pyppeteer\"\nrepository = \"https://github.com/pyppeteer/pyppeteer\"\nkeywords=['pyppeteer', 'puppeteer', 'chrome', 'chromium']\nauthors = [\n    \"granitosaurus <bernardas.alisauskas@pm.me>\",\n    \"Hiroyuki Takagi <miyako.dev@gmail.com>\",\n    \"Mattwmaster58 <mattwmaster58@gmail.com>\",\n    \"pyppeteer <pyppeteer@protonmail.com>\",\n]\nclassifiers = [\n        'Development Status :: 3 - Alpha',\n        'Intended Audience :: Developers',\n        'License :: OSI Approved :: MIT License',\n        'Natural Language :: English',\n        'Programming Language :: Python :: 3',\n        'Programming Language :: Python :: 3.7',\n        'Programming Language :: Python :: 3.8',\n        'Programming Language :: Python :: 3.9',\n        'Programming Language :: Python :: 3.10',\n    ]\npackages = [\n    { include = \"pyppeteer\" },\n]\ninclude = [\n    \"README.md\",\n    \"LICENSE\",\n    \"CHANGES.md\",\n]\nexclude = [\n    '*/__pycache__',\n    '*/*.py[co]',\n]\n\n[tool.poetry.urls]\n\"Bug Tracker\" = \"https://github.com/pyppeteer/pyppeteer/issues\"\n\n[tool.poetry.scripts]\npyppeteer-install = 'pyppeteer.command:install'\n\n[tool.poetry.dependencies]\npython = \"^3.8\"\nappdirs = \"^1.4.3\"\nimportlib-metadata = \">=1.4\"\npyee = \"^11.0.0\"\ntqdm = \"^4.42.1\"\nurllib3 = \"^1.25.8\"\nwebsockets = \"^10.0\"\ncertifi = \">=2023\"\n\n[tool.poetry.dev-dependencies]\ntox = \"^3.20.1\"\nsyncer = \"^1.3.0\"\nlivereload = \"^2.6.1\"\nflake8 = \"^3.7.9\"\nm2r = \"^0.2.1\"\nmypy = \"^0.770\"\npre-commit = \"^2.2.0\"\npydocstyle = \"^5.0.2\"\npylint = \"^2.4.4\"\npytest = \"^5.3.5\"\npytest-cov = \"^2.8.1\"\npytest-xdist = \"^1.31.0\"\nreadme_renderer = \"^24.0\"\nsphinx = \"^2.4.0\"\nsphinxcontrib-asyncio = \"^0.2.0\"\n\n\n[build-system]\nrequires = [\"poetry-core\"]\nbuild-backend = \"poetry.core.masonry.api\"\n\n[tool.black]\nline-length = 120\ntarget-version = ['py38']\nskip-string-normalization = true\n\n[tool.isort]\nline_length = 120\nmulti_line_output = 3\ninclude_trailing_comma = true\nforce_grid_wrap = 0\nuse_parentheses = true\nknown_third_party = [\"appdirs\", \"certifi\", \"livereload\", \"pyee\", \"pyppeteer\", \"syncer\", \"tornado\", \"tqdm\", \"urllib3\", \"websockets\"]\n"
  },
  {
    "path": "spell.txt",
    "content": "abstracteventloop\naccessdenied\nack\naddressunreachable\napi\narg\nargs\narrowleft\nasync\nasyncio\nauth\nawaitable\nbeforeunload\nblockedbyclient\nblockedbyresponse\nbool\nconnectionaborted\nconnectionclosed\nconnectionfailed\nconnectionreset\nconnectionrefused\ncreateincognitobrowsercontext\ncsp\ncss\nctrl\ncustomargs\ndefaultargs\ndefaultdict\ndevtool\ndevtools\ndialog's\ndict\ndoctype\ndomcontentloaded\ndpr\ndumpio\nelementhandle\nemulatemedia\nendswith\nenv\neval\nevaluatehandle\neventsource\nexecutioncontext\ngoogle\ngoto\nhtml\nhttp\nhttps\niframe\nignoredefaultargs\ninnertext\ninternetdisconnected\nip\nisinstance\njpeg\njpg\njs\njson\njsonized\nkeya\nkeydown\nkeypress\nkeyup\nkillchrome\nkwargs\nlen\nlifecycle\nmousedown\nmousemove\nmouseup\nmsec\nmultimap\nnamenotresolved\nnewpage\nnoqa\noffline\npagefunction\nparams\npdf\npng\npopup\npx\npyee\npyppeteer\npyppeteer's\nqueryselector\nqueryselectorall\nqueryselectoralleval\nqueryselectoreval\nraf\nrecalc\nreq\nrequestfailed\nrequestfinished\nrequest's\nrst\nscreenshot\nscreenshots\nscrollable\nsetbypasscsp\nsetextrahttpheaders\nsetrequestinterception\nssl\nstartcsscoverage\nstartjscoverage\nstderr\nstdout\nstopcsscoverage\nstopjscoverage\nstr\nstylesheet\ntcp\ntexttrack\ntimedout\ntimeline\ntimestamp\ntouchend\ntouchstart\ntruthy\nurl\nusedbytes\nusername\nviewport\nwaitfornavigation\nwaitforrequest\nwaitforresponse\nwaitoptions\nwebkit\nwebpage\nwebsocket\nworkercreated\nworkerdestroyed\nwww\nxhr\nxpath\nzipfile\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/base.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport unittest\n\nfrom syncer import sync\n\nfrom pyppeteer import launch\nfrom pyppeteer.util import get_free_port\n\nfrom .server import get_application\n\nDEFAULT_OPTIONS = {'args': ['--no-sandbox']}\n\n\nclass BaseTestCase(unittest.TestCase):\n    @classmethod\n    def setUpClass(cls):\n        cls.port = get_free_port()\n        cls.app = get_application()\n        cls.server = cls.app.listen(cls.port)\n        cls.browser = sync(launch(DEFAULT_OPTIONS))\n        cls.url = 'http://localhost:{}/'.format(cls.port)\n\n    @classmethod\n    def tearDownClass(cls):\n        sync(cls.browser.close())\n        cls.server.stop()\n\n    def setUp(self):\n        self.context = sync(self.browser.createIncognitoBrowserContext())\n        self.page = sync(self.context.newPage())\n        self.result = False\n\n    def tearDown(self):\n        sync(self.context.close())\n        self.context = None\n        self.page = None\n\n    def set_result(self, value):\n        self.result = value\n"
  },
  {
    "path": "tests/closeme.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport asyncio\n\nfrom pyppeteer import launch\n\n\nasync def main() -> None:\n    browser = await launch(args=['--no-sandbox'])\n    print(browser.wsEndpoint, flush=True)\n\n\nasyncio.get_event_loop().run_until_complete(main())\n"
  },
  {
    "path": "tests/dumpio.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport asyncio\nimport sys\n\nfrom pyppeteer import launch\n\ndumpio = '--dumpio' in sys.argv\n\n\nasync def main():\n    browser = await launch(args=['--no-sandbox'], dumpio=dumpio)\n    page = await browser.newPage()\n    await page.evaluate('console.log(\"DUMPIO_TEST\")')\n    await page.close()\n    await browser.close()\n\n\nasyncio.get_event_loop().run_until_complete(main())\n"
  },
  {
    "path": "tests/file-to-upload.txt",
    "content": "contents of the file\n"
  },
  {
    "path": "tests/frame_utils.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nfrom pyppeteer.frame_manager import Frame\nfrom pyppeteer.page import Page\n\n\nasync def attachFrame(page: Page, frameId: str, url: str) -> None:\n    func = '''\n        (frameId, url) => {\n            const frame = document.createElement('iframe');\n            frame.src = url;\n            frame.id = frameId;\n            document.body.appendChild(frame);\n            return new Promise(x => frame.onload = x);\n        }\n    '''\n    await page.evaluate(func, frameId, url)\n\n\nasync def detachFrame(page: Page, frameId: str) -> None:\n    func = '''\n        (frameId) => {\n            const frame = document.getElementById(frameId);\n            frame.remove();\n        }\n    '''\n    await page.evaluate(func, frameId)\n\n\nasync def navigateFrame(page: Page, frameId: str, url: str) -> None:\n    func = '''\n        (frameId, url) => {\n            const frame = document.getElementById(frameId);\n            frame.src = url;\n            return new Promise(x => frame.onload = x);\n        }\n    '''\n    await page.evaluate(func, frameId, url)\n\n\ndef dumpFrames(frame: Frame, indentation: str = '') -> str:\n    results = []\n    results.append(indentation + frame.url)\n    for child in frame.childFrames:\n        results.append(dumpFrames(child, '    ' + indentation))\n    return '\\n'.join(results)\n"
  },
  {
    "path": "tests/server.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport asyncio\nimport base64\nimport functools\nimport os\nfrom typing import Any, Callable\n\nfrom tornado import web\nfrom tornado.log import access_log\n\n\nBASE_HTML = '''\n<html>\n<head><title>main</title></head>\n<body>\n<h1 id=\"hello\">Hello</h1>\n<a id=\"link1\" href=\"./1\">link1</a>\n<a id=\"link2\" href=\"./2\">link2</a>\n</body>\n</html>\n'''\n\n\nclass BaseHandler(web.RequestHandler):\n    def get(self) -> None:\n        self.set_header(\n            'Cache-Control',\n            'no-store, no-cache, must-revalidate, max-age=0',\n        )\n\n\nclass MainHandler(BaseHandler):\n    def get(self) -> None:\n        super().get()\n        self.write(BASE_HTML)\n\n\nclass EmptyHandler(BaseHandler):\n    def get(self) -> None:\n        super().get()\n        self.write('')\n\n\nclass LongHandler(BaseHandler):\n    async def get(self) -> None:\n        super().get()\n        await asyncio.sleep(0.1)\n        self.write('')\n\n\nclass LinkHandler1(BaseHandler):\n    def get(self) -> None:\n        super().get()\n        self.set_status(200)\n        self.write('''\n<head><title>link1</title></head>\n<h1 id=\"link1\">Link1</h1>\n<a id=\"back1\" href=\"./\">back1</a>\n        ''')\n\n\nclass RedirectHandler1(BaseHandler):\n    def get(self) -> None:\n        super().get()\n        self.redirect('/redirect2')\n\n\nclass RedirectHandler2(BaseHandler):\n    def get(self) -> None:\n        super().get()\n        self.write('<h1 id=\"red2\">redirect2</h1>')\n\n\nclass RedirectHandler3(BaseHandler):\n    def get(self) -> None:\n        super().get()\n        self.redirect('/static/one-frame.html')\n\n\nclass ResourceRedirectHandler(BaseHandler):\n    def get(self) -> None:\n        super().get()\n        self.write(\n            '<link rel=\"stylesheet\" href=\"/one-style.css\">'\n            '<div>hello, world!</div>'\n        )\n\n\nclass CSSRedirectHandler1(BaseHandler):\n    def get(self) -> None:\n        super().get()\n        self.redirect('/two-style.css')\n\n\nclass CSSRedirectHandler2(BaseHandler):\n    def get(self) -> None:\n        super().get()\n        self.redirect('/three-style.css')\n\n\nclass CSSRedirectHandler3(BaseHandler):\n    def get(self) -> None:\n        super().get()\n        self.redirect('/four-style.css')\n\n\nclass CSSRedirectHandler4(BaseHandler):\n    def get(self) -> None:\n        super().get()\n        self.write('body {box-sizing: border-box;}')\n\n\nclass CSPHandler(BaseHandler):\n    def get(self) -> None:\n        super().get()\n        self.set_header('Content-Security-Policy', 'script-src \\'self\\'')\n        self.write('')\n\n\ndef auth_api(username: str, password: str) -> bool:\n    if username == 'user' and password == 'pass':\n        return True\n    else:\n        return False\n\n\ndef basic_auth(auth: Callable[[str, str], bool]) -> Callable:\n    def wrapper(f: Callable) -> Callable:\n        def _request_auth(handler: Any) -> None:\n            handler.set_header('WWW-Authenticate', 'Basic realm=JSL')\n            handler.set_status(401)\n            handler.finish()\n\n        @functools.wraps(f)\n        def new_f(*args: Any) -> None:\n            handler = args[0]\n\n            auth_header = handler.request.headers.get('Authorization')\n            if auth_header is None:\n                return _request_auth(handler)\n            if not auth_header.startswith('Basic '):\n                return _request_auth(handler)\n\n            auth_decoded = base64.b64decode(auth_header[6:])\n            username, password = auth_decoded.decode('utf-8').split(':', 2)\n\n            if auth(username, password):\n                f(*args)\n            else:\n                _request_auth(handler)\n\n        return new_f\n    return wrapper\n\n\nclass AuthHandler(BaseHandler):\n    @basic_auth(auth_api)\n    def get(self) -> None:\n        super().get()\n        self.write('ok')\n\n\ndef log_handler(handler: Any) -> None:\n    \"\"\"Override tornado's logging.\"\"\"\n    # log only errors (status >= 500)\n    if handler.get_status() >= 500:\n        access_log.error(\n            '{} {}'.format(handler.get_status(), handler._request_summary())\n        )\n\n\ndef get_application() -> web.Application:\n    static_path = os.path.join(os.path.dirname(__file__), 'static')\n    handlers = [\n        ('/', MainHandler),\n        ('/1', LinkHandler1),\n        ('/redirect1', RedirectHandler1),\n        ('/redirect2', RedirectHandler2),\n        ('/redirect3', RedirectHandler3),\n        ('/one-style.html', ResourceRedirectHandler),\n        ('/one-style.css', CSSRedirectHandler1),\n        ('/two-style.css', CSSRedirectHandler2),\n        ('/three-style.css', CSSRedirectHandler3),\n        ('/four-style.css', CSSRedirectHandler4),\n        ('/auth', AuthHandler),\n        ('/empty', EmptyHandler),\n        ('/long', LongHandler),\n        ('/csp', CSPHandler),\n        ('/static', web.StaticFileHandler, dict(path=static_path)),\n    ]\n    return web.Application(\n        handlers,\n        log_function=log_handler,\n        static_path=static_path,\n    )\n\n\nif __name__ == '__main__':\n    app = get_application()\n    app.listen(9000)\n    print('server running on http://localhost:9000')\n    asyncio.get_event_loop().run_forever()\n"
  },
  {
    "path": "tests/static/beforeunload.html",
    "content": "<script>\nwindow.addEventListener('beforeunload', event => {\n  event.returnValue = 'Leave?';\n});\n</script>\n"
  },
  {
    "path": "tests/static/button.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Button test</title>\n  </head>\n  <body>\n    <script src=\"mouse-helper.js\"></script>\n    <button onclick=\"clicked();\">Click target</button>\n    <script>\n      window.result = 'Was not clicked';\n      function clicked() {\n        result = 'Clicked';\n      }\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "tests/static/cached/one-style.css",
    "content": "body {\n  background-color: pink;\n}\n"
  },
  {
    "path": "tests/static/cached/one-style.html",
    "content": "<link rel='stylesheet' href='./one-style.css'>\n<div>hello, world</div>\n"
  },
  {
    "path": "tests/static/checkbox.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Selection Test</title>\n  </head>\n  <body>\n    <label for=\"agree\">Remember Me</label>\n    <input id=\"agree\" type=\"checkbox\">\n    <script>\n      window.result = {\n        check: null,\n        events: [],\n      };\n\n      let checkbox = document.querySelector('input');\n\n      const events = [\n        'change',\n        'click',\n        'dblclick',\n        'input',\n        'mousedown',\n        'mouseenter',\n        'mouseleave',\n        'mousemove',\n        'mouseout',\n        'mouseover',\n        'mouseup',\n      ];\n\n      for (let event of events) {\n        checkbox.addEventListener(event, () => {\n          if (['change', 'click', 'dblclick', 'input'].includes(event) === true) {\n            result.check = checkbox.checked;\n          }\n\n          result.events.push(event);\n        }, false);\n      }\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "tests/static/csp.html",
    "content": "<meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'self'\">\n"
  },
  {
    "path": "tests/static/csscoverage/involved.html",
    "content": "<style>\n@charset \"utf-8\";\n\n#flutty {\n  border: 1px solid black;\n  z-index: 1;\n  /* -webkit-disabled-property: rgb(1, 2, 3) */\n  -lol-cats: \"dogs\" /* non-existing property */\n}\n\n@media (min-width: 1px) {\n  span {\n    -webkit-border-radius: 10px;\n    font-family: \"Example Font\";\n    animation: 1s identifier;\n  }\n}\n</style>\n<div id=\"flutty\">woof!</div>\n<span>fancy text</span>\n"
  },
  {
    "path": "tests/static/csscoverage/media.html",
    "content": "<style>\n@media screen { div { color: green; } } </style>\n<div>hello, world</div>\n\n"
  },
  {
    "path": "tests/static/csscoverage/multiple.html",
    "content": "<link rel=\"stylesheet\" href=\"stylesheet1.css\">\n<link rel=\"stylesheet\" href=\"stylesheet2.css\">\n<script>\nwindow.addEventListener('DOMContentLoaded', () => {\n  // Force stylesheets to load\n  console.log(window.getComputedStyle(document.body).color);\n}, false);\n</script>\n"
  },
  {
    "path": "tests/static/csscoverage/simple.html",
    "content": "<style>\ndiv { color: green; }\na { color: blue; }\n</style>\n<div>hello, world</div>\n"
  },
  {
    "path": "tests/static/csscoverage/sourceurl.html",
    "content": "<style>\nbody {\n  padding: 10px;\n}\n/*# sourceURL=nicename.css */\n</style>\n\n"
  },
  {
    "path": "tests/static/csscoverage/stylesheet1.css",
    "content": "body {\n  color: red;\n}\n"
  },
  {
    "path": "tests/static/csscoverage/stylesheet2.css",
    "content": "html {\n  margin: 0;\n  padding: 0;\n}\n"
  },
  {
    "path": "tests/static/csscoverage/unused.html",
    "content": "<style>\n@media screen {\n  a { color: green; }\n}\n/*# sourceURL=unused.css */\n</style>\n"
  },
  {
    "path": "tests/static/detect-touch.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Detect Touch Test</title>\n    <script src=\"./modernizr.js\"></script>\n  </head>\n  <body style=\"font-size:30vmin\">\n    <script>\n      document.body.textContent = Modernizr.touchevents ? 'YES' : 'NO';\n    </script>\n  </body>\n</html>\n\n"
  },
  {
    "path": "tests/static/error.html",
    "content": "<script>\na();\n\nfunction a() {\n    b();\n}\n\nfunction b() {\n    c();\n}\n\nfunction c() {\n    throw new Error('Fancy error!');\n}\n</script>\n"
  },
  {
    "path": "tests/static/es6/es6import.js",
    "content": "import num from './es6module.js';\nwindow.__es6injected = num;\n"
  },
  {
    "path": "tests/static/es6/es6module.js",
    "content": "export default 42;\n"
  },
  {
    "path": "tests/static/es6/es6pathimport.js",
    "content": "import num from '/static/es6/es6module.js';\nwindow.__es6injected = num;\n"
  },
  {
    "path": "tests/static/fileupload.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>File upload test</title>\n  </head>\n  <body>\n      <input type=\"file\">\n  </body>\n</html>\n"
  },
  {
    "path": "tests/static/frame-204.html",
    "content": "<iframe src=\"http://httpstat.us/204\"></iframe>\n"
  },
  {
    "path": "tests/static/frame.html",
    "content": "<link rel='stylesheet' href='./style.css'>\n<script src='./script.js' type='text/javascript'></script>\n<style>\n  div {\n    line-height: 18px;\n  }\n</style>\n<div>Hi, I'm frame</div>\n"
  },
  {
    "path": "tests/static/grid.html",
    "content": "<script>\ndocument.addEventListener('DOMContentLoaded', function() {\n    function generatePalette(amount) {\n        var result = [];\n        var hueStep = 360 / amount;\n        for (var i = 0; i < amount; ++i)\n            result.push('hsl(' + (hueStep * i) + ', 100%, 90%)');\n        return result;\n    }\n\n    var palette = generatePalette(100);\n    for (var i = 0; i < 200; ++i) {\n        var box = document.createElement('div');\n        box.classList.add('box');\n        box.style.setProperty('background-color', palette[i % palette.length]);\n        var x = i;\n        do {\n            var digit = x % 10;\n            x = (x / 10)|0;\n            var img = document.createElement('img');\n            img.src = `./digits/${digit}.png`;\n            box.insertBefore(img, box.firstChild);\n        } while (x);\n        document.body.appendChild(box);\n    }\n});\n</script>\n\n<style>\n\nbody {\n    margin: 0;\n    padding: 0;\n}\n\n.box {\n    font-family: arial;\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    margin: 0;\n    padding: 0;\n    width: 50px;\n    height: 50px;\n    box-sizing: border-box;\n    border: 1px solid darkgray;\n}\n\n::-webkit-scrollbar {\n  display: none;\n}\n</style>\n"
  },
  {
    "path": "tests/static/historyapi.html",
    "content": "<script>\nwindow.addEventListener('DOMContentLoaded', () => {\n  history.pushState({}, '', '#1');\n});\n</script>\n"
  },
  {
    "path": "tests/static/huge-page.html",
    "content": "<body style=\"background-image: url(./huge-image.png);\">\n"
  },
  {
    "path": "tests/static/injectedfile.js",
    "content": "window.__injected = 42;\nwindow.__injectedError = new Error('hi');\n"
  },
  {
    "path": "tests/static/injectedstyle.css",
    "content": "body {\n  background-color: red;\n}\n"
  },
  {
    "path": "tests/static/jscoverage/eval.html",
    "content": "<script>eval('console.log(\"foo\")')</script>\n"
  },
  {
    "path": "tests/static/jscoverage/involved.html",
    "content": "<script>\nfunction foo() {\n  if (1 > 2)\n    console.log(1);\n  if (1 < 2)\n    console.log(2);\n  let x = 1 > 2 ? 'foo' : 'bar';\n  let y = 1 < 2 ? 'foo' : 'bar';\n  let z = () => {};\n  let q = () => {};\n  q();\n}\n\nfoo();\n</script>\n"
  },
  {
    "path": "tests/static/jscoverage/multiple.html",
    "content": "<script src=\"script1.js\"></script>\n<script src=\"script2.js\"></script>\n"
  },
  {
    "path": "tests/static/jscoverage/ranges.html",
    "content": "<script>\nfunction unused(){}console.log('used!');</script>\n"
  },
  {
    "path": "tests/static/jscoverage/script1.js",
    "content": "console.log(3);\n"
  },
  {
    "path": "tests/static/jscoverage/script2.js",
    "content": "console.log(3);\n"
  },
  {
    "path": "tests/static/jscoverage/simple.html",
    "content": "<script>\nfunction foo() {function bar() { } console.log(1); } foo(); </script>\n"
  },
  {
    "path": "tests/static/jscoverage/sourceurl.html",
    "content": "<script>\ncolsole.log(1);\n//# sourceURL=nicename.js\n</script>\n"
  },
  {
    "path": "tests/static/jscoverage/unused.html",
    "content": "<script>function foo() { }</script>\n"
  },
  {
    "path": "tests/static/keyboard.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Keyboard test</title>\n  </head>\n  <body>\n    <textarea></textarea>\n    <script>\n      window.result = \"\";\n      let textarea = document.querySelector('textarea');\n      textarea.focus();\n      textarea.addEventListener('keydown', event => {\n        log('Keydown:', event.key, event.code, event.which, modifiers(event));\n      });\n      textarea.addEventListener('keypress', event => {\n        log('Keypress:', event.key, event.code, event.which, event.keyCode, event.charCode, modifiers(event));\n      });\n      textarea.addEventListener('keyup', event => {\n        log('Keyup:', event.key, event.code, event.which, modifiers(event));\n      });\n      function modifiers(event) {\n        let m = [];\n        if (event.altKey)\n          m.push('Alt')\n        if (event.ctrlKey)\n          m.push('Control');\n        if (event.metaKey)\n          m.push('Meta')\n        if (event.shiftKey)\n          m.push('Shift')\n        return '[' + m.join(' ') + ']';\n      }\n      function log(...args) {\n        console.log.apply(console, args);\n        result += args.join(' ') + '\\n';\n      }\n      function getResult() {\n        let temp = result.trim();\n        result = \"\";\n        return temp;\n      }\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "tests/static/mobile.html",
    "content": "<meta name = \"viewport\" content = \"initial-scale = 1, user-scalable = no\">\n"
  },
  {
    "path": "tests/static/modernizr.js",
    "content": "/*! modernizr 3.5.0 (Custom Build) | MIT *\n* https://modernizr.com/download/?-touchevents-setclasses !*/\n!function(e,n,t){function o(e,n){return typeof e===n}function s(){var e,n,t,s,a,i,r;for(var l in c)if(c.hasOwnProperty(l)){if(e=[],n=c[l],n.name&&(e.push(n.name.toLowerCase()),n.options&&n.options.aliases&&n.options.aliases.length))for(t=0;t<n.options.aliases.length;t++)e.push(n.options.aliases[t].toLowerCase());for(s=o(n.fn,\"function\")?n.fn():n.fn,a=0;a<e.length;a++)i=e[a],r=i.split(\".\"),1===r.length?Modernizr[r[0]]=s:(!Modernizr[r[0]]||Modernizr[r[0]]instanceof Boolean||(Modernizr[r[0]]=new Boolean(Modernizr[r[0]])),Modernizr[r[0]][r[1]]=s),f.push((s?\"\":\"no-\")+r.join(\"-\"))}}function a(e){var n=u.className,t=Modernizr._config.classPrefix||\"\";if(p&&(n=n.baseVal),Modernizr._config.enableJSClass){var o=new RegExp(\"(^|\\\\s)\"+t+\"no-js(\\\\s|$)\");n=n.replace(o,\"$1\"+t+\"js$2\")}Modernizr._config.enableClasses&&(n+=\" \"+t+e.join(\" \"+t),p?u.className.baseVal=n:u.className=n)}function i(){return\"function\"!=typeof n.createElement?n.createElement(arguments[0]):p?n.createElementNS.call(n,\"http://www.w3.org/2000/svg\",arguments[0]):n.createElement.apply(n,arguments)}function r(){var e=n.body;return e||(e=i(p?\"svg\":\"body\"),e.fake=!0),e}function l(e,t,o,s){var a,l,f,c,d=\"modernizr\",p=i(\"div\"),h=r();if(parseInt(o,10))for(;o--;)f=i(\"div\"),f.id=s?s[o]:d+(o+1),p.appendChild(f);return a=i(\"style\"),a.type=\"text/css\",a.id=\"s\"+d,(h.fake?h:p).appendChild(a),h.appendChild(p),a.styleSheet?a.styleSheet.cssText=e:a.appendChild(n.createTextNode(e)),p.id=d,h.fake&&(h.style.background=\"\",h.style.overflow=\"hidden\",c=u.style.overflow,u.style.overflow=\"hidden\",u.appendChild(h)),l=t(p,e),h.fake?(h.parentNode.removeChild(h),u.style.overflow=c,u.offsetHeight):p.parentNode.removeChild(p),!!l}var f=[],c=[],d={_version:\"3.5.0\",_config:{classPrefix:\"\",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(e,n){var t=this;setTimeout(function(){n(t[e])},0)},addTest:function(e,n,t){c.push({name:e,fn:n,options:t})},addAsyncTest:function(e){c.push({name:null,fn:e})}},Modernizr=function(){};Modernizr.prototype=d,Modernizr=new Modernizr;var u=n.documentElement,p=\"svg\"===u.nodeName.toLowerCase(),h=d._config.usePrefixes?\" -webkit- -moz- -o- -ms- \".split(\" \"):[\"\",\"\"];d._prefixes=h;var m=d.testStyles=l;Modernizr.addTest(\"touchevents\",function(){var t;if(\"ontouchstart\"in e||e.DocumentTouch&&n instanceof DocumentTouch)t=!0;else{var o=[\"@media (\",h.join(\"touch-enabled),(\"),\"heartz\",\")\",\"{#modernizr{top:9px;position:absolute}}\"].join(\"\");m(o,function(e){t=9===e.offsetTop})}return t}),s(),a(f),delete d.addTest,delete d.addAsyncTest;for(var v=0;v<Modernizr._q.length;v++)Modernizr._q[v]();e.Modernizr=Modernizr}(window,document);\n"
  },
  {
    "path": "tests/static/mouse-helper.js",
    "content": "// This injects a box into the page that moves with the mouse;\n// Useful for debugging\n(function(){\n  const box = document.createElement('div');\n  box.classList.add('mouse-helper');\n  const styleElement = document.createElement('style');\n  styleElement.innerHTML = `\n  .mouse-helper {\n    pointer-events: none;\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 20px;\n    height: 20px;\n    background: rgba(0,0,0,.4);\n    border: 1px solid white;\n    border-radius: 10px;\n    margin-left: -10px;\n    margin-top: -10px;\n    transition: background .2s, border-radius .2s, border-color .2s;\n  }\n  .mouse-helper.button-1 {\n    transition: none;\n    background: rgba(0,0,0,0.9);\n  }\n  .mouse-helper.button-2 {\n    transition: none;\n    border-color: rgba(0,0,255,0.9);\n  }\n  .mouse-helper.button-3 {\n    transition: none;\n    border-radius: 4px;\n  }\n  .mouse-helper.button-4 {\n    transition: none;\n    border-color: rgba(255,0,0,0.9);\n  }\n  .mouse-helper.button-5 {\n    transition: none;\n    border-color: rgba(0,255,0,0.9);\n  }\n  `;\n  document.head.appendChild(styleElement);\n  document.body.appendChild(box);\n  document.addEventListener('mousemove', event => {\n    box.style.left = event.pageX + 'px';\n    box.style.top = event.pageY + 'px';\n    updateButtons(event.buttons);\n  }, true);\n  document.addEventListener('mousedown', event => {\n    updateButtons(event.buttons);\n    box.classList.add('button-' + event.which);\n  }, true);\n  document.addEventListener('mouseup', event => {\n    updateButtons(event.buttons);\n    box.classList.remove('button-' + event.which);\n  }, true);\n  function updateButtons(buttons) {\n    for (let i = 0; i < 5; i++)\n      box.classList.toggle('button-' + i, buttons & (1 << i));\n  }\n})();\n"
  },
  {
    "path": "tests/static/nested-frames.html",
    "content": "<style>\nbody {\n    display: flex;\n}\n\nbody iframe {\n    flex-grow: 1;\n    flex-shrink: 1;\n}\n\n::-webkit-scrollbar {\n  display: none;\n}\n</style>\n<script>\nasync function attachFrame(frameId, url) {\n    var frame = document.createElement('iframe');\n    frame.src = url;\n    frame.id = frameId;\n    document.body.appendChild(frame);\n    await new Promise(x => frame.onload = x);\n    return 'kazakh';\n}\n</script>\n<iframe src='./two-frames.html'></iframe>\n<iframe src='./frame.html'></iframe>\n\n"
  },
  {
    "path": "tests/static/offscreenbuttons.html",
    "content": "<style>\n  button {\n    position: absolute;\n    width: 100px;\n    height: 20px;\n  }\n   #btn0 { right: 0; top: 0; }\n  #btn1 { right: -10px; top: 25px; }\n  #btn2 { right: -20px; top: 50px; }\n  #btn3 { right: -30px; top: 75px; }\n  #btn4 { right: -40px; top: 100px; }\n  #btn5 { right: -50px; top: 125px; }\n  #btn6 { right: -60px; top: 150px; }\n  #btn7 { right: -70px; top: 175px; }\n  #btn8 { right: -80px; top: 200px; }\n  #btn9 { right: -90px; top: 225px; }\n  #btn10 { right: -100px; top: 250px; }\n</style>\n<button id=btn0>0</button>\n<button id=btn1>1</button>\n<button id=btn2>2</button>\n<button id=btn3>3</button>\n<button id=btn4>4</button>\n<button id=btn5>5</button>\n<button id=btn6>6</button>\n<button id=btn7>7</button>\n<button id=btn8>8</button>\n<button id=btn9>9</button>\n<button id=btn10>10</button>\n<script>\nwindow.addEventListener('DOMContentLoaded', () => {\n  for (const button of Array.from(document.querySelectorAll('button')))\n    button.addEventListener('click', () => console.log('button #' + button.textContent + ' clicked'), false);\n}, false);\n</script>\n"
  },
  {
    "path": "tests/static/one-frame.html",
    "content": "<iframe src=\"./frame.html\"></iframe>>\n"
  },
  {
    "path": "tests/static/one-style.css",
    "content": "body {\n    background-color: pink;\n}\n"
  },
  {
    "path": "tests/static/one-style.html",
    "content": "<link rel='stylesheet' href='./one-style.css'>\n<div>hello, world!</div>\n"
  },
  {
    "path": "tests/static/popup/popup.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Popup</title>\n  </head>\n  <body>\n    I am a popup\n  </body>\n</html>\n"
  },
  {
    "path": "tests/static/popup/window-open.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Popup test</title>\n  </head>\n  <body>\n    <script>\n      window.open('./popup.html');\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "tests/static/resetcss.html",
    "content": "<style>\n/* http://meyerweb.com/eric/tools/css/reset/\n   v2.0 | 20110126\n   License: none (public domain)\n*/\n html, body, div, span, applet, object, iframe,\nh1, h2, h3, h4, h5, h6, p, blockquote, pre,\na, abbr, acronym, address, big, cite, code,\ndel, dfn, em, img, ins, kbd, q, s, samp,\nsmall, strike, strong, sub, sup, tt, var,\nb, u, i, center,\ndl, dt, dd, ol, ul, li,\nfieldset, form, label, legend,\ntable, caption, tbody, tfoot, thead, tr, th, td,\narticle, aside, canvas, details, embed,\nfigure, figcaption, footer, header, hgroup,\nmenu, nav, output, ruby, section, summary,\ntime, mark, audio, video {\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n\tfont-size: 100%;\n\tfont: inherit;\n\tvertical-align: baseline;\n}\n/* HTML5 display-role reset for older browsers */\narticle, aside, details, figcaption, figure,\nfooter, header, hgroup, menu, nav, section {\n\tdisplay: block;\n}\nbody {\n\tline-height: 1;\n}\nol, ul {\n\tlist-style: none;\n}\nblockquote, q {\n\tquotes: none;\n}\nblockquote:before, blockquote:after,\nq:before, q:after {\n\tcontent: '';\n\tcontent: none;\n}\ntable {\n\tborder-collapse: collapse;\n\tborder-spacing: 0;\n}\n</style>\n"
  },
  {
    "path": "tests/static/script.js",
    "content": "console.log('Cheers!');\n"
  },
  {
    "path": "tests/static/scrollable.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Scrollable test</title>\n  </head>\n  <body>\n    <script src='mouse-helper.js'></script>\n    <script>\n        for (let i = 0; i < 100; i++) {\n            let button = document.createElement('button');\n            button.textContent = i + ': not clicked';\n            button.id = 'button-' + i;\n            button.onclick = () => button.textContent = 'clicked';\n            button.oncontextmenu = event => {\n              event.preventDefault();\n              button.textContent = 'context menu';\n            }\n            document.body.appendChild(button);\n            document.body.appendChild(document.createElement('br'));\n        }\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "tests/static/select.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Selection Test</title>\n  </head>\n  <body>\n    <select>\n      <option value=\"black\">Black</option>\n      <option value=\"blue\">Blue</option>\n      <option value=\"brown\">Brown</option>\n      <option value=\"cyan\">Cyan</option>\n      <option value=\"gray\">Gray</option>\n      <option value=\"green\">Green</option>\n      <option value=\"indigo\">Indigo</option>\n      <option value=\"magenta\">Magenta</option>\n      <option value=\"orange\">Orange</option>\n      <option value=\"pink\">Pink</option>\n      <option value=\"purple\">Purple</option>\n      <option value=\"red\">Red</option>\n      <option value=\"violet\">Violet</option>\n      <option value=\"white\">White</option>\n      <option value=\"yellow\">Yellow</option>\n    </select>\n    <script>\n      window.result = {\n        onInput: null,\n        onChange: null,\n        onBubblingChange: null,\n        onBubblingInput: null,\n      };\n\n      let select = document.querySelector('select');\n\n      function makeEmpty() {\n        for (let i = select.options.length - 1; i >= 0; --i) {\n          select.remove(i);\n        }\n      }\n\n      function makeMultiple() {\n        select.setAttribute('multiple', true);\n      }\n\n      select.addEventListener('input', () => {\n        result.onInput = Array.from(select.querySelectorAll('option:checked')).map((option) => {\n          return option.value;\n        });\n      }, false);\n\n      select.addEventListener('change', () => {\n        result.onChange = Array.from(select.querySelectorAll('option:checked')).map((option) => {\n          return option.value;\n        });\n      }, false);\n\n      document.body.addEventListener('input', () => {\n        result.onBubblingInput = Array.from(select.querySelectorAll('option:checked')).map((option) => {\n          return option.value;\n        });\n      }, false);\n\n      document.body.addEventListener('change', () => {\n        result.onBubblingChange = Array.from(select.querySelectorAll('option:checked')).map((option) => {\n          return option.value;\n        });\n      }, false);\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "tests/static/self-request.html",
    "content": "<script>\nvar req = new XMLHttpRequest();\nreq.open('GET', '/self-request.html');\nreq.send(null);\n</script>\n"
  },
  {
    "path": "tests/static/serviceworkers/empty/sw.html",
    "content": "<script>\n  window.registrationPromise = navigator.serviceWorker.register('sw.js');\n</script>\n"
  },
  {
    "path": "tests/static/serviceworkers/empty/sw.js",
    "content": ""
  },
  {
    "path": "tests/static/serviceworkers/fetch/style.css",
    "content": "body {\n  background-color: pink;\n}\n"
  },
  {
    "path": "tests/static/serviceworkers/fetch/sw.html",
    "content": "<link rel=\"stylesheet\" href=\"./style.css\">\n<script>\n  navigator.serviceWorker.register('sw.js');\n  window.activationPromise = new Promise(resolve => navigator.serviceWorker.oncontrollerchange = resolve);\n</script>\n\n"
  },
  {
    "path": "tests/static/serviceworkers/fetch/sw.js",
    "content": "self.addEventListener('fetch', event => {\n  event.respondWith(fetch(event.request));\n});\n\nself.addEventListener('activate', event => {\n  event.waitUntil(clients.claim());\n});\n"
  },
  {
    "path": "tests/static/shadow.html",
    "content": "<script>\n\nlet h1 = null;\nlet button = null;\nlet clicked = false;\n\nwindow.addEventListener('DOMContentLoaded', () => {\n  const shadowRoot = document.body.attachShadow({mode: 'open'});\n  h1 = document.createElement('h1');\n  h1.textContent = 'Hello Shadow DOM v1';\n  button = document.createElement('button');\n  button.textContent = 'Click';\n  button.addEventListener('click', () => clicked = true);\n  shadowRoot.appendChild(h1);\n  shadowRoot.appendChild(button);\n});\n</script>\n"
  },
  {
    "path": "tests/static/simple-extension/index.js",
    "content": "// Mock script for background extension\n"
  },
  {
    "path": "tests/static/simple-extension/manifest.json",
    "content": "{\n  \"name\": \"Simple extension\",\n  \"version\": \"0.1\",\n  \"app\": {\n    \"background\": {\n      \"scripts\": [\"index.js\"]\n    }\n  },\n  \"permissions\": [\"background\"],\n\n  \"manifest_version\": 2\n}\n"
  },
  {
    "path": "tests/static/simple.json",
    "content": "{\"foo\": \"bar\"}\n"
  },
  {
    "path": "tests/static/style.css",
    "content": "div {\n    color: blue;\n}\n"
  },
  {
    "path": "tests/static/sw.js",
    "content": ""
  },
  {
    "path": "tests/static/temperable.html",
    "content": "<script>\n    window.result = window.injected;\n</script>\n"
  },
  {
    "path": "tests/static/textarea.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Textarea test</title>\n  </head>\n  <body>\n    <textarea></textarea>\n    <script src='mouse-helper.js'></script>\n    <script>\n      window.result = '';\n      let textarea = document.querySelector('textarea');\n      textarea.addEventListener('input', () => result = textarea.value, false);\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "tests/static/touches.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Touch test</title>\n  </head>\n  <body>\n    <script src=\"mouse-helper.js\"></script>\n    <button onclick=\"clicked();\">Click target</button>\n    <script>\n      window.result = [];\n      const button = document.querySelector('button');\n      button.style.height = '200px';\n      button.style.width = '200px';\n      button.focus();\n      button.addEventListener('touchstart', event => {\n        log('Touchstart:', ...Array.from(event.changedTouches).map(touch => touch.identifier));\n      });\n      button.addEventListener('touchend', event => {\n        log('Touchend:', ...Array.from(event.changedTouches).map(touch => touch.identifier));\n      });\n      button.addEventListener('touchmove', event => {\n        log('Touchmove:', ...Array.from(event.changedTouches).map(touch => touch.identifier));\n      });\n      function log(...args) {\n        console.log.apply(console, args);\n        result.push(args.join(' '));\n      }\n      function getResult() {\n        let temp = result;\n        result = [];\n        return temp;\n      }\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "tests/static/two-frames.html",
    "content": "<style>\nbody {\n    display: flex;\n    flex-direction: column;\n}\n\nbody iframe {\n    flex-grow: 1;\n    flex-shrink: 1;\n}\n</style>\n<iframe src='./frame.html'></iframe>\n<iframe src='./frame.html'></iframe>\n"
  },
  {
    "path": "tests/static/worker/worker.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Worker test</title>\n  </head>\n  <body>\n    <script>\n      var worker = new Worker('worker.js');\n      worker.onmessage = function(message) {\n        console.log(message.data);\n      };\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "tests/static/worker/worker.js",
    "content": "console.log('hello from the worker');\nfunction workerFunction() {\n  return 'worker function result';\n}\nself.addEventListener('message', event => {\n  console.log('got this data: ' + event.data);\n});\n(async function() {\n  while (true) {\n    self.postMessage(workerFunction.toString());\n    await new Promise(x => setTimeout(x, 100));\n  }\n})();\n"
  },
  {
    "path": "tests/static/wrappedlink.html",
    "content": "<style>\n:root {\n  font-family: monospace;\n}\n body {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n div {\n  width: 10ch;\n  word-wrap: break-word;\n  border: 1px solid blue;\n  transform: rotate(33deg);\n  line-height: 8ch;\n  padding: 2ch;\n}\n a {\n  margin-left: 7ch;\n}\n</style>\n<div>\n  <a href='#clicked'>123321</a>\n</div>\n<script>\n</script>\n"
  },
  {
    "path": "tests/test_abnormal_crash.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport asyncio\nimport logging\nimport unittest\n\nfrom syncer import sync\n\nfrom pyppeteer import launch\nfrom pyppeteer.chromium_downloader import current_platform\nfrom pyppeteer.errors import NetworkError\n\n\nclass TestBrowserCrash(unittest.TestCase):\n    @sync\n    async def test_browser_crash_send(self):\n        browser = await launch(args=['--no-sandbox'])\n        page = await browser.newPage()\n        await page.goto('about:blank')\n        await page.querySelector(\"title\")\n        browser.process.terminate()\n        browser.process.wait()\n\n        if current_platform().startswith('win'):\n            # wait for terminating browser process\n            await asyncio.sleep(1)\n\n        with self.assertRaises(NetworkError):\n            await page.querySelector(\"title\")\n        with self.assertRaises(NetworkError):\n            with self.assertLogs('pyppeteer', logging.ERROR):\n                await page.querySelector(\"title\")\n        with self.assertRaises(ConnectionError):\n            await browser.newPage()\n"
  },
  {
    "path": "tests/test_browser.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport asyncio\nfrom copy import deepcopy\nimport os\nfrom pathlib import Path\nimport unittest\n\nfrom syncer import sync\n\nfrom pyppeteer import connect, launch\n\nfrom .base import BaseTestCase, DEFAULT_OPTIONS\nfrom .utils import waitEvent\n\n\nclass TestBrowser(unittest.TestCase):\n    extensionPath = Path(__file__).parent / 'static' / 'simple-extension'\n    extensionOptions = {\n        'headless': False,\n        'args': [\n            '--no-sandbox',\n            '--disable-extensions-except={}'.format(extensionPath),\n            '--load-extensions={}'.format(extensionPath),\n        ]\n    }\n\n    def waitForBackgroundPageTarget(self, browser):\n        promise = asyncio.get_event_loop().create_future()\n        for target in browser.targets():\n            if target.type == 'background_page':\n                promise.set_result(target)\n                return promise\n\n        def _listener(target) -> None:\n            if target.type != 'background_page':\n                return\n            browser.removeListener(_listener)\n            promise.set_result(target)\n\n        browser.on('targetcreated', _listener)\n        return promise\n\n    @sync\n    async def test_browser_process(self):\n        browser = await launch(DEFAULT_OPTIONS)\n        process = browser.process\n        self.assertGreater(process.pid, 0)\n        wsEndpoint = browser.wsEndpoint\n        browser2 = await connect({'browserWSEndpoint': wsEndpoint})\n        self.assertIsNone(browser2.process)\n        await browser.close()\n\n    @sync\n    async def test_version(self):\n        browser = await launch(DEFAULT_OPTIONS)\n        version = await browser.version()\n        self.assertTrue(len(version) > 0)\n        self.assertTrue(version.startswith('Headless'))\n        await browser.close()\n\n    @sync\n    async def test_user_agent(self):\n        browser = await launch(DEFAULT_OPTIONS)\n        userAgent = await browser.userAgent()\n        self.assertGreater(len(userAgent), 0)\n        self.assertIn('WebKit', userAgent)\n        await browser.close()\n\n    @sync\n    async def test_disconnect(self):\n        browser = await launch(DEFAULT_OPTIONS)\n        endpoint = browser.wsEndpoint\n        browser1 = await connect(browserWSEndpoint=endpoint)\n        browser2 = await connect(browserWSEndpoint=endpoint)\n        discon = []\n        discon1 = []\n        discon2 = []\n        browser.on('disconnected', lambda: discon.append(1))\n        browser1.on('disconnected', lambda: discon1.append(1))\n        browser2.on('disconnected', lambda: discon2.append(1))\n\n        await asyncio.wait([\n            browser2.disconnect(),\n            waitEvent(browser2, 'disconnected'),\n        ])\n        self.assertEqual(len(discon), 0)\n        self.assertEqual(len(discon1), 0)\n        self.assertEqual(len(discon2), 1)\n\n        await asyncio.wait([\n            waitEvent(browser1, 'disconnected'),\n            waitEvent(browser, 'disconnected'),\n            browser.close(),\n        ])\n        self.assertEqual(len(discon), 1)\n        self.assertEqual(len(discon1), 1)\n        self.assertEqual(len(discon2), 1)\n\n    @sync\n    async def test_crash(self):\n        browser = await launch(DEFAULT_OPTIONS)\n        page = await browser.newPage()\n        errors = []\n        page.on('error', lambda e: errors.append(e))\n        asyncio.ensure_future(page.goto('chrome://crash'))\n        for i in range(100):\n            await asyncio.sleep(0.01)\n            if errors:\n                break\n        await browser.close()\n        self.assertTrue(errors)\n\n    @unittest.skipIf('CI' in os.environ, 'skip in-browser test on CI server')\n    @sync\n    async def test_background_target_type(self):\n        browser = await launch(self.extensionOptions)\n        page = await browser.newPage()\n        backgroundPageTarget = await self.waitForBackgroundPageTarget(browser)\n        await page.close()\n        await browser.close()\n        self.assertTrue(backgroundPageTarget)\n\n    @unittest.skipIf('CI' in os.environ, 'skip in-browser test on CI server')\n    @sync\n    async def test_OOPIF(self):\n        options = deepcopy(DEFAULT_OPTIONS)\n        options['headless'] = False\n        browser = await launch(options)\n        page = await browser.newPage()\n        example_page = 'http://example.com/'\n        await page.goto(example_page)\n        await page.setRequestInterception(True)\n\n        async def intercept(req):\n            await req.respond({'body': 'YO, GOOGLE.COM'})\n\n        page.on('request', lambda req: asyncio.ensure_future(intercept(req)))\n        await page.evaluate('''() => {\n            const frame = document.createElement('iframe');\n            frame.setAttribute('src', 'https://google.com/');\n            document.body.appendChild(frame);\n            return new Promise(x => frame.onload = x);\n        }''')\n        await page.waitForSelector('iframe[src=\"https://google.com/\"]')\n        urls = []\n        for frame in page.frames:\n            urls.append(frame.url)\n        urls.sort()\n        self.assertEqual(urls, [example_page, 'https://google.com/'])\n        await browser.close()\n\n    @unittest.skipIf('CI' in os.environ, 'skip in-browser test on CI server')\n    @sync\n    async def test_background_page(self):\n        browserWithExtension = await launch(self.extensionOptions)\n        backgroundPageTarget = await self.waitForBackgroundPageTarget(browserWithExtension)  # noqa: E501\n        self.assertIsNotNone(backgroundPageTarget)\n        page = await backgroundPageTarget.page()\n        self.assertEqual(await page.evaluate('2 * 3'), 6)\n        await browserWithExtension.close()\n\n\nclass TestPageClose(BaseTestCase):\n    @sync\n    async def test_not_visible_in_browser_pages(self):\n        newPage = await self.context.newPage()\n        self.assertIn(newPage, await self.browser.pages())\n        await newPage.close()\n        self.assertNotIn(newPage, await self.browser.pages())\n\n    @sync\n    async def test_before_unload(self):\n        newPage = await self.context.newPage()\n        await newPage.goto(self.url + 'static/beforeunload.html')\n        await newPage.click('body')\n        asyncio.ensure_future(newPage.close(runBeforeUnload=True))\n        dialog = await waitEvent(newPage, 'dialog')\n        self.assertEqual(dialog.type, 'beforeunload')\n        self.assertEqual(dialog.defaultValue, '')\n        self.assertEqual(dialog.message, '')\n        asyncio.ensure_future(dialog.accept())\n        await waitEvent(newPage, 'close')\n\n    @sync\n    async def test_page_close_state(self):\n        newPage = await self.context.newPage()\n        self.assertFalse(newPage.isClosed())\n        await newPage.close()\n        self.assertTrue(newPage.isClosed())\n"
  },
  {
    "path": "tests/test_browser_context.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport asyncio\nimport unittest\n\nfrom pyppeteer import connect\nfrom pyppeteer.errors import BrowserError\n\nfrom syncer import sync\n\nfrom .base import BaseTestCase\nfrom .utils import waitEvent\n\n\nclass BrowserBaseTestCase(BaseTestCase):\n    def setUp(self):\n        pass\n\n    def tearDown(self):\n        pass\n\n\nclass TestBrowserContext(BrowserBaseTestCase):\n    @sync\n    async def test_default_context(self):\n        self.assertEqual(len(self.browser.browserContexts), 1)\n        defaultContext = self.browser.browserContexts[0]\n        self.assertFalse(defaultContext.isIncognito())\n        with self.assertRaises(BrowserError) as cm:\n            await defaultContext.close()\n        self.assertIn('cannot be closed', cm.exception.args[0])\n\n    @unittest.skip('this test not pass in some environment')\n    @sync\n    async def test_incognito_context(self):\n        self.assertEqual(len(self.browser.browserContexts), 1)\n        context = await self.browser.createIncognitoBrowserContext()\n        self.assertTrue(context.isIncognito())\n        self.assertEqual(len(self.browser.browserContexts), 2)\n        self.assertIn(context, self.browser.browserContexts)\n        await context.close()\n        self.assertEqual(len(self.browser.browserContexts), 1)\n\n    @sync\n    async def test_close_all_targets_once(self):\n        self.assertEqual(len(await self.browser.pages()), 1)\n        context = await self.browser.createIncognitoBrowserContext()\n        await context.newPage()\n        self.assertEqual(len(await self.browser.pages()), 2)\n        self.assertEqual(len(await context.pages()), 1)\n        await context.close()\n        self.assertEqual(len(await self.browser.pages()), 1)\n\n    @sync\n    async def test_window_open_use_parent_tab_context(self):\n        context = await self.browser.createIncognitoBrowserContext()\n        page = await context.newPage()\n        await page.goto(self.url + 'empty')\n        asyncio.ensure_future(\n            page.evaluate('url => window.open(url)', self.url + 'empty'))\n        popupTarget = await waitEvent(self.browser, 'targetcreated')\n        self.assertEqual(popupTarget.browserContext, context)\n        await context.close()\n\n    @sync\n    async def test_fire_target_event(self):\n        context = await self.browser.createIncognitoBrowserContext()\n        events = []\n        context.on('targetcreated', lambda t: events.append('CREATED: ' + t.url))  # noqa: E501\n        context.on('targetchanged', lambda t: events.append('CHANGED: ' + t.url))  # noqa: E501\n        context.on('targetdestroyed', lambda t: events.append('DESTROYED: ' + t.url))  # noqa: E501\n        page = await context.newPage()\n        await page.goto(self.url + 'empty')\n        await page.close()\n        self.assertEqual(events, [\n            'CREATED: about:blank',\n            'CHANGED: ' + self.url + 'empty',\n            'DESTROYED: ' + self.url + 'empty',\n        ])\n\n    @unittest.skip('this test not pass in some environment')\n    @sync\n    async def test_isolate_local_storage_and_cookie(self):\n        context1 = await self.browser.createIncognitoBrowserContext()\n        context2 = await self.browser.createIncognitoBrowserContext()\n        self.assertEqual(len(context1.targets()), 0)\n        self.assertEqual(len(context2.targets()), 0)\n\n        # create a page in the first incognito context\n        page1 = await context1.newPage()\n        await page1.goto(self.url + 'empty')\n        await page1.evaluate('''() => {\n            localStorage.setItem('name', 'page1');\n            document.cookie = 'name=page1';\n        }''')\n\n        self.assertEqual(len(context1.targets()), 1)\n        self.assertEqual(len(context2.targets()), 0)\n\n        # create a page in the second incognito context\n        page2 = await context2.newPage()\n        await page2.goto(self.url + 'empty')\n        await page2.evaluate('''() => {\n            localStorage.setItem('name', 'page2');\n            document.cookie = 'name=page2';\n        }''')\n\n        self.assertEqual(len(context1.targets()), 1)\n        self.assertEqual(context1.targets()[0], page1.target)\n        self.assertEqual(len(context2.targets()), 1)\n        self.assertEqual(context2.targets()[0], page2.target)\n\n        # make sure pages don't share local storage and cookie\n        self.assertEqual(await page1.evaluate('localStorage.getItem(\"name\")'), 'page1')  # noqa: E501\n        self.assertEqual(await page1.evaluate('document.cookie'), 'name=page1')\n        self.assertEqual(await page2.evaluate('localStorage.getItem(\"name\")'), 'page2')  # noqa: E501\n        self.assertEqual(await page2.evaluate('document.cookie'), 'name=page2')\n\n        await context1.close()\n        await context2.close()\n        self.assertEqual(len(self.browser.browserContexts), 1)\n\n    @sync\n    async def test_across_session(self):\n        self.assertEqual(len(self.browser.browserContexts), 1)\n        context = await self.browser.createIncognitoBrowserContext()\n        self.assertEqual(len(self.browser.browserContexts), 2)\n        remoteBrowser = await connect(\n            browserWSEndpoint=self.browser.wsEndpoint)\n        contexts = remoteBrowser.browserContexts\n        self.assertEqual(len(contexts), 2)\n        await remoteBrowser.disconnect()\n        await context.close()\n"
  },
  {
    "path": "tests/test_connection.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nfrom syncer import sync\n\nfrom pyppeteer.errors import NetworkError\n\nfrom .base import BaseTestCase\n\n\nclass TestConnection(BaseTestCase):\n    @sync\n    async def test_error_msg(self):\n        with self.assertRaises(NetworkError) as cm:\n            await self.page._client.send('ThisCommand.DoesNotExists')\n        self.assertIn('ThisCommand.DoesNotExists', cm.exception.args[0])\n\n\nclass TestCDPSession(BaseTestCase):\n    @sync\n    async def test_create_session(self):\n        client = await self.page.target.createCDPSession()\n        await client.send('Runtime.enable')\n        await client.send('Runtime.evaluate',\n                          {'expression': 'window.foo = \"bar\"'})\n        foo = await self.page.evaluate('window.foo')\n        self.assertEqual(foo, 'bar')\n\n    @sync\n    async def test_send_event(self):\n        client = await self.page.target.createCDPSession()\n        await client.send('Network.enable')\n        events = []\n        client.on('Network.requestWillBeSent', lambda e: events.append(e))\n        await self.page.goto(self.url + 'empty')\n        self.assertEqual(len(events), 1)\n\n    @sync\n    async def test_enable_disable_domain(self):\n        client = await self.page.target.createCDPSession()\n        await client.send('Runtime.enable')\n        await client.send('Debugger.enable')\n        await self.page.coverage.startJSCoverage()\n        await self.page.coverage.stopJSCoverage()\n\n    @sync\n    async def test_detach(self):\n        client = await self.page.target.createCDPSession()\n        await client.send('Runtime.enable')\n        evalResponse = await client.send(\n            'Runtime.evaluate', {'expression': '1 + 2', 'returnByValue': True})\n        self.assertEqual(evalResponse['result']['value'], 3)\n\n        await client.detach()\n        with self.assertRaises(NetworkError):\n            await client.send(\n                'Runtime.evaluate',\n                {'expression': '1 + 3', 'returnByValue': True}\n            )\n"
  },
  {
    "path": "tests/test_coverage.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nfrom syncer import sync\n\nfrom .base import BaseTestCase\n\n\nclass TestJSCoverage(BaseTestCase):\n    @sync\n    async def test_js_coverage(self):\n        await self.page.coverage.startJSCoverage()\n        await self.page.goto(\n            self.url + 'static/jscoverage/simple.html',\n            waitUntil='networkidle0',\n        )\n        coverage = await self.page.coverage.stopJSCoverage()\n        self.assertEqual(len(coverage), 1)\n        self.assertIn('/jscoverage/simple.html', coverage[0]['url'])\n        self.assertEqual(coverage[0]['ranges'], [\n            {'start': 0, 'end': 17},\n            {'start': 35, 'end': 61},\n        ])\n\n    @sync\n    async def test_js_coverage_source_url(self):\n        await self.page.coverage.startJSCoverage()\n        await self.page.goto(self.url + 'static/jscoverage/sourceurl.html')\n        coverage = await self.page.coverage.stopJSCoverage()\n        self.assertEqual(len(coverage), 1)\n        self.assertEqual(coverage[0]['url'], 'nicename.js')\n\n    @sync\n    async def test_js_coverage_ignore_empty(self):\n        await self.page.coverage.startJSCoverage()\n        await self.page.goto(self.url + 'empty')\n        coverage = await self.page.coverage.stopJSCoverage()\n        self.assertEqual(coverage, [])\n\n    @sync\n    async def test_ignore_eval_script_by_default(self):\n        await self.page.coverage.startJSCoverage()\n        await self.page.goto(self.url + 'static/jscoverage/eval.html')\n        coverage = await self.page.coverage.stopJSCoverage()\n        self.assertEqual(len(coverage), 1)\n\n    @sync\n    async def test_not_ignore_eval_script_with_reportAnonymousScript(self):\n        await self.page.coverage.startJSCoverage(reportAnonymousScript=True)\n        await self.page.goto(self.url + 'static/jscoverage/eval.html')\n        coverage = await self.page.coverage.stopJSCoverage()\n        self.assertTrue(any(entry for entry in coverage\n                            if entry['url'].startswith('debugger://')))\n        self.assertEqual(len(coverage), 2)\n\n    @sync\n    async def test_ignore_injected_script(self):\n        await self.page.coverage.startJSCoverage()\n        await self.page.goto(self.url + 'empty')\n        await self.page.evaluate('console.log(\"foo\")')\n        await self.page.evaluate('() => console.log(\"bar\")')\n        coverage = await self.page.coverage.stopJSCoverage()\n        self.assertEqual(len(coverage), 0)\n\n    @sync\n    async def test_ignore_injected_script_with_reportAnonymousScript(self):\n        await self.page.coverage.startJSCoverage(reportAnonymousScript=True)\n        await self.page.goto(self.url + 'empty')\n        await self.page.evaluate('console.log(\"foo\")')\n        await self.page.evaluate('() => console.log(\"bar\")')\n        coverage = await self.page.coverage.stopJSCoverage()\n        self.assertEqual(len(coverage), 0)\n\n    @sync\n    async def test_js_coverage_multiple_script(self):\n        await self.page.coverage.startJSCoverage()\n        await self.page.goto(self.url + 'static/jscoverage/multiple.html')\n        coverage = await self.page.coverage.stopJSCoverage()\n        self.assertEqual(len(coverage), 2)\n        coverage.sort(key=lambda cov: cov['url'])\n        self.assertIn('/jscoverage/script1.js', coverage[0]['url'])\n        self.assertIn('/jscoverage/script2.js', coverage[1]['url'])\n\n    @sync\n    async def test_js_coverage_ranges(self):\n        await self.page.coverage.startJSCoverage()\n        await self.page.goto(self.url + 'static/jscoverage/ranges.html')\n        coverage = await self.page.coverage.stopJSCoverage()\n        self.assertEqual(len(coverage), 1)\n        entry = coverage[0]\n        self.assertEqual(len(entry['ranges']), 1)\n        range = entry['ranges'][0]\n        self.assertEqual(\n            entry['text'][range['start']:range['end']],\n            'console.log(\\'used!\\');',\n        )\n\n    @sync\n    async def test_no_coverage(self):\n        await self.page.coverage.startJSCoverage()\n        await self.page.goto(self.url + 'static/jscoverage/unused.html')\n        coverage = await self.page.coverage.stopJSCoverage()\n        self.assertEqual(len(coverage), 1)\n        entry = coverage[0]\n        self.assertIn('static/jscoverage/unused.html', entry['url'])\n        self.assertEqual(len(entry['ranges']), 0)\n\n    @sync\n    async def test_js_coverage_condition(self):\n        await self.page.coverage.startJSCoverage()\n        await self.page.goto(self.url + 'static/jscoverage/involved.html')\n        coverage = await self.page.coverage.stopJSCoverage()\n        expected_range = [\n            {'start': 0, 'end': 35},\n            {'start': 50, 'end': 100},\n            {'start': 107, 'end': 141},\n            {'start': 148, 'end': 160},\n            {'start': 168, 'end': 207},\n        ]\n        self.assertEqual(coverage[0]['ranges'], expected_range)\n\n    @sync\n    async def test_js_coverage_no_reset_navigation(self):\n        await self.page.coverage.startJSCoverage(resetOnNavigation=False)\n        await self.page.goto(self.url + 'static/jscoverage/multiple.html')\n        await self.page.goto(self.url + 'empty')\n        coverage = await self.page.coverage.stopJSCoverage()\n        self.assertEqual(len(coverage), 2)\n\n    @sync\n    async def test_js_coverage_reset_navigation(self):\n        await self.page.coverage.startJSCoverage()  # enabled by default\n        await self.page.goto(self.url + 'static/jscoverage/multiple.html')\n        await self.page.goto(self.url + 'empty')\n        coverage = await self.page.coverage.stopJSCoverage()\n        self.assertEqual(len(coverage), 0)\n\n\nclass TestCSSCoverage(BaseTestCase):\n    @sync\n    async def test_css_coverage(self):\n        await self.page.coverage.startCSSCoverage()\n        await self.page.goto(self.url + 'static/csscoverage/simple.html')\n        coverage = await self.page.coverage.stopCSSCoverage()\n        self.assertEqual(len(coverage), 1)\n        self.assertIn('/csscoverage/simple.html', coverage[0]['url'])\n        self.assertEqual(coverage[0]['ranges'], [{'start': 1, 'end': 22}])\n        range = coverage[0]['ranges'][0]\n        self.assertEqual(\n            coverage[0]['text'][range['start']:range['end']],\n            'div { color: green; }',\n        )\n\n    @sync\n    async def test_css_coverage_url(self):\n        await self.page.coverage.startCSSCoverage()\n        await self.page.goto(self.url + 'static/csscoverage/sourceurl.html')\n        coverage = await self.page.coverage.stopCSSCoverage()\n        self.assertEqual(len(coverage), 1)\n        self.assertEqual(coverage[0]['url'], 'nicename.css')\n\n    @sync\n    async def test_css_coverage_multiple(self):\n        await self.page.coverage.startCSSCoverage()\n        await self.page.goto(self.url + 'static/csscoverage/multiple.html')\n        coverage = await self.page.coverage.stopCSSCoverage()\n        self.assertEqual(len(coverage), 2)\n        coverage.sort(key=lambda cov: cov['url'])\n        self.assertIn('/csscoverage/stylesheet1.css', coverage[0]['url'])\n        self.assertIn('/csscoverage/stylesheet2.css', coverage[1]['url'])\n\n    @sync\n    async def test_css_coverage_no_coverage(self):\n        await self.page.coverage.startCSSCoverage()\n        await self.page.goto(self.url + 'static/csscoverage/unused.html')\n        coverage = await self.page.coverage.stopCSSCoverage()\n        self.assertEqual(len(coverage), 1)\n        self.assertEqual(coverage[0]['url'], 'unused.css')\n        self.assertEqual(coverage[0]['ranges'], [])\n\n    @sync\n    async def test_css_coverage_media(self):\n        await self.page.coverage.startCSSCoverage()\n        await self.page.goto(self.url + 'static/csscoverage/media.html')\n        coverage = await self.page.coverage.stopCSSCoverage()\n        self.assertEqual(len(coverage), 1)\n        self.assertIn('/csscoverage/media.html', coverage[0]['url'])\n        self.assertEqual(coverage[0]['ranges'], [{'start': 17, 'end': 38}])\n\n    @sync\n    async def test_css_coverage_complicated(self):\n        await self.page.coverage.startCSSCoverage()\n        await self.page.goto(self.url + 'static/csscoverage/involved.html')\n        coverage = await self.page.coverage.stopCSSCoverage()\n        self.assertEqual(len(coverage), 1)\n        range = coverage[0]['ranges']\n        self.assertEqual(range, [\n            {'start': 20, 'end': 168},\n            {'start': 198, 'end': 304},\n        ])\n\n    @sync\n    async def test_css_ignore_injected_css(self):\n        await self.page.goto(self.url + 'empty')\n        await self.page.coverage.startCSSCoverage()\n        await self.page.addStyleTag(content='body { margin: 10px; }')\n        # trigger style recalc\n        margin = await self.page.evaluate(\n            '() => window.getComputedStyle(document.body).margin')\n        self.assertEqual(margin, '10px')\n        coverage = await self.page.coverage.stopCSSCoverage()\n        self.assertEqual(coverage, [])\n\n    @sync\n    async def test_css_coverage_no_reset_navigation(self):\n        await self.page.coverage.startCSSCoverage(resetOnNavigation=False)\n        await self.page.goto(self.url + 'static/csscoverage/multiple.html')\n        await self.page.goto(self.url + 'empty')\n        coverage = await self.page.coverage.stopCSSCoverage()\n        self.assertEqual(len(coverage), 2)\n\n    @sync\n    async def test_css_coverage_reset_navigation(self):\n        await self.page.coverage.startCSSCoverage()  # enabled by default\n        await self.page.goto(self.url + 'static/csscoverage/multiple.html')\n        await self.page.goto(self.url + 'empty')\n        coverage = await self.page.coverage.stopCSSCoverage()\n        self.assertEqual(len(coverage), 0)\n"
  },
  {
    "path": "tests/test_dialog.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport asyncio\n\nfrom syncer import sync\n\nfrom .base import BaseTestCase\n\n\nclass TestDialog(BaseTestCase):\n    @sync\n    async def test_alert(self):\n        def dialog_test(dialog):\n            self.assertEqual(dialog.type, 'alert')\n            self.assertEqual(dialog.defaultValue, '')\n            self.assertEqual(dialog.message, 'yo')\n            asyncio.ensure_future(dialog.accept())\n        self.page.on('dialog', dialog_test)\n        await self.page.evaluate('() => alert(\"yo\")')\n\n    @sync\n    async def test_prompt(self):\n        def dialog_test(dialog):\n            self.assertEqual(dialog.type, 'prompt')\n            self.assertEqual(dialog.defaultValue, 'yes.')\n            self.assertEqual(dialog.message, 'question?')\n            asyncio.ensure_future(dialog.accept('answer!'))\n        self.page.on('dialog', dialog_test)\n        answer = await self.page.evaluate('() => prompt(\"question?\", \"yes.\")')\n        self.assertEqual(answer, 'answer!')\n\n    @sync\n    async def test_prompt_dismiss(self):\n        def dismiss_test(dialog, *args):\n            asyncio.ensure_future(dialog.dismiss())\n        self.page.on('dialog', dismiss_test)\n        result = await self.page.evaluate('() => prompt(\"question?\", \"yes.\")')\n        self.assertIsNone(result)\n"
  },
  {
    "path": "tests/test_element_handle.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport logging\nimport sys\n\nfrom syncer import sync\n\nimport pyppeteer\nfrom pyppeteer.errors import ElementHandleError\n\nfrom .base import BaseTestCase\nfrom .frame_utils import attachFrame\n\n\nclass TestBoundingBox(BaseTestCase):\n    @sync\n    async def test_bounding_box(self):\n        await self.page.setViewport({'width': 500, 'height': 500})\n        await self.page.goto(self.url + 'static/grid.html')\n        elementHandle = await self.page.J('.box:nth-of-type(13)')\n        box = await elementHandle.boundingBox()\n        self.assertEqual({'x': 100, 'y': 50, 'width': 50, 'height': 50}, box)\n\n    @sync\n    async def test_nested_frame(self):\n        await self.page.setViewport({'width': 500, 'height': 500})\n        await self.page.goto(self.url + 'static/nested-frames.html')\n        nestedFrame = self.page.frames[1].childFrames[1]\n        elementHandle = await nestedFrame.J('div')\n        box = await elementHandle.boundingBox()\n        # Frame size is unstable\n        # Frame order is unstable\n        # self.assertIn(box, [\n        #     {'x': 28, 'y': 28, 'width': 264, 'height': 16},\n        #     {'x': 28, 'y': 260, 'width': 264, 'height': 16},\n        # ])\n        self.assertEqual(box['x'], 28)\n        self.assertIn(box['y'], [28, 260])\n        self.assertEqual(box['width'], 264)\n\n    @sync\n    async def test_invisible_element(self):\n        await self.page.setContent('<div style=\"display: none;\">hi</div>')\n        element = await self.page.J('div')\n        self.assertIsNone(await element.boundingBox())\n\n    @sync\n    async def test_force_layout(self):\n        await self.page.setViewport({'width': 500, 'height': 500})\n        await self.page.setContent(\n            '<div style=\"width: 100px; height: 100px;\">hello</div>')\n        elementHandle = await self.page.J('div')\n        await self.page.evaluate(\n            'element => element.style.height = \"200px\"',\n            elementHandle,\n        )\n        box = await elementHandle.boundingBox()\n        self.assertEqual(box, {\n            'x': 8,\n            'y': 8,\n            'width': 100,\n            'height': 200,\n        })\n\n    @sync\n    async def test_svg(self):\n        await self.page.setContent('''\n            <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"500\" height=\"500\">\n                <rect id=\"theRect\" x=\"30\" y=\"50\" width=\"200\" height=\"300\"></rect>\n            </svg>\n        ''')  # noqa: E501\n        element = await self.page.J('#therect')\n        pptrBoundingBox = await element.boundingBox()\n        webBoundingBox = await self.page.evaluate('''e => {\n            const rect = e.getBoundingClientRect();\n            return {x: rect.x, y: rect.y, width: rect.width, height: rect.height};\n        }''', element)  # noqa: E501\n        self.assertEqual(pptrBoundingBox, webBoundingBox)\n\n\nclass TestBoxModel(BaseTestCase):\n    def setUp(self):\n        self._old_debug = pyppeteer.DEBUG\n        super().setUp()\n\n    def tearDown(self):\n        super().tearDown()\n        pyppeteer.DEBUG = self._old_debug\n\n    @sync\n    async def test_box_model(self):\n        await self.page.goto(self.url + 'static/resetcss.html')\n\n        # add frame and position it absolutely\n        await attachFrame(\n            self.page, 'frame1', self.url + 'static/resetcss.html')\n        await self.page.evaluate('''() => {\n            const frame = document.querySelector('#frame1');\n            frame.style = `\n                position: absolute;\n                left: 1px;\n                top: 2px;\n            `;\n        }''')\n\n        # add div and position it absolutely inside frame\n        frame = self.page.frames[1]\n        divHandle = (await frame.evaluateHandle('''() => {\n            const div = document.createElement('div');\n            document.body.appendChild(div);\n            div.style = `\n                box-sizing: border-box;\n                position: absolute;\n                border-left: 1px solid black;\n                padding-left: 2px;\n                margin-left: 3px;\n                left: 4px;\n                top: 5px;\n                width: 6px;\n                height: 7px;\n            `\n            return div\n        }''')).asElement()\n\n        # query div's boxModel and assert box values\n        box = await divHandle.boxModel()\n        self.assertEqual(box['width'], 6)\n        self.assertEqual(box['height'], 7)\n        self.assertEqual(box['margin'][0], {\n            'x': 1 + 4,\n            'y': 2 + 5,\n        })\n        self.assertEqual(box['border'][0], {\n            'x': 1 + 4 + 3,\n            'y': 2 + 5,\n        })\n        self.assertEqual(box['padding'][0], {\n            'x': 1 + 4 + 3 + 1,\n            'y': 2 + 5,\n        })\n        self.assertEqual(box['content'][0], {\n            'x': 1 + 4 + 3 + 1 + 2,\n            'y': 2 + 5,\n        })\n\n    @sync\n    async def test_box_model_invisible(self):\n        await self.page.setContent('<div style=\"display:none;\">hi</div>')\n        element = await self.page.J('div')\n        with self.assertLogs('pyppeteer.element_handle', logging.DEBUG):\n            self.assertIsNone(await element.boxModel())\n\n    @sync\n    async def test_debug_error(self):\n        await self.page.setContent('<div style=\"display:none;\">hi</div>')\n        element = await self.page.J('div')\n        pyppeteer.DEBUG = True\n        with self.assertLogs('pyppeteer.element_handle', logging.ERROR):\n            self.assertIsNone(await element.boxModel())\n        pyppeteer.DEBUG = False\n        with self.assertRaises(AssertionError):\n            with self.assertLogs('pyppeteer.element_handle', logging.INFO):\n                self.assertIsNone(await element.boxModel())\n\n\nclass TestContentFrame(BaseTestCase):\n    @sync\n    async def test_content_frame(self):\n        await self.page.goto(self.url + 'empty')\n        await attachFrame(self.page, 'frame1', self.url + 'empty')\n        elementHandle = await self.page.J('#frame1')\n        frame = await elementHandle.contentFrame()\n        self.assertEqual(frame, self.page.frames[1])\n\n\nclass TestClick(BaseTestCase):\n    @sync\n    async def test_clik(self):\n        await self.page.goto(self.url + 'static/button.html')\n        button = await self.page.J('button')\n        await button.click()\n        self.assertEqual(await self.page.evaluate('result'), 'Clicked')\n\n    @sync\n    async def test_shadow_dom(self):\n        await self.page.goto(self.url + 'static/shadow.html')\n        button = await self.page.evaluateHandle('() => button')\n        await button.click()\n        self.assertTrue(await self.page.evaluate('clicked'))\n\n    @sync\n    async def test_text_node(self):\n        await self.page.goto(self.url + 'static/button.html')\n        buttonTextNode = await self.page.evaluateHandle(\n            '() => document.querySelector(\"button\").firstChild')\n        with self.assertRaises(ElementHandleError) as cm:\n            await buttonTextNode.click()\n        self.assertEqual('Node is not of type HTMLElement',\n                         cm.exception.args[0])\n\n    @sync\n    async def test_detached_node(self):\n        await self.page.goto(self.url + 'static/button.html')\n        button = await self.page.J('button')\n        await self.page.evaluate('btn => btn.remove()', button)\n        with self.assertRaises(ElementHandleError) as cm:\n            await button.click()\n        self.assertEqual('Node is detached from document',\n                         cm.exception.args[0])\n\n    @sync\n    async def test_hidden_node(self):\n        await self.page.goto(self.url + 'static/button.html')\n        button = await self.page.J('button')\n        await self.page.evaluate('btn => btn.style.display = \"none\"', button)\n        with self.assertRaises(ElementHandleError) as cm:\n            await button.click()\n        self.assertEqual(\n            'Node is either not visible or not an HTMLElement',\n            cm.exception.args[0],\n        )\n\n    @sync\n    async def test_recursively_hidden_node(self):\n        await self.page.goto(self.url + 'static/button.html')\n        button = await self.page.J('button')\n        await self.page.evaluate(\n            'btn => btn.parentElement.style.display = \"none\"', button)\n        with self.assertRaises(ElementHandleError) as cm:\n            await button.click()\n        self.assertEqual(\n            'Node is either not visible or not an HTMLElement',\n            cm.exception.args[0],\n        )\n\n    @sync\n    async def test_br_node(self):\n        await self.page.setContent('hello<br>goodbye')\n        br = await self.page.J('br')\n        with self.assertRaises(ElementHandleError) as cm:\n            await br.click()\n        self.assertEqual(\n            'Node is either not visible or not an HTMLElement',\n            cm.exception.args[0],\n        )\n\n\nclass TestHover(BaseTestCase):\n    @sync\n    async def test_hover(self):\n        await self.page.goto(self.url + 'static/scrollable.html')\n        button = await self.page.J('#button-6')\n        await button.hover()\n        self.assertEqual(\n            await self.page.evaluate(\n                'document.querySelector(\"button:hover\").id'),\n            'button-6'\n        )\n\n\nclass TestIsIntersectingViewport(BaseTestCase):\n    @sync\n    async def test_is_intersecting_viewport(self):\n        await self.page.goto(self.url + 'static/offscreenbuttons.html')\n        for i in range(11):\n            button = await self.page.J('#btn{}'.format(i))\n            visible = i < 10\n            self.assertEqual(await button.isIntersectingViewport(), visible)\n\n\nclass TestScreenshot(BaseTestCase):\n    @sync\n    async def test_screenshot_larger_than_viewport(self):\n        await self.page.setViewport({'width': 500, 'height': 500})\n        await self.page.setContent('''\nsomething above\n<style>\ndiv.to-screenshot {\n    border: 1px solid blue;\n    width: 600px;\n    height: 600px;\n    margin-left: 50px;\n}\n\n::-webkit-scrollbar {\n    display: none;\n}\n</style>\n\n<div class=\"to-screenshot\"></div>\n                                   ''')\n        elementHandle = await self.page.J('div.to-screenshot')\n        await elementHandle.screenshot()\n        size = await self.page.evaluate(\n            '() => ({ w: window.innerWidth, h: window.innerHeight })'\n        )\n        self.assertEqual({'w': 500, 'h': 500}, size)\n\n\nclass TestQuerySelector(BaseTestCase):\n    @sync\n    async def test_J(self):\n        await self.page.setContent('''\n<html><body><div class=\"second\"><div class=\"inner\">A</div></div></body></html>\n        ''')\n        html = await self.page.J('html')\n        second = await html.J('.second')\n        inner = await second.J('.inner')\n        content = await self.page.evaluate('e => e.textContent', inner)\n        self.assertEqual(content, 'A')\n\n    @sync\n    async def test_J_none(self):\n        await self.page.setContent('''\n<html><body><div class=\"second\"><div class=\"inner\">A</div></div></body></html>\n        ''')\n        html = await self.page.J('html')\n        second = await html.J('.third')\n        self.assertIsNone(second)\n\n    @sync\n    async def test_Jeval(self):\n        await self.page.setContent('''<html><body>\n            <div class=\"tweet\">\n                <div class=\"like\">100</div>\n                <div class=\"retweets\">10</div>\n            </div>\n        </body></html>''')\n        tweet = await self.page.J('.tweet')\n        content = await tweet.Jeval('.like', 'node => node.innerText')\n        self.assertEqual(content, '100')\n\n    @sync\n    async def test_Jeval_subtree(self):\n        htmlContent = '<div class=\"a\">not-a-child-div</div><div id=\"myId\"><div class=\"a\">a-child-div</div></div>'  # noqa: E501\n        await self.page.setContent(htmlContent)\n        elementHandle = await self.page.J('#myId')\n        content = await elementHandle.Jeval('.a', 'node => node.innerText')\n        self.assertEqual(content, 'a-child-div')\n\n    @sync\n    async def test_Jeval_with_missing_selector(self):\n        htmlContent = '<div class=\"a\">not-a-child-div</div><div id=\"myId\"></div>'  # noqa: E501\n        await self.page.setContent(htmlContent)\n        elementHandle = await self.page.J('#myId')\n        with self.assertRaises(ElementHandleError) as cm:\n            await elementHandle.Jeval('.a', 'node => node.innerText')\n        self.assertIn('Error: failed to find element matching selector \".a\"',\n                      cm.exception.args[0])\n\n    @sync\n    async def test_JJ(self):\n        await self.page.setContent('''\n<html><body><div>A</div><br/><div>B</div></body></html>\n        ''')\n        html = await self.page.J('html')\n        elements = await html.JJ('div')\n        self.assertEqual(len(elements), 2)\n        if sys.version_info >= (3, 6):\n            result = []\n            for elm in elements:\n                result.append(\n                    await self.page.evaluate('(e) => e.textContent', elm)\n                )\n            self.assertEqual(result, ['A', 'B'])\n\n    @sync\n    async def test_JJ_empty(self):\n        await self.page.setContent('''\n<html><body><span>A</span><br/><span>B</span></body></html>\n        ''')\n        html = await self.page.J('html')\n        elements = await html.JJ('div')\n        self.assertEqual(len(elements), 0)\n\n    @sync\n    async def test_JJEval(self):\n        await self.page.setContent(\n            '<html><body><div class=\"tweet\"><div class=\"like\">100</div>'\n            '<div class=\"like\">10</div></div></body></html>'\n        )\n        tweet = await self.page.J('.tweet')\n        content = await tweet.JJeval(\n            '.like', 'nodes => nodes.map(n => n.innerText)')\n        self.assertEqual(content, ['100', '10'])\n\n    @sync\n    async def test_JJEval_subtree(self):\n        await self.page.setContent(\n            '<div class=\"a\">not-a-child-div</div>'\n            '<div id=\"myId\">'\n            '<div class=\"a\">a1-child-div</div>'\n            '<div class=\"a\">a2-child-div</div>'\n            '</div>'\n        )\n        elementHandle = await self.page.J('#myId')\n        content = await elementHandle.JJeval(\n            '.a', 'nodes => nodes.map(n => n.innerText)')\n        self.assertEqual(content, ['a1-child-div', 'a2-child-div'])\n\n    @sync\n    async def test_JJEval_missing_selector(self):\n        await self.page.setContent(\n            '<div class=\"a\">not-a-child-div</div><div id=\"myId\"></div>')\n        elementHandle = await self.page.J('#myId')\n        nodesLength = await elementHandle.JJeval('.a', 'nodes => nodes.length')\n        self.assertEqual(nodesLength, 0)\n\n    @sync\n    async def test_xpath(self):\n        await self.page.setContent(\n            '<html><body><div class=\"second\"><div class=\"inner\">A</div></div></body></html>'  # noqa: E501\n        )\n        html = await self.page.querySelector('html')\n        second = await html.xpath('./body/div[contains(@class, \\'second\\')]')\n        inner = await second[0].xpath('./div[contains(@class, \\'inner\\')]')\n        content = await self.page.evaluate('(e) => e.textContent', inner[0])\n        self.assertEqual(content, 'A')\n\n    @sync\n    async def test_xpath_not_found(self):\n        await self.page.goto(self.url + 'empty')\n        html = await self.page.querySelector('html')\n        element = await html.xpath('/div[contains(@class, \\'third\\')]')\n        self.assertEqual(element, [])\n"
  },
  {
    "path": "tests/test_execution_context.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nfrom syncer import sync\n\nfrom pyppeteer.errors import ElementHandleError, NetworkError\n\nfrom .base import BaseTestCase\n\n\nclass TestQueryObject(BaseTestCase):\n    @sync\n    async def test_query_objects(self):\n        await self.page.goto(self.url + 'empty')\n        await self.page.evaluate(\n            '() => window.set = new Set([\"hello\", \"world\"])'\n        )\n        prototypeHandle = await self.page.evaluateHandle('() => Set.prototype')\n        objectsHandle = await self.page.queryObjects(prototypeHandle)\n        count = await self.page.evaluate(\n            'objects => objects.length',\n            objectsHandle,\n        )\n        self.assertEqual(count, 1)\n        values = await self.page.evaluate(\n            'objects => Array.from(objects[0].values())',\n            objectsHandle,\n        )\n        self.assertEqual(values, ['hello', 'world'])\n\n    @sync\n    async def test_query_objects_disposed(self):\n        await self.page.goto(self.url + 'empty')\n        prototypeHandle = await self.page.evaluateHandle(\n            '() => HTMLBodyElement.prototype'\n        )\n        await prototypeHandle.dispose()\n        with self.assertRaises(ElementHandleError):\n            await self.page.queryObjects(prototypeHandle)\n\n    @sync\n    async def test_query_objects_primitive_value_error(self):\n        await self.page.goto(self.url + 'empty')\n        prototypeHandle = await self.page.evaluateHandle('() => 42')\n        with self.assertRaises(ElementHandleError):\n            await self.page.queryObjects(prototypeHandle)\n\n\nclass TestJSHandle(BaseTestCase):\n    @sync\n    async def test_get_property(self):\n        handle1 = await self.page.evaluateHandle(\n            '() => ({one: 1, two: 2, three: 3})'\n        )\n        handle2 = await handle1.getProperty('two')\n        self.assertEqual(await handle2.jsonValue(), 2)\n\n    @sync\n    async def test_json_value(self):\n        handle1 = await self.page.evaluateHandle('() => ({foo: \"bar\"})')\n        json = await handle1.jsonValue()\n        self.assertEqual(json, {'foo': 'bar'})\n\n    @sync\n    async def test_json_date_fail(self):\n        handle = await self.page.evaluateHandle(\n            '() => new Date(\"2017-09-26T00:00:00.000Z\")'\n        )\n        json = await handle.jsonValue()\n        self.assertEqual(json, {})\n\n    @sync\n    async def test_json_circular_object_error(self):\n        windowHandle = await self.page.evaluateHandle('window')\n        with self.assertRaises(NetworkError) as cm:\n            await windowHandle.jsonValue()\n        self.assertIn('Object reference chain is too long',\n                      cm.exception.args[0])\n\n    @sync\n    async def test_get_properties(self):\n        handle1 = await self.page.evaluateHandle('() => ({foo: \"bar\"})')\n        properties = await handle1.getProperties()\n        foo = properties.get('foo')\n        self.assertTrue(foo)\n        self.assertEqual(await foo.jsonValue(), 'bar')\n\n    @sync\n    async def test_return_non_own_properties(self):\n        aHandle = await self.page.evaluateHandle('''() => {\n            class A {\n                constructor() {\n                    this.a = '1';\n                }\n            }\n            class B extends A {\n                constructor() {\n                    super();\n                    this.b = '2';\n                }\n            }\n            return new B();\n        }''')\n        properties = await aHandle.getProperties()\n        self.assertEqual(await properties.get('a').jsonValue(), '1')\n        self.assertEqual(await properties.get('b').jsonValue(), '2')\n\n    @sync\n    async def test_as_element(self):\n        aHandle = await self.page.evaluateHandle('() => document.body')\n        element = aHandle.asElement()\n        self.assertTrue(element)\n\n    @sync\n    async def test_as_element_non_element(self):\n        aHandle = await self.page.evaluateHandle('() => 2')\n        element = aHandle.asElement()\n        self.assertIsNone(element)\n\n    @sync\n    async def test_as_element_text_node(self):\n        await self.page.setContent('<div>ee!</div>')\n        aHandle = await self.page.evaluateHandle(\n            '() => document.querySelector(\"div\").firstChild')\n        element = aHandle.asElement()\n        self.assertTrue(element)\n        self.assertTrue(await self.page.evaluate(\n            '(e) => e.nodeType === HTMLElement.TEXT_NODE',\n            element,\n        ))\n\n    @sync\n    async def test_to_string_number(self):\n        handle = await self.page.evaluateHandle('() => 2')\n        self.assertEqual(handle.toString(), 'JSHandle:2')\n\n    @sync\n    async def test_to_string_str(self):\n        handle = await self.page.evaluateHandle('() => \"a\"')\n        self.assertEqual(handle.toString(), 'JSHandle:a')\n\n    @sync\n    async def test_to_string_complicated_object(self):\n        handle = await self.page.evaluateHandle('() => window')\n        self.assertEqual(handle.toString(), 'JSHandle@object')\n"
  },
  {
    "path": "tests/test_frame.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport asyncio\nimport time\nimport unittest\n\nfrom syncer import sync\n\nfrom pyppeteer.errors import ElementHandleError, NetworkError, TimeoutError\n\nfrom .base import BaseTestCase\nfrom .frame_utils import attachFrame, detachFrame, dumpFrames, navigateFrame\nfrom .utils import waitEvent\n\naddElement = 'tag=>document.body.appendChild(document.createElement(tag))'\n\n\nclass TestContext(BaseTestCase):\n    @sync\n    async def test_frame_context(self):\n        await self.page.goto(self.url + 'empty')\n        await attachFrame(self.page, 'frame1', self.url + 'empty')\n        self.assertEqual(len(self.page.frames), 2)\n        frame1 = self.page.frames[0]\n        frame2 = self.page.frames[1]\n        context1 = await frame1.executionContext()\n        context2 = await frame2.executionContext()\n        self.assertTrue(context1)\n        self.assertTrue(context2)\n        self.assertTrue(context1 != context2)\n        self.assertEqual(context1.frame, frame1)\n        self.assertEqual(context2.frame, frame2)\n\n        await context1.evaluate('() => window.a = 1')\n        await context2.evaluate('() => window.a = 2')\n        a1 = await context1.evaluate('() => window.a')\n        a2 = await context2.evaluate('() => window.a')\n        self.assertEqual(a1, 1)\n        self.assertEqual(a2, 2)\n\n\nclass TestEvaluateHandle(BaseTestCase):\n    @sync\n    async def test_evaluate_handle(self):\n        await self.page.goto(self.url + 'empty')\n        frame = self.page.mainFrame\n        windowHandle = await frame.evaluateHandle('window')\n        self.assertTrue(windowHandle)\n\n\nclass TestEvaluate(BaseTestCase):\n    @sync\n    async def test_frame_evaluate(self):\n        await self.page.goto(self.url + 'empty')\n        await attachFrame(self.page, 'frame1', self.url + 'empty')\n        self.assertEqual(len(self.page.frames), 2)\n        frame1 = self.page.frames[0]\n        frame2 = self.page.frames[1]\n        await frame1.evaluate('() => window.a = 1')\n        await frame2.evaluate('() => window.a = 2')\n        a1 = await frame1.evaluate('window.a')\n        a2 = await frame2.evaluate('window.a')\n        self.assertEqual(a1, 1)\n        self.assertEqual(a2, 2)\n\n    @sync\n    async def test_frame_evaluate_after_navigation(self):\n        self.result = None\n\n        def frame_navigated(frame):\n            self.result = asyncio.ensure_future(frame.evaluate('6 * 7'))\n\n        self.page.on('framenavigated', frame_navigated)\n        await self.page.goto(self.url + 'empty')\n        self.assertIsNotNone(self.result)\n        self.assertEqual(await self.result, 42)\n\n    @sync\n    async def test_frame_cross_site(self):\n        await self.page.goto(self.url + 'empty')\n        mainFrame = self.page.mainFrame\n        loc = await mainFrame.evaluate('window.location.href')\n        self.assertIn('localhost', loc)\n        await self.page.goto('http://127.0.0.1:{}/empty'.format(self.port))\n        loc = await mainFrame.evaluate('window.location.href')\n        self.assertIn('127.0.0.1', loc)\n\n\nclass TestWaitForFunction(BaseTestCase):\n    @sync\n    async def test_wait_for_expression(self):\n        fut = asyncio.ensure_future(\n            self.page.waitForFunction('window.__FOO === 1')\n        )\n        await self.page.evaluate('window.__FOO = 1;')\n        await fut\n\n    @sync\n    async def test_wait_for_function(self):\n        fut = asyncio.ensure_future(\n            self.page.waitForFunction('() => window.__FOO === 1')\n        )\n        await self.page.evaluate('window.__FOO = 1;')\n        await fut\n\n    @sync\n    async def test_wait_for_function_args(self):\n        fut = asyncio.ensure_future(\n            self.page.waitForFunction(\n                '(a, b) => a + b === 3', {}, 1, 2)\n        )\n        await fut\n\n    @sync\n    async def test_before_execution_context_resolved(self):\n        await self.page.evaluateOnNewDocument('() => window.__RELOADED = true')\n        await self.page.waitForFunction('''() => {\n            if (!window.__RELOADED)\n                window.location.reload();\n            return true;\n        }''')\n\n    @sync\n    async def test_poll_on_interval(self):\n        result = []\n        start_time = time.perf_counter()\n        fut = asyncio.ensure_future(self.page.waitForFunction(\n            '() => window.__FOO === \"hit\"', polling=100,\n        ))\n        fut.add_done_callback(lambda _: result.append(True))\n        await asyncio.sleep(0)  # once switch task\n        await self.page.evaluate('window.__FOO = \"hit\"')\n        await self.page.evaluate(\n            'document.body.appendChild(document.createElement(\"div\"))'\n        )\n        await asyncio.sleep(0.02)\n        self.assertFalse(result)\n        await fut\n        self.assertGreater(time.perf_counter() - start_time, 0.1)\n        self.assertEqual(await self.page.evaluate('window.__FOO'), 'hit')\n\n    @sync\n    async def test_poll_on_mutation(self):\n        result = []\n        fut = asyncio.ensure_future(self.page.waitForFunction(\n            '() => window.__FOO === \"hit\"', polling='mutation',\n        ))\n        fut.add_done_callback(lambda _: result.append(True))\n        await asyncio.sleep(0)  # once switch task\n        await self.page.evaluate('window.__FOO = \"hit\"')\n        await asyncio.sleep(0.1)\n        self.assertFalse(result)\n        await self.page.evaluate(\n            'document.body.appendChild(document.createElement(\"div\"))'\n        )\n        await fut\n        self.assertTrue(result)\n\n    @sync\n    async def test_poll_on_raf(self):\n        result = []\n        fut = asyncio.ensure_future(self.page.waitForFunction(\n            '() => window.__FOO === \"hit\"', polling='raf',\n        ))\n        fut.add_done_callback(lambda _: result.append(True))\n        await asyncio.sleep(0)  # once switch task\n        await self.page.evaluate('window.__FOO = \"hit\"')\n        await asyncio.sleep(0)  # once switch task\n        self.assertFalse(result)\n        await fut\n        self.assertTrue(result)\n\n    @sync\n    async def test_csp(self):\n        await self.page.goto(self.url + 'csp')\n        fut = asyncio.ensure_future(self.page.waitForFunction(\n            '() => window.__FOO === \"hit\"',\n            polling='raf',\n        ))\n        await self.page.evaluate('window.__FOO = \"hit\"')\n        await fut\n\n    @sync\n    async def test_bad_polling_value(self):\n        with self.assertRaises(ValueError) as cm:\n            await self.page.waitForFunction('() => true', polling='unknown')\n        self.assertIn('polling', cm.exception.args[0])\n\n    @sync\n    async def test_negative_polling_value(self):\n        with self.assertRaises(ValueError) as cm:\n            await self.page.waitForFunction('() => true', polling=-100)\n        self.assertIn('Cannot poll with non-positive interval',\n                      cm.exception.args[0])\n\n    @sync\n    async def test_wait_for_function_return_value(self):\n        result = await self.page.waitForFunction('() => 5')\n        self.assertEqual(await result.jsonValue(), 5)\n\n    @sync\n    async def test_wait_for_function_window(self):\n        self.assertTrue(await self.page.waitForFunction('() => window'))\n\n    @sync\n    async def test_wait_for_function_arg_element(self):\n        await self.page.setContent('<div></div>')\n        div = await self.page.J('div')\n        fut = asyncio.ensure_future(\n            self.page.waitForFunction('e => !e.parentElement', {}, div))\n        fut.add_done_callback(lambda _: self.set_result(True))\n        await asyncio.sleep(0.1)\n        self.assertFalse(self.result)\n        await self.page.evaluate('e => e.remove()', div)\n        await fut\n        self.assertTrue(self.result)\n\n    @sync\n    async def test_respect_timeout(self):\n        with self.assertRaises(TimeoutError) as cm:\n            await self.page.waitForFunction('false', {'timeout': 10})\n        self.assertIn(\n            'Waiting for function failed: timeout',\n            cm.exception.args[0],\n        )\n\n    @sync\n    async def test_disable_timeout(self):\n        watchdog = self.page.waitForFunction(\n            '''() => {\n                window.__counter = (window.__counter || 0) + 1;\n                return window.__injected;\n            }''',\n            timeout=0,\n            polling=10,\n        )\n        await self.page.waitForFunction('() => window.__counter > 10')\n        await self.page.evaluate('window.__injected = true')\n        await watchdog\n\n\nclass TestWaitForSelector(BaseTestCase):\n    @sync\n    async def test_wait_for_selector_immediate(self):\n        frame = self.page.mainFrame\n        result = []\n        fut = asyncio.ensure_future(frame.waitForSelector('*'))\n        fut.add_done_callback(lambda _: result.append(True))\n        await fut\n        self.assertTrue(result)\n\n        result.clear()\n        await frame.evaluate(addElement, 'div')\n        fut = asyncio.ensure_future(frame.waitForSelector('div'))\n        fut.add_done_callback(lambda _: result.append(True))\n        await fut\n        self.assertTrue(result)\n\n    @sync\n    async def test_wait_for_selector_after_node_appear(self):\n        frame = self.page.mainFrame\n\n        result = []\n        fut = asyncio.ensure_future(frame.waitForSelector('div'))\n        fut.add_done_callback(lambda _: result.append(True))\n        self.assertEqual(await frame.evaluate('() => 42'), 42)\n        await asyncio.sleep(0.1)\n        self.assertFalse(result)\n        await frame.evaluate(addElement, 'br')\n        await asyncio.sleep(0.1)\n        self.assertFalse(result)\n        await frame.evaluate(addElement, 'div')\n        await fut\n        self.assertTrue(result)\n\n    @sync\n    async def test_wait_for_selector_inner_html(self):\n        fut = asyncio.ensure_future(self.page.waitForSelector('h3 div'))\n        await self.page.evaluate(addElement, 'span')\n        await self.page.evaluate('() => document.querySelector(\"span\").innerHTML = \"<h3><div></div></h3>\"')  # noqa: E501\n        await fut\n\n    @sync\n    async def test_shortcut_for_main_frame(self):\n        await attachFrame(self.page, 'frame1', self.url + 'empty')\n        otherFrame = self.page.frames[1]\n        fut = asyncio.ensure_future(self.page.waitForSelector('div'))\n        fut.add_done_callback(lambda _: self.set_result(True))\n        await otherFrame.evaluate(addElement, 'div')\n        await asyncio.sleep(0.1)\n        self.assertFalse(self.result)\n        await self.page.evaluate(addElement, 'div')\n        await fut\n        self.assertTrue(self.result)\n\n    @sync\n    async def test_run_in_specified_frame(self):\n        await attachFrame(self.page, 'frame1', self.url + 'empty')\n        await attachFrame(self.page, 'frame2', self.url + 'empty')\n        frame1 = self.page.frames[1]\n        frame2 = self.page.frames[2]\n        fut = asyncio.ensure_future(frame2.waitForSelector('div'))\n        fut.add_done_callback(lambda _: self.set_result(True))\n        await frame1.evaluate(addElement, 'div')\n        await asyncio.sleep(0.1)\n        self.assertFalse(self.result)\n        await frame2.evaluate(addElement, 'div')\n        await fut\n        self.assertTrue(self.result)\n\n    @sync\n    async def test_wait_for_selector_fail(self):\n        await self.page.evaluate('() => document.querySelector = null')\n        with self.assertRaises(ElementHandleError):\n            await self.page.waitForSelector('*')\n\n    @sync\n    async def test_wait_for_page_navigation(self):\n        await self.page.goto(self.url + 'empty')\n        task = self.page.waitForSelector('h1')\n        await self.page.goto(self.url + '1')\n        await task\n\n    @sync\n    async def test_fail_page_closed(self):\n        page = await self.context.newPage()\n        await page.goto(self.url + 'empty')\n        task = page.waitForSelector('.box')\n        await page.close()\n        with self.assertRaises(NetworkError):\n            await task\n\n    @unittest.skip('Cannot catch error.')\n    @sync\n    async def test_fail_frame_detached(self):\n        await attachFrame(self.page, 'frame1', self.url + 'empty')\n        frame = self.page.frames[1]\n        fut = frame.waitForSelector('.box')\n        await detachFrame(self.page, 'frame1')\n        with self.assertRaises(Exception):\n            await fut\n\n    @sync\n    async def test_cross_process_navigation(self):\n        fut = asyncio.ensure_future(self.page.waitForSelector('h1'))\n        fut.add_done_callback(lambda _: self.set_result(True))\n        await self.page.goto(self.url + 'empty')\n        await asyncio.sleep(0.1)\n        self.assertFalse(self.result)\n        await self.page.reload()\n        await asyncio.sleep(0.1)\n        self.assertFalse(self.result)\n        await self.page.goto('http://127.0.0.1:{}/'.format(self.port))\n        await fut\n        self.assertTrue(self.result)\n\n    @sync\n    async def test_wait_for_selector_visible(self):\n        div = []\n        fut = asyncio.ensure_future(\n            self.page.waitForSelector('div', visible=True))\n        fut.add_done_callback(lambda _: div.append(True))\n        await self.page.setContent(\n            '<div style=\"display: none; visibility: hidden;\">1</div>'\n        )\n        await asyncio.sleep(0.1)\n        self.assertFalse(div)\n        await self.page.evaluate('() => document.querySelector(\"div\").style.removeProperty(\"display\")')  # noqa: E501\n        await asyncio.sleep(0.1)\n        self.assertFalse(div)\n        await self.page.evaluate('() => document.querySelector(\"div\").style.removeProperty(\"visibility\")')  # noqa: E501\n        await fut\n        self.assertTrue(div)\n\n    @sync\n    async def test_wait_for_selector_visible_inner(self):\n        div = []\n        fut = asyncio.ensure_future(\n            self.page.waitForSelector('div#inner', visible=True))\n        fut.add_done_callback(lambda _: div.append(True))\n        await self.page.setContent(\n            '<div style=\"display: none; visibility: hidden;\">'\n            '<div id=\"inner\">hi</div></div>'\n        )\n        await asyncio.sleep(0.1)\n        self.assertFalse(div)\n        await self.page.evaluate('() => document.querySelector(\"div\").style.removeProperty(\"display\")')  # noqa: E501\n        await asyncio.sleep(0.1)\n        self.assertFalse(div)\n        await self.page.evaluate('() => document.querySelector(\"div\").style.removeProperty(\"visibility\")')  # noqa: E501\n        await fut\n        self.assertTrue(div)\n\n    @sync\n    async def test_wait_for_selector_hidden(self):\n        div = []\n        await self.page.setContent('<div style=\"display: block;\"></div>')\n        fut = asyncio.ensure_future(\n            self.page.waitForSelector('div', hidden=True))\n        fut.add_done_callback(lambda _: div.append(True))\n        await asyncio.sleep(0.1)\n        self.assertFalse(div)\n        await self.page.evaluate('() => document.querySelector(\"div\").style.setProperty(\"visibility\", \"hidden\")')  # noqa: E501\n        await fut\n        self.assertTrue(div)\n\n    @sync\n    async def test_wait_for_selector_display_none(self):\n        div = []\n        await self.page.setContent('<div style=\"display: block;\"></div>')\n        fut = asyncio.ensure_future(\n            self.page.waitForSelector('div', hidden=True))\n        fut.add_done_callback(lambda _: div.append(True))\n        await asyncio.sleep(0.1)\n        self.assertFalse(div)\n        await self.page.evaluate('() => document.querySelector(\"div\").style.setProperty(\"display\", \"none\")')  # noqa: E501\n        await fut\n        self.assertTrue(div)\n\n    @sync\n    async def test_wait_for_selector_remove(self):\n        div = []\n        await self.page.setContent('<div></div>')\n        fut = asyncio.ensure_future(\n            self.page.waitForSelector('div', hidden=True))\n        fut.add_done_callback(lambda _: div.append(True))\n        await asyncio.sleep(0.1)\n        self.assertFalse(div)\n        await self.page.evaluate('() => document.querySelector(\"div\").remove()')  # noqa: E501\n        await fut\n        self.assertTrue(div)\n\n    @sync\n    async def test_wait_for_selector_timeout(self):\n        with self.assertRaises(TimeoutError) as cm:\n            await self.page.waitForSelector('div', timeout=10)\n        self.assertIn(\n            'Waiting for selector \"div\" failed: timeout',\n            cm.exception.args[0],\n        )\n\n    @sync\n    async def test_error_msg_wait_for_hidden(self):\n        await self.page.setContent('<div></div>')\n        with self.assertRaises(TimeoutError) as cm:\n            await self.page.waitForSelector('div', hidden=True, timeout=10)\n        self.assertIn(\n            'Waiting for selector \"div\" to be hidden failed: timeout',\n            cm.exception.args[0],\n        )\n\n    @sync\n    async def test_wait_for_selector_node_mutation(self):\n        div = []\n        fut = asyncio.ensure_future(self.page.waitForSelector('.cls'))\n        fut.add_done_callback(lambda _: div.append(True))\n        await self.page.setContent('<div class=\"noCls\"></div>')\n        self.assertFalse(div)\n        await self.page.evaluate(\n            '() => document.querySelector(\"div\").className=\"cls\"'\n        )\n        await asyncio.sleep(0.1)\n        self.assertTrue(div)\n\n    @sync\n    async def test_wait_for_selector_return_element(self):\n        selector = asyncio.ensure_future(self.page.waitForSelector('.zombo'))\n        await self.page.setContent('<div class=\"zombo\">anything</div>')\n        self.assertEqual(\n            await self.page.evaluate('e => e.textContent', await selector),\n            'anything',\n        )\n\n\nclass TestWaitForXPath(BaseTestCase):\n    @sync\n    async def test_fancy_xpath(self):\n        await self.page.setContent('<p>red herring</p><p>hello world  </p>')\n        waitForXPath = await self.page.waitForXPath('//p[normalize-space(.)=\"hello world\"]')  # noqa: E501\n        self.assertEqual(\n            await self.page.evaluate('x => x.textContent', waitForXPath),\n            'hello world  '\n        )\n\n    @sync\n    async def test_timeout(self):\n        with self.assertRaises(TimeoutError) as cm:\n            await self.page.waitForXPath('//div', timeout=10)\n        self.assertIn(\n            'Waiting for XPath \"//div\" failed: timeout',\n            cm.exception.args[0],\n        )\n\n    @sync\n    async def test_specified_frame(self):\n        await attachFrame(self.page, 'frame1', self.url + 'empty')\n        await attachFrame(self.page, 'frame2', self.url + 'empty')\n        frame1 = self.page.frames[1]\n        frame2 = self.page.frames[2]\n        fut = asyncio.ensure_future(frame2.waitForXPath('//div'))\n        fut.add_done_callback(lambda _: self.set_result(True))\n        self.assertFalse(self.result)\n        await frame1.evaluate(addElement, 'div')\n        self.assertFalse(self.result)\n        await frame2.evaluate(addElement, 'div')\n        self.assertTrue(self.result)\n\n    @sync\n    async def test_evaluation_failed(self):\n        await self.page.evaluateOnNewDocument(\n            'function() {document.evaluate = null;}')\n        await self.page.goto(self.url + 'empty')\n        with self.assertRaises(ElementHandleError):\n            await self.page.waitForXPath('*')\n\n    @unittest.skip('Cannot catch error')\n    @sync\n    async def test_frame_detached(self):\n        await self.page.goto(self.url + 'empty')\n        await attachFrame(self.page, 'frame1', self.url + 'empty')\n        frame = self.page.frames[1]\n        waitPromise = frame.waitForXPath('//*[@class=\"box\"]', timeout=1000)\n        await detachFrame(self.page, 'frame1')\n        with self.assertRaises(Exception):\n            await waitPromise\n\n    @sync\n    async def test_hidden(self):\n        await self.page.setContent('<div style=\"display: block;\"></div>')\n        waitForXPath = asyncio.ensure_future(\n            self.page.waitForXPath('//div', hidden=True))\n        waitForXPath.add_done_callback(lambda _: self.set_result(True))\n        await self.page.waitForXPath('//div')\n        self.assertFalse(self.result)\n        await self.page.evaluate('document.querySelector(\"div\").style.setProperty(\"display\", \"none\")')  # noqa: E501\n        self.assertTrue(await waitForXPath)\n        self.assertTrue(self.result)\n\n    @sync\n    async def test_return_element_handle(self):\n        waitForXPath = self.page.waitForXPath('//*[@class=\"zombo\"]')\n        await self.page.setContent('<div class=\"zombo\">anything</div>')\n        self.assertEqual(\n            await self.page.evaluate('x => x.textContent', await waitForXPath),\n            'anything'\n        )\n\n    @sync\n    async def test_text_node(self):\n        await self.page.setContent('<div>some text</dev>')\n        text = await self.page.waitForXPath('//div/text()')\n        self.assertEqual(\n            await (await text.getProperty('nodeType')).jsonValue(),\n            3  # Node.TEXT_NODE\n        )\n\n    @sync\n    async def test_single_slash(self):\n        await self.page.setContent('<div>some text</div>')\n        waitForXPath = self.page.waitForXPath('/html/body/div')\n        self.assertEqual(\n            await self.page.evaluate('x => x.textContent', await waitForXPath),\n            'some text',\n        )\n\n\nclass TestFrames(BaseTestCase):\n    @sync\n    async def test_frame_nested(self):\n        await self.page.goto(self.url + 'static/nested-frames.html')\n        dumped_frames = dumpFrames(self.page.mainFrame)\n        try:\n            self.assertEqual(\n                dumped_frames, '''\nhttp://localhost:{port}/static/nested-frames.html\n    http://localhost:{port}/static/two-frames.html\n        http://localhost:{port}/static/frame.html\n        http://localhost:{port}/static/frame.html\n    http://localhost:{port}/static/frame.html\n                '''.format(port=self.port).strip()\n            )\n        except AssertionError:\n            print('\\n== Nested frame test failed, which is unstable ==')\n            print(dumpFrames(self.page.mainFrame))\n\n    @sync\n    async def test_frame_events(self):\n        await self.page.goto(self.url + 'empty')\n        attachedFrames = []\n        self.page.on('frameattached', lambda f: attachedFrames.append(f))\n        await attachFrame(self.page, 'frame1', './static/frame.html')\n        self.assertEqual(len(attachedFrames), 1)\n        self.assertIn('static/frame.html', attachedFrames[0].url)\n\n        navigatedFrames = []\n        self.page.on('framenavigated', lambda f: navigatedFrames.append(f))\n        await navigateFrame(self.page, 'frame1', '/empty')\n        self.assertEqual(len(navigatedFrames), 1)\n        self.assertIn('empty', navigatedFrames[0].url)\n\n        detachedFrames = []\n        self.page.on('framedetached', lambda f: detachedFrames.append(f))\n        await detachFrame(self.page, 'frame1')\n        self.assertEqual(len(detachedFrames), 1)\n        self.assertTrue(detachedFrames[0].isDetached())\n\n    @sync\n    async def test_anchor_url(self):\n        await self.page.goto(self.url + 'empty')\n        await asyncio.wait([\n            self.page.goto(self.url + 'empty#foo'),\n            waitEvent(self.page, 'framenavigated'),\n        ])\n        self.assertEqual(self.page.url, self.url+'empty#foo')\n\n    @sync\n    async def test_frame_cross_process(self):\n        await self.page.goto(self.url + 'empty')\n        mainFrame = self.page.mainFrame\n        await self.page.goto('http://127.0.0.1:{}/empty'.format(self.port))\n        self.assertEqual(self.page.mainFrame, mainFrame)\n\n    @sync\n    async def test_frame_events_main(self):\n        # no attach/detach events should be emitted on main frame\n        events = []\n        navigatedFrames = []\n        self.page.on('frameattached', lambda f: events.append(f))\n        self.page.on('framedetached', lambda f: events.append(f))\n        self.page.on('framenavigated', lambda f: navigatedFrames.append(f))\n        await self.page.goto(self.url + 'empty')\n        self.assertFalse(events)\n        self.assertEqual(len(navigatedFrames), 1)\n\n    @sync\n    async def test_frame_events_child(self):\n        attachedFrames = []\n        detachedFrames = []\n        navigatedFrames = []\n        self.page.on('frameattached', lambda f: attachedFrames.append(f))\n        self.page.on('framedetached', lambda f: detachedFrames.append(f))\n        self.page.on('framenavigated', lambda f: navigatedFrames.append(f))\n        await self.page.goto(self.url + 'static/nested-frames.html')\n        self.assertEqual(len(attachedFrames), 4)\n        self.assertEqual(len(detachedFrames), 0)\n        self.assertEqual(len(navigatedFrames), 5)\n\n        attachedFrames.clear()\n        detachedFrames.clear()\n        navigatedFrames.clear()\n        await self.page.goto(self.url + 'empty')\n        self.assertEqual(len(attachedFrames), 0)\n        self.assertEqual(len(detachedFrames), 4)\n        self.assertEqual(len(navigatedFrames), 1)\n\n    @sync\n    async def test_frame_name(self):\n        await self.page.goto(self.url + 'empty')\n        await attachFrame(self.page, 'FrameId', self.url + 'empty')\n        await asyncio.sleep(0.1)\n        await self.page.evaluate(\n            '''(url) => {\n                const frame = document.createElement('iframe');\n                frame.name = 'FrameName';\n                frame.src = url;\n                document.body.appendChild(frame);\n                return new Promise(x => frame.onload = x);\n            }''', self.url + 'empty')\n        await asyncio.sleep(0.1)\n\n        frame1 = self.page.frames[0]\n        frame2 = self.page.frames[1]\n        frame3 = self.page.frames[2]\n        self.assertEqual(frame1.name, '')\n        self.assertEqual(frame2.name, 'FrameId')\n        self.assertEqual(frame3.name, 'FrameName')\n\n    @sync\n    async def test_frame_parent(self):\n        await self.page.goto(self.url + 'empty')\n        await attachFrame(self.page, 'frame1', self.url + 'empty')\n        await attachFrame(self.page, 'frame2', self.url + 'empty')\n        frame1 = self.page.frames[0]\n        frame2 = self.page.frames[1]\n        frame3 = self.page.frames[2]\n        self.assertEqual(frame1, self.page.mainFrame)\n        self.assertEqual(frame1.parentFrame, None)\n        self.assertEqual(frame2.parentFrame, frame1)\n        self.assertEqual(frame3.parentFrame, frame1)\n"
  },
  {
    "path": "tests/test_input.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport asyncio\nfrom pathlib import Path\nimport sys\nimport unittest\n\nfrom syncer import sync\n\nfrom pyppeteer.errors import PageError, PyppeteerError\n\nfrom .base import BaseTestCase\nfrom .frame_utils import attachFrame\n\n\nclass TestClick(BaseTestCase):\n    get_dimensions = '''\n        function () {\n            const rect = document.querySelector('textarea').getBoundingClientRect();\n            return {\n                x: rect.left,\n                y: rect.top,\n                width: rect.width,\n                height: rect.height\n            };\n        }'''  # noqa: E501\n\n    @sync\n    async def test_click(self):\n        await self.page.goto(self.url + 'static/button.html')\n        await self.page.click('button')\n        self.assertEqual(await self.page.evaluate('result'), 'Clicked')\n\n    @sync\n    async def test_click_with_disabled_javascript(self):\n        await self.page.setJavaScriptEnabled(False)\n        await self.page.goto(self.url + 'static/wrappedlink.html')\n        await asyncio.gather(\n            self.page.click('a'),\n            self.page.waitForNavigation(),\n        )\n        self.assertEqual(\n            self.page.url,\n            self.url + 'static/wrappedlink.html#clicked',\n        )\n\n    @sync\n    async def test_click_offscreen_button(self):\n        await self.page.goto(self.url + 'static/offscreenbuttons.html')\n        messages = []\n        self.page.on('console', lambda msg: messages.append(msg.text))\n        for i in range(11):\n            await self.page.evaluate('() => window.scrollTo(0, 0)')\n            await self.page.click('#btn{}'.format(i))\n        self.assertEqual(messages, [\n            'button #0 clicked',\n            'button #1 clicked',\n            'button #2 clicked',\n            'button #3 clicked',\n            'button #4 clicked',\n            'button #5 clicked',\n            'button #6 clicked',\n            'button #7 clicked',\n            'button #8 clicked',\n            'button #9 clicked',\n            'button #10 clicked',\n        ])\n\n    @sync\n    async def test_click_wrapped_links(self):\n        await self.page.goto(self.url + 'static/wrappedlink.html')\n        await asyncio.gather(\n            self.page.click('a'),\n            self.page.waitForNavigation(),\n        )\n        self.assertEqual(self.page.url,\n                         self.url + 'static/wrappedlink.html#clicked')\n\n    @sync\n    async def test_click_events(self):\n        await self.page.goto(self.url + 'static/checkbox.html')\n        self.assertIsNone(await self.page.evaluate('result.check'))\n        await self.page.click('input#agree')\n        self.assertTrue(await self.page.evaluate('result.check'))\n        events = await self.page.evaluate('result.events')\n        self.assertEqual(events, [\n            'mouseover',\n            'mouseenter',\n            'mousemove',\n            'mousedown',\n            'mouseup',\n            'click',\n            'input',\n            'change',\n        ])\n        await self.page.click('input#agree')\n        self.assertEqual(await self.page.evaluate('result.check'), False)\n\n    @sync\n    async def test_click_label(self):\n        await self.page.goto(self.url + 'static/checkbox.html')\n        self.assertIsNone(await self.page.evaluate('result.check'))\n        await self.page.click('label[for=\"agree\"]')\n        self.assertTrue(await self.page.evaluate('result.check'))\n        events = await self.page.evaluate('result.events')\n        self.assertEqual(events, [\n            'click',\n            'input',\n            'change',\n        ])\n        await self.page.click('label[for=\"agree\"]')\n        self.assertEqual(await self.page.evaluate('result.check'), False)\n\n    @sync\n    async def test_click_fail(self):\n        await self.page.goto(self.url + 'static/button.html')\n        with self.assertRaises(PageError) as cm:\n            await self.page.click('button.does-not-exist')\n        self.assertEqual(\n            'No node found for selector: button.does-not-exist',\n            cm.exception.args[0],\n        )\n\n    @sync\n    async def test_touch_enabled_viewport(self):\n        await self.page.setViewport({\n            'width': 375,\n            'height': 667,\n            'deviceScaleFactor': 2,\n            'isMobile': True,\n            'hasTouch': True,\n            'isLandscape': False,\n        })\n        await self.page.mouse.down()\n        await self.page.mouse.move(100, 10)\n        await self.page.mouse.up()\n\n    @sync\n    async def test_click_after_navigation(self):\n        await self.page.goto(self.url + 'static/button.html')\n        await self.page.click('button')\n        await self.page.goto(self.url + 'static/button.html')\n        await self.page.click('button')\n        self.assertEqual(await self.page.evaluate('result'), 'Clicked')\n\n    @sync\n    async def test_resize_textarea(self):\n        await self.page.goto(self.url + 'static/textarea.html')\n        dimensions = await self.page.evaluate(self.get_dimensions)\n        x = dimensions['x']\n        y = dimensions['y']\n        width = dimensions['width']\n        height = dimensions['height']\n        mouse = self.page.mouse\n        await mouse.move(x + width - 4, y + height - 4)\n        await mouse.down()\n        await mouse.move(x + width + 100, y + height + 100)\n        await mouse.up()\n        new_dimensions = await self.page.evaluate(self.get_dimensions)\n        self.assertEqual(new_dimensions['width'], width + 104)\n        self.assertEqual(new_dimensions['height'], height + 104)\n\n    @sync\n    async def test_scroll_and_click(self):\n        await self.page.goto(self.url + 'static/scrollable.html')\n        await self.page.click('#button-5')\n        self.assertEqual(await self.page.evaluate(\n            'document.querySelector(\"#button-5\").textContent'), 'clicked')\n        await self.page.click('#button-80')\n        self.assertEqual(await self.page.evaluate(\n            'document.querySelector(\"#button-80\").textContent'), 'clicked')\n\n    @sync\n    async def test_double_click(self):\n        await self.page.goto(self.url + 'static/button.html')\n        await self.page.evaluate('''() => {\n            window.double = false;\n            const button = document.querySelector('button');\n            button.addEventListener('dblclick', event => {\n                window.double = true;\n            });\n        }''')\n        button = await self.page.J('button')\n        await button.click(clickCount=2)\n        self.assertTrue(await self.page.evaluate('double'))\n        self.assertEqual(await self.page.evaluate('result'), 'Clicked')\n\n    @sync\n    async def test_click_partially_obscured_button(self):\n        await self.page.goto(self.url + 'static/button.html')\n        await self.page.evaluate('''() => {\n            const button = document.querySelector('button');\n            button.textContent = 'Some really long text that will go off screen';\n            button.style.position = 'absolute';\n            button.style.left = '368px';\n        }''')  # noqa: 501\n        await self.page.click('button')\n        self.assertEqual(await self.page.evaluate('result'), 'Clicked')\n\n    @sync\n    async def test_select_text_by_mouse(self):\n        await self.page.goto(self.url + 'static/textarea.html')\n        await self.page.focus('textarea')\n        text = 'This is the text that we are going to try to select. Let\\'s see how it goes.'  # noqa: E501\n        await self.page.keyboard.type(text)\n        await self.page.evaluate(\n            'document.querySelector(\"textarea\").scrollTop = 0')\n        dimensions = await self.page.evaluate(self.get_dimensions)\n        x = dimensions['x']\n        y = dimensions['y']\n        await self.page.mouse.move(x + 2, y + 2)\n        await self.page.mouse.down()\n        await self.page.mouse.move(100, 100)\n        await self.page.mouse.up()\n        self.assertEqual(\n            await self.page.evaluate('window.getSelection().toString()'), text)\n\n    @sync\n    async def test_select_text_by_triple_click(self):\n        await self.page.goto(self.url + 'static/textarea.html')\n        await self.page.focus('textarea')\n        text = 'This is the text that we are going to try to select. Let\\'s see how it goes.'  # noqa: E501\n        await self.page.keyboard.type(text)\n        await self.page.click('textarea')\n        await self.page.click('textarea', clickCount=2)\n        await self.page.click('textarea', clickCount=3)\n        self.assertEqual(\n            await self.page.evaluate('window.getSelection().toString()'), text)\n\n    @sync\n    async def test_trigger_hover(self):\n        await self.page.goto(self.url + 'static/scrollable.html')\n        await self.page.hover('#button-6')\n        self.assertEqual(await self.page.evaluate(\n            'document.querySelector(\"button:hover\").id'), 'button-6')\n        await self.page.hover('#button-2')\n        self.assertEqual(await self.page.evaluate(\n            'document.querySelector(\"button:hover\").id'), 'button-2')\n        await self.page.hover('#button-91')\n        self.assertEqual(await self.page.evaluate(\n            'document.querySelector(\"button:hover\").id'), 'button-91')\n\n    @sync\n    async def test_right_click(self):\n        await self.page.goto(self.url + 'static/scrollable.html')\n        await self.page.click('#button-8', button='right')\n        self.assertEqual(await self.page.evaluate(\n            'document.querySelector(\"#button-8\").textContent'), 'context menu')\n\n    @sync\n    async def test_click_with_modifier_key(self):\n        await self.page.goto(self.url + 'static/scrollable.html')\n        await self.page.evaluate('() => document.querySelector(\"#button-3\").addEventListener(\"mousedown\", e => window.lastEvent = e, true)')  # noqa: E501\n        modifiers = {\n            'Shift': 'shiftKey',\n            'Control': 'ctrlKey',\n            'Alt': 'altKey',\n            'Meta': 'metaKey',\n        }\n        for key, value in modifiers.items():\n            await self.page.keyboard.down(key)\n            await self.page.click('#button-3')\n            self.assertTrue(await self.page.evaluate(\n                'mod => window.lastEvent[mod]', value))\n            await self.page.keyboard.up(key)\n        await self.page.click('#button-3')\n        for key, value in modifiers.items():\n            self.assertFalse(await self.page.evaluate(\n                'mod => window.lastEvent[mod]', value))\n\n    @sync\n    async def test_click_link(self):\n        await self.page.setContent(\n            '<a href=\"{}\">empty.html</a>'.format(self.url + 'empty'))\n        await self.page.click('a')\n\n    @sync\n    async def test_mouse_movement(self):\n        await self.page.mouse.move(100, 100)\n        await self.page.evaluate('''() => {\n                window.result = [];\n                document.addEventListener('mousemove', event => {\n                    window.result.push([event.clientX, event.clientY]);\n                });\n            }''')\n        await self.page.mouse.move(200, 300, steps=5)\n        self.assertEqual(await self.page.evaluate('window.result'), [\n            [120, 140],\n            [140, 180],\n            [160, 220],\n            [180, 260],\n            [200, 300],\n        ])\n\n    @sync\n    async def test_tap_button(self):\n        await self.page.goto(self.url + 'static/button.html')\n        await self.page.tap('button')\n        self.assertEqual(await self.page.evaluate('result'), 'Clicked')\n\n    @unittest.skipIf(sys.version_info < (3, 6), 'Fails on 3.5')\n    @sync\n    async def test_touches_report(self):\n        await self.page.goto(self.url + 'static/touches.html')\n        button = await self.page.J('button')\n        await button.tap()\n        self.assertEqual(await self.page.evaluate('getResult()'),\n                         ['Touchstart: 0', 'Touchend: 0'])\n\n    @sync\n    async def test_click_insilde_frame(self):\n        await self.page.goto(self.url + 'empty')\n        await self.page.setContent(\n            '<div style=\"width:100px;height:100px;>spacer</div>\"')\n        await attachFrame(\n            self.page, 'button-test', self.url + 'static/button.html')\n        frame = self.page.frames[1]\n        button = await frame.J('button')\n        await button.click()\n        self.assertEqual(await frame.evaluate('result'), 'Clicked')\n\n    @sync\n    async def test_click_with_device_scale_factor(self):\n        await self.page.goto(self.url + 'empty')\n        await self.page.setViewport(\n            {'width': 400, 'height': 400, 'deviceScaleFactor': 5})\n        self.assertEqual(await self.page.evaluate('devicePixelRatio'), 5)\n        await self.page.setContent(\n            '<div style=\"width:100px;height:100px;>spacer</div>\"')\n        await attachFrame(\n            self.page, 'button-test', self.url + 'static/button.html')\n        frame = self.page.frames[1]\n        button = await frame.J('button')\n        await button.click()\n        self.assertEqual(await frame.evaluate('result'), 'Clicked')\n\n\nclass TestFileUpload(BaseTestCase):\n    @unittest.skipIf(\n        sys.platform.startswith('cyg') or sys.platform.startswith('msys'),\n        'Hangs on cygwin/msys',\n    )\n    @sync\n    async def test_file_upload(self):\n        await self.page.goto(self.url + 'static/fileupload.html')\n        filePath = Path(__file__).parent / 'file-to-upload.txt'\n        input = await self.page.J('input')\n        await input.uploadFile(str(filePath))\n        self.assertEqual(\n            await self.page.evaluate('e => e.files[0].name', input),\n            'file-to-upload.txt',\n        )\n        self.assertEqual(\n            await self.page.evaluate('''e => {\n                const reader = new FileReader();\n                const promise = new Promise(fulfill => reader.onload = fulfill);\n                reader.readAsText(e.files[0]);\n                return promise.then(() => reader.result);\n            }''', input),  # noqa: E501\n            'contents of the file\\n',\n        )\n\n\nclass TestType(BaseTestCase):\n    @sync\n    async def test_key_type(self):\n        await self.page.goto(self.url + 'static/textarea.html')\n        textarea = await self.page.J('textarea')\n        text = 'Type in this text!'\n        await textarea.type(text)\n        result = await self.page.evaluate(\n            '() => document.querySelector(\"textarea\").value'\n        )\n        self.assertEqual(result, text)\n        result = await self.page.evaluate('() => result')\n        self.assertEqual(result, text)\n\n    @sync\n    async def test_key_arrowkey(self):\n        await self.page.goto(self.url + 'static/textarea.html')\n        await self.page.type('textarea', 'Hello World!')\n        for _ in 'World!':\n            await self.page.keyboard.press('ArrowLeft')\n        await self.page.keyboard.type('inserted ')\n        result = await self.page.evaluate(\n            '() => document.querySelector(\"textarea\").value'\n        )\n        self.assertEqual(result, 'Hello inserted World!')\n\n        await self.page.keyboard.down('Shift')\n        for _ in 'inserted ':\n            await self.page.keyboard.press('ArrowLeft')\n        await self.page.keyboard.up('Shift')\n        await self.page.keyboard.press('Backspace')\n        result = await self.page.evaluate(\n            '() => document.querySelector(\"textarea\").value'\n        )\n        self.assertEqual(result, 'Hello World!')\n\n    @sync\n    async def test_key_press_element_handle(self):\n        await self.page.goto(self.url + 'static/textarea.html')\n        textarea = await self.page.J('textarea')\n        await textarea.press('a', text='f')\n        result = await self.page.evaluate(\n            '() => document.querySelector(\"textarea\").value'\n        )\n        self.assertEqual(result, 'f')\n\n        await self.page.evaluate(\n            '() => window.addEventListener(\"keydown\", e => e.preventDefault(), true)'  # noqa: E501\n        )\n        await textarea.press('a', text='y')\n        self.assertEqual(result, 'f')\n\n    @sync\n    async def test_key_send_char(self):\n        await self.page.goto(self.url + 'static/textarea.html')\n        await self.page.focus('textarea')\n        await self.page.keyboard.sendCharacter('朝')\n        result = await self.page.evaluate(\n            '() => document.querySelector(\"textarea\").value'\n        )\n        self.assertEqual(result, '朝')\n\n        await self.page.evaluate(\n            '() => window.addEventListener(\"keydown\", e => e.preventDefault(), true)'  # noqa: E501\n        )\n        await self.page.keyboard.sendCharacter('a')\n        result = await self.page.evaluate(\n            '() => document.querySelector(\"textarea\").value'\n        )\n        self.assertEqual(result, '朝a')\n\n    @sync\n    async def test_repeat_shift_key(self):\n        await self.page.goto(self.url + 'static/keyboard.html')\n        keyboard = self.page.keyboard\n        codeForKey = {'Shift': 16, 'Alt': 18, 'Meta': 91, 'Control': 17}\n        for key, code in codeForKey.items():\n            await keyboard.down(key)\n            self.assertEqual(\n                await self.page.evaluate('getResult()'),\n                'Keydown: {key} {key}Left {code} [{key}]'.format(\n                    key=key, code=code),\n            )\n            await keyboard.down('!')\n            if key == 'Shift':\n                self.assertEqual(\n                    await self.page.evaluate('getResult()'),\n                    'Keydown: ! Digit1 49 [{key}]\\n'\n                    'Keypress: ! Digit1 33 33 33 [{key}]'.format(key=key),\n                )\n            else:\n                self.assertEqual(\n                    await self.page.evaluate('getResult()'),\n                    'Keydown: ! Digit1 49 [{key}]'.format(key=key),\n                )\n            await keyboard.up('!')\n            self.assertEqual(\n                await self.page.evaluate('getResult()'),\n                'Keyup: ! Digit1 49 [{key}]'.format(key=key),\n            )\n            await keyboard.up(key)\n            self.assertEqual(\n                await self.page.evaluate('getResult()'),\n                'Keyup: {key} {key}Left {code} []'.format(key=key, code=code),\n            )\n\n    @sync\n    async def test_repeat_multiple_modifiers(self):\n        await self.page.goto(self.url + 'static/keyboard.html')\n        keyboard = self.page.keyboard\n        await keyboard.down('Control')\n        self.assertEqual(\n            await self.page.evaluate('getResult()'),\n            'Keydown: Control ControlLeft 17 [Control]',\n        )\n        await keyboard.down('Meta')\n        self.assertEqual(\n            await self.page.evaluate('getResult()'),\n            'Keydown: Meta MetaLeft 91 [Control Meta]',\n        )\n        await keyboard.down(';')\n        self.assertEqual(\n            await self.page.evaluate('getResult()'),\n            'Keydown: ; Semicolon 186 [Control Meta]',\n        )\n        await keyboard.up(';')\n        self.assertEqual(\n            await self.page.evaluate('getResult()'),\n            'Keyup: ; Semicolon 186 [Control Meta]',\n        )\n        await keyboard.up('Control')\n        self.assertEqual(\n            await self.page.evaluate('getResult()'),\n            'Keyup: Control ControlLeft 17 [Meta]',\n        )\n        await keyboard.up('Meta')\n        self.assertEqual(\n            await self.page.evaluate('getResult()'),\n            'Keyup: Meta MetaLeft 91 []',\n        )\n\n    @sync\n    async def test_send_proper_code_while_typing(self):\n        await self.page.goto(self.url + 'static/keyboard.html')\n        await self.page.keyboard.type('!')\n        self.assertEqual(\n            await self.page.evaluate('getResult()'),\n            'Keydown: ! Digit1 49 []\\n'\n            'Keypress: ! Digit1 33 33 33 []\\n'\n            'Keyup: ! Digit1 49 []'\n        )\n        await self.page.keyboard.type('^')\n        self.assertEqual(\n            await self.page.evaluate('getResult()'),\n            'Keydown: ^ Digit6 54 []\\n'\n            'Keypress: ^ Digit6 94 94 94 []\\n'\n            'Keyup: ^ Digit6 54 []'\n        )\n\n    @sync\n    async def test_send_proper_code_while_typing_with_shift(self):\n        await self.page.goto(self.url + 'static/keyboard.html')\n        await self.page.keyboard.down('Shift')\n        await self.page.keyboard.type('~')\n        self.assertEqual(\n            await self.page.evaluate('getResult()'),\n            'Keydown: Shift ShiftLeft 16 [Shift]\\n'\n            'Keydown: ~ Backquote 192 [Shift]\\n'\n            'Keypress: ~ Backquote 126 126 126 [Shift]\\n'\n            'Keyup: ~ Backquote 192 [Shift]'\n        )\n        await self.page.keyboard.up('Shift')\n\n    @sync\n    async def test_not_type_prevent_events(self):\n        await self.page.goto(self.url + 'static/textarea.html')\n        await self.page.focus('textarea')\n        await self.page.evaluate('''\nwindow.addEventListener('keydown', event => {\n    event.stopPropagation();\n    event.stopImmediatePropagation();\n    if (event.key === 'l')\n        event.preventDefault();\n    if (event.key === 'o')\n        Promise.resolve().then(() => event.preventDefault());\n}, false);''', force_expr=True)\n        await self.page.keyboard.type('Hello World!')\n        self.assertEqual(await self.page.evaluate('textarea.value'), 'He Wrd!')\n\n    @sync\n    async def test_key_modifiers(self):\n        keyboard = self.page.keyboard\n        self.assertEqual(keyboard._modifiers, 0)\n        await keyboard.down('Shift')\n        self.assertEqual(keyboard._modifiers, 8)\n        await keyboard.down('Alt')\n        self.assertEqual(keyboard._modifiers, 9)\n        await keyboard.up('Shift')\n        self.assertEqual(keyboard._modifiers, 1)\n        await keyboard.up('Alt')\n        self.assertEqual(keyboard._modifiers, 0)\n\n    @sync\n    async def test_repeat_properly(self):\n        await self.page.goto(self.url + 'static/textarea.html')\n        await self.page.focus('textarea')\n        await self.page.evaluate(\n            'document.querySelector(\"textarea\").addEventListener(\"keydown\",'\n            '    e => window.lastEvent = e, true)', force_expr=True,\n        )\n        await self.page.keyboard.down('a')\n        self.assertFalse(await self.page.evaluate('window.lastEvent.repeat'))\n        await self.page.keyboard.press('a')\n        self.assertTrue(await self.page.evaluate('window.lastEvent.repeat'))\n\n        await self.page.keyboard.down('b')\n        self.assertFalse(await self.page.evaluate('window.lastEvent.repeat'))\n        await self.page.keyboard.down('b')\n        self.assertTrue(await self.page.evaluate('window.lastEvent.repeat'))\n\n        await self.page.keyboard.up('a')\n        await self.page.keyboard.down('a')\n        self.assertFalse(await self.page.evaluate('window.lastEvent.repeat'))\n\n    @sync\n    async def test_key_type_long(self):\n        await self.page.goto(self.url + 'static/textarea.html')\n        textarea = await self.page.J('textarea')\n        text = 'This text is two lines.\\\\nThis is character 朝.'\n        await textarea.type(text)\n        result = await self.page.evaluate(\n            '() => document.querySelector(\"textarea\").value'\n        )\n        self.assertEqual(result, text)\n        result = await self.page.evaluate('() => result')\n        self.assertEqual(result, text)\n\n    @sync\n    async def test_key_location(self):\n        await self.page.goto(self.url + 'static/textarea.html')\n        textarea = await self.page.J('textarea')\n        await self.page.evaluate(\n            '() => window.addEventListener(\"keydown\", e => window.keyLocation = e.location, true)'  # noqa: E501\n        )\n\n        await textarea.press('Digit5')\n        self.assertEqual(await self.page.evaluate('keyLocation'), 0)\n\n        await textarea.press('ControlLeft')\n        self.assertEqual(await self.page.evaluate('keyLocation'), 1)\n\n        await textarea.press('ControlRight')\n        self.assertEqual(await self.page.evaluate('keyLocation'), 2)\n\n        await textarea.press('NumpadSubtract')\n        self.assertEqual(await self.page.evaluate('keyLocation'), 3)\n\n    @sync\n    async def test_key_unknown(self):\n        with self.assertRaises(PyppeteerError):\n            await self.page.keyboard.press('NotARealKey')\n        with self.assertRaises(PyppeteerError):\n            await self.page.keyboard.press('ё')\n        with self.assertRaises(PyppeteerError):\n            await self.page.keyboard.press('😊')\n\n    @sync\n    async def test_emoji(self):\n        await self.page.goto(self.url + 'static/textarea.html')\n        await self.page.type('textarea', '👹 Tokyo street Japan 🇯🇵')\n        self.assertEqual(\n            await self.page.Jeval('textarea', 'textarea => textarea.value'),\n            '👹 Tokyo street Japan 🇯🇵',\n        )\n\n    @sync\n    async def test_emoji_in_iframe(self):\n        await self.page.goto(self.url + 'empty')\n        await attachFrame(\n            self.page,\n            'emoji-test',\n            self.url + 'static/textarea.html',\n        )\n        frame = self.page.frames[1]\n        textarea = await frame.J('textarea')\n        await textarea.type('👹 Tokyo street Japan 🇯🇵')\n        self.assertEqual(\n            await frame.Jeval('textarea', 'textarea => textarea.value'),\n            '👹 Tokyo street Japan 🇯🇵',\n        )\n"
  },
  {
    "path": "tests/test_launcher.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport asyncio\nfrom copy import deepcopy\nimport glob\nimport logging\nimport os\nimport shutil\nimport subprocess\nimport sys\nimport tempfile\nimport time\nimport unittest\nfrom unittest import mock\n\nfrom syncer import sync\nimport websockets\n\nfrom pyppeteer import connect, launch, executablePath, defaultArgs\nfrom pyppeteer.chromium_downloader import chromium_executable, current_platform\nfrom pyppeteer.errors import NetworkError\nfrom pyppeteer.launcher import Launcher\nfrom pyppeteer.util import get_free_port\n\nfrom .base import DEFAULT_OPTIONS\nfrom .server import get_application\n\n\nclass TestLauncher(unittest.TestCase):\n    def setUp(self):\n        self.headless_options = [\n            '--headless',\n            '--hide-scrollbars',\n            '--mute-audio',\n        ]\n        if current_platform().startswith('win'):\n            self.headless_options.append('--disable-gpu')\n\n    def check_default_args(self, launcher):\n        for opt in self.headless_options:\n            self.assertIn(opt, launcher.chromeArguments)\n        self.assertTrue(any(opt for opt in launcher.chromeArguments\n                            if opt.startswith('--user-data-dir')))\n\n    def test_no_option(self):\n        launcher = Launcher()\n        self.check_default_args(launcher)\n        self.assertEqual(launcher.chromeExecutable, str(chromium_executable()))\n\n    def test_disable_headless(self):\n        launcher = Launcher({'headless': False})\n        for opt in self.headless_options:\n            self.assertNotIn(opt, launcher.chromeArguments)\n\n    def test_disable_default_args(self):\n        launcher = Launcher(ignoreDefaultArgs=True)\n        # check default args\n        self.assertNotIn('--no-first-run', launcher.chromeArguments)\n        # check automation args\n        self.assertNotIn('--enable-automation', launcher.chromeArguments)\n\n    def test_executable(self):\n        launcher = Launcher({'executablePath': '/path/to/chrome'})\n        self.assertEqual(launcher.chromeExecutable, '/path/to/chrome')\n\n    def test_args(self):\n        launcher = Launcher({'args': ['--some-args']})\n        self.check_default_args(launcher)\n        self.assertIn('--some-args', launcher.chromeArguments)\n\n    def test_filter_ignore_default_args(self):\n        _defaultArgs = defaultArgs()\n        options = deepcopy(DEFAULT_OPTIONS)\n        launcher = Launcher(\n            options,\n            # ignore first and third default arguments\n            ignoreDefaultArgs=[_defaultArgs[0], _defaultArgs[2]],\n        )\n        self.assertNotIn(_defaultArgs[0], launcher.cmd)\n        self.assertIn(_defaultArgs[1], launcher.cmd)\n        self.assertNotIn(_defaultArgs[2], launcher.cmd)\n\n    def test_user_data_dir(self):\n        launcher = Launcher({'args': ['--user-data-dir=/path/to/profile']})\n        self.check_default_args(launcher)\n        self.assertIn('--user-data-dir=/path/to/profile',\n                      launcher.chromeArguments)\n        self.assertIsNone(launcher.temporaryUserDataDir)\n\n    @sync\n    async def test_close_no_connection(self):\n        browser = await launch(args=['--no-sandbox'])\n        await browser.close()\n\n    @sync\n    async def test_launch(self):\n        browser = await launch(DEFAULT_OPTIONS)\n        await browser.newPage()\n        await browser.close()\n\n    @unittest.skip('should fix ignoreHTTPSErrors.')\n    @sync\n    async def test_ignore_https_errors(self):\n        browser = await launch(DEFAULT_OPTIONS, ignoreHTTPSErrors=True)\n        page = await browser.newPage()\n        port = get_free_port()\n        time.sleep(0.1)\n        app = get_application()\n        server = app.listen(port)\n        response = await page.goto('https://localhost:{}'.format(port))\n        self.assertTrue(response.ok)\n        await browser.close()\n        server.stop()\n\n    @sync\n    async def test_ignore_https_errors_interception(self):\n        browser = await launch(DEFAULT_OPTIONS, ignoreHTTPSErrors=True)\n        page = await browser.newPage()\n        await page.setRequestInterception(True)\n\n        async def check(req) -> None:\n            await req.continue_()\n\n        page.on('request', lambda req: asyncio.ensure_future(check(req)))\n        # TODO: should use user-signed cert\n        response = await page.goto('https://google.com/')\n        self.assertIsNotNone(response)\n        self.assertEqual(response.status, 200)\n\n    @sync\n    async def test_await_after_close(self):\n        browser = await launch(DEFAULT_OPTIONS)\n        page = await browser.newPage()\n        promise = page.evaluate('() => new Promise(r => {})')\n        await browser.close()\n        with self.assertRaises(NetworkError):\n            await promise\n\n    @sync\n    async def test_invalid_executable_path(self):\n        with self.assertRaises(FileNotFoundError):\n            await launch(DEFAULT_OPTIONS, executablePath='not-a-path')\n\n    @unittest.skipIf(sys.platform.startswith('win'), 'skip on windows')\n    def test_dumpio_default(self):\n        basedir = os.path.dirname(os.path.abspath(__file__))\n        path = os.path.join(basedir, 'dumpio.py')\n        proc = subprocess.run(\n            [sys.executable, path],\n            stdout=subprocess.PIPE,\n            stderr=subprocess.PIPE,\n        )\n        self.assertNotIn('DUMPIO_TEST', proc.stdout.decode())\n        self.assertNotIn('DUMPIO_TEST', proc.stderr.decode())\n\n    @unittest.skipIf(sys.platform.startswith('win'), 'skip on windows')\n    def test_dumpio_enable(self):\n        basedir = os.path.dirname(os.path.abspath(__file__))\n        path = os.path.join(basedir, 'dumpio.py')\n        proc = subprocess.run(\n            [sys.executable, path, '--dumpio'],\n            stdout=subprocess.PIPE,\n            stderr=subprocess.PIPE,\n        )\n        # console.log output is sent to stderr\n        self.assertNotIn('DUMPIO_TEST', proc.stdout.decode())\n        self.assertIn('DUMPIO_TEST', proc.stderr.decode())\n\n    @sync\n    async def test_default_viewport(self):\n        options = deepcopy(DEFAULT_OPTIONS)\n        options['defaultViewport'] = {\n            'width': 456,\n            'height': 789,\n        }\n        browser = await launch(options)\n        page = await browser.newPage()\n        self.assertEqual(await page.evaluate('window.innerWidth'), 456)\n        self.assertEqual(await page.evaluate('window.innerHeight'), 789)\n        await browser.close()\n\n    @sync\n    async def test_disable_default_viewport(self):\n        options = deepcopy(DEFAULT_OPTIONS)\n        options['defaultViewport'] = None\n        browser = await launch(options)\n        page = await browser.newPage()\n        self.assertIsNone(page.viewport)\n        await browser.close()\n\n\nclass TestDefaultURL(unittest.TestCase):\n    @sync\n    async def test_default_url(self):\n        browser = await launch(DEFAULT_OPTIONS)\n        pages = await browser.pages()\n        url_list = []\n        for page in pages:\n            url_list.append(page.url)\n        self.assertEqual(url_list, ['about:blank'])\n        await browser.close()\n\n    @unittest.skipIf('CI' in os.environ, 'Skip in-browser test on CI')\n    @sync\n    async def test_default_url_not_headless(self):\n        options = deepcopy(DEFAULT_OPTIONS)\n        options['headless'] = False\n        browser = await launch(options)\n        pages = await browser.pages()\n        url_list = []\n        for page in pages:\n            url_list.append(page.url)\n        self.assertEqual(url_list, ['about:blank'])\n        await browser.close()\n\n    @sync\n    async def test_custom_url(self):\n        customUrl = 'http://example.com/'\n        options = deepcopy(DEFAULT_OPTIONS)\n        options['args'].append(customUrl)\n        browser = await launch(options)\n        pages = await browser.pages()\n        self.assertEqual(len(pages), 1)\n        if pages[0].url != customUrl:\n            await pages[0].waitForNavigation()\n        self.assertEqual(pages[0].url, customUrl)\n        await browser.close()\n\n\nclass TestMixedContent(unittest.TestCase):\n    @unittest.skip('need server-side implementation')\n    @sync\n    async def test_mixed_content(self) -> None:\n        options = {'ignoreHTTPSErrors': True}\n        options.update(DEFAULT_OPTIONS)\n        browser = await launch(options)\n        page = await browser.newPage()\n        # page.goto()\n        await page.close()\n        await browser.close()\n\n\nclass TestLogLevel(unittest.TestCase):\n    def setUp(self):\n        self.logger = logging.getLogger('pyppeteer')\n        self.mock = mock.Mock()\n        self._orig_stderr = sys.stderr.write\n        sys.stderr.write = self.mock\n\n    def tearDown(self):\n        sys.stderr.write = self._orig_stderr\n        logging.getLogger('pyppeteer').setLevel(logging.NOTSET)\n\n    @sync\n    async def test_level_default(self):\n        browser = await launch(args=['--no-sandbox'])\n        await browser.close()\n\n        self.assertTrue(self.logger.isEnabledFor(logging.WARN))\n        self.assertFalse(self.logger.isEnabledFor(logging.INFO))\n        self.assertFalse(self.logger.isEnabledFor(logging.DEBUG))\n        self.mock.assert_not_called()\n\n    @unittest.skipIf(current_platform().startswith('win'), 'error on windows')\n    @sync\n    async def test_level_info(self):\n        browser = await launch(args=['--no-sandbox'], logLevel=logging.INFO)\n        await browser.close()\n\n        self.assertTrue(self.logger.isEnabledFor(logging.WARN))\n        self.assertTrue(self.logger.isEnabledFor(logging.INFO))\n        self.assertFalse(self.logger.isEnabledFor(logging.DEBUG))\n\n        self.assertIn('listening on', self.mock.call_args_list[0][0][0])\n\n    @unittest.skipIf(current_platform().startswith('win'), 'error on windows')\n    @sync\n    async def test_level_debug(self):\n        browser = await launch(args=['--no-sandbox'], logLevel=logging.DEBUG)\n        await browser.close()\n\n        self.assertTrue(self.logger.isEnabledFor(logging.WARN))\n        self.assertTrue(self.logger.isEnabledFor(logging.INFO))\n        self.assertTrue(self.logger.isEnabledFor(logging.DEBUG))\n\n        self.assertIn('listening on', self.mock.call_args_list[0][0][0])\n        if self.mock.call_args_list[1][0][0] == '\\n':\n            # python < 3.7.3\n            self.assertIn('SEND', self.mock.call_args_list[2][0][0])\n            self.assertIn('RECV', self.mock.call_args_list[4][0][0])\n        else:\n            self.assertIn('SEND', self.mock.call_args_list[1][0][0])\n            self.assertIn('RECV', self.mock.call_args_list[2][0][0])\n\n    @unittest.skipIf(current_platform().startswith('win'), 'error on windows')\n    @sync\n    async def test_connect_debug(self):\n        browser = await launch(args=['--no-sandbox'])\n        browser2 = await connect(\n            browserWSEndpoint=browser.wsEndpoint,\n            logLevel=logging.DEBUG,\n        )\n        page = await browser2.newPage()\n        await page.close()\n        await browser2.disconnect()\n        await browser.close()\n\n        self.assertTrue(self.logger.isEnabledFor(logging.WARN))\n        self.assertTrue(self.logger.isEnabledFor(logging.INFO))\n        self.assertTrue(self.logger.isEnabledFor(logging.DEBUG))\n\n        self.assertIn('SEND', self.mock.call_args_list[0][0][0])\n        self.assertIn('RECV', self.mock.call_args_list[2][0][0])\n\n\nclass TestUserDataDir(unittest.TestCase):\n    @classmethod\n    def setUpClass(cls):\n        cls.port = get_free_port()\n        time.sleep(0.1)\n        cls.app = get_application()\n        cls.server = cls.app.listen(cls.port)\n        cls.url = 'http://localhost:{}/'.format(cls.port)\n\n    def setUp(self):\n        self.datadir = tempfile.mkdtemp()\n\n    def tearDown(self):\n        if 'CI' not in os.environ:\n            for _ in range(100):\n                shutil.rmtree(self.datadir, ignore_errors=True)\n                if os.path.exists(self.datadir):\n                    time.sleep(0.01)\n                else:\n                    break\n            else:\n                raise IOError('Unable to remove Temporary User Data')\n\n    @classmethod\n    def tearDownClass(cls):\n        cls.server.stop()\n\n    @unittest.skipIf(sys.platform.startswith('cyg'), 'Fails on cygwin')\n    @sync\n    async def test_user_data_dir_option(self):\n        browser = await launch(DEFAULT_OPTIONS, userDataDir=self.datadir)\n        # Open a page to make sure its functional\n        await browser.newPage()\n        self.assertGreater(len(glob.glob(os.path.join(self.datadir, '**'))), 0)\n        await browser.close()\n        self.assertGreater(len(glob.glob(os.path.join(self.datadir, '**'))), 0)\n\n    @unittest.skipIf(sys.platform.startswith('cyg'), 'Fails on cygwin')\n    @sync\n    async def test_user_data_dir_args(self):\n        options = {}\n        options.update(DEFAULT_OPTIONS)\n        options['args'] = (options['args'] +\n                           ['--user-data-dir={}'.format(self.datadir)])\n        browser = await launch(options)\n        self.assertGreater(len(glob.glob(os.path.join(self.datadir, '**'))), 0)\n        await browser.close()\n        self.assertGreater(len(glob.glob(os.path.join(self.datadir, '**'))), 0)\n\n    @sync\n    async def test_user_data_dir_restore_state(self):\n        browser = await launch(DEFAULT_OPTIONS, userDataDir=self.datadir)\n        page = await browser.newPage()\n        await page.goto(self.url + 'empty')\n        await page.evaluate('() => localStorage.hey = \"hello\"')\n        await browser.close()\n\n        browser2 = await launch(DEFAULT_OPTIONS, userDataDir=self.datadir)\n        page2 = await browser2.newPage()\n        await page2.goto(self.url + 'empty')\n        result = await page2.evaluate('() => localStorage.hey')\n        await browser2.close()\n        self.assertEqual(result, 'hello')\n\n    @unittest.skipIf('CI' in os.environ, 'skip in-browser test on CI server')\n    @sync\n    async def test_user_data_dir_restore_cookie_in_browser(self):\n        browser = await launch(\n            DEFAULT_OPTIONS, userDataDir=self.datadir, headless=False)\n        page = await browser.newPage()\n        await page.goto(self.url + 'empty')\n        await page.evaluate('() => document.cookie = \"foo=true; expires=Fri, 31 Dec 9999 23:59:59 GMT\"')  # noqa: E501\n        await browser.close()\n\n        browser2 = await launch(DEFAULT_OPTIONS, userDataDir=self.datadir)\n        page2 = await browser2.newPage()\n        await page2.goto(self.url + 'empty')\n        result = await page2.evaluate('() => document.cookie')\n        await browser2.close()\n        self.assertEqual(result, 'foo=true')\n\n\nclass TestTargetEvents(unittest.TestCase):\n    @classmethod\n    def setUpClass(cls):\n        cls.port = get_free_port()\n        time.sleep(0.1)\n        cls.app = get_application()\n        cls.server = cls.app.listen(cls.port)\n        cls.url = 'http://localhost:{}/'.format(cls.port)\n\n    @classmethod\n    def tearDownClass(cls):\n        cls.server.stop()\n\n    @sync\n    async def test_target_events(self):\n        browser = await launch(DEFAULT_OPTIONS)\n        events = list()\n        browser.on('targetcreated', lambda _: events.append('CREATED'))\n        browser.on('targetchanged', lambda _: events.append('CHANGED'))\n        browser.on('targetdestroyed', lambda _: events.append('DESTROYED'))\n        page = await browser.newPage()\n        await page.goto(self.url + 'empty')\n        await page.close()\n        self.assertEqual(['CREATED', 'CHANGED', 'DESTROYED'], events)\n        await browser.close()\n\n\nclass TestClose(unittest.TestCase):\n    @sync\n    async def test_close(self):\n        curdir = os.path.dirname(os.path.abspath(__file__))\n        path = os.path.join(curdir, 'closeme.py')\n        proc = subprocess.run(\n            [sys.executable, path],\n            stdout=subprocess.PIPE,\n            stderr=subprocess.STDOUT,\n        )\n        self.assertEqual(proc.returncode, 0)\n        wsEndPoint = proc.stdout.decode()\n        # chrome should be already closed, so fail to connect websocket\n        with self.assertRaises(OSError):\n            await websockets.client.connect(wsEndPoint)\n\n\nclass TestEventLoop(unittest.TestCase):\n    def test_event_loop(self):\n        loop = asyncio.new_event_loop()\n\n        async def inner(_loop) -> None:\n            browser = await launch(args=['--no-sandbox'], loop=_loop)\n            page = await browser.newPage()\n            await page.goto('http://example.com')\n            result = await page.evaluate('() => 1 + 2')\n            self.assertEqual(result, 3)\n            await page.close()\n            await browser.close()\n\n        loop.run_until_complete(inner(loop))\n\n\nclass TestConnect(unittest.TestCase):\n    @sync\n    async def test_connect(self):\n        browser = await launch(DEFAULT_OPTIONS)\n        browser2 = await connect(browserWSEndpoint=browser.wsEndpoint)\n        page = await browser2.newPage()\n        self.assertEqual(await page.evaluate('() => 7 * 8'), 56)\n\n        await browser2.disconnect()\n        page2 = await browser.newPage()\n        self.assertEqual(await page2.evaluate('() => 7 * 6'), 42)\n        await browser.close()\n\n    @sync\n    async def test_reconnect(self):\n        browser = await launch(DEFAULT_OPTIONS)\n        browserWSEndpoint = browser.wsEndpoint\n        await browser.disconnect()\n\n        browser2 = await connect(browserWSEndpoint=browserWSEndpoint)\n        page = await browser2.newPage()\n        self.assertEqual(await page.evaluate('() => 7 * 8'), 56)\n        await browser.close()\n\n    @unittest.skip('This test hangs')\n    @sync\n    async def test_fail_to_connect_closed_chrome(self):\n        browser = await launch(DEFAULT_OPTIONS)\n        browserWSEndpoint = browser.wsEndpoint\n        await browser.close()\n        with self.assertRaises(Exception):\n            await connect(browserWSEndpoint=browserWSEndpoint)\n\n    @sync\n    async def test_executable_path(self):\n        self.assertTrue(os.path.exists(executablePath()))\n"
  },
  {
    "path": "tests/test_misc.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport logging\nimport unittest\n\nimport pyppeteer\nfrom pyppeteer.helper import debugError, get_positive_int\nfrom pyppeteer.page import convertPrintParameterToInches\n\n\nclass TestVersion(unittest.TestCase):\n    def test_version(self):\n        version = pyppeteer.version\n        self.assertTrue(isinstance(version, str))\n        self.assertEqual(version.count('.'), 2)\n\n\n\n\nclass TestDefaultArgs(unittest.TestCase):\n    def test_default_args(self):\n        self.assertIn('--no-first-run', pyppeteer.defaultArgs())\n        self.assertIn('--headless', pyppeteer.defaultArgs())\n        self.assertNotIn('--headless', pyppeteer.defaultArgs({'headless': False}))  # noqa: E501\n        self.assertIn('--user-data-dir=foo', pyppeteer.defaultArgs(userDataDir='foo'))  # noqa: E501\n\n\nclass TestToInches(unittest.TestCase):\n    def test_px(self):\n        self.assertEqual(\n            convertPrintParameterToInches('12px'),\n            12.0 / 96,\n        )\n\n    def test_inch(self):\n        self.assertAlmostEqual(\n            convertPrintParameterToInches('12in'),\n            12.0,\n        )\n\n    def test_cm(self):\n        self.assertAlmostEqual(\n            convertPrintParameterToInches('12cm'),\n            12.0 * 37.8 / 96,\n        )\n\n    def test_mm(self):\n        self.assertAlmostEqual(\n            convertPrintParameterToInches('12mm'),\n            12.0 * 3.78 / 96,\n        )\n\n\nclass TestPositiveInt(unittest.TestCase):\n    def test_badtype(self):\n        with self.assertRaises(TypeError):\n            get_positive_int({'a': 'b'}, 'a')\n\n    def test_negative_int(self):\n        with self.assertRaises(ValueError):\n            get_positive_int({'a': -1}, 'a')\n\n\nclass TestDebugError(unittest.TestCase):\n    def setUp(self):\n        self._old_debug = pyppeteer.DEBUG\n        self.logger = logging.getLogger('pyppeteer.test')\n\n    def tearDown(self):\n        pyppeteer.DEBUG = self._old_debug\n\n    def test_debug_default(self):\n        with self.assertLogs('pyppeteer.test', logging.DEBUG):\n            debugError(self.logger, 'test')\n        with self.assertRaises(AssertionError):\n            with self.assertLogs('pyppeteer', logging.INFO):\n                debugError(self.logger, 'test')\n\n    def test_debug_enabled(self):\n        pyppeteer.DEBUG = True\n        with self.assertLogs('pyppeteer.test', logging.ERROR):\n            debugError(self.logger, 'test')\n\n    def test_debug_enable_disable(self):\n        pyppeteer.DEBUG = True\n        with self.assertLogs('pyppeteer.test', logging.ERROR):\n            debugError(self.logger, 'test')\n        pyppeteer.DEBUG = False\n        with self.assertLogs('pyppeteer.test', logging.DEBUG):\n            debugError(self.logger, 'test')\n        with self.assertRaises(AssertionError):\n            with self.assertLogs('pyppeteer.test', logging.INFO):\n                debugError(self.logger, 'test')\n\n    def test_debug_logger(self):\n        with self.assertRaises(AssertionError):\n            with self.assertLogs('pyppeteer', logging.DEBUG):\n                debugError(logging.getLogger('test'), 'test message')\n"
  },
  {
    "path": "tests/test_network.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport asyncio\nfrom pathlib import Path\nimport sys\nimport unittest\n\nfrom syncer import sync\n\nfrom pyppeteer.errors import NetworkError, PageError\n\nfrom .base import BaseTestCase\n\n\nclass TestNetworkEvent(BaseTestCase):\n    @sync\n    async def test_request(self):\n        requests = []\n        self.page.on('request', lambda req: requests.append(req))\n        await self.page.goto(self.url + 'empty')\n        self.assertEqual(len(requests), 1)\n        req = requests[0]\n        self.assertEqual(req.url, self.url + 'empty')\n        self.assertEqual(req.resourceType, 'document')\n        self.assertEqual(req.method, 'GET')\n        self.assertTrue(req.response)\n        self.assertEqual(req.frame, self.page.mainFrame)\n        self.assertEqual(req.frame.url, self.url + 'empty')\n\n    @sync\n    async def test_request_post(self):\n        await self.page.goto(self.url + 'empty')\n\n        from tornado.web import RequestHandler\n\n        class PostHandler(RequestHandler):\n            def post(self):\n                self.write('')\n\n        self.app.add_handlers('localhost', [('/post', PostHandler)])\n        requests = []\n        self.page.on('request', lambda req: requests.append(req))\n        await self.page.evaluate('fetch(\"/post\", {method: \"POST\", body: JSON.stringify({foo: \"bar\"})})')  # noqa: E501\n        self.assertEqual(len(requests), 1)\n        req = requests[0]\n        self.assertTrue(req)\n        self.assertEqual(req.postData, '{\"foo\":\"bar\"}')\n\n    @sync\n    async def test_response(self):\n        responses = []\n        self.page.on('response', lambda res: responses.append(res))\n        await self.page.goto(self.url + 'empty')\n        self.assertEqual(len(responses), 1)\n        response = responses[0]\n        self.assertEqual(response.url, self.url + 'empty')\n        self.assertEqual(response.status, 200)\n        # self.assertTrue(response.ok)\n        self.assertFalse(response.fromCache)\n        self.assertFalse(response.fromServiceWorker)\n        self.assertTrue(response.request)\n        self.assertEqual(response.securityDetails, {})\n\n    @sync\n    async def test_response_https(self):\n        responses = []\n        self.page.on('response', lambda res: responses.append(res))\n        await self.page.goto('https://example.com/')\n        self.assertEqual(len(responses), 1)\n        response = responses[0]\n        self.assertEqual(response.url, 'https://example.com/')\n        self.assertEqual(response.status, 200)\n        self.assertTrue(response.ok)\n        self.assertFalse(response.fromCache)\n        self.assertFalse(response.fromServiceWorker)\n        self.assertTrue(response.request)\n        self.assertTrue(response.securityDetails)\n        self.assertEqual(response.securityDetails.protocol, 'TLS 1.2')\n\n    @sync\n    async def test_from_cache(self):\n        responses = {}\n\n        def set_response(resp):\n            basename = resp.url.split('/').pop()\n            responses[basename] = resp\n\n        self.page.on('response', set_response)\n\n        await self.page.goto(self.url + 'static/cached/one-style.html')\n        await self.page.reload()\n\n        self.assertEqual(len(responses), 2)\n        self.assertEqual(responses['one-style.html'].status, 304)\n        self.assertFalse(responses['one-style.html'].fromCache)\n        self.assertEqual(responses['one-style.css'].status, 200)\n        self.assertTrue(responses['one-style.css'].fromCache)\n\n    @sync\n    async def test_response_from_service_worker(self):\n        responses = {}\n\n        def set_response(resp):\n            basename = resp.url.split('/').pop()\n            responses[basename] = resp\n\n        self.page.on('response', set_response)\n\n        await self.page.goto(\n            self.url + 'static/serviceworkers/fetch/sw.html',\n            waitUntil='networkidle2',\n        )\n        await self.page.evaluate('async() => await window.activationPromise')\n        await self.page.reload()\n\n        self.assertEqual(len(responses), 2)\n        self.assertEqual(responses['sw.html'].status, 200)\n        self.assertTrue(responses['sw.html'].fromServiceWorker)\n        self.assertEqual(responses['style.css'].status, 200)\n        self.assertTrue(responses['style.css'].fromServiceWorker)\n\n    @unittest.skipIf(sys.platform.startswith('msys'), 'Fails on MSYS')\n    @sync\n    async def test_response_body(self):\n        responses = []\n        self.page.on('response', lambda res: responses.append(res))\n        await self.page.goto(self.url + 'static/simple.json')\n        self.assertEqual(len(responses), 1)\n        res = responses[0]\n        self.assertTrue(res)\n        self.assertEqual(await res.text(), '{\"foo\": \"bar\"}\\n')\n        self.assertEqual(await res.json(), {'foo': 'bar'})\n\n    @sync\n    async def test_fail_get_redirected_body(self):\n        response = await self.page.goto(self.url + 'redirect1')\n        redirectChain = response.request.redirectChain\n        self.assertEqual(len(redirectChain), 1)\n        redirected = redirectChain[0].response\n        self.assertEqual(redirected.status, 302)\n        with self.assertRaises(NetworkError) as cm:\n            await redirected.text()\n        self.assertIn(\n            'Response body is unavailable for redirect response',\n            cm.exception.args[0],\n        )\n\n    @unittest.skip('This test hangs')\n    @sync\n    async def test_not_report_body_unless_finished(self):\n        await self.page.goto(self.url + 'empty')\n        serverResponses = []\n\n        from tornado.web import RequestHandler\n\n        class GetHandler(RequestHandler):\n            def get(self):\n                serverResponses.append(self)\n                self.write('hello ')\n\n        self.app.add_handlers('localhost', [('/get', GetHandler)])\n        pageResponse = asyncio.get_event_loop().create_future()\n        finishedRequests = []\n        self.page.on('response', lambda res: pageResponse.set_result(res))\n        self.page.on('requestfinished', lambda: finishedRequests.append(True))\n\n        asyncio.ensure_future(\n            self.page.evaluate('fetch(\"./get\", {method: \"GET\"})'))\n        response = await pageResponse\n        self.assertTrue(serverResponses)\n        self.assertTrue(response)\n        self.assertEqual(response.status, 200)\n        self.assertFalse(finishedRequests)\n\n        responseText = response.text()\n        serverResponses[0].write('wor')\n        serverResponses[0].finish('ld!')\n        self.assertEqual(await responseText, 'hello world!')\n\n    @sync\n    async def test_request_failed(self):\n        await self.page.setRequestInterception(True)\n\n        async def interception(req):\n            if req.url.endswith('css'):\n                await req.abort()\n            else:\n                await req.continue_()\n\n        self.page.on(\n            'request', lambda req: asyncio.ensure_future(interception(req)))\n\n        failedRequests = []\n        self.page.on('requestfailed', lambda req: failedRequests.append(req))\n        await self.page.goto(self.url + 'static/one-style.html')\n        self.assertEqual(len(failedRequests), 1)\n        self.assertIn('one-style.css', failedRequests[0].url)\n        self.assertIsNone(failedRequests[0].response)\n        self.assertEqual(failedRequests[0].resourceType, 'stylesheet')\n        self.assertEqual(\n            failedRequests[0].failure()['errorText'], 'net::ERR_FAILED')\n        self.assertTrue(failedRequests[0].frame)\n\n    @sync\n    async def test_request_finished(self):\n        requests = []\n        self.page.on('requestfinished', lambda req: requests.append(req))\n        await self.page.goto(self.url + 'empty')\n\n        self.assertEqual(len(requests), 1)\n        req = requests[0]\n        self.assertEqual(req.url, self.url + 'empty')\n        self.assertTrue(req.response)\n        self.assertEqual(req.frame, self.page.mainFrame)\n        self.assertEqual(req.frame.url, self.url + 'empty')\n\n    @sync\n    async def test_events_order(self):\n        events = []\n        self.page.on('request', lambda req: events.append('request'))\n        self.page.on('response', lambda res: events.append('response'))\n        self.page.on(\n            'requestfinished', lambda req: events.append('requestfinished'))\n        await self.page.goto(self.url + 'empty')\n        self.assertEqual(events, ['request', 'response', 'requestfinished'])\n\n    @sync\n    async def test_redirects(self):\n        events = []\n        self.page.on('request', lambda req: events.append(\n            '{} {}'.format(req.method, req.url)))\n        self.page.on('response', lambda res: events.append(\n            '{} {}'.format(res.status, res.url)))\n        self.page.on('requestfinished', lambda req: events.append(\n            'DONE {}'.format(req.url)))\n        self.page.on('requestfailed', lambda req: events.append(\n            'FAIL {}'.format(req.url)))\n        response = await self.page.goto(self.url + 'redirect1')\n        self.assertEqual(events, [\n            'GET {}'.format(self.url + 'redirect1'),\n            '302 {}'.format(self.url + 'redirect1'),\n            'DONE {}'.format(self.url + 'redirect1'),\n            'GET {}'.format(self.url + 'redirect2'),\n            '200 {}'.format(self.url + 'redirect2'),\n            'DONE {}'.format(self.url + 'redirect2'),\n        ])\n\n        # check redirect chain\n        redirectChain = response.request.redirectChain\n        self.assertEqual(len(redirectChain), 1)\n        self.assertIn('redirect1', redirectChain[0].url)\n\n\nclass TestRequestInterception(BaseTestCase):\n    @sync\n    async def test_request_interception(self):\n        await self.page.setRequestInterception(True)\n\n        async def request_check(req):\n            self.assertIn('empty', req.url)\n            self.assertTrue(req.headers.get('user-agent'))\n            self.assertEqual(req.method, 'GET')\n            self.assertIsNone(req.postData)\n            self.assertTrue(req.isNavigationRequest())\n            self.assertEqual(req.resourceType, 'document')\n            self.assertEqual(req.frame, self.page.mainFrame)\n            self.assertEqual(req.frame.url, 'about:blank')\n            await req.continue_()\n\n        self.page.on('request',\n                     lambda req: asyncio.ensure_future(request_check(req)))\n        res = await self.page.goto(self.url + 'empty')\n        self.assertEqual(res.status, 200)\n\n    @sync\n    async def test_referer_header(self):\n        await self.page.setRequestInterception(True)\n        requests = list()\n\n        async def set_request(req):\n            requests.append(req)\n            await req.continue_()\n\n        self.page.on('request',\n                     lambda req: asyncio.ensure_future(set_request(req)))\n        await self.page.goto(self.url + 'static/one-style.html')\n        self.assertIn('/one-style.css', requests[1].url)\n        self.assertIn('/one-style.html', requests[1].headers['referer'])\n\n    @sync\n    async def test_response_with_cookie(self):\n        await self.page.goto(self.url + 'empty')\n        await self.page.setCookie({'name': 'foo', 'value': 'bar'})\n\n        await self.page.setRequestInterception(True)\n\n        async def continue_(req):\n            await req.continue_()\n\n        self.page.on('request', lambda r: asyncio.ensure_future(continue_(r)))\n        response = await self.page.reload()\n        self.assertEqual(response.status, 200)\n\n    @sync\n    async def test_request_interception_stop(self):\n        await self.page.setRequestInterception(True)\n        self.page.once('request',\n                       lambda req: asyncio.ensure_future(req.continue_()))\n        await self.page.goto(self.url + 'empty')\n        await self.page.setRequestInterception(False)\n        await self.page.goto(self.url + 'empty')\n\n    @sync\n    async def test_request_interception_custom_header(self):\n        await self.page.setExtraHTTPHeaders({'foo': 'bar'})\n        await self.page.setRequestInterception(True)\n\n        async def request_check(req):\n            self.assertEqual(req.headers['foo'], 'bar')\n            await req.continue_()\n\n        self.page.on('request',\n                     lambda req: asyncio.ensure_future(request_check(req)))\n        res = await self.page.goto(self.url + 'empty')\n        self.assertEqual(res.status, 200)\n\n    @sync\n    async def test_request_interception_custom_referer_header(self):\n        await self.page.goto(self.url + 'empty')\n        await self.page.setExtraHTTPHeaders({'referer': self.url + 'empty'})\n        await self.page.setRequestInterception(True)\n\n        async def request_check(req):\n            self.assertEqual(req.headers['referer'], self.url + 'empty')\n            await req.continue_()\n\n        self.page.on('request',\n                     lambda req: asyncio.ensure_future(request_check(req)))\n        res = await self.page.goto(self.url + 'empty')\n        self.assertEqual(res.status, 200)\n\n    @sync\n    async def test_request_interception_abort(self):\n        await self.page.setRequestInterception(True)\n\n        async def request_check(req):\n            if req.url.endswith('.css'):\n                await req.abort()\n            else:\n                await req.continue_()\n\n        failedRequests = []\n        self.page.on('request',\n                     lambda req: asyncio.ensure_future(request_check(req)))\n        self.page.on('requestfailed', lambda e: failedRequests.append(e))\n        res = await self.page.goto(self.url + 'static/one-style.html')\n        self.assertTrue(res.ok)\n        self.assertIsNone(res.request.failure())\n        self.assertEqual(len(failedRequests), 1)\n\n    @sync\n    async def test_request_interception_custom_error_code(self):\n        await self.page.setRequestInterception(True)\n\n        async def request_check(req):\n            await req.abort('internetdisconnected')\n\n        self.page.on('request',\n                     lambda req: asyncio.ensure_future(request_check(req)))\n        failedRequests = []\n        self.page.on('requestfailed', lambda req: failedRequests.append(req))\n        with self.assertRaises(PageError):\n            await self.page.goto(self.url + 'empty')\n        self.assertEqual(len(failedRequests), 1)\n        failedRequest = failedRequests[0]\n        self.assertEqual(\n            failedRequest.failure()['errorText'],\n            'net::ERR_INTERNET_DISCONNECTED',\n        )\n\n    @unittest.skip('Need server-side implementation')\n    @sync\n    async def test_request_interception_amend_http_header(self):\n        pass\n\n    @sync\n    async def test_request_interception_abort_main(self):\n        await self.page.setRequestInterception(True)\n\n        async def request_check(req):\n            await req.abort()\n\n        self.page.on('request',\n                     lambda req: asyncio.ensure_future(request_check(req)))\n        with self.assertRaises(PageError) as cm:\n            await self.page.goto(self.url + 'empty')\n        self.assertIn('net::ERR_FAILED', cm.exception.args[0])\n\n    @sync\n    async def test_request_interception_redirects(self):\n        await self.page.setRequestInterception(True)\n        requests = []\n\n        async def check(req):\n            await req.continue_()\n            requests.append(req)\n\n        self.page.on('request', lambda req: asyncio.ensure_future(check(req)))\n        response = await self.page.goto(self.url + 'redirect1')\n        self.assertEqual(response.status, 200)\n\n    @sync\n    async def test_redirect_for_subresource(self):\n        await self.page.setRequestInterception(True)\n        requests = list()\n\n        async def check(req):\n            await req.continue_()\n            requests.append(req)\n\n        self.page.on('request', lambda req: asyncio.ensure_future(check(req)))\n        response = await self.page.goto(self.url + 'one-style.html')\n        self.assertEqual(response.status, 200)\n        self.assertIn('one-style.html', response.url)\n        self.assertEqual(len(requests), 5)\n        self.assertEqual(requests[0].resourceType, 'document')\n        self.assertEqual(requests[1].resourceType, 'stylesheet')\n\n        # check redirect chain\n        redirectChain = requests[1].redirectChain\n        self.assertEqual(len(redirectChain), 3)\n        self.assertIn('/one-style.css', redirectChain[0].url)\n        self.assertIn('/three-style.css', redirectChain[2].url)\n\n    @unittest.skip('This test is not implemented')\n    @sync\n    async def test_request_interception_abort_redirects(self):\n        pass\n\n    @unittest.skip('This test is not implemented')\n    @sync\n    async def test_request_interception_equal_requests(self):\n        pass\n\n    @sync\n    async def test_request_interception_data_url(self):\n        await self.page.setRequestInterception(True)\n        requests = []\n\n        async def check(req):\n            requests.append(req)\n            await req.continue_()\n\n        self.page.on('request', lambda req: asyncio.ensure_future(check(req)))\n        dataURL = 'data:text/html,<div>yo</div>'\n        response = await self.page.goto(dataURL)\n        self.assertEqual(response.status, 200)\n        self.assertEqual(len(requests), 1)\n        self.assertEqual(requests[0].url, dataURL)\n\n    @sync\n    async def test_request_interception_abort_data_url(self):\n        await self.page.setRequestInterception(True)\n\n        async def request_check(req):\n            await req.abort()\n\n        self.page.on('request',\n                     lambda req: asyncio.ensure_future(request_check(req)))\n        with self.assertRaises(PageError) as cm:\n            await self.page.goto('data:text/html,No way!')\n        self.assertIn('net::ERR_FAILED', cm.exception.args[0])\n\n    @sync\n    async def test_request_interception_with_hash(self):\n        await self.page.setRequestInterception(True)\n        requests = []\n\n        async def check(req):\n            requests.append(req)\n            await req.continue_()\n\n        self.page.on('request', lambda req: asyncio.ensure_future(check(req)))\n        response = await self.page.goto(self.url + 'empty#hash')\n        self.assertEqual(response.status, 200)\n        self.assertEqual(response.url, self.url + 'empty')\n        self.assertEqual(len(requests), 1)\n        self.assertEqual(requests[0].url, self.url + 'empty')\n\n    @sync\n    async def test_request_interception_encoded_server(self):\n        await self.page.setRequestInterception(True)\n\n        async def check(req):\n            await req.continue_()\n\n        self.page.on('request', lambda req: asyncio.ensure_future(check(req)))\n        response = await self.page.goto(self.url + 'non existing page')\n        self.assertEqual(response.status, 404)\n\n    @unittest.skip('Need server-side implementation')\n    @sync\n    async def test_request_interception_badly_encoded_server(self):\n        pass\n\n    @unittest.skip('Need server-side implementation')\n    @sync\n    async def test_request_interception_encoded_server_2(self):\n        pass\n\n    @unittest.skip('This test is not implemented')\n    @sync\n    async def test_request_interception_invalid_interception_id(self):\n        pass\n\n    @sync\n    async def test_request_interception_disabled(self):\n        error = None\n\n        async def check(req):\n            try:\n                await req.continue_()\n            except Exception as e:\n                nonlocal error\n                error = e\n\n        self.page.on('request', lambda req: asyncio.ensure_future(check(req)))\n        await self.page.goto(self.url + 'empty')\n        self.assertIsNotNone(error)\n        self.assertIn('Request interception is not enabled', error.args[0])\n\n    @sync\n    async def test_request_interception_with_file_url(self):\n        await self.page.setRequestInterception(True)\n        urls = []\n\n        async def set_urls(req):\n            urls.append(req.url.split('/').pop())\n            await req.continue_()\n\n        self.page.on(\n            'request', lambda req: asyncio.ensure_future(set_urls(req)))\n\n        def pathToFileURL(path: Path):\n            pathName = str(path).replace('\\\\', '/')\n            if not pathName.startswith('/'):\n                pathName = '/{}'.format(pathName)\n            return 'file://{}'.format(pathName)\n\n        target = Path(__file__).parent / 'static' / 'one-style.html'\n        await self.page.goto(pathToFileURL(target))\n        self.assertEqual(len(urls), 2)\n        self.assertIn('one-style.html', urls)\n        self.assertIn('one-style.css', urls)\n\n    @sync\n    async def test_request_respond(self):\n        await self.page.setRequestInterception(True)\n\n        async def interception(req):\n            await req.respond({\n                'status': 201,\n                'headers': {'foo': 'bar'},\n                'body': 'intercepted',\n            })\n\n        self.page.on(\n            'request', lambda req: asyncio.ensure_future(interception(req)))\n        response = await self.page.goto(self.url + 'empty')\n        self.assertEqual(response.status, 201)\n        self.assertEqual(response.headers['foo'], 'bar')\n        body = await self.page.evaluate('() => document.body.textContent')\n        self.assertEqual(body, 'intercepted')\n\n    @unittest.skip('Sending binary object is not implemented')\n    @sync\n    async def test_request_respond_bytes(self):\n        pass\n\n\nclass TestNavigationRequest(BaseTestCase):\n    @sync\n    async def test_navigation_request(self):\n        requests = dict()\n\n        def set_request(req):\n            requests[req.url.split('/').pop()] = req\n\n        self.page.on('request', set_request)\n        await self.page.goto(self.url + 'redirect3')\n        self.assertTrue(requests['redirect3'].isNavigationRequest())\n        self.assertTrue(requests['one-frame.html'].isNavigationRequest())\n        self.assertTrue(requests['frame.html'].isNavigationRequest())\n        self.assertFalse(requests['script.js'].isNavigationRequest())\n        self.assertFalse(requests['style.css'].isNavigationRequest())\n\n    @sync\n    async def test_interception(self):\n        requests = dict()\n\n        async def on_request(req):\n            requests[req.url.split('/').pop()] = req\n            await req.continue_()\n\n        self.page.on('request',\n                     lambda req: asyncio.ensure_future(on_request(req)))\n\n        await self.page.setRequestInterception(True)\n        await self.page.goto(self.url + 'redirect3')\n        self.assertTrue(requests['redirect3'].isNavigationRequest())\n        self.assertTrue(requests['one-frame.html'].isNavigationRequest())\n        self.assertTrue(requests['frame.html'].isNavigationRequest())\n        self.assertFalse(requests['script.js'].isNavigationRequest())\n        self.assertFalse(requests['style.css'].isNavigationRequest())\n\n    @sync\n    async def test_image(self):\n        requests = []\n        self.page.on('request', lambda req: requests.append(req))\n        await self.page.goto(self.url + 'static/huge-image.png')\n        self.assertEqual(len(requests), 1)\n        self.assertTrue(requests[0].isNavigationRequest())\n"
  },
  {
    "path": "tests/test_page.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport asyncio\nimport math\nimport os\nfrom pathlib import Path\nimport sys\nimport time\nimport unittest\n\nfrom syncer import sync\n\nfrom pyppeteer.errors import ElementHandleError, NetworkError, PageError\nfrom pyppeteer.errors import TimeoutError\n\nfrom .base import BaseTestCase\nfrom .frame_utils import attachFrame\nfrom .utils import waitEvent\n\niPhone = {\n    'name': 'iPhone 6',\n    'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',  # noqa: E501\n    'viewport': {\n        'width': 375,\n        'height': 667,\n        'deviceScaleFactor': 2,\n        'isMobile': True,\n        'hasTouch': True,\n        'isLandscape': False,\n    }\n}\n\n\nclass TestEvaluate(BaseTestCase):\n    @sync\n    async def test_evaluate(self):\n        result = await self.page.evaluate('() => 7 * 3')\n        self.assertEqual(result, 21)\n\n    @sync\n    async def test_await_promise(self):\n        result = await self.page.evaluate('() => Promise.resolve(8 * 7)')\n        self.assertEqual(result, 56)\n\n    @sync\n    async def test_error_on_reload(self):\n        with self.assertRaises(Exception) as cm:\n            await self.page.evaluate('''() => {\n                location.reload();\n                return new Promise(resolve => {\n                    setTimeout(() => resolve(1), 0);\n                }\n        )}''')\n        self.assertIn('Protocol error', cm.exception.args[0])\n\n    @sync\n    async def test_after_framenavigation(self):\n        frameEvaluation = asyncio.get_event_loop().create_future()\n\n        async def evaluate_frame(frame):\n            frameEvaluation.set_result(await frame.evaluate('() => 6 * 7'))\n\n        self.page.on(\n            'framenavigated',\n            lambda frame: asyncio.ensure_future(evaluate_frame(frame)),\n        )\n        await self.page.goto(self.url + 'empty')\n        await frameEvaluation\n        self.assertEqual(frameEvaluation.result(), 42)\n\n    @unittest.skip('Pyppeteer does not support async for exposeFunction')\n    @sync\n    async def test_inside_expose_function(self):\n        async def callController(a, b):\n            result = await self.page.evaluate('(a, b) => a + b', a, b)\n            return result\n\n        await self.page.exposeFunction(\n            'callController',\n            lambda *args: asyncio.ensure_future(callController(*args))\n        )\n        result = await self.page.evaluate(\n            'async function() { return await callController(9, 3); }'\n        )\n        self.assertEqual(result, 27)\n\n    @sync\n    async def test_promise_reject(self):\n        with self.assertRaises(ElementHandleError) as cm:\n            await self.page.evaluate('() => not.existing.object.property')\n        self.assertIn('not is not defined', cm.exception.args[0])\n\n    @sync\n    async def test_string_as_error_message(self):\n        with self.assertRaises(Exception) as cm:\n            await self.page.evaluate('() => { throw \"qwerty\"; }')\n        self.assertIn('qwerty', cm.exception.args[0])\n\n    @sync\n    async def test_number_as_error_message(self):\n        with self.assertRaises(Exception) as cm:\n            await self.page.evaluate('() => { throw 100500; }')\n        self.assertIn('100500', cm.exception.args[0])\n\n    @sync\n    async def test_return_complex_object(self):\n        obj = {'foo': 'bar!'}\n        result = await self.page.evaluate('(a) => a', obj)\n        self.assertIsNot(result, obj)\n        self.assertEqual(result, obj)\n\n    @sync\n    async def test_return_nan(self):\n        result = await self.page.evaluate('() => NaN')\n        self.assertIsNone(result)\n\n    @sync\n    async def test_return_minus_zero(self):\n        result = await self.page.evaluate('() => -0')\n        self.assertEqual(result, -0)\n\n    @sync\n    async def test_return_infinity(self):\n        result = await self.page.evaluate('() => Infinity')\n        self.assertEqual(result, math.inf)\n\n    @sync\n    async def test_return_infinity_minus(self):\n        result = await self.page.evaluate('() => -Infinity')\n        self.assertEqual(result, -math.inf)\n\n    @sync\n    async def test_accept_none(self):\n        result = await self.page.evaluate(\n            '(a, b) => Object.is(a, null) && Object.is(b, \"foo\")',\n            None, 'foo',\n        )\n        self.assertTrue(result)\n\n    @sync\n    async def test_serialize_null_field(self):\n        result = await self.page.evaluate('() => ({a: undefined})')\n        self.assertEqual(result, {})\n\n    @sync\n    async def test_fail_window_object(self):\n        self.assertIsNone(await self.page.evaluate('() => window'))\n        self.assertIsNone(await self.page.evaluate('() => [Symbol(\"foo4\")]'))\n\n    @sync\n    async def test_fail_for_circular_object(self):\n        result = await self.page.evaluate('''() => {\n            const a = {};\n            const b = {a};\n            a.b = b;\n            return a;\n        }''')\n        self.assertIsNone(result)\n\n    @sync\n    async def test_accept_string(self):\n        result = await self.page.evaluate('1 + 2')\n        self.assertEqual(result, 3)\n\n    @sync\n    async def test_evaluate_force_expression(self):\n        result = await self.page.evaluate(\n            '() => null;\\n1 + 2;', force_expr=True)\n        self.assertEqual(result, 3)\n\n    @sync\n    async def test_accept_string_with_semicolon(self):\n        result = await self.page.evaluate('1 + 5;')\n        self.assertEqual(result, 6)\n\n    @sync\n    async def test_accept_string_with_comments(self):\n        result = await self.page.evaluate('2 + 5;\\n// do some math!')\n        self.assertEqual(result, 7)\n\n    @sync\n    async def test_element_handle_as_argument(self):\n        await self.page.setContent('<section>42</section>')\n        element = await self.page.J('section')\n        text = await self.page.evaluate('(e) => e.textContent', element)\n        self.assertEqual(text, '42')\n\n    @sync\n    async def test_element_handle_disposed(self):\n        await self.page.setContent('<section>39</section>')\n        element = await self.page.J('section')\n        self.assertTrue(element)\n        await element.dispose()\n        with self.assertRaises(ElementHandleError) as cm:\n            await self.page.evaluate('(e) => e.textContent', element)\n        self.assertIn('JSHandle is disposed', cm.exception.args[0])\n\n    @sync\n    async def test_element_handle_from_other_frame(self):\n        await attachFrame(self.page, 'frame1', self.url + 'empty')\n        body = await self.page.frames[1].J('body')\n        with self.assertRaises(ElementHandleError) as cm:\n            await self.page.evaluate('body => body.innerHTML', body)\n        self.assertIn(\n            'JSHandles can be evaluated only in the context they were created',\n            cm.exception.args[0],\n        )\n\n    @sync\n    async def test_object_handle_as_argument(self):\n        navigator = await self.page.evaluateHandle('() => navigator')\n        self.assertTrue(navigator)\n        text = await self.page.evaluate('(e) => e.userAgent', navigator)\n        self.assertIn('Mozilla', text)\n\n    @sync\n    async def test_object_handle_to_primitive_value(self):\n        aHandle = await self.page.evaluateHandle('() => 5')\n        isFive = await self.page.evaluate('(e) => Object.is(e, 5)', aHandle)\n        self.assertTrue(isFive)\n\n    @sync\n    async def test_simulate_user_gesture(self):\n        playAudio = '''function playAudio() {\n            const audio = document.createElement('audio');\n            audio.src = 'data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAA=';\n            return audio.play();\n        }'''  # noqa: E501\n        await self.page.evaluate(playAudio)\n        await self.page.evaluate('({})()'.format(playAudio), force_expr=True)\n\n    @sync\n    async def test_nice_error_after_navigation(self):\n        executionContext = await self.page.mainFrame.executionContext()\n\n        await asyncio.wait([\n            self.page.waitForNavigation(),\n            executionContext.evaluate('window.location.reload()'),\n        ])\n\n        with self.assertRaises(NetworkError) as cm:\n            await executionContext.evaluate('() => null')\n        self.assertIn('navigation', cm.exception.args[0])\n\n\nclass TestOfflineMode(BaseTestCase):\n    @sync\n    async def test_offline_mode(self):\n        await self.page.setOfflineMode(True)\n        with self.assertRaises(PageError):\n            await self.page.goto(self.url)\n        await self.page.setOfflineMode(False)\n        res = await self.page.reload()\n        self.assertEqual(res.status, 200)\n\n    @sync\n    async def test_emulate_navigator_offline(self):\n        self.assertTrue(await self.page.evaluate('window.navigator.onLine'))\n        await self.page.setOfflineMode(True)\n        self.assertFalse(await self.page.evaluate('window.navigator.onLine'))\n        await self.page.setOfflineMode(False)\n        self.assertTrue(await self.page.evaluate('window.navigator.onLine'))\n\n\nclass TestEvaluateHandle(BaseTestCase):\n    @sync\n    async def test_evaluate_handle(self):\n        windowHandle = await self.page.evaluateHandle('() => window')\n        self.assertTrue(windowHandle)\n\n\nclass TestWaitFor(BaseTestCase):\n    @sync\n    async def test_wait_for_selector(self):\n        fut = asyncio.ensure_future(self.page.waitFor('div'))\n        fut.add_done_callback(lambda f: self.set_result(True))\n        await self.page.goto(self.url + 'empty')\n        self.assertFalse(self.result)\n        await self.page.goto(self.url + 'static/grid.html')\n        await fut\n        self.assertTrue(self.result)\n\n    @sync\n    async def test_wait_for_xpath(self):\n        waitFor = asyncio.ensure_future(self.page.waitFor('//div'))\n        waitFor.add_done_callback(lambda fut: self.set_result(True))\n        await self.page.goto(self.url + 'empty')\n        self.assertFalse(self.result)\n        await self.page.goto(self.url + 'static/grid.html')\n        await waitFor\n        self.assertTrue(self.result)\n\n    @sync\n    async def test_single_slash_fail(self):\n        await self.page.setContent('<div>some text</div>')\n        with self.assertRaises(Exception):\n            await self.page.waitFor('/html/body/div')\n\n    @sync\n    async def test_wait_for_timeout(self):\n        start_time = time.perf_counter()\n        fut = asyncio.ensure_future(self.page.waitFor(100))\n        fut.add_done_callback(lambda f: self.set_result(True))\n        await fut\n        self.assertGreater(time.perf_counter() - start_time, 0.1)\n        self.assertTrue(self.result)\n\n    @sync\n    async def test_wait_for_error_type(self):\n        with self.assertRaises(TypeError) as cm:\n            await self.page.waitFor({'a': 1})\n        self.assertIn('Unsupported target type', cm.exception.args[0])\n\n    @sync\n    async def test_wait_for_func_with_args(self):\n        await self.page.waitFor('(arg1, arg2) => arg1 !== arg2', {}, 1, 2)\n\n\nclass TestConsole(BaseTestCase):\n    @sync\n    async def test_console_event(self):\n        messages = []\n        self.page.once('console', lambda m: messages.append(m))\n        await self.page.evaluate('() => console.log(\"hello\", 5, {foo: \"bar\"})')\n        await asyncio.sleep(0.01)\n        self.assertEqual(len(messages), 1)\n\n        msg = messages[0]\n        self.assertEqual(msg.type, 'log')\n        self.assertEqual(msg.text, 'hello 5 JSHandle@object')\n        self.assertEqual(await msg.args[0].jsonValue(), 'hello')\n        self.assertEqual(await msg.args[1].jsonValue(), 5)\n        self.assertEqual(await msg.args[2].jsonValue(), {'foo': 'bar'})\n\n    @sync\n    async def test_console_event_many(self):\n        messages = []\n        self.page.on('console', lambda m: messages.append(m))\n        await self.page.evaluate('''\n// A pair of time/timeEnd generates only one Console API call.\nconsole.time('calling console.time');\nconsole.timeEnd('calling console.time');\nconsole.trace('calling console.trace');\nconsole.dir('calling console.dir');\nconsole.warn('calling console.warn');\nconsole.error('calling console.error');\nconsole.log(Promise.resolve('should not wait until resolved!'));\n        ''')\n        await asyncio.sleep(0.1)\n        self.assertEqual(\n            [msg.type for msg in messages],\n            ['timeEnd', 'trace', 'dir', 'warning', 'error', 'log'],\n        )\n        self.assertIn('calling console.time', messages[0].text)\n        self.assertEqual([msg.text for msg in messages[1:]], [\n            'calling console.trace',\n            'calling console.dir',\n            'calling console.warn',\n            'calling console.error',\n            'JSHandle@promise',\n        ])\n\n    @sync\n    async def test_console_window(self):\n        messages = []\n        self.page.once('console', lambda m: messages.append(m))\n        await self.page.evaluate('console.error(window);')\n        await asyncio.sleep(0.1)\n        self.assertEqual(len(messages), 1)\n        msg = messages[0]\n        self.assertEqual(msg.text, 'JSHandle@object')\n\n    @sync\n    async def test_trigger_correct_log(self):\n        await self.page.goto('about:blank')\n        messages = []\n        self.page.on('console', lambda m: messages.append(m))\n        asyncio.ensure_future(self.page.evaluate(\n            'async url => fetch(url).catch(e => {})', self.url + 'empty'))\n        await waitEvent(self.page, 'console')\n        self.assertEqual(len(messages), 1)\n        message = messages[0]\n        self.assertIn('No \\'Access-Control-Allow-Origin\\'', message.text)\n        self.assertEqual(message.type, 'error')\n\n\nclass TestDOMContentLoaded(BaseTestCase):\n    @sync\n    async def test_fired(self):\n        self.page.once('domcontentloaded', self.set_result(True))\n        self.assertTrue(self.result)\n\n\nclass TestMetrics(BaseTestCase):\n    def checkMetrics(self, metrics):\n        metrics_to_check = {\n            'Timestamp',\n            'Documents',\n            'Frames',\n            'JSEventListeners',\n            'Nodes',\n            'LayoutCount',\n            'RecalcStyleCount',\n            'LayoutDuration',\n            'RecalcStyleDuration',\n            'ScriptDuration',\n            'TaskDuration',\n            'JSHeapUsedSize',\n            'JSHeapTotalSize',\n        }\n        for name, value in metrics.items():\n            self.assertTrue(name in metrics_to_check)\n            self.assertTrue(value >= 0)\n            metrics_to_check.remove(name)\n        self.assertEqual(len(metrics_to_check), 0)\n\n    @sync\n    async def test_metrics(self):\n        await self.page.goto('about:blank')\n        metrics = await self.page.metrics()\n        self.checkMetrics(metrics)\n\n    @sync\n    async def test_metrics_event(self):\n        fut = asyncio.get_event_loop().create_future()\n        self.page.on('metrics', lambda metrics: fut.set_result(metrics))\n        await self.page.evaluate('() => console.timeStamp(\"test42\")')\n        metrics = await fut\n        self.assertEqual(metrics['title'], 'test42')\n        self.checkMetrics(metrics['metrics'])\n\n\nclass TestGoto(BaseTestCase):\n    @sync\n    async def test_get_http(self):\n        response = await self.page.goto('http://example.com/')\n        self.assertEqual(response.status, 200)\n        self.assertEqual(self.page.url, 'http://example.com/')\n\n    @sync\n    async def test_goto_blank(self):\n        response = await self.page.goto('about:blank')\n        self.assertIsNone(response)\n\n    @sync\n    async def test_response_when_page_changes_url(self):\n        response = await self.page.goto(self.url + 'static/historyapi.html')\n        self.assertTrue(response)\n        self.assertEqual(response.status, 200)\n\n    @sync\n    async def test_goto_subframe_204(self):\n        await self.page.goto(self.url + 'static/frame-204.html')\n\n    @sync\n    async def test_goto_fail_204(self):\n        with self.assertRaises(PageError) as cm:\n            await self.page.goto('http://httpstat.us/204')\n        self.assertIn('net::ERR_ABORTED', cm.exception.args[0])\n\n    @sync\n    async def test_goto_documentloaded(self):\n        import logging\n        with self.assertLogs('pyppeteer', logging.WARNING):\n            response = await self.page.goto(\n                self.url + 'empty', waitUntil='documentloaded')\n        self.assertEqual(response.status, 200)\n\n    @sync\n    async def test_goto_domcontentloaded(self):\n        response = await self.page.goto(self.url + 'empty',\n                                        waitUntil='domcontentloaded')\n        self.assertEqual(response.status, 200)\n\n    @unittest.skip('This test should be fixed')\n    @sync\n    async def test_goto_history_api_beforeunload(self):\n        await self.page.goto(self.url + 'empty')\n        await self.page.evaluate('''() => {\n            window.addEventListener(\n                'beforeunload',\n                () => history.replaceState(null, 'initial', window.location.href),\n                false,\n            );\n        }''')  # noqa: E501\n        response = await self.page.goto(self.url + 'static/grid.html')\n        self.assertTrue(response)\n        self.assertEqual(response.status, 200)\n\n    @sync\n    async def test_goto_networkidle(self):\n        with self.assertRaises(ValueError):\n            await self.page.goto(self.url + 'empty', waitUntil='networkidle')\n\n    @sync\n    async def test_nav_networkidle0(self):\n        response = await self.page.goto(self.url + 'empty',\n                                        waitUntil='networkidle0')\n        self.assertEqual(response.status, 200)\n\n    @sync\n    async def test_nav_networkidle2(self):\n        response = await self.page.goto(self.url + 'empty',\n                                        waitUntil='networkidle2')\n        self.assertEqual(response.status, 200)\n\n    @sync\n    async def test_goto_bad_url(self):\n        with self.assertRaises(NetworkError):\n            await self.page.goto('asdf')\n\n    @sync\n    async def test_goto_bad_resource(self):\n        with self.assertRaises(PageError):\n            await self.page.goto('http://localhost:44123/non-existing-url')\n\n    @sync\n    async def test_timeout(self):\n        with self.assertRaises(TimeoutError):\n            await self.page.goto(self.url + 'long', timeout=1)\n\n    @sync\n    async def test_timeout_default(self):\n        self.page.setDefaultNavigationTimeout(1)\n        with self.assertRaises(TimeoutError):\n            await self.page.goto(self.url + 'long')\n\n    @sync\n    async def test_no_timeout(self):\n        await self.page.goto(self.url + 'long', timeout=0)\n\n    @sync\n    async def test_valid_url(self):\n        response = await self.page.goto(self.url + 'empty')\n        self.assertEqual(response.status, 200)\n\n    @sync\n    async def test_data_url(self):\n        response = await self.page.goto('data:text/html,hello')\n        self.assertTrue(response.ok)\n\n    @sync\n    async def test_404(self):\n        response = await self.page.goto(self.url + '/not-found')\n        self.assertFalse(response.ok)\n        self.assertEqual(response.status, 404)\n\n    @sync\n    async def test_redirect(self):\n        response = await self.page.goto(self.url + 'redirect1')\n        self.assertTrue(response.ok)\n        self.assertEqual(response.url, self.url + 'redirect2')\n\n    @unittest.skip('This test is not implemented')\n    @sync\n    async def test_wait_for_network_idle(self):\n        pass\n\n    @sync\n    async def test_data_url_request(self):\n        requests = []\n        self.page.on('request', lambda req: requests.append(req))\n        dataURL = 'data:text/html,<div>yo</div>'\n        response = await self.page.goto(dataURL)\n        self.assertTrue(response.ok)\n        self.assertEqual(response.status, 200)\n        self.assertEqual(len(requests), 1)\n        self.assertEqual(requests[0].url, dataURL)\n\n    @sync\n    async def test_url_with_hash(self):\n        requests = []\n        self.page.on('request', lambda req: requests.append(req))\n        response = await self.page.goto(self.url + 'empty#hash')\n        self.assertEqual(response.status, 200)\n        self.assertEqual(response.url, self.url + 'empty')\n        self.assertEqual(len(requests), 1)\n        self.assertEqual(requests[0].url, self.url + 'empty')\n\n    @sync\n    async def test_self_request_page(self):\n        response = await self.page.goto(self.url + 'static/self-request.html')\n        self.assertEqual(response.status, 200)\n        self.assertIn('self-request.html', response.url)\n\n    @sync\n    async def test_show_url_in_error_message(self):\n        dummy_port = 9000 if '9000' not in self.url else 9001\n        url = 'http://localhost:{}/test/1.html'.format(dummy_port)\n        with self.assertRaises(PageError) as cm:\n            await self.page.goto(url)\n        self.assertIn(url, cm.exception.args[0])\n\n\nclass TestWaitForNavigation(BaseTestCase):\n    @sync\n    async def test_wait_for_navigatoin(self):\n        await self.page.goto(self.url + 'empty')\n        results = await asyncio.gather(\n            self.page.waitForNavigation(),\n            self.page.evaluate('(url) => window.location.href = url', self.url)\n        )\n        response = results[0]\n        self.assertEqual(response.status, 200)\n        self.assertEqual(response.url, self.url)\n\n    @unittest.skip('Need server-side implementation')\n    @sync\n    async def test_both_domcontentloaded_loaded(self):\n        pass\n\n    @sync\n    async def test_click_anchor_link(self):\n        await self.page.goto(self.url + 'empty')\n        await self.page.setContent('<a href=\"#foobar\">foobar</a>')\n        results = await asyncio.gather(\n            self.page.waitForNavigation(),\n            self.page.click('a'),\n        )\n        self.assertIsNone(results[0])\n        self.assertEqual(self.page.url, self.url + 'empty#foobar')\n\n    @sync\n    async def test_return_nevigated_response_reload(self):\n        await self.page.goto(self.url + 'empty')\n        navPromise = asyncio.ensure_future(self.page.waitForNavigation())\n        await self.page.reload()\n        response = await navPromise\n        self.assertEqual(response.url, self.url + 'empty')\n\n    @sync\n    async def test_history_push_state(self):\n        await self.page.goto(self.url + 'empty')\n        await self.page.setContent('''\n            <a onclick='javascript:pushState()'>SPA</a>\n            <script>\n                function pushState() { history.pushState({}, '', 'wow.html') }\n            </script>\n        ''')\n        results = await asyncio.gather(\n            self.page.waitForNavigation(),\n            self.page.click('a'),\n        )\n        self.assertIsNone(results[0])\n        self.assertEqual(self.page.url, self.url + 'wow.html')\n\n    @sync\n    async def test_history_replace_state(self):\n        await self.page.goto(self.url + 'empty')\n        await self.page.setContent('''\n            <a onclick='javascript:replaceState()'>SPA</a>\n            <script>\n                function replaceState() {\n                    history.replaceState({}, '', 'replaced.html');\n                }\n            </script>\n        ''')\n        results = await asyncio.gather(\n            self.page.waitForNavigation(),\n            self.page.click('a'),\n        )\n        self.assertIsNone(results[0])\n        self.assertEqual(self.page.url, self.url + 'replaced.html')\n\n    @sync\n    async def test_dom_history_back_forward(self):\n        await self.page.goto(self.url + 'empty')\n        await self.page.setContent('''\n            <a id=\"back\" onclick='javascript:goBack()'>back</a>\n            <a id=\"forward\" onclick='javascript:goForward()'>forward</a>\n            <script>\n                function goBack() { history.back(); }\n                function goForward() { history.forward(); }\n                history.pushState({}, '', '/first.html');\n                history.pushState({}, '', '/second.html');\n            </script>\n        ''')\n        self.assertEqual(self.page.url, self.url + 'second.html')\n        results_back = await asyncio.gather(\n            self.page.waitForNavigation(),\n            self.page.click('a#back'),\n        )\n        self.assertIsNone(results_back[0])\n        self.assertEqual(self.page.url, self.url + 'first.html')\n\n        results_forward = await asyncio.gather(\n            self.page.waitForNavigation(),\n            self.page.click('a#forward'),\n        )\n        self.assertIsNone(results_forward[0])\n        self.assertEqual(self.page.url, self.url + 'second.html')\n\n    @sync\n    async def test_subframe_issues(self):\n        navigationPromise = asyncio.ensure_future(\n            self.page.goto(self.url + 'static/one-frame.html'))\n        frame = await waitEvent(self.page, 'frameattached')\n        fut = asyncio.get_event_loop().create_future()\n\n        def is_same_frame(f):\n            if f == frame:\n                fut.set_result(True)\n\n        self.page.on('framenavigated', is_same_frame)\n        asyncio.ensure_future(frame.evaluate('window.stop()'))\n        await navigationPromise\n\n\nclass TestWaitForRequest(BaseTestCase):\n    @sync\n    async def test_wait_for_request(self):\n        await self.page.goto(self.url + 'empty')\n        results = await asyncio.gather(\n            self.page.waitForRequest(self.url + 'static/digits/2.png'),\n            self.page.evaluate('''() => {\n                fetch('/static/digits/1.png');\n                fetch('/static/digits/2.png');\n                fetch('/static/digits/3.png');\n            }''')\n        )\n        request = results[0]\n        self.assertEqual(request.url, self.url + 'static/digits/2.png')\n\n    @sync\n    async def test_predicate(self):\n        await self.page.goto(self.url + 'empty')\n\n        def predicate(req):\n            return req.url == self.url + 'static/digits/2.png'\n\n        results = await asyncio.gather(\n            self.page.waitForRequest(predicate),\n            self.page.evaluate('''() => {\n                fetch('/static/digits/1.png');\n                fetch('/static/digits/2.png');\n                fetch('/static/digits/3.png');\n            }''')\n        )\n        request = results[0]\n        self.assertEqual(request.url, self.url + 'static/digits/2.png')\n\n    @sync\n    async def test_no_timeout(self):\n        await self.page.goto(self.url + 'empty')\n        results = await asyncio.gather(\n            self.page.waitForRequest(\n                self.url + 'static/digits/2.png',\n                timeout=0,\n            ),\n            self.page.evaluate('''() => setTimeout(() => {\n                fetch('/static/digits/1.png');\n                fetch('/static/digits/2.png');\n                fetch('/static/digits/3.png');\n            }, 50)''')\n        )\n        request = results[0]\n        self.assertEqual(request.url, self.url + 'static/digits/2.png')\n\n\nclass TestWaitForResponse(BaseTestCase):\n    @sync\n    async def test_wait_for_response(self):\n        await self.page.goto(self.url + 'empty')\n        results = await asyncio.gather(\n            self.page.waitForResponse(self.url + 'static/digits/2.png'),\n            self.page.evaluate('''() => {\n                fetch('/static/digits/1.png');\n                fetch('/static/digits/2.png');\n                fetch('/static/digits/3.png');\n            }''')\n        )\n        response = results[0]\n        self.assertEqual(response.url, self.url + 'static/digits/2.png')\n\n    @sync\n    async def test_predicate(self):\n        await self.page.goto(self.url + 'empty')\n\n        def predicate(response):\n            return response.url == self.url + 'static/digits/2.png'\n\n        results = await asyncio.gather(\n            self.page.waitForResponse(predicate),\n            self.page.evaluate('''() => {\n                fetch('/static/digits/1.png');\n                fetch('/static/digits/2.png');\n                fetch('/static/digits/3.png');\n            }''')\n        )\n        response = results[0]\n        self.assertEqual(response.url, self.url + 'static/digits/2.png')\n\n    @sync\n    async def test_no_timeout(self):\n        await self.page.goto(self.url + 'empty')\n        results = await asyncio.gather(\n            self.page.waitForResponse(\n                self.url + 'static/digits/2.png',\n                timeout=0,\n            ),\n            self.page.evaluate('''() => setTimeout(() => {\n                fetch('/static/digits/1.png');\n                fetch('/static/digits/2.png');\n                fetch('/static/digits/3.png');\n            }, 50)''')\n        )\n        response = results[0]\n        self.assertEqual(response.url, self.url + 'static/digits/2.png')\n\n\nclass TestGoBack(BaseTestCase):\n    @sync\n    async def test_back(self):\n        await self.page.goto(self.url + 'empty')\n        await self.page.goto(self.url + 'static/textarea.html')\n\n        response = await self.page.goBack()\n        self.assertTrue(response.ok)\n        self.assertIn('empty', response.url)\n\n        response = await self.page.goForward()\n        self.assertTrue(response.ok)\n        self.assertIn('static/textarea.html', response.url)\n\n        response = await self.page.goForward()\n        self.assertIsNone(response)\n\n    @sync\n    async def test_history_api(self):\n        await self.page.goto(self.url + 'empty')\n        await self.page.evaluate('''() => {\n            history.pushState({}, '', '/first.html');\n            history.pushState({}, '', '/second.html');\n        }''')\n        self.assertEqual(self.page.url, self.url + 'second.html')\n\n        await self.page.goBack()\n        self.assertEqual(self.page.url, self.url + 'first.html')\n        await self.page.goBack()\n        self.assertEqual(self.page.url, self.url + 'empty')\n        await self.page.goForward()\n        self.assertEqual(self.page.url, self.url + 'first.html')\n\n\nclass TestExposeFunction(BaseTestCase):\n    @sync\n    async def test_expose_function(self):\n        await self.page.goto(self.url + 'empty')\n        await self.page.exposeFunction('compute', lambda a, b: a * b)\n        result = await self.page.evaluate('(a, b) => compute(a, b)', 9, 4)\n        self.assertEqual(result, 36)\n\n    @sync\n    async def test_call_from_evaluate_on_document(self):\n        await self.page.goto(self.url + 'empty')\n        called = list()\n\n        def woof():\n            called.append(True)\n\n        await self.page.exposeFunction('woof', woof)\n        await self.page.evaluateOnNewDocument('() => woof()')\n        await self.page.reload()\n        self.assertTrue(called)\n\n    @sync\n    async def test_expose_function_other_page(self):\n        await self.page.exposeFunction('compute', lambda a, b: a * b)\n        await self.page.goto(self.url + 'empty')\n        result = await self.page.evaluate('(a, b) => compute(a, b)', 9, 4)\n        self.assertEqual(result, 36)\n\n    @unittest.skip('Python does not support promise in expose function')\n    @sync\n    async def test_expose_function_return_promise(self):\n        async def compute(a, b):\n            return a * b\n\n        await self.page.exposeFunction('compute', compute)\n        result = await self.page.evaluate('() => compute(3, 5)')\n        self.assertEqual(result, 15)\n\n    @sync\n    async def test_expose_function_frames(self):\n        await self.page.exposeFunction('compute', lambda a, b: a * b)\n        await self.page.goto(self.url + 'static/nested-frames.html')\n        frame = self.page.frames[1]\n        result = await frame.evaluate('() => compute(3, 5)')\n        self.assertEqual(result, 15)\n\n    @sync\n    async def test_expose_function_frames_before_navigation(self):\n        await self.page.goto(self.url + 'static/nested-frames.html')\n        await self.page.exposeFunction('compute', lambda a, b: a * b)\n        frame = self.page.frames[1]\n        result = await frame.evaluate('() => compute(3, 5)')\n        self.assertEqual(result, 15)\n\n\nclass TestErrorPage(BaseTestCase):\n    @sync\n    async def test_error_page(self):\n        error = None\n\n        def check(e):\n            nonlocal error\n            error = e\n\n        self.page.on('pageerror', check)\n        await self.page.goto(self.url + 'static/error.html')\n        self.assertIsNotNone(error)\n        self.assertIn('Fancy', error.args[0])\n\n\nclass TestRequest(BaseTestCase):\n    @sync\n    async def test_request(self):\n        requests = []\n        self.page.on('request', lambda req: requests.append(req))\n        await self.page.goto(self.url + 'empty')\n        await attachFrame(self.page, 'frame1', self.url + 'empty')\n        self.assertEqual(len(requests), 2)\n        self.assertEqual(requests[0].url, self.url + 'empty')\n        self.assertEqual(requests[0].frame, self.page.mainFrame)\n        self.assertEqual(requests[0].frame.url, self.url + 'empty')\n        self.assertEqual(requests[1].url, self.url + 'empty')\n        self.assertEqual(requests[1].frame, self.page.frames[1])\n        self.assertEqual(requests[1].frame.url, self.url + 'empty')\n\n\nclass TestQuerySelector(BaseTestCase):\n    @sync\n    async def test_jeval(self):\n        await self.page.setContent(\n            '<section id=\"testAttribute\">43543</section>')\n        idAttribute = await self.page.Jeval('section', 'e => e.id')\n        self.assertEqual(idAttribute, 'testAttribute')\n\n    @sync\n    async def test_jeval_argument(self):\n        await self.page.setContent('<section>hello</section>')\n        text = await self.page.Jeval(\n            'section', '(e, suffix) => e.textContent + suffix', ' world!')\n        self.assertEqual(text, 'hello world!')\n\n    @sync\n    async def test_jeval_argument_element(self):\n        await self.page.setContent('<section>hello</section><div> world</div>')\n        divHandle = await self.page.J('div')\n        text = await self.page.Jeval(\n            'section',\n            '(e, div) => e.textContent + div.textContent',\n            divHandle,\n        )\n        self.assertEqual(text, 'hello world')\n\n    @sync\n    async def test_jeval_not_found(self):\n        await self.page.goto(self.url + 'empty')\n        with self.assertRaises(ElementHandleError) as cm:\n            await self.page.Jeval('section', 'e => e.id')\n        self.assertIn(\n            'failed to find element matching selector \"section\"',\n            cm.exception.args[0],\n        )\n\n    @sync\n    async def test_JJeval(self):\n        await self.page.setContent(\n            '<div>hello</div><div>beautiful</div><div>world</div>')\n        divsCount = await self.page.JJeval('div', 'divs => divs.length')\n        self.assertEqual(divsCount, 3)\n\n    @sync\n    async def test_query_selector(self):\n        await self.page.setContent('<section>test</section>')\n        element = await self.page.J('section')\n        self.assertTrue(element)\n\n    @unittest.skipIf(sys.version_info < (3, 6), 'Elements order is unstable')\n    @sync\n    async def test_query_selector_all(self):\n        await self.page.setContent('<div>A</div><br/><div>B</div>')\n        elements = await self.page.JJ('div')\n        self.assertEqual(len(elements), 2)\n        results = []\n        for e in elements:\n            results.append(await self.page.evaluate('e => e.textContent', e))\n        self.assertEqual(results, ['A', 'B'])\n\n    @sync\n    async def test_query_selector_all_not_found(self):\n        await self.page.goto(self.url + 'empty')\n        elements = await self.page.JJ('div')\n        self.assertEqual(len(elements), 0)\n\n    @sync\n    async def test_xpath(self):\n        await self.page.setContent('<section>test</section>')\n        element = await self.page.xpath('/html/body/section')\n        self.assertTrue(element)\n\n    @sync\n    async def test_xpath_alias(self):\n        await self.page.setContent('<section>test</section>')\n        element = await self.page.Jx('/html/body/section')\n        self.assertTrue(element)\n\n    @sync\n    async def test_xpath_not_found(self):\n        element = await self.page.xpath('/html/body/no-such-tag')\n        self.assertEqual(element, [])\n\n    @sync\n    async def test_xpath_multiple(self):\n        await self.page.setContent('<div></div><div></div>')\n        element = await self.page.xpath('/html/body/div')\n        self.assertEqual(len(element), 2)\n\n\nclass TestUserAgent(BaseTestCase):\n    @sync\n    async def test_user_agent(self):\n        self.assertIn('Mozilla', await self.page.evaluate(\n            '() => navigator.userAgent'))\n        await self.page.setUserAgent('foobar')\n        await self.page.goto(self.url)\n        self.assertEqual('foobar', await self.page.evaluate(\n            '() => navigator.userAgent'))\n\n    @sync\n    async def test_user_agent_mobile_emulate(self):\n        await self.page.goto(self.url + 'static/mobile.html')\n        self.assertIn(\n            'Chrome', await self.page.evaluate('navigator.userAgent'))\n        await self.page.setUserAgent('Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1')  # noqa: E501\n        self.assertIn(\n            'Safari', await self.page.evaluate('navigator.userAgent'))\n\n\nclass TestExtraHTTPHeader(BaseTestCase):\n    @sync\n    async def test_extra_http_header(self):\n        await self.page.setExtraHTTPHeaders({'foo': 'bar'})\n\n        from tornado.web import RequestHandler\n        requests = []\n\n        class HeaderFetcher(RequestHandler):\n            def get(self):\n                requests.append(self.request)\n                self.write('')\n\n        self.app.add_handlers('localhost', [('/header', HeaderFetcher)])\n        await self.page.goto(self.url + 'header')\n        self.assertEqual(len(requests), 1)\n        self.assertEqual(requests[0].headers['foo'], 'bar')\n\n    @sync\n    async def test_non_string_value(self):\n        with self.assertRaises(TypeError) as e:\n            await self.page.setExtraHTTPHeaders({'foo': 1})\n        self.assertIn(\n            'Expected value of header \"foo\" to be string', e.exception.args[0])\n\n\nclass TestAuthenticate(BaseTestCase):\n    @sync\n    async def test_auth(self):\n        response = await self.page.goto(self.url + 'auth')\n        self.assertEqual(response.status, 401)\n        await self.page.authenticate({'username': 'user', 'password': 'pass'})\n        response = await self.page.goto(self.url + 'auth')\n        self.assertEqual(response.status, 200)\n\n\nclass TestAuthenticateFailed(BaseTestCase):\n    @sync\n    async def test_auth_fail(self):\n        await self.page.authenticate({'username': 'foo', 'password': 'bar'})\n        response = await self.page.goto(self.url + 'auth')\n        self.assertEqual(response.status, 401)\n\n\nclass TestAuthenticateDisable(BaseTestCase):\n    @sync\n    async def test_disable_auth(self):\n        await self.page.authenticate({'username': 'user', 'password': 'pass'})\n        response = await self.page.goto(self.url + 'auth')\n        self.assertEqual(response.status, 200)\n        await self.page.authenticate(None)\n        response = await self.page.goto(\n            'http://127.0.0.1:{}/auth'.format(self.port))\n        self.assertEqual(response.status, 401)\n\n\nclass TestSetContent(BaseTestCase):\n    expectedOutput = '<html><head></head><body><div>hello</div></body></html>'\n\n    @sync\n    async def test_set_content(self):\n        await self.page.setContent('<div>hello</div>')\n        result = await self.page.content()\n        self.assertEqual(result, self.expectedOutput)\n\n    @sync\n    async def test_with_doctype(self):\n        doctype = '<!DOCTYPE html>'\n        await self.page.setContent(doctype + '<div>hello</div>')\n        result = await self.page.content()\n        self.assertEqual(result, doctype + self.expectedOutput)\n\n    @sync\n    async def test_with_html4_doctype(self):\n        doctype = ('<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\" '\n                   '\"http://www.w3.org/TR/html4/strict.dtd\">')\n        await self.page.setContent(doctype + '<div>hello</div>')\n        result = await self.page.content()\n        self.assertEqual(result, doctype + self.expectedOutput)\n\n\nclass TestSetBypassCSP(BaseTestCase):\n    @sync\n    async def test_bypass_csp_meta_tag(self):\n        await self.page.goto(self.url + 'static/csp.html')\n        with self.assertRaises(ElementHandleError):\n            await self.page.addScriptTag(content='window.__injected = 42;')\n        self.assertIsNone(await self.page.evaluate('window.__injected'))\n\n        await self.page.setBypassCSP(True)\n        await self.page.reload()\n        await self.page.addScriptTag(content='window.__injected = 42;')\n        self.assertEqual(await self.page.evaluate('window.__injected'), 42)\n\n    @sync\n    async def test_bypass_csp_header(self):\n        await self.page.goto(self.url + 'csp')\n        with self.assertRaises(ElementHandleError):\n            await self.page.addScriptTag(content='window.__injected = 42;')\n        self.assertIsNone(await self.page.evaluate('window.__injected'))\n\n        await self.page.setBypassCSP(True)\n        await self.page.reload()\n        await self.page.addScriptTag(content='window.__injected = 42;')\n        self.assertEqual(await self.page.evaluate('window.__injected'), 42)\n\n    @sync\n    async def test_bypass_scp_cross_process(self):\n        await self.page.setBypassCSP(True)\n        await self.page.goto(self.url + 'static/csp.html')\n        await self.page.addScriptTag(content='window.__injected = 42;')\n        self.assertEqual(await self.page.evaluate('window.__injected'), 42)\n\n        await self.page.goto(\n            'http://127.0.0.1:{}/static/csp.html'.format(self.port))\n        await self.page.addScriptTag(content='window.__injected = 42;')\n        self.assertEqual(await self.page.evaluate('window.__injected'), 42)\n\n\nclass TestAddScriptTag(BaseTestCase):\n    @sync\n    async def test_script_tag_error(self):\n        await self.page.goto(self.url + 'empty')\n        with self.assertRaises(ValueError):\n            await self.page.addScriptTag('/static/injectedfile.js')\n\n    @sync\n    async def test_script_tag_url(self):\n        await self.page.goto(self.url + 'empty')\n        scriptHandle = await self.page.addScriptTag(\n            url='/static/injectedfile.js')\n        self.assertIsNotNone(scriptHandle.asElement())\n        self.assertEqual(await self.page.evaluate('__injected'), 42)\n\n    @sync\n    async def test_script_tag_url_fail(self):\n        await self.page.goto(self.url + 'empty')\n        with self.assertRaises(PageError) as cm:\n            await self.page.addScriptTag({'url': '/nonexistsfile.js'})\n        self.assertEqual(cm.exception.args[0],\n                         'Loading script from /nonexistsfile.js failed')\n\n    @sync\n    async def test_script_tag_path(self):\n        curdir = Path(__file__).parent\n        path = str(curdir / 'static' / 'injectedfile.js')\n        await self.page.goto(self.url + 'empty')\n        scriptHanlde = await self.page.addScriptTag(path=path)\n        self.assertIsNotNone(scriptHanlde.asElement())\n        self.assertEqual(await self.page.evaluate('__injected'), 42)\n\n    @sync\n    async def test_script_tag_path_source_map(self):\n        curdir = Path(__file__).parent\n        path = str(curdir / 'static' / 'injectedfile.js')\n        await self.page.goto(self.url + 'empty')\n        await self.page.addScriptTag(path=path)\n        result = await self.page.evaluate('__injectedError.stack')\n        self.assertIn(str(Path('static') / 'injectedfile.js'), result)\n\n    @sync\n    async def test_script_tag_content(self):\n        await self.page.goto(self.url + 'empty')\n        scriptHandle = await self.page.addScriptTag(\n            content='window.__injected = 35;')\n        self.assertIsNotNone(scriptHandle.asElement())\n        self.assertEqual(await self.page.evaluate('__injected'), 35)\n\n    @sync\n    async def test_scp_error_content(self):\n        await self.page.goto(self.url + 'static/csp.html')\n        with self.assertRaises(ElementHandleError):\n            await self.page.addScriptTag(content='window.__injected = 35;')\n\n    @sync\n    async def test_scp_error_url(self):\n        await self.page.goto(self.url + 'static/csp.html')\n        with self.assertRaises(PageError):\n            await self.page.addScriptTag(\n                url='http://127.0.0.1:{}/static/injectedfile.js'.format(self.port)  # noqa: E501\n            )\n\n    @sync\n    async def test_module_url(self):\n        await self.page.goto(self.url + 'empty')\n        await self.page.addScriptTag(\n            url='/static/es6/es6import.js', type='module')\n        self.assertEqual(await self.page.evaluate('__es6injected'), 42)\n\n    @sync\n    async def test_module_path(self):\n        await self.page.goto(self.url + 'empty')\n        curdir = os.path.dirname(os.path.abspath(__file__))\n        path = os.path.join(curdir, 'static', 'es6', 'es6pathimport.js')\n        await self.page.addScriptTag(path=path, type='module')\n        await self.page.waitForFunction('window.__es6injected')\n        self.assertEqual(await self.page.evaluate('__es6injected'), 42)\n\n    @sync\n    async def test_module_content(self):\n        await self.page.goto(self.url + 'empty')\n        content = '''\n            import num from '/static/es6/es6module.js';\n            window.__es6injected = num;\n        '''\n        await self.page.addScriptTag(content=content, type='module')\n        await self.page.waitForFunction('window.__es6injected')\n        self.assertEqual(await self.page.evaluate('__es6injected'), 42)\n\n\nclass TestAddStyleTag(BaseTestCase):\n    @sync\n    async def test_style_tag_error(self):\n        await self.page.goto(self.url + 'empty')\n        with self.assertRaises(ValueError):\n            await self.page.addStyleTag('/static/injectedstyle.css')\n\n    async def get_bgcolor(self):\n        return await self.page.evaluate('() => window.getComputedStyle(document.querySelector(\"body\")).getPropertyValue(\"background-color\")')  # noqa: E501\n\n    @sync\n    async def test_style_tag_url(self):\n        await self.page.goto(self.url + 'empty')\n        self.assertEqual(await self.get_bgcolor(), 'rgba(0, 0, 0, 0)')\n        styleHandle = await self.page.addStyleTag(url='/static/injectedstyle.css')  # noqa: E501\n        self.assertIsNotNone(styleHandle.asElement())\n        self.assertEqual(await self.get_bgcolor(), 'rgb(255, 0, 0)')\n\n    @sync\n    async def test_style_tag_url_fail(self):\n        await self.page.goto(self.url + 'empty')\n        with self.assertRaises(PageError) as cm:\n            await self.page.addStyleTag(url='/nonexistfile.css')\n        self.assertEqual(cm.exception.args[0],\n                         'Loading style from /nonexistfile.css failed')\n\n    @sync\n    async def test_style_tag_path(self):\n        curdir = Path(__file__).parent\n        path = str(curdir / 'static' / 'injectedstyle.css')\n        await self.page.goto(self.url + 'empty')\n        self.assertEqual(await self.get_bgcolor(), 'rgba(0, 0, 0, 0)')\n        styleHandle = await self.page.addStyleTag(path=path)\n        self.assertIsNotNone(styleHandle.asElement())\n        self.assertEqual(await self.get_bgcolor(), 'rgb(255, 0, 0)')\n\n    @sync\n    async def test_style_tag_path_source_map(self):\n        curdir = Path(__file__).parent\n        path = str(curdir / 'static' / 'injectedstyle.css')\n        await self.page.goto(self.url + 'empty')\n        await self.page.addStyleTag(path=str(path))\n        styleHandle = await self.page.J('style')\n        styleContent = await self.page.evaluate(\n            'style => style.innerHTML', styleHandle)\n        self.assertIn(str(Path('static') / 'injectedstyle.css'), styleContent)\n\n    @sync\n    async def test_style_tag_content(self):\n        await self.page.goto(self.url + 'empty')\n        self.assertEqual(await self.get_bgcolor(), 'rgba(0, 0, 0, 0)')\n        styleHandle = await self.page.addStyleTag(content=' body {background-color: green;}')  # noqa: E501\n        self.assertIsNotNone(styleHandle.asElement())\n        self.assertEqual(await self.get_bgcolor(), 'rgb(0, 128, 0)')\n\n    @sync\n    async def test_csp_error_content(self):\n        await self.page.goto(self.url + 'static/csp.html')\n        with self.assertRaises(ElementHandleError):\n            await self.page.addStyleTag(\n                content='body { background-color: green; }')\n\n    @sync\n    async def test_csp_error_url(self):\n        await self.page.goto(self.url + 'static/csp.html')\n        with self.assertRaises(PageError):\n            await self.page.addStyleTag(\n                url='http://127.0.0.1:{}/static/injectedstyle.css'.format(self.port)  # noqa: E501\n            )\n\n\nclass TestUrl(BaseTestCase):\n    @sync\n    async def test_url(self):\n        await self.page.goto('about:blank')\n        self.assertEqual(self.page.url, 'about:blank')\n        await self.page.goto(self.url + 'empty')\n        self.assertEqual(self.page.url, self.url + 'empty')\n\n\nclass TestViewport(BaseTestCase):\n    iPhoneViewport = iPhone['viewport']\n\n    @sync\n    async def test_viewport(self):\n        self.assertEqual(self.page.viewport, {'width': 800, 'height': 600})\n        await self.page.setViewport({'width': 123, 'height': 456})\n        self.assertEqual(self.page.viewport, {'width': 123, 'height': 456})\n\n    @sync\n    async def test_mobile_emulation(self):\n        await self.page.goto(self.url + 'static/mobile.html')\n        self.assertEqual(await self.page.evaluate('window.innerWidth'), 800)\n        await self.page.setViewport(self.iPhoneViewport)\n        self.assertEqual(await self.page.evaluate('window.innerWidth'), 375)\n        await self.page.setViewport({'width': 400, 'height': 300})\n        self.assertEqual(await self.page.evaluate('window.innerWidth'), 400)\n\n    @sync\n    async def test_touch_emulation(self):\n        await self.page.goto(self.url + 'static/mobile.html')\n        self.assertFalse(await self.page.evaluate('\"ontouchstart\" in window'))\n        await self.page.setViewport(self.iPhoneViewport)\n        self.assertTrue(await self.page.evaluate('\"ontouchstart\" in window'))\n\n        dispatchTouch = '''() => {\n            let fulfill;\n            const promise = new Promise(x => fulfill = x);\n            window.ontouchstart = function(e) {\n                fulfill('Received touch');\n            };\n            window.dispatchEvent(new Event('touchstart'));\n\n            fulfill('Did not receive touch');\n\n            return promise;\n        }'''\n        self.assertEqual(\n            await self.page.evaluate(dispatchTouch), 'Received touch')\n\n        await self.page.setViewport({'width': 100, 'height': 100})\n        self.assertFalse(await self.page.evaluate('\"ontouchstart\" in window'))\n\n    @sync\n    async def test_detect_by_modernizr(self):\n        await self.page.goto(self.url + 'static/detect-touch.html')\n        self.assertEqual(\n            await self.page.evaluate('document.body.textContent.trim()'),\n            'NO'\n        )\n        await self.page.setViewport(self.iPhoneViewport)\n        self.assertEqual(\n            await self.page.evaluate('document.body.textContent.trim()'),\n            'YES'\n        )\n\n    @sync\n    async def test_detect_touch_viewport_touch(self):\n        await self.page.setViewport({'width': 800, 'height': 600, 'hasTouch': True})  # noqa: E501\n        await self.page.addScriptTag({'url': self.url + 'static/modernizr.js'})\n        self.assertTrue(await self.page.evaluate('() => Modernizr.touchevents'))  # noqa: E501\n\n    @sync\n    async def test_landscape_emulation(self):\n        await self.page.goto(self.url + 'static/mobile.html')\n        self.assertEqual(\n            await self.page.evaluate('screen.orientation.type'),\n            'portrait-primary',\n        )\n        iPhoneLandscapeViewport = self.iPhoneViewport.copy()\n        iPhoneLandscapeViewport['isLandscape'] = True\n        await self.page.setViewport(iPhoneLandscapeViewport)\n        self.assertEqual(\n            await self.page.evaluate('screen.orientation.type'),\n            'landscape-primary',\n        )\n        await self.page.setViewport({'width': 100, 'height': 100})\n        self.assertEqual(\n            await self.page.evaluate('screen.orientation.type'),\n            'portrait-primary',\n        )\n\n\nclass TestEmulate(BaseTestCase):\n    @sync\n    async def test_emulate(self):\n        await self.page.goto(self.url + 'static/mobile.html')\n        await self.page.emulate(iPhone)\n        self.assertEqual(await self.page.evaluate('window.innerWidth'), 375)\n        self.assertIn(\n            'Safari', await self.page.evaluate('navigator.userAgent'))\n\n    @sync\n    async def test_click(self):\n        await self.page.emulate(iPhone)\n        await self.page.goto(self.url + 'static/button.html')\n        button = await self.page.J('button')\n        await self.page.evaluate(\n            'button => button.style.marginTop = \"200px\"', button)\n        await button.click()\n        self.assertEqual(await self.page.evaluate('result'), 'Clicked')\n\n\nclass TestEmulateMedia(BaseTestCase):\n    @sync\n    async def test_emulate_media(self):\n        self.assertTrue(\n            await self.page.evaluate('matchMedia(\"screen\").matches'))\n        self.assertFalse(\n            await self.page.evaluate('matchMedia(\"print\").matches'))\n        await self.page.emulateMedia('print')\n        self.assertFalse(\n            await self.page.evaluate('matchMedia(\"screen\").matches'))\n        self.assertTrue(\n            await self.page.evaluate('matchMedia(\"print\").matches'))\n        await self.page.emulateMedia(None)\n        self.assertTrue(\n            await self.page.evaluate('matchMedia(\"screen\").matches'))\n        self.assertFalse(\n            await self.page.evaluate('matchMedia(\"print\").matches'))\n\n    @sync\n    async def test_emulate_media_bad_arg(self):\n        with self.assertRaises(ValueError) as cm:\n            await self.page.emulateMedia('bad')\n        self.assertEqual(cm.exception.args[0], 'Unsupported media type: bad')\n\n\nclass TestJavaScriptEnabled(BaseTestCase):\n    @sync\n    async def test_set_javascript_enabled(self):\n        await self.page.setJavaScriptEnabled(False)\n        await self.page.goto(\n            'data:text/html, <script>var something = \"forbidden\"</script>')\n        with self.assertRaises(ElementHandleError) as cm:\n            await self.page.evaluate('something')\n        self.assertIn('something is not defined', cm.exception.args[0])\n\n        await self.page.setJavaScriptEnabled(True)\n        await self.page.goto(\n            'data:text/html, <script>var something = \"forbidden\"</script>')\n        self.assertEqual(await self.page.evaluate('something'), 'forbidden')\n\n\nclass TestEvaluateOnNewDocument(BaseTestCase):\n    @sync\n    async def test_evaluate_before_else_on_page(self):\n        await self.page.evaluateOnNewDocument('() => window.injected = 123')\n        await self.page.goto(self.url + 'static/temperable.html')\n        self.assertEqual(await self.page.evaluate('window.result'), 123)\n\n    @sync\n    async def test_csp(self):\n        await self.page.evaluateOnNewDocument('() => window.injected = 123')\n        await self.page.goto(self.url + 'csp')\n        self.assertEqual(await self.page.evaluate('window.injected'), 123)\n        with self.assertRaises(ElementHandleError):\n            await self.page.addScriptTag(content='window.e = 10;')\n        self.assertIsNone(await self.page.evaluate('window.e'))\n\n\nclass TestCacheEnabled(BaseTestCase):\n    @sync\n    async def test_cache_enable_disable(self):\n        responses = {}\n\n        def set_response(res):\n            responses[res.url.split('/').pop()] = res\n\n        self.page.on('response', set_response)\n        await self.page.goto(self.url + 'static/cached/one-style.html',\n                             waitUntil='networkidle2')\n        await self.page.reload(waitUntil='networkidle2')\n        self.assertTrue(responses.get('one-style.css').fromCache)\n\n        await self.page.setCacheEnabled(False)\n        await self.page.reload(waitUntil='networkidle2')\n        self.assertFalse(responses.get('one-style.css').fromCache)\n\n\nclass TestPDF(BaseTestCase):\n    @sync\n    async def test_pdf(self):\n        outfile = Path(__file__).parent / 'output.pdf'\n        await self.page.pdf({'path': str(outfile)})\n        self.assertTrue(outfile.is_file())\n        with outfile.open('rb') as f:\n            pdf = f.read()\n        self.assertGreater(len(pdf), 0)\n        outfile.unlink()\n\n\nclass TestTitle(BaseTestCase):\n    @sync\n    async def test_title(self):\n        await self.page.goto(self.url + 'static/button.html')\n        self.assertEqual(await self.page.title(), 'Button test')\n\n\nclass TestSelect(BaseTestCase):\n    def setUp(self):\n        super().setUp()\n        sync(self.page.goto(self.url + 'static/select.html'))\n\n    @sync\n    async def test_select(self):\n        value = await self.page.select('select', 'blue')\n        self.assertEqual(value, ['blue'])\n        _input = await self.page.evaluate('result.onInput')\n        self.assertEqual(_input, ['blue'])\n        change = await self.page.evaluate('result.onChange')\n        self.assertEqual(change, ['blue'])\n\n        _input = await self.page.evaluate('result.onBubblingInput')\n        self.assertEqual(_input, ['blue'])\n        change = await self.page.evaluate('result.onBubblingChange')\n        self.assertEqual(change, ['blue'])\n\n    @sync\n    async def test_select_first_item(self):\n        await self.page.select('select', 'blue', 'green', 'red')\n        self.assertEqual(await self.page.evaluate('result.onInput'), ['blue'])\n        self.assertEqual(await self.page.evaluate('result.onChange'), ['blue'])\n\n    @sync\n    async def test_select_multiple(self):\n        await self.page.evaluate('makeMultiple();')\n        values = await self.page.select('select', 'blue', 'green', 'red')\n        self.assertEqual(values, ['blue', 'green', 'red'])\n        _input = await self.page.evaluate('result.onInput')\n        self.assertEqual(_input, ['blue', 'green', 'red'])\n        change = await self.page.evaluate('result.onChange')\n        self.assertEqual(change, ['blue', 'green', 'red'])\n\n    @sync\n    async def test_select_not_select_element(self):\n        with self.assertRaises(ElementHandleError):\n            await self.page.select('body', '')\n\n    @sync\n    async def test_select_no_match(self):\n        values = await self.page.select('select', 'abc', 'def')\n        self.assertEqual(values, [])\n\n    @sync\n    async def test_return_selected_elements(self):\n        await self.page.evaluate('makeMultiple()')\n        result = await self.page.select('select', 'blue', 'black', 'magenta')\n        self.assertEqual(len(result), 3)\n        self.assertEqual(set(result), {'blue', 'black', 'magenta'})\n\n    @sync\n    async def test_select_not_multiple(self):\n        values = await self.page.select('select', 'blue', 'green', 'red')\n        self.assertEqual(len(values), 1)\n\n    @sync\n    async def test_select_no_value(self):\n        values = await self.page.select('select')\n        self.assertEqual(values, [])\n\n    @sync\n    async def test_select_deselect(self):\n        await self.page.select('select', 'blue', 'green', 'red')\n        await self.page.select('select')\n        result = await self.page.Jeval(\n            'select',\n            'elm => Array.from(elm.options).every(option => !option.selected)'\n        )\n        self.assertTrue(result)\n\n    @sync\n    async def test_select_deselect_multiple(self):\n        await self.page.evaluate('makeMultiple();')\n        await self.page.select('select', 'blue', 'green', 'red')\n        await self.page.select('select')\n        result = await self.page.Jeval(\n            'select',\n            'elm => Array.from(elm.options).every(option => !option.selected)'\n        )\n        self.assertTrue(result)\n\n    @sync\n    async def test_select_nonstring(self):\n        with self.assertRaises(TypeError):\n            await self.page.select('select', 12)\n\n\nclass TestCookie(BaseTestCase):\n    @sync\n    async def test_cookies(self):\n        await self.page.goto(self.url)\n        cookies = await self.page.cookies()\n        self.assertEqual(cookies, [])\n        await self.page.evaluate(\n            'document.cookie = \"username=John Doe\"'\n        )\n        cookies = await self.page.cookies()\n        self.assertEqual(cookies, [{\n            'name': 'username',\n            'value': 'John Doe',\n            'domain': 'localhost',\n            'path': '/',\n            'expires': -1,\n            'size': 16,\n            'httpOnly': False,\n            'secure': False,\n            'session': True,\n        }])\n        await self.page.setCookie({'name': 'password', 'value': '123456'})\n        cookies = await self.page.evaluate(\n            '() => document.cookie'\n        )\n        self.assertEqual(cookies, 'username=John Doe; password=123456')\n        cookies = await self.page.cookies()\n        self.assertIn(cookies, [\n            [\n                {\n                    'name': 'password',\n                    'value': '123456',\n                    'domain': 'localhost',\n                    'path': '/',\n                    'expires': -1,\n                    'size': 14,\n                    'httpOnly': False,\n                    'secure': False,\n                    'session': True,\n                }, {\n                    'name': 'username',\n                    'value': 'John Doe',\n                    'domain': 'localhost',\n                    'path': '/',\n                    'expires': -1,\n                    'size': 16,\n                    'httpOnly': False,\n                    'secure': False,\n                    'session': True,\n                }\n            ],\n            [\n                {\n                    'name': 'username',\n                    'value': 'John Doe',\n                    'domain': 'localhost',\n                    'path': '/',\n                    'expires': -1,\n                    'size': 16,\n                    'httpOnly': False,\n                    'secure': False,\n                    'session': True,\n                }, {\n                    'name': 'password',\n                    'value': '123456',\n                    'domain': 'localhost',\n                    'path': '/',\n                    'expires': -1,\n                    'size': 14,\n                    'httpOnly': False,\n                    'secure': False,\n                    'session': True,\n                }\n            ]\n        ])\n        await self.page.deleteCookie({'name': 'username'})\n        cookies = await self.page.evaluate(\n            '() => document.cookie'\n        )\n        self.assertEqual(cookies, 'password=123456')\n        cookies = await self.page.cookies()\n        self.assertEqual(cookies, [{\n            'name': 'password',\n            'value': '123456',\n            'domain': 'localhost',\n            'path': '/',\n            'expires': -1,\n            'size': 14,\n            'httpOnly': False,\n            'secure': False,\n            'session': True,\n        }])\n\n    @sync\n    async def test_cookie_blank_page(self):\n        await self.page.goto('about:blank')\n        with self.assertRaises(NetworkError):\n            await self.page.setCookie({'name': 'example-cookie', 'value': 'a'})\n\n    @sync\n    async def test_cookie_blank_page2(self):\n        with self.assertRaises(PageError):\n            await self.page.setCookie(\n                {'name': 'example-cookie', 'value': 'best'},\n                {'url': 'about:blank',\n                 'name': 'example-cookie-blank',\n                 'value': 'best'}\n            )\n\n    @sync\n    async def test_cookie_data_url_page(self):\n        await self.page.goto('data:,hello')\n        with self.assertRaises(NetworkError):\n            await self.page.setCookie({'name': 'example-cookie', 'value': 'a'})\n\n    @sync\n    async def test_cookie_data_url_page2(self):\n        with self.assertRaises(PageError):\n            await self.page.setCookie(\n                {'name': 'example-cookie', 'value': 'best'},\n                {'url': 'data:,hello',\n                 'name': 'example-cookie-blank',\n                 'value': 'best'}\n            )\n\n\nclass TestCookieWithPath(BaseTestCase):\n    @sync\n    async def test_set_cookie_with_path(self):\n        await self.page.goto(self.url + 'static/grid.html')\n        await self.page.setCookie({\n            'name': 'gridcookie',\n            'value': 'GRID',\n            'path': '/static/grid.html',\n        })\n        self.assertEqual(await self.page.cookies(), [{\n            'name': 'gridcookie',\n            'value': 'GRID',\n            'path': '/static/grid.html',\n            'domain': 'localhost',\n            'expires': -1,\n            'size': 14,\n            'httpOnly': False,\n            'secure': False,\n            'session': True,\n        }])\n\n\nclass TestCookieDelete(BaseTestCase):\n    @sync\n    async def test_delete_cookie(self):\n        await self.page.goto(self.url)\n        await self.page.setCookie({\n            'name': 'cookie1',\n            'value': '1',\n        }, {\n            'name': 'cookie2',\n            'value': '2',\n        }, {\n            'name': 'cookie3',\n            'value': '3',\n        })\n        self.assertEqual(\n            await self.page.evaluate('document.cookie'),\n            'cookie1=1; cookie2=2; cookie3=3'\n        )\n        await self.page.deleteCookie({'name': 'cookie2'})\n        self.assertEqual(\n            await self.page.evaluate('document.cookie'),\n            'cookie1=1; cookie3=3'\n        )\n\n\nclass TestCookieDomain(BaseTestCase):\n    @sync\n    async def test_different_domain(self):\n        await self.page.goto(self.url + 'static/grid.html')\n        await self.page.setCookie({\n            'name': 'example-cookie',\n            'value': 'best',\n            'url': 'https://www.example.com',\n        })\n        self.assertEqual(await self.page.evaluate('document.cookie'), '')\n        self.assertEqual(await self.page.cookies(), [])\n        self.assertEqual(await self.page.cookies('https://www.example.com'), [{\n            'name': 'example-cookie',\n            'value': 'best',\n            'domain': 'www.example.com',\n            'path': '/',\n            'expires': -1,\n            'size': 18,\n            'httpOnly': False,\n            'secure': True,\n            'session': True,\n        }])\n\n\nclass TestCookieFrames(BaseTestCase):\n    @sync\n    async def test_frame(self):\n        await self.page.goto(self.url + 'static/grid.html')\n        await self.page.setCookie({\n            'name': 'localhost-cookie',\n            'value': 'best',\n        })\n        url_127 = 'http://127.0.0.1:{}'.format(self.port)\n        await self.page.evaluate('''src => {\n            let fulfill;\n            const promise = new Promise(x => fulfill = x);\n            const iframe = document.createElement('iframe');\n            document.body.appendChild(iframe);\n            iframe.onload = fulfill;\n            iframe.src = src;\n            return promise;\n        }''', url_127)\n        await self.page.setCookie({\n            'name': '127-cookie',\n            'value': 'worst',\n            'url': url_127,\n        })\n\n        self.assertEqual(\n            await self.page.evaluate('document.cookie'),\n            'localhost-cookie=best',\n        )\n        self.assertEqual(\n            await self.page.frames[1].evaluate('document.cookie'),\n            '127-cookie=worst',\n        )\n\n        self.assertEqual(await self.page.cookies(), [{\n            'name': 'localhost-cookie',\n            'value': 'best',\n            'domain': 'localhost',\n            'path': '/',\n            'expires': -1,\n            'size': 20,\n            'httpOnly': False,\n            'secure': False,\n            'session': True,\n        }])\n        self.assertEqual(await self.page.cookies(url_127), [{\n            'name': '127-cookie',\n            'value': 'worst',\n            'domain': '127.0.0.1',\n            'path': '/',\n            'expires': -1,\n            'size': 15,\n            'httpOnly': False,\n            'secure': False,\n            'session': True,\n        }])\n\n\nclass TestEvents(BaseTestCase):\n    @sync\n    async def test_close_window_close(self):\n        loop = asyncio.get_event_loop()\n        newPagePromise = loop.create_future()\n\n        async def page_created(target):\n            page = await target.page()\n            newPagePromise.set_result(page)\n\n        self.context.once(\n            'targetcreated',\n            lambda target: loop.create_task(page_created(target)),\n        )\n        await self.page.evaluate(\n            'window[\"newPage\"] = window.open(\"about:blank\")')\n        newPage = await newPagePromise\n\n        closedPromise = loop.create_future()\n        newPage.on('close', lambda: closedPromise.set_result(True))\n        await self.page.evaluate('window[\"newPage\"].close()')\n        await closedPromise\n\n    @sync\n    async def test_close_page_close(self):\n        newPage = await self.context.newPage()\n        closedPromise = asyncio.get_event_loop().create_future()\n        newPage.on('close', lambda: closedPromise.set_result(True))\n        await newPage.close()\n        await closedPromise\n\n\nclass TestBrowser(BaseTestCase):\n    @sync\n    async def test_get_browser(self):\n        self.assertIs(self.page.browser, self.browser)\n"
  },
  {
    "path": "tests/test_pyppeteer.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\ntest_pyppeteer\n----------------------------------\n\nTests for `pyppeteer` module.\n\"\"\"\n\nimport asyncio\nimport logging\nfrom pathlib import Path\n\nfrom syncer import sync\n\nfrom .base import BaseTestCase\n\n\nclass TestPyppeteer(BaseTestCase):\n    @sync\n    async def test_get_https(self):\n        await self.page.goto('https://example.com/')\n        self.assertEqual(self.page.url, 'https://example.com/')\n\n    @sync\n    async def test_get_facebook(self):\n        await self.page.goto('https://www.facebook.com/')\n        self.assertEqual(self.page.url, 'https://www.facebook.com/')\n\n    @sync\n    async def test_plain_text_depr(self):\n        await self.page.goto(self.url)\n        with self.assertLogs('pyppeteer', logging.WARN) as log:\n            text = await self.page.plainText()\n            self.assertIn('deprecated', log.records[0].msg)\n        self.assertEqual(text.split(), ['Hello', 'link1', 'link2'])\n\n    @sync\n    async def test_inject_file(self):  # deprecated\n        tmp_file = Path('tmp.js')\n        with tmp_file.open('w') as f:\n            f.write('''\n() => document.body.appendChild(document.createElement(\"section\"))\n            '''.strip())\n        with self.assertLogs('pyppeteer', logging.WARN) as log:\n            await self.page.injectFile(str(tmp_file))\n            self.assertIn('deprecated', log.records[0].msg)\n        await self.page.waitForSelector('section')\n        self.assertIsNotNone(await self.page.J('section'))\n        tmp_file.unlink()\n\n\nclass TestScreenshot(BaseTestCase):\n    def setUp(self):\n        super().setUp()\n        self.target_path = Path(__file__).resolve().parent / 'test.png'\n        if self.target_path.exists():\n            self.target_path.unlink()\n\n    def tearDown(self):\n        if self.target_path.exists():\n            self.target_path.unlink()\n        super().tearDown()\n\n    @sync\n    async def test_screenshot_large(self):\n        page = await self.context.newPage()\n        await page.setViewport({\n            'width': 2000,\n            'height': 2000,\n        })\n        await page.goto(self.url + 'static/huge-page.html')\n        options = {'path': str(self.target_path)}\n        self.assertFalse(self.target_path.exists())\n        await asyncio.wait_for(page.screenshot(options), 30)\n        self.assertTrue(self.target_path.exists())\n        with self.target_path.open('rb') as fh:\n            bytes = fh.read()\n            self.assertGreater(len(bytes), 2**20)\n"
  },
  {
    "path": "tests/test_screenshot.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport base64\nfrom pathlib import Path\nfrom unittest import TestCase\n\nfrom syncer import sync\n\nfrom pyppeteer import launch\n\nroot_path = Path(__file__).resolve().parent\nblank_png_path = root_path / 'blank_800x600.png'\nblank_pdf_path = root_path / 'blank.pdf'\n\n\nclass TestScreenShot(TestCase):\n    def setUp(self):\n        self.browser = sync(launch(args=['--no-sandbox']))\n        self.target_path = Path(__file__).resolve().parent / 'test.png'\n        if self.target_path.exists():\n            self.target_path.unlink()\n\n    def tearDown(self):\n        if self.target_path.exists():\n            self.target_path.unlink()\n        sync(self.browser.close())\n\n    @sync\n    async def test_screenshot(self):\n        page = await self.browser.newPage()\n        await page.goto('about:blank')\n        options = {'path': str(self.target_path)}\n        self.assertFalse(self.target_path.exists())\n        await page.screenshot(options)\n        self.assertTrue(self.target_path.exists())\n\n        with self.target_path.open('rb') as f:\n            result = f.read()\n        with blank_png_path.open('rb') as f:\n            sample = f.read()\n        self.assertEqual(result, sample)\n\n    @sync\n    async def test_screenshot_binary(self):\n        page = await self.browser.newPage()\n        await page.goto('about:blank')\n        result = await page.screenshot()\n        with blank_png_path.open('rb') as f:\n            sample = f.read()\n        self.assertEqual(result, sample)\n\n    @sync\n    async def test_screenshot_base64(self):\n        page = await self.browser.newPage()\n        await page.goto('about:blank')\n        options = {'encoding': 'base64'}\n        result = await page.screenshot(options)\n        with blank_png_path.open('rb') as f:\n            sample = f.read()\n        self.assertEqual(base64.b64decode(result), sample)\n\n    @sync\n    async def test_screenshot_element(self):\n        page = await self.browser.newPage()\n        await page.goto('http://example.com')\n        element = await page.J('h1')\n        options = {'path': str(self.target_path)}\n        self.assertFalse(self.target_path.exists())\n        await element.screenshot(options)\n        self.assertTrue(self.target_path.exists())\n\n    @sync\n    async def test_unresolved_mimetype(self):\n        page = await self.browser.newPage()\n        await page.goto('about:blank')\n        options = {'path': 'example.unsupported'}\n        with self.assertRaises(ValueError, msg='mime type: unsupported'):\n            await page.screenshot(options)\n\n\nclass TestPDF(TestCase):\n    def setUp(self):\n        self.browser = sync(launch(args=['--no-sandbox']))\n        self.target_path = Path(__file__).resolve().parent / 'test.pdf'\n        if self.target_path.exists():\n            self.target_path.unlink()\n\n    @sync\n    async def test_pdf(self):\n        page = await self.browser.newPage()\n        await page.goto('about:blank')\n        self.assertFalse(self.target_path.exists())\n        await page.pdf(path=str(self.target_path))\n        self.assertTrue(self.target_path.exists())\n        self.assertTrue(self.target_path.stat().st_size >= 800)\n\n    def tearDown(self):\n        if self.target_path.exists:\n            self.target_path.unlink()\n        sync(self.browser.close())\n"
  },
  {
    "path": "tests/test_target.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport asyncio\nimport unittest\n\nfrom syncer import sync\n\nfrom .base import BaseTestCase\n\n\nclass TestTarget(BaseTestCase):\n    @sync\n    async def test_targets(self):\n        targets = self.browser.targets()\n        _list = [target for target in targets\n                 if target.type == 'page' and target.url == 'about:blank']\n        self.assertTrue(any(_list))\n        target_types = [t.type for t in targets]\n        self.assertIn('browser', target_types)\n\n    @sync\n    async def test_return_all_pages(self):\n        pages = await self.context.pages()\n        self.assertEqual(len(pages), 1)\n        self.assertIn(self.page, pages)\n\n    @sync\n    async def test_browser_target(self):\n        targets = self.browser.targets()\n        browserTarget = [t for t in targets if t.type == 'browser']\n        self.assertTrue(browserTarget)\n\n    @sync\n    async def test_default_page(self):\n        pages = await self.browser.pages()\n        page = [page for page in pages if page != self.page][0]\n        self.assertEqual(await page.evaluate('[\"Hello\", \"world\"].join(\" \")'),\n                         'Hello world')\n        self.assertTrue(await page.J('body'))\n\n    @sync\n    async def test_report_new_page(self):\n        otherPagePromise = asyncio.get_event_loop().create_future()\n        self.context.once('targetcreated',\n                          lambda target: otherPagePromise.set_result(target))\n        await self.page.evaluate(\n            'url => window.open(url)',\n            'http://127.0.0.1:{}'.format(self.port))\n        otherPage = await (await otherPagePromise).page()\n\n        self.assertIn('127.0.0.1', otherPage.url)\n        self.assertEqual(\n            await otherPage.evaluate('[\"Hello\", \"world\"].join(\" \")'),\n            'Hello world')\n        self.assertTrue(await otherPage.J('body'))\n\n        pages = await self.context.pages()\n        self.assertIn(self.page, pages)\n        self.assertIn(otherPage, pages)\n\n        closePagePromise = asyncio.get_event_loop().create_future()\n\n        async def get_close_page(target):\n            page = await target.page()\n            closePagePromise.set_result(page)\n\n        self.context.once('targetdestroyed',\n                          lambda t: asyncio.ensure_future(get_close_page(t)))\n        await otherPage.close()\n        self.assertEqual(await closePagePromise, otherPage)\n\n        pages = await self.context.pages()\n        self.assertIn(self.page, pages)\n        self.assertNotIn(otherPage, pages)\n\n    @sync\n    async def test_report_service_worker(self):\n        await self.page.goto(self.url + 'empty')\n        createdTargetPromise = asyncio.get_event_loop().create_future()\n        self.context.once('targetcreated',\n                          lambda t: createdTargetPromise.set_result(t))\n\n        await self.page.goto(self.url + 'static/serviceworkers/empty/sw.html')\n        createdTarget = await createdTargetPromise\n        self.assertEqual(createdTarget.type, 'service_worker')\n        self.assertEqual(\n            createdTarget.url, self.url + 'static/serviceworkers/empty/sw.js')\n\n        destroyedTargetPromise = asyncio.get_event_loop().create_future()\n        self.context.once('targetdestroyed',\n                          lambda t: destroyedTargetPromise.set_result(t))\n        await self.page.evaluate(\n            '() => window.registrationPromise.then(reg => reg.unregister())')\n        destroyedTarget = await destroyedTargetPromise\n        self.assertEqual(destroyedTarget, createdTarget)\n\n    @sync\n    async def test_url_change(self):\n        await self.page.goto(self.url + 'empty')\n\n        changedTargetPromise = asyncio.get_event_loop().create_future()\n        self.context.once('targetchanged',\n                          lambda t: changedTargetPromise.set_result(t))\n        await self.page.goto('http://127.0.0.1:{}/'.format(self.port))\n        changedTarget = await changedTargetPromise\n        self.assertEqual(changedTarget.url,\n                         'http://127.0.0.1:{}/'.format(self.port))\n\n        changedTargetPromise = asyncio.get_event_loop().create_future()\n        self.context.once('targetchanged',\n                          lambda t: changedTargetPromise.set_result(t))\n        await self.page.goto(self.url + 'empty')\n        changedTarget = await changedTargetPromise\n        self.assertEqual(changedTarget.url, self.url + 'empty')\n\n    @sync\n    async def test_not_report_uninitialized_page(self):\n        changedTargets = []\n\n        def listener(target):\n            changedTargets.append(target)\n\n        self.context.on('targetchanged', listener)\n\n        targetPromise = asyncio.get_event_loop().create_future()\n        self.context.once('targetcreated',\n                          lambda t: targetPromise.set_result(t))\n        newPagePromise = asyncio.ensure_future(self.context.newPage())\n        target = await targetPromise\n        self.assertEqual(target.url, 'about:blank')\n\n        newPage = await newPagePromise\n        targetPromise2 = asyncio.get_event_loop().create_future()\n        self.context.once('targetcreated',\n                          lambda t: targetPromise2.set_result(t))\n        evaluatePromise = asyncio.ensure_future(\n            newPage.evaluate('window.open(\"about:blank\")'))\n        target2 = await targetPromise2\n        self.assertEqual(target2.url, 'about:blank')\n        await evaluatePromise\n        await newPage.close()\n\n        self.assertFalse(changedTargets)\n        self.context.remove_listener('targetchanged', listener)\n\n        # cleanup\n        await (await target2.page()).close()\n\n    @unittest.skip('Need server-side implementation')\n    @sync\n    async def test_crash_while_redirect(self):\n        pass\n\n    @sync\n    async def test_opener(self):\n        await self.page.goto(self.url + 'empty')\n        targetPromise = asyncio.get_event_loop().create_future()\n        self.context.once('targetcreated',\n                          lambda target: targetPromise.set_result(target))\n        await self.page.goto(self.url + 'static/popup/window-open.html')\n        createdTarget = await targetPromise\n        self.assertEqual(\n            (await createdTarget.page()).url,\n            self.url + 'static/popup/popup.html',\n        )\n        self.assertEqual(createdTarget.opener, self.page.target)\n        self.assertIsNone(self.page.target.opener)\n        await (await createdTarget.page()).close()\n"
  },
  {
    "path": "tests/test_tracing.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport json\nfrom pathlib import Path\nimport unittest\n\nfrom syncer import sync\n\nfrom pyppeteer.errors import NetworkError\n\nfrom .base import BaseTestCase\n\n\nclass TestTracing(BaseTestCase):\n    def setUp(self):\n        self.outfile = Path(__file__).parent / 'trace.json'\n        if self.outfile.is_file():\n            self.outfile.unlink()\n        super().setUp()\n\n    def tearDown(self):\n        if self.outfile.is_file():\n            self.outfile.unlink()\n        super().tearDown()\n\n    @sync\n    async def test_tracing(self):\n        await self.page.tracing.start({\n            'path': str(self.outfile)\n        })\n        await self.page.goto(self.url)\n        await self.page.tracing.stop()\n        self.assertTrue(self.outfile.is_file())\n\n    @sync\n    async def test_custom_categories(self):\n        await self.page.tracing.start({\n            'path': str(self.outfile),\n            'categories': ['disabled-by-default-v8.cpu_profiler.hires'],\n        })\n        await self.page.tracing.stop()\n        self.assertTrue(self.outfile.is_file())\n        with self.outfile.open() as f:\n            trace_json = json.load(f)\n        self.assertIn(\n            'disabled-by-default-v8.cpu_profiler.hires',\n            trace_json['metadata']['trace-config'],\n        )\n\n    @sync\n    async def test_tracing_two_page_error(self):\n        await self.page.tracing.start({'path': str(self.outfile)})\n        new_page = await self.browser.newPage()\n        with self.assertRaises(NetworkError):\n            await new_page.tracing.start({'path': str(self.outfile)})\n        await new_page.close()\n        await self.page.tracing.stop()\n\n    @sync\n    async def test_return_buffer(self):\n        await self.page.tracing.start(screenshots=True, path=str(self.outfile))\n        await self.page.goto(self.url + 'static/grid.html')\n        trace = await self.page.tracing.stop()\n        with self.outfile.open('r') as f:\n            buf = f.read()\n        self.assertEqual(trace, buf)\n\n    @unittest.skip('Not implemented')\n    @sync\n    async def test_return_null_on_error(self):\n        await self.page.tracing.start(screenshots=True)\n        await self.page.goto(self.url + 'static/grid.html')\n\n    @sync\n    async def test_without_path(self):\n        await self.page.tracing.start(screenshots=True)\n        await self.page.goto(self.url + 'static/grid.html')\n        trace = await self.page.tracing.stop()\n        self.assertIn('screenshot', trace)\n"
  },
  {
    "path": "tests/test_worker.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport asyncio\n\nfrom syncer import sync\n\nfrom .base import BaseTestCase\n\n\nclass TestWorker(BaseTestCase):\n    @sync\n    async def test_worker(self):\n        await self.page.goto(self.url + 'static/worker/worker.html')\n        await self.page.waitForFunction('() => !!worker')\n        worker = self.page.workers[0]\n        self.assertIn('worker.js', worker.url)\n        executionContext = await worker.executionContext()\n        self.assertEqual(\n            await executionContext.evaluate('self.workerFunction()'),\n            'worker function result',\n        )\n\n    @sync\n    async def test_create_destroy_events(self):\n        workerCreatedPromise = asyncio.get_event_loop().create_future()\n        self.page.once('workercreated',\n                       lambda w: workerCreatedPromise.set_result(w))\n        workerObj = await self.page.evaluateHandle(\n            '() => new Worker(\"data:text/javascript,1\")')\n        worker = await workerCreatedPromise\n        workerDestroyedPromise = asyncio.get_event_loop().create_future()\n        self.page.once('workerdestroyed',\n                       lambda w: workerDestroyedPromise.set_result(w))\n        await self.page.evaluate(\n            'workerObj => workerObj.terminate()', workerObj)\n        self.assertEqual(await workerDestroyedPromise, worker)\n\n    @sync\n    async def test_report_console_logs(self):\n        logPromise = asyncio.get_event_loop().create_future()\n        self.page.once('console', lambda m: logPromise.set_result(m))\n        await self.page.evaluate(\n            '() => new Worker(\"data:text/javascript,console.log(1)\")'\n        )\n        log = await logPromise\n        self.assertEqual(log.text, '1')\n\n    @sync\n    async def test_jshandle_for_console_log(self):\n        logPromise = asyncio.get_event_loop().create_future()\n        self.page.on('console', lambda m: logPromise.set_result(m))\n        await self.page.evaluate(\n            '() => new Worker(\"data:text/javascript,console.log(1,2,3,this)\")')\n        log = await logPromise\n        self.assertEqual(log.text, '1 2 3 JSHandle@object')\n        self.assertEqual(len(log.args), 4)\n        self.assertEqual(\n            await (await log.args[3].getProperty('origin')).jsonValue(),\n            'null',\n        )\n\n    @sync\n    async def test_execution_context(self):\n        workerCreatedPromise = asyncio.get_event_loop().create_future()\n        self.page.once('workercreated',\n                       lambda w: workerCreatedPromise.set_result(w))\n        await self.page.evaluate(\n            '() => new Worker(\"data:text/javascript,console.log(1)\")')\n        worker = await workerCreatedPromise\n        self.assertEqual(\n            await (await worker.executionContext()).evaluate('1+1'), 2)\n        self.assertEqual(await worker.evaluate('1+2'), 3)\n\n    @sync\n    async def test_report_error(self):\n        errorPromise = asyncio.get_event_loop().create_future()\n        self.page.on('pageerror', lambda x: errorPromise.set_result(x))\n        await self.page.evaluate('() => new Worker(`data:text/javascript, throw new Error(\"this is my error\");`)')  # noqa: E501\n        errorLog = await errorPromise\n        self.assertIn('this is my error', errorLog.args[0])\n"
  },
  {
    "path": "tests/utils.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport asyncio\n\n\ndef waitEvent(emitter, event_name):\n    fut = asyncio.get_event_loop().create_future()\n\n    def set_done(arg=None):\n        fut.set_result(arg)\n\n    emitter.once(event_name, set_done)\n    return fut\n"
  },
  {
    "path": "tox.ini",
    "content": "[tox]\nenvlist = py3{6,7,8},flake8,mypy\nminversion = 3.4.0\nisolated_build = True\n\n[testenv]\npassenv:\n  PYTEST_ADDOPTS\n  CI*\nwhitelist_external =\n  poetry\ndeps =\n  py3{6,7,8},mypy: poetry\nskip_install = true\ncommands_pre:\n  ; mypy config doesn't play well when checking a dir, have to install the package and check it instead\n  py3{6,7,8},mypy: poetry install -vv\n  py3{6,7,8}: poetry run pyppeteer-install\ncommands =\n  py3{6,7,8}: poetry run pytest {posargs}\n\n\n[testenv:flake8]\ndeps =\n  flake8\n  black\ncommands =\n  flake8 ./\n  black --check -S\n\n[testenv:mypy]\npassenv = MYPY_JUNIT_XML_PATH\ndeps = mypy\nwhitelist_externals: poetry\ncommands = mypy pyppeteer --config-file ./tox.ini\n\n[flake8]\nmax-line-length = 120\nexclude = docs,.svn,CVS,.bzr,.hg,.git,__pycache__,.tox,.eggs,*.egg,out\nmax-complexity = 7\n\n[pydocstyle]\nignore = D105,D107,D203,D213,D402,D404\nmatch_dir = (?!(tmp|docs|ja_docs|tests|\\.)).*\n\n[pytest]\n; default number of workers for testing\n; to change parallelism setting, export the PYTEST_ADDOPTS env variable\n; (no need to change the next line, it's overridden by PYTEST_ADDOPTS)\naddopts = -n 6\nminversion = 5.0.0\n; nicely formatted junit output for CI builds (must be enabled with flag: --junitxml=PATH)\njunit_suite_name: pyppeteer tests\njunit_log_passing_tests: True\njunit_family = xunit2\n\n;mypy config\n;only mypy will be paying attention to this\n;(hence the different env var interpolation format)\n[mypy]\n; this line is currently useless until this PR lands: https://github.com/python/mypy/pull/8479\njunit_xml = $MYPY_JUNIT_XML_PATH\nstrict_optional = true\ndisallow_untyped_defs = true\ndisallow_untyped_calls = true\nfollow_imports = silent\nignore_missing_imports = true\nmypy_path = out\n\n[mypy-pyppeteer.tests.*,pyppeteer.docs.*]\nignore_errors = true\n"
  }
]