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

Playwright测试块失败时重复执行beforeAll及重新初始化数据的问题解决方案咨询

Playwright测试块失败时重复执行beforeAll及重新初始化数据的问题解决方案咨询

各位Playwright大佬好,我最近在做端到端测试时碰到了一个棘手的问题,想请教下大家怎么解决。

我现在的测试架构是按功能划分测试文件,每个文件覆盖一个完整的功能流程:在模块级别生成随机测试数据(比如学校名称、负责人邮箱),然后通过test.beforeAll钩子一次性初始化数据库,后续所有测试块都依赖这套数据,而且必须按顺序执行(已经在配置里设了workers: 1)。

但只要有任何一个测试块失败——不管是用CLI重试还是在UI模式下恢复执行——Playwright就会重新加载整个测试文件。这直接导致beforeAll再次运行,模块级变量被重新初始化,数据库也被填充了全新的数据。后面的测试块用新数据跑,自然就和之前的上下文不匹配,引发连锁失败。

一开始我以为是Faker的随机数生成有问题,特意换成了自定义的generateId函数验证,结果发现问题根源是Playwright本身的行为:单个测试失败后它会重新加载整个文件,和随机数工具无关。

下面是我的配置和最小复现代码,想请教各位:怎么才能让beforeAll(以及模块级变量)在每个文件的一次运行周期里只执行一次,哪怕有测试块失败也不重复初始化?


我的Playwright配置

export default defineConfig({
  testDir: './playwright',
  workers: 1,
  reporter: 'html',
  timeout: 10000,
  use: {
    baseURL: 'http://127.0.0.1:3000',
    screenshot: 'only-on-failure',
    trace: 'retain-on-failure',
    testIdAttribute: 'data-test',
    navigationTimeout: 10000
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] }
    }
  ]
});

最小复现代码

function generateId(length = 6, separator = '-'): string {
  const timestamp = +new Date();
  const _getRandomInt = (min, max): number => {
    return Math.floor(Math.random() * (max - min + 1)) + min;
  };
  const ts = timestamp.toString();
  const parts = ts.split('').reverse();
  let id = '';
  for (let i = 0; i < length; ++i) {
    const index = _getRandomInt(0, parts.length - 1);
    if (i % 3 === 0 && i && separator) id += separator;
    id += parts[index];
  }
  return id;
}

const randomNumber = generateRandomNumber();
faker.seed(randomNumber);
console.log('randomNumber', randomNumber);

test.describe('Playwright behaviour', () => {
  const mySchoolName: string = `${generateSchoolName()} School`;
  const leaderFirstName: string = generateFirstName();
  const leaderlastName: string = generateLastName();
  const schoolEmail: string = generateUserEmail(leaderFirstName, leaderlastName);
  const generateIdString: string = generateId(6, '-');

  test.beforeAll(async () => {
    const { accessToken: adminAccessToken } = await getTokenFromLogin(admin, password);
    await createSchoolAndDummyWondeData(adminAccessToken, {
      schoolName: mySchoolName,
      leaderPassword: password,
      leader: {
        firstname: leaderFirstName,
        lastname: leaderlastName,
        email: schoolEmail,
      }
    });
  });

  test.beforeEach(async ({ page }) => {
    await page.goto('/');
  });

  test('Add test data', async ({ page }) => {});

  test('block 1', async ({ page }) => {
    console.log('TEST BLOCK 1');
    console.log(`School Name: ${mySchoolName}`);
    console.log(`Leader Name: ${leaderFirstName} ${leaderlastName}`);
    console.log(`Leader Email: ${schoolEmail}`);
    console.log('randomNumber', randomNumber);
    console.log('generateIdString', generateIdString);
    await login(page, schoolEmail, password);
  });

  test('block 2', async ({ page }) => {
    console.log('TEST BLOCK 2');
    console.log(`School Name: ${mySchoolName}`);
    console.log(`Leader Name: ${leaderFirstName} ${leaderlastName}`);
    console.log(`Leader Email: ${schoolEmail}`);
    console.log('randomNumber', randomNumber);
    console.log('generateIdString', generateIdString);
    await login(page, 'iwillfail@failure.com', password); // 这行必定失败
  });

  test('block 3', async ({ page }) => {
    console.log('TEST BLOCK 3');
    console.log(`School Name: ${mySchoolName}`);
    console.log(`Leader Name: ${leaderFirstName} ${leaderlastName}`);
    console.log(`Leader Email: ${schoolEmail}`);
    console.log('randomNumber', randomNumber);
    console.log('generateIdString', generateIdString);
    await login(page, schoolEmail, password);
  });

  test('block 4', async ({ page }) => {
    console.log('TEST BLOCK 4');
    console.log(`School Name: ${mySchoolName}`);
    console.log(`Leader Name: ${leaderFirstName} ${leaderlastName}`);
    console.log(`Leader Email: ${schoolEmail}`);
    console.log('randomNumber', randomNumber);
    console.log('generateIdString', generateIdString);
    await login(page, schoolEmail, password);
  });
});

我目前想到的几个方向,但还没找到最优解

  1. 用测试上下文共享状态:把数据存在test.info()的上下文里,beforeAll先检查上下文是否有数据再决定是否初始化,但不确定Playwright重试时上下文会不会被重置。
  2. 用文件系统做初始化标记:在beforeAll里先检查有没有初始化标记文件,有就跳过,没有就执行初始化并创建标记。但要注意清理标记,避免影响下一次测试运行。
  3. 调整Playwright的重试策略:比如给单个测试禁用重试,但这样就失去了重试的便利性。

有没有大佬有成熟的解决方案,或者更优雅的处理方式?麻烦指点下,谢谢!

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 08:04:35