What is E2E · Tools landscape · Playwright setup · Selectors · First test
→ next slide | ESC overview
1. The testing pyramid — where E2E fits, trade-offs
2. Tools landscape — Playwright vs Cypress vs Selenium
3. Playwright setup — install, config, project structure
4. Selectors & auto-wait — writing stable tests
5. First test together — assertions, UI mode, codegen
E2E tests drive a real browser through a real user journey — from the first click to the final confirmation.
E2E tests are the most realistic tests you can write — and the most expensive.
Cover the 3–5 flows that, if broken, would hurt users the most.
| Feature | Playwright | Cypress | Selenium |
|---|---|---|---|
| Browser support | Chromium, Firefox, WebKit | Chrome, Firefox, Edge | All + Safari |
| Language support | JS/TS, Python, Java, C# | JS/TS only | JS, Python, Java, C#, Ruby |
| Auto-wait | Built in — no sleeps needed | Built in | Manual — you manage waits |
| Parallel execution | Native, cross-browser | Paid tier for cross-browser | Via Grid setup |
| Setup complexity | One command | One command | Drivers, env config |
| Debugging | Trace viewer, UI mode, video | Time-travel in dashboard | Screenshots only |
| Industry trend (2024–25) | Growing fast — new standard | Established, slowing | Legacy — still common in Java |
Playwright is the current industry favourite for new projects. Selenium remains dominant in Java enterprise shops.
# Install Playwright — one command does everything
npm init playwright@latest
# Choose:
# ✔ TypeScript or JavaScript? → TypeScript (recommended)
# ✔ Where to put tests? → tests/
# ✔ Add GitHub Actions? → Yes
# ✔ Install browsers? → Yes
# Run all tests (headless)
npx playwright test
# Run with interactive UI mode (recommended for development)
npx playwright test --ui
# Run in visible browser window
npx playwright test --headed
# Record test actions in a browser → generates test code
npx playwright codegen https://www.kriso.ee
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30_000, // 30s per test
retries: 1, // retry flaky tests once
reporter: 'html', // HTML report at end
use: {
baseURL: 'https://www.kriso.ee',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
trace: 'retain-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
],
});
--uiPlaywright UI mode gives you a visual test runner — see exactly what the browser does at each step.
Use--uiwhile writing tests. Switch to headlessnpx playwright testfor CI.
npx playwright codegen https://www.kriso.ee
What happens:
await page.goto('https://www.kriso.ee/');
await page.getByRole('searchbox').click();
await page.getByRole('searchbox').fill('Python');
await page.getByRole('button', { name: 'Otsi' }).click();
await expect(page.getByRole('heading'))
.toContainText('Python');
.click() before .fill(). The selector you choose determines how fragile your test is. Prefer semantic selectors that survive styling changes.
// CSS class — breaks when design changes
page.locator('.btn-primary-lg-v2')
// XPath — brittle, hard to read
page.locator('//div[3]/ul/li[1]/a')
// Position-based — meaningless
page.locator('nth=0')
// By ARIA role + name (best)
page.getByRole('button', { name: 'Sign In' })
// By visible text
page.getByText('Forgot password?')
// By form placeholder
page.getByPlaceholder('Email address')
// By test ID (teams control this)
page.getByTestId('submit-btn')
// By label text
page.getByLabel('Password')
Priority:getByRole→getByLabel→getByPlaceholder→getByText→getByTestId
import { test, expect } from '@playwright/test';
// test.describe groups related tests
test.describe('Playwright Demo Site', () => {
test('page has correct title', async ({ page }) => {
await page.goto('https://playwright.dev');
await expect(page).toHaveTitle(/Playwright/);
});
test('get started link navigates to docs', async ({ page }) => {
await page.goto('https://playwright.dev');
// Find the "Get started" link by its visible text
await page.getByRole('link', { name: 'Get started' }).click();
// The URL should change to /docs/intro
await expect(page).toHaveURL(/.*intro/);
});
test('search works', async ({ page }) => {
await page.goto('https://playwright.dev');
await page.getByRole('button', { name: 'Search' }).click();
await page.getByRole('searchbox').fill('assertions');
// Results should appear
await expect(page.getByRole('option').first()).toBeVisible();
});
});
// ── PAGE ───────────────────────────────
// URL exact or regex
await expect(page).toHaveURL('/dashboard');
await expect(page).toHaveURL(/checkout/);
// Page title
await expect(page).toHaveTitle(/Kriso/i);
// ── ELEMENT VISIBILITY ─────────────────
await expect(locator).toBeVisible();
await expect(locator).toBeHidden();
await expect(locator).toBeAttached(); // in DOM
// ── ELEMENT STATE ──────────────────────
await expect(locator).toBeEnabled();
await expect(locator).toBeDisabled();
await expect(locator).toBeChecked();
await expect(locator).toBeFocused();
// ── CONTENT ────────────────────────────
// Exact text
await expect(locator).toHaveText('Hello');
// Contains text
await expect(locator).toContainText('Hell');
// Attribute value
await expect(locator).toHaveAttribute('href', '/home');
// CSS value
await expect(locator).toHaveCSS('color', 'red');
// ── COUNTS ─────────────────────────────
await expect(locator).toHaveCount(5);
// ── NEGATION ───────────────────────────
// Add .not before any assertion
await expect(locator).not.toBeVisible();
await expect(page).not.toHaveURL('/error');
// ── SOFT ASSERTIONS ────────────────────
// Don't stop on failure — collect all errors
await expect.soft(locator).toHaveText('A');
await expect.soft(locator2).toHaveText('B');
The next 3 lessons all build toward the same assignment.
Fork the repo and start exploring now — we'll walk through the solutions together week by week.
📁 github.com/tanjaq/playwright-kriso
tests/kriso.spec.tstests/kriso-pom.spec.ts using page classes.
Start now:
Fork the repo, install dependencies, explore Kriso manually.
Run npx playwright codegen https://www.kriso.ee