霍格沃兹测试开发学社

《Python测试开发进阶训练营》(随到随学!)
2023年第2期《Python全栈开发与自动化测试班》(开班在即)
报名联系weixin/qq:2314507862

Playwright数据驱动测试:从Excel和JSON读取测试数据

关注 霍格沃兹测试学院公众号,回复「资料」, 领取人工智能测试开发技术合集

如果你还在为每个测试用例硬编码数据而头疼,或者每次数据变更都要翻遍几十个测试文件——是时候了解数据驱动测试了。今天,我们聊聊如何用 Playwright 优雅地从 Excel 和 JSON 文件中读取测试数据,让你的测试代码真正实现“一次编写,到处运行”。

为什么需要数据驱动测试?
先看个反例。假设我们要测试一个登录功能,传统写法可能是:

test('用户登录测试', async ({ page }) => {
await page.fill('#username', 'zhangsan');
await page.fill('#password', '123456');
await page.click('#login-btn');
// 断言...
});

test('管理员登录测试', async ({ page }) => {
await page.fill('#username', 'admin');
await page.fill('#password', 'admin@123');
await page.click('#login-btn');
// 断言...
});
发现问题了吗?每增加一个测试账户,就要复制粘贴一整段代码。当密码策略变化时,你得修改所有相关测试文件。这种维护成本,你懂的。

而数据驱动测试的思想很简单:分离测试逻辑与测试数据。我们的目标是把上面的代码改造成这样:

// 测试逻辑只有一份
test('登录功能测试', async ({ page }) => {
const testData = getTestData(); // 从外部文件读取
for (const data of testData) {
await performLogin(page, data);
// 断言...
}
});
接下来,我们看看具体怎么实现。

实战一:从 Excel 读取测试数据
Excel 可能是产品经理和业务人员最喜欢的数据格式。如果你的测试数据需要经常让非技术人员维护,Excel 是个不错的选择。

第一步:准备测试数据
创建一个 testdata.xlsx 文件,内容如下:

image

notexists
123456
提示用户不存在
保存到项目目录的 data/ 文件夹下。

第二步:安装必要的包
Playwright 本身不处理 Excel,我们需要借助社区包:

npm install xlsx

或者

yarn add xlsx
第三步:实现 Excel 读取工具
创建 utils/excelReader.js:

const XLSX = require('xlsx');
const path = require('path');

class ExcelReader {
/**

  • 读取Excel文件

  • @param {string} filePath - Excel文件路径

  • @param {string} sheetName - 工作表名称(可选,默认为第一个)

  • @returns {Array} 测试数据数组
    */
    static readTestData(filePath, sheetName = null) {
    try {
    // 解析文件路径
    const absolutePath = path.resolve(__dirname, '..', filePath);

    // 读取工作簿
    const workbook = XLSX.readFile(absolutePath);

    // 获取工作表
    const sheet = sheetName
    ? workbook.Sheets[sheetName]
    : workbook.Sheets[workbook.SheetNames[0]];

    if (!sheet) {
    thrownewError(工作表 ${sheetName || '第一个'} 不存在);
    }

    // 转换为JSON
    const jsonData = XLSX.utils.sheet_to_json(sheet);

    console.log(成功从 ${filePath} 读取 ${jsonData.length} 条测试数据);
    return jsonData;

} catch (error) {
  console.error('读取Excel文件失败:', error.message);
  throw error;
}

}

/**

  • 按测试场景筛选数据
  • @param {string} filePath - Excel文件路径
  • @param {string} scenario - 测试场景名称
    */
    static getDataByScenario(filePath, scenario) {
    const allData = this.readTestData(filePath);
    return allData.filter(row => row['测试场景'] === scenario);
    }
    }

module.exports = ExcelReader;
第四步:在测试中使用
现在,让我们重写登录测试:

const { test, expect } = require('@playwright/test');
const ExcelReader = require('../utils/excelReader');

test.describe('登录功能数据驱动测试', () => {
let testData;

test.beforeAll(() => {
// 一次性读取所有测试数据
testData = ExcelReader.readTestData('./data/testdata.xlsx');
console.log(本次执行将运行 ${testData.length} 个测试用例);
});

test('数据驱动登录测试', async ({ page }) => {
// 遍历每条测试数据
for (const data of testData) {
// 使用测试场景作为子测试名称
await test.step(测试场景: ${data['测试场景']}, async () => {
console.log(执行用例: ${data['测试场景']}, 用户名: ${data.username});

    // 导航到登录页
    await page.goto('https://your-app.com/login');
    
    // 使用数据填充表单
    await page.fill('#username', data.username);
    await page.fill('#password', data.password);
    await page.click('#login-btn');
    
    // 根据预期结果进行断言
    if (data.expected_result === '登录成功') {
      await expect(page).toHaveURL('https://your-app.com/dashboard');
      await expect(page.locator('.welcome-message')).toContainText(data.username);
    } elseif (data.expected_result.includes('提示')) {
      await expect(page.locator('.error-message')).toBeVisible();
      await expect(page.locator('.error-message')).toContainText(data.expected_result);
    }
    
    // 如果是管理员登录的特殊断言
    if (data.username === 'admin' && data.expected_result === '跳转管理后台') {
      await expect(page).toHaveURL('https://your-app.com/admin');
    }
  });
}

});
});
Excel 方案的优缺点
优点:

