Playwright Test

In addition to being a browser automation tool, Playwright also comes with acomplete test framework —— Playwright Test。

Playwright Test helps us organize test cases, generate reports, run in parallel, retry on failure, and is fully featured out of the box.

  1. Playwright Test is anintegrated test framework, with no additional dependencies.
  2. The configuration file can settimeout, retries, reports, browser projectsetc.
  3. Test cases supportgrouping, hooks, data-driven。
  4. and provideparallel execution, HTML reports, failure retriesand other advanced capabilities.

Test Framework Overview

  • Integrated test framework: comes with a built-in test runner, no need to install Jest or Mocha separately.
  • Built-in assertion library: supportsexpectassertions.
  • Test isolation: eachtestruns in a new browser context by default, independent of each other.
  • Rich features: parallel execution, retries, screenshots, recording, HTML reports, etc.

Installation method:

npm init playwright@latest

Automatically generate project structure, including configuration fileplaywright.config.js。

1. Configuration File Details

Playwright Test usesplaywright.config.js(or.ts) as the configuration entry point.

// playwright.config.js
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests',         // 测试目录
  timeout: 30 * 1000,         // 单个测试超时时间
  retries: 2,                 // 失败重试次数
  reporter: [['html'], ['list']], // 测试报告类型
  use: {
    headless: true,           // 是否无头模式
    screenshot: 'only-on-failure', // 失败时截图
    video: 'retain-on-failure',    // 失败时保留视频
    baseURL: 'https://example.com', // 基础 URL
  },
  projects: [
    { name: 'Chromium', use: { browserName: 'chromium' } },
    { name: 'Firefox',  use: { browserName: 'firefox' } },
    { name: 'WebKit',   use: { browserName: 'webkit' } },
  ],
});

2. Test Script Structure

Playwright Test scripts usually include:

1. Import the test library

  import { test, expect } from '@playwright/test';

2. Write test cases

test('示例测试', async ({ page }) => {
  await page.goto('https://example.com');
  await expect(page).toHaveTitle('Example Domain');
});

3. Grouping and hooks

  • Usedescribeto organize tests
  • UsebeforeEach / afterEachfor setup and teardown

Writing Test Cases

1. test()Function Usage

test('页面标题验证', async ({ page }) => {
  await page.goto('https://playwright.dev');
  await expect(page).toHaveTitle(/Playwright/);
});

2. describe()Grouping

test.describe('用户登录模块', () => {
  test('输入正确账号密码', async ({ page }) => {
    // 登录逻辑...
  });

  test('输入错误密码', async ({ page }) => {
    // 验证错误提示...
  });
});

3. Test Hooks

test.describe('购物车模块', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('https://example.com/cart');
  });

  test.afterEach(async ({ page }) => {
    await page.screenshot({ path: 'cart.png' });
  });

  test('添加商品', async ({ page }) => {
    await page.click('#add-item');
    await expect(page.locator('#cart-count')).toHaveText('1');
  });
});

4. Test Data Preparation

Supports parameterized tests:

const users = [
  { name: 'admin', password: '123456' },
  { name: 'guest', password: 'guest' },
];

for (const user of users) {
  test(`用户 ${user.name} 登录`, async ({ page }) => {
    await page.goto('https://example.com/login');
    await page.fill('#username', user.name);
    await page.fill('#password', user.password);
    await page.click('#submit');
    await expect(page.locator('.welcome')).toBeVisible();
  });
}

Running and Reporting

1. Test Execution Commands

npx playwright test          # 运行全部测试
npx playwright test login.spec.js   # 运行单个文件
npx playwright test -g "登录"       # 运行指定用例

2. Parallel Testing

Playwright by defaultruns files in parallel, or you can specify the number of workers in the configuration file:

npx playwright test --workers=4

3. Test Report Generation

After running, generateHTML report:

npx playwright show-report

4. Failure Retry Mechanism

In the configuration fileplaywright.config.jsset:

retries: 2

When a test fails, it will automatically retry, suitable for handling intermittent errors.

  1. Playwright Test is anintegrated test framework, no additional dependencies needed.
  2. The configuration file can settimeout, retries, reports, browser projectsetc.
  3. Test cases supportgrouping, hooks, data-driven。
  4. and provideparallel execution, HTML reports, failure retriesand other advanced capabilities.

