Cypress runs only in Chromium. Selenium needs WebDriver setup and flaky waits. Puppeteer doesn't support Firefox or Safari. Your E2E tests are slow, flaky, and only cover one browser.
What if your E2E framework ran Chromium, Firefox, AND WebKit in parallel — with auto-waiting that eliminates flaky tests?
That's Playwright Test.
Quick Start
npm init playwright@latest
This creates:
-
playwright.config.ts— configuration -
tests/— test directory with example -
tests-results/— artifacts (screenshots, videos)
Your First Test
import { test, expect } from "@playwright/test";
test("user can log in", async ({ page }) => {
await page.goto("/login");
await page.fill('[name="email"]', "test@example.com");
await page.fill('[name="password"]', "password123");
await page.click('button[type="submit"]');
// Auto-waits for element to appear
await expect(page.getByText("Welcome back")).toBeVisible();
await expect(page).toHaveURL("/dashboard");
});
test("search returns results", async ({ page }) => {
await page.goto("/");
await page.getByPlaceholder("Search...").fill("typescript");
await page.keyboard.press("Enter");
// Auto-waits and retries until condition is met
await expect(page.getByTestId("result-count")).toContainText("results");
const results = page.getByRole("article");
await expect(results).toHaveCount(10);
});
No await page.waitForTimeout(2000). Playwright auto-waits for elements to be visible, enabled, and stable before interacting.
Configuration
// playwright.config.ts
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./tests",
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 4 : undefined,
reporter: [["html"], ["junit", { outputFile: "results.xml" }]],
use: {
baseURL: "http://localhost:3000",
screenshot: "only-on-failure",
video: "retain-on-failure",
trace: "on-first-retry",
},
projects: [
{ name: "chromium", use: { ...devices["Desktop Chrome"] } },
{ name: "firefox", use: { ...devices["Desktop Firefox"] } },
{ name: "webkit", use: { ...devices["Desktop Safari"] } },
{ name: "mobile", use: { ...devices["iPhone 14"] } },
],
webServer: {
command: "npm run dev",
url: "http://localhost:3000",
reuseExistingServer: !process.env.CI,
},
});
Page Object Model
// pages/login.page.ts
export class LoginPage {
constructor(private page: Page) {}
async goto() {
await this.page.goto("/login");
}
async login(email: string, password: string) {
await this.page.fill('[name="email"]', email);
await this.page.fill('[name="password"]', password);
await this.page.click('button[type="submit"]');
}
async expectError(message: string) {
await expect(this.page.getByRole("alert")).toContainText(message);
}
}
// tests/login.spec.ts
test("shows error for invalid credentials", async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login("wrong@email.com", "wrongpass");
await loginPage.expectError("Invalid credentials");
});
API Testing (No Browser Needed)
test("API: create and retrieve user", async ({ request }) => {
const response = await request.post("/api/users", {
data: { name: "Test User", email: "test@example.com" },
});
expect(response.ok()).toBeTruthy();
const user = await response.json();
expect(user.name).toBe("Test User");
const getResponse = await request.get(`/api/users/${user.id}`);
expect(getResponse.ok()).toBeTruthy();
});
Visual Regression Testing
test("homepage matches snapshot", async ({ page }) => {
await page.goto("/");
await expect(page).toHaveScreenshot("homepage.png", {
maxDiffPixelRatio: 0.01,
});
});
Playwright vs Cypress vs Selenium
| Feature | Playwright | Cypress | Selenium |
|---|---|---|---|
| Browsers | Chromium, Firefox, WebKit | Chromium only* | All (via drivers) |
| Parallel | Built-in | Paid feature | Manual setup |
| Auto-wait | Yes | Yes | No (manual waits) |
| API testing | Built-in | Via cy.request | Separate tool |
| Mobile emulation | Built-in | Limited | Appium |
| Speed | Fast | Medium | Slow |
| Debugging | Trace viewer, VS Code | Time travel | Console logs |
When to Choose Playwright
Choose Playwright when:
- Cross-browser testing matters (Safari/WebKit is critical for iOS)
- You want parallel test execution out of the box
- You need API + UI testing in one framework
- Flaky tests are killing your CI
Skip Playwright when:
- You only target Chrome and want Cypress's interactive runner
- Your team already has a large Cypress test suite
- You need native mobile testing (use Appium or Detox)
The Bottom Line
Playwright eliminates the two biggest E2E testing problems: flakiness (auto-waiting) and coverage (all browsers in parallel). Your tests run fast, test real browsers, and don't break randomly.
Start here: playwright.dev
Need custom data extraction, scraping, or automation? I build tools that collect and process data at scale — 78 actors on Apify Store and 265+ open-source repos. Email me: Spinov001@gmail.com | My Apify Actors
Top comments (0)