├── .gitattributes ├── .gitignore ├── .pre-commit-config.yaml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── ROLLING.md ├── SECURITY.md ├── SUPPORT.md ├── conda_build_config_linux_aarch64.yaml ├── conda_build_config_osx_arm64.yaml ├── examples └── todomvc │ ├── mvctests │ ├── __init__.py │ ├── test_clear_completed_button.py │ ├── test_counter.py │ ├── test_editing.py │ ├── test_item.py │ ├── test_mark_all_as_completed.py │ ├── test_new_todo.py │ ├── test_persistence.py │ ├── test_routing.py │ └── utils.py │ └── requirements.txt ├── local-requirements.txt ├── meta.yaml ├── playwright ├── __init__.py ├── __main__.py ├── _impl │ ├── __init__.py │ ├── __pyinstaller │ │ ├── __init__.py │ │ ├── hook-playwright.async_api.py │ │ └── hook-playwright.sync_api.py │ ├── _accessibility.py │ ├── _api_structures.py │ ├── _artifact.py │ ├── _assertions.py │ ├── _async_base.py │ ├── _browser.py │ ├── _browser_context.py │ ├── _browser_type.py │ ├── _cdp_session.py │ ├── _clock.py │ ├── _connection.py │ ├── _console_message.py │ ├── _dialog.py │ ├── _download.py │ ├── _driver.py │ ├── _element_handle.py │ ├── _errors.py │ ├── _event_context_manager.py │ ├── _fetch.py │ ├── _file_chooser.py │ ├── _frame.py │ ├── _glob.py │ ├── _greenlets.py │ ├── _har_router.py │ ├── _helper.py │ ├── _impl_to_api_mapping.py │ ├── _input.py │ ├── _js_handle.py │ ├── _json_pipe.py │ ├── _local_utils.py │ ├── _locator.py │ ├── _map.py │ ├── _network.py │ ├── _object_factory.py │ ├── _page.py │ ├── _path_utils.py │ ├── _playwright.py │ ├── _selectors.py │ ├── _set_input_files_helpers.py │ ├── _str_utils.py │ ├── _stream.py │ ├── _sync_base.py │ ├── _tracing.py │ ├── _transport.py │ ├── _video.py │ ├── _waiter.py │ ├── _web_error.py │ └── _writable_stream.py ├── async_api │ ├── __init__.py │ ├── _context_manager.py │ └── _generated.py ├── py.typed └── sync_api │ ├── __init__.py │ ├── _context_manager.py │ └── _generated.py ├── pyproject.toml ├── scripts ├── documentation_provider.py ├── expected_api_mismatch.txt ├── generate_api.py ├── generate_async_api.py ├── generate_sync_api.py ├── update_api.sh └── update_versions.py ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── assets │ ├── beforeunload.html │ ├── client-certificates │ │ ├── README.md │ │ ├── client │ │ │ ├── self-signed │ │ │ │ ├── cert.pem │ │ │ │ ├── csr.pem │ │ │ │ └── key.pem │ │ │ └── trusted │ │ │ │ ├── cert.pem │ │ │ │ ├── csr.pem │ │ │ │ └── key.pem │ │ └── server │ │ │ ├── server_cert.pem │ │ │ └── server_key.pem │ ├── client.py │ ├── consolelog.html │ ├── csp.html │ ├── digits │ │ ├── 0.png │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ └── 9.png │ ├── dom.html │ ├── download-blob.html │ ├── drag-n-drop.html │ ├── dummy_bad_browser_executable.js │ ├── empty.html │ ├── error.html │ ├── es6 │ │ ├── .eslintrc │ │ ├── es6import.js │ │ ├── es6module.js │ │ └── es6pathimport.js │ ├── file-to-upload-2.txt │ ├── file-to-upload.txt │ ├── frames │ │ ├── child-redirect.html │ │ ├── frame.html │ │ ├── frameset.html │ │ ├── nested-frames.html │ │ ├── one-frame.html │ │ ├── redirect-my-parent.html │ │ ├── script.js │ │ ├── style.css │ │ └── two-frames.html │ ├── geolocation.html │ ├── global-var.html │ ├── grid.html │ ├── har-fulfill.har │ ├── har-redirect.har │ ├── har-sha1-main-response.txt │ ├── har-sha1.har │ ├── har.html │ ├── headings.html │ ├── historyapi.html │ ├── injectedfile.js │ ├── injectedstyle.css │ ├── input │ │ ├── animating-button.html │ │ ├── button.html │ │ ├── checkbox.html │ │ ├── fileupload-multi.html │ │ ├── fileupload.html │ │ ├── folderupload.html │ │ ├── handle-locator.html │ │ ├── keyboard.html │ │ ├── mouse-helper.js │ │ ├── rotatedButton.html │ │ ├── scrollable.html │ │ ├── select.html │ │ ├── textarea.html │ │ └── touches.html │ ├── mobile.html │ ├── networkidle.html │ ├── networkidle.js │ ├── offscreenbuttons.html │ ├── one-style.css │ ├── one-style.html │ ├── playground.html │ ├── popup │ │ ├── popup.html │ │ └── window-open.html │ ├── pptr.png │ ├── react.html │ ├── react │ │ ├── react-dom@16.13.1.production.min.js │ │ └── react@16.13.1.production.min.js │ ├── sectionselectorengine.js │ ├── self-request.html │ ├── serviceworkers │ │ ├── empty │ │ │ ├── sw.html │ │ │ └── sw.js │ │ ├── fetch │ │ │ ├── style.css │ │ │ ├── sw.html │ │ │ └── sw.js │ │ └── fetchdummy │ │ │ ├── sw.html │ │ │ └── sw.js │ ├── shadow.html │ ├── simple-extension │ │ ├── content-script.js │ │ ├── index.js │ │ └── manifest.json │ ├── simple.json │ ├── title.html │ ├── worker │ │ ├── worker.html │ │ └── worker.js │ └── wrappedlink.html ├── async │ ├── __init__.py │ ├── conftest.py │ ├── test_accessibility.py │ ├── test_add_init_script.py │ ├── test_assertions.py │ ├── test_asyncio.py │ ├── test_browser.py │ ├── test_browsercontext.py │ ├── test_browsercontext_add_cookies.py │ ├── test_browsercontext_clearcookies.py │ ├── test_browsercontext_client_certificates.py │ ├── test_browsercontext_cookies.py │ ├── test_browsercontext_events.py │ ├── test_browsercontext_proxy.py │ ├── test_browsercontext_request_fallback.py │ ├── test_browsercontext_request_intercept.py │ ├── test_browsercontext_route.py │ ├── test_browsercontext_service_worker_policy.py │ ├── test_browsercontext_storage_state.py │ ├── test_browsertype_connect.py │ ├── test_browsertype_connect_cdp.py │ ├── test_cdp_session.py │ ├── test_check.py │ ├── test_chromium_tracing.py │ ├── test_click.py │ ├── test_console.py │ ├── test_context_manager.py │ ├── test_defaultbrowsercontext.py │ ├── test_device_descriptors.py │ ├── test_dialog.py │ ├── test_dispatch_event.py │ ├── test_download.py │ ├── test_element_handle.py │ ├── test_element_handle_wait_for_element_state.py │ ├── test_emulation_focus.py │ ├── test_expect_misc.py │ ├── test_fetch_browser_context.py │ ├── test_fetch_global.py │ ├── test_fill.py │ ├── test_focus.py │ ├── test_frames.py │ ├── test_geolocation.py │ ├── test_har.py │ ├── test_headful.py │ ├── test_ignore_https_errors.py │ ├── test_input.py │ ├── test_issues.py │ ├── test_jshandle.py │ ├── test_keyboard.py │ ├── test_launcher.py │ ├── test_listeners.py │ ├── test_locators.py │ ├── test_navigation.py │ ├── test_network.py │ ├── test_page.py │ ├── test_page_add_locator_handler.py │ ├── test_page_aria_snapshot.py │ ├── test_page_base_url.py │ ├── test_page_clock.py │ ├── test_page_evaluate.py │ ├── test_page_network_request.py │ ├── test_page_network_response.py │ ├── test_page_request_fallback.py │ ├── test_page_request_gc.py │ ├── test_page_request_intercept.py │ ├── test_page_route.py │ ├── test_page_select_option.py │ ├── test_pdf.py │ ├── test_popup.py │ ├── test_proxy.py │ ├── test_queryselector.py │ ├── test_request_continue.py │ ├── test_request_fulfill.py │ ├── test_request_intercept.py │ ├── test_resource_timing.py │ ├── test_route_web_socket.py │ ├── test_screenshot.py │ ├── test_selector_generator.py │ ├── test_selectors_get_by.py │ ├── test_selectors_misc.py │ ├── test_selectors_text.py │ ├── test_tap.py │ ├── test_tracing.py │ ├── test_unroute_behavior.py │ ├── test_video.py │ ├── test_wait_for_function.py │ ├── test_wait_for_url.py │ ├── test_websocket.py │ ├── test_worker.py │ └── utils.py ├── common │ ├── __init__.py │ ├── test_collect_handles.py │ ├── test_events.py │ ├── test_signals.py │ └── test_threads.py ├── conftest.py ├── golden-chromium │ ├── grid-cell-0.png │ ├── mask-should-work-with-element-handle.png │ ├── mask-should-work-with-locator.png │ ├── mask-should-work-with-page.png │ ├── mock-binary-response.png │ ├── mock-svg.png │ ├── screenshot-element-bounding-box.png │ └── screenshot-sanity.png ├── golden-firefox │ ├── grid-cell-0.png │ ├── mask-should-work-with-element-handle.png │ ├── mask-should-work-with-locator.png │ ├── mask-should-work-with-page.png │ ├── mock-binary-response.png │ ├── mock-svg.png │ ├── screenshot-element-bounding-box.png │ └── screenshot-sanity.png ├── golden-webkit │ ├── grid-cell-0.png │ ├── mask-should-work-with-element-handle.png │ ├── mask-should-work-with-locator.png │ ├── mask-should-work-with-page.png │ ├── mock-binary-response.png │ ├── mock-svg.png │ ├── screenshot-element-bounding-box.png │ └── screenshot-sanity.png ├── server.py ├── sync │ ├── __init__.py │ ├── conftest.py │ ├── test_accessibility.py │ ├── test_add_init_script.py │ ├── test_assertions.py │ ├── test_browser.py │ ├── test_browsercontext_client_certificates.py │ ├── test_browsercontext_events.py │ ├── test_browsercontext_request_fallback.py │ ├── test_browsercontext_request_intercept.py │ ├── test_browsercontext_service_worker_policy.py │ ├── test_browsercontext_storage_state.py │ ├── test_browsertype_connect.py │ ├── test_browsertype_connect_cdp.py │ ├── test_cdp_session.py │ ├── test_check.py │ ├── test_console.py │ ├── test_context_manager.py │ ├── test_element_handle.py │ ├── test_element_handle_wait_for_element_state.py │ ├── test_expect_misc.py │ ├── test_fetch_browser_context.py │ ├── test_fetch_global.py │ ├── test_fill.py │ ├── test_har.py │ ├── test_input.py │ ├── test_launcher.py │ ├── test_listeners.py │ ├── test_locator_get_by.py │ ├── test_locators.py │ ├── test_network.py │ ├── test_page.py │ ├── test_page_add_locator_handler.py │ ├── test_page_aria_snapshot.py │ ├── test_page_clock.py │ ├── test_page_network_response.py │ ├── test_page_request_fallback.py │ ├── test_page_request_gc.py │ ├── test_page_request_intercept.py │ ├── test_page_select_option.py │ ├── test_pdf.py │ ├── test_queryselector.py │ ├── test_request_fulfill.py │ ├── test_request_intercept.py │ ├── test_resource_timing.py │ ├── test_route_web_socket.py │ ├── test_selectors_misc.py │ ├── test_sync.py │ ├── test_tap.py │ ├── test_tracing.py │ ├── test_unroute_behavior.py │ ├── test_video.py │ └── utils.py ├── test_installation.py ├── test_reference_count_async.py ├── testserver │ ├── cert.pem │ └── key.pem └── utils.py └── utils ├── docker ├── .gitignore ├── Dockerfile.jammy ├── Dockerfile.noble ├── build.sh └── publish_docker.sh └── linting └── check_file_header.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # text files must be lf for golden file tests to work 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/__pycache__/ 2 | driver/ 3 | playwright/driver/ 4 | playwright.egg-info/ 5 | build/ 6 | dist/ 7 | venv/ 8 | .idea/ 9 | **/*.pyc 10 | env/ 11 | htmlcov/ 12 | .coverage* 13 | .DS_Store 14 | .vscode/ 15 | .eggs 16 | _repo_version.py 17 | coverage.xml 18 | junit/ 19 | htmldocs/ 20 | utils/docker/dist/ 21 | Pipfile 22 | Pipfile.lock 23 | .venv/ 24 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v5.0.0 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: end-of-file-fixer 9 | exclude: tests/assets/har-sha1-main-response.txt 10 | - id: check-yaml 11 | - id: check-toml 12 | - id: requirements-txt-fixer 13 | - id: check-ast 14 | - id: check-builtin-literals 15 | - id: check-executables-have-shebangs 16 | - id: check-merge-conflict 17 | - repo: https://github.com/psf/black 18 | rev: 24.8.0 19 | hooks: 20 | - id: black 21 | - repo: https://github.com/pre-commit/mirrors-mypy 22 | rev: v1.11.2 23 | hooks: 24 | - id: mypy 25 | additional_dependencies: [types-pyOpenSSL==24.1.0.20240722, types-requests==2.32.0.20240914] 26 | - repo: https://github.com/pycqa/flake8 27 | rev: 7.1.1 28 | hooks: 29 | - id: flake8 30 | - repo: https://github.com/pycqa/isort 31 | rev: 5.13.2 32 | hooks: 33 | - id: isort 34 | - repo: local 35 | hooks: 36 | - id: pyright 37 | name: pyright 38 | entry: pyright 39 | language: node 40 | pass_filenames: false 41 | types: [python] 42 | additional_dependencies: ["pyright@1.1.384"] 43 | - repo: local 44 | hooks: 45 | - id: check-license-header 46 | name: Check License Header 47 | entry: ./utils/linting/check_file_header.py 48 | language: python 49 | types: [python] 50 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## How to Contribute 4 | 5 | ### Configuring python environment 6 | 7 | The project development requires Python version 3.9+. To set it as default in the environment run the following commands: 8 | 9 | ```sh 10 | # You may need to install python 3.9 venv if it's missing, on Ubuntu just run `sudo apt-get install python3.9-venv` 11 | python3.9 -m venv env 12 | source ./env/bin/activate 13 | ``` 14 | 15 | Install required dependencies: 16 | 17 | ```sh 18 | python -m pip install --upgrade pip 19 | pip install -r local-requirements.txt 20 | ``` 21 | 22 | Build and install drivers: 23 | 24 | ```sh 25 | pip install -e . 26 | python -m build --wheel 27 | ``` 28 | 29 | Run tests: 30 | 31 | ```sh 32 | pytest --browser chromium 33 | ``` 34 | 35 | Checking for typing errors 36 | 37 | ```sh 38 | mypy playwright 39 | ``` 40 | 41 | Format the code 42 | 43 | ```sh 44 | pre-commit install 45 | pre-commit run --all-files 46 | ``` 47 | 48 | For more details look at the [CI configuration](./.github/workflows/ci.yml). 49 | 50 | Collect coverage 51 | 52 | ```sh 53 | pytest --browser chromium --cov-report html --cov=playwright 54 | open htmlcov/index.html 55 | ``` 56 | 57 | ### Regenerating APIs 58 | 59 | ```bash 60 | ./scripts/update_api.sh 61 | pre-commit run --all-files 62 | ``` 63 | 64 | ## Contributor License Agreement 65 | 66 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 67 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 68 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 69 | 70 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 71 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 72 | provided by the bot. You will only need to do this once across all repos using our CLA. 73 | 74 | ## Code of Conduct 75 | 76 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 77 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 78 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 79 | -------------------------------------------------------------------------------- /ROLLING.md: -------------------------------------------------------------------------------- 1 | # Rolling Playwright-Python to the latest Playwright driver 2 | 3 | * checkout repo: `git clone https://github.com/microsoft/playwright-python` 4 | * make sure local python is 3.9 5 | * create virtual environment, if don't have one: `python -m venv env` 6 | * activate venv: `source env/bin/activate` 7 | * install all deps: 8 | - `python -m pip install --upgrade pip` 9 | - `pip install -r local-requirements.txt` 10 | - `pre-commit install` 11 | - `pip install -e .` 12 | * change driver version in `setup.py` 13 | * download new driver: `python -m build --wheel` 14 | * generate API: `./scripts/update_api.sh` 15 | * commit changes & send PR 16 | * wait for bots to pass & merge the PR 17 | 18 | 19 | ## Fix typing issues with Playwright ToT 20 | 21 | 1. `cd playwright` 22 | 1. `API_JSON_MODE=1 node utils/doclint/generateApiJson.js > ../playwright-python/playwright/driver/package/api.json` 23 | 1. `./scripts/update_api.sh` 24 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | This project uses GitHub issues to track bugs and feature requests. Please search the [existing issues][gh-issues] before filing new ones to avoid duplicates. For new issues, file your bug or feature request as a new issue using corresponding template. 6 | 7 | For help and questions about using this project, please see the [docs site for Playwright for Python][docs]. 8 | 9 | Join our community [Discord Server][discord-server] to connect with other developers using Playwright and ask questions in our 'help-playwright' forum. 10 | 11 | ## Microsoft Support Policy 12 | 13 | Support for Playwright for Python is limited to the resources listed above. 14 | 15 | [gh-issues]: https://github.com/microsoft/playwright-python/issues/ 16 | [docs]: https://playwright.dev/python/ 17 | [discord-server]: https://aka.ms/playwright/discord 18 | -------------------------------------------------------------------------------- /conda_build_config_linux_aarch64.yaml: -------------------------------------------------------------------------------- 1 | target_platform: 2 | - linux-aarch64 3 | -------------------------------------------------------------------------------- /conda_build_config_osx_arm64.yaml: -------------------------------------------------------------------------------- 1 | target_platform: 2 | - osx-arm64 3 | -------------------------------------------------------------------------------- /examples/todomvc/mvctests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebrowser/rebrowser-playwright-python/a3e543d2dbf27b3b24e022db6a8f935e3adb3004/examples/todomvc/mvctests/__init__.py -------------------------------------------------------------------------------- /examples/todomvc/mvctests/test_clear_completed_button.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from typing import Generator 15 | 16 | import pytest 17 | 18 | from playwright.sync_api import Page, expect 19 | 20 | from .utils import TODO_ITEMS, create_default_todos 21 | 22 | 23 | @pytest.fixture(autouse=True) 24 | def run_around_tests(page: Page) -> Generator[None, None, None]: 25 | # setup before a test 26 | page.goto("https://demo.playwright.dev/todomvc") 27 | create_default_todos(page) 28 | # run the actual test 29 | yield 30 | # run any cleanup code 31 | 32 | 33 | def test_should_display_the_correct_text(page: Page) -> None: 34 | page.locator(".todo-list li .toggle").first.check() 35 | expect(page.locator(".clear-completed")).to_have_text("Clear completed") 36 | 37 | 38 | def test_should_clear_completed_items_when_clicked(page: Page) -> None: 39 | todo_items = page.locator(".todo-list li") 40 | todo_items.nth(1).locator(".toggle").check() 41 | page.locator(".clear-completed").click() 42 | expect(todo_items).to_have_count(2) 43 | expect(todo_items).to_have_text([TODO_ITEMS[0], TODO_ITEMS[2]]) 44 | 45 | 46 | def test_should_be_hidden_when_there_are_no_items_that_are_completed( 47 | page: Page, 48 | ) -> None: 49 | page.locator(".todo-list li .toggle").first.check() 50 | page.locator(".clear-completed").click() 51 | expect(page.locator(".clear-completed")).to_be_hidden() 52 | -------------------------------------------------------------------------------- /examples/todomvc/mvctests/test_counter.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from typing import Generator 15 | 16 | import pytest 17 | 18 | from playwright.sync_api import Page, expect 19 | 20 | from .utils import TODO_ITEMS, assert_number_of_todos_in_local_storage 21 | 22 | 23 | @pytest.fixture(autouse=True) 24 | def run_around_tests(page: Page) -> Generator[None, None, None]: 25 | # setup before a test 26 | page.goto("https://demo.playwright.dev/todomvc") 27 | # run the actual test 28 | yield 29 | # run any cleanup code 30 | 31 | 32 | def test_should_display_the_current_number_of_todo_items(page: Page) -> None: 33 | page.locator(".new-todo").fill(TODO_ITEMS[0]) 34 | page.locator(".new-todo").press("Enter") 35 | expect(page.locator(".todo-count")).to_contain_text("1") 36 | 37 | page.locator(".new-todo").fill(TODO_ITEMS[1]) 38 | page.locator(".new-todo").press("Enter") 39 | expect(page.locator(".todo-count")).to_contain_text("2") 40 | 41 | assert_number_of_todos_in_local_storage(page, 2) 42 | -------------------------------------------------------------------------------- /examples/todomvc/mvctests/test_persistence.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from typing import Generator 15 | 16 | import pytest 17 | 18 | from playwright.sync_api import Page, expect 19 | 20 | from .utils import TODO_ITEMS, check_number_of_completed_todos_in_local_storage 21 | 22 | 23 | @pytest.fixture(autouse=True) 24 | def run_around_tests(page: Page) -> Generator[None, None, None]: 25 | # setup before a test 26 | page.goto("https://demo.playwright.dev/todomvc") 27 | # run the actual test 28 | yield 29 | # run any cleanup code 30 | 31 | 32 | def test_should_persist_its_data(page: Page) -> None: 33 | for item in TODO_ITEMS[:2]: 34 | page.locator(".new-todo").fill(item) 35 | page.locator(".new-todo").press("Enter") 36 | 37 | todo_items = page.locator(".todo-list li") 38 | todo_items.nth(0).locator(".toggle").check() 39 | expect(todo_items).to_have_text([TODO_ITEMS[0], TODO_ITEMS[1]]) 40 | expect(todo_items).to_have_class(["completed", ""]) 41 | 42 | # Ensure there is 1 completed item. 43 | check_number_of_completed_todos_in_local_storage(page, 1) 44 | 45 | # Now reload. 46 | page.reload() 47 | expect(todo_items).to_have_text([TODO_ITEMS[0], TODO_ITEMS[1]]) 48 | expect(todo_items).to_have_class(["completed", ""]) 49 | -------------------------------------------------------------------------------- /examples/todomvc/mvctests/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from playwright.sync_api import Page 15 | 16 | TODO_ITEMS = ["buy some cheese", "feed the cat", "book a doctors appointment"] 17 | 18 | 19 | def create_default_todos(page: Page) -> None: 20 | for item in TODO_ITEMS: 21 | page.locator(".new-todo").fill(item) 22 | page.locator(".new-todo").press("Enter") 23 | 24 | 25 | def check_number_of_completed_todos_in_local_storage(page: Page, expected: int) -> None: 26 | assert ( 27 | page.evaluate( 28 | "JSON.parse(localStorage['react-todos']).filter(i => i.completed).length" 29 | ) 30 | == expected 31 | ) 32 | 33 | 34 | def assert_number_of_todos_in_local_storage(page: Page, expected: int) -> None: 35 | assert len(page.evaluate("JSON.parse(localStorage['react-todos'])")) == expected 36 | 37 | 38 | def check_todos_in_local_storage(page: Page, title: str) -> None: 39 | assert title in page.evaluate( 40 | "JSON.parse(localStorage['react-todos']).map(i => i.title)" 41 | ) 42 | -------------------------------------------------------------------------------- /examples/todomvc/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest-playwright 2 | -------------------------------------------------------------------------------- /local-requirements.txt: -------------------------------------------------------------------------------- 1 | autobahn==23.1.2 2 | black==24.8.0 3 | build==1.2.2.post1 4 | flake8==7.1.1 5 | flaky==3.8.1 6 | mypy==1.13.0 7 | objgraph==3.6.2 8 | Pillow==10.4.0 9 | pixelmatch==0.3.0 10 | pre-commit==3.5.0 11 | pyOpenSSL==24.2.1 12 | pytest==8.3.3 13 | pytest-asyncio==0.24.0 14 | pytest-cov==6.0.0 15 | pytest-repeat==0.9.3 16 | pytest-timeout==2.3.1 17 | pytest-xdist==3.6.1 18 | requests==2.32.3 19 | service_identity==24.2.0 20 | twisted==24.10.0 21 | types-pyOpenSSL==24.1.0.20240722 22 | types-requests==2.32.0.20241016 23 | -------------------------------------------------------------------------------- /meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: playwright 3 | version: "{{ environ.get('GIT_DESCRIBE_TAG') | replace('v', '') }}" 4 | 5 | source: 6 | path: . 7 | 8 | build: 9 | number: 0 10 | script: "{{ PYTHON }} -m pip install . --no-deps -vv" 11 | binary_relocation: False 12 | missing_dso_whitelist: "*" 13 | entry_points: 14 | - playwright = playwright.__main__:main 15 | 16 | requirements: 17 | build: 18 | - python >=3.9 # [build_platform != target_platform] 19 | - pip # [build_platform != target_platform] 20 | - cross-python_{{ target_platform }} # [build_platform != target_platform] 21 | host: 22 | - python >=3.9 23 | - wheel 24 | - pip 25 | - curl 26 | - setuptools_scm 27 | run: 28 | - python >=3.9 29 | - greenlet ==3.1.1 30 | - pyee ==12.0.0 31 | 32 | test: # [build_platform == target_platform] 33 | requires: 34 | - pip 35 | imports: 36 | - playwright 37 | - playwright.sync_api 38 | - playwright.async_api 39 | commands: 40 | - playwright --help 41 | 42 | about: 43 | home: https://github.com/microsoft/playwright-python 44 | license: Apache-2.0 45 | license_family: Apache 46 | license_file: LICENSE 47 | summary: Python version of the Playwright testing and automation library. 48 | description: | 49 | Playwright is a Python library to automate Chromium, 50 | Firefox and WebKit browsers with a single API. Playwright 51 | delivers automation that is ever-green, capable, reliable 52 | and fast. 53 | doc_url: https://playwright.dev/python/docs/intro/ 54 | dev_url: https://github.com/microsoft/playwright-python 55 | -------------------------------------------------------------------------------- /playwright/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | Python package `playwright` is a Python library to automate Chromium, 17 | Firefox and WebKit with a single API. Playwright is built to enable cross-browser 18 | web automation that is ever-green, capable, reliable and fast. 19 | """ 20 | -------------------------------------------------------------------------------- /playwright/__main__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import subprocess 16 | import sys 17 | 18 | from playwright._impl._driver import compute_driver_executable, get_driver_env 19 | 20 | 21 | def main() -> None: 22 | try: 23 | driver_executable, driver_cli = compute_driver_executable() 24 | completed_process = subprocess.run( 25 | [driver_executable, driver_cli, *sys.argv[1:]], env=get_driver_env() 26 | ) 27 | sys.exit(completed_process.returncode) 28 | except KeyboardInterrupt: 29 | sys.exit(130) 30 | 31 | 32 | if __name__ == "__main__": 33 | main() 34 | -------------------------------------------------------------------------------- /playwright/_impl/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebrowser/rebrowser-playwright-python/a3e543d2dbf27b3b24e022db6a8f935e3adb3004/playwright/_impl/__init__.py -------------------------------------------------------------------------------- /playwright/_impl/__pyinstaller/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | from typing import List 17 | 18 | 19 | def get_hook_dirs() -> List[str]: 20 | return [os.path.dirname(__file__)] 21 | -------------------------------------------------------------------------------- /playwright/_impl/__pyinstaller/hook-playwright.async_api.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from PyInstaller.utils.hooks import collect_data_files # type: ignore 16 | 17 | datas = collect_data_files("playwright") 18 | -------------------------------------------------------------------------------- /playwright/_impl/__pyinstaller/hook-playwright.sync_api.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from PyInstaller.utils.hooks import collect_data_files # type: ignore 16 | 17 | datas = collect_data_files("playwright") 18 | -------------------------------------------------------------------------------- /playwright/_impl/_accessibility.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import Dict, Optional 16 | 17 | from playwright._impl._connection import Channel 18 | from playwright._impl._element_handle import ElementHandle 19 | from playwright._impl._helper import locals_to_params 20 | 21 | 22 | def _ax_node_from_protocol(axNode: Dict) -> Dict: 23 | result = {**axNode} 24 | if "valueNumber" in axNode: 25 | result["value"] = axNode["valueNumber"] 26 | elif "valueString" in axNode: 27 | result["value"] = axNode["valueString"] 28 | 29 | if "checked" in axNode: 30 | result["checked"] = ( 31 | True 32 | if axNode.get("checked") == "checked" 33 | else ( 34 | False if axNode.get("checked") == "unchecked" else axNode.get("checked") 35 | ) 36 | ) 37 | 38 | if "pressed" in axNode: 39 | result["pressed"] = ( 40 | True 41 | if axNode.get("pressed") == "pressed" 42 | else ( 43 | False if axNode.get("pressed") == "released" else axNode.get("pressed") 44 | ) 45 | ) 46 | 47 | if axNode.get("children"): 48 | result["children"] = list(map(_ax_node_from_protocol, axNode["children"])) 49 | if "valueNumber" in result: 50 | del result["valueNumber"] 51 | if "valueString" in result: 52 | del result["valueString"] 53 | return result 54 | 55 | 56 | class Accessibility: 57 | def __init__(self, channel: Channel) -> None: 58 | self._channel = channel 59 | self._loop = channel._connection._loop 60 | self._dispatcher_fiber = channel._connection._dispatcher_fiber 61 | 62 | async def snapshot( 63 | self, interestingOnly: bool = None, root: ElementHandle = None 64 | ) -> Optional[Dict]: 65 | params = locals_to_params(locals()) 66 | if root: 67 | params["root"] = root._channel 68 | result = await self._channel.send("accessibilitySnapshot", params) 69 | return _ax_node_from_protocol(result) if result else None 70 | -------------------------------------------------------------------------------- /playwright/_impl/_artifact.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import pathlib 16 | from pathlib import Path 17 | from typing import Dict, Optional, Union, cast 18 | 19 | from playwright._impl._connection import ChannelOwner, from_channel 20 | from playwright._impl._helper import Error, make_dirs_for_file, patch_error_message 21 | from playwright._impl._stream import Stream 22 | 23 | 24 | class Artifact(ChannelOwner): 25 | def __init__( 26 | self, parent: ChannelOwner, type: str, guid: str, initializer: Dict 27 | ) -> None: 28 | super().__init__(parent, type, guid, initializer) 29 | self.absolute_path = initializer["absolutePath"] 30 | 31 | async def path_after_finished(self) -> pathlib.Path: 32 | if self._connection.is_remote: 33 | raise Error( 34 | "Path is not available when using browser_type.connect(). Use save_as() to save a local copy." 35 | ) 36 | path = await self._channel.send("pathAfterFinished") 37 | return pathlib.Path(path) 38 | 39 | async def save_as(self, path: Union[str, Path]) -> None: 40 | stream = cast(Stream, from_channel(await self._channel.send("saveAsStream"))) 41 | make_dirs_for_file(path) 42 | await stream.save_as(path) 43 | 44 | async def failure(self) -> Optional[str]: 45 | reason = await self._channel.send("failure") 46 | if reason is None: 47 | return None 48 | return patch_error_message(reason) 49 | 50 | async def delete(self) -> None: 51 | await self._channel.send("delete") 52 | 53 | async def read_info_buffer(self) -> bytes: 54 | stream = cast(Stream, from_channel(await self._channel.send("stream"))) 55 | buffer = await stream.read_all() 56 | return buffer 57 | 58 | async def cancel(self) -> None: 59 | await self._channel.send("cancel") 60 | -------------------------------------------------------------------------------- /playwright/_impl/_cdp_session.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import Any, Dict 16 | 17 | from playwright._impl._connection import ChannelOwner 18 | from playwright._impl._helper import locals_to_params 19 | 20 | 21 | class CDPSession(ChannelOwner): 22 | def __init__( 23 | self, parent: ChannelOwner, type: str, guid: str, initializer: Dict 24 | ) -> None: 25 | super().__init__(parent, type, guid, initializer) 26 | self._channel.on("event", lambda params: self._on_event(params)) 27 | 28 | def _on_event(self, params: Any) -> None: 29 | self.emit(params["method"], params.get("params")) 30 | 31 | async def send(self, method: str, params: Dict = None) -> Dict: 32 | return await self._channel.send("send", locals_to_params(locals())) 33 | 34 | async def detach(self) -> None: 35 | await self._channel.send("detach") 36 | -------------------------------------------------------------------------------- /playwright/_impl/_console_message.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from asyncio import AbstractEventLoop 16 | from typing import TYPE_CHECKING, Any, Dict, List, Optional 17 | 18 | from playwright._impl._api_structures import SourceLocation 19 | from playwright._impl._connection import from_channel, from_nullable_channel 20 | from playwright._impl._js_handle import JSHandle 21 | 22 | if TYPE_CHECKING: # pragma: no cover 23 | from playwright._impl._page import Page 24 | 25 | 26 | class ConsoleMessage: 27 | def __init__( 28 | self, event: Dict, loop: AbstractEventLoop, dispatcher_fiber: Any 29 | ) -> None: 30 | self._event = event 31 | self._loop = loop 32 | self._dispatcher_fiber = dispatcher_fiber 33 | self._page: Optional["Page"] = from_nullable_channel(event.get("page")) 34 | 35 | def __repr__(self) -> str: 36 | return f"" 37 | 38 | def __str__(self) -> str: 39 | return self.text 40 | 41 | @property 42 | def type(self) -> str: 43 | return self._event["type"] 44 | 45 | @property 46 | def text(self) -> str: 47 | return self._event["text"] 48 | 49 | @property 50 | def args(self) -> List[JSHandle]: 51 | return list(map(from_channel, self._event["args"])) 52 | 53 | @property 54 | def location(self) -> SourceLocation: 55 | return self._event["location"] 56 | 57 | @property 58 | def page(self) -> Optional["Page"]: 59 | return self._page 60 | -------------------------------------------------------------------------------- /playwright/_impl/_dialog.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import TYPE_CHECKING, Dict, Optional 16 | 17 | from playwright._impl._connection import ChannelOwner, from_nullable_channel 18 | from playwright._impl._helper import locals_to_params 19 | 20 | if TYPE_CHECKING: # pragma: no cover 21 | from playwright._impl._page import Page 22 | 23 | 24 | class Dialog(ChannelOwner): 25 | def __init__( 26 | self, parent: ChannelOwner, type: str, guid: str, initializer: Dict 27 | ) -> None: 28 | super().__init__(parent, type, guid, initializer) 29 | self._page: Optional["Page"] = from_nullable_channel(initializer.get("page")) 30 | 31 | def __repr__(self) -> str: 32 | return f"" 33 | 34 | @property 35 | def type(self) -> str: 36 | return self._initializer["type"] 37 | 38 | @property 39 | def message(self) -> str: 40 | return self._initializer["message"] 41 | 42 | @property 43 | def default_value(self) -> str: 44 | return self._initializer["defaultValue"] 45 | 46 | @property 47 | def page(self) -> Optional["Page"]: 48 | return self._page 49 | 50 | async def accept(self, promptText: str = None) -> None: 51 | await self._channel.send("accept", locals_to_params(locals())) 52 | 53 | async def dismiss(self) -> None: 54 | await self._channel.send("dismiss") 55 | -------------------------------------------------------------------------------- /playwright/_impl/_download.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import pathlib 16 | from pathlib import Path 17 | from typing import TYPE_CHECKING, Optional, Union 18 | 19 | from playwright._impl._artifact import Artifact 20 | 21 | if TYPE_CHECKING: # pragma: no cover 22 | from playwright._impl._page import Page 23 | 24 | 25 | class Download: 26 | def __init__( 27 | self, page: "Page", url: str, suggested_filename: str, artifact: Artifact 28 | ) -> None: 29 | self._page = page 30 | self._loop = page._loop 31 | self._dispatcher_fiber = page._dispatcher_fiber 32 | self._url = url 33 | self._suggested_filename = suggested_filename 34 | self._artifact = artifact 35 | 36 | def __repr__(self) -> str: 37 | return f"" 38 | 39 | @property 40 | def page(self) -> "Page": 41 | return self._page 42 | 43 | @property 44 | def url(self) -> str: 45 | return self._url 46 | 47 | @property 48 | def suggested_filename(self) -> str: 49 | return self._suggested_filename 50 | 51 | async def delete(self) -> None: 52 | await self._artifact.delete() 53 | 54 | async def failure(self) -> Optional[str]: 55 | return await self._artifact.failure() 56 | 57 | async def path(self) -> pathlib.Path: 58 | return await self._artifact.path_after_finished() 59 | 60 | async def save_as(self, path: Union[str, Path]) -> None: 61 | await self._artifact.save_as(path) 62 | 63 | async def cancel(self) -> None: 64 | return await self._artifact.cancel() 65 | -------------------------------------------------------------------------------- /playwright/_impl/_driver.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import inspect 16 | import os 17 | import sys 18 | from pathlib import Path 19 | from typing import Tuple 20 | 21 | import playwright 22 | from playwright._repo_version import version 23 | 24 | 25 | def compute_driver_executable() -> Tuple[str, str]: 26 | driver_path = Path(inspect.getfile(playwright)).parent / "driver" 27 | cli_path = str(driver_path / "package" / "cli.js") 28 | if sys.platform == "win32": 29 | return ( 30 | os.getenv("PLAYWRIGHT_NODEJS_PATH", str(driver_path / "node.exe")), 31 | cli_path, 32 | ) 33 | return (os.getenv("PLAYWRIGHT_NODEJS_PATH", str(driver_path / "node")), cli_path) 34 | 35 | 36 | def get_driver_env() -> dict: 37 | env = os.environ.copy() 38 | env["PW_LANG_NAME"] = "python" 39 | env["PW_LANG_NAME_VERSION"] = f"{sys.version_info.major}.{sys.version_info.minor}" 40 | env["PW_CLI_DISPLAY_VERSION"] = version 41 | return env 42 | -------------------------------------------------------------------------------- /playwright/_impl/_errors.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # These are types that we use in the API. They are public and are a part of the 16 | # stable API. 17 | 18 | 19 | from typing import Optional 20 | 21 | 22 | def is_target_closed_error(error: Exception) -> bool: 23 | return isinstance(error, TargetClosedError) 24 | 25 | 26 | class Error(Exception): 27 | def __init__(self, message: str) -> None: 28 | self._message = message 29 | self._name: Optional[str] = None 30 | self._stack: Optional[str] = None 31 | super().__init__(message) 32 | 33 | @property 34 | def message(self) -> str: 35 | return self._message 36 | 37 | @property 38 | def name(self) -> Optional[str]: 39 | return self._name 40 | 41 | @property 42 | def stack(self) -> Optional[str]: 43 | return self._stack 44 | 45 | 46 | class TimeoutError(Error): 47 | pass 48 | 49 | 50 | class TargetClosedError(Error): 51 | def __init__(self, message: str = None) -> None: 52 | super().__init__(message or "Target page, context or browser has been closed") 53 | 54 | 55 | def rewrite_error(error: Exception, message: str) -> Exception: 56 | rewritten_exc = type(error)(message) 57 | if isinstance(rewritten_exc, Error) and isinstance(error, Error): 58 | rewritten_exc._name = error.name 59 | rewritten_exc._stack = error.stack 60 | return rewritten_exc 61 | -------------------------------------------------------------------------------- /playwright/_impl/_event_context_manager.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import asyncio 16 | from typing import Any, Generic, TypeVar 17 | 18 | T = TypeVar("T") 19 | 20 | 21 | class EventContextManagerImpl(Generic[T]): 22 | def __init__(self, future: asyncio.Future) -> None: 23 | self._future: asyncio.Future = future 24 | 25 | @property 26 | def future(self) -> asyncio.Future: 27 | return self._future 28 | 29 | async def __aenter__(self) -> asyncio.Future: 30 | return self._future 31 | 32 | async def __aexit__(self, *args: Any) -> None: 33 | await self._future 34 | -------------------------------------------------------------------------------- /playwright/_impl/_file_chooser.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from pathlib import Path 16 | from typing import TYPE_CHECKING, Sequence, Union 17 | 18 | from playwright._impl._api_structures import FilePayload 19 | 20 | if TYPE_CHECKING: # pragma: no cover 21 | from playwright._impl._element_handle import ElementHandle 22 | from playwright._impl._page import Page 23 | 24 | 25 | class FileChooser: 26 | def __init__( 27 | self, page: "Page", element_handle: "ElementHandle", is_multiple: bool 28 | ) -> None: 29 | self._page = page 30 | self._loop = page._loop 31 | self._dispatcher_fiber = page._dispatcher_fiber 32 | self._element_handle = element_handle 33 | self._is_multiple = is_multiple 34 | 35 | def __repr__(self) -> str: 36 | return f"" 37 | 38 | @property 39 | def page(self) -> "Page": 40 | return self._page 41 | 42 | @property 43 | def element(self) -> "ElementHandle": 44 | return self._element_handle 45 | 46 | def is_multiple(self) -> bool: 47 | return self._is_multiple 48 | 49 | async def set_files( 50 | self, 51 | files: Union[ 52 | str, Path, FilePayload, Sequence[Union[str, Path]], Sequence[FilePayload] 53 | ], 54 | timeout: float = None, 55 | noWaitAfter: bool = None, 56 | ) -> None: 57 | await self._element_handle.set_input_files(files, timeout, noWaitAfter) 58 | -------------------------------------------------------------------------------- /playwright/_impl/_glob.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | import re 15 | 16 | # https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions#escaping 17 | escaped_chars = {"$", "^", "+", ".", "*", "(", ")", "|", "\\", "?", "{", "}", "[", "]"} 18 | 19 | 20 | def glob_to_regex(glob: str) -> "re.Pattern[str]": 21 | tokens = ["^"] 22 | in_group = False 23 | 24 | i = 0 25 | while i < len(glob): 26 | c = glob[i] 27 | if c == "\\" and i + 1 < len(glob): 28 | char = glob[i + 1] 29 | tokens.append("\\" + char if char in escaped_chars else char) 30 | i += 1 31 | elif c == "*": 32 | before_deep = glob[i - 1] if i > 0 else None 33 | star_count = 1 34 | while i + 1 < len(glob) and glob[i + 1] == "*": 35 | star_count += 1 36 | i += 1 37 | after_deep = glob[i + 1] if i + 1 < len(glob) else None 38 | is_deep = ( 39 | star_count > 1 40 | and (before_deep == "/" or before_deep is None) 41 | and (after_deep == "/" or after_deep is None) 42 | ) 43 | if is_deep: 44 | tokens.append("((?:[^/]*(?:/|$))*)") 45 | i += 1 46 | else: 47 | tokens.append("([^/]*)") 48 | else: 49 | if c == "?": 50 | tokens.append(".") 51 | elif c == "[": 52 | tokens.append("[") 53 | elif c == "]": 54 | tokens.append("]") 55 | elif c == "{": 56 | in_group = True 57 | tokens.append("(") 58 | elif c == "}": 59 | in_group = False 60 | tokens.append(")") 61 | elif c == "," and in_group: 62 | tokens.append("|") 63 | else: 64 | tokens.append("\\" + c if c in escaped_chars else c) 65 | i += 1 66 | 67 | tokens.append("$") 68 | return re.compile("".join(tokens)) 69 | -------------------------------------------------------------------------------- /playwright/_impl/_greenlets.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | import os 15 | from typing import Tuple 16 | 17 | import greenlet 18 | 19 | 20 | def _greenlet_trace_callback( 21 | event: str, args: Tuple[greenlet.greenlet, greenlet.greenlet] 22 | ) -> None: 23 | if event in ("switch", "throw"): 24 | origin, target = args 25 | print(f"Transfer from {origin} to {target} with {event}") 26 | 27 | 28 | if os.environ.get("INTERNAL_PW_GREENLET_DEBUG"): 29 | greenlet.settrace(_greenlet_trace_callback) 30 | 31 | 32 | class MainGreenlet(greenlet.greenlet): 33 | def __str__(self) -> str: 34 | return "" 35 | 36 | 37 | class RouteGreenlet(greenlet.greenlet): 38 | def __str__(self) -> str: 39 | return "" 40 | 41 | 42 | class LocatorHandlerGreenlet(greenlet.greenlet): 43 | def __str__(self) -> str: 44 | return "" 45 | 46 | 47 | class EventGreenlet(greenlet.greenlet): 48 | def __str__(self) -> str: 49 | return "" 50 | -------------------------------------------------------------------------------- /playwright/_impl/_map.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from typing import Dict, Generic, Tuple, TypeVar 15 | 16 | K = TypeVar("K") 17 | V = TypeVar("V") 18 | 19 | 20 | class Map(Generic[K, V]): 21 | def __init__(self) -> None: 22 | self._entries: Dict[int, Tuple[K, V]] = {} 23 | 24 | def __contains__(self, item: K) -> bool: 25 | return id(item) in self._entries 26 | 27 | def __setitem__(self, idx: K, value: V) -> None: 28 | self._entries[id(idx)] = (idx, value) 29 | 30 | def __getitem__(self, obj: K) -> V: 31 | return self._entries[id(obj)][1] 32 | -------------------------------------------------------------------------------- /playwright/_impl/_path_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import inspect 16 | from pathlib import Path 17 | 18 | 19 | def get_file_dirname() -> Path: 20 | """Returns the callee (`__file__`) directory name""" 21 | frame = inspect.stack()[1] 22 | module = inspect.getmodule(frame[0]) 23 | assert module 24 | assert module.__file__ 25 | return Path(module.__file__).parent.absolute() 26 | -------------------------------------------------------------------------------- /playwright/_impl/_playwright.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import Dict 16 | 17 | from playwright._impl._browser_type import BrowserType 18 | from playwright._impl._connection import ChannelOwner, from_channel 19 | from playwright._impl._fetch import APIRequest 20 | from playwright._impl._selectors import Selectors, SelectorsOwner 21 | 22 | 23 | class Playwright(ChannelOwner): 24 | devices: Dict 25 | selectors: Selectors 26 | chromium: BrowserType 27 | firefox: BrowserType 28 | webkit: BrowserType 29 | request: APIRequest 30 | 31 | def __init__( 32 | self, parent: ChannelOwner, type: str, guid: str, initializer: Dict 33 | ) -> None: 34 | super().__init__(parent, type, guid, initializer) 35 | self.request = APIRequest(self) 36 | self.chromium = from_channel(initializer["chromium"]) 37 | self.chromium._playwright = self 38 | self.firefox = from_channel(initializer["firefox"]) 39 | self.firefox._playwright = self 40 | self.webkit = from_channel(initializer["webkit"]) 41 | self.webkit._playwright = self 42 | 43 | self.selectors = Selectors(self._loop, self._dispatcher_fiber) 44 | selectors_owner: SelectorsOwner = from_channel(initializer["selectors"]) 45 | self.selectors._add_channel(selectors_owner) 46 | 47 | self._connection.on( 48 | "close", lambda: self.selectors._remove_channel(selectors_owner) 49 | ) 50 | self.devices = self._connection.local_utils.devices 51 | 52 | def __getitem__(self, value: str) -> "BrowserType": 53 | if value == "chromium": 54 | return self.chromium 55 | elif value == "firefox": 56 | return self.firefox 57 | elif value == "webkit": 58 | return self.webkit 59 | raise ValueError("Invalid browser " + value) 60 | 61 | def _set_selectors(self, selectors: Selectors) -> None: 62 | selectors_owner = from_channel(self._initializer["selectors"]) 63 | self.selectors._remove_channel(selectors_owner) 64 | self.selectors = selectors 65 | self.selectors._add_channel(selectors_owner) 66 | 67 | async def stop(self) -> None: 68 | pass 69 | -------------------------------------------------------------------------------- /playwright/_impl/_str_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import json 16 | import re 17 | from typing import Pattern, Union 18 | 19 | 20 | def escape_regex_flags(pattern: Pattern) -> str: 21 | flags = "" 22 | if pattern.flags != 0: 23 | flags = "" 24 | if (pattern.flags & int(re.IGNORECASE)) != 0: 25 | flags += "i" 26 | if (pattern.flags & int(re.DOTALL)) != 0: 27 | flags += "s" 28 | if (pattern.flags & int(re.MULTILINE)) != 0: 29 | flags += "m" 30 | assert ( 31 | pattern.flags 32 | & ~(int(re.MULTILINE) | int(re.IGNORECASE) | int(re.DOTALL) | int(re.UNICODE)) 33 | == 0 34 | ), "Unexpected re.Pattern flag, only MULTILINE, IGNORECASE and DOTALL are supported." 35 | return flags 36 | 37 | 38 | def escape_for_regex(text: str) -> str: 39 | return re.sub(r"[.*+?^>${}()|[\]\\]", "\\$&", text) 40 | 41 | 42 | def escape_regex_for_selector(text: Pattern) -> str: 43 | # Even number of backslashes followed by the quote -> insert a backslash. 44 | return ( 45 | "/" 46 | + re.sub(r'(^|[^\\])(\\\\)*(["\'`])', r"\1\2\\\3", text.pattern).replace( 47 | ">>", "\\>\\>" 48 | ) 49 | + "/" 50 | + escape_regex_flags(text) 51 | ) 52 | 53 | 54 | def escape_for_text_selector( 55 | text: Union[str, Pattern[str]], exact: bool = None, case_sensitive: bool = None 56 | ) -> str: 57 | if isinstance(text, Pattern): 58 | return escape_regex_for_selector(text) 59 | return json.dumps(text) + ("s" if exact else "i") 60 | 61 | 62 | def escape_for_attribute_selector( 63 | value: Union[str, Pattern], exact: bool = None 64 | ) -> str: 65 | if isinstance(value, Pattern): 66 | return escape_regex_for_selector(value) 67 | # TODO: this should actually be 68 | # cssEscape(value).replace(/\\ /g, ' ') 69 | # However, our attribute selectors do not conform to CSS parsing spec, 70 | # so we escape them differently. 71 | return ( 72 | '"' 73 | + value.replace("\\", "\\\\").replace('"', '\\"') 74 | + '"' 75 | + ("s" if exact else "i") 76 | ) 77 | -------------------------------------------------------------------------------- /playwright/_impl/_stream.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import base64 16 | from pathlib import Path 17 | from typing import Dict, Union 18 | 19 | from playwright._impl._connection import ChannelOwner 20 | 21 | 22 | class Stream(ChannelOwner): 23 | def __init__( 24 | self, parent: ChannelOwner, type: str, guid: str, initializer: Dict 25 | ) -> None: 26 | super().__init__(parent, type, guid, initializer) 27 | 28 | async def save_as(self, path: Union[str, Path]) -> None: 29 | file = await self._loop.run_in_executor(None, lambda: open(path, "wb")) 30 | while True: 31 | binary = await self._channel.send("read", {"size": 1024 * 1024}) 32 | if not binary: 33 | break 34 | await self._loop.run_in_executor( 35 | None, lambda: file.write(base64.b64decode(binary)) 36 | ) 37 | await self._loop.run_in_executor(None, lambda: file.close()) 38 | 39 | async def read_all(self) -> bytes: 40 | binary = b"" 41 | while True: 42 | chunk = await self._channel.send("read", {"size": 1024 * 1024}) 43 | if not chunk: 44 | break 45 | binary += base64.b64decode(chunk) 46 | return binary 47 | -------------------------------------------------------------------------------- /playwright/_impl/_video.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import pathlib 16 | from typing import TYPE_CHECKING, Union 17 | 18 | from playwright._impl._artifact import Artifact 19 | from playwright._impl._helper import Error 20 | 21 | if TYPE_CHECKING: # pragma: no cover 22 | from playwright._impl._page import Page 23 | 24 | 25 | class Video: 26 | def __init__(self, page: "Page") -> None: 27 | self._loop = page._loop 28 | self._dispatcher_fiber = page._dispatcher_fiber 29 | self._page = page 30 | self._artifact_future = page._loop.create_future() 31 | if page.is_closed(): 32 | self._page_closed() 33 | else: 34 | page.on("close", lambda page: self._page_closed()) 35 | 36 | def __repr__(self) -> str: 37 | return f"