Global setup is being used by many teams and companies to login to an application and then use this setup for tests that need to be in an authenticated state; however, it is lacking some important features. When you use global setup, you don't see a trace for the setup part of your tests and the setup doesn't appear in the HTML report. This can make debugging difficult. It's also not possible to use fixtures in global setup.
In order to fix this issue, project dependencies were created.
What are project dependencies?
Project dependencies are a better way of doing global setup. To add a dependency, so that one project depends on another project, create a separate project in the Playwright config for your setup tests, where each test in this project will be a step in the setup routine.
Every time you run a test from the basic project, it will first run the tests from the setup project.
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'setup',
testMatch: '**/*.setup.ts',
},
{
name: 'basic',
dependencies: ['setup'],
},
],
});
By using project dependencies, the HTML report will show the setup tests, the trace viewer will record traces of the setup, and you can use the inspector to inspect the DOM snapshot of the trace of your setup tests and you can also use fixtures.
Running sequence
Tests in the 'setup' project will run first and then the tests in the 'chromium', 'webkit', and 'firefox' projects will run in parallel once all tests in setup have completed.
What happens if a dependency fails?
In the following example, you will see an 'e2e tests' project that depends on both the 'Browser Login' project and the 'Database' project. The 'Browser Login' project and the Database project will run in parallel. However, as the 'Database' project failed, the 'e2e tests' project will never run as it depends on both the 'Browser Login' and the 'Database' projects to pass.
Project Dependency example
Playwright runs tests in isolated environments called Browser Contexts. Every test runs independently from any other test. This means that each test has its own local storage, session storage, cookies, etc. However, tests can use the storage state, which contains cookies and a local storage snapshot, from other tests so as to run tests in a logged in state.
Let's create an example of how to use Project dependencies to have a global setup that logs into Wikipedia and saves the storage state so that all tests from the e2e project start running in this logged in state.
First, install Playwright using the CLI or VS Code extension. You can then modify the config file, create your login test, and an e2e test that starts from a logged in state by using storage state.
Configuring the setup project
Start by creating a basic playwright.config.ts
file or modifying the one that has already been created for us. The options needed are the testDir
option which is the name of the directory where you want to store your tests and the projects
options which is what projects you want to run.
A project is a logical group of tests that run using the same configuration. The first project you need is one called 'setup' and by using testMatch
you can filter any files that end in setup.ts
and only these tests will be run when running the 'setup' project.
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
projects: [
{
name: 'setup',
testMatch: '**/*.setup.ts',
},
],
});
Create a login test
Next, create a login.setup.ts
. To make it easier to see that this is a setup test, you can import test as setup
and then instead of using the word test
you can use the word setup
when writing your test.
The aim is to create a test that logs into Wikipedia and ensures that the user is in a logged in state. You can use Playwright's test generator either from the VS Code extension or using the CLI to open the Playwright Inspector, and generate the code by clicking on the 'Log in' button and filling out the username and password. You can then add the assertion to ensure that once logged in you can see the 'Personal Tools' option.
If you don't already have a username or password, you can quickly create an account and then use your own credentials to see that the tests work.
// login.setup.ts
import { test as setup, expect } from '@playwright/test';
setup('do login', async ({ page }) => {
await page.goto('https://en.wikipedia.org');
await page.getByRole('link', { name: 'Log in' }).click();
await page.getByPlaceholder('Enter your username').fill('your_username');
await page.getByPlaceholder('Enter your password').fill('your_password');
await page.getByRole('button', { name: 'Log in' }).click();
await expect(page.getByRole('button', { name: 'Personal tools' })).toBeVisible();
});
Using env variables
In order for your username and password to be secure, you can store them as .env
variables and access them in your tests through process.env.USERNAME!
and process.env.PASSWORD!
.
// login.setup.ts
import { test as setup, expect } from '@playwright/test';
setup('do login', async ({ page }) => {
await page.goto('https://en.wikipedia.org');
await page.getByRole('link', { name: 'Log in' }).click();
await page.getByPlaceholder('Enter your username').fill(process.env.USERNAME!);
await page.getByPlaceholder('Enter your password').fill(process.env.PASSWORD!);
await page.getByRole('button', { name: 'Log in' }).click();
await expect(page.getByRole('button', { name: 'Personal tools' })).toBeVisible();
});
Don't forget to install the dotenv
package from npm.
npm i dotenv
Once the package is installed, import it in your Playwright config with require('dotenv').config();
so you have access to the .env
variables.
// playwright.config.ts
import { defineConfig } from '@playwright/test';
require('dotenv').config();
export default defineConfig({
testDir: './tests',
projects: [
{
name: 'setup',
testMatch: '**/*.setup.ts',
},
{
name: 'e2e tests',
dependencies: ['setup'],
},
],
});
Next, create a .env
file and add the username and password. Make sure to add this file to the .gitignore
so that your secrets don't end up on CI.
USERNAME: your_username
PASSWORD: your_password
You can use GitHub secrets when working with CI. Create your repository secrets in the settings of your repo and then add the env variables to the GitHub actions workflow already created when installing Playwright.
env:
USERNAME: ${{secrets.USERNAME}}
PASSWORD: ${{secrets.PASSWORD}}
Create a e2e tests project
Create a project called e2e tests logged in
. This project will depend on the 'setup' project and will match any test files that end in loggedin.spec.ts
.
// playwright.config.ts
import { defineConfig } from '@playwright/test';
require('dotenv').config();
export default defineConfig({
testDir: './tests',
projects: [
{
name: 'setup',
testMatch: '**/*.setup.ts',
},
{
name: 'e2e tests logged in',
testMatch: '**/*loggedin.spec.ts',
dependencies: ['setup'],
},
],
});
Adding storage state
The setup project will write storage state into an 'auth.json' file in a .auth
folder inside the playwright folder. This exports a const of STORAGE_STATE
to share the location of the storage file between projects.
Next, you need to tell your test to use the STORAGE_STATE
variable you have created as the value of its storageStage
. This returns the storage state for the browser context and contains the current cookies and a local storage snapshot.
// playwright.config.ts
import { defineConfig } from '@playwright/test';
import path from 'path';
require('dotenv').config();
export const STORAGE_STATE = path.join(__dirname, 'playwright/.auth/user.json');
export default defineConfig({
testDir: './tests',
projects: [
{
name: 'setup',
testMatch: '**/*.setup.ts',
},
{
name: 'e2e tests logged in',
testMatch: '**/*loggedin.spec.ts',
dependencies: ['setup'],
use: {
storageState: STORAGE_STATE,
},
},
],
});
At the moment, there is nothing saved in the STORAGE_STATE
so the next step is to populate the context with the storage state after login actions have been performed. By doing this you only have to log in once and the credentials will be stored in the STORAGE_STATE
file, meaning you don't need to log in again for every test. Start by importing the STORAGE_STATE
from the Playwright config file and then use this as the path to save your storage state to.
// login.setup.ts
import { test as setup, expect } from '@playwright/test';
import { STORAGE_STATE } from '../playwright.config';
setup('do login', async ({ page }) => {
await page.goto('https://en.wikipedia.org');
await page.getByRole('link', { name: 'Log in' }).click();
await page.getByPlaceholder('Enter your username').fill('TestingLogin');
await page.getByPlaceholder('Enter your password').fill('e2etests');
await page.getByRole('button', { name: 'Log in' }).click();
await expect(page.getByRole('button', { name: 'Personal tools' })).toBeVisible();
await page.context().storageState({ path: STORAGE_STATE });
});
Create a e2e test
Create some e2e tests that continue testing the application from a logged in state. When running all tests in the file the setup will only be run once and the second test will start already authenticated because of the specified storageState
in the config.
// e2e-loggedin.spec.ts
import { test, expect } from '@playwright/test';
test.beforeEach(async ({ page }) => {
await page.goto('https://en.wikipedia.org');
});
test('menu', async ({ page }) => {
await page.getByRole('link', { name: 'TestingLogin' }).click();
await expect(page.getByRole('heading', { name: /TestingLogin/i })).toBeVisible();
await page.getByRole('link', { name: /alerts/i }).click();
await page.getByText('Alerts', { exact: true }).click();
await page.getByRole('button', { name: /notice/i }).click();
await page.getByText('Notices').click();
await page.getByRole('link', { name: /watchlist/i }).click();
})
test('logs user out', async ({ page }) => {
await page.getByRole('button', { name: /Personal tools/i }).check();
await page.getByRole('link', { name: /Log out/i }).click();
await expect(page.getByRole('heading', { name: /Log out/i })).toBeVisible();
await expect(page.getByRole('link', { name: 'Log in', exact: true })).toBeVisible();
})
Configure the baseURL
When using the same URL for both the 'setup' and 'e2e tests', you can configure the baseURL
in the playwright.config.ts
file. Setting a baseURL
means you can just use page.goto('/')
in your tests which is quicker to write, less prone to typos and it makes it easier to manage should the baseURL change in the future.
// playwright.config.ts
import { defineConfig } from '@playwright/test';
import path from 'path';
require('dotenv').config();
export const STORAGE_STATE = path.join(__dirname, 'playwright/.auth/user.json');
export default defineConfig({
testDir: './tests',
use: {
baseURL: 'https://en.wikipedia.org',
},
projects: [
{
name: 'setup',
testMatch: '**/*.setup.ts',
},
{
name: 'e2e tests logged in',
dependencies: ['setup'],
use: {
storageState: STORAGE_STATE,
},
},
],
});
You can then use a /
instead of 'https://en.wikipedia.org' for the page.goto
in all your tests going forward including the 'setup' test that you created.
// e2e-loggedin.spec.ts
import { test, expect } from '@playwright/test';
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
//...
Configure the HTML Reporter
If you don't already have this setup in your Playwright config, then the next step is to add the HTML reporter to the playwright.config.ts
file in order to setup the HTML reports for your tests. You can also add the retries
for CI, set fullyParallel
to true and set the trace
to be recorded on the first retry of a failed test.
// playwright.config.ts
import { defineConfig } from '@playwright/test';
import path from 'path';
require('dotenv').config();
export const STORAGE_STATE = path.join(__dirname, 'playwright/.auth/user.json');
export default defineConfig({
testDir: './tests',
// Configure the reporter
reporter: ['html'],
// Retry on CI only
retries: process.env.CI ? 2 : 0,
// Run tests in files in parallel
fullyParallel: true,
use: {
baseURL: 'https://en.wikipedia.org',
// run traces on the first retry of a failed test
trace: 'on-first-retry',
},
projects: [
{
name: 'setup',
testMatch: '**/*.setup.ts',
},
{
name: 'e2e tests logged in',
dependencies: ['setup'],
use: {
storageState: STORAGE_STATE,
},
},
],
});
You can continue to add more projects to the config to add tests that don't require the user to be logged in. Using the testing filters of testIgnore
you can ignore all the setup tests and logged in tests when running the tests in this project.
// playwright.config.ts
import { defineConfig } from '@playwright/test';
import path from 'path';
require('dotenv').config();
export const STORAGE_STATE = path.join(__dirname, 'playwright/.auth/user.json');
export default defineConfig({
testDir: './tests',
// Configure the reporter
reporter: ['html'],
// Retry on CI only
retries: process.env.CI ? 2 : 0,
// Run tests in files in parallel
fullyParallel: true,
use: {
baseURL: 'https://en.wikipedia.org',
// run traces on the first retry of a failed test
trace: 'on-first-retry',
},
projects: [
{
name: 'setup',
testMatch: '**/*.setup.ts',
},
{
name: 'e2e tests logged in',
dependencies: ['setup'],
use: {
storageState: STORAGE_STATE,
},
},
{
name: 'e2e tests',
testIgnore: ['**/*loggedin.spec.ts', '**/*.setup.ts'],
},
],
});
When you don't specify a browser, tests will be run on Chromium by default. You can of course run these tests on different browsers and devices and setup more projects. To learn more about Projects and Browsers check out the Playwright docs.
Viewing the HTML report and trace viewer
Now you can run your tests from the CLI with the flag --trace on
in order to see a full report of your tests including the 'setup' and 'e2e tests logged in' and also see a trace of each of them.
npx playwright test --trace on
After running the tests, you should see in the CLI that 2 tests have passed and you can now run the command to open the HTML report.
npx playwright show-report
This command opens up the Playwright HTML report where you can see your two projects, the 'setup' project containing the login test and the 'e2e tests logged in' project containing the menu test and the log out test.
You can open the report for each of the tests including the 'setup test' and walk through each step of the test which is really helpful for debugging.
To enhance the debugging experience even further, when you use the --trace on
flag, you have a fully recorded trace of all your tests including the setup test. You can open the trace and view the timeline or go through every action, see the network requests, the console, the test source code and use dev tools to inspect the DOM snapshots.
By clicking on the pop out button above the DOM snapshot you get a full view of your trace where you can easily inspect the code with Dev tools and debug your global setup should you need to.
Conclusion
Using Project dependencies makes it so much easier to have a global setup with out of the box HTML reports and traces of your setup.
- Check out the Playwright docs to learn more
- Check out the 1.31 release video which goes though a similar demo.
- Clone the repository of this demo