Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ Playwright is a Python library to automate [Chromium](https://www.chromium.org/H

| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->139.0.7258.5<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Chromium <!-- GEN:chromium-version -->140.0.7339.16<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| WebKit <!-- GEN:webkit-version -->26.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->140.0.2<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->141.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |

## Documentation

Expand Down
26 changes: 24 additions & 2 deletions playwright/_impl/_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
Optional,
Pattern,
Set,
Tuple,
TypedDict,
TypeVar,
Union,
Expand Down Expand Up @@ -221,14 +222,35 @@ def map_token(original: str, replacement: str) -> str:
processed_parts.append(new_prefix + new_suffix)

relative_path = "/".join(processed_parts)
resolved_url = urljoin(base_url if base_url is not None else "", relative_path)
resolved_url, case_insensitive_part = resolve_base_url(base_url, relative_path)

for replacement, original in token_map.items():
resolved_url = resolved_url.replace(replacement, original, 1)
normalize = case_insensitive_part and replacement in case_insensitive_part
resolved_url = resolved_url.replace(
replacement, original.lower() if normalize else original, 1
)

return ensure_trailing_slash(resolved_url)


def resolve_base_url(
base_url: Optional[str], given_url: str
) -> Tuple[str, Optional[str]]:
try:
resolved = urljoin(base_url if base_url is not None else "", given_url)
parsed = urlparse(resolved)
# Schema and domain are case-insensitive.
hostname_port = (
parsed.hostname or ""
) # can't use parsed.netloc because it includes userinfo (username:password)
if parsed.port:
hostname_port += f":{parsed.port}"
case_insensitive_prefix = f"{parsed.scheme}://{hostname_port}"
return resolved, case_insensitive_prefix
except Exception:
return given_url, None


# In Node.js, new URL('http://localhost') returns 'http://localhost/'.
# To ensure the same url matching behavior, do the same.
def ensure_trailing_slash(url: str) -> str:
Expand Down
27 changes: 25 additions & 2 deletions playwright/async_api/_generated.py
Original file line number Diff line number Diff line change
Expand Up @@ -11487,8 +11487,8 @@ async def main():
async def pause(self) -> None:
"""Page.pause

Pauses script execution. Playwright will stop executing the script and wait for the user to either press 'Resume'
button in the page overlay or to call `playwright.resume()` in the DevTools console.
Pauses script execution. Playwright will stop executing the script and wait for the user to either press the
'Resume' button in the page overlay or to call `playwright.resume()` in the DevTools console.

User can inspect selectors or perform manual steps while paused. Resume will continue running the original script
from the place it was paused.
Expand Down Expand Up @@ -13921,6 +13921,10 @@ async def new_context(
`passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided
with an exact match to the request origin that the certificate is valid for.

Client certificate authentication is only active when at least one client certificate is provided. If you want to
reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that
does not match any of the domains you plan to visit.

**NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
work by replacing `localhost` with `local.playwright`.

Expand Down Expand Up @@ -14152,6 +14156,10 @@ async def new_page(
`passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided
with an exact match to the request origin that the certificate is valid for.

Client certificate authentication is only active when at least one client certificate is provided. If you want to
reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that
does not match any of the domains you plan to visit.

**NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
work by replacing `localhost` with `local.playwright`.

Expand Down Expand Up @@ -14559,6 +14567,13 @@ async def launch_persistent_context(
**parent** directory of the "Profile Path" seen at `chrome://version`.

Note that browsers do not allow launching multiple instances with the same User Data Directory.

**NOTE** Chromium/Chrome: Due to recent Chrome policy changes, automating the default Chrome user profile is not
supported. Pointing `userDataDir` to Chrome's main "User Data" directory (the profile used for your regular
browsing) may result in pages not loading or the browser exiting. Create and use a separate directory (for example,
an empty folder) as your automation profile instead. See https://developer.chrome.com/blog/remote-debugging-port
for details.

channel : Union[str, None]
Browser distribution channel.

Expand Down Expand Up @@ -14733,6 +14748,10 @@ async def launch_persistent_context(
`passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided
with an exact match to the request origin that the certificate is valid for.

Client certificate authentication is only active when at least one client certificate is provided. If you want to
reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that
does not match any of the domains you plan to visit.

**NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
work by replacing `localhost` with `local.playwright`.

Expand Down Expand Up @@ -18801,6 +18820,10 @@ async def new_context(
`passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided
with an exact match to the request origin that the certificate is valid for.

Client certificate authentication is only active when at least one client certificate is provided. If you want to
reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that
does not match any of the domains you plan to visit.

**NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
work by replacing `localhost` with `local.playwright`.

Expand Down
35 changes: 29 additions & 6 deletions playwright/sync_api/_generated.py
Original file line number Diff line number Diff line change
Expand Up @@ -11569,8 +11569,8 @@ def run(playwright: Playwright):
def pause(self) -> None:
"""Page.pause

Pauses script execution. Playwright will stop executing the script and wait for the user to either press 'Resume'
button in the page overlay or to call `playwright.resume()` in the DevTools console.
Pauses script execution. Playwright will stop executing the script and wait for the user to either press the
'Resume' button in the page overlay or to call `playwright.resume()` in the DevTools console.

User can inspect selectors or perform manual steps while paused. Resume will continue running the original script
from the place it was paused.
Expand Down Expand Up @@ -12255,7 +12255,7 @@ def add_locator_handler(

```py
# Setup the handler.
def handler():
async def handler():
await page.get_by_role(\"button\", name=\"No thanks\").click()
await page.add_locator_handler(page.get_by_text(\"Sign up to the newsletter\"), handler)

Expand All @@ -12268,7 +12268,7 @@ def handler():

```py
# Setup the handler.
def handler():
async def handler():
await page.get_by_role(\"button\", name=\"Remind me later\").click()
await page.add_locator_handler(page.get_by_text(\"Confirm your security details\"), handler)

Expand All @@ -12283,7 +12283,7 @@ def handler():

```py
# Setup the handler.
def handler():
async def handler():
await page.evaluate(\"window.removeObstructionsForTestIfNeeded()\")
await page.add_locator_handler(page.locator(\"body\"), handler, no_wait_after=True)

Expand All @@ -12296,7 +12296,7 @@ def handler():
invocations by setting `times`:

```py
def handler(locator):
async def handler(locator):
await locator.click()
await page.add_locator_handler(page.get_by_label(\"Close\"), handler, times=1)
```
Expand Down Expand Up @@ -13952,6 +13952,10 @@ def new_context(
`passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided
with an exact match to the request origin that the certificate is valid for.

Client certificate authentication is only active when at least one client certificate is provided. If you want to
reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that
does not match any of the domains you plan to visit.

**NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
work by replacing `localhost` with `local.playwright`.

Expand Down Expand Up @@ -14185,6 +14189,10 @@ def new_page(
`passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided
with an exact match to the request origin that the certificate is valid for.

Client certificate authentication is only active when at least one client certificate is provided. If you want to
reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that
does not match any of the domains you plan to visit.

**NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
work by replacing `localhost` with `local.playwright`.

Expand Down Expand Up @@ -14598,6 +14606,13 @@ def launch_persistent_context(
**parent** directory of the "Profile Path" seen at `chrome://version`.

Note that browsers do not allow launching multiple instances with the same User Data Directory.

**NOTE** Chromium/Chrome: Due to recent Chrome policy changes, automating the default Chrome user profile is not
supported. Pointing `userDataDir` to Chrome's main "User Data" directory (the profile used for your regular
browsing) may result in pages not loading or the browser exiting. Create and use a separate directory (for example,
an empty folder) as your automation profile instead. See https://developer.chrome.com/blog/remote-debugging-port
for details.

channel : Union[str, None]
Browser distribution channel.

Expand Down Expand Up @@ -14772,6 +14787,10 @@ def launch_persistent_context(
`passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided
with an exact match to the request origin that the certificate is valid for.

Client certificate authentication is only active when at least one client certificate is provided. If you want to
reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that
does not match any of the domains you plan to visit.

**NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
work by replacing `localhost` with `local.playwright`.

Expand Down Expand Up @@ -18922,6 +18941,10 @@ def new_context(
`passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided
with an exact match to the request origin that the certificate is valid for.

Client certificate authentication is only active when at least one client certificate is provided. If you want to
reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that
does not match any of the domains you plan to visit.

**NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
work by replacing `localhost` with `local.playwright`.

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import zipfile
from typing import Dict

driver_version = "1.54.1"
driver_version = "1.55.0-alpha-1755516433000"

base_wheel_bundles = [
{
Expand Down
2 changes: 0 additions & 2 deletions tests/assets/simple-extension/content-script.js

This file was deleted.

2 changes: 0 additions & 2 deletions tests/assets/simple-extension/index.js

This file was deleted.

14 changes: 0 additions & 14 deletions tests/assets/simple-extension/manifest.json

This file was deleted.

38 changes: 1 addition & 37 deletions tests/async/test_launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import asyncio
import os
from pathlib import Path
from typing import Dict, Optional
from typing import Dict

import pytest

Expand Down Expand Up @@ -107,39 +107,3 @@ async def test_browser_close_should_be_callable_twice(
browser.close(),
)
await browser.close()


@pytest.mark.only_browser("chromium")
async def test_browser_launch_should_return_background_pages(
browser_type: BrowserType,
tmp_path: Path,
browser_channel: Optional[str],
assetdir: Path,
launch_arguments: Dict,
) -> None:
if browser_channel:
pytest.skip()

extension_path = str(assetdir / "simple-extension")
context = await browser_type.launch_persistent_context(
str(tmp_path),
**{
**launch_arguments,
"headless": False,
"args": [
f"--disable-extensions-except={extension_path}",
f"--load-extension={extension_path}",
],
},
)
background_page = None
if len(context.background_pages):
background_page = context.background_pages[0]
else:
background_page = await context.wait_for_event("backgroundpage")
assert background_page
assert background_page in context.background_pages
assert background_page not in context.pages
await context.close()
assert len(context.background_pages) == 0
assert len(context.pages) == 0
17 changes: 17 additions & 0 deletions tests/async/test_page_route.py
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,23 @@ def glob_to_regex(pattern: str) -> re.Pattern:
"http://playwright.dev/foo/", "http://playwright.dev/foo/bar?x=y", "./bar?x=y"
)

# Case insensitive matching
assert url_matches(
None, "https://playwright.dev/fooBAR", "HtTpS://pLaYwRiGhT.dEv/fooBAR"
)
assert url_matches(
"http://ignored",
"https://playwright.dev/fooBAR",
"HtTpS://pLaYwRiGhT.dEv/fooBAR",
)
# Path and search query are case-sensitive
assert not url_matches(
None, "https://playwright.dev/foobar", "https://playwright.dev/fooBAR"
)
assert not url_matches(
None, "https://playwright.dev/foobar?a=b", "https://playwright.dev/foobar?A=B"
)

# This is not supported, we treat ? as a query separator.
assert not url_matches(
None,
Expand Down
1 change: 0 additions & 1 deletion tests/async/test_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,5 @@ async def test_should_show_tracing_group_in_action_list(
re.compile(r"inner group 1"),
re.compile(r"Click"),
re.compile(r"inner group 2"),
re.compile(r"Is visible"),
]
)
38 changes: 1 addition & 37 deletions tests/sync/test_launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import os
from pathlib import Path
from typing import Dict, Optional
from typing import Dict

import pytest

Expand Down Expand Up @@ -88,39 +88,3 @@ def test_browser_close_should_be_callable_twice(
browser = browser_type.launch(**launch_arguments)
browser.close()
browser.close()


@pytest.mark.only_browser("chromium")
def test_browser_launch_should_return_background_pages(
browser_type: BrowserType,
tmp_path: Path,
browser_channel: Optional[str],
assetdir: Path,
launch_arguments: Dict,
) -> None:
if browser_channel:
pytest.skip()

extension_path = str(assetdir / "simple-extension")
context = browser_type.launch_persistent_context(
str(tmp_path),
**{
**launch_arguments,
"headless": False,
"args": [
f"--disable-extensions-except={extension_path}",
f"--load-extension={extension_path}",
],
},
)
background_page = None
if len(context.background_pages):
background_page = context.background_pages[0]
else:
background_page = context.wait_for_event("backgroundpage")
assert background_page
assert background_page in context.background_pages
assert background_page not in context.pages
context.close()
assert len(context.background_pages) == 0
assert len(context.pages) == 0
1 change: 0 additions & 1 deletion tests/sync/test_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,5 @@ def test_should_show_tracing_group_in_action_list(
re.compile(r"inner group 1"),
re.compile(r"Click"),
re.compile(r"inner group 2"),
re.compile(r"Is visible"),
]
)
Loading