Complete Test Suite Example

1. Project Structure

playwright-demo/
├─ playwright.config.ts           # 全局配置(多项目、多浏览器、报告、重试…)
├─ package.json
├─ tests/
│  ├─ auth.setup.ts               # 一次性登录,生成 storageState
│  ├─ smoke/
│  │  └─ home.spec.ts             # 冒烟用例:标题/URL/截图对比
│  └─ e2e/
│     └─ cart.spec.ts             # 端到端:添加购物车、断言、附件、步骤
├─ fixtures/
│  ├─ pages.ts                    # 页面对象(PO)
│  └─ test.extend.ts              # 自定义 fixtures
└─ snapshots/                     # 视觉基线(自动生成)

2. Configuration File (playwright.config.ts)

Example

// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  timeout: 30_000,
  retries: 2,
  workers: 4,
  reporter: [['html', { outputFolder: 'report' }], ['list']],
  forbidOnly: !!process.env.CI,

  // Unified default usage (can be overridden at project or test level)
  use: {
    baseURL: 'https://demo.playwright.dev',
    headless: true,
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
    trace: 'retain-on-failure',
    viewport: { width: 1366, height: 900 },
    locale: 'zh-CN',
  },

  // Multiple projects: different browsers/devices/login states
  projects: [
    { name: 'Chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'Firefox',  use: { ...devices['Desktop Firefox'] } },
    {
      name: 'Mobile Chrome',
      use: { ...devices['Pixel 7'], headless: true },
    },
    // Project with login state (reuse storageState)
    {
      name: 'Chromium Logged-in',
      use: { storageState: 'storageState.json' },
      dependencies: ['setup'], // Depends on a setup project to generate login state
    },
    // Setup project: runs once to generate storageState
    {
      name: 'setup',
      testMatch: /auth\.setup\.ts/,
    },
  ],
});

Key points:projects allow the same test suite to run in different browsers/configurations; use can be overridden at the global/project/test level; reports and retries are managed centrally in the configuration.

3. Fixtures and Page Objects

fixtures/test.extend.ts (custom fixtures + encapsulation of common steps)

Example

// fixtures/test.extend.ts
import { test as base } from '@playwright/test';
import { HomePage, CartPage } from './pages';

type MyFixtures = {
  home: HomePage;
  cart: CartPage;
};

export const test = base.extend<MyFixtures>({
  home: async ({ page }, use) => {
    const home = new HomePage(page);
    await use(home);
  },
  cart: async ({ page }, use) => {
    const cart = new CartPage(page);
    await use(cart);
  },
});

export { expect } from '@playwright/test';

Add custom fixtures based on test.extend(), making it easy to directly inject home and cart instances in any test case.

fixtures/pages.ts (page objects)

Example

// fixtures/pages.ts
import { Page, expect } from '@playwright/test';

export class HomePage {
  constructor(private page: Page) {}
  async goto() { await this.page.goto('/'); }
  get tryTodoLink() { return this.page.getByRole('link', { name: /ToDo MVC/i }); }
}

