Run Web Automation Tests at Scale with Agentic AI

Generate tests with AI agents or automate test suites with Selenium, Playwright and other frameworks. Run tests in parallel on TestMu AI scalable cloud grid.

Playwright Testing

How to Write and Run Your First Playwright Test

Learn how to write your first Playwright test, run it locally and on a remote grid, and fix the errors that stop Playwright working with Selenium Grid.

Last Updated on:

The hard part of building a test suite is rarely writing the tests. It is running hundreds of them across browsers your laptop does not have, in an amount of time your pipeline can tolerate. That is the point where you stop running Playwright locally and start running it on a grid.

This tutorial writes a first Playwright test against a live demo store, runs it in headed mode so you can watch it work, then takes the same suite out to a self-hosted Selenium Grid and a cloud grid. Every command and locator here comes from a suite that actually runs, and the errors are the ones you will hit rather than the happy path.

Overview

To write your first Playwright test, set a baseURL in playwright.config.js, create a spec file, then use test() with the page fixture to navigate, act, and assert. Run it with npx playwright test, adding --headed to watch the browser work. The same suite then runs unchanged on a remote grid.

Writing your first Playwright test

  • Base URL config: Setting baseURL in playwright.config.js lets every Playwright spec start from the same origin, so page.goto('/') resolves without repeating the full address in each test file.
  • Role and text locators: getByRole and getByText match elements by what a user sees, which stays readable as the DOM shifts. When several elements match, scope the locator to a parent with filter() instead of writing a brittle CSS path.
  • User-visible assertions: Assert on what a person would actually notice, such as the product name appearing on the cart page, rather than an internal attribute that can pass while the feature is visibly broken.
  • Headed mode: Adding --headed to the Playwright test command opens a visible browser, which is the fastest way to see why a locator is not matching on a first run.

Taking the suite to a Selenium Grid

  • SELENIUM_REMOTE_URL: Pointing this variable at a Selenium 4 hub root, such as http://localhost:4444, sends Playwright tests to the grid with no change to test code. The /wd/hub suffix from Selenium 3 examples is not needed.
  • WebSocket buffer flag: A bug reported against Selenium Grid 4.5.2 drops the Chrome DevTools Protocol connection Playwright depends on mid-test. Upgrade the grid first. If the connection still drops, set -Djdk.httpclient.websocket.intermediateBufferSize=3000000 on the hub and on every node.
  • Playwright treats the Selenium Grid integration as experimental and drives only Google Chrome and Microsoft Edge through it, so Firefox and WebKit coverage has to come from somewhere other than the grid.

Taking the suite to a cloud grid

  • Projects matrix: Each browser and OS target is one entry in the projects array of playwright.config.js, so Playwright multiplies specs by projects. Six specs across three configurations became eighteen sessions in this walkthrough.
  • Session artifacts: TestMu AI's test automation cloud captures video, network logs, console logs, and command logs for each session when the capabilities turn them on, which is how the Edge 90 failure below was diagnosed.
  • Engine coverage: The cloud grid supplies 3,000+ browser and OS combinations, including the Firefox and WebKit engines that the Selenium Grid path cannot launch at all.

Selenium Grid distributes tests across machines you control, and for a WebDriver suite it remains a sensible default. The awkward question is where those machines come from. Hardware you own and VMs you rent both carry patching, driver upgrades, and capacity planning.

A cloud grid removes that question by renting maintained browsers instead of machines, one of the more practical benefits of cloud testing. You send tests, they run in parallel, you get results and artifacts back.

Playwright is a reasonable place to test this, because adoption is no longer in question. The @playwright/test package on npm records over 76 million weekly downloads at version 1.64.0, so the execution problem below is one a very large number of teams hit.

This Playwright tutorial walks through building a small suite and then running it in three places, so you can see exactly where each option stops. If you are preparing for an interview, Playwright interview questions covers the theory side.

Here is what this guide on Playwright testing covers:

  • Build a small but realistic test suite.
  • Run it on your local machine, on a self-hosted Selenium Grid, and on a cloud grid.
  • Fix the version and browser limits that stop the Selenium Grid route working.
  • Decide which of the two remote options your team actually needs.

Building a Playwright test suite

We will build the suite with Playwright itself. Microsoft ships it on a roughly monthly cadence, and it now pulls far more npm traffic than the WebDriver-based automation testing frameworks it competes with. In the week of 5 October 2026, npm recorded 76,192,279 downloads of @playwright/test against 1,780,111 for selenium-webdriver.

npm download trend chart for the Playwright package

Playwright download trend on npm trends

You need two things installed before the setup steps below.

If you need to get up to speed with installation and setup, follow my earlier blog on getting started with the Playwright framework.

For this tutorial on running your first Playwright test, we will be writing tests for the TestMu AI eCommerce Playground with dummy data and content - perfect for experimenting with test automation.

