How to Perform Playwright Headless Testing

Learn how to perform Playwright headless testing for faster, efficient test execution. Discover key methods to run Playwright headless browser tests.

OVERVIEW

Testing often involves running through a graphical user interface or opting for a headless mode, which skips the display of a browser. Headless testing is highly preferred when speed and resource optimization are key to minimizing the need for a visible interface, making it ideal for scaling up automation tasks.

Modern end-to-end testing tools like Playwright allow for both headless and non-headless testing. However, developers and testers can run Playwright headless tests by default. This minimizes the number of configurations to be made before getting started and is very easy to use with support for various programming languages, including Java, Python, Node.js, etc.

What Is Playwright Headless Testing?

Headless browser testing with Playwright involves running tests without opening a visible browser window. In this mode, Playwright performs all actions behind the scenes without displaying any user interface.

It’s commonly used for faster test execution, especially when running a large number of tests or automating tasks like web scraping. Since headless mode doesn’t rely on graphical resources, it helps save time and system resources, making it efficient for continuous integration or large-scale automated testing. It can be used for scraping data from websites, performing end-to-end testing, and debugging.

Playwright supports headless testing by default, but you can easily switch to a headed mode if needed.

Subscribe to the LambdaTest YouTube Channel for more such tutorials.

Why Run Playwright Headless Tests?

Playwright headless testing has several advantages over headed testing. Here are some of those:

  • Scalability: Headless testing scales easily across multiple browser instances, allowing for parallel execution and quicker results.
  • Browser Compatibility: Playwright supports headless testing across popular browsers like Chrome, Firefox, and WebKit, ensuring consistent performance and compatibility across different environments.
  • Better Performance and Speed: Running Playwright in headless mode boosts performance by skipping the rendering of the browser UI. Without loading HTML, CSS, or JavaScript visuals, tests run faster. This makes it ideal for executing large test suites where speed is critical.
  • Efficient Resource Usage: Headless testing reduces CPU and memory usage since no browser window or web page visuals are displayed. This frees up system resources, unlike traditional browser testing, which consumes more due to rendering.

How to Run Playwright Headless Tests?

This section will explore how to perform headless browser testing using Playwright.

1. Run the below command to create a playwright_headless_testing folder:

mkdir playwright_headless_testing

2. Navigate into the playwright_headless_testing folder and create a virtual environment using Python’s built-in module venv:

cd playwright_headless_testing
python3 -m venv env

3. Activate the new virtual environment (env) for the project:

source env/bin/activate

4. Install the Playwright pytest plugin using the following command:

4. pip3 install pytest-playwright

You can use pip for Python versions 2.x and pip3 for Python versions 3.x. This plugin bundles the pytest plugin and Playwright together to enable writing end-to-end tests in Python.

5. Install browsers for running Playwright headless tests locally:

playwright install

This step does not apply if you are running Playwright headless tests on the cloud.

Since we have completed setting up our virtual environment and installed Playwright, check all versions of installed dependencies. It is good practice to keep a record of installed dependencies in the Python virtual environment in a requirements.txt or .toml file.

Currently, we have Playwright 1.44.0, Python 3.10.12, and pytest 8.2.2 set up for this project.

Configuring Headless Tests

One of the pros of headless testing is that it is apt for running tests in cloud environments. Therefore, we will leverage the immense cloud capabilities offered by platforms such as LambdaTest.

LambdaTest is an AI-powered test execution platform that offers real web browsers online to run Playwright headless tests. To run your tests on LambdaTest, get your Username and Access Key from your Account Settings > Password & Security.

...

You can also use the LambdaTest Automation Capabilities Generator to generate automated testing capabilities. These generated capabilities will be used in the test script to create a remote headless browser instance on the cloud environment in the conftest.py file , which holds pytest fixtures.

Since we are running Playwright tests in headless mode, set the headless key value to True in the capabilities dictionary:

