End-to-end Testing
8 min read

What is headless browser testing?

Learn about the advantages and disadvantages of using headless browsers in your automated testing efforts.

Lawrence Eagles
Published July 19, 2022
AI Assistant for Playwright
Code AI-powered test steps with the free ZeroStep JavaScript library
Learn more

Headless browsers have several use-cases including web scraping, automated testing, automating interactions with websites, or more nefarious uses like performing DDOS attacks.

The term isn’t self-explanatory though, and knowing exactly what a headless browser is can be a little confusing.

In this article, we’re going to discuss:

What is a headless browser?

A headless browser reads, parses, and interacts with web pages without rendering a graphical user interface (GUI), and without requiring a user to operate the browser. For normal web browsing, having a GUI is essential for viewing content and interacting with the underlying web page. With a headless browser, you can replicate and automate almost anything a user can do, and do it in a host of programming languages.

A headless browser can programatically check anything a user can, including:

How are headless browsers used for testing?

Because headless browsers can remove some of the overhead associated with running end-to-end tests in fully-fledged browser, they can be a great fit for testing. Specifically, headless browsers are able to bypass some of the rendering, parsing, and compositing phases that a normal browser would go through in order to display a web page.

Their benefits don’t end here - and this is why automated testing is one of the most common uses for headless browsers.

Let’s cover a few specific types of testing where headless browsers can be a good fit.

Component testing

Component tests are automated tests that test a component in isolation. This is a common practice in modern web development, especially when using frameworks like Angular, Vue, or React which are based around building reusable components.

If we’re testing a web component, we likely need at least some access to the DOM (Document Object Model) in order to render the component and interact with it. But since component tests are isolated, running in a fully-fledged browser is often not necessary. With headless browsers, you can get access to DOM methods without the overhead of a full browser session. You could run end to end tests (in a headless browser) on your production site once a day, to ensure you haven’t broken anything if you release regularly.

This is a great benefit, as it saves testers from having to manually repeat tasks over and over, and allows them to focus more time on ad-hoc testing. The increased test coverage from automated tests also can provide a level of comfort that releases are working as expected before they hit production.

Uptime monitoring and synthetic testing

When you think of uptime monitoring, you probably think of tools like Pingdom which issue simple checks at the API-level to verify that your system(s) are online and available. While these types of checks are often essential, there are still classes of availability issues that will be very apparent to users, but are not caught by this type of monitoring.

Synthetic testing, which is the practice of replicating simple end-to-end workflows to verify availability, is a great way to augment API-level monitoring, and is a great use-case for headless browsers. The scenarios that are tested with synthetic testing tend to be pretty simple, usually nothing more complicated than logging in to the application. It’s usually straightforward to build these types of tests in headless browsers, and even if the page ends up rendering differently in the headless browser vs. a normal browser, as long as the underlying functionality works the same then we’re covered.

You can completely change this workflow as to exactly what your team needs. You could run profiles to ensure your website is still performant - and ensure each new release isn’t slowing down your application - or degrading any module in your stack.

Performance and parallelization

Generally speaking, a manual tester would run through a test script on one machine, going through each step “one by one”.

But if you are running headless - you can start multiple terminal instances to run their own tests, and significantly speed up the time taken.

You may not need multiple terminal instances if you are running tests client side, but you could also spin up multiple browsers if you want to run your tests client side.

What are the advantages of a headless browser?

When we have discussed the applications of headless browsers - we have covered a lot of their advantages.

There are more benefits though, so let’s briefly discuss some benefits without strictly discussing their applications.

Resource Utilization

Headless testing generally is cheaper, as they require less resources than a similar UI test that runs on a real browser.

The browser rendering engine, and the extra power drawn to run your tests aren’t necessary and can be totally eliminated from your cost analysis if you decide to go headless.

They can be faster than equivalent UI tests

A lot of our benefits have discussed how much faster your headless browser can be than your manual testers - and save them from repetitive tasks they don’t want to do.

But headless tests can still be 2x-10x faster than their equivalent UI driven tests as you can cut the browsers rendering engine completely out of the speed calculation.

What are the disadvantages of a headless browser?

Headless browsers have their own trade offs though, and aren’t a perfect fix for all testing issues.

They shouldn’t be used in isolation

Jim Holmes has discussed this saying that:

“Starting a browser session does indeed slow down tests … but [headless tests] do not replace browser-based functional tests. The whole point of functional UI testing is to ensure your application works in the various browsers with all their unique quirks and ‘personalities.’ Headless tests are a great, fast integration-level test, but you shouldn’t skip full-blown browser testing.”

If you decide to use headless testing, you still likely will need some level of alternate tests to ensure your coverage is still good, and increase your likelihood of catching issues.

It doesn’t fully replicate a real browser

If the headless test generates its own implementation of the DOM, you sadly have no guarantee that it will render things exactly how users will see.

You can’t be totally sure your headless test implementation is up to date with new spec changes, browser quirks or inconsistencies between the browser engines - and can leave a chink in your armour.

Different headless testing handles this differently, and running something like Puppeteer or Playwright in headless mode mitigates this problem since it’s using a real browser’s rendering engine to render the page. But it is something you’re going to have to consider for your own use case.

Headless browser example

A common choice for running headless tests is Puppeteer - so let’s run through an example test together.

This repository will contain all the steps completed in this tutorial, if you get lost or want to take this further than the article.

You’ll need to create a basic Node environment, and include Puppeteer as a dependency, so lets start there.

First, create a folder called “Puppeteer-Screenshot” and inside that folder create an empty file called index.js.

Next, in a terminal window switch to the Puppeteer-Screenshot directory and run the command npm init. This will initialize a basic Node environment which will be the runtime for your headless browser script.

Now, all you need to do is run npm i puppeteer - to install it as an npm dependency.

I then created a function called screenShot - as this example is simply going to take a screenshot of an example URL.

We then launch Puppeteer and create a page.

Then all that’s left for us is to screenshot the page from the URL we desire and close our browser - it should look something like:

const puppeteer = require("puppeteer");

const screenShot = async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.goto("https://kealanparr.com");
  await page.screenshot({ path: "homepage.png" });
  await browser.close();


I used my own personal site in this example, but this can be any URL you desire.

All you now need to do is invoke the function by running node index.js - and you will see the website screenshot!

This was taken in a headless way, but you can specify it to run non-headless if you desire, by running

const browser = await puppeteer.launch({headless: false}).

The image also is set to a specific dimension, but can also be modified to be a fullPage screenshot, or custom dimensions supplied.


I hope this article has been useful for you to:

As already stated, the example repo can be found here if you want to take this further.

Try Reflect: A modern end-to-end testing platform

Reflect is a no-code testing platform that lets you build and run tests across all popular browsers. Using a cloud testing platform like Reflect allows you run automated tests in real browsers without having to stand-up and maintain your own testing infrastructure yourself.

Try it for free.

Get started with Reflect today

Create your first test in 2 minutes, no installation or setup required. Accelerate your testing efforts with fast and maintainable test suites without writing a line of code.

Copyright © Reflect Software Inc. All Rights Reserved.