Headed mode is useful while writing a test, but CI runs it headless by default, which is covered in Playwright headless testing.

For tests that need to wait on specific backend calls instead of full page navigations, this guide to Playwright waitForResponse covers the predicate patterns, click-then-await ordering, and response object methods that make API-driven UI tests deterministic in CI.

Test approach for writing your first Playwright test

Two journeys drive this Playwright end to end testing suite, both common enough that you have probably written something similar.

  • The first is 'ADD TO CART,' the site's core functionality as a user. We should be able to select an item, add it to our cart and progress to the checkout journey. Any discrepancies in product items or selection along the way should be caught by our test.
  • The second combines 'SEARCH' and 'FILTERING.' We should be able to search for a particular item or category of items and then filter the list returned. The common filtering techniques could be by most popular, lowest price, or most recently added.

Variations on these journeys then add test coverage while reusing the same code. The final project structure:

Test approach for writing your first Playwright test
Test across 3000+ browser and OS environments with TestMu AI

Watch this Playwright tutorial that covers everything you need to get you up and running with the Microsoft Playwright framework with TypeScript.

Youtube thumbnail

Writing your first Playwright test

This is a fairly standard user journey, I'd like to choose an item to purchase, add it to my cart, and the item should be visible when I go to checkout.

In the playwright.config.js, I've defined the eCommerce playground base URL, so all of our tests will start from here.

  use: {
    /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
    actionTimeout: 0,
    /* Base URL to use in actions like `await page.goto('/')`. */
    // baseURL: 'http://localhost:3000',
 
    /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
    trace: 'on-first-retry',
    baseURL: 'https://ecommerce-playground.lambdatest.io'
  },

And in the lambdatest-setup.js, which is referenced in our test files, we define the cloud capabilities and a page fixture. When the project name ends in @lambdatest, the fixture connects to a cloud browser through the wsEndpoint and reads the credentials from environment variables. For any other project it passes on the local page.

/**
 * Import the `test` object from this file in your specs.
 * Projects whose name ends in @lambdatest run on the cloud. Every other project runs locally.
 */
const base = require('@playwright/test')
const path = require('path')

// LambdaTest capabilities
const capabilities = {
  'browserName': 'Chrome', // Browsers allowed: `Chrome`, `MicrosoftEdge`, `pw-chromium`, `pw-firefox` and `pw-webkit`
  'browserVersion': 'latest',
  'LT:Options': {
    'platform': 'Windows 10',
    'build': 'Playwright Build - Full suite',
    'name': 'Playwright Test',
    'user': process.env.LT_USERNAME,
    'accessKey': process.env.LT_ACCESS_KEY,
    'network': true,
    'video': true,
    'console': true,
    'tunnel': false, // Add tunnel configuration if testing locally hosted webpage
    'tunnelName': '', // Optional
    'geoLocation': '', // country code can be fetched from https://www.testmuai.com/capabilities-generator/
    'playwrightClientVersion': require('@playwright/test/package.json').version
  }
}

// Patch the capabilities from the project name: browserName:browserVersion:platform@lambdatest
const modifyCapabilities = (configName, testName) => {
  const [browserName, browserVersion, platform] = configName.split('@lambdatest')[0].split(':')
  capabilities.browserName = browserName || capabilities.browserName
  capabilities.browserVersion = browserVersion || capabilities.browserVersion
  capabilities['LT:Options'].platform = platform || capabilities['LT:Options'].platform
  capabilities['LT:Options'].name = testName
}

exports.test = base.test.extend({
  page: async ({ page }, use, testInfo) => {
    if (!testInfo.project.name.includes('@lambdatest')) {
      // Local project: use the page Playwright already created
      await use(page)
      return
    }

    // Cloud project: connect to a remote browser and hand its page to the test
    modifyCapabilities(testInfo.project.name, `${testInfo.title} - ${path.basename(testInfo.file)}`)
    const browser = await base.chromium.connect(
      `wss://cdp.lambdatest.com/playwright?capabilities=${encodeURIComponent(JSON.stringify(capabilities))}`
    )
    const ltPage = await browser.newPage(testInfo.project.use)
    await use(ltPage)

    // Report the result to the dashboard, then end the session
    const testStatus = {
      action: 'setTestStatus',
      arguments: { status: testInfo.status, remark: testInfo.error?.message }
    }
    await ltPage.evaluate(() => {}, `lambdatest_action: ${JSON.stringify(testStatus)}`)
    await ltPage.close()
    await browser.close()
  }
})

The fixture still asks for Playwright's built-in page, so a local browser also starts for each cloud test. Keep the browsers from npx playwright install on the machine that runs the suite.

This is the complete test file for add-to-cart-laptop.spec.js, I'll walk through each step and explain why we have taken this approach.

// @ts-check
const { test } = require('../../lambdatest-setup')
const { expect } = require('@playwright/test')

test.describe('Add to cart', () => {
  test('Add to cart', async ({ page }) => {

    // Navigate to base url
    await page.goto('/')

    // Click Shop by Category
    await page.getByRole('button', { name: 'Shop by Category' }).click()

    // Click Laptops & Notebooks
    await page.getByRole('link', { name: 'Laptops & Notebooks' }).click()

    // Wait for the category page to finish loading
    await page.waitForURL(/path=18/)

    // Locate the product card
    const product = page.locator('.product-thumb').filter({ hasText: 'HTC Touch HD' })

    // Hover over product
    await product.hover()

    // Wait for element
    await expect(product.getByRole('button').first()).toBeVisible()

    // Click add to cart
    await product.getByTitle('Add to Cart').click()

    // Wait for the confirmation popup
    await expect(page.getByRole('alert')).toBeVisible()

    // Click view cart
    await page.getByRole('link', { name: 'View Cart' }).click()

    // Wait for the cart page to finish loading
    await page.waitForURL(/checkout\/cart/)

    // Assert correct product added to cart
    await expect(page.locator('#content').getByText('HTC Touch HD')).toBeVisible()
  })
})

The completed test suite we will be referencing is on GitHub if you want to follow along.

There is a simple goto navigation, which extends upon our base URL. This is useful if we navigate to sub-pages within the website, as we won't need to type the full base URL every time.

// Navigate to base url
await page.goto('/')
Writing your first Playwright test

The store offers several ways to reach a product: the 'Shop by Category' sub-menu, the search bar, the banners, and product icons further down the page.

We will use the 'Shop by Category' menu, so the first action is a click to expand it. A role locator names the element the way a user would describe it, here a button called Shop by Category, and it still lets you drill down when several elements share a name.

Note - To find the elements, I like to use a mix of Playwright Codegen and manual inspection in DevTools. The Python equivalent is walked through in this Playwright Python tutorial.

// Click Shop by Category
await page.getByRole('button', { name: 'Shop by Category' }).click()
Shop by Category Playwright test

With the menu expanded, pick a category. Let's use the same role locator approach as before, this time for a link. The same idea in WebDriver is covered in locators in Selenium WebDriver.

// Click Laptops & Notebooks
await page.getByRole('link', { name: 'Laptops & Notebooks' }).click()

// Wait for the category page to finish loading
await page.waitForURL(/path=18/)
locatorstrategy

That lands on the Laptops & Notebooks page. The non-laptop images are placeholder content, which is normal on a demo store. The waitForURL call holds the test until the new page fires its load event. The store defines its cart functions in scripts that run late, so a click sent before then does nothing.

menu presenting a few options

Hovering a gallery item reveals a small menu. The first icon adds the product to the cart, so the test hovers, then clicks it.

// Locate the product card
const product = page.locator('.product-thumb').filter({ hasText: 'HTC Touch HD' })

// Hover over product
await product.hover()

// Wait for element
await expect(product.getByRole('button').first()).toBeVisible()

// Click add to cart
await product.getByTitle('Add to Cart').click()

The product locator is the CSS class of the card, filtered down to the one card that contains the product name. The hover is that locator followed by .hover().

Playwright Codegen recording

The wait and the click each extend that locator, deliberately in two different ways so you can compare them.

The wait picks by position. Inside the card, getByRole('button') matches all four buttons and .first() takes the first one.

locator path

The click uses getByTitle('Add to Cart') instead, reaching the same element by a different route. The buttons show only an icon, so the title attribute is the readable name they carry.

The locator approach above breaks down into five steps:

  • Identify product locator.
  • Hover over the product.
  • Identify the actionable element we want to select.
  • Drill down into the group of elements.
  • Pick the button by its title, or by position with .first() or .nth() when nothing names it.

Prefer the second form. A locator that names its target keeps working when the buttons are reordered, while .first(), .last() and .nth(index) take whatever sits in that position, which is why the Playwright docs advise against them.

Running to this point pops a confirmation in the top right offering View Cart and Checkout.

view cart, and checkout

This test only checks the product reached the cart, so click View Cart. If the popup is slow, wait for it first:

View Cart
// Wait for element
await expect(page.getByRole('alert')).toBeVisible()
// Click view cart
await page.getByRole('link', { name: 'View Cart' }).click()

// Wait for the cart page to finish loading
await page.waitForURL(/checkout\/cart/)

The final step asserts on the cart page itself.

shopping cart page

Matching on the product name proves both that something was added and that it was the right item.

// Assert correct product added to cart
await expect(page.locator('#content').getByText('HTC Touch HD')).toBeVisible()

That covers a full journey with several interaction and assertion types.

This script runs the laptop test in headed mode. In the terminal, type npm run test.