export class CartPage {
  constructor(private page: Page) {}
  async goto() { await this.page.goto('/e2e'); } // Assuming an e2e sample page exists
  get addFirstItem() { return this.page.getByRole('button', { name: /Add .* #1/i }); }
  get cartCount() { return this.page.locator('[data-test=cart-count]'); }
  async addOne() { await this.addFirstItem.click(); }
  async expectCount(n: number) { await expect(this.cartCount).toHaveText(String(n)); }
}

4. Pre-login (Generate storageState Once)

tests/auth.setup.ts (runs only in the "setup" project)

Example

// tests/auth.setup.ts
import { test, expect } from '@playwright/test';

test('create storageState', async ({ page }) => {
  await page.goto('https://demo.playwright.dev/todomvc');
  // ...Replace with your login flow...
  // Assume login is successful
  await expect(page).toHaveURL(/todomvc/);
  await page.context().storageState({ path: 'storageState.json' });
});

5. Smoke Tests (Title/URL/Visual Comparison)

tests/smoke/home.spec.ts

Example

import { test, expect } from '@playwright/test';

test.describe('Smoke - Home', () => {
  test('title & url & visual snapshot', async ({ page }) => {
    await page.goto('https://playwright.dev');
    await expect(page).toHaveTitle(/Playwright/);
    await expect(page).toHaveURL(/playwright\.dev/);

    // Visual regression (auto-generate/compare baseline)
    await expect(page).toHaveScreenshot(); // First run generates a baseline
  });
});

For visual comparison, it is recommended to use toHaveScreenshot (preferred over the manual approach using page.screenshot() + toMatchSnapshot).

6. End-to-End Tests (Steps, Attachments, Assertions)

tests/e2e/cart.spec.ts

Example

import { test, expect } from '../../fixtures/test.extend';

test.describe.configure({ mode: 'parallel' });

test.describe('E2E - Cart', () => {
  test.beforeEach(async ({ home }) => {
    await test.step('Go to homepage', async () => {
      await home.goto();
    });
  });

  test('Add product to cart (with attachment and assertion)', async ({ page, cart }) => {
    await test.step('Go to cart page and add product', async () => {
      await cart.goto();
      await cart.addOne();
      await cart.expectCount(1);
    });

    await test.step('Attach debug information to report', async () => {
      await test.info().attach('state.json', {
        contentType: 'application/json',
        body: Buffer.from(JSON.stringify({ ts: Date.now(), note: 'after add' })),
      });
    });

    // Element-level screenshot
    await test.step('Element screenshot', async () => {
      const badge = page.locator('[data-test=cart-count]');
      await expect(badge).toBeVisible();
      await expect(badge).toHaveScreenshot(); // Element snapshot
    });
  });
});

Use test.step to record steps hierarchically, and use test.info().attach() to attach debug data/screenshots to the report for easier troubleshooting; parallel mode can use describe.configure({ mode: 'parallel' }).

7. Running and Reporting

Common commands:

npx playwright test                # 运行全部
npx playwright test tests/e2e      # 运行目录
npx playwright test -g "Cart"      # 按标题过滤
npx playwright test --project="Chromium"   # 指定项目
npx playwright show-report         # 打开HTML报告
npx playwright test --update-snapshots     # 更新视觉基线
npx playwright test --debug        # 调试模式(Inspector)

For CLI filtering, project selection, report viewing, snapshot updates, etc., see the official CLI/Reporting/Snapshot documentation.


Common Related API Quick Reference (Playwright Test)

1. Tests and Grouping

API Purpose
test(name, fn) Define tests
test.describe(title, fn) Grouping
test.describe.configure({ mode }) parallel / serial
test.beforeAll/afterAll Group-level before/after hooks
test.beforeEach/afterEach Test case before/after hooks
test.skip/only/fixme/slow/fail Mark/control test cases

2. Fixtures and Configuration

API Purpose
test.extend<Fixtures>(defs) Extend custom fixtures
test.use(options) Test-level overrideuseOptions (such asstorageState、viewport、localeetc.)
defineConfig({...}) Configuration file entry (testDir、retries、workers、reporter…)
projects multi-project/multi-browser/different configurations running
use: { baseURL, trace, video, screenshot, viewport, locale, timezoneId, storageState } Runtime environment and capture strategy

For details on fixtures / use / configuration options and projects, see the official "Fixtures / Use options / Configuration / Projects" documentation. (Playwright)

3. Assertions (expect)

API Purpose
expect(value).toBe / toEqual / toContain ... General assertions
expect(locator).toHaveText/Value/Attribute Web-specific assertions (with smart waiting)
expect(locator).toBeVisible/Hidden/Enabled/Disabled/Checked State assertions
expect(page).toHaveTitle/URL Page assertions
`expect(page/locator).toHaveScreenshot([name options])` Visual snapshot comparison (recommended)

For recommended practices on assertions and visual snapshots, see the Assertions and Snapshots documentation.(Playwright)

4. Steps and Attachments

API Purpose
test.step(name, fn) Break tests down into steps (report visualization)
`test.info().attach(name, { path body, contentType })` Attach files/data to tests or steps (displayed in the report)

Attachments can also be applied at the step level (see TestStep / TestStepInfo).(Playwright)

5. Reporting and CLI

Command/Configuration Purpose
reporter: [['html', { outputFolder }], ['list']] Report configuration
npx playwright test --project="Chromium" Select project to run
-g "keyword" / --grep / --grep-invert Filter test cases
--update-snapshots Update visual baselines
npx playwright show-report Open report

See the official documentation for the full CLI and Reporter options.(Playwright)

Other extensions