本地正常的Cypress模态框元素测试为何在GitHub Actions中失败?
问题
在Vue+Ionic应用中,自定义Cypress命令cy.el(selector)用于查找模态框内元素,本地测试正常,但在GitHub Actions中部分测试失败,报错Timed out retrying after 10000ms: Expected to find element。已确认模态框存在于DOM中,且延长超时时间至60秒仍无效。
自定义命令代码:
Cypress.Commands.add('el', (selector) => { return cy .get('ion-modal:not(.overlay-hidden)', { timeout: 50000 }) .first({ timeout: 50000 }) .find('.ion-page:not(.ion-page-hidden)', { timeout: 50000 }) .first({ timeout: 50000 }) .find(selector, { timeout: 50000 }) .should('be.exist', { timeout: 50000 }) })
GitHub Actions相关配置:
name: cypress on: [push] jobs: cypress-run: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v1 - name: Install dependencies run: npm ci - name: Cypress - pre-analyses test uses: cypress-io/github-action@v5 with: start: npm run test:e2e:chrome browser: chrome screenshotOnRunFailure: true screenshotsFolder: 'cypress/screenshots' wait-on: http://localhost:8080/ config-file: cypress.config.ts spec: "tests/e2e/**/*" env: CYPRESS_baseUrl: http://localhost:8080/ VUE_APP_API_BASE_URL: ${{ secrets.VUE_APP_API_BASE_URL }} VUE_APP_MAP_KEY: ${{ secrets.VUE_APP_MAP_KEY }} VUE_APP_SEGMENT_KEY: ${{ secrets.VUE_APP_SEGMENT_KEY }} VUE_APP_SENTRY_DSN: ${{ secrets.VUE_APP_SENTRY_DSN }} CYPRESS_USER_EMAIL: ${{ secrets.CYPRESS_USER_EMAIL }} CYPRESS_USER_PASSWORD: ${{ secrets.CYPRESS_USER_PASSWORD }}
测试用例核心逻辑:
describe('Test simulation', { includeShadowDom: true }, () => { beforeEach(() => { cy.login() }) it('simulation flow', () => { cy.location('pathname', { timeout: 50000 }).should('include', 'page/home') cy.wrap(scenarios).each((scenario) => { cy.wait(2000) cy.get('[data-cy="home-page"]').should('be.exist', { timeout: 10000 }).first().click({ force: true }) cy.wait(2000) cy.get('[data-cy="new-simulation"]') .should('be.exist', { timeout: 10000 }) .first() .wait(600) .shadow() .find('button') .first() .click({ force: true }) // step 0 cy.wait(4000) cy.el('[data-cy="address"] input') cy.el('[data-cy="address"] input').click().type(scenario.address, { delay: 100 }) cy.get('div.pac-item').first().click({ force: true }) cy.el('[data-cy="submit"]').shadow().find('button').should('not.be.disabled').click({ force: true }) // ...后续多步骤提交逻辑 }) }) })
原因分析
- 模态框上下文未更新:自定义命令中
ion-modal:not(.overlay-hidden)的选择器,无法确保每次都匹配当前活跃的模态框。在GitHub Actions环境下,DOM更新延迟可能导致命令捕获到已隐藏的旧模态框。 - 硬编码等待不可靠:测试中大量使用
cy.wait(xxx),本地环境速度快可覆盖,但GitHub Actions资源有限,等待时间不足/过长都会导致元素查找失败,且硬等待会破坏测试的稳定性。 - 状态判断不精准:
ion-modal:not(.overlay-hidden)和.ion-page:not(.ion-page-hidden)的组合,无法精准识别Ionic模态框切换时的过渡状态,可能匹配到非目标模态框。 - 重试机制未有效利用:自定义命令中重复设置超时,但未通过Cypress的
should断言触发自动重试,无法应对DOM动态变化。
修复方案
1. 优化自定义命令cy.el()
更新命令,确保每次查找当前可见的活跃模态框,并简化超时配置(继承Cypress全局超时):
Cypress.Commands.add('el', (selector) => { return cy .get('ion-modal', { timeout: 50000 }) .filter(':visible') // 用:visible更可靠判断模态框状态 .first() .find('.ion-page') .filter(':visible') .first() .find(selector) .should('exist') // should会自动触发重试,无需重复设置超时 })
2. 替换硬等待为断言式等待
移除所有cy.wait(xxx),改用元素可见/存在的断言等待,让测试自适应环境速度:
// 替换step0中的硬等待 cy.el('[data-cy="address"] input').should('be.visible') // 提交后等待下一个步骤的元素(替代cy.wait(800)) cy.el('[data-cy="submit"]').shadow().find('button').should('not.be.disabled').click() cy.el('[data-cy="property_ownership_type"]').should('be.visible')
3. 确保模态框切换后的上下文重置
每次提交后,等待旧模态框隐藏或新模态框元素出现,避免上下文混乱:
cy.el('[data-cy="submit"]').shadow().find('button').click() // 等待旧模态框隐藏 cy.get('ion-modal').filter(':visible').should('not.have.attr', 'data-cy', 'step-0') // 或直接等待新步骤元素 cy.el('[data-cy="property_ownership_type"]').should('be.visible')
4. 优化GitHub Actions配置
- 升级依赖版本,适配无头环境:
jobs: cypress-run: runs-on: ubuntu-latest env: NODE_OPTIONS: "--max-old-space-size=8192" # 增加Node内存限制 steps: - name: Checkout uses: actions/checkout@v4 # 升级到最新版本 # ...其他步骤 - name: Cypress - pre-analyses test uses: cypress-io/github-action@v6 # 升级到最新版本 with: start: npm run test:e2e:chrome browser: chrome headless: true # 明确启用无头模式 # ...其他配置
- 在
cypress.config.ts中添加Chrome无头模式参数:
export default defineConfig({ e2e: { browser: { chrome: { args: ['--no-sandbox', '--disable-gpu', '--headless=new'] } } } })
5. 测试用例细节优化
- 移除不必要的
{ force: true },强制点击会跳过元素可见性检查,增加测试不稳定风险。 - 循环测试中重置状态,确保每次循环从干净的首页开始:
cy.wrap(scenarios).each((scenario) => { cy.location('pathname').should('include', 'page/home') // ...后续测试步骤 })
内容的提问来源于stack exchange,提问作者Ali
相关产品推荐
相关产品推荐

