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
66 changes: 42 additions & 24 deletions playwright/_impl/_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
from playwright._impl._playwright import Playwright

TimeoutCalculator = Optional[Callable[[Optional[float]], float]]
_PLAYWRIGHT_MODULE_PATH = str(Path(playwright.__file__).parents[0])


class Channel(AsyncIOEventEmitter):
Expand Down Expand Up @@ -404,7 +405,8 @@ def _send_message_to_server(
task = asyncio.current_task(self._loop)
callback.stack_trace = cast(
traceback.StackSummary,
getattr(task, "__pw_stack_trace__", traceback.extract_stack(limit=10)),
getattr(task, "__pw_stack_trace__", None)
or traceback.extract_stack(limit=10),
)
callback.no_reply = no_reply
stack_trace_information = cast(ParsedStackTrace, self._api_zone.get())
Expand Down Expand Up @@ -618,11 +620,11 @@ async def wrap_api_call(
if self._api_zone.get():
return await cb()
task = asyncio.current_task(self._loop)
st: List[inspect.FrameInfo] = getattr(
task, "__pw_stack__", None
) or inspect.stack(0)

parsed_st = _extract_stack_trace_information_from_stack(st, is_internal, title)
parsed_st = _attach_api_call_information(
getattr(task, "__pw_stack__", None) or _capture_stack_trace(),
is_internal,
title,
)
self._api_zone.set(parsed_st)
try:
return await cb()
Expand All @@ -637,10 +639,11 @@ def wrap_api_call_sync(
if self._api_zone.get():
return cb()
task = asyncio.current_task(self._loop)
st: List[inspect.FrameInfo] = getattr(
task, "__pw_stack__", None
) or inspect.stack(0)
parsed_st = _extract_stack_trace_information_from_stack(st, is_internal, title)
parsed_st = _attach_api_call_information(
getattr(task, "__pw_stack__", None) or _capture_stack_trace(),
is_internal,
title,
)
self._api_zone.set(parsed_st)
try:
return cb()
Expand Down Expand Up @@ -671,32 +674,35 @@ class ParsedStackTrace(TypedDict):
title: Optional[str]


def _extract_stack_trace_information_from_stack(
st: List[inspect.FrameInfo], is_internal: bool, title: str = None
) -> ParsedStackTrace:
playwright_module_path = str(Path(playwright.__file__).parents[0])
def _capture_stack_trace() -> ParsedStackTrace:
frame = inspect.currentframe()
# Skip this helper and the caller that only captures the stack.
frame = frame.f_back.f_back if frame and frame.f_back else None
last_internal_api_name = ""
api_name = ""
parsed_frames: List[StackFrame] = []
for frame in st:
while frame:
code = frame.f_code
filename = code.co_filename
# Sync and Async implementations can have event handlers. When these are sync, they
# get evaluated in the context of the event loop, so they contain the stack trace of when
# the message was received. _impl_to_api_mapping is glue between the user-code and internal
# code to translate impl classes to api classes. We want to ignore these frames.
if playwright._impl._impl_to_api_mapping.__file__ == frame.filename:
if playwright._impl._impl_to_api_mapping.__file__ == filename:
frame = frame.f_back
continue
is_playwright_internal = frame.filename.startswith(playwright_module_path)
is_playwright_internal = filename.startswith(_PLAYWRIGHT_MODULE_PATH)

method_name = ""
if "self" in frame[0].f_locals:
method_name = frame[0].f_locals["self"].__class__.__name__ + "."
method_name += frame[0].f_code.co_name
if "self" in frame.f_locals:
method_name = frame.f_locals["self"].__class__.__name__ + "."
method_name += code.co_name

if not is_playwright_internal:
parsed_frames.append(
{
"file": frame.filename,
"line": frame.lineno,
"file": filename,
"line": frame.f_lineno,
"column": 0,
"function": method_name,
}
Expand All @@ -706,16 +712,28 @@ def _extract_stack_trace_information_from_stack(
elif last_internal_api_name:
api_name = last_internal_api_name
last_internal_api_name = ""
frame = frame.f_back
if not api_name:
api_name = last_internal_api_name

return {
"frames": parsed_frames,
"apiName": "" if is_internal else api_name,
"title": title,
"apiName": api_name,
"title": None,
}


def _attach_api_call_information(
stack_trace_information: ParsedStackTrace,
is_internal: bool,
title: Optional[str],
) -> ParsedStackTrace:
if is_internal:
stack_trace_information["apiName"] = ""
stack_trace_information["title"] = title
return stack_trace_information


def _augment_params(
params: Optional[Dict],
timeout_calculator: Optional[Callable[[Optional[float]], float]],
Expand Down
9 changes: 6 additions & 3 deletions playwright/_impl/_disposable.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@
# limitations under the License.

import asyncio
import inspect
import traceback
from typing import Awaitable, Callable, Dict

import greenlet

from playwright._impl._connection import ChannelOwner
from playwright._impl._connection import ChannelOwner, _capture_stack_trace
from playwright._impl._errors import Error, is_target_closed_error


Expand Down Expand Up @@ -78,7 +77,11 @@ def _sync(self, coro: object) -> object:
raise Error("Event loop is closed! Is Playwright already stopped?")
g_self = greenlet.getcurrent()
task = self._loop.create_task(coro) # type: ignore
setattr(task, "__pw_stack__", inspect.stack(0))
setattr(
task,
"__pw_stack__",
_capture_stack_trace(),
)
setattr(task, "__pw_stack_trace__", traceback.extract_stack(limit=10))
task.add_done_callback(lambda _: g_self.switch())
while not task.done():
Expand Down
5 changes: 3 additions & 2 deletions playwright/_impl/_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

import asyncio
import base64
import inspect
import json
import json as json_utils
import mimetypes
Expand Down Expand Up @@ -47,6 +46,7 @@
)
from playwright._impl._connection import (
ChannelOwner,
_capture_stack_trace,
from_channel,
from_nullable_channel,
)
Expand Down Expand Up @@ -552,7 +552,8 @@ async def _race_with_page_close(self, future: Coroutine) -> None:
setattr(
fut,
"__pw_stack__",
getattr(asyncio.current_task(self._loop), "__pw_stack__", inspect.stack(0)),
getattr(asyncio.current_task(self._loop), "__pw_stack__", None)
or _capture_stack_trace(),
)
target_closed_future = self.request._target_closed_future()
await asyncio.wait(
Expand Down
4 changes: 2 additions & 2 deletions playwright/_impl/_sync_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
# limitations under the License.

import asyncio
import inspect
import traceback
from contextlib import AbstractContextManager
from types import TracebackType
Expand All @@ -32,6 +31,7 @@

import greenlet

from playwright._impl._connection import _capture_stack_trace
from playwright._impl._helper import Error
from playwright._impl._impl_to_api_mapping import ImplToApiMapping, ImplWrapper

Expand Down Expand Up @@ -105,7 +105,7 @@ def _sync(

g_self = greenlet.getcurrent()
task: asyncio.tasks.Task[Any] = self._loop.create_task(coro)
setattr(task, "__pw_stack__", inspect.stack(0))
setattr(task, "__pw_stack__", _capture_stack_trace())
setattr(task, "__pw_stack_trace__", traceback.extract_stack(limit=10))

task.add_done_callback(lambda _: g_self.switch())
Expand Down
Loading