Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
Playwright Mastery
Playwright has revolutionized browser automation by fixing the “flakiness” inherent in older tools like Selenium. The key difference is architectural: Selenium talks to the browser through an HTTP middleman (WebDriver), like giving instructions through a translator. Playwright communicates directly with the browser engine via Chrome DevTools Protocol over a WebSocket — a direct phone line. This bi-directional connection means Playwright can listen for events, intercept network requests, and react instantly instead of polling.1. Core Concepts & Architecture
Browser, Context, Page
Understanding this three-layer hierarchy is the single most important concept in Playwright. Get this wrong and your tests will either be slow (launching too many browsers) or flaky (sharing state between tests).- Browser: The heavy OS process (e.g., Chromium.exe). Think of it as opening the Chrome application. Expensive to start (~1-2 seconds).
- Context: An “Incognito Window” within that browser. Cheap to create (milliseconds). Each context has its own cookies, localStorage, and session — complete isolation. Tests usually run here.
- Page: A single tab within a context. Most tests interact at this level.
Auto-Waiting (The “Flake Killer”)
This is what makes Playwright fundamentally different from Selenium. Before every action, Playwright automatically performs “Actionability Checks” — no more manualsleep(2000) or waitForElement() calls scattered throughout your tests.
Before await page.click('#submit'), Playwright automatically ensures:
- Element is attached to DOM (exists in the page).
- Element is visible (not
display: noneorvisibility: hidden). - Element is stable (not mid-animation or CSS transition).
- Element receives events (not covered by a modal, overlay, or loading spinner).
- Element is enabled (not
disabledattribute).
2. Advanced Selectors & Locators
Stop using XPath or fragile CSS selectors likediv > span:nth-child(3). These break the moment anyone touches the HTML structure. Playwright’s locator engine encourages selectors that match how a user sees the page, not how a developer built it.
User-Facing Locators (ARIA)
Always prefer these. They survive CSS refactors, component library changes, and even complete frontend rewrites — because they are based on what the user sees and interacts with, not implementation details.Layout Selectors
Find elements relative to others.Shadow DOM
Playwright pierces Shadow DOM by default.page.locator('my-custom-element button') works automatically, even if button is in #shadow-root.
3. The Page Object Model (POM)
For any suite larger than 5 tests, you need the Page Object Model. Without it, you end up with selectors duplicated across dozens of test files — and when a single CSS class or label changes, you are fixing 40 files instead of one. POM centralizes page interactions into reusable classes, giving you a single source of truth for each page’s selectors and actions.Base Page Pattern
Feature Page
Usage in Test
4. Network Control & API Testing
Playwright is a full HTTP client — it can intercept, modify, or fabricate any network request the browser makes. This is incredibly powerful for testing: you can simulate server errors, test loading states, and eliminate backend dependencies entirely.Interception (Mocking)
Don’t wait for your slow backend or wrestle with test data setup. Mock the API responses directly in the browser.API Testing (No Browser)
You can use Playwright as a replacement for Postman/Axios.5. Global Setup & Authentication
Don’t log in before every test. Authentication is the most common bottleneck in E2E test suites — if every test navigates to the login page, fills in credentials, and clicks “Sign In”, you are wasting 3-5 seconds per test. Instead, log in once, save the browser state (cookies + localStorage) to a file, and inject it into every subsequent test.1. Global Setup (global-setup.ts)
2. Config (playwright.config.ts)
6. Visual Regression Testing
Detect pixel-perfect changes by comparing screenshots against approved baselines. This catches subtle CSS regressions that functional tests miss entirely — a button shifting 10px to the right, a font-weight change, or a z-index overlap.npx playwright test --update-snapshots
Practical tip: Visual tests are sensitive to OS-level font rendering differences. Run visual tests in CI (Linux) only, or use Docker containers locally to match the CI environment. Otherwise you will get false positives between your Mac and the CI server.
7. Scaling with CI/CD
Parallelism & Sharding
Playwright tests are inherently parallelizable because each test gets its own Context (isolated browser state). Sharding takes this further by splitting your test files across multiple CI machines. A suite taking 1 hour on one machine can take 10-15 minutes with 4 shards running in parallel. GitHub Actions Sharding Example:npx playwright merge-reports.
8. Common Pitfalls & Debugging
Hydration Errors (React/Next.js)
Hydration Errors (React/Next.js)
- Use
await expect(locator).toBeEnabled()to wait for hydration. - Check for a specific hydration indicator (e.g.,
data-hydrated="true").
Multiple Tabs / New Windows
Multiple Tabs / New Windows
await page.click('#open-new-tab') works, but subsequent commands fail.
Cause: Playwright stays on the old page. It does not automatically switch focus.
Fix:Element Not Visible
Element Not Visible
Timeout 30000ms waiting for locator(...)
Cause: Element is technically in DOM but hidden by CSS display:none or behind a sticky header.
Fix: Use .scrollIntoViewIfNeeded() or check force: true (use sparingly).9. Interview Questions
How does Playwright differ from Selenium architecture?
How does Playwright differ from Selenium architecture?
Explain the difference between Browser, Context, and Page.
Explain the difference between Browser, Context, and Page.
- Browser: The OS process (Expensive). Launched once.
- Context: An isolation layer (Incognito profile). Stores cookies/storage. Created for each test.
- Page: A single tab/window within a context. Running tests in new Contexts (instead of new Browsers) allows Playwright to run hundreds of tests in parallel efficiently.
How do you handle flaky tests in Playwright?
How do you handle flaky tests in Playwright?
- Auto-waiting: Ensure I’m avoiding manual sleeps.
- Web-First Assertions: Use
await expect(loc).toBeVisible()instead ofloc.isVisible(). - Tracing: Enable Trace Viewer on CI to see the snapshot exactly when it failed.
- Retries: Configure
retries: 2in config for known unstable environments.
10. Cheat Sheet
Interview Deep-Dive
Q: Your E2E test suite has a 15% flakiness rate in CI. Walk me through your systematic approach to diagnosing and reducing flaky Playwright tests.
Q: Your E2E test suite has a 15% flakiness rate in CI. Walk me through your systematic approach to diagnosing and reducing flaky Playwright tests.
- Step 1 — Enable Trace Viewer on all CI failures: Configure
trace: 'on-first-retry'inplaywright.config.ts. This captures a DOM snapshot, network log, and screenshot at every action for failed tests. Trace Viewer is the single most valuable debugging tool because it shows you exactly what the page looked like when the assertion failed, not what you imagine it looked like. - Step 2 — Categorize flake sources: In my experience, flaky E2E tests fall into three buckets: (a) timing issues — the test acts before the page is ready, (b) test data pollution — tests depend on shared state (a database row, a user session), and (c) environment instability — CI machines have different performance characteristics than local dev.
- Step 3 — Fix timing issues: Replace any manual
page.waitForTimeout(2000)with web-first assertions:await expect(locator).toBeVisible()instead ofawait locator.isVisible(). The former retries automatically until the timeout; the latter checks once and returns immediately. Also check for hydration races in React/Next.js apps — Playwright can click a button before React attaches the event handler. The fix is waiting for a hydration indicator:await page.locator('[data-hydrated=true]').waitFor(). - Step 4 — Fix data pollution: Each test should get a fresh browser context (Playwright does this by default), but if tests share a backend database, one test’s writes can corrupt another. The fix is either API-level test isolation (each test creates its own user/data via API calls in
beforeEach) or database transactions that roll back after each test. - Step 5 — Add retries as a last resort:
retries: 2in config gives failing tests two more chances. This is a band-aid, not a fix, but it reduces the impact while you address root causes. Track which tests use retries and treat them as tech debt.
await expect(locator).toHaveText('...') web-first assertions for post-action verification.Q: Compare Playwright's architecture with Selenium's. Why does the protocol choice (CDP vs WebDriver) matter in practice?
Q: Compare Playwright's architecture with Selenium's. Why does the protocol choice (CDP vs WebDriver) matter in practice?
- Selenium (WebDriver protocol): Commands travel over HTTP. The test sends a JSON-encoded command to a WebDriver server, which translates it into browser-specific instructions, executes the command, and returns a JSON response. This is a request-response model — the test cannot receive events from the browser unless it polls. Want to know when a network request completes? You poll. Want to intercept a request? You cannot, at least not natively. The HTTP middleman also adds latency per command.
- Playwright (Chrome DevTools Protocol / equivalent): Communication happens over a persistent WebSocket connection. This is bidirectional — the browser can push events to the test (network request started, console message logged, page navigated) without the test asking. This enables features that are architecturally impossible in Selenium: network interception (route and modify requests in-flight), console log capture, auto-waiting (the browser tells Playwright when an element becomes stable), and tracing.
- Practical impact: In Selenium, intercepting a network request to mock an API response requires setting up a proxy server (like BrowserMob Proxy) as a separate process. In Playwright, it is
page.route('**/api/*', handler)— one line, in-process, no external dependencies. Similarly, Selenium’sWebDriverWaitpolls the DOM at intervals (typically 500ms); Playwright’s auto-wait is event-driven, reacting within milliseconds of a change.
route.fetch() followed by route.fulfill() with modified data does this natively. Similarly, capturing browser-level performance metrics (Layout Shift, First Contentful Paint) requires CDP access that Selenium does not provide through its protocol.Q: You are designing a Playwright test suite for a large e-commerce application with authentication, multiple user roles, and complex checkout flows. Walk me through your architecture decisions.
Q: You are designing a Playwright test suite for a large e-commerce application with authentication, multiple user roles, and complex checkout flows. Walk me through your architecture decisions.
- Authentication strategy: Log in once in
global-setup.ts, save the storage state (cookies + localStorage) to JSON files — one per role (admin-state.json,customer-state.json,guest-state.json). Tests declare which role they need via thestorageStateoption. This eliminates the 3-5 second login flow from every test. For tests that specifically test the login flow itself, use a project with nostorageState. - Page Object Model with composition: Create page objects for each page (
LoginPage,ProductPage,CheckoutPage) and component objects for reusable UI elements (NavBar,CartDrawer,SearchModal). Page objects encapsulate selectors and actions; they never contain assertions. Tests compose page objects:const checkout = new CheckoutPage(page); await checkout.addItem('SKU-123'); await checkout.applyPromoCode('SAVE20');. This means when the checkout flow changes, you update one class, not 40 test files. - Test data strategy: Use API calls in
beforeEachto create test-specific data (products, orders, users) rather than relying on seed data. This makes each test independent and parallelizable. For example:const product = await api.createProduct({ name: 'Test Widget', price: 9.99 })at the start of a checkout test. Clean up inafterEachor rely on ephemeral test environments. - Parallelism configuration: Use Playwright’s
projectsto split by browser (Chromium, Firefox, WebKit) and by category (smoke tests, full regression). Smoke tests run on every PR; full regression runs nightly. ConfigurefullyParallel: trueand setworkersto match CI machine cores. - Network mocking boundary: Mock third-party services (payment gateways, email providers, analytics) at the network level using
page.route(). Never mock your own application’s APIs in E2E tests — the whole point is testing the full stack. The exception is simulating error conditions (500 responses, timeout scenarios) that are hard to trigger against a real backend.
const aliceContext = await browser.newContext({ storageState: 'alice-state.json' }); const bobContext = await browser.newContext({ storageState: 'bob-state.json' }). Then alicePage.getByRole('textbox').fill('Hello'); await bobPage.waitForText('Hello'). This runs in a single test, giving you deterministic control over the interaction sequence without needing separate test processes.Q: When would you choose Playwright's API testing capabilities over a dedicated tool like Postman or a unit-test-level HTTP client like supertest?
Q: When would you choose Playwright's API testing capabilities over a dedicated tool like Postman or a unit-test-level HTTP client like supertest?
request fixture is surprisingly powerful for API testing, but it occupies a specific niche.- Choose Playwright API testing when: You need API tests that share authentication state with your E2E tests. Since Playwright’s
requestcontext can use the samestorageStateas browser tests, you get cookie-based auth for free. This is ideal for testing authenticated API endpoints alongside the UI that calls them — same test suite, same auth setup, no duplication. It is also the right choice when you want to set up test data via API before running a browser test, all within a single Playwright project. - Choose supertest when: You are testing a Node.js server in isolation, especially during development. Supertest spins up your Express/Fastify app in-process, so there is no network overhead and you get sub-millisecond response times. It is a unit/integration testing tool, not an E2E tool.
- Choose Postman/Insomnia when: You need a manual exploration tool, team-shared collections, or documentation-as-tests for an API contract. Postman excels at developer workflow and API documentation, not CI automation.
request.post('/api/orders', { data: orderPayload }) to create an order via the real API, then use page.route('**/api/shipping', handler) to mock only the shipping calculation response, and finally navigate the browser to the order page to verify the UI renders correctly with the mocked shipping cost. This gives you real data flow for most of the stack while controlling specific edge cases at the network boundary.