"scripts": {
        "test": "npx playwright test tests/add-to-cart/add-to-cart-laptop.spec.js --headed"
      },
npm run test

With a base test in place, variations follow cheaply.

Run your Playwright tests with AWS marketplace directly on the cloud.

Expanding the Playwright test suite

The store has enough categories and products to expand coverage with little more than locator changes.

The files and locators here are duplicated for readability. In a real suite the Page Object Model (POM) removes that duplication and keeps maintenance manageable.

// Locate the product card
const product = page.locator('.product-thumb').filter({ hasText: 'Nikon D300' })

// Hover over product
await product.hover()

// Wait for element
await expect(product.getByRole('button').first()).toBeVisible()

// Click add to cart
await product.getByTitle('Add to Cart').click()
Playwright tests

These still run quickly locally. The script now points at the whole test folder:

"scripts": {
        "test": "npx playwright test tests/add-to-cart/ --headed"
      },
elements to interact

Next, a test that searches for an item, sorts by most popular, and adds the top result to the cart. It reuses code that page objects or helper classes should eventually own.

The search field is matched on its role and accessible name, filled with 'ipod', then submitted.

// Fill the search box
await page.getByRole('textbox', { name: 'Search For Products' }).fill('ipod')
// Click the Search button
await page.getByRole('button', { name: 'Search' }).click()
'Popular'

On the results page, select 'Popular' from the 'Sort by' dropdown, here by index rather than text.

// Sort by 'Popular'
await page.getByRole('combobox', { name: 'Sort By:' }).selectOption({ index: 2 })

// Wait for the sorted results to finish loading
await page.waitForURL(/sort=p.viewed/)
reorder the items for us

That reloads the page with the list reordered, so the test waits for the new URL, then hovers the first product and adds it to the cart. Without the hover the buttons stay tucked under the toolbar above the results, and the click cannot land.

// Hover over the first product
const firstProduct = page.locator('.product-thumb').first()
await firstProduct.hover()

// Wait for element
await expect(firstProduct.getByTitle('Add to Cart')).toBeVisible()

// Click add to cart
await firstProduct.getByTitle('Add to Cart').click()
new test from which we can create iterations

That gives a second journey to iterate on, six tests in total. Running them in parallel is where the local machine starts to strain.

running 6 tests

When running 6 tests, I started to see intermittent failures, despite adding waits and timeouts - this is where we start approaching the limits of a single machine for test execution and need to look towards a distributed solution.

single machine for test execution

A Selenium Grid has three parts: a client, a hub, and the nodes that own the browsers.

automation

The client sends tests to the hub, and the hub distributes them across nodes. With 9 tests and 3 Chrome instances per node, each node runs 3 in parallel.

Running Playwright tests on Selenium Grid

Playwright can hand execution to a Selenium Grid 4 hub by setting a single environment variable, with no change to the test code. Before you build a pipeline on it, read the two constraints the Playwright Selenium Grid documentation states plainly.

  • The integration is experimental. Playwright says the feature "is experimental and is prioritized accordingly" and warns there is a risk of it breaking, because it depends on Selenium continuing to expose the Chrome DevTools Protocol.
  • It drives Google Chrome and Microsoft Edge only. Firefox and WebKit cannot be launched through the Selenium Grid path, so a grid alone will not give you the cross-engine coverage Playwright is usually chosen for.

We will run the grid locally. That is not the intended architecture, but it shows the routing and where it stops.

Selenium Grid setup and test execution

You need Java on the PATH and the Selenium Server (Grid) jar. Chrome and Edge drivers are managed by Selenium Manager in current Selenium 4 releases, so the manual Chromedriver and Geckodriver downloads older tutorials ask for are no longer required, and Geckodriver will not help here in any case because Playwright cannot drive Firefox through the grid.

Start the grid from the folder holding the jar. Selenium's latest stable server release is 4.51.0, so substitute whichever version you downloaded.

java -jar selenium-server-4.51.0.jar standalone

In the terminal, you should see the following:

00:24:02.480 INFO [LoggingOptions.configureLogEncoding] - Using the system default encoding
00:24:02.481 INFO [OpenTelemetryTracer.createTracer] - Using OpenTelemetry for tracing
00:24:02.690 INFO [NodeOptions.getSessionFactories] - Detected 10 available processors
00:24:02.691 INFO [NodeOptions.discoverDrivers] - Looking for existing drivers on the PATH.
00:24:02.691 INFO [NodeOptions.discoverDrivers] - Add '--selenium-manager true' to the startup command to setup drivers automatically.
00:24:05.504 WARN [SeleniumManager.lambda$runCommand$0] - Unable to discover proper msedgedriver version in offline mode
00:24:06.235 WARN [SeleniumManager.lambda$runCommand$0] - Unable to discover proper geckodriver version in offline mode
00:24:06.992 WARN [SeleniumManager.lambda$runCommand$0] - Driver unavailable: /Applications/Safari Technology Preview.app/Contents/MacOS/safaridriver
00:24:06.999 INFO [NodeOptions.report] - Adding Chrome for {"browserName": "chrome","platformName": "mac"} 10 times
00:24:07.000 INFO [NodeOptions.report] - Adding Edge for {"browserName": "MicrosoftEdge","platformName": "mac"} 10 times
00:24:07.000 INFO [NodeOptions.report] - Adding Firefox for {"browserName": "firefox","platformName": "mac"} 10 times
00:24:07.001 INFO [NodeOptions.report] - Adding Safari for {"browserName": "safari","platformName": "mac"} 1 times
00:24:07.020 INFO [Node.<init>] - Binding additional locator mechanisms: relative
00:24:07.027 INFO [LocalGridModel.setAvailability] - Switching Node fe9c7508-b93c-4d23-8b0b-52af7ab8de92 (uri: http://192.168.1.5:4444) from DOWN to UP
00:24:07.028 INFO [LocalNodeRegistry.add] - Added node fe9c7508-b93c-4d23-8b0b-52af7ab8de92 at http://192.168.1.5:4444. Health check every 120s
00:24:07.070 INFO [Standalone.execute] - Started Selenium Standalone 4.51.0 (revision 35c5fab 35c5fab4a58fa5878edb255eb7747b04ca51a015): http://192.168.1.5:4444

The console reports the processors and drivers it detected and registers browser instances against them. The grid console is then served at http://localhost:4444/, and opening it shows the registered nodes:

Selenium Grid 4.51.0 console overview showing one node with Chrome, Firefox, Edge and Safari slots and 0 sessions

Now send tests to the grid. Point SELENIUM_REMOTE_URL at the hub root, as the Playwright docs do. Playwright appends /session to that URL itself, and Selenium Grid 4 still routes the /wd/hub prefix that Selenium 3 examples append, so the suffix is optional.

SELENIUM_REMOTE_URL=http://localhost:4444 npx playwright test

Playwright will execute its tests on the Selenium Grid remote URL, and we should be able to see 5 sessions actively running, one for each of the 5 workers in this run.

Selenium Grid console overview showing 5 sessions running out of a maximum concurrency of 10Selenium Grid Sessions view listing 5 running Chrome sessions with start time, duration and node URI
$ SELENIUM_REMOTE_URL=http://localhost:4444 npx playwright test

Running 6 tests using 5 workers

  ✓  3 [chrome] › tests/add-to-cart/add-to-cart-phone.spec.js:6:3 › Add to cart › Add to cart (15.4s)
  ✓  5 [chrome] › tests/search-item-popular/search-item-iphone.spec.js:6:3 › Search item popular › Search for item (16.1s)
  ✓  1 [chrome] › tests/add-to-cart/add-to-cart-laptop.spec.js:6:3 › Add to cart › Add to cart (17.9s)
  ✓  4 [chrome] › tests/add-to-cart/add_to_cart_component.spec.js:6:3 › Add to cart › Add to cart (18.7s)
  ✓  2 [chrome] › tests/search-item-popular/search-item-imac.spec.js:6:3 › Search item popular › Search for item (25.0s)
  ✓  6 [chrome] › tests/search-item-popular/search-item-ipod.spec.js:6:3 › Search item popular › Add to cart (18.1s)

  6 passed (36.6s)

That covers the basics. Larger setups use multiple VMs, with Docker images and compose templates to spin them up.

Scaling out from here means running more tests concurrently, limited by hardware cost and by how much grid maintenance your team wants to own.

Selenium Grid limits and version compatibility

If the run fails with a dropped WebSocket connection rather than a test failure, check the Selenium version before you debug the tests.

A bug reported against Selenium Grid 4.5.2 treats a multi-frame WebSocket message as final, tracked as WebSocket incorrectly assumes final frame in multi-frame text send in the Selenium repository. Playwright pushes Chrome DevTools Protocol traffic that exceeds the default buffer, so the connection breaks mid-session. The issue was closed in January 2023 without a confirmed fix, and the Selenium changelog lists "Handle fragmented websocket messages" under 4.9.1, so try a current release first. If the connection still drops, the workaround recorded on that issue is to raise the intermediate buffer size on the hub and on every node.

java -Djdk.httpclient.websocket.intermediateBufferSize=3000000 -jar selenium-server-4.51.0.jar standalone

Running the grid in Docker means setting the same flag through SE_JAVA_OPTS on the hub service and on each node service, not just on the hub. The compose file below also publishes the hub port and sets SE_NODE_GRID_URL on the node, which Playwright needs to reach the browser. Docker images are published after the server jar, so use a tag that exists on Docker Hub.

services:
  selenium-hub:
    image: selenium/hub:4.49.0
    ports:
      - "4444:4444"
    environment:
      - SE_JAVA_OPTS=-Djdk.httpclient.websocket.intermediateBufferSize=3000000
  chrome:
    image: selenium/node-chrome:4.49.0
    shm_size: 2gb
    depends_on:
      - selenium-hub
    environment:
      - SE_JAVA_OPTS=-Djdk.httpclient.websocket.intermediateBufferSize=3000000
      - SE_EVENT_BUS_HOST=selenium-hub
      # Hub URL as seen from the machine that runs Playwright
      - SE_NODE_GRID_URL=http://localhost:4444

Two further constraints are worth knowing before you commit. Playwright expects a Selenium 4 hub, with Selenium 3 supported only on a best-effort basis. Playwright also opens the CDP WebSocket at the address each node registers with, so on a hub and node grid set SE_NODE_GRID_URL on every node to the hub URL. That gives Playwright an address it can reach from the machine running the tests.

Add those together and the self-hosted grid gives you Chrome and Edge, on machines you patch, with a documented risk of breaking on upgrade. Whether that trade is worth it depends on how much browser coverage you actually need, which the cloud run below makes concrete.

Note

Note: Skip the grid maintenance. TestMu AI runs your existing Playwright specs on Chrome, Edge, Firefox, and WebKit across 3,000+ browser and OS combinations, with video, network, and console logs on every session. Start running tests free

Running your first Playwright test on cloud grid

Now run the same suite across more platforms and browsers. These examples use the TestMu AI cloud grid, which needs two things configured first.

To run these Playwright tests on the TestMu AI HyperExecute grid using JUnit, refer to the Playwright JUnit on HyperExecute guide.

Cloud testing platforms like TestMu AI help you perform cross browser testing at scale. The online browser farm covers 3,000+ browser and OS combinations, including Chrome, Chromium, Microsoft Edge, Mozilla Firefox, and WebKit, so the engines Selenium Grid could not give you are available from the same config file.

Youtube thumbnail

Subscribe to the TestMu AI YouTube Channel, stay updated with the latest playwright tutorial, and discover tutorials around topics like automated browser testing, Cypress E2E testing, mobile app testing, and more.

Prerequisites to run your first Playwright test on cloud

We will use a new configuration file that references LT_USERNAME AND LT_ACCESS_KEY environment variables.

'user': process.env.LT_USERNAME,
'accessKey': process.env.LT_ACCESS_KEY

You can find this information under Dashboard, Automation, or User settings. Copy the values and set them as your local machines environment variables.

information under Dashboard

Playwright test configuration on cloud

In our playwright.config.js, we have now raised the test timeout and set a few new projects:

// Cloud sessions took from 24 seconds to about 2 minutes in test runs
timeout: 300000,
projects: [
  {
    name: 'chrome:latest:MacOS Sonoma@lambdatest',
    use: {
      viewport: { width: 1920, height: 1080 }
    }
  },
  {
    name: 'chrome:latest:Windows 10@lambdatest',
    use: {
      viewport: { width: 1920, height: 1080 }
    }
  },
  {
    name: 'MicrosoftEdge:latest:Windows 10@lambdatest',
    use: {
      viewport: { width: 1920, height: 1080 }
    }
  }
]

There is logic in the lambdatest-setup.js file, which runs our tests based on the following format: browserName:browserVersion:platform (separate by colons). You can edit and change these values or add new configurations if needed. You can also modify the viewport size and device emulation values.

This list passed in a run in October 2026 on @playwright/test 1.64.0: six specs on three configurations, 18 tests. Edge is set to latest for a reason shown further down. The cloud also offers Firefox and WebKit as pw-firefox and pw-webkit. Those bundled engines are tied to specific Playwright client versions, so run one spec on each before you add them to the matrix.

Playwright test execution on cloud

With the variables and project configurations in place, we are ready to run the tests. Run the npm run test, and you should see tests executing in the terminal.

$ npx playwright test --workers=2 --reporter=line

Running 18 tests using 2 workers

[1/18] [chrome:latest:MacOS Sonoma@lambdatest] › tests/add-to-cart/add-to-cart-laptop.spec.js:6:3 › Add to cart › Add to cart
[2/18] [chrome:latest:MacOS Sonoma@lambdatest] › tests/add-to-cart/add_to_cart_component.spec.js:6:3 › Add to cart › Add to cart
[3/18] [chrome:latest:MacOS Sonoma@lambdatest] › tests/add-to-cart/add-to-cart-phone.spec.js:6:3 › Add to cart › Add to cart
[4/18] [chrome:latest:MacOS Sonoma@lambdatest] › tests/search-item-popular/search-item-imac.spec.js:6:3 › Search item popular › Search for item
[5/18] [chrome:latest:MacOS Sonoma@lambdatest] › tests/search-item-popular/search-item-iphone.spec.js:6:3 › Search item popular › Search for item
[6/18] [chrome:latest:MacOS Sonoma@lambdatest] › tests/search-item-popular/search-item-ipod.spec.js:6:3 › Search item popular › Add to cart
[7/18] [chrome:latest:Windows 10@lambdatest] › tests/add-to-cart/add_to_cart_component.spec.js:6:3 › Add to cart › Add to cart
[8/18] [chrome:latest:Windows 10@lambdatest] › tests/add-to-cart/add-to-cart-laptop.spec.js:6:3 › Add to cart › Add to cart
[9/18] [chrome:latest:Windows 10@lambdatest] › tests/add-to-cart/add-to-cart-phone.spec.js:6:3 › Add to cart › Add to cart
[10/18] [chrome:latest:Windows 10@lambdatest] › tests/search-item-popular/search-item-imac.spec.js:6:3 › Search item popular › Search for item
[11/18] [chrome:latest:Windows 10@lambdatest] › tests/search-item-popular/search-item-iphone.spec.js:6:3 › Search item popular › Search for item
[12/18] [chrome:latest:Windows 10@lambdatest] › tests/search-item-popular/search-item-ipod.spec.js:6:3 › Search item popular › Add to cart
[13/18] [MicrosoftEdge:latest:Windows 10@lambdatest] › tests/add-to-cart/add_to_cart_component.spec.js:6:3 › Add to cart › Add to cart
[14/18] [MicrosoftEdge:latest:Windows 10@lambdatest] › tests/add-to-cart/add-to-cart-laptop.spec.js:6:3 › Add to cart › Add to cart
[15/18] [MicrosoftEdge:latest:Windows 10@lambdatest] › tests/add-to-cart/add-to-cart-phone.spec.js:6:3 › Add to cart › Add to cart
[16/18] [MicrosoftEdge:latest:Windows 10@lambdatest] › tests/search-item-popular/search-item-imac.spec.js:6:3 › Search item popular › Search for item
[17/18] [MicrosoftEdge:latest:Windows 10@lambdatest] › tests/search-item-popular/search-item-iphone.spec.js:6:3 › Search item popular › Search for item
[18/18] [MicrosoftEdge:latest:Windows 10@lambdatest] › tests/search-item-popular/search-item-ipod.spec.js:6:3 › Search item popular › Add to cart
  18 passed (4.7m)

Set the number of workers to the parallel sessions your plan allows. The run above used --workers=2 on a plan with two. With more workers than sessions, the extra sessions wait in the cloud queue, and in an earlier run of this suite with Playwright's default worker count one test timed out while its page was still being set up.

For running web accessibility tests with Playwright on the TestMu AI cloud, refer to the Playwright accessibility test documentation.

For Python-based Playwright tests on the TestMu AI cloud, refer to the Python with Playwright documentation.

Youtube thumbnail

A matrix earns its keep when one configuration behaves differently. With Edge pinned to version 90 instead of latest, the laptop spec fails at its first role locator, because Edge 90 has no element.checkVisibility() and Playwright's role locators call it. The dashboard shows the failed click and the error in the test remark.

TestMu AI automation dashboard showing the add to cart test failed on Microsoft Edge 90 and Windows 10, with the element.checkVisibility is not a function error in the test remark and the failed click highlighted

The terminal shows the same failure:

$ npx playwright test tests/add-to-cart/add-to-cart-laptop.spec.js --project="MicrosoftEdge:90:Windows 10@lambdatest" --reporter=line

Running 1 test using 1 worker

[1/1] [MicrosoftEdge:90:Windows 10@lambdatest] › tests/add-to-cart/add-to-cart-laptop.spec.js:6:3 › Add to cart › Add to cart
  1) [MicrosoftEdge:90:Windows 10@lambdatest] › tests/add-to-cart/add-to-cart-laptop.spec.js:6:3 › Add to cart › Add to cart

    Error: locator.click: TypeError: element.checkVisibility is not a function
        at computeElementStyleVisibilityVisible (<anonymous>:3159:16)
