QA PRACTICE•FRAMEWORK

Playwright · Selenium · Cypress

Framework

Compare three automation frameworks on the same E2E flow, then learn a maintainable Playwright Page Object Model structure through real QA examples.

Same check · three tools

Choose your automation framework

Learn the same stable locator and assertion pattern in Playwright, Selenium and Cypress.

Playwright
playwright-ecommerce.spec
ts
1
2
3
4
5
6
7
8
9
import { test, expect } from '@playwright/test';

test('user can add a product to the cart', async ({ page }) => {
  await page.goto('https://qapractice.hakdogan.com/ecommerce');
  await page.getByTestId('product-search').fill('Keyboard');
  await page.getByRole('button', { name: /add keyboard to cart/i }).click();

  await expect(page.getByTestId('cart-count')).toHaveText('1');
});

Live browser runner

Write and run a focused check

Playwright-style commands execute against the practice target below. Try changing the locator or expected text and read the failure log.

Browser-side
browser-check.spec.tseditable

Practice target

Profile settings

Not run yet

Run the example to see command results.

Lesson 1 of 4 · Beginner

Automate the Inputs test

You already tested this form manually in Practice. Now learn how the same check is organised in Playwright, Selenium and Cypress.

Practice first

Goal

Connect an acceptance criterion to code.

Files

Spec + Page Object + fixture.

Time

About 15 minutes · no account.

Lesson path

Progress

1 of 7 steps

Show in

inputs.spec.ts

Test: what to verify

inputs.spec.ts
ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { test, expect } from '@playwright/test';
import { InputsPage } from './inputs.page';

test('invalid email shows a field error', async ({ page }) => {
  const inputs = new InputsPage(page);
  await inputs.goto();
  await inputs.fillEmail('not-an-email');
  await inputs.fillPassword('Pass-w0rd!');
  await inputs.submit();

  await expect(inputs.emailError).toHaveText(
    'Please enter a valid email address.',
  );
});

Run it

$ npx playwright test inputs.spec.ts
Run the command locally, then record what happened.

Acceptance criteria → assertions

  • AC-002 Invalid email is rejected → emailError.toHaveText()
  • AC-003 Error appears beside the field → getByTestId('email-error')
  • AC-006 Focus order is logical → add the keyboard step next

Common mistakes

  • Do not put raw selectors in the spec.
  • Do not use waitForTimeout for validation.
  • Keep assertions in the test so the expected behaviour is visible.

Your turn

Write the positive Inputs test, then continue with Buttons.

Start the challenge →

The framework

How the framework fits together

scalable by design

Code layers

Test → Page Object → Component

The test describes what should happen. The Page Object owns where things are and how a user interacts with them.

Run layers

Config → Runner → CI

Config sets the rules, Playwright runs the test, and CI repeats it for every change.

  • tests/ → describe-blocks tagged @smoke / @regression.
  • pages/ → Page Object Model for every screen with role-based locators.
  • components/ → reusable component objects (header, toast, modal).
  • utils/auth.ts → shared signInAs for user and admin journeys (storage promotion included).
  • utils/ → cross-cutting helpers: auth, mocks, waits, retries, downloads.
  • data/ → pure data builders (faker-powered) — no I/O, no shared state.
  • tsconfig.json → strict TypeScript, noEmit check-only for the suite.
  • playwright.config.ts → projects, retries, traces, screenshots, video, GREP.
  • .github/ → 4-shard matrix, nightly cron, artifact upload on failure.
+All project files · download the complete starter project

tests/login.spec.ts

Auth happy path + client-side validation negative cases.

tests/login.spec.ts
ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { test, expect } from 'playwright/test';
import { LoginPage } from '../pages/login.page';

test.describe('Authentication @smoke', () => {
  test('demo credentials reach Bug Playground', async ({ page }) => {
    const login = new LoginPage(page);
    await login.goto();
    await login.useDemoAccount();
    await login.submit();
    await expect(page).toHaveURL(/\/bug-playground\/\/?$/);
  });


  test('invalid email and short password show validation messages @regression', async ({
    page,
  }) => {
    const login = new LoginPage(page);
    await login.goto();
    await login.signIn('not-an-email', 'short');
    await expect(login.emailFormatError).toHaveText(/Use a valid email address/i);
    await expect(login.passwordLengthError).toHaveText(
      /Password must be at least 8/i,
    );
  });
});

Run locally

Setup

  • unzip/ → extract qapractice.hakdogan.com.zip.
  • cd/ → enter qapractice.hakdogan.com/.
  • install/ → run npm install.
  • browsers/ → run npx playwright install.
  • test/ → run npx playwright test.

Usage

  • report/ → npx playwright show-report.
  • smoke/ → npx playwright test --grep @smoke.
  • regression/ → npx playwright test --grep @regression.
  • debug/ → npx playwright test --debug.
  • node/ → Node.js v18+ (v20 recommended).