关注 霍格沃兹测试学院公众号,回复「资料」, 领取人工智能测试开发技术合集
当你接手一段两个月前写的Playwright测试代码,是不是常常要花上十分钟才能理清它在测什么?或者当页面元素稍作调整,你就得在十几个测试文件中逐个修改选择器?是时候聊聊重构了。
让我们从一个常见的登录测试开始。这是很多人最初写的版本:
test('用户登录', async ({ page }) => {await page.goto('https://app.example.com');await page.fill('#username', 'testuser');await page.fill('#password', 'password123');await page.click('#login-btn');await expect(page.locator('.welcome-msg')).toContainText('欢迎回来');});这段代码能工作,但问题很明显:URL、选择器、测试数据全部硬编码,改一处就要动全身。
把页面抽象成类,这是提升可维护性的第一步:
// pages/LoginPage.jsclassLoginPage{constructor(page) {this.page = page;this.usernameInput = page.locator('#username');this.passwordInput = page.locator('#password');this.loginButton = page.locator('#login-btn');this.welcomeMessage = page.locator('.welcome-msg'); }async navigate() {awaitthis.page.goto('https://app.example.com/login'); }async login(username, password) {awaitthis.usernameInput.fill(username);awaitthis.passwordInput.fill(password);awaitthis.loginButton.click(); }}// 在测试中的使用test('用户登录', async ({ page }) => {const loginPage = new LoginPage(page);await loginPage.navigate();await loginPage.login('testuser', 'password123');await expect(loginPage.welcomeMessage).toContainText('欢迎回来');});那些散落在代码各处的字符串,早晚会给你带来麻烦:
// config/constants.jsexportconst URLs = {LOGIN: process.env.BASE_URL + '/login',DASHBOARD: process.env.BASE_URL + '/dashboard',};exportconst TestUsers = {ADMIN: { username: 'admin_user', password: process.env.ADMIN_PASS },STANDARD: { username: 'standard_user', password: 'test123' },};避免使用硬性的page.waitForTimeout(3000),那是脆弱的根源:
// utils/waitHelpers.jsexportasyncfunctionwaitForNetworkIdle(page, timeout = 10000) {await page.waitForLoadState('networkidle', { timeout });}// 在页面对象中的使用async submitForm() {const responsePromise = this.page.waitForResponse('**/api/submit');awaitthis.submitButton.click();await responsePromise;}那些频繁出现的操作序列,应该被封装起来:
// test-steps/loginSteps.jsexportasyncfunctionloginAsUser(page, userType = 'STANDARD') {const loginPage = new LoginPage(page);const user = TestUsers[userType];await loginPage.navigate();await loginPage.login(user.username, user.password);await expect(loginPage.welcomeMessage).toBeVisible();returnnew DashboardPage(page); // 返回下一个页面对象}当相似的测试用例只是数据不同时:
// test-data/loginData.jsexportconst loginTestData = [ { userType: 'ADMIN', expectedRole: '管理员' }, { userType: 'EDITOR', expectedRole: '编辑' }, { userType: 'VIEWER', expectedRole: '查看者' },];// 测试文件test.describe('不同角色登录', () => {for (const data of loginTestData) { test(`${data.userType}用户登录后显示正确角色`, async ({ page }) => {const dashboard = await loginAsUser(page, data.userType);await expect(dashboard.roleBadge).toContainText(data.expectedRole); }); }});伙伴们,对AI测试、大模型评测、质量保障感兴趣吗?我们建了一个 「Playwright mcp技术学习交流群」,专门用来探讨相关技术、分享资料、互通有无。无论你是正在实践还是好奇探索,都欢迎扫码加入,一起抱团成长!期待与你交流!👇

loginAsAdmin比loginTest1能传递更多信息// 重构前test('购物流程', async ({ page }) => {// ... 长达50行的代码,混合了登录、搜索、加购、结账});// 重构后test('完整购物流程', async ({ page }) => {const dashboard = await loginAsUser(page, 'STANDARD');const searchResults = await dashboard.searchProduct('Playwright实战指南');await searchResults.selectFirstItem();const productPage = new ProductPage(page);await productPage.addToCart();const cart = await productPage.goToCart();await cart.proceedToCheckout();const orderConfirmation = await cart.completePurchase();await expect(orderConfirmation.successMessage).toBeVisible();});好的测试代码不是一次写成的,而是不断重构的结果。刚开始时,让测试能跑起来更重要;但当测试规模扩大后,可维护性就成为团队效率的关键。
记住,你现在的重构不仅是为自己,也是为三个月后接手这段代码的同事——说不定就是你自己。