非技术人员也能轻松维护
支持复杂的数据格式(合并单元格、公式等)
可以用 Excel 的数据验证功能保证数据质量
缺点:

需要额外依赖
版本控制时二进制文件对比困难
读取速度相对较慢
Playwright mcp技术学习交流群
伙伴们,对AI测试、大模型评测、质量保障感兴趣吗?我们建了一个 「Playwright mcp技术学习交流群」,专门用来探讨相关技术、分享资料、互通有无。无论你是正在实践还是好奇探索,都欢迎扫码加入,一起抱团成长!期待与你交流!👇

image

实战二:从 JSON 读取测试数据
如果你团队里都是开发人员,或者你更喜欢纯文本的版本控制,JSON 可能是更好的选择。

第一步:创建 JSON 数据文件
创建 data/loginTestData.json:

{
"login_cases": [
{
"test_scenario": "普通用户登录",
"username": "zhangsan",
"password": "123456",
"expected_result": "登录成功",
"permissions": ["view", "edit"],
"metadata": {
"priority": "P0",
"tags": ["smoke", "regression"]
}
},
{
"test_scenario": "管理员登录",
"username": "admin",
"password": "admin@123",
"expected_result": "跳转管理后台",
"permissions": ["view", "edit", "delete", "admin"],
"metadata": {
"priority": "P1",
"tags": ["regression"]
}
},
{
"test_scenario": "密码错误",
"username": "lisi",
"password": "wrong_pwd",
"expected_result": "提示密码错误",
"metadata": {
"priority": "P2",
"tags": ["negative"]
}
}
],
"environment_config": {
"base_url": "https://your-app.com",
"timeout": 30000
}
}
第二步:创建 JSON 读取工具
创建 utils/jsonReader.js:

const fs = require('fs').promises;
const path = require('path');

class JsonReader {
/**

  • 读取JSON测试数据

  • @param {string} filePath - JSON文件路径

  • @returns {Promise} 解析后的JSON对象
    */
    staticasync readTestData(filePath) {
    try {
    const absolutePath = path.resolve(__dirname, '..', filePath);
    const fileContent = await fs.readFile(absolutePath, 'utf-8');
    const jsonData = JSON.parse(fileContent);

    console.log(从 ${filePath} 加载了 ${jsonData.login_cases?.length || 0} 个登录测试用例);
    return jsonData;

    } catch (error) {
      if (error.code === 'ENOENT') {
        console.error(`文件不存在: ${filePath}`);
      } elseif (error instanceofSyntaxError) {
        console.error(`JSON格式错误: ${error.message}`);
      }
      throw error;
    }
    

    }

    /**

    • 根据标签过滤测试用例
    • @param {string} filePath - JSON文件路径
    • @param {string} tag - 标签名称
      */
      staticasync getCasesByTag(filePath, tag) {
      const data = awaitthis.readTestData(filePath);
      if (!data.login_cases) return [];
    return data.login_cases.filter(testCase =>
      testCase.metadata?.tags?.includes(tag)
    );
    

    }

