Introduction: What Does a Playwright Test Hanging Issue Mean?
A playwright test hanging issue occurs when a test starts but does not finish, fail, or move to the next step within a reasonable time.
For example:
import { test } from ‘@playwright/test’;
test(‘login test’, async ({ page }) => {
await page.goto(‘https://example.com’);
await page.getByRole(‘button’, { name: ‘Login’ }).click();
// Test appears stuck here
});
A hanging test is different from a normal assertion failure. Instead of quickly reporting that an expected condition was false, the test may remain waiting for an operation to complete.
Common causes include:
- An incorrect locator
- Navigation that never finishes
- An API that never responds
- An infinite wait
- An open browser resource
- WebSocket connections
- Incorrect synchronization
- Excessive waitForTimeout()
- CI/CD resource limitations
- Parallel tests consuming too many resources
This playwright test hanging issue fix tutorial explains how to identify the root cause instead of simply increasing timeouts.
Hanging Test vs Timeout Failure
Understanding the difference is important for debugging.
| Problem | Meaning |
| Hanging test | Test appears stuck and does not complete normally |
| Timeout failure | Playwright waited until the configured timeout and then failed |
| Flaky test | Same test sometimes passes and sometimes fails |
| Assertion failure | Application state does not match the expected result |
| Application failure | The application itself is genuinely broken |
Playwright has built-in timeouts that eventually protect many operations from waiting forever. However, certain user-written code, external processes, or resource problems can make a test appear stuck for a long time.
Common Causes of Playwright Test Getting Stuck
Before changing code, investigate these areas:
- Locator waiting indefinitely
- Navigation waiting too long
- API request not completing
- Incorrect networkidle usage
- waitForTimeout() used repeatedly
- Browser process problems
- Unclosed pages or contexts
- Authentication redirects
- WebSocket connections
- Parallel execution overload
- CI/CD network problems
- Incorrect timeout configuration
The fastest playwright test hanging issue fix is usually to identify the exact statement where execution stops.
Infinite Waits and Incorrect Synchronization
Problem → Root Cause → Fix → Best Practice
Problem
A test waits for an element that never appears:
await page.getByText(‘Payment Successful’).click();
Root Cause
The application displays:
instead.
Playwright keeps waiting for the requested element to become actionable until the applicable timeout is reached.
Fix
Use the correct locator:
await page.getByText(‘Payment completed’).click();
Or, preferably, use a stable test identifier:
await page.getByTestId(‘payment-success’).click();
Best Practice
Prefer reliable locators and meaningful assertions instead of guessing selectors.
Locator and Assertion Waits
Playwright automatically waits for many locator actions and assertions.
For example:
await page.getByRole(‘button’, {
name: ‘Submit’
}).click();
You generally do not need:
await page.waitForTimeout(3000);
before clicking.
Instead:
await expect(
page.getByRole(‘heading’, { name: ‘Order Confirmed’ })
).toBeVisible();
This is a deterministic synchronization strategy.
Fixing waitForTimeout() Problems
Problem → Root Cause → Fix → Best Practice
Problem
await page.waitForTimeout(10000);
The test takes 10 seconds even when the page becomes ready after one second.
Root Cause
waitForTimeout() always waits for the requested duration.
Fix
Replace it with an assertion:
await expect(
page.getByTestId(‘dashboard’)
).toBeVisible();
Best Practice
Use waitForTimeout() only for controlled debugging or unusual cases where a fixed delay is genuinely required.
It should not be your primary synchronization mechanism.
Navigation and Network-Related Hanging
Navigation is a common source of a Playwright test stuck problem.
For example:
await page.goto(‘https://example.com’, {
waitUntil: ‘networkidle’
});
Modern applications may continuously send:
- Analytics requests
- Polling requests
- WebSocket traffic
- Notifications
- Background API requests
Therefore, the network may never become completely idle.
Better Approach
await page.goto(‘https://example.com’);
await expect(
page.getByRole(‘heading’, { name: ‘Dashboard’ })
).toBeVisible();
Wait for the state your test actually needs.
For API-dependent tests, use a targeted response:
const responsePromise = page.waitForResponse(response =>
response.url().includes(‘/api/dashboard’) &&
response.status() === 200
);
await page.goto(‘https://example.com/dashboard’);
await responsePromise;
await expect(
page.getByTestId(‘dashboard’)
).toBeVisible();
This is much more reliable than waiting for every network request to finish.
Handling Slow Navigation
If the application genuinely needs more time, configure a reasonable navigation timeout.
import { defineConfig } from ‘@playwright/test’;
export default defineConfig({
use: {
navigationTimeout: 45_000
}
});
You can also configure it for a specific test:
test(‘slow page’, async ({ page }) => {
page.setDefaultNavigationTimeout(45_000);
await page.goto(‘https://example.com/slow-page’);
});
However, increasing the timeout should not be the first response to every hanging test.
Ask:
Is the application actually slow, or am I waiting for the wrong condition?
Browser, Page, Context, and Process Issues
Browser resources can also cause a Playwright test not completing problem.
If you manually create resources:
import { chromium } from ‘@playwright/test’;
(async () => {
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(‘https://example.com’);
console.log(await page.title());
await page.close();
await context.close();
await browser.close();
})();
Make sure resources are closed.
For standard Playwright Test fixtures, Playwright handles browser/page lifecycle for you:
test(‘homepage’, async ({ page }) => {
await page.goto(‘https://example.com’);
});
Avoid manually launching additional browsers inside every test unless you have a specific reason.
Authentication and API-Related Hanging Tests
Authentication can create confusing loops.
For example:
Login → Redirect → Login → Redirect → Login
The test may appear to hang because authentication never reaches the expected application state.
Check:
- Login API status
- Redirect URL
- Cookies
- Storage state
- Authentication tokens
- Environment variables
- API availability
Use explicit API synchronization where necessary:
const loginResponse = page.waitForResponse(response =>
response.url().includes(‘/api/login’) &&
response.ok()
);
await page.getByLabel(‘Username’).fill(‘testuser’);
await page.getByLabel(‘Password’).fill(‘password’);
await page.getByRole(‘button’, { name: ‘Login’ }).click();
await loginResponse;
await expect(
page.getByRole(‘heading’, { name: ‘Dashboard’ })
).toBeVisible();
Parallel Execution and Resource Problems
A test may pass alone but hang when many tests execute simultaneously.
For example:
npx playwright test –workers=8
Eight workers may overload:
- CPU
- RAM
- Application servers
- Database connections
- API rate limits
- Docker containers
Try reducing workers:
npx playwright test –workers=2
Or temporarily run serially:
npx playwright test –workers=1
If the problem disappears, investigate resource contention instead of modifying every test.
Debugging Playwright Hanging Tests
1. Use Playwright Inspector
Run:
npx playwright test –debug
The Inspector lets you step through actions and identify where the test stops progressing.
2. Add Request and Response Logging
page.on(‘request’, request => {
console.log(‘REQUEST:’, request.method(), request.url());
});
page.on(‘response’, response => {
console.log(‘RESPONSE:’, response.status(), response.url());
});
page.on(‘requestfailed’, request => {
console.log(
‘FAILED:’,
request.url(),
request.failure()?.errorText
);
});
This is useful for identifying:
- Failed APIs
- Repeated requests
- Slow services
- Unexpected redirects
- Network problems
3. Use Trace Viewer
Run:
npx playwright test –trace=on
Then inspect the trace:
npx playwright show-trace trace.zip
Trace Viewer can help you understand the sequence of actions before the test became stuck.
4. Capture Screenshots
A screenshot can show whether the application is:
- Still loading
- Showing a login page
- Displaying an error
- Stuck behind a modal
- Waiting for an unexpected state
await page.screenshot({
path: ‘debug-page.png’,
fullPage: true
});
Fixing Playwright Hanging Tests in CI/CD
A common scenario is:
The test works locally but hangs in CI.
Check the following:
- Node.js version
- Playwright version
- Browser installation
- Docker image
- CPU and memory
- Network connectivity
- API environment
- Authentication configuration
- Test workers
- Timeouts
- External service availability
Run with fewer workers:
npx playwright test –workers=1
Enable tracing:
npx playwright test –trace=on
Also collect the HTML report:
npx playwright show-report
CI failures often expose race conditions or resource problems hidden by a fast local machine.
Real-World Playwright Test Hanging Issue Examples
Example 1: Infinite Wait
Problem:
await page.waitForSelector(‘.loading-complete’);
Root Cause: The application uses a different selector.
Fix:
await expect(
page.getByTestId(‘dashboard’)
).toBeVisible();
Best Practice: Use stable application-specific locators.
Example 2: Network Hanging
Problem:
await page.goto(‘/dashboard’, {
waitUntil: ‘networkidle’
});
Root Cause: Dashboard polling never stops.
Fix:
await page.goto(‘/dashboard’);
await expect(
page.getByTestId(‘dashboard’)
).toBeVisible();
Best Practice: Wait for application readiness rather than network inactivity.
Example 3: CI Hanging During Parallel Execution
Problem: Tests complete individually but stall when running 12 workers.
Root Cause: CI machine has insufficient resources.
Fix:
npx playwright test –workers=2
Best Practice: Choose worker counts based on available CI resources.
Common Mistakes and Solutions
| Mistake | Solution |
| Using long waitForTimeout() calls | Use assertions |
| Waiting for networkidle everywhere | Wait for specific conditions |
| Increasing every timeout | Identify the root cause |
| Ignoring failed requests | Log requests and responses |
| Running too many workers | Reduce parallelism |
| Manually opening browsers unnecessarily | Use Playwright fixtures |
| Ignoring authentication redirects | Validate login/API state |
| Debugging only locally | Reproduce in CI-like environment |
| Using unstable locators | Prefer role, label, and test IDs |
Playwright Test Stability Best Practices
For a reliable Playwright TypeScript framework:
Use deterministic synchronization
await expect(
page.getByTestId(‘results’)
).toBeVisible();
Avoid unnecessary sleeps
await page.waitForTimeout(5000);
should not be your normal synchronization method.
Use targeted API waits
await page.waitForResponse(response =>
response.url().includes(‘/api/results’) &&
response.ok()
);
Keep timeouts reasonable
Do not hide defects with extremely large timeout values.
Use stable locators
Prefer:
page.getByRole(‘button’, { name: ‘Save’ })
over brittle CSS/XPath selectors.
Debug failures with evidence
Use:
- Trace Viewer
- Screenshots
- Video
- HTML reports
- Request logs
- Response logs
Playwright Interview Questions With Answers
Why is my Playwright test hanging?
Common causes include incorrect waits, unavailable locators, slow navigation, APIs that never respond, continuous network requests, browser issues, and CI resource constraints.
How do you fix a Playwright test that is stuck?
Identify the exact statement where execution stops. Then inspect the locator, network request, navigation, timeout, browser process, and CI environment involved.
Why should you avoid excessive waitForTimeout()?
It introduces fixed delays and does not verify that the application is actually ready.
Is networkidle always recommended?
No. Applications with polling, WebSockets, analytics, and background requests may never become idle.
How do you debug Playwright hanging in CI/CD?
Run with fewer workers, enable tracing, collect screenshots and reports, inspect request failures, and compare the CI environment with local execution.
What is the difference between a timeout and a hanging test?
A timeout is a controlled failure after Playwright waits for a configured duration. A hanging test appears stuck because an operation, resource, process, or synchronization mechanism is preventing normal completion.
FAQs
Why is my Playwright test hanging?
A Playwright test can hang because it is waiting for an element, navigation, API response, network state, browser resource, authentication redirect, or another condition that never completes.
How do I fix a Playwright test stuck problem?
Find the exact operation where the test stops, then replace unnecessary waits with deterministic locators, assertions, targeted API waits, or appropriate timeouts.
How do I fix a Playwright test not completing?
Check open resources, navigation, API requests, WebSockets, authentication, parallel workers, and CI resource constraints.
Can waitForTimeout() cause Playwright tests to hang?
A large waitForTimeout() makes tests unnecessarily slow. It can contribute to the appearance of hanging when several long fixed waits are chained together.
Why does Playwright hang in CI but pass locally?
CI machines may have different CPU, memory, network latency, browser dependencies, environment variables, API availability, or worker configurations.
