Playwright (Python)
Overview
The playwright-python plugin generates Python code using the Playwright library for browser automation and testing.
Within the Browser (plugin: playwright-python, type: browser) go to "https://example.com"Types
browser
The main browser context. Launches a Chromium browser and creates a new page.
Type Modifiers:
| Modifier | Type | Description |
|---|---|---|
url | quoted | Base URL for the browser context (use go to "/" to navigate) |
headless | quoted | Run browser headlessly ('true') or with visible UI ('false') |
Patterns:
| Pattern | Description |
|---|---|
go to <url> | Navigate to URL |
see url <url> | Assert current URL matches exactly |
see url contains <url> | Assert current URL contains string |
click <target> | Click element by selector |
type <text> into <target> | Type text into input element |
press <key> | Press a keyboard key (e.g., Enter, Tab, Escape) |
see a/an/any/no <target> | Assert element visibility or count |
wait for <target> | Wait for element to appear |
expect failure | Expect code block to raise an exception |
Pattern Modifiers:
| Pattern | Modifier | Type | Description |
|---|---|---|---|
go to | timeout | quoted | Navigation timeout |
see url | timeout | quoted | Assertion timeout |
see url contains | timeout | quoted | Assertion timeout |
click | timeout | quoted | Click timeout |
click | role | quoted | ARIA role (uses get_by_role) |
click | name | quoted | Accessible name (combines with role or target) |
click | id | quoted | Element ID selector |
click | class | quoted | CSS class(es), space-separated (e.g., 'btn primary' -> .btn.primary) |
click | testid | quoted | data-testid attribute |
click | text | quoted | Text content filter |
click | type | quoted | HTML type attribute (e.g., 'checkbox', 'submit') |
type | timeout | quoted | Type timeout |
type | role | quoted | ARIA role (uses get_by_role) |
type | name | quoted | Accessible name |
type | id | quoted | Element ID selector |
type | class | quoted | CSS class |
type | testid | quoted | data-testid attribute |
press | timeout | quoted | Key press timeout |
see | text | quoted | Text content to match (substring) |
see | exact | quoted | Set to 'true' for exact text match |
see | role | quoted | ARIA role |
see | name | quoted | Accessible name |
see | id | quoted | Element ID selector |
see | class | quoted | CSS class(es), space-separated (e.g., 'btn primary' -> .btn.primary) |
see | testid | quoted | data-testid attribute |
see | state | quoted | Assertion state: attached or hidden (see below) |
wait for | timeout | quoted | Wait timeout |
wait for | state | quoted | Wait state (visible, hidden, attached, detached) |
wait for | id | quoted | Element ID selector |
wait for | class | quoted | CSS class(es), space-separated |
wait for | testid | quoted | data-testid attribute |
wait for | text | quoted | Text content to match |
wait for | role | quoted | ARIA role |
wait for | name | quoted | Accessible name (combines with role) |
expect failure | error | quoted | Expected exception type |
expect failure | capture | quoted | Variable name to capture exception |
element
A scoped element context for targeting nested elements. Inherits patterns from browser type.
Type Modifiers:
| Modifier | Type | Description |
|---|---|---|
id | quoted | Select by element ID (#id) |
class | quoted | CSS class(es), space-separated (e.g., 'btn primary' -> .btn.primary) |
testid | quoted | Select by data-testid attribute |
label | quoted | Select by accessible label |
role | quoted | Select by ARIA role (uses get_by_role() for implicit role matching) |
name | quoted | Accessible name for role-based selection (combines with role to use get_by_role) |
Within the Login Form (type: element, testid: 'login-form') Define email-input (label: 'Email') type "user@example.com" into email-inputPatterns:
The element type supports the same patterns as browser: click, type, press, see, wait for, and expect failure.
failure_block
A passthrough block used inside expect failure to contain actions that should raise an exception.
Modifier Composition
When multiple modifiers are combined on an action, the generated selector combines them appropriately. Understanding how modifiers compose helps write precise selectors.
Selector Priority
Modifiers are evaluated in this order of priority:
- Role-based selectors (
role+name) - usesget_by_role() - ID selectors (
id) - uses#idCSS selector - Test ID selectors (
testid) - uses[data-testid='...'] - Class + Text (
class+text) - useslocator("tag.class", has_text="...") - Class only (
class) - usestag.classCSS selector - Text only (
text) - useslocator("tag", has_text="...") - Tag only - uses bare
locator("tag")
Combining class and text
When both class: and text: are specified, they are combined into a single locator:
# Generates: locator("li.completed", has_text="Buy milk")see a li (class: 'completed', text: 'Buy milk')
# Multiple classes (space-separated): generates locator("li.todo-item.completed", has_text="Buy milk")see a li (class: 'todo-item completed', text: 'Buy milk')
# With exact matching: locator("li.completed").filter(has_text="Buy milk")see a li (class: 'completed', text: 'Buy milk', exact: 'true')Combining text with element tag
The element tag is always included in the selector when using text::
# Generates: locator("h1", has_text="Login")see a h1 (text: 'Login')
# Generates: locator("button", has_text="Submit")click button (text: 'Submit')This ensures the selector targets the correct element type, avoiding strict-mode violations when multiple elements contain the same text.
Best practices
-
Use
role+namefor interactive elements - Most accessible and reliable:click button (role: 'button', name: 'Submit') -
Use
testidfor unique identification - Stable across refactors:see a div (testid: 'welcome-message') -
Use
class+textfor list items - When you need both styling and content:see a li (class: 'completed', text: 'Buy groceries') -
Avoid
text:alone on common text - May match multiple elements:# Risky: "Login" appears in heading and buttonsee a h1 (text: 'Login') # Good: scoped to h1
Pattern Details
see
The see pattern requires a quantifier to specify the expected element count:
| Quantifier | Assertion | Generated Code |
|---|---|---|
a or an | Element is visible | to_be_visible() |
any | At least one element is visible | to_be_visible() |
no | Zero elements exist | to_have_count(0) |
Note: The a/an quantifier uses Playwright’s to_be_visible() assertion, which passes as long as at least one matching element is visible. It does NOT enforce exactly one match. For strict single-element assertions, use a more specific selector.
see a "Submit Button"see no "Error Message"see any "Product Card"Using the state: modifier:
By default, see asserts visibility using to_be_visible(). For empty containers or hidden elements, use the state: modifier:
| State | Assertion | Use Case |
|---|---|---|
| (default) | to_be_visible() | Element is rendered and has non-zero size |
attached | to_be_attached() | Element exists in DOM (even if empty/hidden) |
hidden | to_be_hidden() | Element exists but is not visible |
# Assert element is visible (default)see a ul (testid: 'todo-list')
# Assert element exists in DOM (useful for empty containers)see a ul (testid: 'todo-list', state: 'attached')
# Assert element is hiddensee a div (class: 'loading-overlay', state: 'hidden')Gotcha: Empty containers are “hidden”
Playwright’s to_be_visible() requires non-zero size. An empty <ul> or <div> with no content will fail visibility assertions. Use state: 'attached' for empty containers:
# This fails on an empty list (no visible items)see a ul (testid: 'todo-list')
# This works - just checks the element existssee a ul (testid: 'todo-list', state: 'attached')Using exact for strict text matching:
By default, text: uses Playwright’s has_text which matches substrings and ancestor elements. This can cause strict-mode violations when multiple elements contain the same text. Use exact: 'true' for precise matching:
# May match parent elements containing this textsee a div (text: 'Error')
# Strict exact match - recommended for error messagessee a div (text: 'Invalid username', exact: 'true')expect failure
Wrap actions that should raise an exception. Useful for testing error handling.
expect failure (error: "TimeoutError", capture: "exc_info") click "Nonexistent Element"Generated code uses pytest.raises context manager.
Example
Vanya test scenario (vanya-lang.org/v0.1)
Script title: "Complete checkout flow"
Within the Browser (plugin: playwright-python, type: browser, headless: "true") go to "https://shop.example.com" click "Add to Cart" click "Checkout" Within the Shipping Form (type: element, testid: "shipping-form") type "123 Main St" into "Address" type "10001" into "ZIP" click "Place Order" see a "Order Confirmation" see no "Error Message"Generated Code
When using --format pytest-function, vanya generates tests that use the pytest-playwright page fixture. This provides several benefits:
- Browser reuse: Tests share browser instances for faster execution
- Trace-on-failure: Automatic trace collection when tests fail
- Video-on-failure: Optional video recording of failed tests
- Screenshots: Automatic screenshot capture on assertion failures
The above example generates Python code similar to:
from playwright.sync_api import sync_playwright, expectimport pytest
def test_complete_checkout_flow(page): # Uses pytest-playwright 'page' fixture - browser lifecycle managed automatically page.goto("https://shop.example.com") page.locator("Add to Cart").click() page.locator("Checkout").click()
# Nested element scope shipping_form = page.locator("[data-testid='shipping-form']") shipping_form.locator("Address").fill("123 Main St") shipping_form.locator("ZIP").fill("10001")
page.locator("Place Order").click() expect(page.locator("Order Confirmation")).to_be_visible() expect(page.locator("Error Message")).to_have_count(0)To enable trace collection in CI, add to your pytest.ini or pyproject.toml:
[pytest]addopts = --tracing=retain-on-failureMore Examples
Selecting by different attributes
Using Within blocks for scoping:
# By IDWithin the Header (type: element, id: 'main-header') click logout-button
# By CSS classWithin the Card (type: element, class: 'product-card') see a price-label
# By data-testidWithin the Form (type: element, testid: 'login-form') type "user@test.com" into email-input
# By accessible labelWithin the Dialog (type: element, label: 'Confirmation') click submit-button
# By ARIA role and nameWithin the Navigation (type: element, role: 'navigation') click home-linkUsing inline modifiers on actions:
Modifiers can be placed directly on action targets instead of using Define:
Within the Browser (plugin: playwright-python, type: browser) # Click by ARIA role + accessible name (recommended for buttons) click button (role: 'button', name: 'Submit')
# Click by role only (matches implicit ARIA roles) click input (role: 'checkbox')
# Click by HTML type attribute (e.g., input type="checkbox") click input (type: 'checkbox')
# Type into element by testid type "user@example.com" into input (testid: 'email-field')
# See element with tag + class combined see a li (class: 'completed') # generates: li.completed
# Exact text matching to avoid ancestor matches see a div (text: 'Error: Invalid input', exact: 'true')Using Define for reusable selectors
Within the Browser (plugin: playwright-python, type: browser) go to "/checkout"
# Define reusable selectors Define email-field (testid: 'email-input') Define submit-btn (testid: 'checkout-submit') Define error-msg (class: 'error-message')
# Use them in actions type "customer@example.com" into email-field click submit-btn see no error-msgAsserting text content
Within the Browser (plugin: playwright-python, type: browser) go to "/dashboard"
# Assert element exists with specific text see a h1 (text: 'Welcome back') see a div (text: 'No notifications')
# Assert element does NOT exist see no alert (text: 'Error')Waiting for elements
Within the Browser (plugin: playwright-python, type: browser) go to "/async-page"
# Wait for element with default timeout wait for loading-spinner
# Wait with custom timeout (ms) wait for data-table (timeout: '10000')
# Wait for specific state wait for modal (state: 'hidden')
# Wait with selector modifiers wait for li (class: 'completed') wait for button (role: 'button', name: 'Submit') wait for div (testid: 'results-panel')Pressing keyboard keys
Within the Browser (plugin: playwright-python, type: browser) go to "/search"
# Type into search box and press Enter to submit type "search query" into search-input press Enter
# Other common keys: Tab, Escape, ArrowDown, Space press Tab press EscapeTesting error conditions
Within the Browser (plugin: playwright-python, type: browser) go to "/form"
expect failure (error: 'TimeoutError') click nonexistent-button (timeout: '1000')