capabilities = {
   "browserName": "Chrome",  # Browsers allowed: 'Chrome', 'MicrosoftEdge', 'pw-chromium', 'pw-firefox' and 'pw-webkit'
   "browserVersion": "latest",
   "LT:Options": {
       "platform": "Windows 11",
       "build": "Playwright Headless Python Build",
       "name": "Complete Guide to Playwright Headless Testing",
       "user": os.getenv("LT_USERNAME"),
       "accessKey": os.getenv("LT_ACCESS_KEY"),
       "network": True,
       "video": True,
       "console": True,
       "headless": True,
       "tunnel": False,  # Add tunnel configuration if testing locally hosted webpage
       "tunnelName": "",  # Optional
       "geoLocation": "",  # country code can be fetched from https://www.lambdatest.com/capabilities-generator/
   },
}
LambdaTest

Code Walkthrough:

Import necessary packages, dependencies, and modules in the conftest.py file. For example, os, load_dotenv(), and os, load_dotenv(), functions.

The LambdaTest Username and Access Key are stored in a .env file. Hence, the load_dotenv("../.env", override=True) function is used to read these values into the capabilities dictionary.

values into the capabilities dictionary.

The playwrightVersion variable bears a string value of the current Playwright version as output by the subprocess module. The lt_cdp_url holds the URL for connecting to a remote headless browser instance.

The browser variable represents the headless Playwright Chrome browser instance. The playwright.chromium.connect() method takes two arguments, the lt_cdp_url and a timeout value, to connect to LambdaTest and create the browser.

to connect to LambdaTest and create the browser

After creating the browser, the page fixture is created using the @pytest.fxture decorator. Like with the browser, the page fixture will be available for our tests. The browser.new_page() method is used to create a new page instance.

 page fixture is created using the

We created another playwright_local_page function for local Playwright headless tests. The playwright.chromium.launch() method will create a headless browser instance as the headless keyword argument is set to True.

A page object variable is created using the browser.new_page() method and yielded from the function. Finally, the set_test_status() function checks if the test case passed or failed using the page.evaluate() method.

The Page Object Model (POM) is used to structure the test script for this project. For this, we’ll scrap the LambdaTest eCommerce Playground for product titles, names, prices, and their respective images and also fetch build data from LambdaTest Selenium Automation API endpoints through the LambdaTest cloud platform.

The code snippet below scraps the product titles, names, prices, and images from the LambdaTest eCommerce Playground:

# scrape product names/titles
    def product_title_grid_list(self):
        # ensure DOM content is loaded
        self.page.wait_for_load_state("domcontentloaded")


        # scrape product names
        return (
            self.page.locator("#entry_212408 div")
            .locator(".product-grid div")
            .locator(".caption")
            .locator(".title")
            .all_inner_texts()
        )


    def product_price_grid_list(self):
        # ensure DOM content is loaded
        self.page.wait_for_load_state("domcontentloaded")


        # scrape product prices
        return (
            self.page.locator("#entry_212408 div")
            .locator(".product-grid div")
            .locator(".caption")
            .locator(".price")
            .all_inner_texts()
        )


    def product_image_grid_list(self):
        # ensure DOM content is loaded
        self.page.wait_for_load_state("domcontentloaded")


        # initial downward scroll
        self.scroll_down_page()


        # scrape product image links
        images = (
            self.page.locator("#entry_212408 div")
            .locator(".product-grid div")
            .locator(".product-thumb-top")
            .locator(".image .carousel-inner")
            .locator(".active > img")
        )

Code Walkthrough:

Import the time module, which will be used later in the code. Create an EcommerceScrapper class, initialize it, and navigate to the LambdaTest eCommerce Playground homepage.

Create a select_product_category() method that will be used to initiate navigation to the product category to be scraped. The self.page.get_by_role("button", name="Shop by Category").click() method is used to click on the Shop by Category button.

method is used to click on the Shop

We use the Playwright self.page.wait_for_load_state() method to wait for the load event. Click on the Laptops & Notebooks category using the self.page.get_by_role("link", name="Laptops & Notebooks").click() method.

Another scroll_down_page() function is created for scrolling down the product category page to ensure that the product images are loaded before scraping. The self.page.mouse.wheel() method is used to make a downward movement. The time.sleep() function delays the scroll for a second to ensure product images are fully loaded.

Another scroll_down_page() function is created

A full screenshot of the web page is taken to confirm scrolling and image load using self.page.screenshot(path="screenshot.png", full_page=True). A product_title_grid_list() method is created for scraping the product name/title. The Playwright wait() method self.page.wait_for_load_state("domcontentloaded") waits for elements whose content is loaded before scraping.

