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

Playwright测试Angular应用:保存按钮触发无效表单及page未定义错误

问题解决:Playwright测试Angular应用时保存按钮点击报错及表单无效问题

一、修复ReferenceError: page is not defined错误

检查代码可见一行注释代码:

// await page.getByRole('button').click({ force: true });

这里直接使用了未定义的page变量,而非类实例的this.page。若后续启用该行代码会触发ReferenceError。确保所有页面操作代码均使用this.page,避免裸用未绑定的page变量。

二、解决“无效表单”提示问题

Angular表单依赖变更检测和用户交互事件更新控件状态,直接调用fill或selectOption可能无法触发Angular的表单验证逻辑,导致表单判定字段未填写。以下是针对性修复方案:

1. 模拟真实输入触发表单变更

替换部分fill为type,或在填充后触发input/blur事件,确保Angular检测到控件值变化:

// 用type模拟真实输入,触发Angular变更检测
await this.page.type('(//input[@id="organizationName"])[1]', customerData.customerName);
// 触发blur事件,触发字段验证
await this.page.dispatchEvent('(//input[@id="organizationName"])[1]', 'blur');

2. 下拉选择后触发change事件

Angular下拉控件依赖change事件更新表单状态,选择选项后手动触发:

await this.page.selectOption("(//select[@id='opucode dropdown-toggle'])[2]", customerData.customerType);
await this.page.dispatchEvent("(//select[@id='opucode dropdown-toggle'])[2]", 'change');

3. 正确处理日期选择器

直接填充日期输入框可能无法触发Angular日期控件的验证逻辑,建议通过点击日期面板选择日期:

// 打开日期选择面板
await this.page.click('#ej2-datepicker_0 input');
// 选择对应日期(需匹配日期面板的data-date属性格式)
await this.page.click(`.e-calendar td[data-date="${customerData.InductionDate}"]`);
// 等待输入框值更新完成
await this.page.waitForFunction(
  (selector, date) => document.querySelector(selector).value === date,
  "#ej2-datepicker_0_input",
  customerData.InductionDate
);

4. 移除硬编码等待,改用状态等待

替换waitForTimeout为等待表单验证完成或元素状态变化,提升稳定性:

// 等待表单验证通过(等待所有无效状态元素消失)
await this.page.waitForSelector('.ng-invalid', { state: 'detached', timeout: 10000 });

5. 点击保存前检查表单有效性

通过执行页面内代码检查表单状态,确认无误后再点击保存:

const isFormValid = await this.page.evaluate(() => {
  // 替换为你的表单选择器
  const form = document.querySelector('form');
  return form?.checkValidity();
});

if (isFormValid) {
  await this.page.getByRole('button', { name: 'Save' }).first().click();
} else {
  this.logger.error('表单验证未通过');
  // 打印所有无效字段便于调试
  const invalidFields = await this.page.evaluate(() => {
    return Array.from(document.querySelectorAll('.ng-invalid')).map(el => el.id || el.name);
  });
  this.logger.error(`无效字段:${invalidFields.join(', ')}`);
}

修改后的完整代码示例

async fillCustomerForm(customerData) {
  this.logger.info('填充客户详情');

  try {
    // 等待文件输入元素可见
    await this.page.waitForSelector("#fileInput");

    // 设置文件输入并等待上传状态
    await this.page.setInputFiles("#fileInput", "C:\\Users\\sathishd\\Downloads\\EtihadAirways.jpg");
    await this.page.waitForSelector("#fileInput[value]", { timeout: 5000 });

    // 填写组织名称并触发验证
    await this.page.type('(//input[@id="organizationName"])[1]', customerData.customerName);
    await this.page.dispatchEvent('(//input[@id="organizationName"])[1]', 'blur');

    // 填写短名称
    const shortNameInput = this.page.getByLabel('Short Name');
    await shortNameInput.click();
    await shortNameInput.fill('SPR');
    await shortNameInput.dispatchEvent('blur');

    // 填写客户ECode
    await this.page.type("(//input[@id='shortName'])[2]", customerData.customerECode);
    await this.page.dispatchEvent("(//input[@id='shortName'])[2]", 'blur');

    // 选择客户类型并触发变更
    await this.page.selectOption("(//select[@id='opucode dropdown-toggle'])[2]", customerData.customerType);
    await this.page.dispatchEvent("(//select[@id='opucode dropdown-toggle'])[2]", 'change');

    // 填写ICAO和IATA
    await this.page.type("(//input[@type='text'])[5]", customerData.ICAO);
    await this.page.dispatchEvent("(//input[@type='text'])[5]", 'blur');
    await this.page.type("(//input[@type='text'])[6]", customerData.IATA);
    await this.page.dispatchEvent("(//input[@type='text'])[6]", 'blur');

    // 处理日期选择器
    await this.page.click('#ej2-datepicker_0 input');
    await this.page.click(`.e-calendar td[data-date="${customerData.InductionDate}"]`);
    await this.page.waitForFunction(
      (selector, date) => document.querySelector(selector).value === date,
      "#ej2-datepicker_0_input",
      customerData.InductionDate
    );

    // 选择自定义分类并触发变更
    await this.page.selectOption("(//select[@id='opucode dropdown-toggle'])[3]", customerData.userdefinedCategory);
    await this.page.dispatchEvent("(//select[@id='opucode dropdown-toggle'])[3]", 'change');

    // 等待表单验证通过
    await this.page.waitForSelector('.ng-invalid', { state: 'detached', timeout: 10000 });

    try {
      await this.page.waitForSelector('button:has-text("Save")', { visible: true, timeout: 5000 });
      await this.page.getByRole('button', { name: 'Save' }).first().click();
      this.logger.info('保存按钮点击成功');
    } catch (error) {
      this.logger.error(`点击保存按钮出错: ${error}`);
      
      try {
        const screenshotPath = 'C:/sathish/AiDocx_Automation_Plywrt02/screenshot/save_button_error.png';
        await this.page.screenshot({ path: screenshotPath });
        this.logger.info(`已捕获截图: ${screenshotPath}`);
      } catch (screenshotError) {
        this.logger.error(`捕获截图出错: ${screenshotError}`);
      }
    }
  } catch (error) {
    this.logger.error(`填充表单出错: ${error}`);
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 13:08:12