8000 test: refactor trace viewer tests to use actual trace viewer by mxschmitt · Pull Request #2885 · microsoft/playwright-python · GitHub
[go: up one dir, main page]

Skip to content

test: refactor trace viewer tests to use actual trace viewer #2885

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 11, 2025
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
80 changes: 80 additions & 0 deletions tests/async/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,25 @@
# limitations under the License.

import asyncio
from contextlib import asynccontextmanager
from pathlib import Path
from typing import Any, AsyncGenerator, Awaitable, Callable, Dict, Generator

import pytest

from playwright._impl._driver import compute_driver_executable
from playwright.async_api import (
Browser,
BrowserContext,
BrowserType,
FrameLocator,
Locator,
Page,
Playwright,
Selectors,
async_playwright,
)
from tests.server import HTTPServer

from .utils import Utils
from .utils import utils as utils_object
Expand Down Expand Up @@ -131,3 +137,77 @@ async def page(context: BrowserContext) -> AsyncGenerator[Page, None]:
@pytest.fixture(scope="session")
def selectors(playwright: Playwright) -> Selectors:
return playwright.selectors


class TraceViewerPage:
def __init__(self, page: Page):
self.page = page

@property
def actions_tree(self) -> Locator:
return self.page.get_by_test_id("actions-tree")

@property
def action_titles(self) -> Locator:
return self.page.locator(".action-title")

@property
def stack_frames(self) -> Locator:
return self.page.get_by_test_id("stack-trace-list").locator(".list-view-entry")

async def select_action(self, title: str, ordinal: int = 0) -> None:
await self.page.locator(f'.action-title:has-text("{title}")').nth(
ordinal
).click()

async def select_snapshot(self, name: str) -> None:
await self.page.click(
f'.snapshot-tab .tabbed-pane-tab-label:has-text("{name}")'
)

async def snapshot_frame(
self, action_name: str, ordinal: int = 0, has_subframe: bool = False
) -> FrameLocator:
await self.select_action(action_name, ordinal)
expected_frames = 4 if has_subframe else 3
while len(self.page.frames) < expected_frames:
await self.page.wait_for_event("frameattached")
return self.page.frame_locator("iframe.snapshot-visible[name=snapshot]")

async def show_source_tab(self) -> None:
await self.page.click("text='Source'")

async def expand_action(self, title: str, ordinal: int = 0) -> None:
await self.actions_tree.locator(".tree-view-entry", has_text=title).nth(
ordinal
).locator(".codicon-chevron-right").click()


@pytest.fixture
async def show_trace_viewer(browser: Browser) -> AsyncGenerator[Callable, None]:
"""Fixture that provides a function to show trace viewer for a trace file."""

@asynccontextmanager
async def _show_trace_viewer(
trace_path: Path,
) -> AsyncGenerator[TraceViewerPage, None]:
trace_viewer_path = (
Path(compute_driver_executable()[0]) / "../package/lib/vite/traceViewer"
).resolve()

server = HTTPServer()
server.start(trace_viewer_path)
server.set_route("/trace.zip", lambda request: request.serve_file(trace_path))

page = await browser.new_page()

try:
await page.goto(
f"{server.PREFIX}/index.html?trace={server.PREFIX}/trace.zip"
)
yield TraceViewerPage(page)
finally:
await page.close()
server.stop()

yield _show_trace_viewer
39 changes: 28 additions & 11 deletions tests/async/test_browsertype_connect.py
8000
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@
import os
import re
from pathlib import Path
from typing import Callable
from typing import AsyncContextManager, Callable

import pytest

from playwright.async_api import BrowserType, Error, Playwright, Route
from playwright.async_api import BrowserType, Error, Playwright, Route, expect
from tests.conftest import RemoteServer
from tests.server import Server, TestServerRequest, WebSocketProtocol
from tests.utils import chromium_version_less_than, parse_trace
from tests.utils import chromium_version_less_than

from .conftest import TraceViewerPage


async def test_should_print_custom_ws_close_error(
Expand Down Expand Up @@ -325,6 +327,7 @@ async def test_should_record_trace_with_source(
server: Server,
tmp_path: Path,
browser_type: BrowserType,
show_trace_viewer: Callable[[Path], AsyncContextManager[TraceViewerPage]],
) -> None:
remote = launch_server()
browser = await browser_type.connect(remote.ws_endpoint)
Expand All @@ -341,14 +344,28 @@ async def test_should_record_trace_with_source(
await context.close()
await browser.close()

(resources, events) = parse_trace(path)
current_file_content = Path(__file__).read_bytes()
found_current_file = False
for name, resource in resources.items():
if resource == current_file_content:
found_current_file = True
break
assert found_current_file
async with show_trace_viewer(path) as trace_viewer:
await expect(trace_viewer.action_titles).to_have_text(
[
re.compile("Page.goto"),
re.compile("Page.set_content"),
re.compile("Page.click"),
]
)
await trace_viewer.show_source_tab()
await expect(trace_viewer.stack_frames).to_contain_text(
[
re.compile(r"test_should_record_trace_with_source"),
]
)
await trace_viewer.select_action("Page.set_content")
# Check that the source file is shown
await expect(
trace_viewer.page.locator(".source-tab-file-name")
).to_have_attribute("title", re.compile(r".*test_browsertype_connect\.py"))
await expect(trace_viewer.page.locator(".source-line-running")).to_contain_text(
'page.set_content("<button>Click</button>")'
)


async def test_should_record_trace_with_relative_trace_path(
Expand Down
Loading
Loading
0