...
    Call log:
      - waiting for getByRole('button', { name: 'Shop by Category' })
...
  1 failed
    [MicrosoftEdge:90:Windows 10@lambdatest] › tests/add-to-cart/add-to-cart-laptop.spec.js:6:3 › Add to cart › Add to cart

The fix is to test the browser versions your users run. On Edge latest all six specs pass, which is why the list above uses it.

On the local grid the 6 tests ran on one machine's Chrome. On the cloud grid the same 6 specs became 18 sessions across Windows and macOS on Chrome and Edge, from a config change rather than new hardware.

Test infrastructure that does not break, from TestMu AI

Which grid should you use?

For this tutorial the answer follows from what you just saw. The self-hosted grid ran the suite on Chrome, and the cloud grid ran the same six specs as eighteen sessions across Chrome and Edge on Windows and macOS.

Stay on the self-hosted grid when the app must not leave your network and Chrome plus Edge is genuinely enough coverage. Move to a cloud grid when you need Firefox or WebKit, need macOS without buying Macs, or do not want browser upgrades sitting on your team backlog.

Migrating is a small change rather than a rewrite: keep the test code, swap the endpoint for the remote hub, add LT_USERNAME and LT_ACCESS_KEY, generate the capabilities block, run one config to confirm the wiring, then fan out the matrix. For the full comparison of grid options, including sharding and worker tuning, see Playwright grid.