    /**

    • 获取环境配置
    • @param {string} filePath - JSON文件路径
      */
      staticasync getConfig(filePath) {
      const data = awaitthis.readTestData(filePath);
      return data.environment_config || {};
      }
      }

    module.exports = JsonReader;
    第三步:在测试中使用 JSON 数据
    const { test, expect } = require('@playwright/test');
    const JsonReader = require('../utils/jsonReader');

    test.describe('JSON数据驱动登录测试', () => {
    let testCases;
    let config;

    test.beforeAll(async () => {
    // 异步读取数据和配置
    const testData = await JsonReader.readTestData('./data/loginTestData.json');
    testCases = testData.login_cases;
    config = testData.environment_config;

    console.log(`基础URL: ${config.base_url}, 超时: ${config.timeout}ms`);
    

    });

    // 只运行冒烟测试用例
    test('冒烟测试:登录功能', async ({ page }) => {
    const smokeCases = await JsonReader.getCasesByTag('./data/loginTestData.json', 'smoke');

    for (const testCase of smokeCases) {
      await test.step(`冒烟测试 - ${testCase.test_scenario}`, async () => {
        await page.goto(`${config.base_url}/login`);
        
        await page.fill('#username', testCase.username);
        await page.fill('#password', testCase.password);
        await page.click('#login-btn');
        
        // 使用环境配置中的超时时间
        await page.waitForTimeout(config.timeout);
        
        // 这里可以根据你的实际需求添加断言
        await expect(page).not.toHaveURL(`${config.base_url}/login`);
      });
    }
    

    });

    // 运行所有测试用例,带详细断言
    test('完整登录测试套件', async ({ page }) => {
    for (const testCase of testCases) {
    await test.step(testCase.test_scenario, async () => {
    // 这里可以添加更复杂的测试逻辑
    console.log(测试用户权限: ${testCase.permissions?.join(', ') || '无'});

        // 实际测试步骤...
        await page.goto(`${config.base_url}/login`);
        // ... 更多测试代码
      });
    }
    

    });
    });
    更高级的用法:配合 Playwright Fixtures
    如果你想让测试数据在整个项目范围内可用,可以创建自定义 fixture:

    // fixtures/testDataFixture.js
    const { test: baseTest } = require('@playwright/test');
    const JsonReader = require('../utils/jsonReader');

    const test = baseTest.extend({
    testData: async ({}, use) => {
    // 这里可以读取任何你需要的数据文件
    const data = await JsonReader.readTestData('./data/loginTestData.json');
    await use(data);
    },

    smokeCases: async ({}, use) => {
    const cases = await JsonReader.getCasesByTag('./data/loginTestData.json', 'smoke');
    await use(cases);
    }
    });

    module.exports = { test };
    然后在测试中直接使用:

    const { test } = require('../fixtures/testDataFixture');

    test('使用fixture的测试', async ({ page, testData, smokeCases }) => {
    console.log(总用例数: ${testData.login_cases.length});
    console.log(冒烟用例数: ${smokeCases.length});

    // ... 测试逻辑
    });
    JSON 方案的优缺点
    优点:

    纯文本,版本控制友好
    无需额外依赖(Node.js 原生支持)
    结构灵活,支持嵌套数据
    读取速度快
    缺点:

    非技术人员编辑困难
    没有 Excel 的数据验证功能
    容易因格式错误导致解析失败
    如何选择?
    根据我的经验,选择建议如下:

    选 Excel 如果:

    测试数据经常由产品/业务人员提供
    数据需要复杂的计算或格式
    已经有现成的 Excel 数据源
    选 JSON 如果:

    团队都是技术人员
    需要频繁进行版本控制和代码审查
    数据需要嵌套结构或复杂数据类型
    混合使用(进阶方案):

    // 用Excel作为数据源,但转换为JSON格式存储
    const excelData = ExcelReader.readTestData('./data/raw/source.xlsx');
    const jsonData = JSON.stringify(excelData, null, 2);
    await fs.writeFile('./data/processed/testData.json', jsonData);
    // 然后在测试中使用JSON版本
    避坑指南
    路径问题:始终使用 path.resolve 处理文件路径,避免不同操作系统下的问题。

    数据验证:在读取数据后,添加验证逻辑:

    function validateTestData(data) {
    const requiredFields = ['username', 'password', 'expected_result'];
    data.forEach((row, index) => {
    requiredFields.forEach(field => {
    if (!row[field]) {
    throw new Error(第${index + 1}行缺少必要字段: ${field});
    }
    });
    });
    }
    性能优化:对于大量测试数据,考虑分批执行:

    // 分批执行,每批5个用例
    const batchSize = 5;
    for (let i = 0; i < testData.length; i += batchSize) {
    const batch = testData.slice(i, i + batchSize);
    // 执行批次...
    }
    错误处理:添加详细的错误日志,方便调试:

    try {
    await performTest(data);
    } catch (error) {
    console.error(用例失败: ${data.test_scenario}, {
    username: data.username,
    error: error.message
    });
    // 可以继续执行下一个用例,而不是整个测试失败
    }
    总结
    数据驱动测试不是银弹,但它是提升测试代码可维护性的重要手段。通过将测试数据从代码中分离出来:

    你的测试逻辑会更简洁,不再充斥着各种硬编码值
    测试数据维护成本大大降低,非技术人员也能参与
    更容易实现测试用例的复用和组合
    测试报告更清晰,每个数据行都可以作为一个独立的测试步骤
    无论是选择 Excel 还是 JSON,关键是开始实践。从最简单的登录测试开始,逐步将你的测试套件改造为数据驱动模式。你会发现,当产品经理直接给你一个 Excel 文件说“把这些测试用例都跑一下”时,你的内心会是多么的平静。

    最后提醒一点:数据驱动测试虽然好,但不要过度设计。简单的、不会频繁变化的测试数据,直接写在代码里也许更合适。找到适合你项目的平衡点,这才是真正的工程智慧。

    推荐学习

    欢迎加入霍格沃兹测试开发学社 · 人工智能测试开发训练营(VIP)支持 线上线下

    扫码进群了解课程详情
    领取 VIP 专属优惠与学习资料

    image

posted @ 2026-01-11 12:46  霍格沃兹测试开发学社  阅读(17)  评论(0)    收藏  举报