Resillion
Internal cheat sheet
Back to Cheat Sheets Hub

Playwright + TypeScript Cheat Sheet

A compact reference for writing stable browser tests with Playwright and TypeScript. Focused on practical patterns Resillion engineers can reuse across selectors, assertions, fixtures, waits, auth reuse, API checks, and flaky-test reduction.

Playwright essentials

High-value reminders for stable, readable end-to-end tests.

11 sections
1

Setup and Config

Keep the project lean: use Playwright's test runner, TypeScript, and a config that matches how the app is deployed.

npx playwright test tsconfig.json playwright.config.ts
npm init playwright@latest // playwright.config.ts import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testDir: './tests', retries: process.env.CI ? 2 : 0, use: { baseURL: 'http://localhost:3000', trace: 'on-first-retry', screenshot: 'only-on-failure', }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, ], });
2

Locators

Prefer resilient, user-facing selectors. Reach for `getByRole` first, then labels, text, placeholders, and test ids.

1
Best default
`page.getByRole('button', { name: 'Save' })`
2
Forms
`page.getByLabel('Email')` or `page.getByPlaceholder('Search')`
3
Fallback
`page.getByTestId('checkout-submit')` when UI text is unstable.
Avoid brittle CSS chains and XPath unless there is no better option.
3

Assertions

Let Playwright do the waiting for you. Assert on visible outcomes rather than arbitrary timing.

Good
await expect(page.getByRole('heading', { name: 'Orders' })).toBeVisible(); await expect(page).toHaveURL(/\/orders$/); await expect(page.getByText('Saved')).toBeVisible();
Avoid
await page.waitForTimeout(3000); await expect(await page.title()).toContain('Orders');
4

Fixtures

Use fixtures to keep setup reusable and test data predictable.

import { test as base, expect } from '@playwright/test'; type Fixtures = { userName: string }; export const test = base.extend({ userName: async ({}, use) => { await use('qa.user@example.com'); }, }); test('profile', async ({ page, userName }) => { await page.goto('/profile'); await expect(page.getByText(userName)).toBeVisible(); });
5

Waits and Synchronization

Use Playwright's auto-waiting first. Add explicit waits only for real state changes.

A
Prefer assertions
`await expect(locator).toBeVisible()` usually replaces a manual wait.
B
Wait on signals
Use `waitForURL`, `waitForResponse`, or `waitForEvent` when the app truly needs it.
C
Avoid network idle as a default
It can be unreliable in modern apps with background traffic.
6

API Testing

Use the API client to set up data, verify backend state, or speed up preconditions.

test('create order via API', async ({ request }) => { const response = await request.post('/api/orders', { data: { sku: 'ABC123', qty: 2 }, }); expect(response.ok()).toBeTruthy(); const body = await response.json(); expect(body.status).toBe('created'); });
7

Auth and State Reuse

Avoid logging in through the UI for every test when a saved state can safely speed things up.

1
Save once
Run a setup test, then write `storageState` to reuse the session.
2
Share carefully
Reuse auth only for tests that need the same user role and permissions.
// playwright.config.ts use: { storageState: 'playwright/.auth/user.json', }
8

Debugging

When a test fails, trace and screenshots usually tell the story faster than console logs alone.

PWDEBUG=1 --headed trace viewer screenshots
npx playwright test --headed npx playwright test --debug npx playwright show-trace trace.zip
9

Downloads and Uploads

Use the built-in file APIs instead of driving the native dialog when you can.

Upload
await page.getByLabel('Attachment').setInputFiles('fixtures/sample.pdf');
Download
const [download] = await Promise.all([ page.waitForEvent('download'), page.getByRole('button', { name: 'Export' }).click(), ]);
10

Flaky-Test Reduction

1
Use stable locators
Prefer roles and labels over layout-driven selectors.
2
Isolate state
Avoid shared data and make each test set up its own requirements.
3
Retry only as a safety net
Retries help catch transient issues, but they should not hide a broken test.
11

Useful Snippets

Use case Snippet
Navigate await page.goto('/dashboard');
Click a button await page.getByRole('button', { name: 'Save' }).click();
Check text await expect(page.getByText('Done')).toBeVisible();
Wait for API await page.waitForResponse(res => res.url().includes('/api/orders') && res.ok());
Screenshot on failure use: { screenshot: 'only-on-failure' }
If a test needs `waitForTimeout`, it usually means the assertion or locator can be improved.