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

Playwright测试中setup函数被调用两次的原因及单次调用方案

Playwright + TypeScript 测试中getInsureApplications()被重复调用的问题

在基于TypeScript的Playwright(@playwright/test)端到端测试项目中,测试设置里的getInsureApplications()函数被调用了两次,但预期仅调用一次。该函数用于生成测试数据,初始化insureApplications数组。

代码片段

// test.spec.ts
import { test } from '@playwright/test'

export const insureApplications = getInsureApplications()
function getInsureApplications(): any[] {
  console.log('...getInsureApplications() called...')
  return ['A', 'B']
}

for (let i = 0; i < insureApplications.length; i++) {
  test.only(`Email ${i}`, async () => {
    // Test logic...
  })
}

预期与实际行为

  • 预期行为:getInsureApplications()在测试设置阶段仅调用一次,生成测试数据数组。
  • 实际行为:运行测试时,控制台多次打印日志,显示该函数被调用两次。

环境信息

  • Node版本:v20.11.0
  • Playwright版本:1.41.2

问题原因与解决方案

问题根源在于Playwright的运行机制:它会先加载测试文件收集测试用例(此时执行顶层的getInsureApplications()调用),之后在实际运行测试时,会再次加载测试文件,导致顶层代码被执行第二次。

方法1:用test.beforeAll初始化数据

把数据初始化逻辑放到test.beforeAll钩子中,确保只在测试启动前执行一次:

// test.spec.ts
import { test } from '@playwright/test'

let insureApplications: any[];

test.beforeAll(() => {
  insureApplications = getInsureApplications();
});

function getInsureApplications(): any[] {
  console.log('...getInsureApplications() called...')
  return ['A', 'B']
}

// 提前明确数组长度,或结合其他方式动态生成测试用例
for (let i = 0; i < 2; i++) {
  test.only(`Email ${i}`, async () => {
    console.log(insureApplications[i]);
    // Test logic...
  })
}

方法2:使用参数化测试test.describe.each

如果是要根据数据生成测试用例,推荐用Playwright原生的参数化语法,避免顶层代码重复执行:

// test.spec.ts
import { test } from '@playwright/test'

function getInsureApplications(): any[] {
  console.log('...getInsureApplications() called...')
  return ['A', 'B']
}

// 通过describe.each参数化测试,确保数据只生成一次
test.describe.each(getInsureApplications())('Email test', (app) => {
  test.only(`Email for ${app}`, async () => {
    // Test logic using 'app'...
  })
})

方法3:缓存生成的测试数据(适用于数据生成成本高的场景)

把生成的数据写入临时文件,后续直接读取缓存内容,避免重复生成:

// test.spec.ts
import { test } from '@playwright/test'
import fs from 'fs/promises'
import path from 'path'

async function getInsureApplications(): Promise<any[]> {
  const cacheFile = path.join(__dirname, 'test-data.json');
  try {
    const data = await fs.readFile(cacheFile, 'utf-8');
    return JSON.parse(data);
  } catch (e) {
    console.log('...getInsureApplications() called...')
    const data = ['A', 'B'];
    await fs.writeFile(cacheFile, JSON.stringify(data));
    return data;
  }
}

// 结合beforeAll读取缓存数据
let insureApplications: any[];
test.beforeAll(async () => {
  insureApplications = await getInsureApplications();
});

for (let i = 0; i < insureApplications.length; i++) {
  test.only(`Email ${i}`, async () => {
    // Test logic...
  })
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 05:42:56