Parallel and cross-browser testing with Playwright

Posted By
Neha Nemade

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.
Related Blogs

