⚡ Local SDET practice ground — Selenium · Playwright · Appium · API testingnavtutorial.com ↗
Playwright
All guides ← Hub
Auto-waiting · JS/TS & Python

Playwright guide

Web-first assertions and getByTestId against this hub. Serve it on localhost:8000 first.

1 · Install

npm init playwright@latest
# choose TypeScript or JavaScript, adds tests/ + playwright.config
npx playwright test        # run
npx playwright test --ui   # watch mode
pip install pytest-playwright
playwright install         # download browsers
pytest

2 · Point getByTestId at data-test

Playwright's default test-id attribute is data-testid; this hub uses data-test. Set it once, then use the clean getByTestId() API.

// playwright.config.js
module.exports = {
  use: {
    baseURL: 'http://localhost:8000',
    testIdAttribute: 'data-test',   // <- point at our attribute
  },
};
# conftest.py
import pytest
from playwright.sync_api import expect

@pytest.fixture(autouse=True)
def _testid():
    # applies to expect + page.get_by_test_id
    from playwright.sync_api import Locator  # noqa
    import playwright.sync_api as pw
    pw.Playwright.selectors = getattr(pw.Playwright, 'selectors', None)
    # simplest: just use css selectors [data-test='...'] in Python

3 · Login & checkout (CartPilot)

const { test, expect } = require('@playwright/test');

test('login and reach inventory', async ({ page }) => {
  await page.goto('/swaglabs/index.html');
  await page.getByTestId('username').fill('standard_user');
  await page.getByTestId('password').fill('Pass@123');
  await page.getByTestId('login-button').click();

  // web-first assertion auto-waits for the grid
  await expect(page.locator("[data-test^='item-']").first()).toBeVisible();

  // add the first product, open cart, checkout
  await page.locator("[data-test^='add-']").first().click();
  await expect(page.getByTestId('cart-badge')).toHaveText('1');
  await page.getByTestId('cart-icon').click();
  await page.getByTestId('checkout').click();
  await page.getByTestId('first-name').fill('Ava');
  await page.getByTestId('last-name').fill('Tester');
  await page.getByTestId('postal-code').fill('12345');
  await page.getByTestId('continue').click();
  await page.getByTestId('finish').click();
  await expect(page.getByTestId('complete-header')).toContainText('Thank you');
});
from playwright.sync_api import Page, expect

def test_login_checkout(page: Page):
    page.goto('http://localhost:8000/swaglabs/index.html')
    page.locator("[data-test='username']").fill('standard_user')
    page.locator("[data-test='password']").fill('Pass@123')
    page.locator("[data-test='login-button']").click()

    expect(page.locator("[data-test^='item-']").first).to_be_visible()
    page.locator("[data-test^='add-']").first.click()
    expect(page.locator("[data-test='cart-badge']")).to_have_text('1')

4 · Auto-waiting beats manual sleeps

The Testbed → #dynamic-loading. No explicit wait needed — the assertion retries until it passes.

await page.goto('/internet/index.html#dynamic-loading');
await page.getByTestId('start').click();
await expect(page.getByTestId('finish')).toBeVisible();   // retries automatically

5 · Dialogs (alert / confirm / prompt)

page.on('dialog', dialog => {
  console.log(dialog.type(), dialog.message());
  dialog.accept();            // or dialog.dismiss(); prompt: dialog.accept('text')
});
await page.getByTestId('js-confirm').click();

6 · Frames

Demo Elements → #frames. Use a frame locator — no manual context switching.

await page.goto('/elements/index.html#frames');
const frame = page.frameLocator("[data-test='frame1']");
await expect(frame.locator("[data-test='frame-heading']")).toBeVisible();

7 · Drag & drop

await page.goto('/elements/index.html#droppable');
await page.getByTestId('drag-me').dragTo(page.getByTestId('drop-here'));
await expect(page.getByTestId('output')).toContainText('Dropped');
Why Playwright feels different. Locators are lazy and every action/assertion auto-waits for the element to be actionable, so the flaky sleep patterns you fight in Selenium mostly disappear. The hub's random-delay challenges are a good place to feel that.

Selector map (used above)

getByTestId('username')CartPilot login username
[data-test^='add-']CartPilot add-to-cart buttons
getByTestId('cart-badge')Cart count badge
getByTestId('finish')Checkout finish / dynamic-loading result
getByTestId('js-confirm')Confirm dialog trigger
frameLocator([data-test='frame1'])Demo Elements iframe
getByTestId('drag-me' / 'drop-here')Demo Elements droppable