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
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 -->111.0.5563.19<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Chromium <!-- GEN:chromium-version -->112.0.5615.20<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| WebKit <!-- GEN:webkit-version -->16.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->109.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->110.0.1<!-- GEN:stop --> | ✅ | ✅ | ✅ |

## Documentation

Expand Down
16 changes: 12 additions & 4 deletions playwright/_impl/_browser_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
from playwright._impl._frame import Frame
from playwright._impl._har_router import HarRouter
from playwright._impl._helper import (
HarContentPolicy,
HarMode,
HarRecordingMetadata,
RouteFromHarNotFoundPolicy,
RouteHandler,
Expand Down Expand Up @@ -326,31 +328,37 @@ async def _record_into_har(
har: Union[Path, str],
page: Optional[Page] = None,
url: Union[Pattern[str], str] = None,
content: HarContentPolicy = None,
mode: HarMode = None,
) -> None:
params: Dict[str, Any] = {
"options": prepare_record_har_options(
{
"recordHarPath": har,
"recordHarContent": "attach",
"recordHarMode": "minimal",
"recordHarContent": content or "attach",
"recordHarMode": mode or "minimal",
"recordHarUrlFilter": url,
}
)
}
if page:
params["page"] = page._channel
har_id = await self._channel.send("harStart", params)
self._har_recorders[har_id] = {"path": str(har), "content": "attach"}
self._har_recorders[har_id] = {"path": str(har), "content": content or "attach"}

async def route_from_har(
self,
har: Union[Path, str],
url: Union[Pattern[str], str] = None,
not_found: RouteFromHarNotFoundPolicy = None,
update: bool = None,
content: HarContentPolicy = None,
mode: HarMode = None,
) -> None:
if update:
await self._record_into_har(har=har, page=None, url=url)
await self._record_into_har(
har=har, page=None, url=url, content=content, mode=mode
)
return
router = await HarRouter.create(
local_utils=self._connection.local_utils,
Expand Down
87 changes: 61 additions & 26 deletions playwright/_impl/_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import asyncio
import contextvars
import datetime
import inspect
import sys
import traceback
Expand All @@ -33,6 +34,12 @@
from playwright._impl._playwright import Playwright


if sys.version_info >= (3, 8): # pragma: no cover
from typing import TypedDict
else: # pragma: no cover
from typing_extensions import TypedDict


class Channel(AsyncIOEventEmitter):
def __init__(self, connection: "Connection", guid: str) -> None:
super().__init__()
Expand Down Expand Up @@ -220,10 +227,11 @@ def __init__(
self._error: Optional[BaseException] = None
self.is_remote = False
self._init_task: Optional[asyncio.Task] = None
self._api_zone: contextvars.ContextVar[Optional[Dict]] = contextvars.ContextVar(
"ApiZone", default=None
)
self._api_zone: contextvars.ContextVar[
Optional[ParsedStackTrace]
] = contextvars.ContextVar("ApiZone", default=None)
self._local_utils: Optional["LocalUtils"] = local_utils
self._stack_collector: List[List[Dict[str, Any]]] = []

@property
def local_utils(self) -> "LocalUtils":
Expand Down Expand Up @@ -271,6 +279,13 @@ def call_on_object_with_known_name(
) -> None:
self._waiting_for_object[guid] = callback

def start_collecting_call_metadata(self, collector: Any) -> None:
if collector not in self._stack_collector:
self._stack_collector.append(collector)

def stop_collecting_call_metadata(self, collector: Any) -> None:
self._stack_collector.remove(collector)

def _send_message_to_server(
self, guid: str, method: str, params: Dict
) -> ProtocolCallback:
Expand All @@ -283,12 +298,30 @@ def _send_message_to_server(
getattr(task, "__pw_stack_trace__", traceback.extract_stack()),
)
self._callbacks[id] = callback
stack_trace_information = cast(ParsedStackTrace, self._api_zone.get())
for collector in self._stack_collector:
collector.append({"stack": stack_trace_information["frames"], "id": id})
frames = stack_trace_information.get("frames", [])
location = (
{
"file": frames[0]["file"],
"line": frames[0]["line"],
"column": frames[0]["column"],
}
if len(frames) > 0
else None
)
message = {
"id": id,
"guid": guid,
"method": method,
"params": self._replace_channels_with_guids(params),
"metadata": self._api_zone.get(),
"metadata": {
"wallTime": int(datetime.datetime.now().timestamp() * 1000),
"apiName": stack_trace_information["apiName"],
"location": location,
"internal": not stack_trace_information["apiName"],
},
}
self._transport.send(message)
self._callbacks[id] = callback
Expand Down Expand Up @@ -412,9 +445,7 @@ async def wrap_api_call(
return await cb()
task = asyncio.current_task(self._loop)
st: List[inspect.FrameInfo] = getattr(task, "__pw_stack__", inspect.stack())
metadata = _extract_metadata_from_stack(st, is_internal)
if metadata:
self._api_zone.set(metadata)
self._api_zone.set(_extract_stack_trace_information_from_stack(st, is_internal))
try:
return await cb()
finally:
Expand All @@ -427,9 +458,7 @@ def wrap_api_call_sync(
return cb()
task = asyncio.current_task(self._loop)
st: List[inspect.FrameInfo] = getattr(task, "__pw_stack__", inspect.stack())
metadata = _extract_metadata_from_stack(st, is_internal)
if metadata:
self._api_zone.set(metadata)
self._api_zone.set(_extract_stack_trace_information_from_stack(st, is_internal))
try:
return cb()
finally:
Expand All @@ -444,19 +473,25 @@ def from_nullable_channel(channel: Optional[Channel]) -> Optional[Any]:
return channel._object if channel else None


def _extract_metadata_from_stack(
class StackFrame(TypedDict):
file: str
line: int
column: int
function: Optional[str]


class ParsedStackTrace(TypedDict):
frames: List[StackFrame]
apiName: Optional[str]


def _extract_stack_trace_information_from_stack(
st: List[inspect.FrameInfo], is_internal: bool
) -> Optional[Dict]:
if is_internal:
return {
"apiName": "",
"stack": [],
"internal": True,
}
) -> Optional[ParsedStackTrace]:
playwright_module_path = str(Path(playwright.__file__).parents[0])
last_internal_api_name = ""
api_name = ""
stack: List[Dict] = []
parsed_frames: List[StackFrame] = []
for frame in st:
is_playwright_internal = frame.filename.startswith(playwright_module_path)

Expand All @@ -466,10 +501,11 @@ def _extract_metadata_from_stack(
method_name += frame[0].f_code.co_name

if not is_playwright_internal:
stack.append(
parsed_frames.append(
{
"file": frame.filename,
"line": frame.lineno,
"column": 0,
"function": method_name,
}
)
Expand All @@ -480,9 +516,8 @@ def _extract_metadata_from_stack(
last_internal_api_name = ""
if not api_name:
api_name = last_internal_api_name
if api_name:
return {
"apiName": api_name,
"stack": stack,
}
return None

return {
"frames": parsed_frames,
"apiName": "" if is_internal else api_name,
}
8 changes: 4 additions & 4 deletions playwright/_impl/_local_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
# limitations under the License.

import base64
from typing import Dict, List, Optional, cast
from typing import Dict, Optional, cast

from playwright._impl._api_structures import HeadersArray, NameValue
from playwright._impl._api_structures import HeadersArray
from playwright._impl._connection import ChannelOwner
from playwright._impl._helper import HarLookupResult, locals_to_params

Expand All @@ -26,8 +26,8 @@ def __init__(
) -> None:
super().__init__(parent, type, guid, initializer)

async def zip(self, zip_file: str, entries: List[NameValue]) -> None:
await self._channel.send("zip", {"zipFile": zip_file, "entries": entries})
async def zip(self, params: Dict) -> None:
await self._channel.send("zip", params)

async def har_open(self, file: str) -> None:
params = locals_to_params(locals())
Expand Down
29 changes: 25 additions & 4 deletions playwright/_impl/_locator.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
MouseButton,
locals_to_params,
monotonic_time,
to_impl,
)
from playwright._impl._js_handle import Serializable, parse_value, serialize_argument
from playwright._impl._str_utils import (
Expand Down Expand Up @@ -207,13 +208,23 @@ async def clear(

def locator(
self,
selector: str,
selector_or_locator: Union[str, "Locator"],
has_text: Union[str, Pattern[str]] = None,
has: "Locator" = None,
) -> "Locator":
if isinstance(selector_or_locator, str):
return Locator(
self._frame,
f"{self._selector} >> {selector_or_locator}",
has_text=has_text,
has=has,
)
selector_or_locator = to_impl(selector_or_locator)
if selector_or_locator._frame != self._frame:
raise Error("Locators must belong to the same frame.")
return Locator(
self._frame,
f"{self._selector} >> {selector}",
f"{self._selector} >> {selector_or_locator._selector}",
has_text=has_text,
has=has,
)
Expand Down Expand Up @@ -663,13 +674,23 @@ def __init__(self, frame: "Frame", frame_selector: str) -> None:

def locator(
self,
selector: str,
selector_or_locator: Union["Locator", str],
has_text: Union[str, Pattern[str]] = None,
has: "Locator" = None,
) -> Locator:
if isinstance(selector_or_locator, str):
return Locator(
self._frame,
f"{self._frame_selector} >> internal:control=enter-frame >> {selector_or_locator}",
has_text=has_text,
has=has,
)
selector_or_locator = to_impl(selector_or_locator)
if selector_or_locator._frame != self._frame:
raise ValueError("Locators must belong to the same frame.")
return Locator(
self._frame,
f"{self._frame_selector} >> internal:control=enter-frame >> {selector}",
f"{self._frame_selector} >> internal:control=enter-frame >> {selector_or_locator._selector}",
has_text=has_text,
has=has,
)
Expand Down
8 changes: 7 additions & 1 deletion playwright/_impl/_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
ColorScheme,
DocumentLoadState,
ForcedColors,
HarContentPolicy,
HarMode,
KeyboardModifier,
MouseButton,
ReducedMotion,
Expand Down Expand Up @@ -617,9 +619,13 @@ async def route_from_har(
url: Union[Pattern[str], str] = None,
not_found: RouteFromHarNotFoundPolicy = None,
update: bool = None,
content: HarContentPolicy = None,
mode: HarMode = None,
) -> None:
if update:
await self._browser_context._record_into_har(har=har, page=self, url=url)
await self._browser_context._record_into_har(
har=har, page=self, url=url, content=content, mode=mode
)
return
router = await HarRouter.create(
local_utils=self._connection.local_utils,
Expand Down
Loading