If you're interviewing for a QA Automation or SDET role that uses Python, expect Playwright questions tailored to the Python ecosystem: pytest fixtures, conftest.py patterns, sync vs async API, and Pythonic Page Object Models. This guide covers every topic interviewers ask — from beginner fundamentals to advanced async patterns and AI-powered testing.
Questions are organized into seven sections: Beginner (fundamentals), Intermediate (pytest patterns & framework design), Advanced (async API & architecture), Coding Challenges (hands-on problems), CI/CD & DevOps, AI & Modern Testing, and Interview Tips.
Beginner Playwright Python Questions
These questions test your understanding of Playwright's core concepts in a Python context. Expect 3–5 of these in any Playwright Python interview.
Playwright for Python is Microsoft's open-source browser automation library with a native Python API. It communicates with browsers through the Chrome DevTools Protocol (CDP) and equivalent native protocols for Firefox and WebKit — making it significantly faster than Selenium's WebDriver protocol.
Key differences from Selenium in a Python context:
- Auto-waiting — no need for
WebDriverWaitorexpected_conditions - Built-in pytest plugin —
pytest-playwrightprovides fixtures out of the box - All three browser engines — Chromium, Firefox, and WebKit with one
pip install - Network interception — built-in
page.route()vs. Selenium's limited proxy-based approach - API testing — Playwright has a native request context, Selenium has none
Installation requires two steps: install the Python package, then download the browser binaries:
# Install Playwright and the pytest plugin pip install playwright pytest-playwright # Download browser binaries (Chromium, Firefox, WebKit) playwright install # Or install only Chromium to save disk space playwright install chromium
Playwright Python offers two APIs:
- Sync API (
from playwright.sync_api import sync_playwright) — blocking calls, simpler to read and write. Used withpytest-playwrightand ideal for test automation. - Async API (
from playwright.async_api import async_playwright) — non-blocking calls usingasync/await. Used withasyncioorpytest-asyncio. Ideal for scraping, concurrent operations, or integration into async frameworks.
For testing, the sync API is recommended because pytest-playwright fixtures use it by default, and synchronous test code is easier to debug and maintain.
With pytest-playwright, you get a page fixture automatically — no setup or teardown code needed:
import re from playwright.sync_api import Page, expect def test_homepage_title(page: Page): page.goto("https://playwright.dev/") expect(page).to_have_title(re.compile("Playwright")) def test_get_started_link(page: Page): page.goto("https://playwright.dev/") page.get_by_role("link", name="Get started").click() expect(page.get_by_role("heading", name="Installation")).to_be_visible()
Playwright supports Chromium (Chrome, Edge), Firefox, and WebKit (Safari). With pytest-playwright, you select browsers using the --browser CLI flag:
# Run on Chromium (default) pytest # Run on Firefox pytest --browser firefox # Run on WebKit pytest --browser webkit # Run on ALL browsers pytest --browser chromium --browser firefox --browser webkit
By default, pytest-playwright runs in headless mode (no visible browser window). To run headed:
# Headed mode — shows the browser pytest --headed # Slow motion for demos/debugging pytest --headed --slowmo 500
Use the page.screenshot() method with various options:
def test_screenshot(page: Page): page.goto("https://example.com") # Full page screenshot page.screenshot(path="screenshot.png", full_page=True) # Element screenshot page.get_by_role("heading").screenshot(path="heading.png") # Screenshot as bytes (for comparison) img_bytes = page.screenshot()
Playwright Python uses the same locator hierarchy as all Playwright bindings, but with Python naming conventions (snake_case):
page.get_by_role("button", name="Submit")— ARIA role + accessible name (most resilient)page.get_by_label("Email")— form controls by label textpage.get_by_text("Welcome")— visible text contentpage.get_by_placeholder("Search...")— input placeholderspage.get_by_test_id("submit-btn")— data-testid attributepage.locator("css=.my-class")— CSS selector (less preferred)
Always prefer semantic locators (get_by_role, get_by_label) over CSS/XPath — they survive UI redesigns and improve test readability.
Playwright Python provides web-first assertions through the expect() function. These assertions auto-retry until the condition is met or the timeout expires:
from playwright.sync_api import Page, expect def test_assertions(page: Page): page.goto("https://example.com") # Element assertions expect(page.get_by_role("heading")).to_be_visible() expect(page.get_by_role("button")).to_be_enabled() expect(page.get_by_label("Email")).to_have_value("test@example.com") expect(page.get_by_text("Success")).to_have_count(1) # Page assertions expect(page).to_have_url(re.compile(r"dashboard")) expect(page).to_have_title("My Dashboard") # Negation expect(page.get_by_text("Error")).not_to_be_visible()
With pytest-playwright installed, you run tests using standard pytest commands:
# Run all tests pytest # Run a specific file pytest tests/test_login.py # Run a specific test by name pytest -k "test_login_success" # Run with verbose output pytest -v # Run with 4 parallel workers pytest -n 4
Intermediate Playwright Python Questions
These questions test your ability to build and maintain a Playwright Python test framework. Expect these in mid-level SDET and QA Automation Engineer interviews.
The pytest-playwright plugin provides these built-in fixtures:
page— a fresh Page instance in an isolated BrowserContext (most commonly used)context— the BrowserContext for the current test (useful for multi-page scenarios)browser— the shared Browser instance (useful for creating additional contexts)browser_name— string name of the current browser ("chromium", "firefox", "webkit")browser_type— the BrowserType object for launching additional browsersplaywright— the Playwright instance itself
Each page fixture creates a new BrowserContext, providing complete test isolation (separate cookies, localStorage, cache) without launching a new browser process.
conftest.py is pytest's mechanism for sharing fixtures across test files. In Playwright Python projects, you use it to configure browser options, create custom fixtures, and set up shared state:
import pytest from playwright.sync_api import Page @pytest.fixture(scope="session") def browser_context_args(browser_context_args): """Override default context options for all tests.""" return { **browser_context_args, "viewport": {"width": 1920, "height": 1080}, "ignore_https_errors": True, } @pytest.fixture def authenticated_page(page: Page): """Provide a logged-in page to any test that needs it.""" page.goto("https://myapp.com/login") page.get_by_label("Email").fill("admin@test.com") page.get_by_label("Password").fill("password123") page.get_by_role("button", name="Sign In").click() page.wait_for_url("**/dashboard") return page
POM in Python uses classes that accept a Page instance and expose properties (locators) and methods (actions). Unlike Java, Python POM classes are concise thanks to property decorators and type hints:
from playwright.sync_api import Page, expect class LoginPage: def __init__(self, page: Page): self.page = page self.email_input = page.get_by_label("Email") self.password_input = page.get_by_label("Password") self.submit_button = page.get_by_role("button", name="Sign In") self.error_message = page.get_by_role("alert") def navigate(self): self.page.goto("https://myapp.com/login") def login(self, email: str, password: str): self.email_input.fill(email) self.password_input.fill(password) self.submit_button.click() def expect_error(self, message: str): expect(self.error_message).to_have_text(message)
For native <select> elements, use select_option(). For custom dropdowns (div-based), click to open, then click the option:
# Native <select> element page.get_by_label("Country").select_option("US") # by value page.get_by_label("Country").select_option(label="United States") # by visible text page.get_by_label("Country").select_option(index=3) # by index # Custom dropdown (click-based) page.get_by_role("combobox", name="Country").click() page.get_by_role("option", name="United States").click()
Register an event listener before the action that triggers the dialog. Playwright auto-dismisses unhandled dialogs, so you must set the handler first:
# Accept an alert dialog page.on("dialog", lambda dialog: dialog.accept()) page.get_by_role("button", name="Delete").click() # Dismiss a confirm dialog page.on("dialog", lambda dialog: dialog.dismiss()) # Enter text in a prompt dialog page.on("dialog", lambda dialog: dialog.accept("my input")) # One-time handler using expect_event with page.expect_event("dialog") as dialog_info: page.get_by_role("button", name="Confirm").click() dialog = dialog_info.value assert dialog.message == "Are you sure?"
Use page.route() to intercept requests matching a URL pattern and provide mock responses, abort requests, or modify responses:
import json def test_mock_api(page: Page): # Mock an API response def handle_products(route): route.fulfill( status=200, content_type="application/json", body=json.dumps([{"id": 1, "name": "Mock Product"}]), ) page.route("**/api/products", handle_products) page.goto("https://myapp.com/shop") expect(page.get_by_text("Mock Product")).to_be_visible() # Abort image requests (speed up tests) page.route("**/*.{png,jpg}", lambda route: route.abort())
Playwright's auto-waiting handles most cases, but explicit waits are sometimes needed:
page.wait_for_url("**/dashboard")— wait for navigation to a URL patternpage.wait_for_load_state("networkidle")— wait for network to be idle (500ms with no requests)page.wait_for_selector(".spinner", state="hidden")— wait for an element to disappearpage.wait_for_timeout(1000)— hard wait (avoid in tests, use for debugging only)locator.wait_for(state="visible")— wait for a specific locator stateexpect(locator).to_be_visible(timeout=10000)— assertion with custom timeout
Best practice: Rely on auto-waiting and web-first assertions. Avoid wait_for_timeout() in production tests.
Use the browser fixture to create additional contexts. This is useful for testing multi-user scenarios:
from playwright.sync_api import Browser, expect def test_two_users_chatting(browser: Browser): # Create two isolated contexts alice_context = browser.new_context() bob_context = browser.new_context() alice_page = alice_context.new_page() bob_page = bob_context.new_page() # Alice and Bob are completely isolated alice_page.goto("https://chat.example.com") bob_page.goto("https://chat.example.com") # Clean up alice_context.close() bob_context.close()
Use pytest's @pytest.mark.parametrize decorator to run the same test with different data sets:
import pytest from playwright.sync_api import Page, expect @pytest.mark.parametrize("username,password,expected", [ ("admin@test.com", "correct_pass", "Dashboard"), ("admin@test.com", "wrong_pass", "Invalid credentials"), ("", "", "Email is required"), ]) def test_login_scenarios(page: Page, username, password, expected): page.goto("https://myapp.com/login") page.get_by_label("Email").fill(username) page.get_by_label("Password").fill(password) page.get_by_role("button", name="Sign In").click() expect(page.get_by_text(expected)).to_be_visible()
Use python-dotenv for environment-specific config and pytest fixtures for base URLs:
import os import pytest from dotenv import load_dotenv load_dotenv() @pytest.fixture(scope="session") def base_url(): """Return the base URL from environment or default.""" return os.getenv("BASE_URL", "https://staging.myapp.com") @pytest.fixture(scope="session") def browser_context_args(browser_context_args, base_url): return { **browser_context_args, "base_url": base_url, }
Tip: With base_url configured, you can use relative URLs in tests: page.goto("/login") instead of the full URL. This makes switching between staging and production seamless.
Advanced Playwright Python Questions
These questions probe your experience with real-world architecture, async patterns, and debugging. Interviewers want depth and trade-off analysis.
Use pytest-asyncio alongside Playwright's async API for concurrent test operations. This is useful when you need to perform multiple async operations simultaneously:
import asyncio import pytest from playwright.async_api import async_playwright, expect @pytest.mark.asyncio async def test_concurrent_pages(): async with async_playwright() as p: browser = await p.chromium.launch() # Create two pages concurrently context = await browser.new_context() page1 = await context.new_page() page2 = await context.new_page() # Navigate both pages simultaneously await asyncio.gather( page1.goto("https://example.com/page1"), page2.goto("https://example.com/page2"), ) await expect(page1).to_have_title("Page 1") await expect(page2).to_have_title("Page 2") await browser.close()
Custom fixtures extend pytest-playwright's built-in fixtures. Use yield for setup/teardown patterns and type hints for IDE support:
import pytest from playwright.sync_api import Page, BrowserContext from pages.login_page import LoginPage from pages.dashboard_page import DashboardPage @pytest.fixture def login_page(page: Page) -> LoginPage: """Provide a LoginPage POM instance.""" login = LoginPage(page) login.navigate() return login @pytest.fixture def admin_dashboard(page: Page) -> DashboardPage: """Provide a logged-in admin dashboard page.""" login = LoginPage(page) login.navigate() login.login("admin@test.com", "password123") dashboard = DashboardPage(page) yield dashboard # Teardown: log out after test completes dashboard.logout()
Use pytest-xdist for parallel execution. Each worker gets its own browser instance:
# Install pytest-xdist pip install pytest-xdist # Run with 4 workers pytest -n 4 # Auto-detect number of CPU cores pytest -n auto # Distribute tests by file (default) or by test pytest -n 4 --dist loadfile
Important: When running in parallel, ensure your tests are fully isolated. Avoid shared database state, shared files, or any test that depends on another test's output. Each worker gets its own browser context, but your backend state must be isolated too.
Playwright traces capture a complete timeline of test execution — DOM snapshots, network requests, console logs, and screenshots at every step:
def test_with_trace(page: Page, context: BrowserContext): # Start tracing before the test actions context.tracing.start(screenshots=True, snapshots=True, sources=True) page.goto("https://myapp.com") page.get_by_role("link", name="Dashboard").click() # Stop and save the trace context.tracing.stop(path="trace.zip") # View the trace in the browser # playwright show-trace trace.zip
# Enable tracing via CLI (no code changes needed) pytest --tracing on # Trace only on first retry (recommended for CI) pytest --tracing retain-on-failure
Playwright's APIRequestContext lets you make HTTP requests without launching a browser. Use it for API-level setup, teardown, or pure API testing:
from playwright.sync_api import Playwright def test_api_crud(playwright: Playwright): api = playwright.request.new_context( base_url="https://api.myapp.com", extra_http_headers={"Authorization": "Bearer token123"}, ) # Create a user response = api.post("/users", data={ "name": "Jane Doe", "email": "jane@test.com", }) assert response.status == 201 user = response.json() # Fetch the user response = api.get(f"/users/{user['id']}") assert response.json()["name"] == "Jane Doe" # Clean up api.delete(f"/users/{user['id']}") api.dispose()
Use storage_state to save and restore cookies/localStorage. Log in once, save the state to a file, and load it in subsequent tests:
import pytest from playwright.sync_api import Browser @pytest.fixture(scope="session") def auth_state(browser: Browser): """Log in once and save auth state for the entire session.""" context = browser.new_context() page = context.new_page() page.goto("https://myapp.com/login") page.get_by_label("Email").fill("admin@test.com") page.get_by_label("Password").fill("password123") page.get_by_role("button", name="Sign In").click() page.wait_for_url("**/dashboard") context.storage_state(path="auth.json") context.close() return "auth.json" @pytest.fixture def authenticated_page(browser: Browser, auth_state): """Create a pre-authenticated page for each test.""" context = browser.new_context(storage_state=auth_state) page = context.new_page() yield page context.close()
Key BrowserContext options you should know:
viewport={"width": 1280, "height": 720}— set the browser viewport sizestorage_state="auth.json"— load saved authentication stateignore_https_errors=True— bypass SSL certificate errors in staginglocale="fr-FR"— set the browser locale for i18n testingtimezone_id="America/New_York"— override timezonegeolocation={"latitude": 40.7, "longitude": -74.0}— mock GPS locationpermissions=["geolocation", "notifications"]— grant permissionscolor_scheme="dark"— test dark moderecord_video_dir="videos/"— record video of test executionhttp_credentials={"username": "user", "password": "pass"}— HTTP Basic auth
Pytest's monkeypatch fixture lets you override environment variables, module attributes, or dictionary values during a test. Combined with Playwright, it's useful for injecting test config:
def test_admin_feature_flag(page: Page, monkeypatch): # Override env var for this test only monkeypatch.setenv("FEATURE_ADMIN_PANEL", "true") page.goto("https://myapp.com/dashboard") # Admin panel should be visible because feature flag is on expect(page.get_by_role("navigation", name="Admin")).to_be_visible()
pytest-bdd lets you write Gherkin-style feature files and map them to Playwright step definitions:
Feature: User Login
Scenario: Successful login with valid credentials
Given I am on the login page
When I enter "admin@test.com" and "password123"
And I click the Sign In button
Then I should see the dashboard
from pytest_bdd import scenario, given, when, then, parsers from playwright.sync_api import Page, expect @scenario("features/login.feature", "Successful login with valid credentials") def test_login(): pass @given("I am on the login page") def navigate_to_login(page: Page): page.goto("https://myapp.com/login") @when(parsers.parse('I enter "{email}" and "{password}"')) def enter_credentials(page: Page, email, password): page.get_by_label("Email").fill(email) page.get_by_label("Password").fill(password) @when("I click the Sign In button") def click_sign_in(page: Page): page.get_by_role("button", name="Sign In").click() @then("I should see the dashboard") def verify_dashboard(page: Page): expect(page).to_have_url(re.compile(r"dashboard"))
Key performance strategies for large Playwright Python suites:
- Reuse auth state — log in once with
storage_stateinstead of logging in per test - Use API for setup/teardown — create test data via API instead of UI clicks
- Run in parallel — use
pytest-xdistwith-n autofor parallel workers - Block unnecessary resources — abort images, fonts, and analytics with
page.route() - Use headless mode — headless is faster than headed (the default)
- Minimize
wait_for_timeout()— hard waits add up and are often unnecessary - Use
sessionscope for browser — pytest-playwright already does this by default - Docker with pre-installed browsers — avoids downloading browsers in CI
Coding Challenges
These are hands-on problems you may be asked to solve during a live coding interview. Practice writing these from memory.
This test covers the happy path and verifies the user lands on the dashboard after login:
import re from playwright.sync_api import Page, expect def test_successful_login(page: Page): # Navigate to login page page.goto("https://myapp.com/login") # Fill in credentials page.get_by_label("Email").fill("user@example.com") page.get_by_label("Password").fill("securePassword1!") # Submit the form page.get_by_role("button", name="Sign In").click() # Verify successful login expect(page).to_have_url(re.compile(r"/dashboard")) expect(page.get_by_role("heading", name="Welcome")).to_be_visible() expect(page.get_by_text("user@example.com")).to_be_visible() def test_login_with_invalid_password(page: Page): page.goto("https://myapp.com/login") page.get_by_label("Email").fill("user@example.com") page.get_by_label("Password").fill("wrongpassword") page.get_by_role("button", name="Sign In").click() # Verify error message expect(page.get_by_role("alert")).to_have_text("Invalid email or password") # Verify we stayed on login page expect(page).to_have_url(re.compile(r"/login"))
A complete POM class with locators, actions, and assertion helpers:
from playwright.sync_api import Page, Locator, expect class ProductsPage: URL = "/products" def __init__(self, page: Page): self.page = page self.search_input = page.get_by_placeholder("Search products...") self.category_select = page.get_by_label("Category") self.product_cards = page.locator("[data-testid='product-card']") self.cart_badge = page.get_by_test_id("cart-count") self.sort_dropdown = page.get_by_label("Sort by") def navigate(self): self.page.goto(self.URL) expect(self.page).to_have_url(re.compile(r"/products")) def search(self, query: str): self.search_input.fill(query) self.search_input.press("Enter") def filter_by_category(self, category: str): self.category_select.select_option(label=category) def add_to_cart(self, product_name: str): card = self.product_cards.filter(has_text=product_name) card.get_by_role("button", name="Add to Cart").click() def expect_product_count(self, count: int): expect(self.product_cards).to_have_count(count) def expect_cart_count(self, count: int): expect(self.cart_badge).to_have_text(str(count))
A complete CRUD API test without launching a browser:
from playwright.sync_api import Playwright def test_todo_api_crud(playwright: Playwright): api = playwright.request.new_context( base_url="https://jsonplaceholder.typicode.com", ) # CREATE create_resp = api.post("/todos", data={ "title": "Write Playwright tests", "completed": False, "userId": 1, }) assert create_resp.status == 201 todo = create_resp.json() assert todo["title"] == "Write Playwright tests" # READ get_resp = api.get("/todos/1") assert get_resp.status == 200 assert get_resp.json()["id"] == 1 # UPDATE update_resp = api.put("/todos/1", data={ "title": "Updated title", "completed": True, "userId": 1, }) assert update_resp.status == 200 # DELETE delete_resp = api.delete("/todos/1") assert delete_resp.status == 200 api.dispose()
Playwright Python supports screenshot comparison for visual regression testing:
from playwright.sync_api import Page, expect def test_homepage_visual(page: Page): page.goto("https://myapp.com") # Wait for animations to settle page.wait_for_load_state("networkidle") # Full page screenshot comparison expect(page).to_have_screenshot("homepage.png", max_diff_pixels=100) def test_component_visual(page: Page): page.goto("https://myapp.com/pricing") # Compare a specific component pricing_card = page.get_by_test_id("pricing-pro") expect(pricing_card).to_have_screenshot( "pricing-pro-card.png", max_diff_pixel_ratio=0.01, # Allow 1% pixel difference )
# First run creates baseline screenshots pytest --update-snapshots # Subsequent runs compare against baselines pytest
Load test data from a JSON file and use pytest.mark.parametrize:
import json import pytest from pathlib import Path from playwright.sync_api import Page, expect # Load test data from JSON file DATA_FILE = Path(__file__).parent / "data" / "users.json" TEST_USERS = json.loads(DATA_FILE.read_text()) @pytest.mark.parametrize("user", TEST_USERS, ids=[u["name"] for u in TEST_USERS]) def test_user_profile_display(page: Page, user): """Verify each user's profile displays correctly.""" page.goto(f"https://myapp.com/users/{user['id']}") expect(page.get_by_role("heading")).to_have_text(user["name"]) expect(page.get_by_text(user["email"])).to_be_visible() expect(page.get_by_text(user["role"])).to_be_visible()
[
{"id": 1, "name": "Alice Johnson", "email": "alice@test.com", "role": "Admin"},
{"id": 2, "name": "Bob Smith", "email": "bob@test.com", "role": "Editor"},
{"id": 3, "name": "Carol Williams", "email": "carol@test.com", "role": "Viewer"}
]
CI/CD & DevOps Questions
These questions test your ability to run Playwright Python tests in production pipelines.
Here's a production-ready GitHub Actions workflow for Playwright Python:
name: Playwright Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: |
pip install -r requirements.txt
playwright install --with-deps chromium
- name: Run tests
run: pytest --browser chromium --tracing retain-on-failure
- name: Upload traces
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-traces
path: test-results/
Use Microsoft's official Playwright Docker image which includes all browser dependencies pre-installed:
FROM mcr.microsoft.com/playwright/python:v1.48.0-noble WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . CMD ["pytest", "--browser", "chromium", "-n", "auto"]
Markers let you categorize tests and run subsets selectively:
# conftest.py — register custom markers def pytest_configure(config): config.addinivalue_line("markers", "smoke: quick smoke tests") config.addinivalue_line("markers", "regression: full regression suite") config.addinivalue_line("markers", "api: API-only tests (no browser)") # test_login.py — use markers @pytest.mark.smoke def test_login_success(page: Page): ... @pytest.mark.regression def test_login_with_expired_session(page: Page): ... # Run only smoke tests # pytest -m smoke # Run everything except API tests # pytest -m "not api"
Use pytest-html for HTML reports or Playwright's built-in reporting:
# Install pytest-html pip install pytest-html # Generate HTML report pytest --html=report.html --self-contained-html # Generate JUnit XML for CI integration pytest --junitxml=results.xml # Use Allure for richer reports pip install allure-pytest pytest --alluredir=allure-results allure serve allure-results
Use pytest-rerunfailures to automatically retry failed tests:
# Install the plugin pip install pytest-rerunfailures # Retry failed tests up to 2 times pytest --reruns 2 # Add a delay between retries pytest --reruns 2 --reruns-delay 3 # Mark individual tests for retry @pytest.mark.flaky(reruns=3, reruns_delay=2) def test_sometimes_flaky(page: Page): ...
Warning: Retries are a band-aid, not a fix. If a test needs retries, investigate the root cause: timing issues, shared state, or external service dependencies. Fix the flake, then remove the retry.
AI & Modern Testing Questions
These questions are the 2026 differentiator. Mentioning AI-powered testing signals you're up to date with current tooling.
Claude AI can generate complete Playwright Python test files from natural language descriptions. Unlike Codegen (which records clicks), Claude understands intent and generates:
- Semantic locators (
get_by_role,get_by_label) instead of brittle CSS selectors - Proper POM structure with type hints
- Edge case tests (empty fields, boundary values, error states)
- Meaningful assertions beyond just "element exists"
- conftest.py fixtures and pytest markers
You provide a prompt like "Write a Playwright Python test for the checkout flow including empty cart, single item, and coupon code scenarios" and Claude generates a complete, runnable test file with proper Python conventions.
The Model Context Protocol (MCP) Server connects Claude AI to a running Playwright browser, giving the AI live access to the DOM, accessibility tree, network requests, and console logs. With MCP, Claude can:
- Navigate to your actual application and inspect the live DOM
- Read the accessibility tree to find the best locators
- Generate tests based on the real page structure, not guessed selectors
- Debug failing tests by examining the actual page state
The generated tests are standard pytest-playwright files — no MCP dependency at runtime. MCP is only used during test authoring.
Self-healing tests use AI to automatically fix broken selectors when the UI changes. The workflow:
- A test fails because a locator no longer matches (e.g., button text changed from "Submit" to "Save")
- An AI agent detects the failure, navigates to the page via MCP, and inspects the current DOM
- The agent identifies the updated element using the accessibility tree and context
- The test file is automatically updated with the corrected locator
- The updated test runs again to verify the fix
This dramatically reduces maintenance burden for large test suites where locator breakage is the #1 cause of test failures.
Agentic testing uses AI agents that can plan, generate, execute, and heal Playwright Python tests autonomously. The emerging architecture in 2026:
- Planner Agent — takes a user story and produces a structured test plan (pages to cover, flows to test, edge cases)
- Generator Agent — takes the plan and generates
pytest-playwrightspec files using MCP for live DOM context - Healer Agent — monitors CI runs, detects failures, and fixes broken locators automatically
QA engineers become "test architects" who design the agentic pipeline and review AI output, rather than writing individual selectors and assertions by hand.
AI-generated tests require the same review rigor as human-written code. Key review checklist:
- Locator quality — verify semantic locators (
get_by_role,get_by_label) are used instead of brittle CSS selectors - Assertion coverage — check that tests assert meaningful outcomes, not just element visibility
- Test isolation — ensure each test is independent and doesn't rely on another test's state
- Data management — verify test data is created/cleaned up properly, not hardcoded
- Error paths — confirm the AI included negative test cases and edge cases
- Naming conventions — function names should describe the scenario, not implementation details
- No sleep calls — AI sometimes adds
wait_for_timeout()unnecessarily; remove them
Tips for Acing the Interview
- Practice writing pytest-playwright code by hand — interviewers may ask you to write a test without IDE autocomplete. Practice
get_by_role,get_by_label,expect(), and fixture syntax from memory. Know the Python snake_case API, not just the TypeScript camelCase. - Know conftest.py deeply — the ability to explain fixture scopes (
function,class,module,session),yieldfixtures for teardown, and fixture composition is a strong signal of pytest mastery. - Have a debugging story ready — prepare a specific example of a flaky or hard-to-debug Playwright Python test you diagnosed. Include the symptom, your investigation process, the root cause, and the fix. Mention Trace Viewer if you used it.
- Know trade-offs — "It depends" is acceptable if you explain the reasoning. Why sync vs async API? Why POM vs raw page objects? Why
get_by_roleoverget_by_test_id? Whypytest-xdistvs single-threaded? - Mention AI/MCP proactively — even if not asked directly, mentioning Claude AI + MCP Server for test generation signals you're current with 2026 tooling. Explain that the generated tests are standard pytest files with zero AI runtime dependency.
- Ask about their test suite — "How many tests do you have?", "What's your flake rate?", "Do you use parallel execution?" shows you think at the architecture level, not just the test-writing level.
Common mistake: Candidates who only know TypeScript Playwright syntax struggle with Python-specific questions about conftest.py, fixtures, markers, and pytest conventions. If the role requires Python, practice the Python API specifically.
Frequently Asked Questions
What Playwright Python topics are asked in interviews in 2026?
Interviews cover pytest-playwright fixtures, sync vs async API, Page Object Model in Python, conftest.py patterns, network interception, API testing with request context, parallel execution, tracing, CI/CD, and AI-powered testing with Claude and MCP Server.
Is Playwright Python as popular as Playwright TypeScript for interviews?
TypeScript remains the most popular Playwright language, but Python is growing rapidly — especially in teams with Python backends, data engineering, or ML. Many companies now accept either language. If the job listing mentions Python or pytest, expect Python-specific questions.
Do I need to know pytest to use Playwright Python?
Yes. The pytest-playwright plugin is the standard way to run Playwright tests in Python. You need to understand fixtures, conftest.py, markers, parametrize, and basic pytest conventions.
What coding challenges are common in Playwright Python interviews?
Common challenges: writing a login test, implementing a POM class, creating data-driven tests with pytest.mark.parametrize, writing API tests with request context, and setting up authentication state reuse with storage_state.
How do I prepare for a Playwright Python interview in 2026?
Focus on four areas: (1) pytest-playwright fundamentals, (2) Playwright Python API — locators, actions, assertions, network interception, (3) framework patterns — POM, custom fixtures, parallel execution, CI/CD, and (4) AI topics — Claude AI, MCP Server, self-healing locators, and agentic testing.
Asim Noaman
Senior QA Automation Engineer & AI Testing Specialist
With years of hands-on experience building test automation frameworks for production applications, Asim specializes in combining traditional QA methodologies with cutting-edge AI tools. He has helped teams adopt Playwright and AI-driven testing workflows to ship faster with fewer bugs.