Troubleshooting Playwright grid errors

Four failures account for most of the time lost when moving Playwright onto a remote grid. Each has a specific cause rather than a general flakiness explanation.

  • The connection drops part-way through a test. Upgrade the grid to a current Selenium release first. If it still drops, set -Djdk.httpclient.websocket.intermediateBufferSize=3000000 on the hub and on every node, not just the hub.
  • Playwright cannot reach the hub at all. Check that SELENIUM_REMOTE_URL holds the hub address and port, then run a Selenium WebDriver script against the same URL. The Playwright docs ask you to confirm the grid works with WebDriver before you connect Playwright to it.
  • Firefox or WebKit projects never start on the grid. That is expected, since the Selenium Grid integration launches Chrome and Edge only, so those engines need a cloud grid or local execution.
  • Tests connect but time out on a node. Start each node with SE_NODE_GRID_URL pointing at the hub, because Playwright connects to the CDP WebSocket address the node registers with, and that address has to be reachable from the Playwright machine. Running with DEBUG=pw:browser* prints the endpoint Playwright tried.

Treat a failure that appears on only some configurations as a real defect until proven otherwise. In the cloud run above, Edge 90 failed at every role locator because it lacks a browser API that Playwright now calls, a difference the local Chrome run could never have surfaced.

For grid-side execution problems, the Playwright JUnit on HyperExecute guide linked earlier covers orchestration and retries, and Playwright sharding explains how to split a suite across workers once concurrency is no longer the bottleneck.

