You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Playwright测试中如何确保fixture的beforeEach在每个测试中执行

问题:Playwright 多测试文件共享 beforeEach 仅首次执行

我用Playwright编写E2E测试,定义了两个Fixture:

  • 启动带有Chrome扩展的浏览器(fixtures.js)
  • Mock外部API调用以测试认证状态(fixtures-auth.js)

两个测试文件分别位于不同目录,均通过import { test, expect } from '../fixtures-auth.js'导入Fixture,但同时运行测试时,仅第一个测试会执行fixtures-auth.js中的test.beforeEach逻辑,第二个测试虽导入却未执行。单独运行每个测试均正常,目标是让每个测试都自动执行该beforeEach以避免代码重复。


相关代码

fixtures-auth.js

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

test.beforeEach(async ({ context }) => {
    await context.route(
        'https://some.url.com/api/some-endpoint',
        async (route) => {
            if (route.request().serviceWorker()) {
                const json = {
                    // json content
                };
                await route.fulfill({ json });
            }
        },
    );
});

fixtures.js

const base = require('@playwright/test');
const { chromium } = require('@playwright/test');
const path = require('path');

process.env.PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS = '1';

exports.test = base.test.extend({
    context: async ({ }, use) => {
        const pathToExtension = path.join(__dirname, '../dist');
        const context = await chromium.launchPersistentContext('', {
            headless: false,
            args: [
                `--disable-extensions-except=${pathToExtension}`,
                `--load-extension=${pathToExtension}`,
            ],
        });
        await use(context);
        await context.close();
    },
    extensionId: async ({ context }, use) => {
        // for manifest v3:
        let [background] = context.serviceWorkers();
        if (!background)
            background = await context.waitForEvent('serviceworker');

        const extensionId = background.url().split('/')[2];
        await use(extensionId);
    },
});
exports.expect = base.expect;

Playwright 配置文件

const { defineConfig } = require('@playwright/test');

const baseURL = 'http://127.0.0.1:8080';
module.exports = defineConfig({
    testDir: './playwright',
    fullyParallel: true,
    forbidOnly: false,
    retries: 0,
    maxFailures: 1,
    workers: 1,
    reporter: 'line',
    expect: {
        timeout: 10 * 1000,
    },
    use: {
        baseURL: baseURL,
        trace: 'on-first-retry',
        serviceWorkers: 'allow',
    },
    projects: [
        {
            name: 'chromium',
            use: { browserName: 'chromium' },
        },
    ],
    webServer: {
        command: 'npx http-server ./playwright',
        url: baseURL,
        reuseExistingServer: true,
    },
});

问题分析

问题核心在于fixtures-auth.js的写法:直接在从fixtures.js导入的test实例上注册全局beforeEach,虽然模块缓存会让这个注册全局生效,但Playwright的测试隔离机制可能导致后续测试无法触发该钩子;或者因为上下文创建与钩子执行的时序问题,导致路由未正确绑定到新的测试上下文。

另外,你要Mock的是Chrome扩展后台Service Worker的请求,当前写法依赖beforeEach在每个测试开始时给新上下文添加路由,但串行运行时,第二个测试的上下文创建后,钩子可能未被正确触发(或你误以为未触发)。


解决方案

方案1:将路由逻辑整合到Context Fixture中(推荐)

修改fixtures-auth.js,通过extend扩展Context Fixture,确保每个新创建的上下文都会自动添加路由规则,完全避免依赖全局beforeEach:

import { test as baseTest } from './fixtures.js';

// 扩展原有test实例,为context添加路由逻辑
export const test = baseTest.extend({
    context: async ({ context }, use) => {
        await context.route(
            'https://some.url.com/api/some-endpoint',
            async (route) => {
                if (route.request().serviceWorker()) {
                    const json = {
                        // json content
                    };
                    await route.fulfill({ json });
                }
            }
        );
        // 将配置好的上下文传递给测试
        await use(context);
    }
});

export { expect } from './fixtures.js';

这种方式利用Playwright Fixture的生命周期,每个测试的上下文在创建后都会立即绑定路由,完全隔离且可靠,无论单独还是批量运行测试都会生效。

方案2:验证并修复全局beforeEach的触发

如果你希望保留原有的beforeEach写法,可以先添加日志验证钩子是否真的未执行:

test.beforeEach(async ({ context }) => {
    console.log(`[BEFORE EACH] 为上下文添加路由`);
    await context.route(/* ... */);
});

运行测试后查看控制台输出:

  • 如果第二个测试有日志输出,说明钩子已执行,问题出在路由匹配逻辑(例如Service Worker请求未触发路由),可以检查route.request().serviceWorker()的判断是否准确,或调整路由注册时机(确保Service Worker已启动后再注册路由)。
  • 如果无日志输出,说明钩子未被注册到第二个测试,可将fixtures-auth.js改为导出扩展后的test实例:
import { test as baseTest } from './fixtures.js';

export const test = baseTest;

test.beforeEach(async ({ context }) => {
    await context.route(/* ... */);
});

export { expect } from './fixtures.js';

额外注意事项

  • 你的Playwright配置中fullyParallel: true但workers: 1,测试会串行执行,每个测试都会创建全新的上下文,因此必须确保每个上下文都独立配置路由。
  • 由于你Mock的是Service Worker的请求,确保在路由注册前,Service Worker已完成启动(可利用fixtures.js中extensionId Fixture的等待逻辑,确保Service Worker就绪后再执行测试逻辑)。

内容的提问来源于stack exchange,提问作者mrzalais

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 00:07:34