From 32e5ce85213722a27e9df0302b34c3eabed9567d Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 19 Jan 2021 12:37:57 -0800 Subject: [PATCH 001/788] chore: nit fixes (#441) --- playwright/_impl/_helper.py | 2 +- playwright/_impl/_page.py | 6 ++++-- tests/async/test_page.py | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/playwright/_impl/_helper.py b/playwright/_impl/_helper.py index f91b9154e..4d1035f06 100644 --- a/playwright/_impl/_helper.py +++ b/playwright/_impl/_helper.py @@ -150,7 +150,7 @@ def navigation_timeout(self) -> float: def serialize_error(ex: Exception, tb: Optional[TracebackType]) -> ErrorPayload: - return dict(message=str(ex), stack="".join(traceback.format_tb(tb))) + return dict(message=str(ex), name="Error", stack="".join(traceback.format_tb(tb))) def parse_error(error: ErrorPayload) -> Error: diff --git a/playwright/_impl/_page.py b/playwright/_impl/_page.py index 51cbacbfd..3afce4b33 100644 --- a/playwright/_impl/_page.py +++ b/playwright/_impl/_page.py @@ -949,11 +949,13 @@ async def call(self, func: Callable) -> None: else: func_args = list(map(parse_result, self._initializer["args"])) result = func(source, *func_args) - if asyncio.isfuture(result): + if inspect.iscoroutine(result): result = await result await self._channel.send("resolve", dict(result=serialize_argument(result))) except Exception as e: tb = sys.exc_info()[2] asyncio.create_task( - self._channel.send("reject", dict(error=serialize_error(e, tb))) + self._channel.send( + "reject", dict(error=dict(error=serialize_error(e, tb))) + ) ) diff --git a/tests/async/test_page.py b/tests/async/test_page.py index dfe58cdb9..01978ce7c 100644 --- a/tests/async/test_page.py +++ b/tests/async/test_page.py @@ -351,7 +351,7 @@ async def test_expose_function_should_await_returned_promise(page): async def mul(a, b): return a * b - await page.expose_function("compute", lambda a, b: asyncio.create_task(mul(a, b))) + await page.expose_function("compute", mul) assert await page.evaluate("compute(3, 5)") == 15 From fcbc86a7c842a6501d022206f8f3fc26b5c19f82 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 19 Jan 2021 12:38:23 -0800 Subject: [PATCH 002/788] feat: add mac universal installer (#442) --- setup.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f8aea2569..5d6f7eac8 100644 --- a/setup.py +++ b/setup.py @@ -95,6 +95,13 @@ def run(self) -> None: from_path = os.path.join(dir_path, file) to_path = os.path.relpath(from_path, driver_root) zip.write(from_path, f"playwright/driver/{to_path}") + if platform == "mac": + # Ship mac both as 10_13 as and 11_0 universal to work across Macs. + universal_location = without_platform + "macosx_11_0_universal2.whl" + shutil.copyfile(wheel_location, universal_location) + with zipfile.ZipFile(universal_location, "a") as zip: + zip.writestr("playwright/driver/README.md", "Universal Mac package") + os.remove(base_wheel_location) @@ -108,7 +115,7 @@ def run(self) -> None: url="https://github.com/Microsoft/playwright-python", packages=["playwright"], include_package_data=True, - install_requires=["greenlet==1.0a1", "pyee>=8.0.1", "typing-extensions"], + install_requires=["greenlet==1.0.0", "pyee>=8.0.1", "typing-extensions"], classifiers=[ "Topic :: Software Development :: Testing", "Topic :: Internet :: WWW/HTTP :: Browsers", From dc8b54b79aac190537a63a9c97ddbd97a0d1c512 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 19 Jan 2021 12:38:47 -0800 Subject: [PATCH 003/788] feat: add cli `playwright` as an entry point. (#388) --- playwright/__main__.py | 18 ++++++++++++------ setup.py | 5 +++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/playwright/__main__.py b/playwright/__main__.py index 8677c47ba..5298192dc 100644 --- a/playwright/__main__.py +++ b/playwright/__main__.py @@ -18,9 +18,15 @@ from playwright._impl._driver import compute_driver_executable -driver_executable = compute_driver_executable() -my_env = os.environ.copy() -# VSCode's JavaScript Debug Terminal provides it but driver/pkg does not support it -my_env.pop("NODE_OPTIONS", None) -my_env["PW_CLI_TARGET_LANG"] = "python" -subprocess.run([str(driver_executable), *sys.argv[1:]], env=my_env) + +def main() -> None: + driver_executable = compute_driver_executable() + my_env = os.environ.copy() + # VSCode's JavaScript Debug Terminal provides it but driver/pkg does not support it + my_env.pop("NODE_OPTIONS", None) + my_env["PW_CLI_TARGET_LANG"] = "python" + subprocess.run([str(driver_executable), *sys.argv[1:]], env=my_env) + + +if __name__ == "__main__": + main() diff --git a/setup.py b/setup.py index 5d6f7eac8..79e7214cd 100644 --- a/setup.py +++ b/setup.py @@ -135,4 +135,9 @@ def run(self) -> None: "write_to_template": 'version = "{version}"\n', }, setup_requires=["setuptools_scm"], + entry_points={ + "console_scripts": [ + "playwright=playwright.__main__:main", + ], + }, ) From 1f2fcd9353a4f0db0f27b4499d764c932e3ddf49 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Tue, 19 Jan 2021 14:03:40 -0800 Subject: [PATCH 004/788] devops: try to publish under playwright namespace in MCR (#445) This patch attempts to publish playwright under `playwright/python` mcr namespace, thus re-using the default `playwrigh` namespace that was already approved. Just to be on the safe side, this disables all image publishing except the one marked with a unique SHA. --- .github/workflows/publish_canary_docker.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish_canary_docker.yml b/.github/workflows/publish_canary_docker.yml index 429537823..18cdde3cf 100644 --- a/.github/workflows/publish_canary_docker.yml +++ b/.github/workflows/publish_canary_docker.yml @@ -37,6 +37,6 @@ jobs: - run: docker build -t playwright-python:localbuild . - name: tag & publish run: | - ./scripts/tag_image_and_push.sh playwright-python:localbuild playwright.azurecr.io/public/playwright-python:next - ./scripts/tag_image_and_push.sh playwright-python:localbuild playwright.azurecr.io/public/playwright-python:next-focal - ./scripts/tag_image_and_push.sh playwright-python:localbuild playwright.azurecr.io/public/playwright-python:sha-${{ github.sha }} + # ./scripts/tag_image_and_push.sh playwright-python:localbuild playwright.azurecr.io/public/playwright/python:next + # ./scripts/tag_image_and_push.sh playwright-python:localbuild playwright.azurecr.io/public/playwright/python:next-focal + ./scripts/tag_image_and_push.sh playwright-python:localbuild playwright.azurecr.io/public/playwright/python:sha-${{ github.sha }} From 383d03291a4360c425a89aab9a91f21d667837a5 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 19 Jan 2021 15:00:35 -0800 Subject: [PATCH 005/788] chore: update the docs for the 1.8a release (#444) --- README.md | 248 +++++++++++------------- playwright/async_api/_generated.py | 6 + playwright/sync_api/_context_manager.py | 5 +- playwright/sync_api/_generated.py | 6 + setup.py | 2 +- tests/async/test_page.py | 5 +- tests/sync/test_sync.py | 5 +- 7 files changed, 133 insertions(+), 144 deletions(-) diff --git a/README.md b/README.md index b528d5b27..8174b743f 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # 🎭 [Playwright](https://playwright.dev) for Python [![PyPI version](https://badge.fury.io/py/playwright.svg)](https://pypi.python.org/pypi/playwright/) [![Join Slack](https://img.shields.io/badge/join-slack-infomational)](https://join.slack.com/t/playwright/shared_invite/enQtOTEyMTUxMzgxMjIwLThjMDUxZmIyNTRiMTJjNjIyMzdmZDA3MTQxZWUwZTFjZjQwNGYxZGM5MzRmNzZlMWI5ZWUyOTkzMjE5Njg1NDg) -#### [Docs](#documentation) | [Website](https://playwright.dev/) | [Python API reference](https://microsoft.github.io/playwright-python/) +#### [Docs](https://playwright.dev/python/docs/intro) | [API](https://playwright.dev/python/docs/api/class-playwright) -Playwright is a Python library to automate [Chromium](https://www.chromium.org/Home), [Firefox](https://www.mozilla.org/en-US/firefox/new/) and [WebKit](https://webkit.org/) browsers with a single API. Playwright delivers automation that is **ever-green**, **capable**, **reliable** and **fast**. [See how Playwright is better](https://playwright.dev/#path=docs%2Fwhy-playwright.md&q=). +Playwright is a Python library to automate [Chromium](https://www.chromium.org/Home), [Firefox](https://www.mozilla.org/en-US/firefox/new/) and [WebKit](https://webkit.org/) browsers with a single API. Playwright delivers automation that is **ever-green**, **capable**, **reliable** and **fast**. [See how Playwright is better](https://playwright.dev/python/docs/why-playwright). | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 89.0.4344.0 | ✅ | ✅ | ✅ | +| Chromium 90.0.4392.0 | ✅ | ✅ | ✅ | | WebKit 14.1 | ✅ | ✅ | ✅ | | Firefox 85.0b5 | ✅ | ✅ | ✅ | @@ -23,12 +23,14 @@ Headless execution is supported for all browsers on all platforms. - [Evaluate JS in browser](#evaluate-js-in-browser) - [Intercept network requests](#intercept-network-requests) * [Documentation](#documentation) +* [Is Playwright ready?](#is-playwright-ready) +* [Migration from the pre-release versions](#migration-from-the-pre-release-versions) ## Usage ```sh pip install playwright -python -m playwright install +playwright install ``` This installs Playwright and browser binaries for Chromium, Firefox and WebKit. Playwright requires Python 3.7+. @@ -39,20 +41,23 @@ Playwright can record user interactions in a browser and generate code. [See dem ```sh # Pass --help to see all options -python -m playwright codegen +playwright codegen ``` Playwright offers both sync (blocking) API and async API. They are identical in terms of capabilities and only differ in how one consumes the API. #### Sync API +This is our default API for short snippets and tests. If you are not using asyncio in your +application, it is the easiest to use Sync API notation. + ```py -from playwright import sync_playwright +from playwright.sync_api import sync_playwright with sync_playwright() as p: for browser_type in [p.chromium, p.firefox, p.webkit]: browser = browser_type.launch() - page = browser.newPage() + page = browser.new_page() page.goto('http://whatsmyuseragent.org/') page.screenshot(path=f'example-{browser_type.name}.png') browser.close() @@ -60,9 +65,13 @@ with sync_playwright() as p: #### Async API +If you app is based on the modern asyncio loop and you are used to async/await constructs, +Playwright exposes Async API for you. You should also use this API inside the Jupyter Notebook +and other REPL frameworks that are already based on asyncio. + ```py import asyncio -from playwright import async_playwright +from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: @@ -85,25 +94,40 @@ def test_playwright_is_visible_on_google(page): page.goto("https://www.google.com") page.type("input[name=q]", "Playwright GitHub") page.click("input[type=submit]") - page.waitForSelector("text=microsoft/Playwright") + page.wait_for_selector("text=microsoft/Playwright") ``` #### Interactive mode (REPL) +Blocking REPL, as in CLI: + ```py ->>> from playwright import sync_playwright +>>> from playwright.sync_api import sync_playwright >>> playwright = sync_playwright().start() # Use playwright.chromium, playwright.firefox or playwright.webkit # Pass headless=False to see the browser UI >>> browser = playwright.chromium.launch() ->>> page = browser.newPage() +>>> page = browser.new_page() >>> page.goto("http://whatsmyuseragent.org/") >>> page.screenshot(path="example.png") >>> browser.close() >>> playwright.stop() ``` +Async REPL such as Jupyter Notebook: + +```py +>>> from playwright.async_api import async_playwright +>>> playwright = await async_playwright().start() +>>> browser = await playwright.chromium.launch() +>>> page = await browser.new_page() +>>> await page.goto("http://whatsmyuseragent.org/") +>>> await page.screenshot(path="example.png") +>>> await browser.close() +>>> await playwright.stop() +``` + ## Examples #### Mobile and geolocation @@ -111,21 +135,21 @@ def test_playwright_is_visible_on_google(page): This snippet emulates Mobile Safari on a device at a given geolocation, navigates to maps.google.com, performs action and takes a screenshot. ```py -from playwright import sync_playwright +from playwright.sync_api import sync_playwright with sync_playwright() as p: - iphone_11 = p.devices['iPhone 11 Pro'] + iphone_11 = p.devices["iPhone 11 Pro"] browser = p.webkit.launch(headless=False) - context = browser.newContext( + context = browser.new_context( **iphone_11, - locale='en-US', - geolocation={ 'longitude': 12.492507, 'latitude': 41.889938 }, - permissions=['geolocation'] + locale="en-US", + geolocation={"longitude": 12.492507, "latitude": 41.889938 }, + permissions=["geolocation"] ) - page = context.newPage() - page.goto('https://maps.google.com') - page.click('text="Your location"') - page.screenshot(path='colosseum-iphone.png') + page = context.new_page() + page.goto("https://maps.google.com") + page.click("text=Your location") + page.screenshot(path="colosseum-iphone.png") browser.close() ``` @@ -134,22 +158,22 @@ with sync_playwright() as p: ```py import asyncio -from playwright import async_playwright +from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: - iphone_11 = p.devices['iPhone 11 Pro'] + iphone_11 = p.devices["iPhone 11 Pro"] browser = await p.webkit.launch(headless=False) context = await browser.newContext( **iphone_11, - locale='en-US', - geolocation={ 'longitude': 12.492507, 'latitude': 41.889938 }, - permissions=['geolocation'] + locale="en-US", + geolocation={"longitude": 12.492507, "latitude": 41.889938}, + permissions=["geolocation"] ) page = await context.newPage() - await page.goto('https://maps.google.com') - await page.click('text="Your location"') - await page.screenshot(path='colosseum-iphone.png') + await page.goto("https://maps.google.com") + await page.click("text="Your location"") + await page.screenshot(path="colosseum-iphone.png") await browser.close() asyncio.run(main()) @@ -161,19 +185,19 @@ asyncio.run(main()) This code snippet navigates to example.com in Firefox, and executes a script in the page context. ```py -from playwright import sync_playwright +from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.firefox.launch() - page = browser.newPage() - page.goto('https://www.example.com/') - dimensions = page.evaluate('''() => { + page = browser.new_page() + page.goto("https://www.example.com/") + dimensions = page.evaluate("""() => { return { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight, deviceScaleFactor: window.devicePixelRatio } - }''') + }""") print(dimensions) browser.close() ``` @@ -182,20 +206,20 @@ with sync_playwright() as p: ```py import asyncio -from playwright import async_playwright +from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: browser = await p.firefox.launch() - page = await browser.newPage() - await page.goto('https://www.example.com/') - dimensions = await page.evaluate('''() => { + page = await browser.new_page() + await page.goto("https://www.example.com/") + dimensions = await page.evaluate("""() => { return { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight, deviceScaleFactor: window.devicePixelRatio } - }''') + }""") print(dimensions) await browser.close() @@ -208,20 +232,20 @@ asyncio.run(main()) This code snippet sets up request routing for a Chromium page to log all network requests. ```py -from playwright import sync_playwright +from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch() - page = browser.newPage() + page = browser.new_page() def log_and_continue_request(route, request): - print(request.url) - route.continue_() + print(request.url) + route.continue_() # Log and continue all network requests - page.route('**', lambda route, request: log_and_continue_request(route, request)) + page.route("**/*", log_and_continue_request) - page.goto('http://todomvc.com') + page.goto("http://todomvc.com") browser.close() ```
@@ -229,21 +253,20 @@ with sync_playwright() as p: ```py import asyncio -from playwright import async_playwright +from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: browser = await p.chromium.launch() page = await browser.newPage() - def log_and_continue_request(route, request): + async def log_and_continue_request(route, request): print(request.url) - asyncio.create_task(route.continue_()) + await route.continue_() # Log and continue all network requests - await page.route('**', lambda route, request: log_and_continue_request(route, request)) - - await page.goto('http://todomvc.com') + await page.route("**/*", log_and_continue_request) + await page.goto("http://todomvc.com") await browser.close() asyncio.run(main()) @@ -252,91 +275,38 @@ asyncio.run(main()) ## Documentation -We are in the process of converting our [documentation](https://playwright.dev/) from the Node.js form to Python. You can go ahead and use the Node.js documentation since the API is pretty much the same. Playwright uses non-Python naming conventions (`camelCase` instead of `snake_case`) for its methods. We recognize that this is not ideal, but it was done deliberately, so that you could rely upon Stack Overflow answers and existing documentation. - -### Named arguments - -Since Python allows named arguments, we didn't need to put the `options` parameter into every call as in the Node.js API. So when you see example like this in JavaScript - -```js -await webkit.launch({ headless: false }); -``` - -It translates into Python like this: - -```py -webkit.launch(headless=False) -``` - -If you are using an IDE, it will suggest parameters that are available in every call. - -### Evaluating functions - -Another difference is that in the JavaScript version, `page.evaluate` accepts JavaScript functions, while this does not make any sense in the Python version. - -In JavaScript it will be documented as: - -```js -const result = await page.evaluate(([x, y]) => { - return Promise.resolve(x * y); -}, [7, 8]); -console.log(result); // prints "56" -``` - -And in Python that would look like: - -```py -result = page.evaluate(""" - ([x, y]) => { - return Promise.resolve(x * y); - }""", - [7, 8]) -print(result) # prints "56" -``` - -The library will detect that what are passing it is a function and will invoke it with the given parameters. You can opt out of this function detection and pass `force_expr=True` to all evaluate functions, but you probably will never need to do that. - -### Using context managers - -Python enabled us to do some of the things that were not possible in the Node.js version and we used the opportunity. Instead of using the `page.waitFor*` methods, we recommend using corresponding `page.expect_*` context manager. - -In JavaScript it will be documented as: - -```js -const [ download ] = await Promise.all([ - page.waitForEvent('download'), // <-- start waiting for the download - page.click('button#delayed-download') // <-- perform the action that directly or indirectly initiates it. -]); -const path = await download.path(); -``` - -And in Python that would look much simpler: - -```py -with page.expect_download() as download_info: - page.click("button#delayed-download") -download = download_info.value -path = download.path() -``` - -Similarly, for waiting for the network response: - -```js -const [response] = await Promise.all([ - page.waitForResponse('**/api/fetch_data'), - page.click('button#update'), -]); -``` - -Becomes - -```py -with page.expect_response("**/api/fetch_data"): - page.click("button#update") -``` - -## Is Playwright for Python ready? - -Yes, Playwright for Python is ready. We are still not at the version v1.0, so minor breaking API changes could potentially happen. But a) this is unlikely and b) we will only do that if we know it improves your experience with the new library. We'd like to collect your feedback before we freeze the API for v1.0. - -> Note: We don't yet support some of the edge-cases of the vendor-specific APIs such as collecting Chromium trace, coverage report, etc. +Check out our [new documentation site](https://playwright.dev/python/docs/intro)! + +## Is Playwright ready? + +Yes, Playwright for Python is ready! The latest version of Playwright for +Python is 1.8.0a. We are ready to drop the Alpha bit once we hear from you. +Once it is gone, we will become semver compatible and the API will be +frozen in its present form for years. We will still be adding features with +every release, but we promise to not break it anymore! + +## Migration from the pre-release versions + +The API has changed since the last 0.170.0 version: +- Snake case notation for methods and arguments: + + ```py + # old + browser.newPage() + ``` + ```py + # new + browser.new_page() + ``` + +- Import has changed to include sync vs async mode explicitly: + ```py + # old + from playwright import sync_playwright + ``` + ```py + # new + from playwright.sync_api import sync_playwright + ``` + +That's about it! Our new [doc site](https://playwright.dev/python/docs/intro) uses proper notation and examples for the new API. diff --git a/playwright/async_api/_generated.py b/playwright/async_api/_generated.py index 66cf69b84..07d648ecd 100644 --- a/playwright/async_api/_generated.py +++ b/playwright/async_api/_generated.py @@ -1881,6 +1881,8 @@ async def select_option( Triggers a `change` and `input` event once all the provided options have been selected. If element is not a `` element. + ```py # single selection matching the value await handle.select_option(\"blue\") @@ -4388,6 +4390,8 @@ async def select_option( Triggers a `change` and `input` event once all the provided options have been selected. If there's no `` element. + ```py # single selection matching the value await frame.select_option(\"select#colors\", \"blue\") @@ -7646,6 +7650,8 @@ async def select_option( Triggers a `change` and `input` event once all the provided options have been selected. If there's no `` element. + ```py # single selection matching the value await page.select_option(\"select#colors\", \"blue\") diff --git a/playwright/sync_api/_context_manager.py b/playwright/sync_api/_context_manager.py index 3c0f0d9b8..43fb80040 100644 --- a/playwright/sync_api/_context_manager.py +++ b/playwright/sync_api/_context_manager.py @@ -40,7 +40,10 @@ def greenlet_main() -> None: own_loop = loop if loop.is_running(): - raise Error("Can only run one Playwright at a time.") + raise Error( + """It looks like you are using Playwright Sync API inside the asyncio loop. +Please use the Async API instead.""" + ) loop.run_until_complete(self._connection.run_as_sync()) diff --git a/playwright/sync_api/_generated.py b/playwright/sync_api/_generated.py index 892ca0e7f..cad932dd5 100644 --- a/playwright/sync_api/_generated.py +++ b/playwright/sync_api/_generated.py @@ -1908,6 +1908,8 @@ def select_option( Triggers a `change` and `input` event once all the provided options have been selected. If element is not a `` element. + ```py # single selection matching the value handle.select_option(\"blue\") @@ -4501,6 +4503,8 @@ def select_option( Triggers a `change` and `input` event once all the provided options have been selected. If there's no `` element. + ```py # single selection matching the value frame.select_option(\"select#colors\", \"blue\") @@ -7838,6 +7842,8 @@ def select_option( Triggers a `change` and `input` event once all the provided options have been selected. If there's no `` element. + ```py # single selection matching the value page.select_option(\"select#colors\", \"blue\") diff --git a/setup.py b/setup.py index 79e7214cd..292947d79 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ import setuptools from wheel.bdist_wheel import bdist_wheel as BDistWheelCommand -driver_version = "1.8.0-next-1610755301000" +driver_version = "1.8.0-next-1611093761000" with open("README.md", "r", encoding="utf-8") as fh: diff --git a/tests/async/test_page.py b/tests/async/test_page.py index 01978ce7c..cc25ff8b4 100644 --- a/tests/async/test_page.py +++ b/tests/async/test_page.py @@ -810,8 +810,9 @@ async def test_select_option_should_throw_when_element_is_not_a__select_(page, s async def test_select_option_should_return_on_no_matched_values(page, server): await page.goto(server.PREFIX + "/input/select.html") - result = await page.select_option("select", ["42", "abc"]) - assert result == [] + with pytest.raises(TimeoutError) as exc_info: + await page.select_option("select", ["42", "abc"], timeout=1000) + assert "Timeout 1000" in exc_info.value.message async def test_select_option_should_return_an_array_of_matched_values(page, server): diff --git a/tests/sync/test_sync.py b/tests/sync/test_sync.py index d1b801510..37f239fcd 100644 --- a/tests/sync/test_sync.py +++ b/tests/sync/test_sync.py @@ -177,7 +177,10 @@ def test_sync_playwright_multiple_times(): with pytest.raises(Error) as exc: with sync_playwright() as pw: assert pw.chromium - assert "Can only run one Playwright at a time." in exc.value.message + assert ( + "It looks like you are using Playwright Sync API inside the asyncio loop." + in exc.value.message + ) def test_sync_set_default_timeout(page): From 15fa8666c4a9d0ab253ef069300e2743e9be0ee6 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 19 Jan 2021 16:29:08 -0800 Subject: [PATCH 006/788] test: simplify confusing test (#446) --- tests/async/test_navigation.py | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/tests/async/test_navigation.py b/tests/async/test_navigation.py index b7a3e4f82..e77bf77ff 100644 --- a/tests/async/test_navigation.py +++ b/tests/async/test_navigation.py @@ -782,31 +782,26 @@ async def test_wait_for_load_state_should_wait_for_load_state_of_new_page( assert await new_page.evaluate("document.readyState") == "complete" -async def test_wait_for_load_state_should_resolve_after_popup_load(context, server): +async def test_wait_for_load_state_in_popup(context, server): page = await context.new_page() await page.goto(server.EMPTY_PAGE) - # Stall the 'load' by delaying css. css_requests = [] - server.set_route("/one-style.css", lambda request: css_requests.append(request)) + + def handle_request(request): + css_requests.append(request) + request.write(b"body {}") + request.finish() + + server.set_route("/one-style.css", handle_request) async with page.expect_popup() as popup_info: await page.evaluate( "url => window.popup = window.open(url)", server.PREFIX + "/one-style.html" ) - [popup, _] = await asyncio.gather( - popup_info.value, - server.wait_for_request("/one-style.css"), - ) - resolved = [] - load_state_task = asyncio.create_task(popup.wait_for_load_state()) - # Round trips! - for _ in range(5): - await page.evaluate("window") - assert not resolved - css_requests[0].finish() - await load_state_task - assert popup.url == server.PREFIX + "/one-style.html" + popup = await popup_info.value + await popup.wait_for_load_state() + assert len(css_requests) async def test_go_back_should_work(page, server): From bcee6e3574019aeac8dc56847ec567377ce0db41 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 19 Jan 2021 20:59:05 -0800 Subject: [PATCH 007/788] test: add some proxy tests (#447) --- tests/async/conftest.py | 28 ++++- tests/async/test_browsercontext_proxy.py | 123 ++++++++++++++++++++++ tests/async/test_interception.py | 7 -- tests/async/test_proxy.py | 124 +++++++++++++++++++++++ tests/conftest.py | 1 - tests/server.py | 27 ++--- 6 files changed, 286 insertions(+), 24 deletions(-) create mode 100644 tests/async/test_browsercontext_proxy.py create mode 100644 tests/async/test_proxy.py diff --git a/tests/async/conftest.py b/tests/async/conftest.py index f6dbec34e..1048be55c 100644 --- a/tests/async/conftest.py +++ b/tests/async/conftest.py @@ -48,10 +48,16 @@ def browser_type(playwright, browser_name: str): @pytest.fixture(scope="session") async def browser_factory(launch_arguments, browser_type): + browsers = [] + async def launch(**kwargs): - return await browser_type.launch(**launch_arguments, **kwargs) + browser = await browser_type.launch(**launch_arguments, **kwargs) + browsers.append(browser) + return browser - return launch + yield launch + for browser in browsers: + await browser.close() @pytest.fixture(scope="session") @@ -62,8 +68,22 @@ async def browser(browser_factory): @pytest.fixture -async def context(browser): - context = await browser.new_context() +async def context_factory(browser): + contexts = [] + + async def launch(**kwargs): + context = await browser.new_context(**kwargs) + contexts.append(context) + return context + + yield launch + for context in contexts: + await context.close() + + +@pytest.fixture +async def context(context_factory): + context = await context_factory() yield context await context.close() diff --git a/tests/async/test_browsercontext_proxy.py b/tests/async/test_browsercontext_proxy.py new file mode 100644 index 000000000..a0edb4521 --- /dev/null +++ b/tests/async/test_browsercontext_proxy.py @@ -0,0 +1,123 @@ +# Copyright (c) Microsoft Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import base64 + +import pytest + + +@pytest.fixture(scope="session") +async def browser(browser_factory): + browser = await browser_factory(proxy={"server": "dummy"}) + yield browser + await browser.close() + + +async def test_should_use_proxy(context_factory, server): + server.set_route( + "/target.html", + lambda r: ( + r.write(b"Served by the proxy"), + r.finish(), + ), + ) + context = await context_factory(proxy={"server": f"localhost:{server.PORT}"}) + page = await context.new_page() + await page.goto("http://non-existent.com/target.html") + assert await page.title() == "Served by the proxy" + + +async def test_should_use_proxy_for_second_page(context_factory, server): + server.set_route( + "/target.html", + lambda r: ( + r.write(b"Served by the proxy"), + r.finish(), + ), + ) + context = await context_factory(proxy={"server": f"localhost:{server.PORT}"}) + + page1 = await context.new_page() + await page1.goto("http://non-existent.com/target.html") + assert await page1.title() == "Served by the proxy" + + page2 = await context.new_page() + await page2.goto("http://non-existent.com/target.html") + assert await page2.title() == "Served by the proxy" + + +async def test_should_work_with_ip_port_notion(context_factory, server): + server.set_route( + "/target.html", + lambda r: ( + r.write(b"Served by the proxy"), + r.finish(), + ), + ) + context = await context_factory(proxy={"server": f"127.0.0.1:{server.PORT}"}) + page = await context.new_page() + await page.goto("http://non-existent.com/target.html") + assert await page.title() == "Served by the proxy" + + +async def test_should_authenticate(context_factory, server): + def handler(req): + print(req) + auth = req.getHeader("proxy-authorization") + if not auth: + req.setHeader( + b"Proxy-Authenticate", b'Basic realm="Access to internal site"' + ) + req.setResponseCode(407) + else: + req.write(f"{auth}".encode("utf-8")) + req.finish() + + server.set_route("/target.html", handler) + + context = await context_factory( + proxy={ + "server": f"localhost:{server.PORT}", + "username": "user", + "password": "secret", + } + ) + page = await context.new_page() + await page.goto("http://non-existent.com/target.html") + assert await page.title() == "Basic " + base64.b64encode(b"user:secret").decode( + "utf-8" + ) + + +async def test_should_authenticate_with_empty_password(context_factory, server): + def handler(req): + print(req) + auth = req.getHeader("proxy-authorization") + if not auth: + req.setHeader( + b"Proxy-Authenticate", b'Basic realm="Access to internal site"' + ) + req.setResponseCode(407) + else: + req.write(f"{auth}".encode("utf-8")) + req.finish() + + server.set_route("/target.html", handler) + + context = await context_factory( + proxy={"server": f"localhost:{server.PORT}", "username": "user", "password": ""} + ) + page = await context.new_page() + await page.goto("http://non-existent.com/target.html") + assert await page.title() == "Basic " + base64.b64encode(b"user:").decode("utf-8") diff --git a/tests/async/test_interception.py b/tests/async/test_interception.py index 80d1145b5..9304bc544 100644 --- a/tests/async/test_interception.py +++ b/tests/async/test_interception.py @@ -460,13 +460,6 @@ async def test_page_route_should_work_with_encoded_server(page, server): assert response.status == 404 -async def test_page_route_should_work_with_badly_encoded_server(page, server): - server.set_route("/malformed?rnd=%911", lambda req: req.finish()) - await page.route("**/*", lambda route: route.continue_()) - response = await page.goto(server.PREFIX + "/malformed?rnd=%911") - assert response.status == 200 - - async def test_page_route_should_work_with_encoded_server___2(page, server): # The requestWillBeSent will report URL as-is, whereas interception will # report encoded URL for stylesheet. @see crbug.com/759388 diff --git a/tests/async/test_proxy.py b/tests/async/test_proxy.py new file mode 100644 index 000000000..4a9ead417 --- /dev/null +++ b/tests/async/test_proxy.py @@ -0,0 +1,124 @@ +# Copyright (c) Microsoft Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import base64 + +import pytest + +from playwright.async_api import Error + + +async def test_should_throw_for_bad_server_value(browser_factory): + with pytest.raises(Error) as exc_info: + await browser_factory(proxy={"server": 123}) + assert "proxy.server: expected string, got number" in exc_info.value.message + + +async def test_should_use_proxy(browser_factory, server): + server.set_route( + "/target.html", + lambda r: ( + r.write(b"Served by the proxy"), + r.finish(), + ), + ) + browser = await browser_factory(proxy={"server": f"localhost:{server.PORT}"}) + page = await browser.new_page() + await page.goto("http://non-existent.com/target.html") + assert await page.title() == "Served by the proxy" + + +async def test_should_use_proxy_for_second_page(browser_factory, server): + server.set_route( + "/target.html", + lambda r: ( + r.write(b"Served by the proxy"), + r.finish(), + ), + ) + browser = await browser_factory(proxy={"server": f"localhost:{server.PORT}"}) + + page1 = await browser.new_page() + await page1.goto("http://non-existent.com/target.html") + assert await page1.title() == "Served by the proxy" + + page2 = await browser.new_page() + await page2.goto("http://non-existent.com/target.html") + assert await page2.title() == "Served by the proxy" + + +async def test_should_work_with_ip_port_notion(browser_factory, server): + server.set_route( + "/target.html", + lambda r: ( + r.write(b"Served by the proxy"), + r.finish(), + ), + ) + browser = await browser_factory(proxy={"server": f"127.0.0.1:{server.PORT}"}) + page = await browser.new_page() + await page.goto("http://non-existent.com/target.html") + assert await page.title() == "Served by the proxy" + + +async def test_should_authenticate(browser_factory, server): + def handler(req): + print(req) + auth = req.getHeader("proxy-authorization") + if not auth: + req.setHeader( + b"Proxy-Authenticate", b'Basic realm="Access to internal site"' + ) + req.setResponseCode(407) + else: + req.write(f"{auth}".encode("utf-8")) + req.finish() + + server.set_route("/target.html", handler) + + browser = await browser_factory( + proxy={ + "server": f"localhost:{server.PORT}", + "username": "user", + "password": "secret", + } + ) + page = await browser.new_page() + await page.goto("http://non-existent.com/target.html") + assert await page.title() == "Basic " + base64.b64encode(b"user:secret").decode( + "utf-8" + ) + + +async def test_should_authenticate_with_empty_password(browser_factory, server): + def handler(req): + print(req) + auth = req.getHeader("proxy-authorization") + if not auth: + req.setHeader( + b"Proxy-Authenticate", b'Basic realm="Access to internal site"' + ) + req.setResponseCode(407) + else: + req.write(f"{auth}".encode("utf-8")) + req.finish() + + server.set_route("/target.html", handler) + + browser = await browser_factory( + proxy={"server": f"localhost:{server.PORT}", "username": "user", "password": ""} + ) + page = await browser.new_page() + await page.goto("http://non-existent.com/target.html") + assert await page.title() == "Basic " + base64.b64encode(b"user:").decode("utf-8") diff --git a/tests/conftest.py b/tests/conftest.py index 2aa90a115..cccce0d63 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -50,7 +50,6 @@ def assetdir(): def launch_arguments(pytestconfig): return { "headless": not pytestconfig.getoption("--headful"), - "chromium_sandbox": False, } diff --git a/tests/server.py b/tests/server.py index f37a80383..6fc86122e 100644 --- a/tests/server.py +++ b/tests/server.py @@ -20,6 +20,7 @@ import threading from contextlib import closing from http import HTTPStatus +from urllib.parse import urlparse from autobahn.twisted.websocket import WebSocketServerFactory, WebSocketServerProtocol from OpenSSL import crypto @@ -78,18 +79,20 @@ def process(self): request = self self.post_body = request.content.read() request.content.seek(0, 0) - uri = request.uri.decode() - if request_subscribers.get(uri): - request_subscribers[uri].set_result(request) - request_subscribers.pop(uri) + uri = urlparse(request.uri.decode()) + path = uri.path - if auth.get(uri): + if request_subscribers.get(path): + request_subscribers[path].set_result(request) + request_subscribers.pop(path) + + if auth.get(path): authorization_header = request.requestHeaders.getRawHeaders( "authorization" ) creds_correct = False if authorization_header: - creds_correct = auth.get(uri) == ( + creds_correct = auth.get(path) == ( request.getUser(), request.getPassword(), ) @@ -100,19 +103,19 @@ def process(self): request.setResponseCode(HTTPStatus.UNAUTHORIZED) request.finish() return - if csp.get(uri): - request.setHeader(b"Content-Security-Policy", csp[uri]) - if routes.get(uri): - routes[uri](request) + if csp.get(path): + request.setHeader(b"Content-Security-Policy", csp[path]) + if routes.get(path): + routes[path](request) return file_content = None try: file_content = ( static_path / request.path.decode()[1:] ).read_bytes() - request.setHeader(b"Content-Type", mimetypes.guess_type(uri)[0]) + request.setHeader(b"Content-Type", mimetypes.guess_type(path)[0]) request.setHeader(b"Cache-Control", "no-cache, no-store") - if uri in gzip_routes: + if path in gzip_routes: request.setHeader("Content-Encoding", "gzip") request.write(gzip.compress(file_content)) else: From c4320c27cb080b385a5e45be46baa3cb7a9409ff Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 21 Jan 2021 13:38:12 -0800 Subject: [PATCH 008/788] chore: update README to suggest alpha installation (#455) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8174b743f..4d8c6b0e1 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Headless execution is supported for all browsers on all platforms. ## Usage ```sh -pip install playwright +pip install playwright==1.8.0a1 playwright install ``` From bce0873d85997f3085a7d668e798abe5731142d2 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Fri, 22 Jan 2021 02:25:44 -0800 Subject: [PATCH 009/788] Revert "devops: try to publish under playwright namespace in MCR (#445)" (#457) This reverts commit 1f2fcd9353a4f0db0f27b4499d764c932e3ddf49. The old approach was actually correct. --- .github/workflows/publish_canary_docker.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish_canary_docker.yml b/.github/workflows/publish_canary_docker.yml index 18cdde3cf..429537823 100644 --- a/.github/workflows/publish_canary_docker.yml +++ b/.github/workflows/publish_canary_docker.yml @@ -37,6 +37,6 @@ jobs: - run: docker build -t playwright-python:localbuild . - name: tag & publish run: | - # ./scripts/tag_image_and_push.sh playwright-python:localbuild playwright.azurecr.io/public/playwright/python:next - # ./scripts/tag_image_and_push.sh playwright-python:localbuild playwright.azurecr.io/public/playwright/python:next-focal - ./scripts/tag_image_and_push.sh playwright-python:localbuild playwright.azurecr.io/public/playwright/python:sha-${{ github.sha }} + ./scripts/tag_image_and_push.sh playwright-python:localbuild playwright.azurecr.io/public/playwright-python:next + ./scripts/tag_image_and_push.sh playwright-python:localbuild playwright.azurecr.io/public/playwright-python:next-focal + ./scripts/tag_image_and_push.sh playwright-python:localbuild playwright.azurecr.io/public/playwright-python:sha-${{ github.sha }} From 9746474e5b9a40adbe1751278c9250b63e7caf9c Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 28 Jan 2021 15:15:10 -0800 Subject: [PATCH 010/788] chore: enforce keyword-only arguments (#467) --- playwright/async_api/__init__.py | 1 - playwright/async_api/_generated.py | 382 +++++++++++++++++------------ playwright/sync_api/__init__.py | 1 - playwright/sync_api/_generated.py | 382 +++++++++++++++++------------ scripts/generate_api.py | 46 +++- 5 files changed, 491 insertions(+), 321 deletions(-) diff --git a/playwright/async_api/__init__.py b/playwright/async_api/__init__.py index 8e83bc72e..95c17eadd 100644 --- a/playwright/async_api/__init__.py +++ b/playwright/async_api/__init__.py @@ -24,7 +24,6 @@ from playwright.async_api._context_manager import PlaywrightContextManager from playwright.async_api._generated import ( Accessibility, - BindingCall, Browser, BrowserContext, BrowserType, diff --git a/playwright/async_api/_generated.py b/playwright/async_api/_generated.py index 07d648ecd..6b5343585 100644 --- a/playwright/async_api/_generated.py +++ b/playwright/async_api/_generated.py @@ -60,7 +60,6 @@ from playwright._impl._network import Response as ResponseImpl from playwright._impl._network import Route as RouteImpl from playwright._impl._network import WebSocket as WebSocketImpl -from playwright._impl._page import BindingCall as BindingCallImpl from playwright._impl._page import Page as PageImpl from playwright._impl._page import Worker as WorkerImpl from playwright._impl._playwright import Playwright as PlaywrightImpl @@ -530,11 +529,12 @@ async def abort(self, error_code: str = None) -> NoneType: async def fulfill( self, + *, status: int = None, headers: typing.Union[typing.Dict[str, str]] = None, body: typing.Union[str, bytes] = None, path: typing.Union[str, pathlib.Path] = None, - content_type: str = None, + content_type: str = None ) -> NoneType: """Route.fulfill @@ -589,10 +589,11 @@ async def fulfill( async def continue_( self, + *, url: str = None, method: str = None, headers: typing.Union[typing.Dict[str, str]] = None, - post_data: typing.Union[str, bytes] = None, + post_data: typing.Union[str, bytes] = None ) -> NoneType: """Route.continue_ @@ -660,7 +661,7 @@ def url(self) -> str: return mapping.from_maybe_impl(self._impl_obj.url) def expect_event( - self, event: str, predicate: typing.Callable = None, timeout: float = None + self, event: str, predicate: typing.Callable = None, *, timeout: float = None ) -> AsyncEventContextManager: """WebSocket.expect_event @@ -689,7 +690,7 @@ def expect_event( ) async def wait_for_event( - self, event: str, predicate: typing.Callable = None, timeout: float = None + self, event: str, predicate: typing.Callable = None, *, timeout: float = None ) -> typing.Any: """WebSocket.wait_for_event @@ -847,7 +848,7 @@ async def insert_text(self, text: str) -> NoneType: log_api("<= keyboard.insert_text failed") raise e - async def type(self, text: str, delay: float = None) -> NoneType: + async def type(self, text: str, *, delay: float = None) -> NoneType: """Keyboard.type Sends a `keydown`, `keypress`/`input`, and `keyup` event for each character in the text. @@ -880,7 +881,7 @@ async def type(self, text: str, delay: float = None) -> NoneType: log_api("<= keyboard.type failed") raise e - async def press(self, key: str, delay: float = None) -> NoneType: + async def press(self, key: str, *, delay: float = None) -> NoneType: """Keyboard.press `key` can specify the intended [keyboardEvent.key](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key) @@ -941,7 +942,7 @@ class Mouse(AsyncBase): def __init__(self, obj: MouseImpl): super().__init__(obj) - async def move(self, x: float, y: float, steps: int = None) -> NoneType: + async def move(self, x: float, y: float, *, steps: int = None) -> NoneType: """Mouse.move Dispatches a `mousemove` event. @@ -966,7 +967,10 @@ async def move(self, x: float, y: float, steps: int = None) -> NoneType: raise e async def down( - self, button: Literal["left", "middle", "right"] = None, click_count: int = None + self, + *, + button: Literal["left", "middle", "right"] = None, + click_count: int = None ) -> NoneType: """Mouse.down @@ -992,7 +996,10 @@ async def down( raise e async def up( - self, button: Literal["left", "middle", "right"] = None, click_count: int = None + self, + *, + button: Literal["left", "middle", "right"] = None, + click_count: int = None ) -> NoneType: """Mouse.up @@ -1021,9 +1028,10 @@ async def click( self, x: float, y: float, + *, delay: float = None, button: Literal["left", "middle", "right"] = None, - click_count: int = None, + click_count: int = None ) -> NoneType: """Mouse.click @@ -1058,8 +1066,9 @@ async def dblclick( self, x: float, y: float, + *, delay: float = None, - button: Literal["left", "middle", "right"] = None, + button: Literal["left", "middle", "right"] = None ) -> NoneType: """Mouse.dblclick @@ -1124,7 +1133,7 @@ def __init__(self, obj: JSHandleImpl): super().__init__(obj) async def evaluate( - self, expression: str, arg: typing.Any = None, force_expr: bool = None + self, expression: str, arg: typing.Any = None, *, force_expr: bool = None ) -> typing.Any: """JSHandle.evaluate @@ -1174,7 +1183,7 @@ async def evaluate( raise e async def evaluate_handle( - self, expression: str, arg: typing.Any = None, force_expr: bool = None + self, expression: str, arg: typing.Any = None, *, force_expr: bool = None ) -> "JSHandle": """JSHandle.evaluate_handle @@ -1646,7 +1655,7 @@ async def dispatch_event( log_api("<= element_handle.dispatch_event failed") raise e - async def scroll_into_view_if_needed(self, timeout: float = None) -> NoneType: + async def scroll_into_view_if_needed(self, *, timeout: float = None) -> NoneType: """ElementHandle.scroll_into_view_if_needed This method waits for [actionability](./actionability.md) checks, then tries to scroll element into view, unless it is @@ -1676,12 +1685,13 @@ async def scroll_into_view_if_needed(self, timeout: float = None) -> NoneType: async def hover( self, + *, modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, position: Position = None, timeout: float = None, - force: bool = None, + force: bool = None ) -> NoneType: """ElementHandle.hover @@ -1726,6 +1736,7 @@ async def hover( async def click( self, + *, modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, @@ -1735,7 +1746,7 @@ async def click( click_count: int = None, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """ElementHandle.click @@ -1797,6 +1808,7 @@ async def click( async def dblclick( self, + *, modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, @@ -1805,7 +1817,7 @@ async def dblclick( button: Literal["left", "middle", "right"] = None, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """ElementHandle.dblclick @@ -1868,11 +1880,12 @@ async def dblclick( async def select_option( self, value: typing.Union[str, typing.List[str]] = None, + *, index: typing.Union[int, typing.List[int]] = None, label: typing.Union[str, typing.List[str]] = None, element: typing.Union["ElementHandle", typing.List["ElementHandle"]] = None, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> typing.List[str]: """ElementHandle.select_option @@ -1937,13 +1950,14 @@ async def select_option( async def tap( self, + *, modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, position: Position = None, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """ElementHandle.tap @@ -1997,7 +2011,7 @@ async def tap( raise e async def fill( - self, value: str, timeout: float = None, no_wait_after: bool = None + self, value: str, *, timeout: float = None, no_wait_after: bool = None ) -> NoneType: """ElementHandle.fill @@ -2031,7 +2045,7 @@ async def fill( log_api("<= element_handle.fill failed") raise e - async def select_text(self, timeout: float = None) -> NoneType: + async def select_text(self, *, timeout: float = None) -> NoneType: """ElementHandle.select_text This method waits for [actionability](./actionability.md) checks, then focuses the element and selects all its text @@ -2064,8 +2078,9 @@ async def set_input_files( typing.List[typing.Union[str, pathlib.Path]], typing.List[FilePayload], ], + *, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """ElementHandle.set_input_files @@ -2118,9 +2133,10 @@ async def focus(self) -> NoneType: async def type( self, text: str, + *, delay: float = None, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """ElementHandle.type @@ -2172,9 +2188,10 @@ async def type( async def press( self, key: str, + *, delay: float = None, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """ElementHandle.press @@ -2226,7 +2243,7 @@ async def press( raise e async def check( - self, timeout: float = None, force: bool = None, no_wait_after: bool = None + self, *, timeout: float = None, force: bool = None, no_wait_after: bool = None ) -> NoneType: """ElementHandle.check @@ -2271,7 +2288,7 @@ async def check( raise e async def uncheck( - self, timeout: float = None, force: bool = None, no_wait_after: bool = None + self, *, timeout: float = None, force: bool = None, no_wait_after: bool = None ) -> NoneType: """ElementHandle.uncheck @@ -2352,11 +2369,12 @@ async def bounding_box(self) -> typing.Union[FloatRect, NoneType]: async def screenshot( self, + *, timeout: float = None, type: Literal["jpeg", "png"] = None, path: typing.Union[str, pathlib.Path] = None, quality: int = None, - omit_background: bool = None, + omit_background: bool = None ) -> bytes: """ElementHandle.screenshot @@ -2467,7 +2485,8 @@ async def eval_on_selector( selector: str, expression: str, arg: typing.Any = None, - force_expr: bool = None, + *, + force_expr: bool = None ) -> typing.Any: """ElementHandle.eval_on_selector @@ -2526,7 +2545,8 @@ async def eval_on_selector_all( selector: str, expression: str, arg: typing.Any = None, - force_expr: bool = None, + *, + force_expr: bool = None ) -> typing.Any: """ElementHandle.eval_on_selector_all @@ -2592,7 +2612,8 @@ async def wait_for_element_state( state: Literal[ "disabled", "editable", "enabled", "hidden", "stable", "visible" ], - timeout: float = None, + *, + timeout: float = None ) -> NoneType: """ElementHandle.wait_for_element_state @@ -2636,8 +2657,9 @@ async def wait_for_element_state( async def wait_for_selector( self, selector: str, + *, state: Literal["attached", "detached", "hidden", "visible"] = None, - timeout: float = None, + timeout: float = None ) -> typing.Union["ElementHandle", NoneType]: """ElementHandle.wait_for_selector @@ -2701,7 +2723,7 @@ def __init__(self, obj: AccessibilityImpl): super().__init__(obj) async def snapshot( - self, interesting_only: bool = None, root: "ElementHandle" = None + self, *, interesting_only: bool = None, root: "ElementHandle" = None ) -> typing.Union[typing.Dict, NoneType]: """Accessibility.snapshot @@ -2820,8 +2842,9 @@ async def set_files( typing.List[typing.Union[str, pathlib.Path]], typing.List[FilePayload], ], + *, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """FileChooser.set_files @@ -2926,9 +2949,10 @@ def child_frames(self) -> typing.List["Frame"]: async def goto( self, url: str, + *, timeout: float = None, wait_until: Literal["domcontentloaded", "load", "networkidle"] = None, - referer: str = None, + referer: str = None ) -> typing.Union["Response", NoneType]: """Frame.goto @@ -2989,9 +3013,10 @@ async def goto( def expect_navigation( self, + *, url: typing.Union[str, typing.Pattern, typing.Callable[[str], bool]] = None, wait_until: Literal["domcontentloaded", "load", "networkidle"] = None, - timeout: float = None, + timeout: float = None ) -> AsyncEventContextManager["Response"]: """Frame.expect_navigation @@ -3040,7 +3065,8 @@ def expect_navigation( async def wait_for_load_state( self, state: Literal["domcontentloaded", "load", "networkidle"] = None, - timeout: float = None, + *, + timeout: float = None ) -> NoneType: """Frame.wait_for_load_state @@ -3111,7 +3137,7 @@ async def frame_element(self) -> "ElementHandle": raise e async def evaluate( - self, expression: str, arg: typing.Any = None, force_expr: bool = None + self, expression: str, arg: typing.Any = None, *, force_expr: bool = None ) -> typing.Any: """Frame.evaluate @@ -3177,7 +3203,7 @@ async def evaluate( raise e async def evaluate_handle( - self, expression: str, arg: typing.Any = None, force_expr: bool = None + self, expression: str, arg: typing.Any = None, *, force_expr: bool = None ) -> "JSHandle": """Frame.evaluate_handle @@ -3306,8 +3332,9 @@ async def query_selector_all(self, selector: str) -> typing.List["ElementHandle" async def wait_for_selector( self, selector: str, + *, timeout: float = None, - state: Literal["attached", "detached", "hidden", "visible"] = None, + state: Literal["attached", "detached", "hidden", "visible"] = None ) -> typing.Union["ElementHandle", NoneType]: """Frame.wait_for_selector @@ -3374,7 +3401,7 @@ async def main(): log_api("<= frame.wait_for_selector failed") raise e - async def is_checked(self, selector: str, timeout: float = None) -> bool: + async def is_checked(self, selector: str, *, timeout: float = None) -> bool: """Frame.is_checked Returns whether the element is checked. Throws if the element is not a checkbox or radio input. @@ -3404,7 +3431,7 @@ async def is_checked(self, selector: str, timeout: float = None) -> bool: log_api("<= frame.is_checked failed") raise e - async def is_disabled(self, selector: str, timeout: float = None) -> bool: + async def is_disabled(self, selector: str, *, timeout: float = None) -> bool: """Frame.is_disabled Returns whether the element is disabled, the opposite of [enabled](./actionability.md#enabled). @@ -3434,7 +3461,7 @@ async def is_disabled(self, selector: str, timeout: float = None) -> bool: log_api("<= frame.is_disabled failed") raise e - async def is_editable(self, selector: str, timeout: float = None) -> bool: + async def is_editable(self, selector: str, *, timeout: float = None) -> bool: """Frame.is_editable Returns whether the element is [editable](./actionability.md#editable). @@ -3464,7 +3491,7 @@ async def is_editable(self, selector: str, timeout: float = None) -> bool: log_api("<= frame.is_editable failed") raise e - async def is_enabled(self, selector: str, timeout: float = None) -> bool: + async def is_enabled(self, selector: str, *, timeout: float = None) -> bool: """Frame.is_enabled Returns whether the element is [enabled](./actionability.md#enabled). @@ -3494,7 +3521,7 @@ async def is_enabled(self, selector: str, timeout: float = None) -> bool: log_api("<= frame.is_enabled failed") raise e - async def is_hidden(self, selector: str, timeout: float = None) -> bool: + async def is_hidden(self, selector: str, *, timeout: float = None) -> bool: """Frame.is_hidden Returns whether the element is hidden, the opposite of [visible](./actionability.md#visible). @@ -3524,7 +3551,7 @@ async def is_hidden(self, selector: str, timeout: float = None) -> bool: log_api("<= frame.is_hidden failed") raise e - async def is_visible(self, selector: str, timeout: float = None) -> bool: + async def is_visible(self, selector: str, *, timeout: float = None) -> bool: """Frame.is_visible Returns whether the element is [visible](./actionability.md#visible). @@ -3559,7 +3586,8 @@ async def dispatch_event( selector: str, type: str, event_init: typing.Dict = None, - timeout: float = None, + *, + timeout: float = None ) -> NoneType: """Frame.dispatch_event @@ -3626,7 +3654,8 @@ async def eval_on_selector( selector: str, expression: str, arg: typing.Any = None, - force_expr: bool = None, + *, + force_expr: bool = None ) -> typing.Any: """Frame.eval_on_selector @@ -3685,7 +3714,8 @@ async def eval_on_selector_all( selector: str, expression: str, arg: typing.Any = None, - force_expr: bool = None, + *, + force_expr: bool = None ) -> typing.Any: """Frame.eval_on_selector_all @@ -3759,8 +3789,9 @@ async def content(self) -> str: async def set_content( self, html: str, + *, timeout: float = None, - wait_until: Literal["domcontentloaded", "load", "networkidle"] = None, + wait_until: Literal["domcontentloaded", "load", "networkidle"] = None ) -> NoneType: """Frame.set_content @@ -3814,10 +3845,11 @@ def is_detached(self) -> bool: async def add_script_tag( self, + *, url: str = None, path: typing.Union[str, pathlib.Path] = None, content: str = None, - type: str = None, + type: str = None ) -> "ElementHandle": """Frame.add_script_tag @@ -3858,9 +3890,10 @@ async def add_script_tag( async def add_style_tag( self, + *, url: str = None, path: typing.Union[str, pathlib.Path] = None, - content: str = None, + content: str = None ) -> "ElementHandle": """Frame.add_style_tag @@ -3898,6 +3931,7 @@ async def add_style_tag( async def click( self, selector: str, + *, modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, @@ -3907,7 +3941,7 @@ async def click( click_count: int = None, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Frame.click @@ -3974,6 +4008,7 @@ async def click( async def dblclick( self, selector: str, + *, modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, @@ -3982,7 +4017,7 @@ async def dblclick( button: Literal["left", "middle", "right"] = None, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Frame.dblclick @@ -4049,13 +4084,14 @@ async def dblclick( async def tap( self, selector: str, + *, modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, position: Position = None, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Frame.tap @@ -4116,8 +4152,9 @@ async def fill( self, selector: str, value: str, + *, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Frame.fill @@ -4160,7 +4197,7 @@ async def fill( log_api("<= frame.fill failed") raise e - async def focus(self, selector: str, timeout: float = None) -> NoneType: + async def focus(self, selector: str, *, timeout: float = None) -> NoneType: """Frame.focus This method fetches an element with `selector` and focuses it. If there's no element matching `selector`, the method @@ -4188,7 +4225,7 @@ async def focus(self, selector: str, timeout: float = None) -> NoneType: raise e async def text_content( - self, selector: str, timeout: float = None + self, selector: str, *, timeout: float = None ) -> typing.Union[str, NoneType]: """Frame.text_content @@ -4219,7 +4256,7 @@ async def text_content( log_api("<= frame.text_content failed") raise e - async def inner_text(self, selector: str, timeout: float = None) -> str: + async def inner_text(self, selector: str, *, timeout: float = None) -> str: """Frame.inner_text Returns `element.innerText`. @@ -4249,7 +4286,7 @@ async def inner_text(self, selector: str, timeout: float = None) -> str: log_api("<= frame.inner_text failed") raise e - async def inner_html(self, selector: str, timeout: float = None) -> str: + async def inner_html(self, selector: str, *, timeout: float = None) -> str: """Frame.inner_html Returns `element.innerHTML`. @@ -4280,7 +4317,7 @@ async def inner_html(self, selector: str, timeout: float = None) -> str: raise e async def get_attribute( - self, selector: str, name: str, timeout: float = None + self, selector: str, name: str, *, timeout: float = None ) -> typing.Union[str, NoneType]: """Frame.get_attribute @@ -4318,12 +4355,13 @@ async def get_attribute( async def hover( self, selector: str, + *, modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, position: Position = None, timeout: float = None, - force: bool = None, + force: bool = None ) -> NoneType: """Frame.hover @@ -4377,11 +4415,12 @@ async def select_option( self, selector: str, value: typing.Union[str, typing.List[str]] = None, + *, index: typing.Union[int, typing.List[int]] = None, label: typing.Union[str, typing.List[str]] = None, element: typing.Union["ElementHandle", typing.List["ElementHandle"]] = None, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> typing.List[str]: """Frame.select_option @@ -4457,8 +4496,9 @@ async def set_input_files( typing.List[typing.Union[str, pathlib.Path]], typing.List[FilePayload], ], + *, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Frame.set_input_files @@ -4503,9 +4543,10 @@ async def type( self, selector: str, text: str, + *, delay: float = None, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Frame.type @@ -4558,9 +4599,10 @@ async def press( self, selector: str, key: str, + *, delay: float = None, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Frame.press @@ -4619,9 +4661,10 @@ async def press( async def check( self, selector: str, + *, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Frame.check @@ -4674,9 +4717,10 @@ async def check( async def uncheck( self, selector: str, + *, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Frame.uncheck @@ -4754,10 +4798,11 @@ async def wait_for_timeout(self, timeout: float) -> NoneType: async def wait_for_function( self, expression: str, + *, arg: typing.Any = None, force_expr: bool = None, timeout: float = None, - polling: typing.Union[float, Literal["raf"]] = None, + polling: typing.Union[float, Literal["raf"]] = None ) -> "JSHandle": """Frame.wait_for_function @@ -4867,7 +4912,7 @@ def url(self) -> str: return mapping.from_maybe_impl(self._impl_obj.url) async def evaluate( - self, expression: str, arg: typing.Any = None, force_expr: bool = None + self, expression: str, arg: typing.Any = None, *, force_expr: bool = None ) -> typing.Any: """Worker.evaluate @@ -4912,7 +4957,7 @@ async def evaluate( raise e async def evaluate_handle( - self, expression: str, arg: typing.Any = None, force_expr: bool = None + self, expression: str, arg: typing.Any = None, *, force_expr: bool = None ) -> "JSHandle": """Worker.evaluate_handle @@ -4967,8 +5012,9 @@ async def register( self, name: str, script: str = None, + *, path: typing.Union[str, pathlib.Path] = None, - content_script: bool = None, + content_script: bool = None ) -> NoneType: """Selectors.register @@ -5280,27 +5326,6 @@ async def path(self) -> pathlib.Path: mapping.register(VideoImpl, Video) -class BindingCall(AsyncBase): - def __init__(self, obj: BindingCallImpl): - super().__init__(obj) - - async def call(self, func: typing.Callable) -> NoneType: - - try: - log_api("=> binding_call.call started") - result = mapping.from_maybe_impl( - await self._impl_obj.call(func=self._wrap_handler(func)) - ) - log_api("<= binding_call.call succeded") - return result - except Exception as e: - log_api("<= binding_call.call failed") - raise e - - -mapping.register(BindingCallImpl, BindingCall) - - class Page(AsyncBase): def __init__(self, obj: PageImpl): super().__init__(obj) @@ -5452,7 +5477,8 @@ async def opener(self) -> typing.Union["Page", NoneType]: def frame( self, name: str = None, - url: typing.Union[str, typing.Pattern, typing.Callable[[str], bool]] = None, + *, + url: typing.Union[str, typing.Pattern, typing.Callable[[str], bool]] = None ) -> typing.Union["Frame", NoneType]: """Page.frame @@ -5607,8 +5633,9 @@ async def query_selector_all(self, selector: str) -> typing.List["ElementHandle" async def wait_for_selector( self, selector: str, + *, timeout: float = None, - state: Literal["attached", "detached", "hidden", "visible"] = None, + state: Literal["attached", "detached", "hidden", "visible"] = None ) -> typing.Union["ElementHandle", NoneType]: """Page.wait_for_selector @@ -5675,7 +5702,7 @@ async def main(): log_api("<= page.wait_for_selector failed") raise e - async def is_checked(self, selector: str, timeout: float = None) -> bool: + async def is_checked(self, selector: str, *, timeout: float = None) -> bool: """Page.is_checked Returns whether the element is checked. Throws if the element is not a checkbox or radio input. @@ -5705,7 +5732,7 @@ async def is_checked(self, selector: str, timeout: float = None) -> bool: log_api("<= page.is_checked failed") raise e - async def is_disabled(self, selector: str, timeout: float = None) -> bool: + async def is_disabled(self, selector: str, *, timeout: float = None) -> bool: """Page.is_disabled Returns whether the element is disabled, the opposite of [enabled](./actionability.md#enabled). @@ -5735,7 +5762,7 @@ async def is_disabled(self, selector: str, timeout: float = None) -> bool: log_api("<= page.is_disabled failed") raise e - async def is_editable(self, selector: str, timeout: float = None) -> bool: + async def is_editable(self, selector: str, *, timeout: float = None) -> bool: """Page.is_editable Returns whether the element is [editable](./actionability.md#editable). @@ -5765,7 +5792,7 @@ async def is_editable(self, selector: str, timeout: float = None) -> bool: log_api("<= page.is_editable failed") raise e - async def is_enabled(self, selector: str, timeout: float = None) -> bool: + async def is_enabled(self, selector: str, *, timeout: float = None) -> bool: """Page.is_enabled Returns whether the element is [enabled](./actionability.md#enabled). @@ -5795,7 +5822,7 @@ async def is_enabled(self, selector: str, timeout: float = None) -> bool: log_api("<= page.is_enabled failed") raise e - async def is_hidden(self, selector: str, timeout: float = None) -> bool: + async def is_hidden(self, selector: str, *, timeout: float = None) -> bool: """Page.is_hidden Returns whether the element is hidden, the opposite of [visible](./actionability.md#visible). @@ -5825,7 +5852,7 @@ async def is_hidden(self, selector: str, timeout: float = None) -> bool: log_api("<= page.is_hidden failed") raise e - async def is_visible(self, selector: str, timeout: float = None) -> bool: + async def is_visible(self, selector: str, *, timeout: float = None) -> bool: """Page.is_visible Returns whether the element is [visible](./actionability.md#visible). @@ -5860,7 +5887,8 @@ async def dispatch_event( selector: str, type: str, event_init: typing.Dict = None, - timeout: float = None, + *, + timeout: float = None ) -> NoneType: """Page.dispatch_event @@ -5923,7 +5951,7 @@ async def dispatch_event( raise e async def evaluate( - self, expression: str, arg: typing.Any = None, force_expr: bool = None + self, expression: str, arg: typing.Any = None, *, force_expr: bool = None ) -> typing.Any: """Page.evaluate @@ -5993,7 +6021,7 @@ async def evaluate( raise e async def evaluate_handle( - self, expression: str, arg: typing.Any = None, force_expr: bool = None + self, expression: str, arg: typing.Any = None, *, force_expr: bool = None ) -> "JSHandle": """Page.evaluate_handle @@ -6062,7 +6090,8 @@ async def eval_on_selector( selector: str, expression: str, arg: typing.Any = None, - force_expr: bool = None, + *, + force_expr: bool = None ) -> typing.Any: """Page.eval_on_selector @@ -6121,7 +6150,8 @@ async def eval_on_selector_all( selector: str, expression: str, arg: typing.Any = None, - force_expr: bool = None, + *, + force_expr: bool = None ) -> typing.Any: """Page.eval_on_selector_all @@ -6173,10 +6203,11 @@ async def eval_on_selector_all( async def add_script_tag( self, + *, url: str = None, path: typing.Union[str, pathlib.Path] = None, content: str = None, - type: str = None, + type: str = None ) -> "ElementHandle": """Page.add_script_tag @@ -6218,9 +6249,10 @@ async def add_script_tag( async def add_style_tag( self, + *, url: str = None, path: typing.Union[str, pathlib.Path] = None, - content: str = None, + content: str = None ) -> "ElementHandle": """Page.add_style_tag @@ -6323,7 +6355,7 @@ async def main(): raise e async def expose_binding( - self, name: str, callback: typing.Callable, handle: bool = None + self, name: str, callback: typing.Callable, *, handle: bool = None ) -> NoneType: """Page.expose_binding @@ -6455,8 +6487,9 @@ async def content(self) -> str: async def set_content( self, html: str, + *, timeout: float = None, - wait_until: Literal["domcontentloaded", "load", "networkidle"] = None, + wait_until: Literal["domcontentloaded", "load", "networkidle"] = None ) -> NoneType: """Page.set_content @@ -6492,9 +6525,10 @@ async def set_content( async def goto( self, url: str, + *, timeout: float = None, wait_until: Literal["domcontentloaded", "load", "networkidle"] = None, - referer: str = None, + referer: str = None ) -> typing.Union["Response", NoneType]: """Page.goto @@ -6557,8 +6591,9 @@ async def goto( async def reload( self, + *, timeout: float = None, - wait_until: Literal["domcontentloaded", "load", "networkidle"] = None, + wait_until: Literal["domcontentloaded", "load", "networkidle"] = None ) -> typing.Union["Response", NoneType]: """Page.reload @@ -6597,7 +6632,8 @@ async def reload( async def wait_for_load_state( self, state: Literal["domcontentloaded", "load", "networkidle"] = None, - timeout: float = None, + *, + timeout: float = None ) -> NoneType: """Page.wait_for_load_state @@ -6649,7 +6685,7 @@ async def wait_for_load_state( raise e async def wait_for_event( - self, event: str, predicate: typing.Callable = None, timeout: float = None + self, event: str, predicate: typing.Callable = None, *, timeout: float = None ) -> typing.Any: """Page.wait_for_event @@ -6691,8 +6727,9 @@ async def wait_for_event( async def go_back( self, + *, timeout: float = None, - wait_until: Literal["domcontentloaded", "load", "networkidle"] = None, + wait_until: Literal["domcontentloaded", "load", "networkidle"] = None ) -> typing.Union["Response", NoneType]: """Page.go_back @@ -6732,8 +6769,9 @@ async def go_back( async def go_forward( self, + *, timeout: float = None, - wait_until: Literal["domcontentloaded", "load", "networkidle"] = None, + wait_until: Literal["domcontentloaded", "load", "networkidle"] = None ) -> typing.Union["Response", NoneType]: """Page.go_forward @@ -6773,8 +6811,9 @@ async def go_forward( async def emulate_media( self, + *, media: Literal["print", "screen"] = None, - color_scheme: Literal["dark", "light", "no-preference"] = None, + color_scheme: Literal["dark", "light", "no-preference"] = None ) -> NoneType: """Page.emulate_media @@ -6878,7 +6917,7 @@ async def bring_to_front(self) -> NoneType: raise e async def add_init_script( - self, script: str = None, path: typing.Union[str, pathlib.Path] = None + self, script: str = None, *, path: typing.Union[str, pathlib.Path] = None ) -> NoneType: """Page.add_init_script @@ -7015,13 +7054,14 @@ async def unroute( async def screenshot( self, + *, timeout: float = None, type: Literal["jpeg", "png"] = None, path: typing.Union[str, pathlib.Path] = None, quality: int = None, omit_background: bool = None, full_page: bool = None, - clip: FloatRect = None, + clip: FloatRect = None ) -> bytes: """Page.screenshot @@ -7095,7 +7135,7 @@ async def title(self) -> str: log_api("<= page.title failed") raise e - async def close(self, run_before_unload: bool = None) -> NoneType: + async def close(self, *, run_before_unload: bool = None) -> NoneType: """Page.close If `runBeforeUnload` is `false`, does not run any unload handlers and waits for the page to be closed. If @@ -7146,6 +7186,7 @@ def is_closed(self) -> bool: async def click( self, selector: str, + *, modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, @@ -7155,7 +7196,7 @@ async def click( click_count: int = None, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Page.click @@ -7224,6 +7265,7 @@ async def click( async def dblclick( self, selector: str, + *, modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, @@ -7232,7 +7274,7 @@ async def dblclick( button: Literal["left", "middle", "right"] = None, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Page.dblclick @@ -7301,13 +7343,14 @@ async def dblclick( async def tap( self, selector: str, + *, modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, position: Position = None, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Page.tap @@ -7370,8 +7413,9 @@ async def fill( self, selector: str, value: str, + *, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Page.fill @@ -7416,7 +7460,7 @@ async def fill( log_api("<= page.fill failed") raise e - async def focus(self, selector: str, timeout: float = None) -> NoneType: + async def focus(self, selector: str, *, timeout: float = None) -> NoneType: """Page.focus This method fetches an element with `selector` and focuses it. If there's no element matching `selector`, the method @@ -7446,7 +7490,7 @@ async def focus(self, selector: str, timeout: float = None) -> NoneType: raise e async def text_content( - self, selector: str, timeout: float = None + self, selector: str, *, timeout: float = None ) -> typing.Union[str, NoneType]: """Page.text_content @@ -7477,7 +7521,7 @@ async def text_content( log_api("<= page.text_content failed") raise e - async def inner_text(self, selector: str, timeout: float = None) -> str: + async def inner_text(self, selector: str, *, timeout: float = None) -> str: """Page.inner_text Returns `element.innerText`. @@ -7507,7 +7551,7 @@ async def inner_text(self, selector: str, timeout: float = None) -> str: log_api("<= page.inner_text failed") raise e - async def inner_html(self, selector: str, timeout: float = None) -> str: + async def inner_html(self, selector: str, *, timeout: float = None) -> str: """Page.inner_html Returns `element.innerHTML`. @@ -7538,7 +7582,7 @@ async def inner_html(self, selector: str, timeout: float = None) -> str: raise e async def get_attribute( - self, selector: str, name: str, timeout: float = None + self, selector: str, name: str, *, timeout: float = None ) -> typing.Union[str, NoneType]: """Page.get_attribute @@ -7576,12 +7620,13 @@ async def get_attribute( async def hover( self, selector: str, + *, modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, position: Position = None, timeout: float = None, - force: bool = None, + force: bool = None ) -> NoneType: """Page.hover @@ -7637,11 +7682,12 @@ async def select_option( self, selector: str, value: typing.Union[str, typing.List[str]] = None, + *, index: typing.Union[int, typing.List[int]] = None, label: typing.Union[str, typing.List[str]] = None, element: typing.Union["ElementHandle", typing.List["ElementHandle"]] = None, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> typing.List[str]: """Page.select_option @@ -7720,8 +7766,9 @@ async def set_input_files( typing.List[typing.Union[str, pathlib.Path]], typing.List[FilePayload], ], + *, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Page.set_input_files @@ -7766,9 +7813,10 @@ async def type( self, selector: str, text: str, + *, delay: float = None, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Page.type @@ -7823,9 +7871,10 @@ async def press( self, selector: str, key: str, + *, delay: float = None, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Page.press @@ -7898,9 +7947,10 @@ async def press( async def check( self, selector: str, + *, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Page.check @@ -7955,9 +8005,10 @@ async def check( async def uncheck( self, selector: str, + *, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Page.uncheck @@ -8044,10 +8095,11 @@ async def wait_for_timeout(self, timeout: float) -> NoneType: async def wait_for_function( self, expression: str, + *, arg: typing.Any = None, force_expr: bool = None, timeout: float = None, - polling: typing.Union[float, Literal["raf"]] = None, + polling: typing.Union[float, Literal["raf"]] = None ) -> "JSHandle": """Page.wait_for_function @@ -8123,6 +8175,7 @@ async def main(): async def pdf( self, + *, scale: float = None, display_header_footer: bool = None, header_template: str = None, @@ -8135,7 +8188,7 @@ async def pdf( height: typing.Union[str, float] = None, prefer_css_page_size: bool = None, margin: PdfMargins = None, - path: typing.Union[str, pathlib.Path] = None, + path: typing.Union[str, pathlib.Path] = None ) -> bytes: """Page.pdf @@ -8253,7 +8306,7 @@ async def pdf( raise e def expect_event( - self, event: str, predicate: typing.Callable = None, timeout: float = None + self, event: str, predicate: typing.Callable = None, *, timeout: float = None ) -> AsyncEventContextManager: """Page.expect_event @@ -8290,7 +8343,8 @@ def expect_event( def expect_console_message( self, predicate: typing.Union[typing.Callable[["ConsoleMessage"], bool]] = None, - timeout: float = None, + *, + timeout: float = None ) -> AsyncEventContextManager["ConsoleMessage"]: """Page.expect_console_message @@ -8320,7 +8374,8 @@ def expect_console_message( def expect_download( self, predicate: typing.Union[typing.Callable[["Download"], bool]] = None, - timeout: float = None, + *, + timeout: float = None ) -> AsyncEventContextManager["Download"]: """Page.expect_download @@ -8350,7 +8405,8 @@ def expect_download( def expect_file_chooser( self, predicate: typing.Union[typing.Callable[["FileChooser"], bool]] = None, - timeout: float = None, + *, + timeout: float = None ) -> AsyncEventContextManager["FileChooser"]: """Page.expect_file_chooser @@ -8379,9 +8435,10 @@ def expect_file_chooser( def expect_navigation( self, + *, url: typing.Union[str, typing.Pattern, typing.Callable[[str], bool]] = None, wait_until: Literal["domcontentloaded", "load", "networkidle"] = None, - timeout: float = None, + timeout: float = None ) -> AsyncEventContextManager["Response"]: """Page.expect_navigation @@ -8433,7 +8490,8 @@ def expect_navigation( def expect_popup( self, predicate: typing.Union[typing.Callable[["Page"], bool]] = None, - timeout: float = None, + *, + timeout: float = None ) -> AsyncEventContextManager["Page"]: """Page.expect_popup @@ -8465,7 +8523,8 @@ def expect_request( url_or_predicate: typing.Union[ str, typing.Pattern, typing.Callable[["Request"], bool] ], - timeout: float = None, + *, + timeout: float = None ) -> AsyncEventContextManager["Request"]: """Page.expect_request @@ -8505,7 +8564,8 @@ def expect_response( url_or_predicate: typing.Union[ str, typing.Pattern, typing.Callable[["Response"], bool] ], - timeout: float = None, + *, + timeout: float = None ) -> AsyncEventContextManager["Response"]: """Page.expect_response @@ -8539,7 +8599,8 @@ def expect_response( def expect_worker( self, predicate: typing.Union[typing.Callable[["Worker"], bool]] = None, - timeout: float = None, + *, + timeout: float = None ) -> AsyncEventContextManager["Worker"]: """Page.expect_worker @@ -8743,7 +8804,7 @@ async def clear_cookies(self) -> NoneType: raise e async def grant_permissions( - self, permissions: typing.List[str], origin: str = None + self, permissions: typing.List[str], *, origin: str = None ) -> NoneType: """BrowserContext.grant_permissions @@ -8886,7 +8947,7 @@ async def set_offline(self, offline: bool) -> NoneType: raise e async def add_init_script( - self, script: str = None, path: typing.Union[str, pathlib.Path] = None + self, script: str = None, *, path: typing.Union[str, pathlib.Path] = None ) -> NoneType: """BrowserContext.add_init_script @@ -8929,7 +8990,7 @@ async def add_init_script( raise e async def expose_binding( - self, name: str, callback: typing.Callable, handle: bool = None + self, name: str, callback: typing.Callable, *, handle: bool = None ) -> NoneType: """BrowserContext.expose_binding @@ -9173,7 +9234,7 @@ async def unroute( raise e def expect_event( - self, event: str, predicate: typing.Callable = None, timeout: float = None + self, event: str, predicate: typing.Callable = None, *, timeout: float = None ) -> AsyncEventContextManager: """BrowserContext.expect_event @@ -9225,7 +9286,7 @@ async def close(self) -> NoneType: raise e async def storage_state( - self, path: typing.Union[str, pathlib.Path] = None + self, *, path: typing.Union[str, pathlib.Path] = None ) -> StorageState: """BrowserContext.storage_state @@ -9253,7 +9314,7 @@ async def storage_state( raise e async def wait_for_event( - self, event: str, predicate: typing.Callable = None, timeout: float = None + self, event: str, predicate: typing.Callable = None, *, timeout: float = None ) -> typing.Any: """BrowserContext.wait_for_event @@ -9296,7 +9357,8 @@ async def wait_for_event( def expect_page( self, predicate: typing.Union[typing.Callable[["Page"], bool]] = None, - timeout: float = None, + *, + timeout: float = None ) -> AsyncEventContextManager["Page"]: """BrowserContext.expect_page @@ -9491,6 +9553,7 @@ def is_connected(self) -> bool: async def new_context( self, + *, viewport: ViewportSize = None, no_viewport: bool = None, ignore_https_errors: bool = None, @@ -9515,7 +9578,7 @@ async def new_context( record_har_omit_content: bool = None, record_video_dir: typing.Union[str, pathlib.Path] = None, record_video_size: ViewportSize = None, - storage_state: typing.Union[StorageState, str, pathlib.Path] = None, + storage_state: typing.Union[StorageState, str, pathlib.Path] = None ) -> "BrowserContext": """Browser.new_context @@ -9634,6 +9697,7 @@ async def new_context( async def new_page( self, + *, viewport: ViewportSize = None, no_viewport: bool = None, ignore_https_errors: bool = None, @@ -9658,7 +9722,7 @@ async def new_page( record_har_omit_content: bool = None, record_video_dir: typing.Union[str, pathlib.Path] = None, record_video_size: ViewportSize = None, - storage_state: typing.Union[StorageState, str, pathlib.Path] = None, + storage_state: typing.Union[StorageState, str, pathlib.Path] = None ) -> "Page": """Browser.new_page @@ -9825,6 +9889,7 @@ def executable_path(self) -> str: async def launch( self, + *, executable_path: typing.Union[str, pathlib.Path] = None, args: typing.List[str] = None, ignore_default_args: typing.Union[bool, typing.List[str]] = None, @@ -9841,7 +9906,7 @@ async def launch( chromium_sandbox: bool = None, firefox_user_prefs: typing.Union[ typing.Dict[str, typing.Union[str, float, bool]] - ] = None, + ] = None ) -> "Browser": """BrowserType.launch @@ -9949,6 +10014,7 @@ async def launch( async def launch_persistent_context( self, user_data_dir: typing.Union[str, pathlib.Path], + *, executable_path: typing.Union[str, pathlib.Path] = None, args: typing.List[str] = None, ignore_default_args: typing.Union[bool, typing.List[str]] = None, @@ -9984,7 +10050,7 @@ async def launch_persistent_context( record_har_path: typing.Union[str, pathlib.Path] = None, record_har_omit_content: bool = None, record_video_dir: typing.Union[str, pathlib.Path] = None, - record_video_size: ViewportSize = None, + record_video_size: ViewportSize = None ) -> "BrowserContext": """BrowserType.launch_persistent_context diff --git a/playwright/sync_api/__init__.py b/playwright/sync_api/__init__.py index a0e2d68a5..837a1a2d9 100644 --- a/playwright/sync_api/__init__.py +++ b/playwright/sync_api/__init__.py @@ -24,7 +24,6 @@ from playwright.sync_api._context_manager import PlaywrightContextManager from playwright.sync_api._generated import ( Accessibility, - BindingCall, Browser, BrowserContext, BrowserType, diff --git a/playwright/sync_api/_generated.py b/playwright/sync_api/_generated.py index cad932dd5..a2e067eb4 100644 --- a/playwright/sync_api/_generated.py +++ b/playwright/sync_api/_generated.py @@ -59,7 +59,6 @@ from playwright._impl._network import Response as ResponseImpl from playwright._impl._network import Route as RouteImpl from playwright._impl._network import WebSocket as WebSocketImpl -from playwright._impl._page import BindingCall as BindingCallImpl from playwright._impl._page import Page as PageImpl from playwright._impl._page import Worker as WorkerImpl from playwright._impl._playwright import Playwright as PlaywrightImpl @@ -530,11 +529,12 @@ def abort(self, error_code: str = None) -> NoneType: def fulfill( self, + *, status: int = None, headers: typing.Union[typing.Dict[str, str]] = None, body: typing.Union[str, bytes] = None, path: typing.Union[str, pathlib.Path] = None, - content_type: str = None, + content_type: str = None ) -> NoneType: """Route.fulfill @@ -591,10 +591,11 @@ def fulfill( def continue_( self, + *, url: str = None, method: str = None, headers: typing.Union[typing.Dict[str, str]] = None, - post_data: typing.Union[str, bytes] = None, + post_data: typing.Union[str, bytes] = None ) -> NoneType: """Route.continue_ @@ -664,7 +665,7 @@ def url(self) -> str: return mapping.from_maybe_impl(self._impl_obj.url) def expect_event( - self, event: str, predicate: typing.Callable = None, timeout: float = None + self, event: str, predicate: typing.Callable = None, *, timeout: float = None ) -> EventContextManager: """WebSocket.expect_event @@ -693,7 +694,7 @@ def expect_event( ) def wait_for_event( - self, event: str, predicate: typing.Callable = None, timeout: float = None + self, event: str, predicate: typing.Callable = None, *, timeout: float = None ) -> typing.Any: """WebSocket.wait_for_event @@ -853,7 +854,7 @@ def insert_text(self, text: str) -> NoneType: log_api("<= keyboard.insert_text failed") raise e - def type(self, text: str, delay: float = None) -> NoneType: + def type(self, text: str, *, delay: float = None) -> NoneType: """Keyboard.type Sends a `keydown`, `keypress`/`input`, and `keyup` event for each character in the text. @@ -886,7 +887,7 @@ def type(self, text: str, delay: float = None) -> NoneType: log_api("<= keyboard.type failed") raise e - def press(self, key: str, delay: float = None) -> NoneType: + def press(self, key: str, *, delay: float = None) -> NoneType: """Keyboard.press `key` can specify the intended [keyboardEvent.key](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key) @@ -947,7 +948,7 @@ class Mouse(SyncBase): def __init__(self, obj: MouseImpl): super().__init__(obj) - def move(self, x: float, y: float, steps: int = None) -> NoneType: + def move(self, x: float, y: float, *, steps: int = None) -> NoneType: """Mouse.move Dispatches a `mousemove` event. @@ -972,7 +973,10 @@ def move(self, x: float, y: float, steps: int = None) -> NoneType: raise e def down( - self, button: Literal["left", "middle", "right"] = None, click_count: int = None + self, + *, + button: Literal["left", "middle", "right"] = None, + click_count: int = None ) -> NoneType: """Mouse.down @@ -998,7 +1002,10 @@ def down( raise e def up( - self, button: Literal["left", "middle", "right"] = None, click_count: int = None + self, + *, + button: Literal["left", "middle", "right"] = None, + click_count: int = None ) -> NoneType: """Mouse.up @@ -1027,9 +1034,10 @@ def click( self, x: float, y: float, + *, delay: float = None, button: Literal["left", "middle", "right"] = None, - click_count: int = None, + click_count: int = None ) -> NoneType: """Mouse.click @@ -1066,8 +1074,9 @@ def dblclick( self, x: float, y: float, + *, delay: float = None, - button: Literal["left", "middle", "right"] = None, + button: Literal["left", "middle", "right"] = None ) -> NoneType: """Mouse.dblclick @@ -1134,7 +1143,7 @@ def __init__(self, obj: JSHandleImpl): super().__init__(obj) def evaluate( - self, expression: str, arg: typing.Any = None, force_expr: bool = None + self, expression: str, arg: typing.Any = None, *, force_expr: bool = None ) -> typing.Any: """JSHandle.evaluate @@ -1186,7 +1195,7 @@ def evaluate( raise e def evaluate_handle( - self, expression: str, arg: typing.Any = None, force_expr: bool = None + self, expression: str, arg: typing.Any = None, *, force_expr: bool = None ) -> "JSHandle": """JSHandle.evaluate_handle @@ -1664,7 +1673,7 @@ def dispatch_event(self, type: str, event_init: typing.Dict = None) -> NoneType: log_api("<= element_handle.dispatch_event failed") raise e - def scroll_into_view_if_needed(self, timeout: float = None) -> NoneType: + def scroll_into_view_if_needed(self, *, timeout: float = None) -> NoneType: """ElementHandle.scroll_into_view_if_needed This method waits for [actionability](./actionability.md) checks, then tries to scroll element into view, unless it is @@ -1694,12 +1703,13 @@ def scroll_into_view_if_needed(self, timeout: float = None) -> NoneType: def hover( self, + *, modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, position: Position = None, timeout: float = None, - force: bool = None, + force: bool = None ) -> NoneType: """ElementHandle.hover @@ -1749,6 +1759,7 @@ def hover( def click( self, + *, modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, @@ -1758,7 +1769,7 @@ def click( click_count: int = None, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """ElementHandle.click @@ -1822,6 +1833,7 @@ def click( def dblclick( self, + *, modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, @@ -1830,7 +1842,7 @@ def dblclick( button: Literal["left", "middle", "right"] = None, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """ElementHandle.dblclick @@ -1895,11 +1907,12 @@ def dblclick( def select_option( self, value: typing.Union[str, typing.List[str]] = None, + *, index: typing.Union[int, typing.List[int]] = None, label: typing.Union[str, typing.List[str]] = None, element: typing.Union["ElementHandle", typing.List["ElementHandle"]] = None, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> typing.List[str]: """ElementHandle.select_option @@ -1978,13 +1991,14 @@ def select_option( def tap( self, + *, modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, position: Position = None, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """ElementHandle.tap @@ -2040,7 +2054,7 @@ def tap( raise e def fill( - self, value: str, timeout: float = None, no_wait_after: bool = None + self, value: str, *, timeout: float = None, no_wait_after: bool = None ) -> NoneType: """ElementHandle.fill @@ -2076,7 +2090,7 @@ def fill( log_api("<= element_handle.fill failed") raise e - def select_text(self, timeout: float = None) -> NoneType: + def select_text(self, *, timeout: float = None) -> NoneType: """ElementHandle.select_text This method waits for [actionability](./actionability.md) checks, then focuses the element and selects all its text @@ -2109,8 +2123,9 @@ def set_input_files( typing.List[typing.Union[str, pathlib.Path]], typing.List[FilePayload], ], + *, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """ElementHandle.set_input_files @@ -2165,9 +2180,10 @@ def focus(self) -> NoneType: def type( self, text: str, + *, delay: float = None, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """ElementHandle.type @@ -2224,9 +2240,10 @@ def type( def press( self, key: str, + *, delay: float = None, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """ElementHandle.press @@ -2280,7 +2297,7 @@ def press( raise e def check( - self, timeout: float = None, force: bool = None, no_wait_after: bool = None + self, *, timeout: float = None, force: bool = None, no_wait_after: bool = None ) -> NoneType: """ElementHandle.check @@ -2327,7 +2344,7 @@ def check( raise e def uncheck( - self, timeout: float = None, force: bool = None, no_wait_after: bool = None + self, *, timeout: float = None, force: bool = None, no_wait_after: bool = None ) -> NoneType: """ElementHandle.uncheck @@ -2412,11 +2429,12 @@ def bounding_box(self) -> typing.Union[FloatRect, NoneType]: def screenshot( self, + *, timeout: float = None, type: Literal["jpeg", "png"] = None, path: typing.Union[str, pathlib.Path] = None, quality: int = None, - omit_background: bool = None, + omit_background: bool = None ) -> bytes: """ElementHandle.screenshot @@ -2527,7 +2545,8 @@ def eval_on_selector( selector: str, expression: str, arg: typing.Any = None, - force_expr: bool = None, + *, + force_expr: bool = None ) -> typing.Any: """ElementHandle.eval_on_selector @@ -2588,7 +2607,8 @@ def eval_on_selector_all( selector: str, expression: str, arg: typing.Any = None, - force_expr: bool = None, + *, + force_expr: bool = None ) -> typing.Any: """ElementHandle.eval_on_selector_all @@ -2655,7 +2675,8 @@ def wait_for_element_state( state: Literal[ "disabled", "editable", "enabled", "hidden", "stable", "visible" ], - timeout: float = None, + *, + timeout: float = None ) -> NoneType: """ElementHandle.wait_for_element_state @@ -2699,8 +2720,9 @@ def wait_for_element_state( def wait_for_selector( self, selector: str, + *, state: Literal["attached", "detached", "hidden", "visible"] = None, - timeout: float = None, + timeout: float = None ) -> typing.Union["ElementHandle", NoneType]: """ElementHandle.wait_for_selector @@ -2766,7 +2788,7 @@ def __init__(self, obj: AccessibilityImpl): super().__init__(obj) def snapshot( - self, interesting_only: bool = None, root: "ElementHandle" = None + self, *, interesting_only: bool = None, root: "ElementHandle" = None ) -> typing.Union[typing.Dict, NoneType]: """Accessibility.snapshot @@ -2887,8 +2909,9 @@ def set_files( typing.List[typing.Union[str, pathlib.Path]], typing.List[FilePayload], ], + *, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """FileChooser.set_files @@ -2995,9 +3018,10 @@ def child_frames(self) -> typing.List["Frame"]: def goto( self, url: str, + *, timeout: float = None, wait_until: Literal["domcontentloaded", "load", "networkidle"] = None, - referer: str = None, + referer: str = None ) -> typing.Union["Response", NoneType]: """Frame.goto @@ -3060,9 +3084,10 @@ def goto( def expect_navigation( self, + *, url: typing.Union[str, typing.Pattern, typing.Callable[[str], bool]] = None, wait_until: Literal["domcontentloaded", "load", "networkidle"] = None, - timeout: float = None, + timeout: float = None ) -> EventContextManager["Response"]: """Frame.expect_navigation @@ -3111,7 +3136,8 @@ def expect_navigation( def wait_for_load_state( self, state: Literal["domcontentloaded", "load", "networkidle"] = None, - timeout: float = None, + *, + timeout: float = None ) -> NoneType: """Frame.wait_for_load_state @@ -3184,7 +3210,7 @@ def frame_element(self) -> "ElementHandle": raise e def evaluate( - self, expression: str, arg: typing.Any = None, force_expr: bool = None + self, expression: str, arg: typing.Any = None, *, force_expr: bool = None ) -> typing.Any: """Frame.evaluate @@ -3252,7 +3278,7 @@ def evaluate( raise e def evaluate_handle( - self, expression: str, arg: typing.Any = None, force_expr: bool = None + self, expression: str, arg: typing.Any = None, *, force_expr: bool = None ) -> "JSHandle": """Frame.evaluate_handle @@ -3380,8 +3406,9 @@ def query_selector_all(self, selector: str) -> typing.List["ElementHandle"]: def wait_for_selector( self, selector: str, + *, timeout: float = None, - state: Literal["attached", "detached", "hidden", "visible"] = None, + state: Literal["attached", "detached", "hidden", "visible"] = None ) -> typing.Union["ElementHandle", NoneType]: """Frame.wait_for_selector @@ -3447,7 +3474,7 @@ def run(playwright): log_api("<= frame.wait_for_selector failed") raise e - def is_checked(self, selector: str, timeout: float = None) -> bool: + def is_checked(self, selector: str, *, timeout: float = None) -> bool: """Frame.is_checked Returns whether the element is checked. Throws if the element is not a checkbox or radio input. @@ -3479,7 +3506,7 @@ def is_checked(self, selector: str, timeout: float = None) -> bool: log_api("<= frame.is_checked failed") raise e - def is_disabled(self, selector: str, timeout: float = None) -> bool: + def is_disabled(self, selector: str, *, timeout: float = None) -> bool: """Frame.is_disabled Returns whether the element is disabled, the opposite of [enabled](./actionability.md#enabled). @@ -3511,7 +3538,7 @@ def is_disabled(self, selector: str, timeout: float = None) -> bool: log_api("<= frame.is_disabled failed") raise e - def is_editable(self, selector: str, timeout: float = None) -> bool: + def is_editable(self, selector: str, *, timeout: float = None) -> bool: """Frame.is_editable Returns whether the element is [editable](./actionability.md#editable). @@ -3543,7 +3570,7 @@ def is_editable(self, selector: str, timeout: float = None) -> bool: log_api("<= frame.is_editable failed") raise e - def is_enabled(self, selector: str, timeout: float = None) -> bool: + def is_enabled(self, selector: str, *, timeout: float = None) -> bool: """Frame.is_enabled Returns whether the element is [enabled](./actionability.md#enabled). @@ -3575,7 +3602,7 @@ def is_enabled(self, selector: str, timeout: float = None) -> bool: log_api("<= frame.is_enabled failed") raise e - def is_hidden(self, selector: str, timeout: float = None) -> bool: + def is_hidden(self, selector: str, *, timeout: float = None) -> bool: """Frame.is_hidden Returns whether the element is hidden, the opposite of [visible](./actionability.md#visible). @@ -3605,7 +3632,7 @@ def is_hidden(self, selector: str, timeout: float = None) -> bool: log_api("<= frame.is_hidden failed") raise e - def is_visible(self, selector: str, timeout: float = None) -> bool: + def is_visible(self, selector: str, *, timeout: float = None) -> bool: """Frame.is_visible Returns whether the element is [visible](./actionability.md#visible). @@ -3642,7 +3669,8 @@ def dispatch_event( selector: str, type: str, event_init: typing.Dict = None, - timeout: float = None, + *, + timeout: float = None ) -> NoneType: """Frame.dispatch_event @@ -3711,7 +3739,8 @@ def eval_on_selector( selector: str, expression: str, arg: typing.Any = None, - force_expr: bool = None, + *, + force_expr: bool = None ) -> typing.Any: """Frame.eval_on_selector @@ -3772,7 +3801,8 @@ def eval_on_selector_all( selector: str, expression: str, arg: typing.Any = None, - force_expr: bool = None, + *, + force_expr: bool = None ) -> typing.Any: """Frame.eval_on_selector_all @@ -3848,8 +3878,9 @@ def content(self) -> str: def set_content( self, html: str, + *, timeout: float = None, - wait_until: Literal["domcontentloaded", "load", "networkidle"] = None, + wait_until: Literal["domcontentloaded", "load", "networkidle"] = None ) -> NoneType: """Frame.set_content @@ -3905,10 +3936,11 @@ def is_detached(self) -> bool: def add_script_tag( self, + *, url: str = None, path: typing.Union[str, pathlib.Path] = None, content: str = None, - type: str = None, + type: str = None ) -> "ElementHandle": """Frame.add_script_tag @@ -3951,9 +3983,10 @@ def add_script_tag( def add_style_tag( self, + *, url: str = None, path: typing.Union[str, pathlib.Path] = None, - content: str = None, + content: str = None ) -> "ElementHandle": """Frame.add_style_tag @@ -3993,6 +4026,7 @@ def add_style_tag( def click( self, selector: str, + *, modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, @@ -4002,7 +4036,7 @@ def click( click_count: int = None, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Frame.click @@ -4071,6 +4105,7 @@ def click( def dblclick( self, selector: str, + *, modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, @@ -4079,7 +4114,7 @@ def dblclick( button: Literal["left", "middle", "right"] = None, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Frame.dblclick @@ -4148,13 +4183,14 @@ def dblclick( def tap( self, selector: str, + *, modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, position: Position = None, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Frame.tap @@ -4217,8 +4253,9 @@ def fill( self, selector: str, value: str, + *, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Frame.fill @@ -4263,7 +4300,7 @@ def fill( log_api("<= frame.fill failed") raise e - def focus(self, selector: str, timeout: float = None) -> NoneType: + def focus(self, selector: str, *, timeout: float = None) -> NoneType: """Frame.focus This method fetches an element with `selector` and focuses it. If there's no element matching `selector`, the method @@ -4291,7 +4328,7 @@ def focus(self, selector: str, timeout: float = None) -> NoneType: raise e def text_content( - self, selector: str, timeout: float = None + self, selector: str, *, timeout: float = None ) -> typing.Union[str, NoneType]: """Frame.text_content @@ -4324,7 +4361,7 @@ def text_content( log_api("<= frame.text_content failed") raise e - def inner_text(self, selector: str, timeout: float = None) -> str: + def inner_text(self, selector: str, *, timeout: float = None) -> str: """Frame.inner_text Returns `element.innerText`. @@ -4356,7 +4393,7 @@ def inner_text(self, selector: str, timeout: float = None) -> str: log_api("<= frame.inner_text failed") raise e - def inner_html(self, selector: str, timeout: float = None) -> str: + def inner_html(self, selector: str, *, timeout: float = None) -> str: """Frame.inner_html Returns `element.innerHTML`. @@ -4389,7 +4426,7 @@ def inner_html(self, selector: str, timeout: float = None) -> str: raise e def get_attribute( - self, selector: str, name: str, timeout: float = None + self, selector: str, name: str, *, timeout: float = None ) -> typing.Union[str, NoneType]: """Frame.get_attribute @@ -4429,12 +4466,13 @@ def get_attribute( def hover( self, selector: str, + *, modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, position: Position = None, timeout: float = None, - force: bool = None, + force: bool = None ) -> NoneType: """Frame.hover @@ -4490,11 +4528,12 @@ def select_option( self, selector: str, value: typing.Union[str, typing.List[str]] = None, + *, index: typing.Union[int, typing.List[int]] = None, label: typing.Union[str, typing.List[str]] = None, element: typing.Union["ElementHandle", typing.List["ElementHandle"]] = None, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> typing.List[str]: """Frame.select_option @@ -4572,8 +4611,9 @@ def set_input_files( typing.List[typing.Union[str, pathlib.Path]], typing.List[FilePayload], ], + *, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Frame.set_input_files @@ -4620,9 +4660,10 @@ def type( self, selector: str, text: str, + *, delay: float = None, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Frame.type @@ -4677,9 +4718,10 @@ def press( self, selector: str, key: str, + *, delay: float = None, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Frame.press @@ -4740,9 +4782,10 @@ def press( def check( self, selector: str, + *, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Frame.check @@ -4797,9 +4840,10 @@ def check( def uncheck( self, selector: str, + *, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Frame.uncheck @@ -4879,10 +4923,11 @@ def wait_for_timeout(self, timeout: float) -> NoneType: def wait_for_function( self, expression: str, + *, arg: typing.Any = None, force_expr: bool = None, timeout: float = None, - polling: typing.Union[float, Literal["raf"]] = None, + polling: typing.Union[float, Literal["raf"]] = None ) -> "JSHandle": """Frame.wait_for_function @@ -4991,7 +5036,7 @@ def url(self) -> str: return mapping.from_maybe_impl(self._impl_obj.url) def evaluate( - self, expression: str, arg: typing.Any = None, force_expr: bool = None + self, expression: str, arg: typing.Any = None, *, force_expr: bool = None ) -> typing.Any: """Worker.evaluate @@ -5038,7 +5083,7 @@ def evaluate( raise e def evaluate_handle( - self, expression: str, arg: typing.Any = None, force_expr: bool = None + self, expression: str, arg: typing.Any = None, *, force_expr: bool = None ) -> "JSHandle": """Worker.evaluate_handle @@ -5095,8 +5140,9 @@ def register( self, name: str, script: str = None, + *, path: typing.Union[str, pathlib.Path] = None, - content_script: bool = None, + content_script: bool = None ) -> NoneType: """Selectors.register @@ -5415,27 +5461,6 @@ def path(self) -> pathlib.Path: mapping.register(VideoImpl, Video) -class BindingCall(SyncBase): - def __init__(self, obj: BindingCallImpl): - super().__init__(obj) - - def call(self, func: typing.Callable) -> NoneType: - - try: - log_api("=> binding_call.call started") - result = mapping.from_maybe_impl( - self._sync(self._impl_obj.call(func=self._wrap_handler(func))) - ) - log_api("<= binding_call.call succeded") - return result - except Exception as e: - log_api("<= binding_call.call failed") - raise e - - -mapping.register(BindingCallImpl, BindingCall) - - class Page(SyncBase): def __init__(self, obj: PageImpl): super().__init__(obj) @@ -5587,7 +5612,8 @@ def opener(self) -> typing.Union["Page", NoneType]: def frame( self, name: str = None, - url: typing.Union[str, typing.Pattern, typing.Callable[[str], bool]] = None, + *, + url: typing.Union[str, typing.Pattern, typing.Callable[[str], bool]] = None ) -> typing.Union["Frame", NoneType]: """Page.frame @@ -5740,8 +5766,9 @@ def query_selector_all(self, selector: str) -> typing.List["ElementHandle"]: def wait_for_selector( self, selector: str, + *, timeout: float = None, - state: Literal["attached", "detached", "hidden", "visible"] = None, + state: Literal["attached", "detached", "hidden", "visible"] = None ) -> typing.Union["ElementHandle", NoneType]: """Page.wait_for_selector @@ -5807,7 +5834,7 @@ def run(playwright): log_api("<= page.wait_for_selector failed") raise e - def is_checked(self, selector: str, timeout: float = None) -> bool: + def is_checked(self, selector: str, *, timeout: float = None) -> bool: """Page.is_checked Returns whether the element is checked. Throws if the element is not a checkbox or radio input. @@ -5839,7 +5866,7 @@ def is_checked(self, selector: str, timeout: float = None) -> bool: log_api("<= page.is_checked failed") raise e - def is_disabled(self, selector: str, timeout: float = None) -> bool: + def is_disabled(self, selector: str, *, timeout: float = None) -> bool: """Page.is_disabled Returns whether the element is disabled, the opposite of [enabled](./actionability.md#enabled). @@ -5871,7 +5898,7 @@ def is_disabled(self, selector: str, timeout: float = None) -> bool: log_api("<= page.is_disabled failed") raise e - def is_editable(self, selector: str, timeout: float = None) -> bool: + def is_editable(self, selector: str, *, timeout: float = None) -> bool: """Page.is_editable Returns whether the element is [editable](./actionability.md#editable). @@ -5903,7 +5930,7 @@ def is_editable(self, selector: str, timeout: float = None) -> bool: log_api("<= page.is_editable failed") raise e - def is_enabled(self, selector: str, timeout: float = None) -> bool: + def is_enabled(self, selector: str, *, timeout: float = None) -> bool: """Page.is_enabled Returns whether the element is [enabled](./actionability.md#enabled). @@ -5935,7 +5962,7 @@ def is_enabled(self, selector: str, timeout: float = None) -> bool: log_api("<= page.is_enabled failed") raise e - def is_hidden(self, selector: str, timeout: float = None) -> bool: + def is_hidden(self, selector: str, *, timeout: float = None) -> bool: """Page.is_hidden Returns whether the element is hidden, the opposite of [visible](./actionability.md#visible). @@ -5965,7 +5992,7 @@ def is_hidden(self, selector: str, timeout: float = None) -> bool: log_api("<= page.is_hidden failed") raise e - def is_visible(self, selector: str, timeout: float = None) -> bool: + def is_visible(self, selector: str, *, timeout: float = None) -> bool: """Page.is_visible Returns whether the element is [visible](./actionability.md#visible). @@ -6002,7 +6029,8 @@ def dispatch_event( selector: str, type: str, event_init: typing.Dict = None, - timeout: float = None, + *, + timeout: float = None ) -> NoneType: """Page.dispatch_event @@ -6067,7 +6095,7 @@ def dispatch_event( raise e def evaluate( - self, expression: str, arg: typing.Any = None, force_expr: bool = None + self, expression: str, arg: typing.Any = None, *, force_expr: bool = None ) -> typing.Any: """Page.evaluate @@ -6139,7 +6167,7 @@ def evaluate( raise e def evaluate_handle( - self, expression: str, arg: typing.Any = None, force_expr: bool = None + self, expression: str, arg: typing.Any = None, *, force_expr: bool = None ) -> "JSHandle": """Page.evaluate_handle @@ -6209,7 +6237,8 @@ def eval_on_selector( selector: str, expression: str, arg: typing.Any = None, - force_expr: bool = None, + *, + force_expr: bool = None ) -> typing.Any: """Page.eval_on_selector @@ -6270,7 +6299,8 @@ def eval_on_selector_all( selector: str, expression: str, arg: typing.Any = None, - force_expr: bool = None, + *, + force_expr: bool = None ) -> typing.Any: """Page.eval_on_selector_all @@ -6324,10 +6354,11 @@ def eval_on_selector_all( def add_script_tag( self, + *, url: str = None, path: typing.Union[str, pathlib.Path] = None, content: str = None, - type: str = None, + type: str = None ) -> "ElementHandle": """Page.add_script_tag @@ -6371,9 +6402,10 @@ def add_script_tag( def add_style_tag( self, + *, url: str = None, path: typing.Union[str, pathlib.Path] = None, - content: str = None, + content: str = None ) -> "ElementHandle": """Page.add_style_tag @@ -6477,7 +6509,7 @@ def run(playwright): raise e def expose_binding( - self, name: str, callback: typing.Callable, handle: bool = None + self, name: str, callback: typing.Callable, *, handle: bool = None ) -> NoneType: """Page.expose_binding @@ -6610,8 +6642,9 @@ def content(self) -> str: def set_content( self, html: str, + *, timeout: float = None, - wait_until: Literal["domcontentloaded", "load", "networkidle"] = None, + wait_until: Literal["domcontentloaded", "load", "networkidle"] = None ) -> NoneType: """Page.set_content @@ -6649,9 +6682,10 @@ def set_content( def goto( self, url: str, + *, timeout: float = None, wait_until: Literal["domcontentloaded", "load", "networkidle"] = None, - referer: str = None, + referer: str = None ) -> typing.Union["Response", NoneType]: """Page.goto @@ -6716,8 +6750,9 @@ def goto( def reload( self, + *, timeout: float = None, - wait_until: Literal["domcontentloaded", "load", "networkidle"] = None, + wait_until: Literal["domcontentloaded", "load", "networkidle"] = None ) -> typing.Union["Response", NoneType]: """Page.reload @@ -6756,7 +6791,8 @@ def reload( def wait_for_load_state( self, state: Literal["domcontentloaded", "load", "networkidle"] = None, - timeout: float = None, + *, + timeout: float = None ) -> NoneType: """Page.wait_for_load_state @@ -6810,7 +6846,7 @@ def wait_for_load_state( raise e def wait_for_event( - self, event: str, predicate: typing.Callable = None, timeout: float = None + self, event: str, predicate: typing.Callable = None, *, timeout: float = None ) -> typing.Any: """Page.wait_for_event @@ -6854,8 +6890,9 @@ def wait_for_event( def go_back( self, + *, timeout: float = None, - wait_until: Literal["domcontentloaded", "load", "networkidle"] = None, + wait_until: Literal["domcontentloaded", "load", "networkidle"] = None ) -> typing.Union["Response", NoneType]: """Page.go_back @@ -6897,8 +6934,9 @@ def go_back( def go_forward( self, + *, timeout: float = None, - wait_until: Literal["domcontentloaded", "load", "networkidle"] = None, + wait_until: Literal["domcontentloaded", "load", "networkidle"] = None ) -> typing.Union["Response", NoneType]: """Page.go_forward @@ -6940,8 +6978,9 @@ def go_forward( def emulate_media( self, + *, media: Literal["print", "screen"] = None, - color_scheme: Literal["dark", "light", "no-preference"] = None, + color_scheme: Literal["dark", "light", "no-preference"] = None ) -> NoneType: """Page.emulate_media @@ -7046,7 +7085,7 @@ def bring_to_front(self) -> NoneType: raise e def add_init_script( - self, script: str = None, path: typing.Union[str, pathlib.Path] = None + self, script: str = None, *, path: typing.Union[str, pathlib.Path] = None ) -> NoneType: """Page.add_init_script @@ -7187,13 +7226,14 @@ def unroute( def screenshot( self, + *, timeout: float = None, type: Literal["jpeg", "png"] = None, path: typing.Union[str, pathlib.Path] = None, quality: int = None, omit_background: bool = None, full_page: bool = None, - clip: FloatRect = None, + clip: FloatRect = None ) -> bytes: """Page.screenshot @@ -7269,7 +7309,7 @@ def title(self) -> str: log_api("<= page.title failed") raise e - def close(self, run_before_unload: bool = None) -> NoneType: + def close(self, *, run_before_unload: bool = None) -> NoneType: """Page.close If `runBeforeUnload` is `false`, does not run any unload handlers and waits for the page to be closed. If @@ -7320,6 +7360,7 @@ def is_closed(self) -> bool: def click( self, selector: str, + *, modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, @@ -7329,7 +7370,7 @@ def click( click_count: int = None, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Page.click @@ -7400,6 +7441,7 @@ def click( def dblclick( self, selector: str, + *, modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, @@ -7408,7 +7450,7 @@ def dblclick( button: Literal["left", "middle", "right"] = None, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Page.dblclick @@ -7479,13 +7521,14 @@ def dblclick( def tap( self, selector: str, + *, modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, position: Position = None, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Page.tap @@ -7550,8 +7593,9 @@ def fill( self, selector: str, value: str, + *, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Page.fill @@ -7598,7 +7642,7 @@ def fill( log_api("<= page.fill failed") raise e - def focus(self, selector: str, timeout: float = None) -> NoneType: + def focus(self, selector: str, *, timeout: float = None) -> NoneType: """Page.focus This method fetches an element with `selector` and focuses it. If there's no element matching `selector`, the method @@ -7628,7 +7672,7 @@ def focus(self, selector: str, timeout: float = None) -> NoneType: raise e def text_content( - self, selector: str, timeout: float = None + self, selector: str, *, timeout: float = None ) -> typing.Union[str, NoneType]: """Page.text_content @@ -7661,7 +7705,7 @@ def text_content( log_api("<= page.text_content failed") raise e - def inner_text(self, selector: str, timeout: float = None) -> str: + def inner_text(self, selector: str, *, timeout: float = None) -> str: """Page.inner_text Returns `element.innerText`. @@ -7693,7 +7737,7 @@ def inner_text(self, selector: str, timeout: float = None) -> str: log_api("<= page.inner_text failed") raise e - def inner_html(self, selector: str, timeout: float = None) -> str: + def inner_html(self, selector: str, *, timeout: float = None) -> str: """Page.inner_html Returns `element.innerHTML`. @@ -7726,7 +7770,7 @@ def inner_html(self, selector: str, timeout: float = None) -> str: raise e def get_attribute( - self, selector: str, name: str, timeout: float = None + self, selector: str, name: str, *, timeout: float = None ) -> typing.Union[str, NoneType]: """Page.get_attribute @@ -7766,12 +7810,13 @@ def get_attribute( def hover( self, selector: str, + *, modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, position: Position = None, timeout: float = None, - force: bool = None, + force: bool = None ) -> NoneType: """Page.hover @@ -7829,11 +7874,12 @@ def select_option( self, selector: str, value: typing.Union[str, typing.List[str]] = None, + *, index: typing.Union[int, typing.List[int]] = None, label: typing.Union[str, typing.List[str]] = None, element: typing.Union["ElementHandle", typing.List["ElementHandle"]] = None, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> typing.List[str]: """Page.select_option @@ -7914,8 +7960,9 @@ def set_input_files( typing.List[typing.Union[str, pathlib.Path]], typing.List[FilePayload], ], + *, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Page.set_input_files @@ -7962,9 +8009,10 @@ def type( self, selector: str, text: str, + *, delay: float = None, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Page.type @@ -8021,9 +8069,10 @@ def press( self, selector: str, key: str, + *, delay: float = None, timeout: float = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Page.press @@ -8098,9 +8147,10 @@ def press( def check( self, selector: str, + *, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Page.check @@ -8157,9 +8207,10 @@ def check( def uncheck( self, selector: str, + *, timeout: float = None, force: bool = None, - no_wait_after: bool = None, + no_wait_after: bool = None ) -> NoneType: """Page.uncheck @@ -8248,10 +8299,11 @@ def wait_for_timeout(self, timeout: float) -> NoneType: def wait_for_function( self, expression: str, + *, arg: typing.Any = None, force_expr: bool = None, timeout: float = None, - polling: typing.Union[float, Literal["raf"]] = None, + polling: typing.Union[float, Literal["raf"]] = None ) -> "JSHandle": """Page.wait_for_function @@ -8326,6 +8378,7 @@ def run(playwright): def pdf( self, + *, scale: float = None, display_header_footer: bool = None, header_template: str = None, @@ -8338,7 +8391,7 @@ def pdf( height: typing.Union[str, float] = None, prefer_css_page_size: bool = None, margin: PdfMargins = None, - path: typing.Union[str, pathlib.Path] = None, + path: typing.Union[str, pathlib.Path] = None ) -> bytes: """Page.pdf @@ -8458,7 +8511,7 @@ def pdf( raise e def expect_event( - self, event: str, predicate: typing.Callable = None, timeout: float = None + self, event: str, predicate: typing.Callable = None, *, timeout: float = None ) -> EventContextManager: """Page.expect_event @@ -8495,7 +8548,8 @@ def expect_event( def expect_console_message( self, predicate: typing.Union[typing.Callable[["ConsoleMessage"], bool]] = None, - timeout: float = None, + *, + timeout: float = None ) -> EventContextManager["ConsoleMessage"]: """Page.expect_console_message @@ -8525,7 +8579,8 @@ def expect_console_message( def expect_download( self, predicate: typing.Union[typing.Callable[["Download"], bool]] = None, - timeout: float = None, + *, + timeout: float = None ) -> EventContextManager["Download"]: """Page.expect_download @@ -8555,7 +8610,8 @@ def expect_download( def expect_file_chooser( self, predicate: typing.Union[typing.Callable[["FileChooser"], bool]] = None, - timeout: float = None, + *, + timeout: float = None ) -> EventContextManager["FileChooser"]: """Page.expect_file_chooser @@ -8584,9 +8640,10 @@ def expect_file_chooser( def expect_navigation( self, + *, url: typing.Union[str, typing.Pattern, typing.Callable[[str], bool]] = None, wait_until: Literal["domcontentloaded", "load", "networkidle"] = None, - timeout: float = None, + timeout: float = None ) -> EventContextManager["Response"]: """Page.expect_navigation @@ -8638,7 +8695,8 @@ def expect_navigation( def expect_popup( self, predicate: typing.Union[typing.Callable[["Page"], bool]] = None, - timeout: float = None, + *, + timeout: float = None ) -> EventContextManager["Page"]: """Page.expect_popup @@ -8670,7 +8728,8 @@ def expect_request( url_or_predicate: typing.Union[ str, typing.Pattern, typing.Callable[["Request"], bool] ], - timeout: float = None, + *, + timeout: float = None ) -> EventContextManager["Request"]: """Page.expect_request @@ -8710,7 +8769,8 @@ def expect_response( url_or_predicate: typing.Union[ str, typing.Pattern, typing.Callable[["Response"], bool] ], - timeout: float = None, + *, + timeout: float = None ) -> EventContextManager["Response"]: """Page.expect_response @@ -8744,7 +8804,8 @@ def expect_response( def expect_worker( self, predicate: typing.Union[typing.Callable[["Worker"], bool]] = None, - timeout: float = None, + *, + timeout: float = None ) -> EventContextManager["Worker"]: """Page.expect_worker @@ -8950,7 +9011,7 @@ def clear_cookies(self) -> NoneType: raise e def grant_permissions( - self, permissions: typing.List[str], origin: str = None + self, permissions: typing.List[str], *, origin: str = None ) -> NoneType: """BrowserContext.grant_permissions @@ -9099,7 +9160,7 @@ def set_offline(self, offline: bool) -> NoneType: raise e def add_init_script( - self, script: str = None, path: typing.Union[str, pathlib.Path] = None + self, script: str = None, *, path: typing.Union[str, pathlib.Path] = None ) -> NoneType: """BrowserContext.add_init_script @@ -9142,7 +9203,7 @@ def add_init_script( raise e def expose_binding( - self, name: str, callback: typing.Callable, handle: bool = None + self, name: str, callback: typing.Callable, *, handle: bool = None ) -> NoneType: """BrowserContext.expose_binding @@ -9390,7 +9451,7 @@ def unroute( raise e def expect_event( - self, event: str, predicate: typing.Callable = None, timeout: float = None + self, event: str, predicate: typing.Callable = None, *, timeout: float = None ) -> EventContextManager: """BrowserContext.expect_event @@ -9442,7 +9503,7 @@ def close(self) -> NoneType: raise e def storage_state( - self, path: typing.Union[str, pathlib.Path] = None + self, *, path: typing.Union[str, pathlib.Path] = None ) -> StorageState: """BrowserContext.storage_state @@ -9472,7 +9533,7 @@ def storage_state( raise e def wait_for_event( - self, event: str, predicate: typing.Callable = None, timeout: float = None + self, event: str, predicate: typing.Callable = None, *, timeout: float = None ) -> typing.Any: """BrowserContext.wait_for_event @@ -9517,7 +9578,8 @@ def wait_for_event( def expect_page( self, predicate: typing.Union[typing.Callable[["Page"], bool]] = None, - timeout: float = None, + *, + timeout: float = None ) -> EventContextManager["Page"]: """BrowserContext.expect_page @@ -9714,6 +9776,7 @@ def is_connected(self) -> bool: def new_context( self, + *, viewport: ViewportSize = None, no_viewport: bool = None, ignore_https_errors: bool = None, @@ -9738,7 +9801,7 @@ def new_context( record_har_omit_content: bool = None, record_video_dir: typing.Union[str, pathlib.Path] = None, record_video_size: ViewportSize = None, - storage_state: typing.Union[StorageState, str, pathlib.Path] = None, + storage_state: typing.Union[StorageState, str, pathlib.Path] = None ) -> "BrowserContext": """Browser.new_context @@ -9859,6 +9922,7 @@ def new_context( def new_page( self, + *, viewport: ViewportSize = None, no_viewport: bool = None, ignore_https_errors: bool = None, @@ -9883,7 +9947,7 @@ def new_page( record_har_omit_content: bool = None, record_video_dir: typing.Union[str, pathlib.Path] = None, record_video_size: ViewportSize = None, - storage_state: typing.Union[StorageState, str, pathlib.Path] = None, + storage_state: typing.Union[StorageState, str, pathlib.Path] = None ) -> "Page": """Browser.new_page @@ -10052,6 +10116,7 @@ def executable_path(self) -> str: def launch( self, + *, executable_path: typing.Union[str, pathlib.Path] = None, args: typing.List[str] = None, ignore_default_args: typing.Union[bool, typing.List[str]] = None, @@ -10068,7 +10133,7 @@ def launch( chromium_sandbox: bool = None, firefox_user_prefs: typing.Union[ typing.Dict[str, typing.Union[str, float, bool]] - ] = None, + ] = None ) -> "Browser": """BrowserType.launch @@ -10178,6 +10243,7 @@ def launch( def launch_persistent_context( self, user_data_dir: typing.Union[str, pathlib.Path], + *, executable_path: typing.Union[str, pathlib.Path] = None, args: typing.List[str] = None, ignore_default_args: typing.Union[bool, typing.List[str]] = None, @@ -10213,7 +10279,7 @@ def launch_persistent_context( record_har_path: typing.Union[str, pathlib.Path] = None, record_har_omit_content: bool = None, record_video_dir: typing.Union[str, pathlib.Path] = None, - record_video_size: ViewportSize = None, + record_video_size: ViewportSize = None ) -> "BrowserContext": """BrowserType.launch_persistent_context diff --git a/scripts/generate_api.py b/scripts/generate_api.py index a4daa4792..31b16908d 100644 --- a/scripts/generate_api.py +++ b/scripts/generate_api.py @@ -41,7 +41,7 @@ from playwright._impl._input import Keyboard, Mouse, Touchscreen from playwright._impl._js_handle import JSHandle, Serializable from playwright._impl._network import Request, Response, Route, WebSocket -from playwright._impl._page import BindingCall, Page, Worker +from playwright._impl._page import Page, Worker from playwright._impl._playwright import Playwright from playwright._impl._selectors import Selectors from playwright._impl._video import Video @@ -64,14 +64,55 @@ def process_type(value: Any, param: bool = False) -> str: return value +positional_exceptions = [ + r"abort\.errorCode", + r"accept\.promptText", + r"add_init_script\.script", + r"cookies\.urls", + r"dispatch_event\.eventInit", + r"eval.*\.arg", + r"expect_.*\.predicate", + r"evaluate_handle\.arg", + r"frame.*\.name", + r"register\.script", + r"select_option\.value", + r"send\.params", + r"set_geolocation\.geolocation", + r"wait_for_.*\.predicate", + r"wait_for_load_state\.state", + r"unroute\.handler", +] + + +def is_positional_exception(key: str) -> bool: + for pattern in positional_exceptions: + if re.match(pattern, key): + return True + return False + + def signature(func: FunctionType, indent: int) -> str: hints = get_type_hints(func, globals()) tokens = ["self"] split = ",\n" + " " * indent + saw_optional = False for [name, value] in hints.items(): if name == "return": continue + positional_exception = is_positional_exception(f"{func.__name__}.{name}") + if saw_optional and positional_exception: + raise Exception( + "Positional exception is not first in the list " + + f"{func.__name__}.{name}" + ) + if ( + not positional_exception + and not saw_optional + and str(value).endswith("NoneType]") + ): + saw_optional = True + tokens.append("*") processed = process_type(value, True) tokens.append(f"{to_snake_case(name)}: {processed}") return split.join(tokens) @@ -174,7 +215,7 @@ def return_value(value: Any) -> List[str]: from playwright._impl._input import Keyboard as KeyboardImpl, Mouse as MouseImpl, Touchscreen as TouchscreenImpl from playwright._impl._js_handle import JSHandle as JSHandleImpl from playwright._impl._network import Request as RequestImpl, Response as ResponseImpl, Route as RouteImpl, WebSocket as WebSocketImpl -from playwright._impl._page import BindingCall as BindingCallImpl, Page as PageImpl, Worker as WorkerImpl +from playwright._impl._page import Page as PageImpl, Worker as WorkerImpl from playwright._impl._playwright import Playwright as PlaywrightImpl from playwright._impl._selectors import Selectors as SelectorsImpl from playwright._impl._video import Video as VideoImpl @@ -202,7 +243,6 @@ def return_value(value: Any) -> List[str]: Dialog, Download, Video, - BindingCall, Page, BrowserContext, CDPSession, From 659b0e63bcca6ab4c80a47b9f3a1d395b8ae41e5 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 28 Jan 2021 15:25:24 -0800 Subject: [PATCH 011/788] chore: remove pdoc (#468) --- .github/workflows/deploy-docs.yml | 31 ---------------------------- local-requirements.txt | 1 - playwright/__init__.py | 34 ------------------------------- 3 files changed, 66 deletions(-) delete mode 100644 .github/workflows/deploy-docs.yml diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml deleted file mode 100644 index 2e90d2599..000000000 --- a/.github/workflows/deploy-docs.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Deploy docs -on: - push: - branches: [ master ] -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Node.js - uses: actions/setup-node@v1 - with: - node-version: 12.x - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r local-requirements.txt - pip install -e . - - name: Generate docs - run: pdoc3 --html -o htmldocs playwright - - name: Post doc generation - run: node scripts/postPdoc3Generation.js - - name: Deploy - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./htmldocs/playwright diff --git a/local-requirements.txt b/local-requirements.txt index 70384b335..2c29f2fec 100644 --- a/local-requirements.txt +++ b/local-requirements.txt @@ -19,4 +19,3 @@ flake8==3.8.3 twine==3.2.0 pyOpenSSL==19.1.0 service_identity==18.1.0 -pdoc3==0.9.1 diff --git a/playwright/__init__.py b/playwright/__init__.py index b537c637c..a3ee486ad 100644 --- a/playwright/__init__.py +++ b/playwright/__init__.py @@ -19,37 +19,3 @@ For more information you'll find the documentation for the sync API [here](sync_api.html) and for the async API [here](async_api.html). """ - -__pdoc__ = { - "_accessibility": False, - "_async_base": False, - "_browser": False, - "_browser_context": False, - "_browser_type": False, - "_cdp_session": False, - "_chromium_browser_context": False, - "_connection": False, - "_console_message": False, - "_dialog": False, - "_download": False, - "_element_handle": False, - "_event_context_manager": False, - "_file_chooser": False, - "_frame": False, - "_helper": False, - "_impl_to_api_mapping": False, - "_input": False, - "_js_handle": False, - "_main": False, - "_network": False, - "_object_factory": False, - "_page": False, - "_path_utils": False, - "_playwright": False, - "_selectors": False, - "_sync_base": False, - "_transport": False, - "_wait_helper": False, - "_async_playwright": False, - "_sync_playwright": False, -} From 28bba74627e876cd8ea5015b29e12dc37e1b71e6 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Tue, 2 Feb 2021 00:07:41 +0100 Subject: [PATCH 012/788] fix: stderr forward if no stderr exists (#472) Fixes #471 --- playwright/_impl/_transport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright/_impl/_transport.py b/playwright/_impl/_transport.py index c53810eb9..8e07ad1e7 100644 --- a/playwright/_impl/_transport.py +++ b/playwright/_impl/_transport.py @@ -29,7 +29,7 @@ def _get_stderr_fileno() -> Optional[int]: # pytest-xdist monkeypatches sys.stderr with an object that is not an actual file. # https://docs.python.org/3/library/faulthandler.html#issue-with-file-descriptors # This is potentially dangerous, but the best we can do. - if not hasattr(sys, "__stderr__"): + if not hasattr(sys, "__stderr__") or not sys.__stderr__: return None return sys.__stderr__.fileno() From 979182b2884d27102786852d9baf7228bc23c84d Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Wed, 3 Feb 2021 20:53:42 -0800 Subject: [PATCH 013/788] chore: roll to Playwright ToT (#481) --- README.md | 4 +- playwright/_impl/_browser_context.py | 3 + playwright/_impl/_element_handle.py | 4 - playwright/_impl/_frame.py | 21 +- playwright/_impl/_helper.py | 9 - playwright/_impl/_js_handle.py | 13 +- playwright/_impl/_page.py | 53 +- playwright/async_api/_generated.py | 555 +++++++----------- playwright/sync_api/_generated.py | 537 +++++++---------- scripts/documentation_provider.py | 85 +-- setup.py | 2 +- tests/async/test_dialog.py | 13 + tests/async/test_emulation_focus.py | 6 +- ...t_element_handle_wait_for_element_state.py | 16 +- 14 files changed, 544 insertions(+), 777 deletions(-) diff --git a/README.md b/README.md index 4d8c6b0e1..fce362ecf 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ Playwright is a Python library to automate [Chromium](https://www.chromium.org/H | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 90.0.4392.0 | ✅ | ✅ | ✅ | +| Chromium 90.0.4396.0 | ✅ | ✅ | ✅ | | WebKit 14.1 | ✅ | ✅ | ✅ | -| Firefox 85.0b5 | ✅ | ✅ | ✅ | +| Firefox 85.0b10 | ✅ | ✅ | ✅ | Headless execution is supported for all browsers on all platforms. diff --git a/playwright/_impl/_browser_context.py b/playwright/_impl/_browser_context.py index 3a10ea3da..e89195b56 100644 --- a/playwright/_impl/_browser_context.py +++ b/playwright/_impl/_browser_context.py @@ -235,6 +235,9 @@ async def close(self) -> None: if not is_safe_close_error(e): raise e + async def _pause(self) -> None: + await self._channel.send("pause") + async def storage_state(self, path: Union[str, Path] = None) -> StorageState: result = await self._channel.send_return_as_dict("storageState") if path: diff --git a/playwright/_impl/_element_handle.py b/playwright/_impl/_element_handle.py index 3baee9dab..af0861f44 100644 --- a/playwright/_impl/_element_handle.py +++ b/playwright/_impl/_element_handle.py @@ -243,7 +243,6 @@ async def eval_on_selector( selector: str, expression: str, arg: Serializable = None, - force_expr: bool = None, ) -> Any: return parse_result( await self._channel.send( @@ -251,7 +250,6 @@ async def eval_on_selector( dict( selector=selector, expression=expression, - isFunction=not (force_expr), arg=serialize_argument(arg), ), ) @@ -262,7 +260,6 @@ async def eval_on_selector_all( selector: str, expression: str, arg: Serializable = None, - force_expr: bool = None, ) -> Any: return parse_result( await self._channel.send( @@ -270,7 +267,6 @@ async def eval_on_selector_all( dict( selector=selector, expression=expression, - isFunction=not (force_expr), arg=serialize_argument(arg), ), ) diff --git a/playwright/_impl/_frame.py b/playwright/_impl/_frame.py index e4248356d..77de350c4 100644 --- a/playwright/_impl/_frame.py +++ b/playwright/_impl/_frame.py @@ -36,7 +36,6 @@ MouseButton, URLMatch, URLMatcher, - is_function_body, locals_to_params, monotonic_time, ) @@ -195,33 +194,25 @@ async def wait_for_load_state( async def frame_element(self) -> ElementHandle: return from_channel(await self._channel.send("frameElement")) - async def evaluate( - self, expression: str, arg: Serializable = None, force_expr: bool = None - ) -> Any: - if not is_function_body(expression): - force_expr = True + async def evaluate(self, expression: str, arg: Serializable = None) -> Any: return parse_result( await self._channel.send( "evaluateExpression", dict( expression=expression, - isFunction=not (force_expr), arg=serialize_argument(arg), ), ) ) async def evaluate_handle( - self, expression: str, arg: Serializable = None, force_expr: bool = None + self, expression: str, arg: Serializable = None ) -> JSHandle: - if not is_function_body(expression): - force_expr = True return from_channel( await self._channel.send( "evaluateExpressionHandle", dict( expression=expression, - isFunction=not (force_expr), arg=serialize_argument(arg), ), ) @@ -281,7 +272,6 @@ async def eval_on_selector( selector: str, expression: str, arg: Serializable = None, - force_expr: bool = None, ) -> Any: return parse_result( await self._channel.send( @@ -289,7 +279,6 @@ async def eval_on_selector( dict( selector=selector, expression=expression, - isFunction=not (force_expr), arg=serialize_argument(arg), ), ) @@ -300,7 +289,6 @@ async def eval_on_selector_all( selector: str, expression: str, arg: Serializable = None, - force_expr: bool = None, ) -> Any: return parse_result( await self._channel.send( @@ -308,7 +296,6 @@ async def eval_on_selector_all( dict( selector=selector, expression=expression, - isFunction=not (force_expr), arg=serialize_argument(arg), ), ) @@ -516,14 +503,10 @@ async def wait_for_function( self, expression: str, arg: Serializable = None, - force_expr: bool = None, timeout: float = None, polling: Union[float, Literal["raf"]] = None, ) -> JSHandle: - if not is_function_body(expression): - force_expr = True params = locals_to_params(locals()) - params["isFunction"] = not (force_expr) params["arg"] = serialize_argument(arg) return from_channel(await self._channel.send("waitForFunction", params)) diff --git a/playwright/_impl/_helper.py b/playwright/_impl/_helper.py index 4d1035f06..6b30a2bcd 100644 --- a/playwright/_impl/_helper.py +++ b/playwright/_impl/_helper.py @@ -175,15 +175,6 @@ def patch_error_message(message: Optional[str]) -> Optional[str]: return message -def is_function_body(expression: str) -> bool: - expression = expression.strip() - return ( - expression.startswith("function") - or expression.startswith("async ") - or "=>" in expression - ) - - def locals_to_params(args: Dict) -> Dict: copy = {} for key in args: diff --git a/playwright/_impl/_js_handle.py b/playwright/_impl/_js_handle.py index 62f046624..c7e7eea92 100644 --- a/playwright/_impl/_js_handle.py +++ b/playwright/_impl/_js_handle.py @@ -18,7 +18,6 @@ from playwright._impl._api_types import Error from playwright._impl._connection import ChannelOwner, from_channel -from playwright._impl._helper import is_function_body if TYPE_CHECKING: # pragma: no cover from playwright._impl._element_handle import ElementHandle @@ -43,33 +42,25 @@ def __str__(self) -> str: def _on_preview_updated(self, preview: str) -> None: self._preview = preview - async def evaluate( - self, expression: str, arg: Serializable = None, force_expr: bool = None - ) -> Any: - if not is_function_body(expression): - force_expr = True + async def evaluate(self, expression: str, arg: Serializable = None) -> Any: return parse_result( await self._channel.send( "evaluateExpression", dict( expression=expression, - isFunction=not (force_expr), arg=serialize_argument(arg), ), ) ) async def evaluate_handle( - self, expression: str, arg: Serializable = None, force_expr: bool = None + self, expression: str, arg: Serializable = None ) -> "JSHandle": - if not is_function_body(expression): - force_expr = True return from_channel( await self._channel.send( "evaluateExpressionHandle", dict( expression=expression, - isFunction=not (force_expr), arg=serialize_argument(arg), ), ) diff --git a/playwright/_impl/_page.py b/playwright/_impl/_page.py index 3afce4b33..e7c19cce4 100644 --- a/playwright/_impl/_page.py +++ b/playwright/_impl/_page.py @@ -52,7 +52,6 @@ URLMatcher, URLMatchRequest, URLMatchResponse, - is_function_body, is_safe_close_error, locals_to_params, parse_error, @@ -139,12 +138,7 @@ def __init__( ), ) self._channel.on("crash", lambda _: self._on_crash()) - self._channel.on( - "dialog", - lambda params: self.emit( - Page.Events.Dialog, from_channel(params["dialog"]) - ), - ) + self._channel.on("dialog", lambda params: self._on_dialog(params)) self._channel.on( "domcontentloaded", lambda _: self.emit(Page.Events.DOMContentLoaded) ) @@ -290,6 +284,13 @@ def _on_close(self) -> None: def _on_crash(self) -> None: self.emit(Page.Events.Crash) + def _on_dialog(self, params: Any) -> None: + dialog = from_channel(params["dialog"]) + if self.listeners(Page.Events.Dialog): + self.emit(Page.Events.Dialog, dialog) + else: + asyncio.create_task(dialog.dismiss()) + def _add_event_handler(self, event: str, k: Any, v: Any) -> None: if event == Page.Events.FileChooser and len(self.listeners(event)) == 0: self._channel.send_no_reply( @@ -375,39 +376,29 @@ async def dispatch_event( ) -> None: return await self._main_frame.dispatch_event(**locals_to_params(locals())) - async def evaluate( - self, expression: str, arg: Serializable = None, force_expr: bool = None - ) -> Any: - return await self._main_frame.evaluate(expression, arg, force_expr=force_expr) + async def evaluate(self, expression: str, arg: Serializable = None) -> Any: + return await self._main_frame.evaluate(expression, arg) async def evaluate_handle( - self, expression: str, arg: Serializable = None, force_expr: bool = None + self, expression: str, arg: Serializable = None ) -> JSHandle: - return await self._main_frame.evaluate_handle( - expression, arg, force_expr=force_expr - ) + return await self._main_frame.evaluate_handle(expression, arg) async def eval_on_selector( self, selector: str, expression: str, arg: Serializable = None, - force_expr: bool = None, ) -> Any: - return await self._main_frame.eval_on_selector( - selector, expression, arg, force_expr=force_expr - ) + return await self._main_frame.eval_on_selector(selector, expression, arg) async def eval_on_selector_all( self, selector: str, expression: str, arg: Serializable = None, - force_expr: bool = None, ) -> Any: - return await self._main_frame.eval_on_selector_all( - selector, expression, arg, force_expr=force_expr - ) + return await self._main_frame.eval_on_selector_all(selector, expression, arg) async def add_script_tag( self, @@ -729,18 +720,18 @@ async def wait_for_function( self, expression: str, arg: Serializable = None, - force_expr: bool = None, timeout: float = None, polling: Union[float, Literal["raf"]] = None, ) -> JSHandle: - if not is_function_body(expression): - force_expr = True return await self._main_frame.wait_for_function(**locals_to_params(locals())) @property def workers(self) -> List["Worker"]: return self._workers.copy() + async def pause(self) -> None: + await self._browser_context._pause() + async def pdf( self, scale: float = None, @@ -903,31 +894,25 @@ def _on_close(self) -> None: def url(self) -> str: return self._initializer["url"] - async def evaluate( - self, expression: str, arg: Serializable = None, force_expr: bool = None - ) -> Any: - if not is_function_body(expression): - force_expr = True + async def evaluate(self, expression: str, arg: Serializable = None) -> Any: return parse_result( await self._channel.send( "evaluateExpression", dict( expression=expression, - isFunction=not (force_expr), arg=serialize_argument(arg), ), ) ) async def evaluate_handle( - self, expression: str, arg: Serializable = None, force_expr: bool = None + self, expression: str, arg: Serializable = None ) -> JSHandle: return from_channel( await self._channel.send( "evaluateExpressionHandle", dict( expression=expression, - isFunction=not (force_expr), arg=serialize_argument(arg), ), ) diff --git a/playwright/async_api/_generated.py b/playwright/async_api/_generated.py index 6b5343585..fe5a7cf5c 100644 --- a/playwright/async_api/_generated.py +++ b/playwright/async_api/_generated.py @@ -1132,17 +1132,14 @@ class JSHandle(AsyncBase): def __init__(self, obj: JSHandleImpl): super().__init__(obj) - async def evaluate( - self, expression: str, arg: typing.Any = None, *, force_expr: bool = None - ) -> typing.Any: + async def evaluate(self, expression: str, arg: typing.Any = None) -> typing.Any: """JSHandle.evaluate - Returns the return value of `pageFunction` + Returns the return value of `expression`. - This method passes this handle as the first argument to `pageFunction`. + This method passes this handle as the first argument to `expression`. - If `pageFunction` returns a [Promise], then `handle.evaluate` would wait for the promise to resolve and return its - value. + If `expression` returns a [Promise], then `handle.evaluate` would wait for the promise to resolve and return its value. Examples: @@ -1157,10 +1154,7 @@ async def evaluate( JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted as a function. Otherwise, evaluated as an expression. arg : Union[Any, NoneType] - Optional argument to pass to `pageFunction` - force_expr : Union[bool, NoneType] - Whether to treat given `expression` as JavaScript evaluate expression, even though it looks like an arrow function. - Optional. + Optional argument to pass to `expression` Returns ------- @@ -1171,9 +1165,7 @@ async def evaluate( log_api("=> js_handle.evaluate started") result = mapping.from_maybe_impl( await self._impl_obj.evaluate( - expression=expression, - arg=mapping.to_impl(arg), - force_expr=force_expr, + expression=expression, arg=mapping.to_impl(arg) ) ) log_api("<= js_handle.evaluate succeded") @@ -1183,16 +1175,16 @@ async def evaluate( raise e async def evaluate_handle( - self, expression: str, arg: typing.Any = None, *, force_expr: bool = None + self, expression: str, arg: typing.Any = None ) -> "JSHandle": """JSHandle.evaluate_handle - Returns the return value of `pageFunction` as in-page object (JSHandle). + Returns the return value of `expression` as a `JSHandle`. - This method passes this handle as the first argument to `pageFunction`. + This method passes this handle as the first argument to `expression`. The only difference between `jsHandle.evaluate` and `jsHandle.evaluateHandle` is that `jsHandle.evaluateHandle` returns - in-page object (JSHandle). + `JSHandle`. If the function passed to the `jsHandle.evaluateHandle` returns a [Promise], then `jsHandle.evaluateHandle` would wait for the promise to resolve and return its value. @@ -1205,10 +1197,7 @@ async def evaluate_handle( JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted as a function. Otherwise, evaluated as an expression. arg : Union[Any, NoneType] - Optional argument to pass to `pageFunction` - force_expr : Union[bool, NoneType] - Whether to treat given `expression` as JavaScript evaluate expression, even though it looks like an arrow function. - Optional. + Optional argument to pass to `expression` Returns ------- @@ -1219,9 +1208,7 @@ async def evaluate_handle( log_api("=> js_handle.evaluate_handle started") result = mapping.from_impl( await self._impl_obj.evaluate_handle( - expression=expression, - arg=mapping.to_impl(arg), - force_expr=force_expr, + expression=expression, arg=mapping.to_impl(arg) ) ) log_api("<= js_handle.evaluate_handle succeded") @@ -2016,8 +2003,10 @@ async def fill( """ElementHandle.fill This method waits for [actionability](./actionability.md) checks, focuses the element, fills it and triggers an `input` - event after filling. If the element is not an ``, ` diff --git a/tests/async/test_dispatch_event.py b/tests/async/test_dispatch_event.py index 701e5585f..897d797ef 100644 --- a/tests/async/test_dispatch_event.py +++ b/tests/async/test_dispatch_event.py @@ -123,7 +123,7 @@ async def test_should_be_atomic(selectors, page, utils): return result; }, queryAll(root, selector) { - const result = Array.from(root.query_selector_all(selector)); + const result = Array.from(root.querySelectorAll(selector)); for (const e of result) Promise.resolve().then(() => result.onclick = ""); return result; diff --git a/tests/async/test_locators.py b/tests/async/test_locators.py new file mode 100644 index 000000000..3d66e19c2 --- /dev/null +++ b/tests/async/test_locators.py @@ -0,0 +1,458 @@ +# Copyright (c) Microsoft Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +import pytest + +from playwright._impl._path_utils import get_file_dirname +from playwright.async_api import Error, Page +from tests.server import Server + +_dirname = get_file_dirname() +FILE_TO_UPLOAD = _dirname / ".." / "assets/file-to-upload.txt" + + +async def test_locators_click_should_work(page: Page, server: Server): + await page.goto(server.PREFIX + "/input/button.html") + button = page.locator("button") + await button.click() + assert await page.evaluate("window['result']") == "Clicked" + + +async def test_locators_click_should_work_with_node_removed(page: Page, server: Server): + await page.goto(server.PREFIX + "/input/button.html") + await page.evaluate("delete window['Node']") + button = page.locator("button") + await button.click() + assert await page.evaluate("window['result']") == "Clicked" + + +async def test_locators_click_should_work_for_text_nodes(page: Page, server: Server): + await page.goto(server.PREFIX + "/input/button.html") + await page.evaluate( + """() => { + window['double'] = false; + const button = document.querySelector('button'); + button.addEventListener('dblclick', event => { + window['double'] = true; + }); + }""" + ) + button = page.locator("button") + await button.dblclick() + assert await page.evaluate("double") is True + assert await page.evaluate("result") == "Clicked" + + +async def test_locators_should_have_repr(page: Page, server: Server): + await page.goto(server.PREFIX + "/input/button.html") + button = page.locator("button") + await button.click() + assert ( + str(button) + == f" selector='button'>" + ) + + +async def test_locators_get_attribute_should_work(page: Page, server: Server): + await page.goto(server.PREFIX + "/dom.html") + button = page.locator("#outer") + assert await button.get_attribute("name") == "value" + assert await button.get_attribute("foo") is None + + +async def test_locators_input_value_should_work(page: Page, server: Server): + await page.goto(server.PREFIX + "/dom.html") + await page.fill("#textarea", "input value") + text_area = page.locator("#textarea") + assert await text_area.input_value() == "input value" + + +async def test_locators_inner_html_should_work(page: Page, server: Server): + await page.goto(server.PREFIX + "/dom.html") + locator = page.locator("#outer") + assert await locator.inner_html() == '
Text,\nmore text
' + + +async def test_locators_inner_text_should_work(page: Page, server: Server): + await page.goto(server.PREFIX + "/dom.html") + locator = page.locator("#inner") + assert await locator.inner_text() == "Text, more text" + + +async def test_locators_text_content_should_work(page: Page, server: Server): + await page.goto(server.PREFIX + "/dom.html") + locator = page.locator("#inner") + assert await locator.text_content() == "Text,\nmore text" + + +async def test_locators_is_hidden_and_is_visible_should_work(page: Page): + await page.set_content("
Hi
") + + div = page.locator("div") + assert await div.is_visible() is True + assert await div.is_hidden() is False + + span = page.locator("span") + assert await span.is_visible() is False + assert await span.is_hidden() is True + + +async def test_locators_is_enabled_and_is_disabled_should_work(page: Page): + await page.set_content( + """ + + +
div
+ """ + ) + + div = page.locator("div") + assert await div.is_enabled() is True + assert await div.is_disabled() is False + + button1 = page.locator(':text("button1")') + assert await button1.is_enabled() is False + assert await button1.is_disabled() is True + + button1 = page.locator(':text("button2")') + assert await button1.is_enabled() is True + assert await button1.is_disabled() is False + + +async def test_locators_is_editable_should_work(page: Page): + await page.set_content( + """ + + """ + ) + + input1 = page.locator("#input1") + assert await input1.is_editable() is False + + input2 = page.locator("#input2") + assert await input2.is_editable() is True + + +async def test_locators_is_checked_should_work(page: Page): + await page.set_content( + """ +
Not a checkbox
+ """ + ) + + element = page.locator("input") + assert await element.is_checked() is True + await element.evaluate("e => e.checked = false") + assert await element.is_checked() is False + + +async def test_locators_all_text_contents_should_work(page: Page): + await page.set_content( + """ +
A
B
C
+ """ + ) + + element = page.locator("div") + assert await element.all_text_contents() == ["A", "B", "C"] + + +async def test_locators_all_inner_texts(page: Page): + await page.set_content( + """ +
A
B
C
+ """ + ) + + element = page.locator("div") + assert await element.all_inner_texts() == ["A", "B", "C"] + + +async def test_locators_should_query_existing_element(page: Page, server: Server): + await page.goto(server.PREFIX + "/playground.html") + await page.set_content( + """
A
""" + ) + html = page.locator("html") + second = html.locator(".second") + inner = second.locator(".inner") + assert ( + await page.evaluate("e => e.textContent", await inner.element_handle()) == "A" + ) + + +async def test_locators_evaluate_handle_should_work(page: Page, server: Server): + await page.goto(server.PREFIX + "/dom.html") + outer = page.locator("#outer") + inner = outer.locator("#inner") + check = inner.locator("#check") + text = await inner.evaluate_handle("e => e.firstChild") + await page.evaluate("1 + 1") + assert ( + str(outer) + == f" selector='#outer'>" + ) + assert ( + str(inner) + == f" selector='#outer >> #inner'>" + ) + assert str(text) == "JSHandle@#text=Text,↵more text" + assert ( + str(check) + == f" selector='#outer >> #inner >> #check'>" + ) + + +async def test_locators_should_query_existing_elements(page: Page): + await page.set_content( + """
A

B
""" + ) + html = page.locator("html") + elements = await html.locator("div").element_handles() + assert len(elements) == 2 + result = [] + for element in elements: + result.append(await page.evaluate("e => e.textContent", element)) + assert result == ["A", "B"] + + +async def test_locators_return_empty_array_for_non_existing_elements(page: Page): + await page.set_content( + """
A

B
""" + ) + html = page.locator("html") + elements = await html.locator("abc").element_handles() + assert len(elements) == 0 + assert elements == [] + + +async def test_locators_evaluate_all_should_work(page: Page): + await page.set_content( + """
""" + ) + tweet = page.locator(".tweet .like") + content = await tweet.evaluate_all("nodes => nodes.map(n => n.innerText)") + assert content == ["100", "10"] + + +async def test_locators_evaluate_all_should_work_with_missing_selector(page: Page): + await page.set_content( + """
not-a-child-div
nodes.length") + assert nodes_length == 0 + + +async def test_locators_hover_should_work(page: Page, server: Server): + await page.goto(server.PREFIX + "/input/scrollable.html") + button = page.locator("#button-6") + await button.hover() + assert ( + await page.evaluate("document.querySelector('button:hover').id") == "button-6" + ) + + +async def test_locators_fill_should_work(page: Page, server: Server): + await page.goto(server.PREFIX + "/input/textarea.html") + button = page.locator("input") + await button.fill("some value") + assert await page.evaluate("result") == "some value" + + +async def test_locators_check_should_work(page: Page): + await page.set_content("") + button = page.locator("input") + await button.check() + assert await page.evaluate("checkbox.checked") is True + + +async def test_locators_uncheck_should_work(page: Page): + await page.set_content("") + button = page.locator("input") + await button.uncheck() + assert await page.evaluate("checkbox.checked") is False + + +async def test_locators_select_option_should_work(page: Page, server: Server): + await page.goto(server.PREFIX + "/input/select.html") + select = page.locator("select") + await select.select_option("blue") + assert await page.evaluate("result.onInput") == ["blue"] + assert await page.evaluate("result.onChange") == ["blue"] + + +async def test_locators_focus_should_work(page: Page, server: Server): + await page.goto(server.PREFIX + "/input/button.html") + button = page.locator("button") + assert await button.evaluate("button => document.activeElement === button") is False + await button.focus() + assert await button.evaluate("button => document.activeElement === button") is True + + +async def test_locators_dispatch_event_should_work(page: Page, server: Server): + await page.goto(server.PREFIX + "/input/button.html") + button = page.locator("button") + await button.dispatch_event("click") + assert await page.evaluate("result") == "Clicked" + + +async def test_locators_should_upload_a_file(page: Page, server: Server): + await page.goto(server.PREFIX + "/input/fileupload.html") + input = page.locator("input[type=file]") + + file_path = os.path.relpath(FILE_TO_UPLOAD, os.getcwd()) + await input.set_input_files(file_path) + assert ( + await page.evaluate("e => e.files[0].name", await input.element_handle()) + == "file-to-upload.txt" + ) + + +async def test_locators_should_press(page: Page): + await page.set_content("") + await page.locator("input").press("h") + await page.eval_on_selector("input", "input => input.value") == "h" + + +async def test_locators_should_scroll_into_view(page: Page, server: Server): + await page.goto(server.PREFIX + "/offscreenbuttons.html") + for i in range(11): + button = page.locator(f"#btn{i}") + before = await button.evaluate( + "button => button.getBoundingClientRect().right - window.innerWidth" + ) + assert before == 10 * i + await button.scroll_into_view_if_needed() + after = await button.evaluate( + "button => button.getBoundingClientRect().right - window.innerWidth" + ) + assert after <= 0 + await page.evaluate("window.scrollTo(0, 0)") + + +async def test_locators_should_select_textarea( + page: Page, server: Server, browser_name: str +): + await page.goto(server.PREFIX + "/input/textarea.html") + textarea = page.locator("textarea") + await textarea.evaluate("textarea => textarea.value = 'some value'") + await textarea.select_text() + if browser_name == "firefox": + assert await textarea.evaluate("el => el.selectionStart") == 0 + assert await textarea.evaluate("el => el.selectionEnd") == 10 + else: + assert await page.evaluate("window.getSelection().toString()") == "some value" + + +async def test_locators_should_type(page: Page): + await page.set_content("") + await page.locator("input").type("hello") + await page.eval_on_selector("input", "input => input.value") == "hello" + + +async def test_locators_should_screenshot( + page: Page, server: Server, assert_to_be_golden +): + await page.set_viewport_size( + { + "width": 500, + "height": 500, + } + ) + await page.goto(server.PREFIX + "/grid.html") + await page.evaluate("window.scrollBy(50, 100)") + element = page.locator(".box:nth-of-type(3)") + assert_to_be_golden( + await element.screenshot(), "screenshot-element-bounding-box.png" + ) + + +async def test_locators_should_return_bounding_box(page: Page, server: Server): + await page.set_viewport_size( + { + "width": 500, + "height": 500, + } + ) + await page.goto(server.PREFIX + "/grid.html") + element = page.locator(".box:nth-of-type(13)") + box = await element.bounding_box() + assert box == { + "x": 100, + "y": 50, + "width": 50, + "height": 50, + } + + +async def test_locators_should_respect_first_and_last(page: Page): + await page.set_content( + """ +
+

A

+

A

A

+

A

A

A

+
""" + ) + assert await page.locator("div >> p").count() == 6 + assert await page.locator("div").locator("p").count() == 6 + assert await page.locator("div").first.locator("p").count() == 1 + assert await page.locator("div").last.locator("p").count() == 3 + + +async def test_locators_should_respect_nth(page: Page): + await page.set_content( + """ +
+

A

+

A

A

+

A

A

A

+
""" + ) + assert await page.locator("div >> p").nth(0).count() == 1 + assert await page.locator("div").nth(1).locator("p").count() == 2 + assert await page.locator("div").nth(2).locator("p").count() == 3 + + +async def test_locators_should_throw_on_capture_without_nth(page: Page): + await page.set_content( + """ +

A

+ """ + ) + with pytest.raises(Error, match="Can't query n-th element"): + await page.locator("*css=div >> p").nth(0).click() + + +async def test_locators_should_throw_due_to_strictness(page: Page): + await page.set_content( + """ +
A
B
+ """ + ) + with pytest.raises(Error, match="strict mode violation"): + await page.locator("div").is_visible() + + +async def test_locators_should_throw_due_to_strictness_2(page: Page): + await page.set_content( + """ + + """ + ) + with pytest.raises(Error, match="strict mode violation"): + await page.locator("option").evaluate("e => {}") diff --git a/tests/async/test_tap.py b/tests/async/test_tap.py index f60cfddce..026e3cdcd 100644 --- a/tests/async/test_tap.py +++ b/tests/async/test_tap.py @@ -16,7 +16,7 @@ import pytest -from playwright.async_api import ElementHandle, JSHandle +from playwright.async_api import ElementHandle, JSHandle, Page @pytest.fixture @@ -190,6 +190,34 @@ async def test_should_wait_until_an_element_is_visible_to_tap_it(page): assert await div.text_content() == "clicked" +async def test_locators_tap(page: Page): + await page.set_content( + """ +
a
+
b
+ """ + ) + await page.locator("#a").tap() + element_handle = await track_events(await page.query_selector("#b")) + await page.locator("#b").tap() + assert await element_handle.json_value() == [ + "pointerover", + "pointerenter", + "pointerdown", + "touchstart", + "pointerup", + "pointerout", + "pointerleave", + "touchend", + "mouseover", + "mouseenter", + "mousemove", + "mousedown", + "mouseup", + "click", + ] + + async def track_events(target: ElementHandle) -> JSHandle: return await target.evaluate_handle( """target => { diff --git a/tests/golden-chromium/screenshot-element-bounding-box.png b/tests/golden-chromium/screenshot-element-bounding-box.png new file mode 100644 index 0000000000000000000000000000000000000000..c2c3ddca298aba5c502f56e6656fd9330220b327 GIT binary patch literal 474 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-#^NA%Cx&(BWL^TK3NDj4wz}vwUf`e5)Ukjg=c4B?ZY6Q{gD+<_wP}}l z6nSQ36>Zn#-f|$iNz7fE$&pKHm-_rCzvuN??>N7ATC?@xO>6j=CvYp+J%0ba_|K}- z?e1;A{tEc13fvXy$m4X`&ax<)>7s7qi)jue-U{}mHHEE$Jc%sMXWq*OW$s-$i%G;W zZF||t6r&}VGkq>ExtSx>>!xYDf7J|T5r=j2<5pbF65(PsvM%I1RJ=tim8p?oS*Dfk z^|OT?x8ELn{&}OJ?ag94p-v0itCJs3c=3Z{t=G@fKZ902`4Zw^|LI!P4gZAOW-CLy zZnhPjNK*3L8l^h>>?RwlH95zZzB$)Wuj{urPJRCQ;w>U!mP`4PSezL|x?Qg=R|`2? z63iqS9eMQe#}9S&%O8Ea|IFgamuF(Pw=sImn|nEL`|j&|;`G&T&-U|Y;!@~!TUip(bP*}7q&3SPEgSM@h9ZZ`&x!)qR}^g{rtKT7+DOSu6{1- HoD!M<45`mj literal 0 HcmV?d00001 diff --git a/tests/golden-firefox/screenshot-element-bounding-box.png b/tests/golden-firefox/screenshot-element-bounding-box.png new file mode 100644 index 0000000000000000000000000000000000000000..9e208f86d8f7690a79f6bf31ccdd2032512693a3 GIT binary patch literal 311 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nETf1WOmAsLNtuPUxdz^zaVj`B z@qPFONtbB0i_;vpqzdFBF)Le3V%-^74w(2>&P%VDpyJ8VkbApmUV0Y?W5NCJ1q!#f z2{KGi{dq{#RgmwA1z*>R>s67{5*_PQl^!~8(otrJyDqAya=w`9fkofFZy)$NAJ1U0 z@SS}9@F%XH^A%t3s&cJp?ci9f@rmyPmt@<+eZ9*vBSNpn-v9p4;nl8vit}0j|7H8R wg(pd%`Ne64TSgt*coyk4XF!TA6BrTzopr0K4LVFaQ7m literal 0 HcmV?d00001 diff --git a/tests/golden-webkit/screenshot-element-bounding-box.png b/tests/golden-webkit/screenshot-element-bounding-box.png new file mode 100644 index 0000000000000000000000000000000000000000..1f74a3bafbc259b654dfd30bea8ac1eb72be4ccb GIT binary patch literal 445 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1SBWM%0B~AY)RhkE)4%caKYZ?lNlHoi#%N% zLn2z=-mvvPoFLNnQ26Dfg&abQC-YrB$iZkB!L_R+vZYC0K*WW`b%nRFXno_1rOpe3 z=d|#x+|qndPi)SdUCVE3Z2`eBeB+G?2?ilS$|-u1QW zZ;tN#6A4T@eqjs??&js6Kf7VQ>&8vX%*;05eDnLUv(hXJwQEoB)=IQ71nk=-$Jc%_ zP@tvz?_HC~dCy(@qt=SacOO+^(C*%>(_Pp5#}wIbP_Hf%Oig82a8rKC*&jFAr+$pQ VbeIWRsLJYD@<);T3K0RTXQytV)U literal 0 HcmV?d00001 diff --git a/tests/sync/test_locators.py b/tests/sync/test_locators.py new file mode 100644 index 000000000..2ffc8388f --- /dev/null +++ b/tests/sync/test_locators.py @@ -0,0 +1,442 @@ +# Copyright (c) Microsoft Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +import pytest + +from playwright._impl._path_utils import get_file_dirname +from playwright.sync_api import Error, Page +from tests.server import Server + +_dirname = get_file_dirname() +FILE_TO_UPLOAD = _dirname / ".." / "assets/file-to-upload.txt" + + +def test_locators_click_should_work(page: Page, server: Server): + page.goto(server.PREFIX + "/input/button.html") + button = page.locator("button") + button.click() + assert page.evaluate("window['result']") == "Clicked" + + +def test_locators_click_should_work_with_node_removed(page: Page, server: Server): + page.goto(server.PREFIX + "/input/button.html") + page.evaluate("delete window['Node']") + button = page.locator("button") + button.click() + assert page.evaluate("window['result']") == "Clicked" + + +def test_locators_click_should_work_for_text_nodes(page: Page, server: Server): + page.goto(server.PREFIX + "/input/button.html") + page.evaluate( + """() => { + window['double'] = false; + const button = document.querySelector('button'); + button.addEventListener('dblclick', event => { + window['double'] = true; + }); + }""" + ) + button = page.locator("button") + button.dblclick() + assert page.evaluate("double") is True + assert page.evaluate("result") == "Clicked" + + +def test_locators_should_have_repr(page: Page, server: Server): + page.goto(server.PREFIX + "/input/button.html") + button = page.locator("button") + button.click() + assert ( + str(button) + == f" selector='button'>" + ) + + +def test_locators_get_attribute_should_work(page: Page, server: Server): + page.goto(server.PREFIX + "/dom.html") + button = page.locator("#outer") + assert button.get_attribute("name") == "value" + assert button.get_attribute("foo") is None + + +def test_locators_input_value_should_work(page: Page, server: Server): + page.goto(server.PREFIX + "/dom.html") + page.fill("#textarea", "input value") + text_area = page.locator("#textarea") + assert text_area.input_value() == "input value" + + +def test_locators_inner_html_should_work(page: Page, server: Server): + page.goto(server.PREFIX + "/dom.html") + locator = page.locator("#outer") + assert locator.inner_html() == '
Text,\nmore text
' + + +def test_locators_inner_text_should_work(page: Page, server: Server): + page.goto(server.PREFIX + "/dom.html") + locator = page.locator("#inner") + assert locator.inner_text() == "Text, more text" + + +def test_locators_text_content_should_work(page: Page, server: Server): + page.goto(server.PREFIX + "/dom.html") + locator = page.locator("#inner") + assert locator.text_content() == "Text,\nmore text" + + +def test_locators_is_hidden_and_is_visible_should_work(page: Page): + page.set_content("
Hi
") + + div = page.locator("div") + assert div.is_visible() is True + assert div.is_hidden() is False + + span = page.locator("span") + assert span.is_visible() is False + assert span.is_hidden() is True + + +def test_locators_is_enabled_and_is_disabled_should_work(page: Page): + page.set_content( + """ + + +
div
+ """ + ) + + div = page.locator("div") + assert div.is_enabled() is True + assert div.is_disabled() is False + + button1 = page.locator(':text("button1")') + assert button1.is_enabled() is False + assert button1.is_disabled() is True + + button1 = page.locator(':text("button2")') + assert button1.is_enabled() is True + assert button1.is_disabled() is False + + +def test_locators_is_editable_should_work(page: Page): + page.set_content( + """ + + """ + ) + + input1 = page.locator("#input1") + assert input1.is_editable() is False + + input2 = page.locator("#input2") + assert input2.is_editable() is True + + +def test_locators_is_checked_should_work(page: Page): + page.set_content( + """ +
Not a checkbox
+ """ + ) + + element = page.locator("input") + assert element.is_checked() is True + element.evaluate("e => e.checked = false") + assert element.is_checked() is False + + +def test_locators_all_text_contents_should_work(page: Page): + page.set_content( + """ +
A
B
C
+ """ + ) + + element = page.locator("div") + assert element.all_text_contents() == ["A", "B", "C"] + + +def test_locators_all_inner_texts(page: Page): + page.set_content( + """ +
A
B
C
+ """ + ) + + element = page.locator("div") + assert element.all_inner_texts() == ["A", "B", "C"] + + +def test_locators_should_query_existing_element(page: Page, server: Server): + page.goto(server.PREFIX + "/playground.html") + page.set_content( + """
A
""" + ) + html = page.locator("html") + second = html.locator(".second") + inner = second.locator(".inner") + assert page.evaluate("e => e.textContent", inner.element_handle()) == "A" + + +def test_locators_evaluate_handle_should_work(page: Page, server: Server): + page.goto(server.PREFIX + "/dom.html") + outer = page.locator("#outer") + inner = outer.locator("#inner") + check = inner.locator("#check") + text = inner.evaluate_handle("e => e.firstChild") + page.evaluate("1 + 1") + assert ( + str(outer) + == f" selector='#outer'>" + ) + assert ( + str(inner) + == f" selector='#outer >> #inner'>" + ) + assert str(text) == "JSHandle@#text=Text,↵more text" + assert ( + str(check) + == f" selector='#outer >> #inner >> #check'>" + ) + + +def test_locators_should_query_existing_elements(page: Page): + page.set_content("""
A

B
""") + html = page.locator("html") + elements = html.locator("div").element_handles() + assert len(elements) == 2 + result = [] + for element in elements: + result.append(page.evaluate("e => e.textContent", element)) + assert result == ["A", "B"] + + +def test_locators_return_empty_array_for_non_existing_elements(page: Page): + page.set_content("""
A

B
""") + html = page.locator("html") + elements = html.locator("abc").element_handles() + assert len(elements) == 0 + assert elements == [] + + +def test_locators_evaluate_all_should_work(page: Page): + page.set_content( + """
""" + ) + tweet = page.locator(".tweet .like") + content = tweet.evaluate_all("nodes => nodes.map(n => n.innerText)") + assert content == ["100", "10"] + + +def test_locators_evaluate_all_should_work_with_missing_selector(page: Page): + page.set_content("""
not-a-child-div
nodes.length") + assert nodes_length == 0 + + +def test_locators_hover_should_work(page: Page, server: Server): + page.goto(server.PREFIX + "/input/scrollable.html") + button = page.locator("#button-6") + button.hover() + assert page.evaluate("document.querySelector('button:hover').id") == "button-6" + + +def test_locators_fill_should_work(page: Page, server: Server): + page.goto(server.PREFIX + "/input/textarea.html") + button = page.locator("input") + button.fill("some value") + assert page.evaluate("result") == "some value" + + +def test_locators_check_should_work(page: Page): + page.set_content("") + button = page.locator("input") + button.check() + assert page.evaluate("checkbox.checked") is True + + +def test_locators_uncheck_should_work(page: Page): + page.set_content("") + button = page.locator("input") + button.uncheck() + assert page.evaluate("checkbox.checked") is False + + +def test_locators_select_option_should_work(page: Page, server: Server): + page.goto(server.PREFIX + "/input/select.html") + select = page.locator("select") + select.select_option("blue") + assert page.evaluate("result.onInput") == ["blue"] + assert page.evaluate("result.onChange") == ["blue"] + + +def test_locators_focus_should_work(page: Page, server: Server): + page.goto(server.PREFIX + "/input/button.html") + button = page.locator("button") + assert button.evaluate("button => document.activeElement === button") is False + button.focus() + assert button.evaluate("button => document.activeElement === button") is True + + +def test_locators_dispatch_event_should_work(page: Page, server: Server): + page.goto(server.PREFIX + "/input/button.html") + button = page.locator("button") + button.dispatch_event("click") + assert page.evaluate("result") == "Clicked" + + +def test_locators_should_upload_a_file(page: Page, server: Server): + page.goto(server.PREFIX + "/input/fileupload.html") + input = page.locator("input[type=file]") + + file_path = os.path.relpath(FILE_TO_UPLOAD, os.getcwd()) + input.set_input_files(file_path) + assert ( + page.evaluate("e => e.files[0].name", input.element_handle()) + == "file-to-upload.txt" + ) + + +def test_locators_should_press(page: Page): + page.set_content("") + page.locator("input").press("h") + page.eval_on_selector("input", "input => input.value") == "h" + + +def test_locators_should_scroll_into_view(page: Page, server: Server): + page.goto(server.PREFIX + "/offscreenbuttons.html") + for i in range(11): + button = page.locator(f"#btn{i}") + before = button.evaluate( + "button => button.getBoundingClientRect().right - window.innerWidth" + ) + assert before == 10 * i + button.scroll_into_view_if_needed() + after = button.evaluate( + "button => button.getBoundingClientRect().right - window.innerWidth" + ) + assert after <= 0 + page.evaluate("window.scrollTo(0, 0)") + + +def test_locators_should_select_textarea(page: Page, server: Server, browser_name: str): + page.goto(server.PREFIX + "/input/textarea.html") + textarea = page.locator("textarea") + textarea.evaluate("textarea => textarea.value = 'some value'") + textarea.select_text() + if browser_name == "firefox": + assert textarea.evaluate("el => el.selectionStart") == 0 + assert textarea.evaluate("el => el.selectionEnd") == 10 + else: + assert page.evaluate("window.getSelection().toString()") == "some value" + + +def test_locators_should_type(page: Page): + page.set_content("") + page.locator("input").type("hello") + page.eval_on_selector("input", "input => input.value") == "hello" + + +def test_locators_should_screenshot(page: Page, server: Server, assert_to_be_golden): + page.set_viewport_size( + { + "width": 500, + "height": 500, + } + ) + page.goto(server.PREFIX + "/grid.html") + page.evaluate("window.scrollBy(50, 100)") + element = page.locator(".box:nth-of-type(3)") + assert_to_be_golden(element.screenshot(), "screenshot-element-bounding-box.png") + + +def test_locators_should_return_bounding_box(page: Page, server: Server): + page.set_viewport_size( + { + "width": 500, + "height": 500, + } + ) + page.goto(server.PREFIX + "/grid.html") + element = page.locator(".box:nth-of-type(13)") + box = element.bounding_box() + assert box == { + "x": 100, + "y": 50, + "width": 50, + "height": 50, + } + + +def test_locators_should_respect_first_and_last(page: Page): + page.set_content( + """ +
+

A

+

A

A

+

A

A

A

+
""" + ) + assert page.locator("div >> p").count() == 6 + assert page.locator("div").locator("p").count() == 6 + assert page.locator("div").first.locator("p").count() == 1 + assert page.locator("div").last.locator("p").count() == 3 + + +def test_locators_should_respect_nth(page: Page): + page.set_content( + """ +
+

A

+

A

A

+

A

A

A

+
""" + ) + assert page.locator("div >> p").nth(0).count() == 1 + assert page.locator("div").nth(1).locator("p").count() == 2 + assert page.locator("div").nth(2).locator("p").count() == 3 + + +def test_locators_should_throw_on_capture_without_nth(page: Page): + page.set_content( + """ +

A

+ """ + ) + with pytest.raises(Error, match="Can't query n-th element"): + page.locator("*css=div >> p").nth(0).click() + + +def test_locators_should_throw_due_to_strictness(page: Page): + page.set_content( + """ +
A
B
+ """ + ) + with pytest.raises(Error, match="strict mode violation"): + page.locator("div").is_visible() + + +def test_locators_should_throw_due_to_strictness_2(page: Page): + page.set_content( + """ + + """ + ) + with pytest.raises(Error, match="strict mode violation"): + page.locator("option").evaluate("e => {}") From 12ba09d279b2bb8da75a853f9cd04cd67121c619 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 9 Aug 2021 01:23:19 +0200 Subject: [PATCH 135/788] feat(roll): roll Playwright 1.14.0-next-1628399336000 (#839) --- playwright/_impl/_frame.py | 2 ++ playwright/_impl/_page.py | 2 ++ playwright/_impl/_tracing.py | 2 +- playwright/async_api/_generated.py | 28 ++++++++++++++++++++++++---- playwright/sync_api/_generated.py | 28 ++++++++++++++++++++++++---- setup.py | 2 +- tests/async/test_console.py | 19 +++++++++++++------ tests/async/test_jshandle.py | 13 ++++++++----- tests/async/test_worker.py | 7 +++++-- tests/sync/test_console.py | 19 +++++++++++++------ tests/sync/test_sync.py | 10 +++++++--- 11 files changed, 100 insertions(+), 32 deletions(-) diff --git a/playwright/_impl/_frame.py b/playwright/_impl/_frame.py index c79a50b95..a947d9cf1 100644 --- a/playwright/_impl/_frame.py +++ b/playwright/_impl/_frame.py @@ -522,6 +522,8 @@ async def drag_and_drop( self, source: str, target: str, + source_position: Position = None, + target_position: Position = None, force: bool = None, noWaitAfter: bool = None, strict: bool = None, diff --git a/playwright/_impl/_page.py b/playwright/_impl/_page.py index fd0c2aa19..5e0bce5cd 100644 --- a/playwright/_impl/_page.py +++ b/playwright/_impl/_page.py @@ -699,6 +699,8 @@ async def drag_and_drop( self, source: str, target: str, + source_position: Position = None, + target_position: Position = None, force: bool = None, noWaitAfter: bool = None, timeout: float = None, diff --git a/playwright/_impl/_tracing.py b/playwright/_impl/_tracing.py index 45b9bb0da..784770888 100644 --- a/playwright/_impl/_tracing.py +++ b/playwright/_impl/_tracing.py @@ -37,7 +37,6 @@ async def start( await self._channel.send("tracingStart", params) async def stop(self, path: Union[pathlib.Path, str] = None) -> None: - await self._channel.send("tracingStop") if path: artifact = cast( Artifact, from_channel(await self._channel.send("tracingExport")) @@ -46,3 +45,4 @@ async def stop(self, path: Union[pathlib.Path, str] = None) -> None: artifact._is_remote = self._context._browser._is_remote await artifact.save_as(path) await artifact.delete() + await self._channel.send("tracingStop") diff --git a/playwright/async_api/_generated.py b/playwright/async_api/_generated.py index 6d2371ef7..c0372b75d 100644 --- a/playwright/async_api/_generated.py +++ b/playwright/async_api/_generated.py @@ -1905,7 +1905,7 @@ async def select_text( async def input_value(self, *, timeout: float = None) -> str: """ElementHandle.input_value - Returns `input.value` for `` or `") page.eval_on_selector("textarea", "t => t.readOnly = true") input1 = page.query_selector("#input1") + assert input1 assert input1.is_editable() is False assert page.is_editable("#input1") is False input2 = page.query_selector("#input2") + assert input2 assert input2.is_editable() assert page.is_editable("#input2") textarea = page.query_selector("textarea") + assert textarea assert textarea.is_editable() is False assert page.is_editable("textarea") is False -def test_is_checked_should_work(page): +def test_is_checked_should_work(page: Page) -> None: page.set_content('
Not a checkbox
') handle = page.query_selector("input") + assert handle assert handle.is_checked() assert page.is_checked("input") handle.evaluate("input => input.checked = false") @@ -557,9 +637,10 @@ def test_is_checked_should_work(page): assert "Not a checkbox or radio button" in exc_info.value.message -def test_input_value(page: Page, server: Server): +def test_input_value(page: Page, server: Server) -> None: page.goto(server.PREFIX + "/input/textarea.html") element = page.query_selector("input") + assert element element.fill("my-text-content") assert element.input_value() == "my-text-content" @@ -567,9 +648,10 @@ def test_input_value(page: Page, server: Server): assert element.input_value() == "" -def test_set_checked(page: Page): +def test_set_checked(page: Page) -> None: page.set_content("``") input = page.query_selector("input") + assert input input.set_checked(True) assert page.evaluate("checkbox.checked") input.set_checked(False) diff --git a/tests/sync/test_element_handle_wait_for_element_state.py b/tests/sync/test_element_handle_wait_for_element_state.py index c5604c2a7..8f1f2912c 100644 --- a/tests/sync/test_element_handle_wait_for_element_state.py +++ b/tests/sync/test_element_handle_wait_for_element_state.py @@ -14,94 +14,105 @@ import pytest -from playwright.async_api import Error +from playwright.sync_api import Error, Page -def test_should_wait_for_visible(page): +def test_should_wait_for_visible(page: Page) -> None: page.set_content('') div = page.query_selector("div") + assert div page.evaluate('setTimeout(() => div.style.display = "block", 500)') assert div.is_visible() is False div.wait_for_element_state("visible") assert div.is_visible() -def test_should_wait_for_already_visible(page): +def test_should_wait_for_already_visible(page: Page) -> None: page.set_content("
content
") div = page.query_selector("div") + assert div div.wait_for_element_state("visible") -def test_should_timeout_waiting_for_visible(page): +def test_should_timeout_waiting_for_visible(page: Page) -> None: page.set_content('
content
') div = page.query_selector("div") + assert div with pytest.raises(Error) as exc_info: div.wait_for_element_state("visible", timeout=1000) assert "Timeout 1000ms exceeded" in exc_info.value.message -def test_should_throw_waiting_for_visible_when_detached(page): +def test_should_throw_waiting_for_visible_when_detached(page: Page) -> None: page.set_content('') div = page.query_selector("div") + assert div page.evaluate("setTimeout(() => div.remove(), 500)") with pytest.raises(Error) as exc_info: div.wait_for_element_state("visible") assert "Element is not attached to the DOM" in exc_info.value.message -def test_should_wait_for_hidden(page): +def test_should_wait_for_hidden(page: Page) -> None: page.set_content("
content
") div = page.query_selector("div") + assert div page.evaluate('setTimeout(() => div.style.display = "none", 500)') assert div.is_hidden() is False div.wait_for_element_state("hidden") assert div.is_hidden() -def test_should_wait_for_already_hidden(page): +def test_should_wait_for_already_hidden(page: Page) -> None: page.set_content("
") div = page.query_selector("div") + assert div div.wait_for_element_state("hidden") -def test_should_wait_for_hidden_when_detached(page): +def test_should_wait_for_hidden_when_detached(page: Page) -> None: page.set_content("
content
") div = page.query_selector("div") + assert div page.evaluate("setTimeout(() => div.remove(), 500)") div.wait_for_element_state("hidden") assert div.is_hidden() -def test_should_wait_for_enabled_button(page, server): +def test_should_wait_for_enabled_button(page: Page) -> None: page.set_content("") span = page.query_selector("text=Target") + assert span assert span.is_enabled() is False page.evaluate("setTimeout(() => button.disabled = false, 500)") span.wait_for_element_state("enabled") assert span.is_enabled() -def test_should_throw_waiting_for_enabled_when_detached(page): +def test_should_throw_waiting_for_enabled_when_detached(page: Page) -> None: page.set_content("") button = page.query_selector("button") + assert button page.evaluate("setTimeout(() => button.remove(), 500)") with pytest.raises(Error) as exc_info: button.wait_for_element_state("enabled") assert "Element is not attached to the DOM" in exc_info.value.message -def test_should_wait_for_disabled_button(page): +def test_should_wait_for_disabled_button(page: Page) -> None: page.set_content("") span = page.query_selector("text=Target") + assert span assert span.is_disabled() is False page.evaluate("setTimeout(() => button.disabled = true, 500)") span.wait_for_element_state("disabled") assert span.is_disabled() -def test_should_wait_for_editable_input(page, server): +def test_should_wait_for_editable_input(page: Page) -> None: page.set_content("") input = page.query_selector("input") + assert input page.evaluate("setTimeout(() => input.readOnly = false, 500)") assert input.is_editable() is False input.wait_for_element_state("editable") diff --git a/tests/sync/test_har.py b/tests/sync/test_har.py index f511c74c9..52e3166cf 100644 --- a/tests/sync/test_har.py +++ b/tests/sync/test_har.py @@ -15,9 +15,13 @@ import base64 import json import os +from pathlib import Path +from playwright.sync_api import Browser +from tests.server import Server -def test_should_work(browser, server, tmpdir): + +def test_should_work(browser: Browser, server: Server, tmpdir: Path) -> None: path = os.path.join(tmpdir, "log.har") context = browser.new_context(record_har_path=path) page = context.new_page() @@ -28,7 +32,7 @@ def test_should_work(browser, server, tmpdir): assert "log" in data -def test_should_omit_content(browser, server, tmpdir): +def test_should_omit_content(browser: Browser, server: Server, tmpdir: Path) -> None: path = os.path.join(tmpdir, "log.har") context = browser.new_context(record_har_path=path, record_har_omit_content=True) page = context.new_page() @@ -43,7 +47,7 @@ def test_should_omit_content(browser, server, tmpdir): assert "text" not in content1 -def test_should_include_content(browser, server, tmpdir): +def test_should_include_content(browser: Browser, server: Server, tmpdir: Path) -> None: path = os.path.join(tmpdir, "log.har") context = browser.new_context(record_har_path=path) page = context.new_page() diff --git a/tests/sync/test_input.py b/tests/sync/test_input.py index 86a64d106..ff28f6a63 100644 --- a/tests/sync/test_input.py +++ b/tests/sync/test_input.py @@ -13,7 +13,10 @@ # limitations under the License. -def test_expect_file_chooser(page, server): +from playwright.sync_api import Page + + +def test_expect_file_chooser(page: Page) -> None: page.set_content("") with page.expect_file_chooser() as fc_info: page.click('input[type="file"]') diff --git a/tests/sync/test_listeners.py b/tests/sync/test_listeners.py index 49d766114..56a7afb2f 100644 --- a/tests/sync/test_listeners.py +++ b/tests/sync/test_listeners.py @@ -13,10 +13,14 @@ # limitations under the License. -def test_listeners(page, server): +from playwright.sync_api import Page, Response +from tests.server import Server + + +def test_listeners(page: Page, server: Server) -> None: log = [] - def print_response(response): + def print_response(response: Response) -> None: log.append(response) page.on("response", print_response) diff --git a/tests/sync/test_locators.py b/tests/sync/test_locators.py index 3c9cac1c5..07050542e 100644 --- a/tests/sync/test_locators.py +++ b/tests/sync/test_locators.py @@ -13,6 +13,7 @@ # limitations under the License. import os +from typing import Callable import pytest @@ -24,14 +25,16 @@ FILE_TO_UPLOAD = _dirname / ".." / "assets/file-to-upload.txt" -def test_locators_click_should_work(page: Page, server: Server): +def test_locators_click_should_work(page: Page, server: Server) -> None: page.goto(server.PREFIX + "/input/button.html") button = page.locator("button") button.click() assert page.evaluate("window['result']") == "Clicked" -def test_locators_click_should_work_with_node_removed(page: Page, server: Server): +def test_locators_click_should_work_with_node_removed( + page: Page, server: Server +) -> None: page.goto(server.PREFIX + "/input/button.html") page.evaluate("delete window['Node']") button = page.locator("button") @@ -39,7 +42,7 @@ def test_locators_click_should_work_with_node_removed(page: Page, server: Server assert page.evaluate("window['result']") == "Clicked" -def test_locators_click_should_work_for_text_nodes(page: Page, server: Server): +def test_locators_click_should_work_for_text_nodes(page: Page, server: Server) -> None: page.goto(server.PREFIX + "/input/button.html") page.evaluate( """() => { @@ -56,7 +59,7 @@ def test_locators_click_should_work_for_text_nodes(page: Page, server: Server): assert page.evaluate("result") == "Clicked" -def test_locators_should_have_repr(page: Page, server: Server): +def test_locators_should_have_repr(page: Page, server: Server) -> None: page.goto(server.PREFIX + "/input/button.html") button = page.locator("button") button.click() @@ -66,39 +69,39 @@ def test_locators_should_have_repr(page: Page, server: Server): ) -def test_locators_get_attribute_should_work(page: Page, server: Server): +def test_locators_get_attribute_should_work(page: Page, server: Server) -> None: page.goto(server.PREFIX + "/dom.html") button = page.locator("#outer") assert button.get_attribute("name") == "value" assert button.get_attribute("foo") is None -def test_locators_input_value_should_work(page: Page, server: Server): +def test_locators_input_value_should_work(page: Page, server: Server) -> None: page.goto(server.PREFIX + "/dom.html") page.fill("#textarea", "input value") text_area = page.locator("#textarea") assert text_area.input_value() == "input value" -def test_locators_inner_html_should_work(page: Page, server: Server): +def test_locators_inner_html_should_work(page: Page, server: Server) -> None: page.goto(server.PREFIX + "/dom.html") locator = page.locator("#outer") assert locator.inner_html() == '
Text,\nmore text
' -def test_locators_inner_text_should_work(page: Page, server: Server): +def test_locators_inner_text_should_work(page: Page, server: Server) -> None: page.goto(server.PREFIX + "/dom.html") locator = page.locator("#inner") assert locator.inner_text() == "Text, more text" -def test_locators_text_content_should_work(page: Page, server: Server): +def test_locators_text_content_should_work(page: Page, server: Server) -> None: page.goto(server.PREFIX + "/dom.html") locator = page.locator("#inner") assert locator.text_content() == "Text,\nmore text" -def test_locators_is_hidden_and_is_visible_should_work(page: Page): +def test_locators_is_hidden_and_is_visible_should_work(page: Page) -> None: page.set_content("
Hi
") div = page.locator("div") @@ -110,7 +113,7 @@ def test_locators_is_hidden_and_is_visible_should_work(page: Page): assert span.is_hidden() is True -def test_locators_is_enabled_and_is_disabled_should_work(page: Page): +def test_locators_is_enabled_and_is_disabled_should_work(page: Page) -> None: page.set_content( """ @@ -132,7 +135,7 @@ def test_locators_is_enabled_and_is_disabled_should_work(page: Page): assert button1.is_disabled() is False -def test_locators_is_editable_should_work(page: Page): +def test_locators_is_editable_should_work(page: Page) -> None: page.set_content( """ @@ -146,7 +149,7 @@ def test_locators_is_editable_should_work(page: Page): assert input2.is_editable() is True -def test_locators_is_checked_should_work(page: Page): +def test_locators_is_checked_should_work(page: Page) -> None: page.set_content( """
Not a checkbox
@@ -159,7 +162,7 @@ def test_locators_is_checked_should_work(page: Page): assert element.is_checked() is False -def test_locators_all_text_contents_should_work(page: Page): +def test_locators_all_text_contents_should_work(page: Page) -> None: page.set_content( """
A
B
C
@@ -170,7 +173,7 @@ def test_locators_all_text_contents_should_work(page: Page): assert element.all_text_contents() == ["A", "B", "C"] -def test_locators_all_inner_texts(page: Page): +def test_locators_all_inner_texts(page: Page) -> None: page.set_content( """
A
B
C
@@ -181,7 +184,7 @@ def test_locators_all_inner_texts(page: Page): assert element.all_inner_texts() == ["A", "B", "C"] -def test_locators_should_query_existing_element(page: Page, server: Server): +def test_locators_should_query_existing_element(page: Page, server: Server) -> None: page.goto(server.PREFIX + "/playground.html") page.set_content( """
A
""" @@ -192,7 +195,7 @@ def test_locators_should_query_existing_element(page: Page, server: Server): assert page.evaluate("e => e.textContent", inner.element_handle()) == "A" -def test_locators_evaluate_handle_should_work(page: Page, server: Server): +def test_locators_evaluate_handle_should_work(page: Page, server: Server) -> None: page.goto(server.PREFIX + "/dom.html") outer = page.locator("#outer") inner = outer.locator("#inner") @@ -214,7 +217,7 @@ def test_locators_evaluate_handle_should_work(page: Page, server: Server): ) -def test_locators_should_query_existing_elements(page: Page): +def test_locators_should_query_existing_elements(page: Page) -> None: page.set_content("""
A

B
""") html = page.locator("html") elements = html.locator("div").element_handles() @@ -225,7 +228,7 @@ def test_locators_should_query_existing_elements(page: Page): assert result == ["A", "B"] -def test_locators_return_empty_array_for_non_existing_elements(page: Page): +def test_locators_return_empty_array_for_non_existing_elements(page: Page) -> None: page.set_content("""
A

B
""") html = page.locator("html") elements = html.locator("abc").element_handles() @@ -233,7 +236,7 @@ def test_locators_return_empty_array_for_non_existing_elements(page: Page): assert elements == [] -def test_locators_evaluate_all_should_work(page: Page): +def test_locators_evaluate_all_should_work(page: Page) -> None: page.set_content( """
""" ) @@ -242,42 +245,42 @@ def test_locators_evaluate_all_should_work(page: Page): assert content == ["100", "10"] -def test_locators_evaluate_all_should_work_with_missing_selector(page: Page): +def test_locators_evaluate_all_should_work_with_missing_selector(page: Page) -> None: page.set_content("""
not-a-child-div
nodes.length") assert nodes_length == 0 -def test_locators_hover_should_work(page: Page, server: Server): +def test_locators_hover_should_work(page: Page, server: Server) -> None: page.goto(server.PREFIX + "/input/scrollable.html") button = page.locator("#button-6") button.hover() assert page.evaluate("document.querySelector('button:hover').id") == "button-6" -def test_locators_fill_should_work(page: Page, server: Server): +def test_locators_fill_should_work(page: Page, server: Server) -> None: page.goto(server.PREFIX + "/input/textarea.html") button = page.locator("input") button.fill("some value") assert page.evaluate("result") == "some value" -def test_locators_check_should_work(page: Page): +def test_locators_check_should_work(page: Page) -> None: page.set_content("") button = page.locator("input") button.check() assert page.evaluate("checkbox.checked") is True -def test_locators_uncheck_should_work(page: Page): +def test_locators_uncheck_should_work(page: Page) -> None: page.set_content("") button = page.locator("input") button.uncheck() assert page.evaluate("checkbox.checked") is False -def test_locators_select_option_should_work(page: Page, server: Server): +def test_locators_select_option_should_work(page: Page, server: Server) -> None: page.goto(server.PREFIX + "/input/select.html") select = page.locator("select") select.select_option("blue") @@ -285,7 +288,7 @@ def test_locators_select_option_should_work(page: Page, server: Server): assert page.evaluate("result.onChange") == ["blue"] -def test_locators_focus_should_work(page: Page, server: Server): +def test_locators_focus_should_work(page: Page, server: Server) -> None: page.goto(server.PREFIX + "/input/button.html") button = page.locator("button") assert button.evaluate("button => document.activeElement === button") is False @@ -293,14 +296,14 @@ def test_locators_focus_should_work(page: Page, server: Server): assert button.evaluate("button => document.activeElement === button") is True -def test_locators_dispatch_event_should_work(page: Page, server: Server): +def test_locators_dispatch_event_should_work(page: Page, server: Server) -> None: page.goto(server.PREFIX + "/input/button.html") button = page.locator("button") button.dispatch_event("click") assert page.evaluate("result") == "Clicked" -def test_locators_should_upload_a_file(page: Page, server: Server): +def test_locators_should_upload_a_file(page: Page, server: Server) -> None: page.goto(server.PREFIX + "/input/fileupload.html") input = page.locator("input[type=file]") @@ -312,13 +315,13 @@ def test_locators_should_upload_a_file(page: Page, server: Server): ) -def test_locators_should_press(page: Page): +def test_locators_should_press(page: Page) -> None: page.set_content("") page.locator("input").press("h") page.eval_on_selector("input", "input => input.value") == "h" -def test_locators_should_scroll_into_view(page: Page, server: Server): +def test_locators_should_scroll_into_view(page: Page, server: Server) -> None: page.goto(server.PREFIX + "/offscreenbuttons.html") for i in range(11): button = page.locator(f"#btn{i}") @@ -334,7 +337,9 @@ def test_locators_should_scroll_into_view(page: Page, server: Server): page.evaluate("window.scrollTo(0, 0)") -def test_locators_should_select_textarea(page: Page, server: Server, browser_name: str): +def test_locators_should_select_textarea( + page: Page, server: Server, browser_name: str +) -> None: page.goto(server.PREFIX + "/input/textarea.html") textarea = page.locator("textarea") textarea.evaluate("textarea => textarea.value = 'some value'") @@ -346,13 +351,15 @@ def test_locators_should_select_textarea(page: Page, server: Server, browser_nam assert page.evaluate("window.getSelection().toString()") == "some value" -def test_locators_should_type(page: Page): +def test_locators_should_type(page: Page) -> None: page.set_content("") page.locator("input").type("hello") page.eval_on_selector("input", "input => input.value") == "hello" -def test_locators_should_screenshot(page: Page, server: Server, assert_to_be_golden): +def test_locators_should_screenshot( + page: Page, server: Server, assert_to_be_golden: Callable[[bytes, str], None] +) -> None: page.set_viewport_size( { "width": 500, @@ -365,7 +372,7 @@ def test_locators_should_screenshot(page: Page, server: Server, assert_to_be_gol assert_to_be_golden(element.screenshot(), "screenshot-element-bounding-box.png") -def test_locators_should_return_bounding_box(page: Page, server: Server): +def test_locators_should_return_bounding_box(page: Page, server: Server) -> None: page.set_viewport_size( { "width": 500, @@ -383,7 +390,7 @@ def test_locators_should_return_bounding_box(page: Page, server: Server): } -def test_locators_should_respect_first_and_last(page: Page): +def test_locators_should_respect_first_and_last(page: Page) -> None: page.set_content( """
@@ -398,7 +405,7 @@ def test_locators_should_respect_first_and_last(page: Page): assert page.locator("div").last.locator("p").count() == 3 -def test_locators_should_respect_nth(page: Page): +def test_locators_should_respect_nth(page: Page) -> None: page.set_content( """
@@ -412,7 +419,7 @@ def test_locators_should_respect_nth(page: Page): assert page.locator("div").nth(2).locator("p").count() == 3 -def test_locators_should_throw_on_capture_without_nth(page: Page): +def test_locators_should_throw_on_capture_without_nth(page: Page) -> None: page.set_content( """

A

@@ -422,7 +429,7 @@ def test_locators_should_throw_on_capture_without_nth(page: Page): page.locator("*css=div >> p").nth(1).click() -def test_locators_should_throw_due_to_strictness(page: Page): +def test_locators_should_throw_due_to_strictness(page: Page) -> None: page.set_content( """
A
B
@@ -432,7 +439,7 @@ def test_locators_should_throw_due_to_strictness(page: Page): page.locator("div").is_visible() -def test_locators_should_throw_due_to_strictness_2(page: Page): +def test_locators_should_throw_due_to_strictness_2(page: Page) -> None: page.set_content( """ @@ -442,7 +449,7 @@ def test_locators_should_throw_due_to_strictness_2(page: Page): page.locator("option").evaluate("e => {}") -def test_locators_set_checked(page: Page): +def test_locators_set_checked(page: Page) -> None: page.set_content("``") locator = page.locator("input") locator.set_checked(True) diff --git a/tests/sync/test_network.py b/tests/sync/test_network.py index c81263799..f974b0a6c 100644 --- a/tests/sync/test_network.py +++ b/tests/sync/test_network.py @@ -18,8 +18,9 @@ from tests.server import Server -def test_response_server_addr(page: Page, server: Server): +def test_response_server_addr(page: Page, server: Server) -> None: response = page.goto(server.EMPTY_PAGE) + assert response server_addr = response.server_addr() assert server_addr assert server_addr["port"] == server.PORT @@ -27,12 +28,17 @@ def test_response_server_addr(page: Page, server: Server): def test_response_security_details( - browser: Browser, https_server: Server, browser_name, is_win, is_linux -): + browser: Browser, + https_server: Server, + browser_name: str, + is_win: bool, + is_linux: bool, +) -> None: if browser_name == "webkit" and is_linux: pytest.skip("https://github.com/microsoft/playwright/issues/6759") page = browser.new_page(ignore_https_errors=True) response = page.goto(https_server.EMPTY_PAGE) + assert response response.finished() security_details = response.security_details() assert security_details @@ -60,7 +66,10 @@ def test_response_security_details( page.close() -def test_response_security_details_none_without_https(page: Page, server: Server): +def test_response_security_details_none_without_https( + page: Page, server: Server +) -> None: response = page.goto(server.EMPTY_PAGE) + assert response security_details = response.security_details() assert security_details is None diff --git a/tests/sync/test_page.py b/tests/sync/test_page.py index 9e59256c2..372aecda6 100644 --- a/tests/sync/test_page.py +++ b/tests/sync/test_page.py @@ -16,7 +16,7 @@ from tests.server import Server -def test_input_value(page: Page, server: Server): +def test_input_value(page: Page, server: Server) -> None: page.goto(server.PREFIX + "/input/textarea.html") page.fill("input", "my-text-content") @@ -26,7 +26,7 @@ def test_input_value(page: Page, server: Server): assert page.input_value("input") == "" -def test_drag_and_drop_helper_method(page: Page, server: Server): +def test_drag_and_drop_helper_method(page: Page, server: Server) -> None: page.goto(server.PREFIX + "/drag-n-drop.html") page.drag_and_drop("#source", "#target") assert ( @@ -37,7 +37,7 @@ def test_drag_and_drop_helper_method(page: Page, server: Server): ) -def test_should_check_box_using_set_checked(page: Page): +def test_should_check_box_using_set_checked(page: Page) -> None: page.set_content("``") page.set_checked("input", True) assert page.evaluate("checkbox.checked") is True @@ -45,7 +45,7 @@ def test_should_check_box_using_set_checked(page: Page): assert page.evaluate("checkbox.checked") is False -def test_should_set_bodysize_and_headersize(page: Page, server: Server): +def test_should_set_bodysize_and_headersize(page: Page, server: Server) -> None: page.goto(server.EMPTY_PAGE) with page.expect_event("request") as req_info: page.evaluate( @@ -57,7 +57,7 @@ def test_should_set_bodysize_and_headersize(page: Page, server: Server): assert req.sizes["requestHeadersSize"] >= 300 -def test_should_set_bodysize_to_0(page: Page, server: Server): +def test_should_set_bodysize_to_0(page: Page, server: Server) -> None: page.goto(server.EMPTY_PAGE) with page.expect_event("request") as req_info: page.evaluate("() => fetch('./get').then(r => r.text())") diff --git a/tests/sync/test_pdf.py b/tests/sync/test_pdf.py index b93de201d..af9217ea7 100644 --- a/tests/sync/test_pdf.py +++ b/tests/sync/test_pdf.py @@ -21,13 +21,13 @@ @pytest.mark.only_browser("chromium") -def test_should_be_able_to_save_pdf_file(page: Page, server, tmpdir: Path): +def test_should_be_able_to_save_pdf_file(page: Page, tmpdir: Path) -> None: output_file = tmpdir / "foo.png" page.pdf(path=str(output_file)) assert os.path.getsize(output_file) > 0 @pytest.mark.only_browser("chromium") -def test_should_be_able_capture_pdf_without_path(page: Page): +def test_should_be_able_capture_pdf_without_path(page: Page) -> None: buffer = page.pdf() assert buffer diff --git a/tests/sync/test_resource_timing.py b/tests/sync/test_resource_timing.py index 00d8de4ab..a68143a6e 100644 --- a/tests/sync/test_resource_timing.py +++ b/tests/sync/test_resource_timing.py @@ -15,8 +15,11 @@ import pytest from flaky import flaky +from playwright.sync_api import Browser, Page +from tests.server import Server -def test_should_work(page, server, is_webkit, is_mac): + +def test_should_work(page: Page, server: Server, is_webkit: bool, is_mac: bool) -> None: if is_webkit and is_mac: pytest.skip() with page.expect_event("requestfinished") as request_info: @@ -35,7 +38,9 @@ def test_should_work(page, server, is_webkit, is_mac): @flaky -def test_should_work_for_subresource(page, server, is_win, is_mac, is_webkit): +def test_should_work_for_subresource( + page: Page, server: Server, is_win: bool, is_mac: bool, is_webkit: bool +) -> None: if is_webkit and is_mac: pytest.skip() requests = [] @@ -63,7 +68,9 @@ def test_should_work_for_subresource(page, server, is_win, is_mac, is_webkit): assert timing["responseEnd"] < 10000 -def test_should_work_for_ssl(browser, https_server, is_mac, is_webkit): +def test_should_work_for_ssl( + browser: Browser, https_server: Server, is_mac: bool, is_webkit: bool +) -> None: if is_webkit and is_mac: pytest.skip() page = browser.new_page(ignore_https_errors=True) @@ -86,7 +93,7 @@ def test_should_work_for_ssl(browser, https_server, is_mac, is_webkit): @pytest.mark.skip_browser("webkit") # In WebKit, redirects don"t carry the timing info -def test_should_work_for_redirect(page, server): +def test_should_work_for_redirect(page: Page, server: Server) -> None: server.set_redirect("/foo.html", "/empty.html") responses = [] page.on("response", lambda response: responses.append(response)) diff --git a/tests/sync/test_sync.py b/tests/sync/test_sync.py index 175a34b65..ef2304507 100644 --- a/tests/sync/test_sync.py +++ b/tests/sync/test_sync.py @@ -25,26 +25,28 @@ TimeoutError, sync_playwright, ) +from tests.server import Server -def test_sync_query_selector(page): +def test_sync_query_selector(page: Page) -> None: page.set_content( """

Bar

""" ) - assert ( - page.query_selector("#foo").inner_text() - == page.query_selector("h1").inner_text() - ) + e1 = page.query_selector("#foo") + assert e1 + e2 = page.query_selector("h1") + assert e2 + assert e1.inner_text() == e2.inner_text() -def test_page_repr(page): +def test_page_repr(page: Page) -> None: page.goto("https://example.com") assert repr(page) == f"" -def test_frame_repr(page: Page): +def test_frame_repr(page: Page) -> None: page.goto("https://example.com") assert ( repr(page.main_frame) @@ -52,18 +54,18 @@ def test_frame_repr(page: Page): ) -def test_browser_context_repr(context: BrowserContext): +def test_browser_context_repr(context: BrowserContext) -> None: assert repr(context) == f"" -def test_browser_repr(browser: Browser): +def test_browser_repr(browser: Browser) -> None: assert ( repr(browser) == f"" ) -def test_browser_type_repr(browser: Browser): +def test_browser_type_repr(browser: Browser) -> None: browser_type = browser._impl_obj._browser_type assert ( repr(browser_type) @@ -71,8 +73,8 @@ def test_browser_type_repr(browser: Browser): ) -def test_dialog_repr(page: Page, server): - def on_dialog(dialog: Dialog): +def test_dialog_repr(page: Page) -> None: + def on_dialog(dialog: Dialog) -> None: dialog.accept() assert ( repr(dialog) @@ -83,7 +85,7 @@ def on_dialog(dialog: Dialog): page.evaluate("alert('yo')") -def test_console_repr(page: Page, server): +def test_console_repr(page: Page) -> None: messages = [] page.on("console", lambda m: messages.append(m)) page.evaluate('() => console.log("Hello world")') @@ -91,7 +93,7 @@ def test_console_repr(page: Page, server): assert repr(message) == f"" -def test_sync_click(page): +def test_sync_click(page: Page) -> None: page.set_content( """ @@ -101,7 +103,7 @@ def test_sync_click(page): assert page.evaluate("()=>window.clicked") -def test_sync_nested_query_selector(page): +def test_sync_nested_query_selector(page: Page) -> None: page.set_content( """
@@ -114,12 +116,15 @@ def test_sync_nested_query_selector(page): """ ) e1 = page.query_selector("#one") + assert e1 e2 = e1.query_selector(".two") + assert e2 e3 = e2.query_selector("label") + assert e3 assert e3.inner_text() == "MyValue" -def test_sync_handle_multiple_pages(context): +def test_sync_handle_multiple_pages(context: BrowserContext) -> None: page1 = context.new_page() page2 = context.new_page() assert len(context.pages) == 2 @@ -136,27 +141,27 @@ def test_sync_handle_multiple_pages(context): page.content() -def test_sync_wait_for_event(page: Page, server): +def test_sync_wait_for_event(page: Page, server: Server) -> None: with page.expect_event("popup", timeout=10000) as popup: page.evaluate("(url) => window.open(url)", server.EMPTY_PAGE) assert popup.value -def test_sync_wait_for_event_raise(page): +def test_sync_wait_for_event_raise(page: Page) -> None: with pytest.raises(Error): with page.expect_event("popup", timeout=500) as popup: assert False assert popup.value is None -def test_sync_make_existing_page_sync(page): +def test_sync_make_existing_page_sync(page: Page) -> None: page = page assert page.evaluate("() => ({'playwright': true})") == {"playwright": True} page.set_content("

myElement

") page.wait_for_selector("text=myElement") -def test_sync_network_events(page, server): +def test_sync_network_events(page: Page, server: Server) -> None: server.set_route( "/hello-world", lambda request: ( @@ -182,7 +187,7 @@ def test_sync_network_events(page, server): ] -def test_console_should_work(page, browser_name): +def test_console_should_work(page: Page, browser_name: str) -> None: messages = [] page.once("console", lambda m: messages.append(m)) page.evaluate('() => console.log("hello", 5, {foo: "bar"})'), @@ -200,7 +205,7 @@ def test_console_should_work(page, browser_name): assert message.args[2].json_value() == {"foo": "bar"} -def test_sync_download(browser: Browser, server): +def test_sync_download(browser: Browser, server: Server) -> None: server.set_route( "/downloadWithFilename", lambda request: ( @@ -224,7 +229,7 @@ def test_sync_download(browser: Browser, server): page.close() -def test_sync_workers_page_workers(page: Page, server): +def test_sync_workers_page_workers(page: Page, server: Server) -> None: with page.expect_event("worker") as event_worker: page.goto(server.PREFIX + "/worker/worker.html") assert event_worker.value @@ -237,7 +242,7 @@ def test_sync_workers_page_workers(page: Page, server): assert len(page.workers) == 0 -def test_sync_playwright_multiple_times(): +def test_sync_playwright_multiple_times() -> None: with pytest.raises(Error) as exc: with sync_playwright() as pw: assert pw.chromium @@ -247,14 +252,14 @@ def test_sync_playwright_multiple_times(): ) -def test_sync_set_default_timeout(page): +def test_sync_set_default_timeout(page: Page) -> None: page.set_default_timeout(1) with pytest.raises(TimeoutError) as exc: page.wait_for_function("false") assert "Timeout 1ms exceeded." in exc.value.message -def test_close_should_reject_all_promises(context): +def test_close_should_reject_all_promises(context: BrowserContext) -> None: new_page = context.new_page() with pytest.raises(Error) as exc_info: new_page._gather( @@ -264,7 +269,7 @@ def test_close_should_reject_all_promises(context): assert "Target closed" in exc_info.value.message -def test_expect_response_should_work(page: Page, server): +def test_expect_response_should_work(page: Page, server: Server) -> None: with page.expect_response("**/*") as resp: page.goto(server.EMPTY_PAGE) assert resp.value diff --git a/tests/sync/test_tap.py b/tests/sync/test_tap.py index 560d3be96..762698b04 100644 --- a/tests/sync/test_tap.py +++ b/tests/sync/test_tap.py @@ -12,19 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Generator, Optional + import pytest -from playwright.sync_api import ElementHandle, JSHandle +from playwright.sync_api import Browser, BrowserContext, ElementHandle, JSHandle, Page @pytest.fixture -def context(browser): +def context(browser: Browser) -> Generator[BrowserContext, None, None]: context = browser.new_context(has_touch=True) yield context context.close() -def test_should_send_all_of_the_correct_events(page): +def test_should_send_all_of_the_correct_events(page: Page) -> None: page.set_content( """
a
@@ -52,7 +54,7 @@ def test_should_send_all_of_the_correct_events(page): ] -def test_should_not_send_mouse_events_touchstart_is_canceled(page): +def test_should_not_send_mouse_events_touchstart_is_canceled(page: Page) -> None: page.set_content("hello world") page.evaluate( """() => { @@ -74,7 +76,7 @@ def test_should_not_send_mouse_events_touchstart_is_canceled(page): ] -def test_should_not_send_mouse_events_touchend_is_canceled(page): +def test_should_not_send_mouse_events_touchend_is_canceled(page: Page) -> None: page.set_content("hello world") page.evaluate( """() => { @@ -96,7 +98,8 @@ def test_should_not_send_mouse_events_touchend_is_canceled(page): ] -def track_events(target: ElementHandle) -> JSHandle: +def track_events(target: Optional[ElementHandle]) -> JSHandle: + assert target return target.evaluate_handle( """target => { const events = []; diff --git a/tests/sync/test_tracing.py b/tests/sync/test_tracing.py index 5adf6e679..2cce1548d 100644 --- a/tests/sync/test_tracing.py +++ b/tests/sync/test_tracing.py @@ -14,8 +14,13 @@ from pathlib import Path +from playwright.sync_api import Browser +from tests.server import Server -def test_browser_context_output_trace(browser, server, tmp_path): + +def test_browser_context_output_trace( + browser: Browser, server: Server, tmp_path: Path +) -> None: context = browser.new_context() context.tracing.start(screenshots=True, snapshots=True) page = context.new_page() diff --git a/tests/sync/test_video.py b/tests/sync/test_video.py index f354e92f5..1a2d3ba41 100644 --- a/tests/sync/test_video.py +++ b/tests/sync/test_video.py @@ -13,34 +13,47 @@ # limitations under the License. import os +from pathlib import Path +from typing import Dict +from playwright.sync_api import Browser, BrowserType +from tests.server import Server -def test_should_expose_video_path(browser, tmpdir, server): + +def test_should_expose_video_path( + browser: Browser, tmpdir: Path, server: Server +) -> None: page = browser.new_page( record_video_dir=tmpdir, record_video_size={"width": 100, "height": 200} ) page.goto(server.PREFIX + "/grid.html") - path = page.video.path() + video = page.video + assert video + path = video.path() assert repr(page.video) == f"