Playwright Codegen → Clean Spec
When to Use
- You recorded a test with
playwright codegen - You want to convert raw recorded code into proper POM structure
- You need to clean up auto-generated selectors
- You want reusable page objects from a recording
How to Generate a Recording
# Launch codegen — opens browser + inspector
playwright codegen https://app.example.com
# Record to a file
playwright codegen --output recorded_test.py https://app.example.com
# Record with specific browser
playwright codegen --browser firefox https://app.example.com
# Record with device emulation
playwright codegen --device "iPhone 12" https://app.example.com
Step 1 — Typical Raw Codegen Output
# recorded_test.py ← raw codegen — DO NOT use as-is
from playwright.sync_api import Playwright, sync_playwright
def run(playwright: Playwright) -> None:
browser = playwright.chromium.launch(headless=False)
context = browser.new_context()
page = context.new_page()
page.goto("https://app.example.com/login")
page.locator("#username").click()
page.locator("#username").fill("admin@example.com")
page.locator("#password").click()
page.locator("#password").fill("password123")
page.locator("#loginBtn").click()
page.wait_for_url("https://app.example.com/dashboard")
page.locator(".welcome-message").click()
page.locator("a[href='/settings']").click()
page.locator("#save-btn").click()
context.close()
browser.close()
with sync_playwright() as playwright:
run(playwright)
Step 2 — Copilot Converts to Page Object
Ask Copilot:
"Convert this codegen output into a Page Object Model with proper fixtures"
Generated: pages/login_page.py
from pages.base_page import PlaywrightBasePage
from utils.logger import get_logger
logger = get_logger(__name__)
class LoginPage(PlaywrightBasePage):
"""Generated from codegen recording — login flow."""
# Selectors extracted from recording & improved
USERNAME = "#username"
PASSWORD = "#password"
LOGIN_BUTTON = "#loginBtn"
ERROR_MSG = ".error-message"
def login(self, username: str, password: str) -> None:
logger.info(f"Login as: {username}")
self.fill(self.USERNAME, username)
self.fill(self.PASSWORD, password)
self.click(self.LOGIN_BUTTON)
self.wait_for_load_state("networkidle")
def is_error_displayed(self) -> bool:
return self.is_visible(self.ERROR_MSG)
def get_error_text(self) -> str:
return self.get_text(self.ERROR_MSG) if self.is_error_displayed() else ""
Generated: pages/settings_page.py
from pages.base_page import PlaywrightBasePage
from utils.logger import get_logger
logger = get_logger(__name__)
class SettingsPage(PlaywrightBasePage):
"""Generated from codegen recording — settings flow."""
SAVE_BUTTON = "#save-btn"
SUCCESS_TOAST = ".toast-success"
def save_settings(self) -> None:
self.click(self.SAVE_BUTTON)
self.wait_for_selector(self.SUCCESS_TOAST)
logger.info("Settings saved")
def is_save_successful(self) -> bool:
return self.is_visible(self.SUCCESS_TOAST)
Step 3 — Copilot Generates Clean Test Spec
Generated: tests/ui/test_login_flow.py
import pytest
from pages.login_page import LoginPage
from pages.settings_page import SettingsPage
from playwright.sync_api import expect
class TestLoginFlow:
"""Test spec generated from codegen recording."""
@pytest.fixture(autouse=True)
def setup(self, page):
self.page = page.page
self.page.goto("https://app.example.com/login")
@pytest.mark.smoke
def test_login_redirects_to_dashboard(self):
login = LoginPage(self.page)
login.login("admin@example.com", "password123")
expect(self.page).to_have_url("**/dashboard")
@pytest.mark.regression
def test_login_then_navigate_to_settings(self):
login = LoginPage(self.page)
login.login("admin@example.com", "password123")
# Navigate to settings
self.page.click("a[href='/settings']")
settings = SettingsPage(self.page)
settings.save_settings()
assert settings.is_save_successful()
@pytest.mark.regression
def test_invalid_login_shows_error(self):
login = LoginPage(self.page)
login.login("wrong@example.com", "badpass")
assert login.is_error_displayed()
assert "Invalid" in login.get_error_text()
Step 4 — Selector Upgrade Map
Copilot automatically upgrades codegen selectors:
| Raw Codegen Selector | Upgraded Selector | Why |
|---|---|---|
page.locator("#username") |
page.get_by_label("Username") |
Accessible + resilient |
page.locator(".btn-primary") |
page.get_by_role("button", name="Login") |
Role-based |
page.locator("a[href='/settings']") |
page.get_by_role("link", name="Settings") |
Intent-based |
page.locator(".welcome-message") |
page.get_by_text("Welcome") |
Text-based |
page.locator("#save-btn") |
page.get_by_test_id("save-btn") |
Stable test ID |
Usage
- Run
playwright codegen <url>and copy the output - Open Copilot Chat and paste the raw code
- Ask: "Convert this Playwright codegen output into Page Object Model with proper test spec"
- Copilot generates:
pages/,tests/, upgraded selectors, fixtures