The self.page.locator() method combined with the .all_inner_texts() method will return an array of inner text from matching elements.

 self.page.locator() method combined

Next, create another product_price_grid_list() method, which would be used to scrape out product prices. Like the previous method, we use self.page.wait_for_load_state("domcontentloaded") to wait for Document Object Model (DOM) content to be loaded.

The chained self.page.locator() method is used to select the specific elements, and the all_inner_texts() method returns an array of each product price.

 chained self.page.locator() method

We’ll also extract the links for product images using the product_image_grid_list() method. We initiate a downward page scroll with the self.scroll_down_page() method. We use a for loop to iterate over images.all() function to extract the image link from each element locator returned.

Image links are extracted using the get_attribute() method and added to the links list and returned from the function.

The display_scraped_product_details() method will return a list of Python dictionaries containing scraped product details. The product prices, names, and image links are combined into dictionaries and returned using the method.

Now, the code snippet below fetches build data from LambdaTest Selenium Automation API endpoints through the LambdaTest cloud platform.

"""LambdaTest API Metadata Class"""


# fetch builds metadata from LambdaTest API
def fetch_builds_data(self):
url = "https://api.lambdatest.com/automation/api/v1/builds"
auth = (username, access_key)


response = requests.get(url=url, auth=auth)
if response.status_code == 200:
builds_data = response.json()
return builds_data
else:
print("Failed to fetch builds. Status code:", response.status_code)
print("Error Message:", response.text)
return None


# fetch sessions metadata from LambdaTest API
def fetch_sessions_data(self):
url = "https://api.lambdatest.com/automation/api/v1/sessions"
auth = (username, access_key)


response = requests.get(url=url, auth=auth)
if response.status_code == 200:
sessions_data = response.json()
return sessions_data
else:
print("Failed to fetch sessions. Status code:", response.status_code)
print("Error Message:", response.text)
return None

Code Walkthrough:

Import the os, requests modules, and load_env() function into the fetch_api_metadata.py file.

The variables username and access_key store the LambdaTest Username and Access Key saved in a .env file. The load_dotenv("../.env", override=True) function reads these values into the capabilities dictionary variables.

Now, create APImetadata class and an instance fetch_builds_data() method to fetch builds API metadata from the endpoint. Use the requests library .get() method passing in the keyword arguments for url and auth to get builds metadata from the LambdaTest API builds endpoint. Also, check if the response status code is OK and return the JSON data from the function; otherwise, return None.

return the JSON data from the function

Create an instance fetch_sessions_data() method to fetch sessions API metadata from the endpoint. Pass in the keyword arguments for url and auth to the requests library .get() method to get session metadata from the LambdaTest API sessions endpoint.

Also, check if the response status code is OK and return the JSON data from the function else, return None.

 return the JSON data from the function else

Writing Headless Tests to Scrape eCommerce Website

We’ll write Playwright headless tests for our EcommerceScraper class.

Test Scenario:

  • Navigate to the homepage of the LambdaTest eCommerce Playground website.
  • Click on Shop by Category to reveal product categories.
  • Click on the Laptops & Notebooks category.
  • Scrape the image, product name, and price of products on the first page of the Laptops & Notebooks category.
  • Use Playwright page object locators to extract the image, product name, and price for each product.
  • Raise an assertion that 15 product names were scraped.
  • Raise an assertion that 15 product prices were scraped.
  • Raise an assertion that 15 product image links were scraped.
  • Raise an assertion that a list of 15 Python dictionaries with product details was returned.

Implementation:

This test script validates the functionality of the LambdaTest eCommerce Playground website that extracts information from a product listing page, ensuring that it correctly scrapes product titles, prices, image links, and detailed product information.

def test_product_title_list(page, set_test_status):
scraper = EcommerceScraper(page)
scraper.select_product_category()
product_title_list = scraper.product_title_grid_list()


if len(product_title_list) == 15:
set_test_status(status="passed", remark="Product names scraped")
else:
set_test_status(status="failed", remark="Product names not scraped")


assert isinstance(
product_title_list, list
), f"Expected {product_title_list} to be a list"
assert (
len(product_title_list) == 15
), f"Expected {product_title_list} to contain product names"




