1. 引言:当自动化测试遇上 AI 助手

在 Web 自动化测试领域,Playwright Inspector 是一个强大的可视化录制工具,它允许测试人员通过浏览器操作自动生成可执行的测试脚本。然而,这些录制生成的代码往往是“一次性”的——它们忠实地记录了操作步骤,但缺乏灵活性、可维护性,也难以应对动态变化的页面元素。

随着 AI 编程助手(如 GitHub Copilot、Cursor、通义灵码等)的普及,我们迎来了新的机遇:将 Playwright Inspector 录制的“原始代码”转化为可供 AI 理解、重构和智能复用的高质量“素材”。本文旨在提供一套完整的技术方案,教你如何清洗、结构化录制代码,并利用 AI 将其转化为可维护、可扩展的自动化测试资产。

2. Playwright Inspector 录制代码的典型问题

直接使用 Inspector 录制的代码,通常存在以下痛点,阻碍了 AI 的有效理解和复用:

  1. 硬编码定位器:过度依赖 page.locator('text=Submit') 或冗长的 CSS 选择器,一旦页面文本或结构微调,脚本立即失效。
  2. 缺乏抽象与模块化:所有操作线性堆叠在一个长函数中,没有提取公共操作(如登录、导航)为可复用的函数或 Page Object。
  3. 脆弱的等待与断言:依赖固定的 page.waitForTimeout(3000),而非更健壮的等待策略(如 waitForSelectorwaitForLoadState)。
  4. 数据与逻辑耦合:测试数据(如用户名、搜索关键词)直接写在操作中,难以进行数据驱动测试。
  5. 注释与上下文缺失:生成的代码缺少说明“为什么这么做”的注释,AI 难以推断业务意图。

3. 预处理:将录制代码转化为 AI 友好的“原料”

在将代码喂给 AI 之前,我们需要进行初步清洗和标注。

3.1 提取核心操作流

删除所有 page.waitForTimeout 语句,并用 // TODO: 替换为更健壮的等待策略 注释标记其位置。这向 AI 发出了明确的优化指令。

录制原始代码示例:

await page.goto('https://example.com/login');
await page.waitForTimeout(2000); // 脆弱等待
await page.locator('input[name="username"]').fill('testuser');
await page.locator('input[name="password"]').fill('password123');
await page.locator('button:has-text("Sign In")').click();
await page.waitForTimeout(5000); // 脆弱等待

预处理后:

await page.goto('https://example.com/login');
// TODO: 替换为更健壮的等待策略,例如等待登录表单可见
await page.locator('input[name="username"]').fill('testuser');
await page.locator('input[name="password"]').fill('password123');
await page.locator('button:has-text("Sign In")').click();
// TODO: 替换为更健壮的等待策略,例如等待页面导航完成

3.2 添加语义化注释

在关键业务步骤前后添加注释,说明意图而非操作

// 步骤1: 导航至登录页面
await page.goto('https://example.com/login');

// 步骤2: 填写用户凭证
await page.locator('input[name="username"]').fill('testuser');
await page.locator('input[name="password"]').fill('password123');

// 步骤3: 提交登录表单
await page.locator('button:has-text("Sign In")').click();

4. 核心策略:指导 AI 重构与复用录制代码

准备好“原料”后,你可以向 AI 助手提出以下类型的指令,进行智能化重构。

4.1 指令类型一:转换为 Page Object 模式

你的提示词:

“将下面这段 Playwright 录制代码重构为 Page Object 模式。请创建一个 LoginPage 类,包含所有定位器和操作方法(如 gotofillUsernamesubmit)。将测试步骤提取到一个单独的测试函数中。”

AI 可能生成的结果:

// LoginPage.js
class LoginPage {
  constructor(page) {
    this.page = page;
    this.usernameInput = page.locator('input[name="username"]');
    this.passwordInput = page.locator('input[name="password"]');
    this.submitButton = page.locator('button:has-text("Sign In")');
  }

  async goto() {
    await this.page.goto('https://example.com/login');
  }

  async login(username, password) {
    await this.usernameInput.fill(username);
    await this.passwordInput.fill(password);
    await this.submitButton.click();
  }
}

// test.spec.js
const { test, expect } = require('@playwright/test');
test('用户登录流程', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.goto();
  await loginPage.login('testuser', 'password123');
  // 添加断言...
});

4.2 指令类型二:增强定位器与等待策略

你的提示词:

“优化下面代码中的定位器和等待策略。将文本定位器改为更具弹性的属性定位器(如 data-testid)。将所有 waitForTimeout 替换为 Playwright 推荐的显式等待(如 waitForSelector, waitForLoadState)。”

AI 可能生成的结果:

// 优化前:await page.locator('button:has-text("Submit")').click();
// 优化后:假设按钮有 data-testid 属性
await page.locator('[data-testid="submit-button"]').click();

// 优化前:await page.waitForTimeout(5000);
// 优化后:等待某个成功元素出现
await page.waitForSelector('.welcome-message', { state: 'visible' });
// 或者等待网络请求完成
await page.waitForLoadState('networkidle');

