如何配置Playwright仅对测试所需用户执行多角色认证?
如何让Playwright仅认证测试所需的用户?
问题场景
我按照Playwright多角色认证的方案实现了多用户认证、状态保存与复用,但遇到一个效率问题:当测试用户数量较多(比如100个)时,即便只运行某部分测试(如仅标注Smoke的冒烟测试),Playwright仍会先对所有用户完成认证,再执行目标测试。而冒烟测试只用到其中个别用户,全量认证完全没必要。
现有认证逻辑代码:
import { test as setup, expect } from '@playwright/test'; import * as data from '@/source/testData/data.json'; const keys = Object.keys(data.users); keys.forEach((key) => { const credentials = data.users[key as keyof typeof data.users]; const userFile = `playwright/.auth/${credentials.username}.json`; setup(`Authenticate user ${credentials.username}`, async ({ page }) => { await page.goto('https://somerandomwebsite.com'); await page.getByLabel('Username').fill(credentials.username); await page.getByLabel('Password').fill(credentials.password); await page.click('text=Submit'); await expect(page.getByRole('button', { name: 'Profile' })).toBeVisible({ timeout: 30000 }); await page.context().storageState({ path: userFile }); }); });
测试中使用认证状态的方式:
test.describe('Smoke tests', () => { test.use({ storageState: 'playwright/.auth/customer1.json' }); test('add item to cart', async ({ page }) => { // 测试逻辑 }); });
解决方案
方法一:通过环境变量指定目标用户
运行测试时通过环境变量传递需要认证的用户名,让setup脚本仅处理这些用户,同时保留兼容性(未指定时默认全量认证)。
修改后的setup代码:
import { test as setup, expect } from '@playwright/test'; import * as data from '@/source/testData/data.json'; import fs from 'fs'; // 从环境变量获取需要认证的用户(支持逗号分隔多个用户) const targetUsers = process.env.TARGET_USERS ? process.env.TARGET_USERS.split(',') : []; // 确定要认证的用户列表:指定了用户则只处理指定的,否则处理所有 const usersToAuth = targetUsers.length > 0 ? targetUsers.map(username => data.users[username as keyof typeof data.users]) : Object.values(data.users); // 过滤掉不存在的用户,避免报错 const validUsers = usersToAuth.filter(user => !!user); if (validUsers.length !== targetUsers.length) { const missingUsers = targetUsers.filter(u => !data.users[u as keyof typeof data.users]); console.warn(`Warning: Users ${missingUsers.join(',')} not found in data.json`); } validUsers.forEach((credentials) => { const userFile = `playwright/.auth/${credentials.username}.json`; setup(`Authenticate user ${credentials.username}`, async ({ page }) => { // 先检查状态文件是否存在,存在则跳过认证 if (fs.existsSync(userFile)) { console.log(`Skipping authentication for ${credentials.username} (state file exists)`); return; } await page.goto('https://somerandomwebsite.com'); await page.getByLabel('Username').fill(credentials.username); await page.getByLabel('Password').fill(credentials.password); await page.click('text=Submit'); await expect(page.getByRole('button', { name: 'Profile' })).toBeVisible({ timeout: 30000 }); await page.context().storageState({ path: userFile }); }); });
运行冒烟测试时,只需指定目标用户:
TARGET_USERS=customer1 npx playwright test --grep "Smoke"
方法二:自动检测测试中用到的用户
编写脚本扫描测试文件,提取所有用到的storageState路径,解析出对应的用户名,让setup仅认证这些用户,无需手动指定。
修改后的setup代码(包含扫描逻辑):
import { test as setup, expect } from '@playwright/test'; import * as data from '@/source/testData/data.json'; import fs from 'fs'; import path from 'path'; // 扫描测试目录,提取所有用到的用户名 function getUsedUsers(testDir: string): string[] { const usedUsernames = new Set<string>(); // 匹配storageState路径的正则 const statePathRegex = /storageState: ['"]playwright\/\.auth\/(.*)\.json['"]/g; // 递归扫描目录 function scanDirectory(dir: string) { const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { scanDirectory(fullPath); } else if (entry.isFile() && (fullPath.endsWith('.spec.ts') || fullPath.endsWith('.spec.js'))) { const content = fs.readFileSync(fullPath, 'utf8'); let match; while ((match = statePathRegex.exec(content)) !== null) { usedUsernames.add(match[1]); } } } } scanDirectory(testDir); return Array.from(usedUsernames); } // 获取当前测试用到的用户 const usedUsers = getUsedUsers('./tests'); // 替换为你的测试目录路径 const usersToAuth = usedUsers.map(username => data.users[username as keyof typeof data.users]).filter(user => !!user); usersToAuth.forEach((credentials) => { const userFile = `playwright/.auth/${credentials.username}.json`; setup(`Authenticate user ${credentials.username}`, async ({ page }) => { if (fs.existsSync(userFile)) { console.log(`Skipping authentication for ${credentials.username} (state file exists)`); return; } await page.goto('https://somerandomwebsite.com'); await page.getByLabel('Username').fill(credentials.username); await page.getByLabel('Password').fill(credentials.password); await page.click('text=Submit'); await expect(page.getByRole('button', { name: 'Profile' })).toBeVisible({ timeout: 30000 }); await page.context().storageState({ path: userFile }); }); });
这种方式会自动识别当前要运行的测试中用到的所有用户,无需额外参数,适合不想手动管理用户列表的场景。
方法三:利用Playwright Fixture的依赖机制
将每个用户的认证逻辑封装为fixture,测试中明确依赖对应的fixture,Playwright会自动仅运行被依赖的fixture(即仅认证测试用到的用户)。
首先创建fixture文件:
// fixtures.ts import { test as base } from '@playwright/test'; import * as data from '@/source/testData/data.json'; import fs from 'fs'; // 定义用户fixture类型 type UserFixtures = { [K in keyof typeof data.users]: void }; // 动态生成所有用户的fixture const userFixtures: Partial<UserFixtures> = {}; Object.entries(data.users).forEach(([userKey, credentials]) => { userFixtures[userKey as keyof UserFixtures] = [async ({ page }, use) => { const userFile = `playwright/.auth/${credentials.username}.json`; // 状态文件不存在时才执行认证 if (!fs.existsSync(userFile)) { await page.goto('https://somerandomwebsite.com'); await page.getByLabel('Username').fill(credentials.username); await page.getByLabel('Password').fill(credentials.password); await page.click('text=Submit'); await expect(page.getByRole('button', { name: 'Profile' })).toBeVisible({ timeout: 30000 }); await page.context().storageState({ path: userFile }); } await use(); }, { scope: 'worker' }]; // 设置为worker级别,每个worker只认证一次 }); // 扩展基础test,加入用户fixtures export const test = base.extend<UserFixtures>(userFixtures);
然后在测试中使用:
import { test } from './fixtures'; test.describe('Smoke tests', () => { test.use({ storageState: 'playwright/.auth/customer1.json' }); // 依赖customer1的fixture,触发该用户的认证(如果状态文件不存在) test('add item to cart', async ({ page, customer1 }) => { // 测试逻辑 }); });
这种方式完全贴合Playwright的测试范式,且能自动管理认证逻辑的执行时机,仅为被测试用到的用户执行认证。
内容的提问来源于stack exchange,提问作者autoMagic
相关产品推荐
相关产品推荐

