QA Regression Testing
Build and run automated regression tests using Playwright. Each test is a reusable skill that can be composed into full test suites.
Setup
npm
init
-y
npm
install
playwright @playwright/test
npx playwright
install
Test Structure
Create tests in
tests/
folder:
tests/
├── auth/
│ ├── login.spec.ts
│ └── logout.spec.ts
├── dashboard/
│ └── load.spec.ts
├── users/
│ ├── create.spec.ts
│ └── delete.spec.ts
└── regression.spec.ts # Full suite
Common Test Skills
Login Test
// tests/auth/login.spec.ts
import
{
test
,
expect
}
from
'@playwright/test'
;
test
.
describe
(
'Login Flow'
,
(
)
=>
{
test
(
'should login with valid credentials'
,
async
(
{
page
}
)
=>
{
await
page
.
goto
(
'/login'
)
;
await
page
.
fill
(
'[data-testid="email"]'
,
process
.
env
.
TEST_EMAIL
!
)
;
await
page
.
fill
(
'[data-testid="password"]'
,
process
.
env
.
TEST_PASSWORD
!
)
;
await
page
.
click
(
'[data-testid="submit"]'
)
;
// Verify redirect to dashboard
await
expect
(
page
)
.
toHaveURL
(
/
dashboard
/
)
;
await
expect
(
page
.
locator
(
'[data-testid="user-menu"]'
)
)
.
toBeVisible
(
)
;
}
)
;
test
(
'should show error for invalid credentials'
,
async
(
{
page
}
)
=>
{
await
page
.
goto
(
'/login'
)
;
await
page
.
fill
(
'[data-testid="email"]'
,
'wrong@example.com'
)
;
await
page
.
fill
(
'[data-testid="password"]'
,
'wrongpassword'
)
;
await
page
.
click
(
'[data-testid="submit"]'
)
;
await
expect
(
page
.
locator
(
'[data-testid="error-message"]'
)
)
.
toBeVisible
(
)
;
}
)
;
}
)
;
Dashboard Load Test
// tests/dashboard/load.spec.ts
import
{
test
,
expect
}
from
'@playwright/test'
;
import
{
login
}
from
'../helpers/auth'
;
test
.
describe
(
'Dashboard'
,
(
)
=>
{
test
.
beforeEach
(
async
(
{
page
}
)
=>
{
await
login
(
page
)
;
}
)
;
test
(
'should load dashboard within 3 seconds'
,
async
(
{
page
}
)
=>
{
const
start
=
Date
.
now
(
)
;
await
page
.
goto
(
'/dashboard'
)
;
await
page
.
waitForSelector
(
'[data-testid="dashboard-content"]'
)
;
const
loadTime
=
Date
.
now
(
)
-
start
;
expect
(
loadTime
)
.
toBeLessThan
(
3000
)
;
}
)
;
test
(
'should display all widgets'
,
async
(
{
page
}
)
=>
{
await
page
.
goto
(
'/dashboard'
)
;
await
expect
(
page
.
locator
(
'[data-testid="stats-widget"]'
)
)
.
toBeVisible
(
)
;
await
expect
(
page
.
locator
(
'[data-testid="chart-widget"]'
)
)
.
toBeVisible
(
)
;
await
expect
(
page
.
locator
(
'[data-testid="activity-widget"]'
)
)
.
toBeVisible
(
)
;
}
)
;
test
(
'should refresh data on button click'
,
async
(
{
page
}
)
=>
{
await
page
.
goto
(
'/dashboard'
)
;
const
initialValue
=
await
page
.
locator
(
'[data-testid="last-updated"]'
)
.
textContent
(
)
;
await
page
.
click
(
'[data-testid="refresh-button"]'
)
;
await
page
.
waitForTimeout
(
1000
)
;
const
newValue
=
await
page
.
locator
(
'[data-testid="last-updated"]'
)
.
textContent
(
)
;
expect
(
newValue
)
.
not
.
toBe
(
initialValue
)
;
}
)
;
}
)
;
Create User Test
// tests/users/create.spec.ts
import
{
test
,
expect
}
from
'@playwright/test'
;
import
{
login
}
from
'../helpers/auth'
;
import
{
generateTestUser
,
deleteTestUser
}
from
'../helpers/users'
;
test
.
describe
(
'User Creation'
,
(
)
=>
{
let
testUser
:
{
email
:
string
;
name
:
string
}
;
test
.
beforeEach
(
async
(
{
page
}
)
=>
{
await
login
(
page
)
;
testUser
=
generateTestUser
(
)
;
}
)
;
test
.
afterEach
(
async
(
)
=>
{
// Cleanup
await
deleteTestUser
(
testUser
.
email
)
;
}
)
;
test
(
'should create new user successfully'
,
async
(
{
page
}
)
=>
{
await
page
.
goto
(
'/users/new'
)
;
await
page
.
fill
(
'[data-testid="user-name"]'
,
testUser
.
name
)
;
await
page
.
fill
(
'[data-testid="user-email"]'
,
testUser
.
email
)
;
await
page
.
selectOption
(
'[data-testid="user-role"]'
,
'member'
)
;
await
page
.
click
(
'[data-testid="create-user-btn"]'
)
;
// Verify success
await
expect
(
page
.
locator
(
'[data-testid="success-toast"]'
)
)
.
toBeVisible
(
)
;
await
expect
(
page
)
.
toHaveURL
(
/
users
/
)
;
// Verify user appears in list
await
expect
(
page
.
locator
(
text=
${
testUser
.
email
}
)
)
.
toBeVisible
(
)
;
}
)
;
test
(
'should validate required fields'
,
async
(
{
page
}
)
=>
{
await
page
.
goto
(
'/users/new'
)
;
await
page
.
click
(
'[data-testid="create-user-btn"]'
)
;
await
expect
(
page
.
locator
(
'[data-testid="name-error"]'
)
)
.
toBeVisible
(
)
;
await
expect
(
page
.
locator
(
'[data-testid="email-error"]'
)
)
.
toBeVisible
(
)
;
}
)
;
}
)
;
Shared Helpers
// tests/helpers/auth.ts
import
{
Page
}
from
'@playwright/test'
;
export
async
function
login
(
page
:
Page
)
{
await
page
.
goto
(
'/login'
)
;
await
page
.
fill
(
'[data-testid="email"]'
,
process
.
env
.
TEST_EMAIL
!
)
;
await
page
.
fill
(
'[data-testid="password"]'
,
process
.
env
.
TEST_PASSWORD
!
)
;
await
page
.
click
(
'[data-testid="submit"]'
)
;
await
page
.
waitForURL
(
/
dashboard
/
)
;
}
export
async
function
logout
(
page
:
Page
)
{
await
page
.
click
(
'[data-testid="user-menu"]'
)
;
await
page
.
click
(
'[data-testid="logout"]'
)
;
await
page
.
waitForURL
(
/
login
/
)
;
}
// tests/helpers/users.ts
export
function
generateTestUser
(
)
{
const
id
=
Date
.
now
(
)
;
return
{
name
:
Test User
${
id
}
,
email
:
test-
${
id
}
@example.com
,
}
;
}
export
async
function
deleteTestUser
(
email
:
string
)
{
// API call to cleanup test user
await
fetch
(
${
process
.
env
.
API_URL
}
/admin/users
,
{
method
:
'DELETE'
,
headers
:
{
'Authorization'
:
Bearer
${
process
.
env
.
ADMIN_TOKEN
}
,
'Content-Type'
:
'application/json'
,
}
,
body
:
JSON
.
stringify
(
{
email
}
)
,
}
)
;
}
Full Regression Suite
// tests/regression.spec.ts
import
{
test
}
from
'@playwright/test'
;
// Import all test suites
import
'./auth/login.spec'
;
import
'./auth/logout.spec'
;
import
'./dashboard/load.spec'
;
import
'./users/create.spec'
;
import
'./users/delete.spec'
;
test
.
describe
(
'Full Regression Suite'
,
(
)
=>
{
// Tests run in order defined above
}
)
;
Playwright Config
// playwright.config.ts
import
{
defineConfig
,
devices
}
from
'@playwright/test'
;
export
default
defineConfig
(
{
testDir
:
'./tests'
,
fullyParallel
:
true
,
forbidOnly
:
!
!
process
.
env
.
CI
,
retries
:
process
.
env
.
CI
?
2
:
0
,
workers
:
process
.
env
.
CI
?
1
:
undefined
,
reporter
:
[
[
'html'
]
,
[
'json'
,
{
outputFile
:
'test-results.json'
}
]
,
]
,
use
:
{
baseURL
:
process
.
env
.
BASE_URL
||
'http://localhost:3000'
,
trace
:
'on-first-retry'
,
screenshot
:
'only-on-failure'
,
}
,
projects
:
[
{
name
:
'chromium'
,
use
:
{
...
devices
[
'Desktop Chrome'
]
}
,
}
,
{
name
:
'firefox'
,
use
:
{
...
devices
[
'Desktop Firefox'
]
}
,
}
,
{
name
:
'webkit'
,
use
:
{
...
devices
[
'Desktop Safari'
]
}
,
}
,
]
,
}
)
;
Running Tests
Run all tests
npx playwright test
Run specific test file
npx playwright test tests/auth/login.spec.ts
Run tests with UI
npx playwright test --ui
Run in headed mode (see browser)
npx playwright test --headed
Generate report
npx playwright show-report CI Integration
.github/workflows/regression.yml
name : Regression Tests on : push : branches : [ main ] pull_request : branches : [ main ] schedule : - cron : '0 6 * * *'
Daily at 6 AM
jobs : test : runs-on : ubuntu - latest steps : - uses : actions/checkout@v4 - uses : actions/setup - node@v4 with : node-version : 20 - name : Install dependencies run : npm ci - name : Install Playwright run : npx playwright install - - with - deps - name : Run tests run : npx playwright test env : BASE_URL : $ { { secrets.STAGING_URL } } TEST_EMAIL : $ { { secrets.TEST_EMAIL } } TEST_PASSWORD : $ { { secrets.TEST_PASSWORD } } - uses : actions/upload - artifact@v4 if : always() with : name : playwright - report path : playwright - report/ Best Practices Use data-testid attributes - More stable than CSS selectors Clean up test data - Always delete what you create Avoid hardcoded waits - Use waitForSelector instead of waitForTimeout Run in parallel - Faster feedback on CI Screenshot on failure - Easier debugging Environment variables - Never commit credentials Quick Commands Task Command Run all npx playwright test Run one file npx playwright test login.spec.ts Debug mode npx playwright test --debug UI mode npx playwright test --ui Update snapshots npx playwright test --update-snapshots