Skip to main content

Parallel and cross-browser testing with Playwright

Posted By

Neha Nemade

Date Posted
13-Jun-2025

Welcome to Part 2 of our Playwright automation series! In Part 1, I walked you through setting up Playwright, writing your first test, and exploring its powerful features like the Codegen tool, Playwright Inspector, and CI/CD integration. If you haven’t read it yet, I recommend starting there to get up to speed.

In this second installment, let’s dive deeper into advanced Playwright capabilities, focusing on parallel and cross-browser testing, handling form elements, managing timeouts and errors, and working with frames and multi-tab scenarios. This guide will equip you with practical techniques to enhance your automation workflow.

Why parallel and cross-browser testing matters

Parallel and cross-browser testing are critical for efficient, scalable test suites. Playwright’s out-of-the-box support for Chromium, Firefox, and WebKit ensures your web applications work seamlessly across browsers. Running tests in parallel saves time, especially for large projects. This guide provides step-by-step examples in Python and Java to help you master these features.

Configuring cross-browser testing

Playwright simplifies testing across multiple browsers. Below, I’ll show you how to configure cross-browser tests in Python and Java, ensuring compatibility with different browser engines.

Python (via pytest-playwright or manual configuration)

Playwright for Python doesn’t use a playwright.config.ts like JavaScript. To test across multiple browsers, you'll need to parameterize your tests manually or use Pytest plugins.

Manual Parametrization
import pytest
from playwright.sync_api import sync_playwright

@pytest.mark.parametrize("browser_name", ["chromium", "firefox", "webkit"])
def test_cross_browser(browser_name):
    with sync_playwright() as p:
        browser = getattr(p, browser_name).launch()
        page = browser.new_page()
        page.goto("https://example.com")
        assert "Example" in page.title()
        browser.close()

For full automation, you can also integrate this with pytest-xdist for parallelism:

pytest -n 3  # Run tests in parallel using 3 workers

Java (via TestNG or JUnit with Playwright)

Use a test framework like TestNG with parameters for browser types.

Parallel Cross-Browser Test
@Parameters({"browser"})
@BeforeMethod
public void setup(@Optional("chromium") String browserName) {
    switch (browserName) {
        case "firefox":
            browser = playwright.firefox().launch();
            break;
        case "webkit":
            browser = playwright.webkit().launch();
            break;
        default:
            browser = playwright.chromium().launch();
    }
    page = browser.newPage();
}

Run tests in parallel using TestNG XML configuration:

<suite name="CrossBrowserSuite" parallel="tests" thread-count="3">
  <test name="ChromiumTest">
    <parameter name="browser" value="chromium"/>
    <classes><class name="tests.LoginTest"/></classes>
  </test>
  <test name="FirefoxTest">
    <parameter name="browser" value="firefox"/>
    <classes><class name="tests.LoginTest"/></classes>
  </test>
</suite>

Python /  Java

There is no native "shard" option, but you can achieve the same via:
•    pytest-xdist (Python): pytest -n auto
•    CI test partitioning (Java/Python): Split test files across job matrices

Screenshots and Video Recording in Playwright

Playwright offers built-in support for capturing screenshots and videos during test execution — invaluable for visual debugging and test reporting.

Python

i)  Capture Screenshot

page.screenshot(path="screenshot.png")

Use this after a failed step or before assertions to capture the page's current state.

ii)  Record Video

Enable video recording by configuring the browser context:

browser = p.chromium.launch()
context = browser.new_context(record_video_dir="videos/")
page = context.new_page()
page.goto("https://example.com")
context.close()  # Video is saved on context close

Video is saved in the specified folder once the context is closed.

Java

i)  Capture Screenshot

page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("screenshot.png")));

ii) Record Video

Set up video recording via context options:

Browser.NewContextOptions contextOptions = new Browser.NewContextOptions()
    .setRecordVideoDir(Paths.get("videos/"));

BrowserContext context = browser.newContext(contextOptions);
Page page = context.newPage();
page.navigate("https://example.com");
context.close();  // Saves the video

Handling dropdowns and input fields

Interacting with form elements is simple and reliable with Playwright's API.

Python

i)  Select Dropdown

page.select_option("#country", "IN")

ii)  Clear and Refill Input

page.fill("#email", "")         # Clear the field
page.fill("#email", "user@example.com")  # Enter new value

Java

i)  Select Dropdown

page.selectOption("#country", "IN");

ii) Clear and Refill Input

page.fill("#email", "");  // Clear existing value
page.fill("#email", "user@example.com");  // Enter new value

Timeouts and error handling in playwright

Playwright is built with robust error handling and flexible timeout configurations to help stabilize tests in dynamic web environments. This is especially helpful when dealing with asynchronous UI rendering or unpredictable network behavior.

i) Set Timeout

Python

You can set a custom timeout for operations like waiting for selectors:

page.wait_for_selector(".item", timeout=5000)  # Timeout in milliseconds

You can also override default timeouts globally:

page.set_default_timeout(10000)  # Set 10-second timeout for all actions

Java

Wait for a selector with a timeout:

page.waitForSelector(".item", new Page.WaitForSelectorOptions().setTimeout(5000));

Set a global timeout for all operations:

page.setDefaultTimeout(10000);  // 10 seconds

ii) Retry Logic

