Test isolation definition and purpose
Test isolation is when each test runs completely independently from other tests. Each test has its own local storage, session storage, and cookies. Playwright achieves test isolation using BrowserContexts, which are equivalent to incognito-like profiles. They are fast and cheap to create and completely isolated, even when running in a single browser.
Creating browser context manually in library mode
When using Playwright as a library rather than as a Test Runner, create a browser context manually by calling browser.newContext(), then create a page within that context by calling context.newPage().
How Playwright creates test isolation
Playwright creates a new browser context for each test. When using Playwright as a Test Runner, browser contexts are created automatically. The test receives an isolated BrowserContext and a default Page within that context.
Example: isolated tests with Playwright Test
import { test } from '@playwright/test';
test('example test', async ({ page, context }) => {
// "context" is an isolated BrowserContext, created for this specific test.
// "page" belongs to this context.
});
test('another test', async ({ page, context }) => {
// "context" and "page" in this second test are completely
// isolated from the first test.
});
Benefits of test isolation
Test isolation provides three main benefits: (1) No failure carry-over—if one test fails it does not affect other tests. (2) Easy debugging—you can run a single test multiple times without order dependencies. (3) No need to consider test order when running in parallel, sharding, etc.
Test isolation strategies: start from scratch vs cleanup
There are two strategies for test isolation: start from scratch or cleanup in between tests. Cleaning up between tests is problematic because it is easy to forget cleanup steps and some state is impossible to clean up, such as visited links history. State from one test can leak into the next, causing failures and debugging difficulties. Starting from scratch is better because everything is new, so failures point only to the current test.
Example: multiple contexts in single test with Playwright Test
import { test } from '@playwright/test';
test('admin and user', async ({ browser }) => {
// Create two isolated browser contexts
const adminContext = await browser.newContext();
const userContext = await browser.newContext();
// Create pages and interact with contexts independently
const adminPage = await adminContext.newPage();
const userPage = await userContext.newPage();
});
Multiple browser contexts in a single test
Playwright allows creating multiple browser contexts within a single test scenario. This is useful for testing multi-user functionality, such as testing a chat application by simulating both admin and user interactions in separate isolated contexts.
Default fixtures for isolated tests in Playwright Test
When using Playwright Test, each test function receives 'context' and 'page' fixtures. The 'context' is an isolated BrowserContext created specifically for that test, and the 'page' belongs to that context. Different tests receive completely isolated context and page instances.
runFor manual time ticking behavior
runFor ticks through time manually, firing all timers and animation frames in the process. This achieves fine-grained control over the passage of time. The parameter is specified in milliseconds. All timers due during that interval will fire as they would during normal execution.
setFixedTime example with JavaScript
Example: Set fixed time to 2024-02-02T10:00:00, navigate to page, and assert that time element shows '2/2/2024, 10:00:00 AM'. Then set fixed time to 2024-02-02T10:30:00 and assert element shows '2/2/2024, 10:30:00 AM'.
Code:
await page.clock.setFixedTime(new Date('2024-02-02T10:00:00'));
await page.goto('http://localhost:3333');
await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:00:00 AM');
await page.clock.setFixedTime(new Date('2024-02-02T10:30:00'));
await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:30:00 AM');
Testing inactivity monitoring with clock
Example: Call page.clock.install() with no arguments to use current time, navigate to page, interact with page, fast forward 5 minutes with page.clock.fastForward('05:00'), then verify logout message is visible.
Code:
await page.clock.install();
await page.goto('http://localhost:3333');
await page.getByRole('button').click();
await page.clock.fastForward('05:00');
await expect(page.getByText('You have been logged out due to inactivity.')).toBeVisible();
install and pauseAt example with JavaScript
Example: Initialize clock at 2024-02-02T08:00:00, navigate to page, pause at 2024-02-02T10:00:00, assert time shows '2/2/2024, 10:00:00 AM', fast forward 30 minutes, and assert time shows '2/2/2024, 10:30:00 AM'.
Code:
await page.clock.install({ time: new Date('2024-02-02T08:00:00') });
await page.goto('http://localhost:3333');
await page.clock.pauseAt(new Date('2024-02-02T10:00:00'));
await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:00:00 AM');
await page.clock.fastForward('30:00');
await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:30:00 AM');
Clock API methods for time control
The Clock API provides the following methods to control time in tests: setFixedTime sets the fixed time for Date.now() and new Date(); install initializes the clock; pauseAt pauses time at a specific time; fastForward fast forwards the time; runFor runs time for a specific duration; resume resumes time; setSystemTime sets the current system time. The recommended approach is to use setFixedTime for simple cases. Use install if you need to pause time later, fast forward, or tick time. setSystemTime is only recommended for advanced use cases.
install() must be called before other clock methods
If you call install() at any point in your test, it MUST occur before any other clock-related calls. Calling these methods out of order will result in undefined behavior. For example, you cannot call setInterval, followed by install, then clearInterval, because install overrides the native definition of clock functions.
Page.clock overrides native time-related classes and functions
Page.clock overrides the following native global classes and functions to allow manual control: Date, setTimeout, clearTimeout, setInterval, clearInterval, requestAnimationFrame, cancelAnimationFrame, requestIdleCallback, cancelIdleCallback, performance, and Event.timeStamp.
runFor example with pauseAt
Example: Initialize clock at 2024-02-02T08:00:00, navigate to page, pause at 2024-02-02T10:00:00, assert time shows '2/2/2024, 10:00:00 AM', run for 2000 milliseconds, and assert time shows '2/2/2024, 10:00:02 AM'.
Code:
await page.clock.install({ time: new Date('2024-02-02T08:00:00') });
await page.goto('http://localhost:3333');
await page.clock.pauseAt(new Date('2024-02-02T10:00:00'));
await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:00:00 AM');
await page.clock.runFor(2000);
await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:00:02 AM');
fastForward time format and behavior
fastForward accepts time in 'MM:SS' format, for example '30:00' for 30 minutes or '05:00' for 5 minutes. When you fast forward, it is like closing the laptop lid and opening it after the specified time. All timers that are due will fire once immediately, as they would in a real browser.
Global setup feature in VS Code
Use global setup for tasks that need to run only once before all tests, such as seeding a database. You can trigger global setup and teardown manually from the Playwright sidebar.
Project dependencies in VS Code
Use project dependencies to define setup tests that run before other tests. For example, you can create a login test that runs first, then reuse that authenticated state across multiple tests without logging in again for each test. In VS Code, you can see these setup tests in the Test Explorer and run them independently when needed.
Playwright Library JSDoc type annotations
You can use JSDoc to set types for variables in JavaScript. Example: `/** @type {import('playwright').Page} */ let page;`
Slow down Playwright Library execution with slowMo
The `slowMo` option slows down Playwright Library execution by the specified number of milliseconds. For example: `firefox.launch({ slowMo: 50 })` slows execution by 50ms.
Playwright Test example with device emulation
This example achieves similar behavior to the Library example using Playwright Test:
```js
import { expect, test, devices } from '@playwright/test';
test.use(devices['iPhone 11']);
test('should be titled', async ({ page, context }) => {
await context.route('**.jpg', route => route.abort());
await page.goto('https://example.com/');
await expect(page).toHaveTitle('Example');
});
```
Run it with `npx playwright test`. The page and context are automatically provided as fixtures, device emulation is set via test.use(), and Web-First assertions are used.
Playwright Library basic setup and teardown pattern
Playwright Library uses an async/await pattern with manual setup and teardown. The typical pattern is:
```js
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
// interactions and assertions
await browser.close();
})();
```
The entire script is wrapped in an unnamed async arrow function that is immediately invoked. Browser and context must be closed explicitly in the teardown phase.
Enable Playwright Library browser UI with headless: false
By default, Playwright Library runs browsers in headless mode. To see the browser UI, pass `headless: false` when launching the browser. For example: `firefox.launch({ headless: false, slowMo: 50 })`.
Playwright Library screenshot example
Example of taking a screenshot with Playwright Library:
```js
const { webkit } = require('playwright');
(async () => {
const browser = await webkit.launch();
const page = await browser.newPage();
await page.goto('https://playwright.dev/');
await page.screenshot({ path: `example.png` });
await browser.close();
})();
```
This uses WebKit browser, navigates to a URL, takes a screenshot, and saves it to a file.
Playwright Library example with device emulation and route interception
This example shows how to use Playwright Library directly:
```js
import { chromium, devices } from 'playwright';
import assert from 'node:assert';
(async () => {
const browser = await chromium.launch();
const context = await browser.newContext(devices['iPhone 11']);
const page = await context.newPage();
await context.route('**.jpg', route => route.abort());
await page.goto('https://example.com/');
assert(await page.title() === 'Example Domain');
await context.close();
await browser.close();
})();
```
Run it with `node my-script.js`. This example demonstrates explicit setup/teardown, device emulation via devices object, route interception, and plain assertion instead of Web-First assertions.
Playwright Library TypeScript support with // @ts-check
In JavaScript files, add `// @ts-check` at the top to enable type-checking in VS Code or WebStorm when using Playwright.
Playwright Library requires explicit browser and context setup
When using Playwright Library directly, you must explicitly: launch a browser instance with chromium.launch(), create a browser context with browser.newContext(), and create a page with context.newPage(). Context options like emulated devices must be passed explicitly as parameters.
Playwright Library TypeScript type import
In TypeScript, type definitions are imported automatically. You can also explicitly import types: `let page: import('playwright').Page;`
Playwright Library vs Test Runner comparison table
Key differences between Playwright Library and Playwright Test:
| Aspect | Library | Test |
| - | - | - |
| Installation | `npm install playwright` | `npm init playwright@latest` |
| Import from | `playwright` | `@playwright/test` |
| Browser initialization | Explicitly: pick browser, launch with BrowserType.launch(), create context with Browser.newContext(), create page with BrowserContext.newPage() | Isolated page and context provided out-of-box; lazy-initialized if referenced in test arguments |
| Assertions | No built-in Web-First Assertions, must use plain assertions like node:assert | Web-First assertions with auto-wait and retry (e.g., PageAssertions.toHaveTitle, PageAssertions.toHaveScreenshot) |
| Timeouts | Defaults to 30s for most operations | Most operations don't timeout; tests have 30s timeout by default |
| Cleanup | Explicitly close context and browser | Test Runner handles cleanup of built-in fixtures automatically |
| Running | Run as Node.js script with `node my-script.js` | Run with `npx playwright test` command; Test Runner handles compilation |
| Browser install | Install `@playwright/browser-chromium`, `@playwright/browser-firefox`, `@playwright/browser-webkit` or run `npx playwright install` | `npx playwright install` or `npx playwright install chromium` for single browser |
Playwright Test features beyond Library
Playwright Test includes additional features not available in Playwright Library: Configuration Matrix and Projects, Parallelization, Web-First Assertions, Reporting, Retries, and easily enabled Tracing.
Playwright Test file structure with imports and async test function
Each Playwright Test file requires explicit import of test and expect functions from '@playwright/test'. The test function is marked with async and receives fixtures including page as parameters.
Playwright Test fixture system
Playwright Test provides useful fixtures like page as parameters to test functions, enabling dependency injection and test isolation.
Playwright Test provides isolated Page per test
Playwright Test creates an isolated Page object for each test by default. If reusing a single Page object between multiple tests is desired, you can create it in test.beforeAll() and close it in test.afterAll().
Annotations have type and description fields
Annotations have a type field and a description field for more context. Annotations are available in the reporter API. Playwright's built-in HTML reporter shows all annotations, except those where type starts with underscore symbol.
Tag all tests in a describe group
You can tag all tests in a test.describe group by providing a tag property in the details object passed to test.describe, for example test.describe('group', { tag: '@report' }, () => { ... }).
Runtime annotation example - add browser version
Example of adding annotation at runtime: test('example test', async ({ page, browser }) => { test.info().annotations.push({ type: 'browser version', description: browser.version() }); ... });
Use fixme in beforeEach hook to avoid running hook
You can put annotations like test.fixme inside beforeEach hooks to avoid running the hook. For example, test.fixme(isMobile, 'Settings page does not work in mobile yet') in a beforeEach hook will skip running that hook (and subsequently skip running tests that depend on it) when isMobile is true.
Hooks respect skip annotations in parent describe block
When you skip a group of tests using test.skip in the describe block, the beforeAll, beforeEach, and other hooks in that group are also only run when the condition for skip is not met. For example, beforeAll hooks run only in the browsers where the group is not skipped.
Conditionally skip group of tests with callback
You can run a group of tests in specific browser only by passing a callback to test.skip within the test.describe block that checks the browserName fixture. For example, test.skip(({ browserName }) => browserName !== 'chromium', 'Chromium only!') skips all tests in the group for non-chromium browsers.
Annotate all tests in a describe group
You can annotate all tests in a test.describe group by providing an annotation property in the details object passed to test.describe.
Multiple annotations on single test
You can provide multiple annotations to a single test by passing an array of annotation objects in the annotation property: { annotation: [{ type: 'issue', description: '...' }, { type: 'performance', description: '...' }] }.
Annotate test with issue URL example
Example of annotating a test with an issue URL: test('test login page', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/23180' } }, async ({ page }) => { ... });
Multiple tags on single test
You can provide multiple tags to a single test by passing an array of tags in the tag property: { tag: ['@slow', '@vrt'] }.
Tag syntax - details object versus title
Tags can be added in two ways: as a tag property in an additional details object when declaring a test (e.g., { tag: '@fast' }), or by adding @-token directly in the test title (e.g., 'test name @fast').
Tag tests with @ symbol for filtering
Tags are prefixed with the @ symbol. You can tag a test by providing a tag property in the details object when declaring a test, or by adding @-token to the test title. Tags must start with @ symbol.
test.describe groups tests with logical name and scoped hooks
You can group tests using test.describe to give them a logical name or to scope before and after hooks to the group. Tests within a describe block share the group context.
Conditionally skip a test using test.skip inside test body
You can skip a test conditionally by calling test.skip inside the test body with a condition and optional description. The test will be skipped only when the condition is truthy.
test.only focuses tests to run only focused tests
When you use test.only to focus some tests, only those focused tests run in the entire project.
Annotations can be conditional and depend on fixtures
Built-in annotations can be conditional, in which case they apply when the condition is truthy. Conditional annotations may depend on test fixtures. Multiple annotations can be applied to the same test, possibly in different configurations.
test.slow marks test as slow and triples timeout
The test.slow annotation marks a test as slow and triples the test timeout.
test.fixme marks test as failing without running it
The test.fixme annotation marks a test as failing but does not run the test, unlike the fail annotation. Use fixme when running the test is slow or crashes.
Runtime annotations via test.info().annotations
While a test is already running, you can add annotations to test.info().annotations array. This allows you to add annotations dynamically during test execution, for example to record browser version or other runtime information.
test.fail marks test as failing and ensures it fails
The test.fail annotation marks a test as failing. Playwright will run this test and ensure it does indeed fail. If the test does not fail, Playwright will report an error.
test.skip marks test as irrelevant and not run
The test.skip annotation marks a test as irrelevant. Playwright does not run tests marked with skip. Use this annotation when the test is not applicable in some configuration.
Global setup test file structure
Global setup code must be defined as regular tests by calling the test() function from @playwright/test. Import test as setup with 'import { test as setup } from @playwright/test;' and define setup actions within a setup() block. The setup file should be stored at tests/global.setup.ts by default.
TestProject.dependencies property syntax
Add the dependencies property to a test project configuration as an array of project names that must run before the current project. For example, if a project named 'chromium with db' depends on a setup project named 'setup db', set dependencies: ['setup db'] on the 'chromium with db' project.
Project dependencies testMatch for setup files
To configure a setup project using project dependencies, add a project with a testMatch property that matches your setup file, typically named /global\.setup\.ts/. This project should not have any browser configuration, as it only needs to run the setup logic.
Project dependencies vs globalSetup comparison
Project dependencies are the recommended approach for global setup and teardown over the globalSetup config option. Project dependencies offer these advantages over globalSetup: HTML report shows setup as a separate project, full trace recording is available, Playwright fixtures are fully supported, browser management is via the browser fixture instead of manual browserType.launch(), parallelism and retries are supported via standard config, and config options like headless or testIdAttribute are automatically applied. globalSetup lacks all these features.