Playwright tests don’t actually run in the browser; they control the browser remotely.
Here’s a simple Node.js test using Playwright to log into a hypothetical website. First, we need to install Playwright and a testing framework like vitest.
npm install -D playwright @playwright/test vitest
Now, let’s create a test file, login.spec.ts:
import { test, expect } from '@playwright/test';
test('successful login', async ({ page }) => {
// Navigate to the login page
await page.goto('https://example.com/login');
// Fill in the username and password fields
await page.fill('input[name="username"]', 'testuser');
await page.fill('input[name="password"]', 'password123');
// Click the login button
await page.click('button[type="submit"]');
// Assert that we are on the dashboard page
await expect(page).toHaveURL('https://example.com/dashboard');
await expect(page.locator('h1')).toContainText('Dashboard');
});
To run this test, we’ll add a script to our package.json:
{
"scripts": {
"test": "vitest run"
}
}
Then, simply run npm test. Playwright will launch a browser instance (by default, headed Chromium), navigate to the page, perform the actions, and check the assertions. The entire process is orchestrated by the Playwright test runner, which communicates with the browser via the DevTools Protocol.
This test’s primary purpose is to automate repetitive user interactions and verify that a critical user flow, like logging in, functions as expected. It simulates real user behavior by interacting with elements on the page through their selectors. The page object, injected by the Playwright test runner, is the main interface for interacting with the browser context.
The key to Playwright’s power lies in its architecture. It doesn’t run JavaScript within the browser in the same way a user’s browser does. Instead, it acts as an external controller. When you call page.fill(), Playwright sends a command over a WebSocket connection to the browser’s debugging endpoint. The browser then executes the command locally. This distinction is crucial for understanding why Playwright can reliably interact with elements, even those that might be dynamically loaded or modified by client-side JavaScript, and why it offers such robust cross-browser and cross-platform capabilities. It’s akin to having a remote control for the browser, capable of issuing precise instructions.
The expect assertions are powered by a separate assertion library (in this case, Playwright’s built-in one, which is compatible with vitest and jest). These assertions check the state of the page after Playwright has performed its actions. For instance, toHaveURL verifies that the browser’s current URL matches the expected one, and toContainText checks if a specific element contains certain text. Playwright waits for elements to be ready before interacting with them, abstracting away many timing issues that plague older automation tools.
When you run npm test, Playwright will, by default, use a headed Chromium browser. You can configure this behavior. For example, to run tests in headless mode (without a visible browser window) and using Firefox, you’d typically modify your Playwright configuration file (playwright.config.ts). A common setup might look like this:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests', // Directory containing your tests
// Other configurations like timeout, reporters, etc.
use: {
browserName: 'firefox', // Use Firefox by default
headless: true, // Run in headless mode
viewport: { width: 1280, height: 720 }, // Set a consistent viewport size
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
});
This configuration allows you to define different browser environments and run your tests across them. The projects array is particularly useful for parallel execution and testing on specific platforms.
The most surprising aspect is how Playwright achieves its reliability across different browsers and operating systems without compromising on speed. It achieves this by leveraging native browser debugging protocols (like Chrome DevTools Protocol, Firefox Remote Debugging Protocol, and WebKit’s equivalent) and abstracting away the differences. This means Playwright isn’t emulating browser behavior; it’s directly commanding the actual browser engine, making its interactions extremely faithful to how a real user would experience the application.
The next step is to explore Playwright’s advanced features, such as intercepting network requests and mocking API responses.