|
16 | 16 | import json
|
17 | 17 | from pathlib import Path
|
18 | 18 | from types import SimpleNamespace
|
19 |
| -from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set, Union, cast |
| 19 | +from typing import ( |
| 20 | + TYPE_CHECKING, |
| 21 | + Any, |
| 22 | + Callable, |
| 23 | + Dict, |
| 24 | + List, |
| 25 | + Optional, |
| 26 | + Pattern, |
| 27 | + Set, |
| 28 | + Union, |
| 29 | + cast, |
| 30 | +) |
20 | 31 |
|
21 | 32 | from playwright._impl._api_structures import (
|
22 | 33 | Cookie,
|
|
37 | 48 | from playwright._impl._frame import Frame
|
38 | 49 | from playwright._impl._har_router import HarRouter
|
39 | 50 | from playwright._impl._helper import (
|
| 51 | + HarRecordingMetadata, |
40 | 52 | RouteFromHarNotFoundPolicy,
|
41 | 53 | RouteHandler,
|
42 | 54 | RouteHandlerCallback,
|
|
47 | 59 | async_writefile,
|
48 | 60 | is_safe_close_error,
|
49 | 61 | locals_to_params,
|
| 62 | + prepare_record_har_options, |
50 | 63 | to_impl,
|
51 | 64 | )
|
52 | 65 | from playwright._impl._network import Request, Response, Route, serialize_headers
|
|
56 | 69 |
|
57 | 70 | if TYPE_CHECKING: # pragma: no cover
|
58 | 71 | from playwright._impl._browser import Browser
|
| 72 | + from playwright._impl._browser_type import BrowserType |
59 | 73 |
|
60 | 74 |
|
61 | 75 | class BrowserContext(ChannelOwner):
|
@@ -85,6 +99,7 @@ def __init__(
|
85 | 99 | self._background_pages: Set[Page] = set()
|
86 | 100 | self._service_workers: Set[Worker] = set()
|
87 | 101 | self._tracing = cast(Tracing, from_channel(initializer["tracing"]))
|
| 102 | + self._har_recorders: Dict[str, HarRecordingMetadata] = {} |
88 | 103 | self._request: APIRequestContext = from_channel(
|
89 | 104 | initializer["APIRequestContext"]
|
90 | 105 | )
|
@@ -201,6 +216,14 @@ def pages(self) -> List[Page]:
|
201 | 216 | def browser(self) -> Optional["Browser"]:
|
202 | 217 | return self._browser
|
203 | 218 |
|
| 219 | + def _set_browser_type(self, browser_type: "BrowserType") -> None: |
| 220 | + self._browser_type = browser_type |
| 221 | + if self._options.get("recordHar"): |
| 222 | + self._har_recorders[""] = { |
| 223 | + "path": self._options["recordHar"]["path"], |
| 224 | + "content": self._options["recordHar"].get("content"), |
| 225 | + } |
| 226 | + |
204 | 227 | async def new_page(self) -> Page:
|
205 | 228 | if self._owner_page:
|
206 | 229 | raise Error("Please use browser.new_context()")
|
@@ -294,12 +317,37 @@ async def unroute(
|
294 | 317 | if len(self._routes) == 0:
|
295 | 318 | await self._disable_interception()
|
296 | 319 |
|
| 320 | + async def _record_into_har( |
| 321 | + self, |
| 322 | + har: Union[Path, str], |
| 323 | + page: Optional[Page] = None, |
| 324 | + url: Union[Pattern, str] = None, |
| 325 | + ) -> None: |
| 326 | + params = { |
| 327 | + "options": prepare_record_har_options( |
| 328 | + { |
| 329 | + "recordHarPath": har, |
| 330 | + "recordHarContent": "attach", |
| 331 | + "recordHarMode": "minimal", |
| 332 | + "recordHarUrlFilter": url, |
| 333 | + } |
| 334 | + ) |
| 335 | + } |
| 336 | + if page: |
| 337 | + params["page"] = page._channel |
| 338 | + har_id = await self._channel.send("harStart", params) |
| 339 | + self._har_recorders[har_id] = {"path": str(har), "content": "attach"} |
| 340 | + |
297 | 341 | async def route_from_har(
|
298 | 342 | self,
|
299 | 343 | har: Union[Path, str],
|
300 |
| - url: URLMatch = None, |
| 344 | + url: Union[Pattern, str] = None, |
301 | 345 | not_found: RouteFromHarNotFoundPolicy = None,
|
| 346 | + update: bool = None, |
302 | 347 | ) -> None:
|
| 348 | + if update: |
| 349 | + await self._record_into_har(har=har, page=None, url=url) |
| 350 | + return |
303 | 351 | router = await HarRouter.create(
|
304 | 352 | local_utils=self._connection.local_utils,
|
305 | 353 | file=str(har),
|
@@ -338,13 +386,26 @@ def _on_close(self) -> None:
|
338 | 386 |
|
339 | 387 | async def close(self) -> None:
|
340 | 388 | try:
|
341 |
| - if self._options.get("recordHar"): |
| 389 | + for har_id, params in self._har_recorders.items(): |
342 | 390 | har = cast(
|
343 |
| - Artifact, from_channel(await self._channel.send("harExport")) |
344 |
| - ) |
345 |
| - await har.save_as( |
346 |
| - cast(Dict[str, str], self._options["recordHar"])["path"] |
| 391 | + Artifact, |
| 392 | + from_channel( |
| 393 | + await self._channel.send("harExport", {"harId": har_id}) |
| 394 | + ), |
347 | 395 | )
|
| 396 | + # Server side will compress artifact if content is attach or if file is .zip. |
| 397 | + is_compressed = params.get("content") == "attach" or params[ |
| 398 | + "path" |
| 399 | + ].endswith(".zip") |
| 400 | + need_compressed = params["path"].endswith(".zip") |
| 401 | + if is_compressed and not need_compressed: |
| 402 | + tmp_path = params["path"] + ".tmp" |
| 403 | + await har.save_as(tmp_path) |
| 404 | + await self._connection.local_utils.har_unzip( |
| 405 | + zipFile=tmp_path, harFile=params["path"] |
| 406 | + ) |
| 407 | + else: |
| 408 | + await har.save_as(params["path"]) |
348 | 409 | await har.delete()
|
349 | 410 | await self._channel.send("close")
|
350 | 411 | await self._closed_future
|
|
0 commit comments