4.3 指令类型三:实现数据驱动测试

你的提示词:

“将下面的登录脚本改造成数据驱动测试。从外部 JSON 文件或数组读取多组用户名和密码进行测试,并为每次循环添加清晰的日志输出。”

AI 可能生成的结果:

const testData = [
  { username: 'standard_user', password: 'secret_sauce', expected: 'success' },
  { username: 'locked_user', password: 'secret_sauce', expected: 'error' },
];

for (const data of testData) {
  test(`登录测试 - ${data.username}`, async ({ page }) => {
    console.log(`正在测试用户: ${data.username}`);
    const loginPage = new LoginPage(page);
    await loginPage.goto();
    await loginPage.login(data.username, data.password);

    if (data.expected === 'success') {
      await expect(page).toHaveURL(/dashboard/);
    } else {
      await expect(page.locator('.error-message')).toBeVisible();
    }
  });
}

5. 高级技巧:构建可复用的 AI 提示模板

将最佳实践固化为提示模板,实现“一次设计,多次复用”。

5.1 定位器优化模板

你是一个 Playwright 测试专家。请优化以下代码片段中的定位器:
1. 优先使用 `data-testid`、`role` 或 `aria-label` 等语义化属性。
2. 其次考虑稳定的 CSS 选择器(如 `[name="..."]`)。
3. 尽量避免使用包含动态文本(如 `:has-text()`)的定位器。
4. 为每个优化后的定位器添加一行注释,说明优化原因。

代码片段:

[粘贴你的录制代码]

5.2 完整流程重构模板

请基于以下 Playwright Inspector 录制代码,执行以下重构任务:
1. **提取 Page Object**:识别出独立的页面或组件,为其创建类。
2. **封装业务操作**:将连续的操作步骤封装为语义化的方法(如 `loginAsAdmin`)。
3. **替换脆弱等待**:移除所有 `waitForTimeout`,改用显式等待。
4. **添加关键断言**:在业务流程的关键节点(如表单提交后、页面跳转后)插入合理的断言。
5. **输出最终代码**:包含重构后的 Page Object 类和一段使用它的测试用例。

原始录制代码:

[粘贴你的完整录制代码]

6. 实践案例:从录制到 AI 增强的完整工作流

场景:录制一个在电商网站搜索商品并加入购物车的流程。

  1. 录制原始代码 (Playwright Inspector 生成):

    await page.goto('https://shop.example.com');
    await page.locator('#search-box').fill('laptop');
    await page.locator('button.search-button').click();
    await page.waitForTimeout(3000);
    await page.locator('.product-item:has-text("Gaming Laptop") >> button.add-to-cart').click();
    await page.waitForTimeout(2000);
    
  2. 人工预处理:添加注释,标记待优化点。

  3. 使用 AI 提示模板(5.2):将代码和提示词提交给 AI 助手。

  4. 获得 AI 重构代码

    // ProductSearchPage.js
    class ProductSearchPage {
      constructor(page) {
        this.page = page;
        this.searchInput = page.locator('#search-box');
        this.searchButton = page.locator('button.search-button');
      }
      async searchProduct(keyword) {
        await this.searchInput.fill(keyword);
        await this.searchButton.click();
        await this.page.waitForSelector('.product-list', { state: 'visible' });
      }
    }
    
    // ProductListingPage.js
    class ProductListingPage {
      constructor(page) {
        this.page = page;
      }
      async addProductToCart(productName) {
        const productItem = this.page.locator(`.product-item:has-text("${productName}")`);
        const addButton = productItem.locator('button.add-to-cart');
        await addButton.click();
        await this.page.waitForSelector('.cart-notification', { state: 'visible' });
      }
    }
    
    // test.spec.js
    test('搜索并添加商品到购物车', async ({ page }) => {
      const searchPage = new ProductSearchPage(page);
      const listingPage = new ProductListingPage(page);
    
      await page.goto('https://shop.example.com');
      await searchPage.searchProduct('laptop');
      await listingPage.addProductToCart('Gaming Laptop');
      await expect(page.locator('.cart-count')).toHaveText('1');
    });
    

7. 总结与最佳实践

  1. 录制是起点,而非终点:将 Playwright Inspector 视为“操作记录仪”,其产出需要经过加工才能成为资产。
  2. 预处理是关键:花几分钟添加注释和标记,能极大提升 AI 的理解和输出质量。
  3. 提供明确指令:AI 需要方向。使用“重构为 Page Object”、“优化定位器”、“实现数据驱动”等具体动词。
  4. 迭代与验证:AI 生成的代码需要人工审查和运行验证。将其集成到你的 CI/CD 流水线中,确保功能正确。
  5. 积累提示库:将成功的 AI 交互保存为模板,逐步形成属于你团队的“AI 增强测试开发工作流”。

通过结合 Playwright Inspector 的便捷录制与 AI 助手的智能重构能力,你可以将原本繁琐、脆弱的脚本维护工作,转变为高效、可持续的自动化测试资产建设过程。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