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

Playwright forEach循环变量在test块内访问返回undefined问题

Playwright循环生成多用户用例时test块内变量为undefined解决方案

问题复现

使用Playwright开展Web自动化测试时,通过forEach循环遍历经csv-parse解析得到的多用户CSV数据,配置describe块为serial串行模式实现批量用户测试执行,出现以下异常:

  • forEach遍历生成的user变量可在beforeAll钩子中正常访问取值,账号、密码字段可正常填入登录表单完成登录
  • 在test测试用例块内访问user的属性时返回undefined,例如执行await page.locator('#searchByCode').fill(${user.Code});语句时,无法获取到预期的字段值,已确认CSV文件中对应字段存在有效值。

复现代码如下:

import fs from 'fs';
import path from 'path';
import { test, expect, Page } from '@playwright/test';
import { parse } from 'csv-parse/sync';


const users = parse(fs.readFileSync(path.join(__dirname, '../users.csv')), {
    columns: true,
    skip_empty_lines: true
  });

// 全局配置serial模式
test.describe.configure({ mode: 'serial' });

test.use({ viewport: { width: 600, height: 900 } });

// 全局声明共享page变量
let page: Page;

users.forEach(user => {
    console.log(user.Email);

    test.describe(`Login for user ${user.Email}`, () => {
       
        test.beforeAll(async ({ browser }) => {
            page = await browser.newPage();
            await page.goto('https://XXXXXXX.com');
            
            // 此处可正常获取user属性
            await page.fill("#logInEmail",`${user.Email}`);
            await page.fill('#loginInPassword', `${user.Password}`);
        
            await Promise.all([
                page.waitForNavigation(),
                page.locator('#loginSignIn').click()
            ]);
        });
        
        test.afterAll(async () => {
            await page.close();
        });
        
       
        test('SearchBusinessByCode', async ({ browser }, testInfo) => {
            // 此处获取user属性返回undefined
            await page.locator('#searchByCode').fill(`${user.Code}`);
            await page.locator('button', { hasText: 'Search' }).click();
        });
    });
});

根因分析

  1. 配置作用域错误:serial模式被配置在全局作用域,而非每个用户独立的describe块下,所有用户的测试用例、钩子共享同一个执行上下文,全局声明的page变量会被多个beforeAll钩子反复覆盖,执行顺序和变量绑定关系混乱。
  2. 生命周期不匹配:Playwright执行分为「用例收集」和「用例执行」两个完全独立的阶段,forEach循环会在收集阶段全部执行完成,配合全局共享变量的写法,执行阶段test块内拿到的user变量引用已经被循环覆盖,最终返回undefined;而beforeAll在收集阶段就和当前循环的describe块绑定,固化了当前轮次的user引用,因此可以正常取值。

修复方案

按照以下步骤调整代码即可解决问题:

  • 将serial模式配置移动到每个用户对应的describe块内部,保证每个用户的测试套件独立串行,不与其他用户的用例共享执行上下文
  • 删除全局声明的page变量,改为在每个describe块内部声明块级作用域的page变量,避免多用户执行时变量被覆盖
  • 将forEach循环替换为for...of循环遍历用户列表,从语法层面减少闭包变量引用异常的概率
  • 移除test块中未使用的browser夹具参数,避免不必要的上下文创建

修复后的可运行代码:

import fs from 'fs';
import path from 'path';
import { test, expect, Page } from '@playwright/test';
import { parse } from 'csv-parse/sync';

const users = parse(fs.readFileSync(path.join(__dirname, '../users.csv')), {
  columns: true,
  skip_empty_lines: true
});

test.use({ viewport: { width: 600, height: 900 } });

// 使用for...of替代forEach遍历用户列表
for (const user of users) {
  test.describe(`Login for user ${user.Email}`, () => {
    // 将serial配置绑定到当前用户的describe块,仅作用于当前用户的用例
    test.describe.configure({ mode: 'serial' });
    // 在describe块内声明page变量,块级作用域不与其他用户的测试套件共享
    let page: Page;

    test.beforeAll(async ({ browser }) => {
      page = await browser.newPage();
      await page.goto('https://XXXXXXX.com');
      
      await page.fill("#logInEmail", user.Email);
      await page.fill('#loginInPassword', user.Password);
    
      await Promise.all([
        page.waitForNavigation(),
        page.locator('#loginSignIn').click()
      ]);
    });
    
    test.afterAll(async () => {
      await page.close();
    });
    
    test('SearchBusinessByCode', async () => {
      // 此时可正常获取user的所有属性
      await page.locator('#searchByCode').fill(user.Code);
      await page.locator('button', { hasText: 'Search' }).click();
      // 后续补充业务断言即可
    });
  });
}

注意事项

  • 禁止在全局作用域声明多个测试套件共享的page/browser等实例,串行执行时前一个套件的afterAll关闭实例后,后一个套件的用例会拿到已失效的实例引用,或者被后续钩子创建的新实例覆盖,导致操作对象不匹配
  • 动态循环生成测试用例时,所有测试配置、变量声明都要绑定到对应的describe块作用域下,避免全局污染
  • 循环生成用例优先使用for...of语法,可规避绝大多数JS闭包带来的变量引用异常问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 04:48:30