Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion DRIVER_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.62.0-alpha-2026-07-16
1.62.0
2 changes: 1 addition & 1 deletion NODE_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
24.18.0
24.18.1
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 -->151.0.7922.19<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Chromium <!-- GEN:chromium-version -->151.0.7922.34<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| WebKit <!-- GEN:webkit-version -->26.5<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->152.0.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->153.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |

## Documentation

Expand Down
2 changes: 1 addition & 1 deletion ROLLING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pre-commit install
pip install -e .
```
* change the driver pin in `DRIVER_VERSION` (the `playwright-core` npm version, e.g. `1.61.0`) and refresh `NODE_VERSION`: `python scripts/update_node_version.py`
* download the new driver: `python -m build --wheel` (fetches `playwright-core` from npm + the matching Node.js binary and assembles the bundle; no source build)
* download the new driver: `python -m build --wheel` (fetches `playwright-core` from npm + the matching Node.js binary and assembles the bundle; no source build). Set `npm_config_registry` to use a different npm registry.
* generate API (needs a nearby `microsoft/playwright` checkout at `v<new>`): `PW_SRC_DIR=../playwright ./scripts/update_api.sh`
* commit changes & send PR
* wait for bots to pass & merge the PR
Expand Down
3 changes: 0 additions & 3 deletions playwright/_impl/_browser_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,6 @@ async def route_from_har(
update: bool = None,
updateContent: Literal["attach", "embed"] = None,
updateMode: HarMode = None,
interceptAPIRequests: bool = None,
) -> None:
if update:
await self._tracing._record_into_har(
Expand All @@ -518,8 +517,6 @@ async def route_from_har(
)
self._har_routers.append(router)
await router.add_context_route(self)
if interceptAPIRequests:
await router.add_api_request_route(self)

async def _update_interception_patterns(self) -> None:
patterns = RouteHandler.prepare_interception_patterns(self._routes)
Expand Down
28 changes: 1 addition & 27 deletions playwright/_impl/_har_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,13 @@
# limitations under the License.
import asyncio
import base64
import re
from typing import TYPE_CHECKING, List, Optional, Tuple, cast
from typing import TYPE_CHECKING, Optional, cast

from playwright._impl._api_structures import HeadersArray
from playwright._impl._helper import (
HarLookupResult,
RouteFromHarNotFoundPolicy,
URLMatch,
escape_regex_flags,
)
from playwright._impl._local_utils import LocalUtils

Expand All @@ -43,7 +41,6 @@ def __init__(
self._har_id: str = har_id
self._not_found_action: RouteFromHarNotFoundPolicy = not_found_action
self._options_url_match: Optional[URLMatch] = url_matcher
self._api_request_registrations: List[Tuple["BrowserContext", str]] = []

@staticmethod
async def create(
Expand Down Expand Up @@ -119,30 +116,7 @@ async def add_page_route(self, page: "Page") -> None:
handler=lambda route, _: asyncio.create_task(self._handle(route)),
)

async def add_api_request_route(self, context: "BrowserContext") -> None:
url_match = self._options_url_match
params: dict = {
"harId": self._har_id,
"notFound": self._not_found_action,
}
if isinstance(url_match, str):
params["urlGlob"] = url_match
elif isinstance(url_match, re.Pattern):
params["urlRegexSource"] = url_match.pattern
params["urlRegexFlags"] = escape_regex_flags(url_match)
result = await context._channel.send_return_as_dict(
"routeAPIRequestsFromHar", None, params
)
self._api_request_registrations.append((context, result["registrationId"]))

def dispose(self) -> None:
for context, registration_id in self._api_request_registrations:
context._channel.send_may_fail(
"unrouteAPIRequestsFromHar",
None,
{"registrationId": registration_id},
)
self._api_request_registrations = []
self._local_utils._channel.send_may_fail(
"harClose", None, {"harId": self._har_id}
)
2 changes: 1 addition & 1 deletion playwright/_impl/_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ async def sizes(self) -> RequestSizes:
@property
def post_data(self) -> Optional[str]:
data = self._fallback_overrides.post_data_buffer
if data:
if data is not None:
return data.decode()
base64_post_data = self._initializer.get("postData")
if base64_post_data is not None:
Expand Down
59 changes: 41 additions & 18 deletions playwright/_impl/_screencast.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,24 +51,47 @@ def __init__(self, page: "Page") -> None:
self._save_path: Optional[Union[str, Path]] = None
self._on_frame: Optional[ScreencastFrameCallback] = None
self._artifact: Optional[Artifact] = None
page._channel.on("screencastFrame", lambda params: self._dispatch_frame(params))

def _dispatch_frame(self, params: dict) -> None:
if not self._on_frame:
return
data = params["data"]
if isinstance(data, str):
data = base64.b64decode(data)
result = self._on_frame(
{
"data": data,
"timestamp": params.get("timestamp", 0),
"viewportWidth": params["viewportWidth"],
"viewportHeight": params["viewportHeight"],
}
)
if hasattr(result, "__await__"):
self._page._loop.create_task(result)
page._channel.on("screencastFrame", self._dispatch_frame)

def _ack_frame(self, frame_id: int) -> None:
try:
self._page._channel.send_no_reply(
"screencastFrameAck", None, {"frameId": frame_id}
)
except (Error, OSError):
pass

async def _await_callback_and_ack(self, result: Any, frame_id: int) -> None:
try:
await result
finally:
self._ack_frame(frame_id)

def _dispatch_frame(self, params: dict) -> Any:
result = None
task = None
try:
if not self._on_frame:
return
data = params["data"]
if isinstance(data, str):
data = base64.b64decode(data)
result = self._on_frame(
{
"data": data,
"timestamp": params.get("timestamp", 0),
"viewportWidth": params["viewportWidth"],
"viewportHeight": params["viewportHeight"],
}
)
finally:
if hasattr(result, "__await__"):
task = self._loop.create_task(
self._await_callback_and_ack(result, params["frameId"])
)
else:
self._ack_frame(params["frameId"])
return task

async def start(
self,
Expand Down
26 changes: 16 additions & 10 deletions playwright/async_api/_generated.py
Original file line number Diff line number Diff line change
Expand Up @@ -7240,7 +7240,7 @@ async def install(
Parameters
----------
time : Union[datetime.datetime, float, str, None]
Time to initialize with, current system time by default.
Time to initialize with, current system time by default. Numeric values are Unix time in seconds.
"""

return mapping.from_maybe_impl(await self._impl_obj.install(time=time))
Expand Down Expand Up @@ -7299,7 +7299,7 @@ async def pause_at(self, time: typing.Union[float, str, datetime.datetime]) -> N
Parameters
----------
time : Union[datetime.datetime, float, str]
Time to pause at.
Time to pause at. Numeric values are Unix time in seconds.
"""

return mapping.from_maybe_impl(await self._impl_obj.pause_at(time=time))
Expand Down Expand Up @@ -7354,7 +7354,7 @@ async def set_fixed_time(
Parameters
----------
time : Union[datetime.datetime, float, str]
Time to be set.
Time to be set. Numeric values are Unix time in seconds.
"""

return mapping.from_maybe_impl(await self._impl_obj.set_fixed_time(time=time))
Expand All @@ -7378,7 +7378,7 @@ async def set_system_time(
Parameters
----------
time : Union[datetime.datetime, float, str]
Time to be set.
Time to be set. Numeric values are Unix time in seconds.
"""

return mapping.from_maybe_impl(await self._impl_obj.set_system_time(time=time))
Expand Down Expand Up @@ -10254,6 +10254,12 @@ async def go_back(

Navigate to the previous page in history.

**NOTE** **Testing Back/Forward Cache (BFCache) is not supported.** By default, Playwright disables the
Back/Forward Cache across all browsers. Even if explicitly enabled, Playwright's internal state relies on
network-level navigation events. Because BFCache restores unfreeze the DOM without firing these events, using
`page.goBack()` or `page.goForward()` to trigger a BFCache restore will result in timeouts and a desynchronized
`Page` state.

Parameters
----------
timeout : Union[float, None]
Expand Down Expand Up @@ -10296,6 +10302,12 @@ async def go_forward(

Navigate to the next page in history.

**NOTE** **Testing Back/Forward Cache (BFCache) is not supported.** By default, Playwright disables the
Back/Forward Cache across all browsers. Even if explicitly enabled, Playwright's internal state relies on
network-level navigation events. Because BFCache restores unfreeze the DOM without firing these events, using
`page.goBack()` or `page.goForward()` to trigger a BFCache restore will result in timeouts and a desynchronized
`Page` state.

Parameters
----------
timeout : Union[float, None]
Expand Down Expand Up @@ -15092,7 +15104,6 @@ async def route_from_har(
update: typing.Optional[bool] = None,
update_content: typing.Optional[Literal["attach", "embed"]] = None,
update_mode: typing.Optional[Literal["full", "minimal"]] = None,
intercept_api_requests: typing.Optional[bool] = None,
) -> None:
"""BrowserContext.route_from_har

Expand Down Expand Up @@ -15126,10 +15137,6 @@ async def route_from_har(
When set to `minimal`, only record information necessary for routing from HAR. This omits sizes, timing, page,
cookies, security and other types of HAR information that are not used when replaying from HAR. Defaults to
`minimal`.
intercept_api_requests : Union[bool, None]
If set to `true`, requests made via `APIRequestContext` (such as `browser_context.request` or
`page.request`) are also served from the HAR file. By default these requests are sent to the network,
matching the behavior prior to v1.62. Defaults to `false` for backward compatibility.
"""

return mapping.from_maybe_impl(
Expand All @@ -15140,7 +15147,6 @@ async def route_from_har(
update=update,
updateContent=update_content,
updateMode=update_mode,
interceptAPIRequests=intercept_api_requests,
)
)

Expand Down
26 changes: 16 additions & 10 deletions playwright/sync_api/_generated.py
Original file line number Diff line number Diff line change
Expand Up @@ -7330,7 +7330,7 @@ def install(
Parameters
----------
time : Union[datetime.datetime, float, str, None]
Time to initialize with, current system time by default.
Time to initialize with, current system time by default. Numeric values are Unix time in seconds.
"""

return mapping.from_maybe_impl(self._sync(self._impl_obj.install(time=time)))
Expand Down Expand Up @@ -7391,7 +7391,7 @@ def pause_at(self, time: typing.Union[float, str, datetime.datetime]) -> None:
Parameters
----------
time : Union[datetime.datetime, float, str]
Time to pause at.
Time to pause at. Numeric values are Unix time in seconds.
"""

return mapping.from_maybe_impl(self._sync(self._impl_obj.pause_at(time=time)))
Expand Down Expand Up @@ -7444,7 +7444,7 @@ def set_fixed_time(self, time: typing.Union[float, str, datetime.datetime]) -> N
Parameters
----------
time : Union[datetime.datetime, float, str]
Time to be set.
Time to be set. Numeric values are Unix time in seconds.
"""

return mapping.from_maybe_impl(
Expand All @@ -7470,7 +7470,7 @@ def set_system_time(
Parameters
----------
time : Union[datetime.datetime, float, str]
Time to be set.
Time to be set. Numeric values are Unix time in seconds.
"""

return mapping.from_maybe_impl(
Expand Down Expand Up @@ -10270,6 +10270,12 @@ def go_back(

Navigate to the previous page in history.

**NOTE** **Testing Back/Forward Cache (BFCache) is not supported.** By default, Playwright disables the
Back/Forward Cache across all browsers. Even if explicitly enabled, Playwright's internal state relies on
network-level navigation events. Because BFCache restores unfreeze the DOM without firing these events, using
`page.goBack()` or `page.goForward()` to trigger a BFCache restore will result in timeouts and a desynchronized
`Page` state.

Parameters
----------
timeout : Union[float, None]
Expand Down Expand Up @@ -10314,6 +10320,12 @@ def go_forward(

Navigate to the next page in history.

**NOTE** **Testing Back/Forward Cache (BFCache) is not supported.** By default, Playwright disables the
Back/Forward Cache across all browsers. Even if explicitly enabled, Playwright's internal state relies on
network-level navigation events. Because BFCache restores unfreeze the DOM without firing these events, using
`page.goBack()` or `page.goForward()` to trigger a BFCache restore will result in timeouts and a desynchronized
`Page` state.

Parameters
----------
timeout : Union[float, None]
Expand Down Expand Up @@ -15093,7 +15105,6 @@ def route_from_har(
update: typing.Optional[bool] = None,
update_content: typing.Optional[Literal["attach", "embed"]] = None,
update_mode: typing.Optional[Literal["full", "minimal"]] = None,
intercept_api_requests: typing.Optional[bool] = None,
) -> None:
"""BrowserContext.route_from_har

Expand Down Expand Up @@ -15127,10 +15138,6 @@ def route_from_har(
When set to `minimal`, only record information necessary for routing from HAR. This omits sizes, timing, page,
cookies, security and other types of HAR information that are not used when replaying from HAR. Defaults to
`minimal`.
intercept_api_requests : Union[bool, None]
If set to `true`, requests made via `APIRequestContext` (such as `browser_context.request` or
`page.request`) are also served from the HAR file. By default these requests are sent to the network,
matching the behavior prior to v1.62. Defaults to `false` for backward compatibility.
"""

return mapping.from_maybe_impl(
Expand All @@ -15142,7 +15149,6 @@ def route_from_har(
update=update,
updateContent=update_content,
updateMode=update_mode,
interceptAPIRequests=intercept_api_requests,
)
)
)
Expand Down
7 changes: 6 additions & 1 deletion scripts/build_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
scripts/build_driver.py # assemble every platform bundle
scripts/build_driver.py <suffix> # assemble a single bundle, e.g. mac-arm64

Set ``npm_config_registry`` to download ``playwright-core`` from an
alternative npm registry.

``setup.py`` invokes the single-suffix form so a wheel build only downloads the
one Node.js binary it needs.
"""
Expand All @@ -53,7 +56,9 @@
REPO_ROOT = Path(__file__).resolve().parent.parent
DRIVER_DIR = REPO_ROOT / "driver"

NPM_REGISTRY = "https://registry.npmjs.org"
NPM_REGISTRY = os.environ.get(
"npm_config_registry", "https://registry.npmjs.org"
).rstrip("/")
NODEJS_DIST = "https://nodejs.org/dist"


Expand Down
6 changes: 2 additions & 4 deletions tests/async/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,8 @@ async def context(


@pytest.fixture
async def page(context: BrowserContext) -> AsyncGenerator[Page, None]:
page = await context.new_page()
yield page
await page.close()
async def page(context: BrowserContext) -> Page:
return await context.new_page()


@pytest.fixture(scope="session")
Expand Down
Loading