Working with playwright¶
Turşu does not provide playwright option, but you can works with both playwright and Turşu for testing your web application.
Actually, it has been develop for that!
Installation¶
uv add tursu pytest-playwright
uv run playwright install chromium
uv tursu init --no-dummies -o tests/functionals-playwright/
playwright requires to download its own browsers, refer to the playwright documentation for more options.
The
--no-dummies
does not create dummy scenario.
Create a step¶
from playwright.sync_api import Page, expect
from tursu import given, then, when
@given("anonymous user on {path}")
@when("I visit {path}")
def i_visit(page: Page, http_server: str, path: str):
page.goto(f"{http_server}{path}")
@then('the user sees the text "{text}"')
def assert_text(page: Page, text: str):
loc = page.get_by_text(text)
expect(loc).to_be_visible()
That’s it. You can just use the “Page” fixture provided by pytest-playwright directly.
Create a scenario¶
@wip
Feature: Basic Test
Scenario: Hello world
Given anonymous user on /
Then the user sees the text "Hello, World!"
Run the test¶
uv run pytest --browser chromium -v tests/using_playwright/
Sure this tests will fail, this is BDD :)
Adding a fixture¶
import socket
import threading
import time
from collections.abc import Iterator
from http.server import BaseHTTPRequestHandler, HTTPServer
import pytest
from tursu import tursu_collect_file
tursu_collect_file()
class HelloWorldHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(b"<body>Hello, World!</body>")
def wait_for_socket(host: str, port: int, timeout: int = 5, poll_time: float = 0.1):
"""Wait until the socket is open, or raise an error if the timeout is exceeded."""
for _ in range(timeout * int(1 / poll_time)):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
if sock.connect_ex((host, port)) == 0:
break
time.sleep(poll_time)
else:
raise RuntimeError(f"Server on {host}:{port} did not start in time.")
@pytest.fixture(autouse=True)
def http_server() -> Iterator[str]:
"""Start the service in a thread."""
server_address = ("127.0.0.1", 8888)
httpd = HTTPServer(server_address, HelloWorldHandler)
thread = threading.Thread(target=httpd.serve_forever, daemon=True)
thread.start()
wait_for_socket(*server_address)
yield "http://127.0.0.1:8888"
httpd.shutdown()
thread.join()
Run the test¶
$ uv run pytest --browser chromium -v tests/using_playwright/
================================= test session starts =================================
baseurl: http://localhost:8888
configfile: pyproject.toml
plugins: cov-6.0.0, playwright-0.7.0, base-url-2.1.0
collected 1 item
📄 Document: 01_basic.feature_Basic_Test.py::test_2_Hello_world[chromium]
🥒 Feature: Basic Test
🎬 Scenario: Hello world
✅ Given anonymous user on /
✅ Then the user sees the text "Hello, World!"
PASSED [100%]
================================== 1 passed in 0.94s ==================================
Use the trace artefact¶
While doing continuous integration, always activate the option
--tracing retain-on-failure
from pytest-playright, it will generate a trace.zip
file in a test-results
directory to configured as a build artefact.
Afterwhat you can see all step of the scenario using the show-trace command:
$ uv run playwright show-trace trace.zip
While running locally, you can stop on the first error and then show the trace.
$ rm -rf test-results
$ uv run pytest -sxv --tracing retain-on-failure tests/functionals
$ uv run playwright show-trace test-results/**/trace.zip
Asyncio¶
pytest-playwright does not works well with pytest-asyncio, both plugins conflicts on the asyncio loop, and in that case, the package pytest-playwright-asyncio shoud be used, and step definition has to be coroutine and the scenario decorated manually with a @asyncio tag.
This is the subject of the next chapter.