The most surprising thing about Playwright’s type-safe configuration is that it actively prevents you from making common, subtle mistakes before you even run your tests.
Let’s see it in action. Imagine you’re setting up your Playwright tests and want to define some global settings and browser configurations.
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
// Global configurations
globalTimeout: 60000, // 60 seconds
timeout: 30000, // 30 seconds per test
expect: {
timeout: 5000, // 5 seconds for expect assertions
},
// Define projects for different environments or configurations
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
baseURL: 'http://localhost:3000',
},
},
{
name: 'firefox',
use: {
...devices['Desktop Firefox'],
baseURL: 'http://localhost:3000',
},
dependencies: ['chromium'], // Example of project dependency
},
{
name: 'webkit',
use: {
...devices['Desktop Safari'],
baseURL: 'http://localhost:3000',
},
},
{
name: 'mobile-chrome',
use: {
...devices['Pixel 5'],
baseURL: 'http://localhost:3000',
},
},
],
// Reporter configuration
reporter: 'list',
// Other configurations
outputDir: 'test-results/',
snapshotDir: '__snapshots__',
testDir: 'tests/',
fullyParallel: true,
});
This playwright.config.ts file is the heart of your Playwright setup. It’s where you tell Playwright everything it needs to know to run your tests: how long to wait for things, which browsers to use, where to find your tests, and where to put the results.
The magic of TypeScript here is that defineConfig isn’t just a function; it’s a typed factory. It expects specific keys and specific types for those keys. If you try to pass an invalid value, like globalTimeout: 'a long time' or reporter: ['html', 'json'] without the correct structure, your IDE will scream at you before you even save the file. This prevents a whole class of runtime errors where a configuration value is simply the wrong type.
You define projects, each with a name and a use object. The use object is where you specify browser-specific configurations, like baseURL. Playwright provides pre-defined devices that map to common device configurations, saving you from manually setting viewport sizes and user agents. You can also specify dependencies between projects, ensuring that one project’s tests run only after another’s have completed, which is useful for setup or integration scenarios.
The reporter option lets you choose how test results are displayed. 'list' is a simple, human-readable format. You could also use 'html' for a detailed HTML report, or even chain multiple reporters. outputDir and snapshotDir clearly define where Playwright should stash its generated files.
The fullyParallel: true setting tells Playwright to run tests in parallel across all projects and all workers, significantly speeding up your test suite. Conversely, fullyParallel: false would mean tests run sequentially within each project.
The expect block is crucial. timeout here is specifically for Playwright’s assertion library (expect). If an assertion like expect(page).toHaveTitle('My App') takes longer than 5 seconds to resolve, it will fail. This is distinct from the timeout for individual test steps, which is 30 seconds. This layered timeout system allows for fine-grained control over how long Playwright will wait for different kinds of operations.
What most people miss is that the use object within a project can also accept contextOptions and pageOptions. These allow you to pass specific arguments to browser.newContext() and context.newPage(), respectively. For instance, you could set storageState or viewport at the context level, or userAgent at the page level, directly within your configuration.
// Example of contextOptions and pageOptions
{
name: 'custom-setup',
use: {
...devices['Desktop Chrome'],
contextOptions: {
permissions: ['geolocation'],
geolocation: { latitude: 40.7128, longitude: -74.0060 },
},
pageOptions: {
userAgent: 'MyCustomUserAgent/1.0',
},
},
}
This level of configuration detail, enforced by TypeScript, ensures your tests are robust and that your environment is precisely as intended before execution even begins.
The next thing you’ll likely want to explore is how to leverage Playwright’s test.describe.configure for more localized configuration overrides within your test files.