def test_product_price_list(page, set_test_status):
scraper = EcommerceScraper(page)
scraper.select_product_category()
product_price_list = scraper.product_price_grid_list()


if len(product_price_list) == 15:
set_test_status(status="passed", remark="Product prices scraped")
else:
set_test_status(status="failed", remark="Product prices not scraped")


assert isinstance(
product_price_list, list
), f"Expected {product_price_list} to be a list"
assert (
len(product_price_list) == 15
), f"Expected {product_price_list} to contain product prices"




def test_product_image_link_list(page, set_test_status):
scraper = EcommerceScraper(page)
scraper.select_product_category()
image_link_list = scraper.product_image_grid_list()


if len(image_link_list) == 15:
set_test_status(status="passed", remark="Product-image links scraped")
else:
set_test_status(status="failed", remark="Product-image links not scraped")


assert isinstance(image_link_list, list), f"Expected {image_link_list} to be a list"
assert (
len(image_link_list) == 15
), f"Expected {image_link_list} to contain product-image links"




def test_product_detail_list(page, set_test_status):
scraper = EcommerceScraper(page)
scraper.select_product_category()
product_detail_list = scraper.display_scraped_product_details()
print(product_detail_list)


print("Scraped Product Details: ", product_detail_list)

Code Walkthrough:

Import the needed dependencies needed for our test file. Then, create a test_product_title_list() function to test that product names have been scraped successfully. Use the scraper.select_product_category() method to navigate to the Laptops & Notebooks product category.

Now, extract product names using scraper.product_title_grid_list() from the LambdaTest eCommerce Playground website and store them in the product_title_list variable. Check if product_title_list contains 15 items. If true, call the set_test_status() method with the status passed else failed.

After that, raise an assertion that product_title_list has a length of 15.

assertion that product_title_list

Another test_product_price_list() function is invoked for scraping out product prices. After creating a scraper instance and navigating to the Laptops & Notebooks category, use the scraper.product_price_grid_list() function to scrape the product prices and store them in a variable product_price_list.

Now, check if the product_price_list has a length of 15. If true, use the set_test_status() method to set the test status as passed else failed.

Then, raise an assertion to verify if the product_price_list length equals 15.

 verify if the product_price_list length equals 15

To test the extraction of product image links, the test_product_image_link_list() function is created. Navigate to the product category and scrape out the product image links using the scraper.product_image_grid_list() function. After that, save the list returned in a variable image_link_list.

Now, set the test status to passed if the image_link_list has a length of 15. Else, set the status as failed. Assert that image_link_list has 15 items (links) in it.

Assert that image_link_list has 15 items (links) in it

The test_product_detail_list() method tests that product image links, names, and prices have all been scraped and returned in a list of Python dictionaries.

The scraper.display_scraped_product_details() method creates a dictionary consisting of an image link, product name, and product price for each scraped product and saves it in the variable product_detail_list.

Use the set_test_status() function to set the test status to passed if product_detail_list equals 15. If a check fails, set the status to failed.

Now assert that product_detail_list is of type list. Raise another assertion to validate that the items in the product_detail_list are Python dictionaries. After that, validate that product_detail_list has 15 items in it.

items in the product_detail_list are Python dictionaries

Test Execution:

Run the Playwright headless tests using the command:

pytest -s tests/test_ecommerce_scraper.py

Here is the test execution result from the LambdaTest Web Automation Dashboard.

Here is the test execution result from the LambdaTest Web Automation Dashboard

We’ll again execute the above test in headed and headless mode to evaluate execution time. We’ll use hyperfine, a command line benchmarking tool, to compare the execution time/speed in both cases.

eCommerce scraper test in headed mode:

hyperfine "pytest tests/test_ecommerce_scraper.py" -w 2

The average time for the execution of the web scraping test in headed mode was 73.77 seconds, with a minimum time of 69.07 and a maximum time of 85.10 seconds.

eCommerce scraper test in headless mode:

hyperfine "pytest tests/test_ecommerce_scraper.py" -w 2

The average time for the execution of the web scraping test in headless mode was 56.21 seconds, with a minimum time of 53.50 and a maximum time of 60.00 seconds.

There is a difference of 17.56 seconds between headed and headless test modes, showcasing that the headless mode is faster.

Note

Note : Run Playwright headless tests on automation cloud. Try LambdaTest Now!

Writing Headless Tests to Fetch API Metadata

A Playwright headless test will be written to test the APImetadata class.

Test Scenario 1:

  • Go to the LambdaTest Builds API endpoint.
  • Fill in the LambdaTest Username and Password.
  • Click the Sign in button.
  • Raise an assertion statement to validate that JSON builds metadata was returned as a response.

Test Scenario 2:

  • Go to the LambdaTest Sessions API endpoint.
  • Fill in the LambdaTest Username and Password.
  • Click the Sign in button.
  • Raise an assertion statement to validate that JSON Sessions metadata was returned as a response.

Implementation:

This script verifies that the API responsible for returning metadata about builds and sessions is functioning as expected.

def test_builds_metadata(set_test_status):
api_metadata = APImetadata()


builds = api_metadata.fetch_builds_data()
assert builds.get('status') == 'success', f'Expected 'success' as value of "status" key'


if isinstance(builds, dict):
set_test_status(status="passed", remark="API builds metadata returned")
else:
set_test_status(status="failed", remark="API builds metadata not returned")
assert "Meta" in builds, f"Expected 'Meta' in {builds}"




def test_sessions_metadata(set_test_status):
api_metadata = APImetadata()


sessions = api_metadata.fetch_sessions_data()
assert sessions["data"] is not None, f"Expected {sessions['data']} is not None"


if isinstance(sessions, dict):
set_test_status(status="passed", remark="API sessions metadata returned")
else:
set_test_status(status="failed", remark="API sessions metadata not returned")

Code Walkthrough:

Import the APImetadata class and set_test_status fixture functions into the test_fetch_api_metadata.py file. Now, define a ,test_builds_metadata() function to test the APImetadata class.

After that, create an instance, api_metadata, from the APImetadata class. We use the api_metadata.fetch_builds_data() method to fetch LambdaTest builds metadata from API endpoint.

Raise an assertion that the response was successful. Post that, set the test status as passed on success and failed on failure.

set the test status as passed on success and failed on failure

Define a function test_sessions_metadata() in the APImetadata class. Instantiate the APImetadata class. Use the api_metadata.fetch_sessions_data() to fetch LambdaTest sessions metadata from the API endpoint.

Raise an assertion that the response was successful. Then, set the test status as passed on success and failed on failure.

Raise an assertion that the response was successful

Test Execution:

Run the following command to execute this Playwright headless test:

pytest -s tests/test_fetch_api_metadata.py

Here is the test execution result from the LambdaTest Web Automation Dashboard.

Here is the test execution result from the LambdaTest Web Automation Dashboard

Again, we will execute the fetch API metadata test in both headed and headless modes to gauge the time of execution used by each mode of testing.

Fetch API metadata test in headed mode:

hyperfine "pytest -s tests/test_fetch_api_metadata.py" -w 2

The average time for the execution of the fetch API metadata test in headed mode was 1.37 seconds, with a minimum time of 1.25 and a maximum time of 1.52 seconds.

Fetch API metadata test in headless mode:

hyperfine "pytest -s tests/test_fetch_api_metadata.py" -w 2

The average time for the execution of the fetch API metadata test in headed mode was 1.33 seconds, with a minimum time of 1.21 and a maximum time of 1.43 seconds. The test is 4 seconds faster in headless mode versus headed mode.

Conclusion

Headless testing allows tests to be run with minimal human involvement, making it highly efficient for various testing scenarios. The Playwright framework offers powerful classes, methods, and APIs to support headless browser testing.

New to the Playwright framework? Check out this guide on Playwright.

When the objective is to maximize speed, performance, and scalability with low overhead, Playwright's headless browser testing should be considered. It is especially useful in scenarios where user interaction or GUI is unnecessary.

Frequently Asked Questions (FAQs)

  • General ...
What is the headless mode in Playwright?
Headless mode runs a browser without a graphical interface, allowing tests to execute faster and more efficiently in the background.
What are the disadvantages of Playwright?
Playwright has limited community support compared to older tools, and it requires more system resources for parallel execution.
How do I turn off headless mode in Python Playwright?
To disable headless mode, set the headless=False option when launching the browser: browser = playwright.chromium.launch(headless=False).

Did you find this page helpful?

Helpful

NotHelpful

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud