The two best Playwright productivity tools — Inspector and Codegen — work the same in Python as they do in TypeScript, with one extra knob: a --target flag that picks the language and style of the generated code. This lesson covers when to reach for codegen, what the --target python-pytest output looks like, how to step through a failing test with Inspector, how page.pause() lets you debug interactively without environment variables, and the device-emulation and storage-state recipes that make codegen genuinely useful for real apps.
Codegen — record actions, get pytest code
Codegen launches a browser, records everything you do, and emits idiomatic test code in the language of your choice. The Python target you'll use most:
playwright codegen https://myapp.com --target python-pytestTwo windows open: the browser you're driving, and the Inspector panel where Playwright transcribes every click, fill, and navigation into Python on the fly. Stop when you've performed the flow you want; the panel holds the full test, ready to copy.
The three Python targets:
--target python-pytest— the default for this course. Generates adef test_(page: Page):function with sync calls and thePagetype hint, ready to drop into a test file.--target python— rawsync_playwright()script, no pytest. Good for one-off automation scripts that aren't tests.--target python-async— async API equivalent. Reach for it only if your suite is async-first (see lesson 2).
A typical generated test:
from playwright.sync_api import Page, expect
def test_example(page: Page):
page.goto("https://myapp.com/login")
page.get_by_label("Email").fill("alice@test.com")
page.get_by_label("Password").fill("password123")
page.get_by_role("button", name="Sign in").click()
expect(page).to_have_url("https://myapp.com/dashboard")Codegen prefers the user-facing locators we'll cover in chapter 2 — get_by_role, get_by_label, get_by_placeholder — over CSS selectors, exactly the same priority Playwright recommends for hand-written tests. That alone makes codegen output a useful starting point even for engineers who could write the test from scratch.
Inspector — pause, step, inspect
Inspector is the same panel codegen uses, but pointed at running tests instead of a recording session. Two ways to launch it:
Environment variable — opens Inspector for the entire pytest run:
PWDEBUG=1 pytest tests/test_login.py -vPWDEBUG=1 does three things at once:
- Launches the Playwright Inspector window alongside the browser.
- Disables the default action and assertion timeouts so you can pause indefinitely while you poke around.
- Forces headed mode regardless of
pytest.ini— there's no point in an Inspector you can't see.
On Windows the syntax differs slightly:
set PWDEBUG=1 && pytest tests/test_login.pyIn PowerShell: $env:PWDEBUG=1; pytest tests/test_login.py.
page.pause() — drop a breakpoint in code:
def test_debug_me(page: Page):
page.goto("/products")
page.pause() # test pauses here, Inspector opens
# continue interacting manually, then press Resume in the Inspector
page.get_by_role("button", name="Add to cart").first.click()
expect(page.get_by_test_id("cart-count")).to_have_text("1")page.pause() is the form to reach for when you want to debug one specific test without setting an env var. The browser opens, the Inspector appears, the test halts at the pause() call, and you can:
- Step through the next actions one at a time.
- Click the Pick locator button and hover any element on the page — Playwright suggests the best Python locator for it (
page.get_by_role("button", name="Add to cart")) which you can paste straight into your test. - Edit the locator in the Inspector and watch it light up in the page.
- Click Resume to let the rest of the test run, or Step over to advance one action.
It's the closest thing the Python Playwright world has to a graphical debugger, and you'll use it weekly.
Codegen with device emulation
Need to record a mobile flow? Pass --device:
playwright codegen --device="iPhone 13" https://myapp.com --target python-pytestThe browser opens with the iPhone 13's viewport, user agent, device-scale-factor, and touch enabled. Playwright bundles ~140 device descriptors — iPhone 13, Pixel 5, iPad Pro 11, Galaxy S9+, etc. The full list is at playwright.devices in code, or playwright codegen --help at the CLI.
The generated test embeds the device into the context:
from playwright.sync_api import Page, expect, sync_playwright
def test_mobile_homepage(page: Page):
# The page fixture would carry the device config from pytest config
page.goto("https://myapp.com")
expect(page.get_by_role("button", name="Menu")).to_be_visible()In a real test suite you'd push the device config into a fixture override (chapter 5 covers this) so every test in a mobile/ folder runs against the same device profile.
Codegen with storage state — capture a logged-in session
The --save-storage flag tells codegen to write cookies and localStorage to a file when the browser closes:
playwright codegen --save-storage=auth.json https://myapp.com --target python-pytestLog in once during the recording, close the browser, and auth.json contains the authenticated session. Re-use it in tests by loading it into the context:
def test_logged_in_dashboard(browser):
context = browser.new_context(storage_state="auth.json")
page = context.new_page()
page.goto("/dashboard")
expect(page.get_by_role("heading")).to_contain_text("Welcome back")
context.close()This is the same pattern as Playwright TypeScript's storageState config — chapter 5 wires it into pytest fixtures so every test in the suite can opt-in to a logged-in starting state without paying the login cost per test.
Inspector vs codegen — when to reach for which
Inspector and codegen — same window, two purposes
Codegen — record new tests
Command: playwright codegen --target python-pytest <url>
Goal: turn a manual click-through into a Python test in 60 seconds
Best for: exploring an unfamiliar app, capturing a known-good flow
Output: copy-paste pytest code with idiomatic locators
Inspector — debug existing tests
Command: PWDEBUG=1 pytest <file> — or page.pause() in code
Goal: figure out why a test fails, find the right locator, validate selectors
Best for: debugging a flake, building a new locator, stepping through actions
Output: corrections and locators you paste into the existing test
Coming from Playwright TypeScript?
The mapping is nearly 1:1 — same tools, different --target:
- TS
npx playwright codegen→ Pythonplaywright codegen --target python-pytest - TS
npx playwright test --debug→ PythonPWDEBUG=1 pytest - TS
await page.pause()→ Pythonpage.pause()(sync — noawait) - TS Inspector window → identical Inspector window, language-agnostic UI
- TS
--save-storage=auth.json→ identical Python flag
If you've used codegen and Inspector in TypeScript, you've already used them for Python. The only thing that changes is the language of the code in the right-hand pane.
When to use codegen — and when not to
Codegen is the right tool for the first 30 seconds of a new test, not the last 30. The output is correct but generic — it doesn't know which assertions you actually care about, which locators are stable in your design system, or which pieces of the flow are setup vs the thing you're testing. Always:
- Clean up the locators. Codegen sometimes falls back to CSS or XPath when a role-based locator would work. Re-write those by hand.
- Add the assertions you actually care about. Codegen records what you did, not what you wanted to verify. Most generated tests need an extra
expect(...)or two. - Group repeated setup into fixtures or
beforeEach-style helpers. Codegen records every step inline, including the login steps you'll repeat in 50 tests. Extract them. - Delete redundant intermediate clicks. Codegen captures every navigation, including detours. The committed test should be the shortest path to the assertion.
For an experienced Playwright engineer, codegen is mostly a "what's the best locator for this element?" tool — open it, pick the locator, paste. For a beginner, it's a way to see what good Playwright Python looks like before you've internalised it. Both uses are legitimate.
⚠️ Common mistakes
- Forgetting the
--targetflag. Default codegen output is JavaScript. Without--target python-pytest, you'll get a TS test you have to translate by hand. Always specify the target — and usepython-pytestover plainpythonso the output drops into a pytest file unchanged. - Committing raw codegen output. The generated test is almost idiomatic but rarely commit-ready. CSS selector fallbacks slip in, important assertions are missing, the login flow is repeated inline. Treat codegen output as a first draft you edit, never as production code.
- Leaving
page.pause()in a committed test. Apause()halts the test and waits for human input — disastrous on CI, which has no human. Strip pauses before you commit. A pre-commit hook that greps forpage.pause()is worth setting up after your second incident.
🎯 Practice task
Use codegen and Inspector to build and debug a real test. 25-30 minutes.
-
Run codegen against Sauce Demo:
playwright codegen https://www.saucedemo.com --target python-pytest -
In the browser that opens, log in as
standard_user/secret_sauce, click on a product, add it to the cart, click the cart icon, and click Checkout. Stop the recording. -
Copy the generated code into
tests/test_codegen_checkout.py. Run it withpytest tests/test_codegen_checkout.py -v. It should pass on the first try. -
Clean up the generated test. Replace any CSS-based locator with a
get_by_role,get_by_label, orget_by_test_idequivalent. Add at least twoexpect(...)assertions — the cart count after add-to-cart, and a URL assertion after Checkout. Re-run; still passes. -
Use Inspector to debug. Deliberately break one of the locators (change
get_by_role("button", name="Add to cart")toget_by_role("button", name="Add to wishlist")). Run the test withPWDEBUG=1 pytest tests/test_codegen_checkout.py. The Inspector pauses at the broken step. Use Pick locator to find the correct selector for the Add to cart button. Fix the test, re-run normally — green. -
Use
page.pause(). Addpage.pause()right after the login step in your test. Run withpytest tests/test_codegen_checkout.py -v(no PWDEBUG needed). The test stops, Inspector opens. Click Step over to advance one action at a time and watch each locator highlight on the real page. Press Resume to finish. -
Stretch: record a mobile version.
playwright codegen --device="iPhone 13" https://www.saucedemo.com --target python-pytest. The same flow records with a mobile viewport and user agent. Save it astests/test_mobile_checkout.py. Compare it to the desktop version — most of the locators are identical because they were role-based, not layout-based. That's the resilience win.
Codegen and Inspector are the two productivity tools that turn Playwright Python from "type every locator by hand" to "click and copy." Chapter 2 starts the deep dive into locators themselves — the techniques, the priority order, the strict-mode behaviour, and how to write locators that survive your design team's next refactor.