level 02 / first-test
Your First Playwright Test
Install Playwright, understand playwright.config.ts, and write a test that runs in Chromium, Firefox, and WebKit simultaneously.
practice site: SauceDemo
Installation
# In an existing Node.js project
npm init playwright@latest
# Scaffolds: playwright.config.ts, tests/example.spec.ts,
# tests-examples/, .github/workflows/playwright.yml
# Installs: @playwright/test + browser binaries
# Install browsers separately (if already have @playwright/test)
npx playwright install
# Install specific browser
npx playwright install chromium
npx playwright test
Run all tests
npx playwright test --headed
Run with visible browser
npx playwright test --ui
Open Playwright UI mode (interactive debugger)
npx playwright test login.spec.ts
Run one file
npx playwright test --grep "login"
Run tests matching a pattern
npx playwright show-report
Open last HTML report
npx playwright codegen https://saucedemo.com
Record interactions as test code
The anatomy of a Playwright test
import { test, expect } from '@playwright/test';
test('page title matches', async ({ page }) => {
// Arrange: navigate to the page
await page.goto('https://www.saucedemo.com');
// Act: (none — reading state only)
// Assert: check the title
await expect(page).toHaveTitle('Swag Labs');
});
test()— registers a test case. The callback receives a fixture object.{ page }— thepagefixture is a PlaywrightPageinstance, auto-created per test.async/await— every browser interaction is asynchronous.expect()— wraps a locator or page value with auto-retry assertions.
playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
// Directory where test files live
testDir: './tests',
// Run tests in parallel (each test gets its own browser context)
fullyParallel: true,
// Fail the build on CI if test.only is left in source
forbidOnly: !!process.env.CI,
// Retry failed tests on CI
retries: process.env.CI ? 2 : 0,
// Reporter: 'html' generates a browsable report
reporter: 'html',
// Shared settings for all projects
use: {
baseURL: 'https://www.saucedemo.com',
trace: 'on-first-retry', // record trace on first retry
},
// Run across multiple browsers
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
});
Your first real test
import { test, expect } from '@playwright/test';
test.describe('SauceDemo login', () => {
test('standard user can log in', async ({ page }) => {
await page.goto('/');
await page.getByPlaceholder('Username').fill('standard_user');
await page.getByPlaceholder('Password').fill('secret_sauce');
await page.getByRole('button', { name: 'Login' }).click();
await expect(page.getByText('Products')).toBeVisible();
await expect(page).toHaveURL(/inventory/);
});
test('locked out user sees error', async ({ page }) => {
await page.goto('/');
await page.getByPlaceholder('Username').fill('locked_out_user');
await page.getByPlaceholder('Password').fill('secret_sauce');
await page.getByRole('button', { name: 'Login' }).click();
await expect(page.getByText('Sorry, this user has been locked out')).toBeVisible();
});
});import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
reporter: 'html',
use: {
baseURL: 'https://www.saucedemo.com',
trace: 'on-first-retry',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
],
});Key insight: Use
baseURL in playwright.config.ts so every page.goto('/') resolves to the right environment. Switch environments by changing one config value, not every test.Running and debugging
# Run with UI mode (shows timeline, trace, DOM snapshot)
npx playwright test --ui
# Run in debug mode (step through test line by line)
npx playwright test --debug login.spec.ts
# Show the HTML report from the last run
npx playwright show-report