Conclusion

Start by running one spec against a single remote configuration. Export LT_USERNAME and LT_ACCESS_KEY, add one entry to the projects array, and confirm the session appears in the dashboard before you widen the matrix. Wiring problems are far easier to read on one config than on eighteen.

If your coverage stops at Chrome and Edge, the self-hosted grid is doing its job and you can stay there. If you need macOS or more browsers, the config in this guide runs on TestMu AI's Playwright cloud, and the parallel testing with Playwright documentation covers how concurrency is allocated once the matrix grows.

The complete suite used here is on the GitHub repository linked earlier in this guide, so you can clone it and run the same comparison yourself.

Testing from the terminal? Kane CLI by TestMu AI lets SDETs and developers plan, write, run and debug tests from the command line.

To run your first test, start free, or
for a full walkthrough of the product.

Author

...

Srinivasan Sekar

Blogs: 20

  • Twitter
  • Linkedin

Srinivasan Sekar is Director of Engineering at TestMu AI, where he leads engineering and open-source initiatives behind the Selenium and Appium automation grid and owns TestMu AI's MCP Server. A committer to Appium and a contributor to Selenium, WebdriverIO, Taiko, and AppiumTestDistribution, he brings over 15 years of experience in quality engineering and open-source technologies. He is the author of the Apress book 'The MCP Standard: A Developer's Guide to Building Universal AI Tools with the Model Context Protocol,' a Certified Kubernetes and Cloud Native Associate, and an international conference speaker. Before TestMu AI he spent over eight years at Thoughtworks as a Principal Consultant and Quality Architect. Srinivasan holds a B.Tech in Information Technology from Anna University.

Reviewer

...

Harshit Paul

Reviewer

  • Linkedin

Harshit Paul is Director of Product Marketing at TestMu AI, with over 8 years of experience in product and growth marketing for developer and QA tools, leading the Agentic AI in Quality Engineering space. He has authored 80+ technical articles for TestMu AI on software testing and automation, and hosted webinars on Selenium, automation testing, browser compatibility, DevOps, and continuous testing. He has led go-to-market and technical marketing initiatives across software testing products, contributing to SEO, content strategy, and developer marketing. He began his career as a certified Salesforce developer at Wipro Technologies, where he worked for 2 years before moving into marketing. Harshit holds a degree in computer programming from Vivekananda Institute of Professional Studies.

First Playwright Test FAQs

Did you find this page helpful?

More Related Blogs

TestMu AI forEnterprise

Get access to solutions built on Enterprise
grade security, privacy, & compliance

  • Advanced access controls
  • Advanced data retention rules
  • Advanced Local Testing
  • Premium Support options
  • Early access to beta features
  • Private Slack Channel
  • Unlimited Manual Accessibility DevTools Tests