Why Playwright Tests Pass in Headful But Fail Headless: 4 Key Reasons and Fixes

Apr 2, 2024 ยท 3 min read

When first setting up Playwright for test automation, you may encounter a frustrating scenario - your tests run smoothly in headful mode, but fail unexpectedly when run headlessly.

What gives? This article will explore the main culprits behind this headless/headful discrepancy, with actionable fixes to help your tests achieve consistency regardless of run mode.

1. Async Code Issues

JavaScript executed in headless mode handles promises and async code differently than headful. Code that seems to work fine when visible can expose race conditions or timing issues when run headlessly.

The fix: Audit use of await and setTimeout calls in your tests. Refactor async logic to eliminate unintended race conditions between Playwright actions.

// Avoid this antipattern that makes tests flaky
await page.click('button'); 
await page.waitForNavigation();

// Better approach:
await page.click('button'); 
const [response] = await Promise.all([
  page.waitForNavigation(),
  page.waitForResponse() 
]);

2. POPUP Windows Create Headaches

By default, Playwright suppresses new tab/window opens when running headlessly. What works fine for visible browser windows can therefore break in headless mode.

The fix: Override the default context setting for ignoring HTTPS errors to handle popups.

// Have context handle browser popups instead of suppressing 
const context = await browser.newContext({
  ignoreHTTPSErrors: true,
  acceptDownloads: true 
});

3. Page Visibility Differences

Certain DOM elements may have subtle differences in behavior based on whether they are currently visible on a loaded page.

The fix: Use more precise selectors and assertions that do not depend on visible state.

// Fragile check dependent on visibility  
await expect(page.getByText('Submit').toBeVisible();

// More robust validation
const submitButton = await page.$('#submitButton'); 
await expect(submitButton).toBeDefined();

4. Environment-Specific Issues

Depending on your test environment, factors like mouse/keyboard input, screen size, or installed fonts could lead to inconsistencies between headful and headless execution.

The fix: Standardize environment conditions where possible using browserContext.overridePermissions() and related methods.

await context.overridePermissions(
  "https://example.com", 
  ["clipboard-read"]
);

const browser = await chromium.launchPersistentContext(userDataDir, {
  headless: false,
  args: [
    '--window-size=1280,720'
  ]
}); 

These tweaks help make your test logic and environment more resilient to the key differences between headless and headful modes.

Additional Strategies for Smoother Playwright Tests

Beyond addressing what causes headless vs headful discrepancies, here are some best practices that will serve you well in Playwright:

  • Carefully manage test state - Reset context, storage, cookies etc between tests to avoid cascade failures
  • Use trace viewer - Debug failing tests by tracing Playwright execution to pinpoint issues
  • Simulate real-world environments - Configure browsers and devices to match your actual users
  • Implement retry logic and timeouts - Make tests more resilient to flakes and environments issues
  • Key Takeaways: No More "Works in Headful, Fails Headless"

    With Playwright offering the performance of headless execution plus the environment visibility of headful mode, you don't need to compromise on test reliability.

    Focus on the fixes and strategies outlined here - like managing async code, handling popups properly, and standardizing environment conditions. This will help bulletproof your tests to run consistently and provide value, whether executed visibly or invisibly.

    The end result? Faster and more robust browser automation without surprises between headful and headless test runs.

    Browse by language:

    The easiest way to do Web Scraping

    Get HTML from any page with a simple API call. We handle proxy rotation, browser identities, automatic retries, CAPTCHAs, JavaScript rendering, etc automatically for you


    Try ProxiesAPI for free

    curl "http://api.proxiesapi.com/?key=API_KEY&url=https://example.com"

    <!doctype html>
    <html>
    <head>
        <title>Example Domain</title>
        <meta charset="utf-8" />
        <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
    ...

    X

    Don't leave just yet!

    Enter your email below to claim your free API key: