On this page10 sections
Playwright — Automate a Login Flow
Write a Playwright + TypeScript test suite for a login page: happy path, wrong credentials, lockout, session persistence, and a Page Object Model structure.
Role
Automation QA engineer
Difficulty
IntermediateTime limit
90–120 min
Category
ui automation
PlaywrightTypeScriptNode.js 18+
Scenario
You have been given access to the Sauce Demo application (https://www.saucedemo.com) — a purpose-built demo site for automation practice. Your task is to write a Playwright + TypeScript test suite for the login flow that a real team could drop into a CI pipeline on day one. You must structure the code using the Page Object Model; the suite must pass `npx tsc --noEmit` with no type errors.
Requirements
- 1.Create a LoginPage class with locators and action methods (no locators or clicks inside test files)
- 2.Write at least 5 test cases: standard_user login (success), locked_out_user (error message), wrong password (error message), empty email (validation), empty password (validation)
- 3.Assert the exact error message text for failure cases — not just that an error element exists
- 4.After a successful login, assert that the URL changes to /inventory.html or a named element confirming the user is logged in
- 5.Tests must run headlessly with `npx playwright test` and produce a passing HTML report
- 6.The suite must have zero TypeScript errors: `npx tsc --noEmit` must exit 0
- 7.Store the base URL and test credentials in a config object or .env — do not hardcode them in test files
Starter data
- ›Target app: https://www.saucedemo.com
- ›standard_user / secret_sauce — logs in successfully
- ›locked_out_user / secret_sauce — shows 'Sorry, this user has been locked out.'
- ›problem_user / secret_sauce — logs in but with broken UI elements (useful for extension task)
- ›Login page URL: https://www.saucedemo.com/
- ›Post-login URL: https://www.saucedemo.com/inventory.html
- ›Error element selector (for reference, prefer role-based): [data-test='error']
Expected deliverables
- ✓A TypeScript Playwright project that can be cloned and run with `npm install && npx playwright test`
- ✓A LoginPage class in a pages/ or page-objects/ directory
- ✓A login.spec.ts file with at least 5 test cases
- ✓A playwright.config.ts configuring at minimum: baseURL, headless mode, HTML reporter
- ✓Output of `npx playwright test` showing all tests passing
- ✓Output of `npx tsc --noEmit` showing zero errors
Evaluation rubric
| Dimension | What reviewers look for |
|---|---|
| Page Object Model correctness | Are all locators and browser interactions encapsulated in LoginPage? Do test files contain only assertions and high-level action calls (loginPage.login(email, pass))? Are locators using role-based selectors (getByRole, getByLabel) rather than CSS classes? |
| Test case quality | Are assertions specific? Does the successful login test assert the URL or a page-level element, not just 'no error was shown'? Do error-case tests assert the exact message text? |
| TypeScript quality | Is strict mode respected? Are there any implicit `any` types? Are Playwright types used correctly (Locator vs ElementHandle, Page vs Browser)? |
| Playwright idioms | Does the suite use auto-wait locators rather than manual waitForTimeout() calls? Is each test independent (no shared page state from a previous test)? Are fixtures used if multiple tests share setup? |
| Configuration | Is the base URL set in playwright.config.ts rather than hardcoded in test files? Are credentials sourced from environment variables or a config object? |
| Runnability | Does `npm install && npx playwright install && npx playwright test` succeed without manual intervention? Is there a README with setup instructions? |
Sample solution outline
- ›playwright.config.ts: baseURL = process.env.BASE_URL || 'https://www.saucedemo.com', use: { headless: true }, reporter: 'html'
- ›pages/LoginPage.ts: constructor(private page: Page); usernameInput = page.getByLabel('Username'); passwordInput = page.getByLabel('Password'); loginButton = page.getByRole('button', { name: 'Login' }); errorMessage = page.getByTestId('error'); async login(u, p) { await this.usernameInput.fill(u); await this.passwordInput.fill(p); await this.loginButton.click(); }
- ›tests/login.spec.ts: import { test, expect } from '@playwright/test'; import { LoginPage } from '../pages/LoginPage'; test.beforeEach(async ({ page }) => { await page.goto('/'); })
- ›TC-01: new LoginPage(page).login('standard_user', 'secret_sauce'); expect(page).toHaveURL('/inventory.html');
- ›TC-02: LoginPage.login('locked_out_user', 'secret_sauce'); expect(loginPage.errorMessage).toHaveText('Epic sadface: Sorry, this user has been locked out.');
- ›TC-03: LoginPage.login('standard_user', 'wrong_password'); expect(loginPage.errorMessage).toContainText('Epic sadface: Username and password do not match');
- ›TC-04: LoginPage.login('', 'secret_sauce'); expect(loginPage.errorMessage).toHaveText('Epic sadface: Username is required');
- ›TC-05: LoginPage.login('standard_user', ''); expect(loginPage.errorMessage).toHaveText('Epic sadface: Password is required');
Common mistakes
- Putting `page.goto()`, `page.fill()`, or `page.click()` directly inside test files instead of in the Page Object — this is the most common structural error and immediately signals that the candidate doesn't understand POM
- Using `page.waitForTimeout(2000)` to wait for navigation instead of `await expect(page).toHaveURL(...)` — Playwright auto-waits; explicit timeouts create flakiness
- Asserting `expect(errorDiv).toBeVisible()` without checking the text — two different error conditions could both show a visible error, making the assertion too weak
- Hardcoding `https://www.saucedemo.com` in test files — the base URL must come from playwright.config.ts so it is overridable per environment
- A single test that runs all 5 scenarios sequentially — tests must be independent; a single `test()` block with 5 sets of actions is not 5 tests
- No `await` on Playwright actions — TypeScript and the Playwright ESLint plugin catch this, but missing awaits cause tests to pass spuriously
Submission checklist
- Project has a package.json, playwright.config.ts, and a pages/ directory
- LoginPage class is in pages/ with all locators and no test assertions
- login.spec.ts has at least 5 independent test cases (separate test() calls)
- All tests pass: `npx playwright test` exits 0
- No type errors: `npx tsc --noEmit` exits 0
- Base URL and credentials are not hardcoded in test files
- README explains how to install and run
Extension ideas
- +Add a test for problem_user login and assert that at least one product image on /inventory.html is broken (src contains 'sl-404')
- +Refactor to use Playwright fixtures: a `loggedInPage` fixture that skips the login UI by using storageState
- +Add a data-driven test using test.each() covering all 4 user types and their expected post-login state