Retries help automatically re-run failed tests — useful for flaky tests caused by timing issues or network instability.

Python

Pytest-style retries can be added using pytest-rerunfailures plugin:

Install the plugin:

pip install pytest-rerunfailures

Then apply it in your test:

import pytest

@pytest.mark.flaky(reruns=2)
def test_unstable_feature(page):
    page.goto("https://example.com")
    # test logic here

Java

In TestNG, configure retries using IRetryAnalyzer:

public class RetryAnalyzer implements IRetryAnalyzer {
  private int count = 0;
  private static final int MAX_RETRIES = 2;

  @Override
  public boolean retry(ITestResult result) {
    if (count < MAX_RETRIES) {
      count++;
      return true;
    }
    return false;
  }
}

Apply it to a test:

@Test(retryAnalyzer = RetryAnalyzer.class)
public void testFlakyComponent() {
    // test logic
}

For large test suites, implement global retry configuration in your test runner or CI pipeline.

Handling Frames and IFrames

Playwright provides powerful APIs to interact with <iframe> and <frame> elements, allowing test automation even inside embedded documents.

i) Switch to Frame

Python

To access a frame by name, URL, or selector:

frame = page.frame(name="loginFrame")  # by frame name
# OR
frame = page.frame(url=re.compile(".*login.*"))  # by URL

Java

Frame frame = page.frameByName("loginFrame");
// OR
Frame frame = page.frameByUrl(Pattern.compile(".*login.*"));

ii) Interact Inside Frame

Python

frame.click("#loginButton")

Java

frame.click("#loginButton");

Frames behave like regular Page objects — you can use all locator methods and actions.

Multi-Tab and Multi-Context Testing

Playwright supports true browser-level isolation through contexts (for session separation) and tabs/pages (for multi-tab testing).

i) Open multiple pages (tabs)

Python

context = browser.new_context()
page1 = context.new_page()
page2 = context.new_page()  # Acts as a new tab

Java

BrowserContext context = browser.newContext();
Page page1 = context.newPage();
Page page2 = context.newPage();  // New tab in same session

ii) Multiple browser contexts (isolated sessions)

Each context has its own cookies, cache, and storage — useful for testing logged-in vs guest users.

Python

context1 = browser.new_context()
page1 = context1.new_page()
context2 = browser.new_context()
page2 = context2.new_page()

Java

BrowserContext context1 = browser.newContext();
Page page1 = context1.newPage();

BrowserContext context2 = browser.newContext();
Page page2 = context2.newPage();

Use this setup to simulate two users interacting with each other — for example, in chat apps or collaborative tools.

Summary Table:

Feature Python Example Java Example
Headless Mode launch(headless=True) .setHeadless(true)
Headed Mode launch(headless=False) .setHeadless(false)
Slow Motion slow_mo=100 .setSlowMo(100)
Launch Chromium p.chromium.launch() playwright.chromium().launch()
Launch Firefox p.firefox.launch() playwright.firefox().launch()
Launch WebKit p.webkit.launch() playwright.webkit().launch()
Auto Wait page.click("#submit") page.click("#submit")
Wait for Selector page.wait_for_selector(".msg") page.waitForSelector(".msg")
Text Assertion expect(locator).to_have_text("text") assertEquals("text", locator.textContent())
Visibility Check expect(locator).to_be_visible() assertTrue(locator.isVisible())
Locate by ID page.locator("#id") page.locator("#id")
Locate by Text page.get_by_text("text") page.getByText("text")
Chained Locators page.locator("form").locator("button") Same as Python
Headless Launch launch(headless=True) .setHeadless(true)
Headed Launch launch(headless=False) .setHeadless(false)
Test Block def test_example(): @Test public void testExample()
Group Tests class TestGroup: public class TestGroup
Hooks (Setup/Teardown) @pytest.fixture @BeforeEach / @AfterEach
Multi-browser config @pytest.mark.parametrize @Parameters in TestNG
Parallel execution pytest -n auto <suite parallel="tests" thread-count="3">
Run specific browser Parametrize with "chromium", "firefox" Launch by name switch case
Sharding CI-level test split (no native shard) CI-level test split
Screenshot page.screenshot(path="screenshot.png") page.screenshot(setPath(...))
Record Video record_video_dir="videos/" in context setRecordVideoDir(Paths.get("videos/"))
Select Dropdown page.select_option("#id", "value") page.selectOption("#id", "value")
Clear & Fill Input page.fill("#input", "") then re-fill Same: two fill() calls
Wait for Selector page.wait_for_selector(".item", timeout=5000) page.waitForSelector(".item", setTimeout(5000))
Set Global Timeout page.set_default_timeout(10000) page.setDefaultTimeout(10000)
Retry Logic @pytest.mark.flaky(reruns=2) @Test(retryAnalyzer = RetryAnalyzer.class)
Access Frame page.frame(name="loginFrame") page.frameByName("loginFrame")
Click in Frame frame.click("#loginButton") frame.click("#loginButton")
New Tab (Page) context.new_page() context.newPage()
New Context (Isolation) browser.new_context() browser.newContext()

Wrapping up

At Opcito, our experts specialize in Playwright and test automation to streamline your testing processes. Contact us to get your project started with our comprehensive Test Automation services. Stay tuned for Part 3 where we will talk about MCP server with Playwright.
 

Subscribe to our feed

select webform