Testing a web application on just one browser is not enough because your users are everywhere, including Chrome, Firefox, Safari(Webkit), mobile devices, and even embedded browsers. What works fine for you might break for client’s mobile device. That’s where cross-browser testing comes in. and why tools like Playwright have become useful for modern teams.
In this guide (updated for 2025), we’ll walk through setup, debugging, mobile/device testing, and best practices used in real ThinkSys client projects.
Whether you're building a simple site or a complex web app, this guide gives you the foundation to test it confidently across browsers.
This guide is perfect for: Frontend developers, QA engineers, automation testers, solo devs, and product teams.
So let’s begin.
Take an example to understand the problem. Suppose you built a feature and tested it in Chrome. Everything worked well, but then your teammate or users opened it in Firefox or any other browser, they noticed the layout was broken. The button that was supposed to be centered floated off to the side. The form didn’t behave the same. Even worse, a client tried it in Safari on their iPhone, and it wouldn’t even submit.
This is the cross-browser problem.
These issues are caused by:
And it gets worse when you factor in devices, what works on a large desktop screen might break on mobile Safari. These issues can easily affect functionality, accessibility, and ultimately the user’s trust.
When you don’t test across browsers, you're assuming that all your users:
That’s rarely true.
In reality, most developers skip cross-browser testing despite knowing its importance because it feels like a tedious task. Setting up multiple browsers, managing driver versions, and writing separate test cases for each environment takes time. And time is something most devs don’t have.
Fact: According to StatCounter’s 2025 browser usage report, , Chrome is the dominant browser but Firefox, Safari, and mobile browsers still have millions of daily users. If your product breaks for just 5–10% of your users, that’s a serious problem and you're risking trust, revenue, and user retention. Cross-browser testing is about validating real-world usability for real users.
Cross‑browser issues arise from fundamental differences in rendering and JavaScript engines. Browsers like Chrome (Blink & V8), Firefox (Gecko & SpiderMonkey) and Safari (WebKit & JavaScriptCore) interpret CSS, JavaScript APIs, HTML rendering, event handling and default styles differently. These differences often lead to inconsistent behaviour or degraded performance across browsers.
Safari, for example, has known quirks with flex item sizing and alignment – explicitly setting flex-basis can resolve sizing issues. By understanding these engine‑level variations, you can better anticipate and prevent cross‑browser bugs.
Here are just a few examples of browser-specific flaws:
Cross-browser testing is not limited to testing on Chrome and Firefox on your laptop. In fact, you want to test for the real-world experience, and that includes mobile.
When you test only on the desktop, you miss all these issues.
You need a way to test across browsers and devices without pain. That’s where Playwright comes in. It’s designed to make cross-browser testing simple, fast, and scalable. With a single test file, you can run your tests in Chromium, Firefox, and WebKit (Safari's engine), both in desktop and mobile modes.
We'll get into that soon. But first, let's make sure your environment is ready.
Playwright is an open-source tool from Microsoft that helps you write and run automated tests for web applications. It allows you to simulate how a real user interacts with your app like clicking buttons, filling forms, navigating pages, across multiple browsers.
In simple terms, you just need to write one test, and Playwright will run it in Chrome, Firefox, and Safari.
As we discussed above, if you’re building a web app, you need to ensure that it works well in different browsers, and on different devices.
But doing this manually is exhausting. You’d have to open your app in Chrome, Firefox, Safari, maybe even on a phone, and test the same flow over and over.
That’s where Playwright steps in:
Unlike older tools that depend on browser extensions or flaky drivers, Playwright talks directly to the browser engines. That makes tests faster and more stable.
To help you understand where Playwright shines compared to another popular framework, here’s a quick comparison:
Feature | Playwright | Cypress |
---|---|---|
Browser support | Chrome, Firefox, Edge & Safari | Primarily Chrome/Edge; limited Firefox, no Safari |
Language support | TypeScript, JavaScript, Python, .NET, Java | JavaScript only |
Parallel execution | Built‑in concurrency & auto‑waiting | Limited parallelism |
Debugging | Trace Viewer, UI Mode, Inspector, network mocking | Time‑travel debugging, interactive test runner |
Know the complete difference between both the tools in our comparison guide here.
Playwright supports all major modern browsers:
Browser | Engine | Example |
---|---|---|
Chromium | Chrome, Edge | Most common |
Firefox | Gecko | Good for Mozilla testing |
WebKit | Safari | Crucial for Apple devices |
It runs on:
And supports multiple languages:
In this guide, we’ll use JavaScript/TypeScript because it’s the default and easiest to start with.
When Playwright runs tests, it can do so:
You can switch modes in your config with a single flag. More on that in the configuration section.
Real‑world impact
Playwright’s concurrency and auto‑waiting features allow developers to run several tests at once across different browsers, reducing overall runtime. It automatically waits for network events and UI elements to be ready, leading to enhanced test accuracy and fewer flaky tests.
In the next section, we’ll set up our development environment and install Playwright.
Before we can write or run any tests, we need to set up our development environment. In this guide, we’re using JavaScript with Node.js, which is the default setup recommended by the Playwright team.
Even if you've never worked with Node.js before, don’t worry because every step is explained clearly.
Playwright runs on top of Node.js. So first, we need to make sure Node is installed on your system.
To check, open your terminal or command prompt and type:
Command: node --version
If Node is installed, you’ll see a version number like:
v18.17.1
If it says something like command not found, you’ll need to install Node first from the official site: https://nodejs.org
Make sure you download LTS (Long-Term Support) version 18 or later. LTS is more stable and better supported by most tools.
Next, create a folder to keep all your Playwright test files.
In your terminal, run:
mkdir playwright-demo
cd playwright-demo
This creates a folder called playwright-demo and switches into it so that everything you install or create stays organized inside that folder.
Now, let’s turn this folder into a Node.js project by creating a package.json file. This file tracks your dependencies (like Playwright) and project settings.
npm init -y
This command quickly creates a default package.json without asking you setup questions (thanks to the -y flag).
You’ll see a message like:
Wrote to /path/to/playwright-demo/package.json
If you check your folder now, you’ll see the package.json file was created.
Now, we’ll install Playwright’s test runner.
In the same terminal window, run:
npm install -D @playwright/test
Here’s what this command means:
Once the command finishes, you’ll see a node_modules/ folder (where all the packages are stored) and your package.json will be updated with Playwright listed under devDependencies.
Here’s something important that surprises many beginners:
Playwright doesn’t use your system’s Chrome, Firefox, or Safari. Instead, it downloads its own versions of these browsers to ensure tests are consistent on every machine.
To download these browsers, run:
npx playwright install
You’ll see it download:
This ensures that even if you don’t have Firefox or Safari installed locally, Playwright can still run tests in those engines.
If you're using Python, Java, or .NET, Playwright has versions for those too.
For example, Python users can run:
pip install playwright
playwright install
But in this guide, we’ll focus only on JavaScript and Node.js.
Now that you’ve installed Playwright, it’s time to write your first test.
In this section, you’ll learn:
If you're just starting out, don’t worry. We’ll go step by step.
A test is a script that tells Playwright what to do in the browser and what to expect.
For example:
Each test is written in code and saved in a .spec.ts file (the .spec part tells Playwright it's a test file).
Playwright looks for tests inside a folder called tests by default.
In your terminal, run:
mkdir tests
Then open your project folder in VS Code (or another code editor), and you should see the tests/ folder created.
Inside the tests/ folder, create a new file named:
example.spec.ts
This file will hold your first two tests.
Note: If you're not using TypeScript, you can name the file example.spec.js, both work the same way for simple tests.
Paste the following code into example.spec.ts:
Let’s break down what this code does:
This line brings in Playwright's test functions.
First test: "has title"
await page.goto('https://playwright.dev/');
await expect(page).toHaveTitle(/Playwright/);
This opens the Playwright homepage and checks that the browser title includes the word “Playwright”.
Second test: "get started link"
await page.getByRole('link', { name: 'Get Started' }).click();
await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
This:
A locator is how Playwright finds an element on the page (like a button or link).
Playwright uses smart locators like getByRole() instead of fragile CSS selectors. These are:
If you’re familiar with Selenium or Cypress, this is a much more stable approach.
Go back to your terminal and make sure you’re in the project root (not inside the tests/ folder).
Then run:
npx playwright test
This tells Playwright to:
If everything is set up correctly, Playwright will:
This means all tests passed, without any extra work from you.
When you first run Playwright, it automatically uses Chromium, the open-source engine behind Google Chrome and Microsoft Edge. That’s a good start but real users visit your site using Firefox, Safari, mobile devices, and different operating systems. If you only test on Chromium, you may miss bugs that show up elsewhere.
Playwright makes it easy to run your tests across multiple browsers from a single test file. You can also simulate mobile devices or open visible browser windows for debugging. All of this is controlled from a single file: playwright.config.ts.
Let’s walk through how to configure Playwright for cross-browser and device testing, step by step.
This is a configuration file that tells Playwright:
And more
You only need to create it once, and it becomes your test setup command center.
Open your project in VS Code. In the root folder (the same place as your package.json), create a new file called:
playwright.config.ts
Paste the following code into playwright.config.ts:
What this does:
By default, this setup will test your app in Chromium, Firefox, and WebKit (Safari's engine).
Go back to your terminal and make sure you're in your project root. Then run:
npx playwright test
Playwright will now:
Sometimes you don’t want to run every browser, especially when debugging.
To test only in Chromium:
npx playwright test --project=chromium
You can replace chromium with firefox or webkit depending on your need.
Beyond the basics, Playwright lets you define special browser environments inside the same config file. Here are two useful cases:
By default, Playwright runs in headless mode, meaning the browser runs in the background without showing a window. It’s fast, but not helpful when you're trying to figure out why something isn’t working.
You can run tests in headed mode, which opens a visible browser window while the test runs. This is great for debugging.
Inside your projects array, add this:
How to run it:
npx playwright test --project=chromium-debug
Now you’ll see the actual browser window open and actions being performed in real-time.
Here’s how this will look like in your Chromium browser.
Playwright includes a library of device profiles that simulate mobile devices, including screen size, resolution, touch input, and user-agent headers.
At the top of your config file, update your import:
import { defineConfig, devices } from '@playwright/test';
Then add this to your projects array:
This tells Playwright to:
How to run it:
npx playwright test --project="Mobile Safari"
You’ll now see your tests run as if on an iPhone, using Safari’s rendering engine.
Once you’ve written your Playwright tests and configured your browsers, the next step is to run them smartly. This needs to be a smart process because, you can’t just run a test and hope for the best. You must understand the feedback you can use quickly, clearly, and with control.
In this section, we’ll walk through how to run your tests across browsers, isolate test cases, control parallelism, and generate helpful reports.
By default, Playwright makes smart decisions about how to run your tests. But once you’re working with a larger suite, debugging flaky tests, or integrating into CI, you need more control. This is where CLI options, test isolation, and reports become essential.
If you’ve set up your playwright.config.ts to define multiple browser projects (e.g., Chromium, Firefox, WebKit), Playwright will run all tests in all of them by default. We explained this previous section.
Behind the scenes, Playwright:
And even if you don’t want to want to test all browsers, you can run it in a specific browser or do a mobile emulation for a specific device.
This isolation gives you faster feedback and avoids unnecessary parallel runs during debugging. Similarly, there are multiple ways to isolate your test.
If you only want to run one test file, for example, when working on a feature:
npx playwright test tests/example.spec.ts
This is helpful when you want to:
You can filter tests by name using -g (short for grep):
npx playwright test -g "get started link"
Playwright will only run tests that match the string you provide. This is useful when isolating one failing test or running a small group of related ones.
For a complete list of test runner flags and configuration options, check the official Playwright docs
Playwright runs tests in parallel using multiple workers, each test runs in its own browser instance and its own thread.
You can control how many workers are used:
npx playwright test --workers=1
When would you do this?
Every Playwright test runs in its own browser context. Think of this like a private, fresh incognito tab. That means:
This test isolation is one reason Playwright tests are more stable by default.
Text logs are helpful but they’re not the best way to understand why a test failed. Playwright provides a built-in HTML report that gives you a visual summary of your test run.
Step 1: Enable the HTML Reporter
Open your playwright.config.ts and add (or confirm) the reporter section:
reporter: [['html', { outputFolder: 'playwright-report' }]],
This tells Playwright to create a playwright-report/ folder after every test run.
Step 2: Run Tests
npx playwright test
All tests will run, and the report will be generated at the end.
Step 3: Open the Report
Use this command:
npx playwright show-report
It will open your default browser and show a visual summary of the results.
What You’ll See in the Report
Let’s say a test passed on retry. The HTML report will:
Or maybe a test takes 4x longer than others. The report will:
These kinds of insights help you identify tests that need work.
Now, we’ll cover how to debug failed tests with the help of Playwright’s useful features. Playwright includes several built-in tools to help you debug tests with ease:
Let’s go through each of these tools step by step.
To use these tools, update your playwright.config.ts file with the following:
What these settings do:
Playwright will automatically create a test-results/ folder to store these files.
Open your terminal and run:
npx playwright test
Your tests will run like before but now, if anything goes wrong, you'll get rich artifacts: traces, screenshots, and videos.
Even when tests pass, traces are still captured (since we set trace: 'on'), which is helpful for debugging behavior that "almost" fails.
Traces are stored as .zip files inside the test-results/ folder. You can open them with:
npx playwright show-trace test-results/<your-folder-name>/trace.zip
Once opened, the Trace Viewer will show a timeline with:
This is extremely useful when a test fails, and you're not sure why.
The Trace Viewer shows each step your test took, including clicks, input, network calls, and more.
Sometimes you just want to quickly prototype a test. Instead of writing everything from scratch, you can use Playwright’s code generator.
Run this command:
npx playwright codegen https://playwright.dev
This will:
The output will look like this:
await page.goto('https://playwright.dev');
await page.getByRole('link', { name: 'Get Started' }).click();
You can copy and paste this into your own test files as a starting point.
As you interact with the browser, Playwright generates test code in real time.
Sometimes, you want to watch it run and pause at each step.
Use Inspector Mode to do just that:
npx playwright test --debug
This opens two windows:
The test pauses before each action. In the Inspector, you can:
It’s like setting breakpoints for your tests.
Inspector Mode pauses the test at each action so you can step through and explore the page.
Sometimes your API isn’t ready yet. Or maybe you want to test what happens when it fails (like a server error). With Playwright, you can mock any network request.
Here’s how to simulate a 500 Internal Server Error:
What this does:
You don’t need a real backend to test failure cases. This is especially useful for edge case testing, mobile fallbacks, and error handling.
In the image, you can see there’s an option to see the video of the failed test. After clicking the video icon, the screen looks like this.
Functional tests alone won’t catch visual regressions. Playwright supports snapshot testing, where you capture a screenshot of an element or page and compare it against a stored baseline. If the pixel‑diff exceeds a threshold, the test fails.
You can integrate Playwright with visual testing tools like Percy or Chromatic to generate visual diffs across browsers and screen sizes. For example, after each test, capture a screenshot:
This is particularly important because browsers handle layout and rendering differently. Safari’s flexbox sizing quirks, for instance, can cause subtle UI shifts that only a visual comparison will reveal.
Running tests locally covers only a subset of browsers and devices. To ensure coverage across older browser versions and real devices, integrate Playwright with cloud‑based services like BrowserStack or Sauce Labs. These platforms let you run the same Playwright test suite against a wide matrix of browsers and operating systems.
Example: Set environment variables for BrowserStack credentials and modify playwright.config.ts to use their capabilities. Then run your tests on real iOS or Android devices without owning the hardware.
This approach complements Playwright’s built‑in emulation and ensures your UI works on real devices with actual rendering engines and touch interactions.
Not everyone tests the same way. A solo developer might be focused on quick feedback. A QA engineer might care about detailed structure and coverage. A product team might need stability across environments and browsers.
Playwright supports all of them, but how you use it depends on who you are and what your priorities are. Here’s how to adapt your testing practices based on your role.
If you’re building and testing in the same project, your goal is usually fast feedback and low maintenance. You want your tests to run quickly and reliably without making your codebase harder to manage.
Use test IDs or accessible roles
A common mistake is relying on fragile selectors like class names or DOM order. These break easily when your UI changes.
Instead, use semantic locators
Good example using roles:
page.getByRole('button', { name: 'Submit' });
Bad example using fragile CSS selectors:
page.locator('.btn-primary:nth-child(2)');
If your elements don’t have clear roles or names, add a data-testid:
In your HTML:
<button data-testid="submit-button">Submit</button>
In your test:
page.getByTestId('submit-button');
Using roles and test IDs makes your tests stable and aligned with accessibility standards.
When more than one person works on the project, testing needs to be repeatable, fast, and integrated with your tools. You’ll want good setup practices, CI/CD integration, and helpful failure feedback.
Use fixtures for shared setup: Fixtures let you define common environments or setups, like logging in a user or connecting to a clean database.
Without fixtures, you’d repeat the same setup in every test. That leads to duplication and inconsistent state. Basic fixture example:
You usually place these in a fixtures.ts file and import them in your tests.
This helps your teammates understand failures without rerunning anything locally.
Your focus might be on coverage, clarity, and ensuring test suites scale as the app grows. Structure, grouping, and decision-making around mocks vs real data matter here.
Example:
This mirrors how real users use your app, and makes it easier to track what’s tested (and what’s not).
Example using describe:
Then you can run just those tests:
npx playwright test -g @mobile
This is useful for Smoke tests, Regression suites, Mobile-only flows.
Scenario | Use Mocking | Use Staging API |
---|---|---|
API not built yet | ✅ | ❌ |
Test needs fast, predictable result | ✅ | ❌ |
Testing full user flow | ❌ | ✅ |
Pre-release validation | ❌ | ✅ |
Use mocking when speed and control matter. Use staging when you need realism.
Beyond the role‑based advice already in this guide, consider these:
Explore complete list of all latest playwright features here.
Cross-browser testing is no longer optional. With Playwright, you now have:
At ThinkSys, we specialize in delivering enterprise-grade Playwright automation solutions. Whether you’re starting from scratch or migrating from Selenium, our engineers design scalable, maintainable frameworks that integrate seamlessly into your workflows. From cross-browser coverage to performance and accessibility checks — we help you go beyond “it works” to “it works everywhere, every time.
We invite you to explore the official release notes and our own case studies to see how Playwright’s latest features can supercharge your test strategy.
Share This Article: