Introduction: Why Do Playwright Tests Pass Locally but Fail in CI?
One of the most frustrating automation problems is:
Local → PASS
CI/CD → FAIL
The test code may not have changed, yet a Playwright test fails in GitHub Actions, Jenkins, Azure DevOps, or another CI server.
The reason is simple: local and CI are different execution environments.
A developer machine may have:
- A different operating system
- More CPU and memory
- Different browser versions
- Cached Playwright browsers
- Local environment variables
- Existing authentication
- Faster network access
- Different test data
CI may have none of these.
This playwright test failing in ci but passing locally guide explains how to identify the actual cause instead of simply increasing timeouts or retries.
Common Reasons for Playwright CI Failures
The most common causes are:
| Cause | Local | CI |
| Browser dependencies | Already installed | Missing |
| Environment variables | .env available | Missing secret |
| CPU/memory | Usually higher | Limited |
| Browser mode | Often headed during debugging | Usually headless |
| OS | Windows/macOS | Usually Linux |
| Authentication | Existing/local state | Fresh environment |
| Network | Fast/local | Restricted/slower |
| Test data | Existing data | Different/empty |
| Parallel workers | Fewer | More |
| Application URL | Local config | CI config |
The first troubleshooting step is therefore to identify what is different, not to change the test randomly.
Local Environment vs CI Environment Differences
Start by printing safe environment information.
import { test } from ‘@playwright/test’;
test(‘environment diagnostics’, async ({ page }) => {
console.log(‘URL:’, process.env.BASE_URL);
console.log(‘CI:’, process.env.CI);
console.log(‘Platform:’, process.platform);
await page.goto(process.env.BASE_URL!);
console.log(‘Actual URL:’, page.url());
});
Never print passwords, API keys, tokens, cookies, or authentication state into CI logs.
A useful comparison is:
Local:
Node version
Browser version
OS
BASE_URL
Test data
Authentication
CI:
Node version
Playwright version
Browser version
OS
BASE_URL
Test data
Authentication
Differences in any of these can explain a Playwright CI Failure.
Browser Installation and Dependency Issues
A very common CI problem is that npm dependencies are installed but Playwright browsers are not.
This:
npm ci
does not necessarily mean your CI environment has the required browser binaries available.
Install them explicitly:
npx playwright install –with-deps
For Chromium only:
npx playwright install chromium –with-deps
A typical Linux CI sequence is:
npm ci
npx playwright install –with-deps
npx playwright test
This is particularly important when the local machine already has Playwright browsers cached.
Headless Browser and Operating-System Differences
CI commonly runs browsers headlessly on Linux.
A test may behave differently because:
- Font rendering differs.
- Viewport differs.
- Linux dependencies differ.
- Animations behave differently.
- Browser versions differ.
- OS-specific behavior changes.
Configure the viewport explicitly when it matters:
import { defineConfig } from ‘@playwright/test’;
export default defineConfig({
use: {
viewport: {
width: 1280,
height: 720
},
headless: true
}
});
Don’t attempt to reproduce every local machine detail. Instead, make the test environment deterministic.
Environment Variables, URLs, and Test Credentials
This is another major cause of playwright test failing in ci but passing locally.
Local:
BASE_URL=https://staging.example.com
TEST_USERNAME=qa-user
TEST_PASSWORD=******
CI might have:
BASE_URL=https://wrong-environment.example.com
or no credentials at all.
Use environment variables:
const baseURL = process.env.BASE_URL;
if (!baseURL) {
throw new Error(‘BASE_URL is not configured’);
}
Then:
await page.goto(baseURL);
For credentials:
await page.getByLabel(‘Email’)
.fill(process.env.TEST_USERNAME!);
await page.getByLabel(‘Password’)
.fill(process.env.TEST_PASSWORD!);
Store credentials in CI secret management rather than committing them to Git.
Timeout and Synchronization Problems
CI machines can be slower than developer machines.
A test like this is fragile:
await page.getByRole(‘button’, {
name: ‘Generate Report
}).click();
await page.waitForTimeout(2000);
await expect(
page.getByText(‘Report ready’)
).toBeVisible();
The 2-second wait might work locally but fail on a busy CI runner.
Use application-state synchronization:
await page.getByRole(‘button’, {
name: ‘Generate Report’
}).click();
await expect(
page.getByText(‘Report ready’)
).toBeVisible({
timeout: 20_000
});
The important distinction is that the assertion waits for a meaningful condition.
Don’t simply turn every timeout into:
timeout: 120000
That can hide a real synchronization problem.
Flaky Tests and Parallel Execution Issues
CI often exposes Playwright Test Flakiness that isn’t obvious locally.
Suppose two tests use the same account:
Worker 1 → Add product to cart
Worker 2 → Remove product from cart
The result becomes unpredictable.
Run the suite with one worker as a diagnostic:
npx playwright test –workers=1
If failures disappear, investigate shared state.
Possible conflicts include:
- Same user
- Same shopping cart
- Same database record
- Same file
- Same order
- Same test account
The solution is test isolation, not permanently disabling parallel execution.
Test Data, Authentication, and Shared-State Problems
A local machine might already have authentication cookies or data.
CI starts from a clean environment.
For authenticated tests, use controlled authentication setup and storageState where appropriate.
Example:
await page.context().storageState({
path: ‘playwright/.auth/user.json’
});
Then configure:
use: {
storageState: ‘playwright/.auth/user.json’
}
However, authentication files can contain sensitive state and should not be committed to source control.
For CI, generate authentication state as part of the pipeline or setup process using secure credentials.
Debugging Playwright Failures in CI
When a test fails only in CI, you need evidence.
Configure:
import { defineConfig } from ‘@playwright/test’;
export default defineConfig({
use: {
screenshot: ‘only-on-failure’,
video: ‘retain-on-failure’,
trace: ‘retain-on-failure’
},
reporter: [
[‘html’]
]
});
Now a failed test can provide:
- Screenshot
- Video
- Trace
- HTML report
Open the report locally after downloading the CI artifact:
npx playwright show-report
Trace Viewer can reveal:
Last successful action
↓
Locator resolution
↓
Page screenshot
↓
Network activity
↓
Failure
This is one of the most effective techniques for Playwright CI debugging.
Playwright GitHub Actions Troubleshooting Example
A practical GitHub Actions workflow:
name: Playwright Tests
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
– name: Checkout
uses: actions/checkout@v6
– name: Setup Node
uses: actions/setup-node@v6
with:
node-version: lts/*
– name: Install dependencies
run: npm ci
– name: Install Playwright browsers
run: npx playwright install –with-deps
– name: Run Playwright tests
run: npx playwright test
– name: Upload Playwright report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v5
with:
name: playwright-report
path: playwright-report/
Playwright’s CI documentation recommends installing dependencies and browsers before running the tests, with reports and other artifacts available for failures.
For secrets, configure GitHub repository or environment secrets and access them through environment variables:
– name: Run tests
env:
BASE_URL: ${{ secrets.BASE_URL }}
TEST_USERNAME: ${{ secrets.TEST_USERNAME }}
TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}
run: npx playwright test
Never put real credentials directly into YAML.
Configuring Retries and Workers
Retries can help identify environmental failures:
import { defineConfig } from ‘@playwright/test’;
export default defineConfig({
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 2 : undefined
});
But retries should not become the primary solution.
If this happens:
Attempt 1 → FAIL
Attempt 2 → PASS
investigate the reason.
Similarly, reducing workers may make CI stable temporarily, but shared test data should ultimately be fixed.
Docker-Based CI Troubleshooting
Docker can reduce environment differences by standardizing:
- Node
- Browser
- Linux dependencies
- Playwright version
Example:
FROM mcr.microsoft.com/playwright:v1.62.0-noble
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD [“npx”, “playwright”, “test”]
Keep the Docker image and Playwright package versions aligned.
Then:
docker build -t playwright-tests .
docker run –rm playwright-tests
Docker is particularly useful when a team wants Playwright Docker execution to resemble CI closely.
Real-World CI Failure Scenarios
Scenario 1: Browser works locally but not CI
Problem: Local PASS, CI says browser executable is missing.
Root cause: Browser binaries were not installed.
Fix:
npx playwright install –with-deps
Best practice: Make browser installation an explicit pipeline step.
Scenario 2: Locator timeout in CI
Problem: Button exists locally but times out in CI.
Root cause: Slower rendering or API response.
Incorrect approach:
await page.waitForTimeout(10000);
Fix:
await expect(
page.getByRole(‘button’, {
name: ‘Checkout’
})
).toBeVisible({
timeout: 15000
});
Best practice: Use auto-waiting and meaningful assertions.
Scenario 3: Authentication failure
Problem: Dashboard test fails because the dashboard isn’t found.
Root cause: CI credentials are missing or invalid.
Fix: Verify the CI secret and assert authentication:
await expect(page).toHaveURL(/dashboard/);
Best practice: Separate authentication setup from business workflow tests.
Scenario 4: Parallel test failure
Problem: Tests pass with one worker but fail with four.
Root cause: Shared test data.
Fix: Generate independent users/orders or isolate database records.
Best practice: Design tests to be worker-safe.
Playwright CI/CD Best Practices
Use this checklist when troubleshooting playwright test failing in ci but passing locally:
Environment
- Pin Node versions.
- Lock dependencies.
- Standardize browsers.
- Use Docker when appropriate.
- Explicitly install browser dependencies.
Test design
- Use reliable locators.
- Avoid waitForTimeout().
- Use web-first assertions.
- Make tests independent.
- Generate isolated test data.
Authentication
- Use dedicated CI test accounts.
- Store credentials as secrets.
- Generate authentication state securely.
- Never commit tokens or cookies.
CI configuration
- Install browsers.
- Configure required environment variables.
- Collect traces.
- Upload screenshots and reports.
- Use retries only as a safety mechanism.
Parallel execution
- Avoid shared users.
- Avoid shared files.
- Avoid shared mutable records.
- Verify tests with multiple workers.
Playwright Interview Questions with Answers
Why does Playwright pass locally but fail in CI?
Because CI may use different operating systems, browsers, environment variables, credentials, network conditions, resources, or test data.
How do you debug a Playwright CI failure?
Collect screenshots, videos, traces, HTML reports, logs, and environment information, then inspect the failing action.
Should you increase timeout when CI is slow?
Only when the application operation is legitimately slower. First investigate synchronization and environment issues.
How do you install browsers in GitHub Actions?
npx playwright install –with-deps
How do you diagnose parallel failures?
Run:
npx playwright test –workers=1
If the problem disappears, investigate shared state and test isolation.
Why use Docker for Playwright?
Docker helps standardize the browser, OS dependencies, Node environment, and Playwright version.
FAQs: Playwright Test Failing in CI but Passing Locally
Why does Playwright pass locally but fail in CI?
The local and CI environments may have different browsers, operating systems, environment variables, credentials, network conditions, resources, or test data.
How do I fix Playwright CI failures?
Start by comparing environments. Install browsers and dependencies, verify secrets and URLs, collect traces, check synchronization, and investigate test isolation.
Why does Playwright timeout only in CI?
CI machines can be slower, APIs may respond differently, or the test may depend on timing. Use web-first assertions and investigate the CI environment.
Why does Playwright authentication work locally but fail in CI?
Local authentication may rely on existing cookies or environment variables. CI starts clean and requires explicit credentials and authentication setup.
Should I use more retries in CI?
Retries can reduce the impact of transient infrastructure problems, but repeated retry success is a signal that the test may be flaky.
Does Docker fix all Playwright CI problems?
No. Docker standardizes the execution environment but cannot fix incorrect locators, bad test data, authentication errors, or application defects.
