了解 Cypress 测试框架,给已有项目加上 Cypress 测试

什么是 Cypress

巴拉巴拉,不如看图,执行命令之后,就能自己这样了,

好,如果觉得有点意思,就往下看,然后刚自己项目也来个,一方面能装,一方面能加绩效。

正经文开始。

Cypress 是一个现代化的端到端测试框架,专为现代 Web 应用而设计,其提供了完整的测试解决方案,包括:

  • 浏览器测试:直接在浏览器中运行测试,提供真实的用户交互体验
  • 时间旅行调试:可以回放测试的每个步骤,查看 DOM 状态、网络请求等
  • 自动等待:智能等待元素出现和网络请求完成,减少测试的不稳定性
  • 实时重载:修改测试代码时自动重新运行测试
  • 丰富的断言:提供多种断言方法,支持 DOM、网络、Cookie 等验证
  • 可视化调试:实时查看测试执行过程,快速定位问题
  • 测试生成:通过 Cypress Studio 录制用户操作生成测试代码
  • 自愈能力AI 驱动的智能测试修复和优化

如何使用 Cypress

1. 安装和初始化

bash 复制代码
# 安装 Cypress
npm install cypress --save-dev
# 或
yarn add cypress --dev
# 或 推荐
pnpm add cypress --dev

# 初始化 Cypress,运行之后项目根目录自动生成cypress
npx cypress open

2. 项目结构

bash 复制代码
cypress/
├── e2e/                 # 端到端测试文件
│   ├── home.cy.js
│   └── user.cy.js
├── fixtures/            # 测试数据文件
│   └── users.json
├── support/             # 支持文件
│   ├── commands.js      # 自定义命令
│   └── e2e.js          # 全局配置
├── screenshots/         # 失败截图
└── videos/             # 测试视频

3. 基本测试结构

javascript 复制代码
describe('功能模块测试', () => {
  beforeEach(() => {
    // 每个测试前的准备工作
    cy.visit('/')
  })

  it('应该能够执行某个操作', () => {
    // 测试步骤
    cy.get('[data-testid="button"]').click()
    cy.contains('成功').should('be.visible')
  })
})

常用方法分类

1. 页面导航

javascript 复制代码
// 访问页面
cy.visit('/login')
cy.visit('https://example.com')

// 页面重载
cy.reload()
cy.reload(true) // 强制重载,忽略缓存

// 前进/后退
cy.go('back')
cy.go('forward')
cy.go(-1) // 后退1步
cy.go(1)  // 前进1步

2. 元素查找和操作

javascript 复制代码
// 查找元素
cy.get('#id')                    // 通过ID
cy.get('.class')                 // 通过class
cy.get('[data-testid="button"]') // 通过属性
cy.get('button').contains('登录') // 通过文本内容

// 点击操作
cy.get('button').click()
cy.get('button').click({ force: true }) // 强制点击
cy.get('button').dblclick()            // 双击
cy.get('button').rightclick()          // 右键点击

// 输入操作
cy.get('input').type('文本内容')
cy.get('input').clear().type('新内容')  // 清空后输入
cy.get('input').type('{enter}')         // 按回车键
cy.get('input').type('{selectall}{backspace}') // 全选删除

// 选择操作
cy.get('select').select('选项值')
cy.get('input[type="checkbox"]').check()
cy.get('input[type="radio"]').check()

3. 断言验证

javascript 复制代码
// 可见性断言
cy.get('button').should('be.visible')
cy.get('button').should('not.be.visible')
cy.get('button').should('exist')
cy.get('button').should('not.exist')

// 文本断言
cy.get('h1').should('contain', '标题')
cy.get('h1').should('have.text', '完整标题')
cy.get('input').should('have.value', '输入值')

// 属性断言
cy.get('input').should('have.attr', 'type', 'text')
cy.get('input').should('have.class', 'form-control')
cy.get('input').should('have.css', 'color', 'rgb(0, 0, 0)')

// 数量断言
cy.get('li').should('have.length', 3)
cy.get('li').should('have.length.greaterThan', 2)
cy.get('li').should('have.length.lessThan', 5)

4. 网络请求

javascript 复制代码
// 监听网络请求
cy.intercept('GET', '/api/users').as('getUsers')
cy.intercept('POST', '/api/login').as('login')

// 等待请求完成
cy.wait('@getUsers')
cy.wait('@login').then((interception) => {
  expect(interception.response.statusCode).to.eq(200)
})

// 模拟网络响应
cy.intercept('GET', '/api/users', { fixture: 'users.json' })
cy.intercept('POST', '/api/login', { statusCode: 401, body: { error: 'Unauthorized' } })

5. 文件操作

javascript 复制代码
// 文件上传
cy.get('input[type="file"]').selectFile('cypress/fixtures/image.jpg')
cy.get('input[type="file"]').selectFile(['file1.txt', 'file2.txt'])

// 文件下载
cy.get('a[download]').click()
cy.readFile('cypress/downloads/document.pdf')

6. 窗口和视口

javascript 复制代码
// 视口操作
cy.viewport(1280, 720)
cy.viewport('iphone-6')
cy.viewport('macbook-15')

// 窗口操作
cy.window().its('localStorage').should('exist')
cy.window().then((win) => {
  win.localStorage.setItem('token', 'abc123')
})
javascript 复制代码
// Cookie 操作
cy.setCookie('sessionId', 'abc123')
cy.getCookie('sessionId').should('have.property', 'value', 'abc123')
cy.clearCookie('sessionId')
cy.clearCookies()

// 本地存储
cy.clearLocalStorage()
cy.getAllLocalStorage()
cy.setLocalStorage('key', 'value')

8. 自定义命令

javascript 复制代码
// 在 cypress/support/commands.js 中定义
Cypress.Commands.add('login', (username, password) => {
  cy.visit('/login')
  cy.get('#username').type(username)
  cy.get('#password').type(password)
  cy.get('button[type="submit"]').click()
})

// 使用自定义命令
cy.login('user@example.com', 'password123')

常用配置

1. 基础配置 (cypress.config.js)

javascript 复制代码
import { defineConfig } from 'cypress'

export default defineConfig({
  // 视口设置
  viewportWidth: 1280,
  viewportHeight: 720,
  
  // 超时设置
  defaultCommandTimeout: 10000,
  requestTimeout: 10000,
  responseTimeout: 10000,
  pageLoadTimeout: 30000,
  
  // 视频和截图
  video: true,
  screenshotOnRunFailure: true,
  videosFolder: 'cypress/videos',
  screenshotsFolder: 'cypress/screenshots',
  
  // 浏览器安全 
  chromeWebSecurity: false,
  
  // E2E 配置
  e2e: {
    baseUrl: 'http://localhost:3000',
    supportFile: 'cypress/support/e2e.js',
    specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
    setupNodeEvents(on, config) {
      // 插件配置
      on('task', {
        log(message) {
          console.log(message)
          return null
        }
      })
    },
  },
  
  // 组件测试配置
  component: {
    devServer: {
      framework: 'create-react-app',
      bundler: 'webpack',
    },
  },
})

2. 环境变量配置

Cypress 支持多种方式设置环境变量,特别适合多套环境的场景:

2.1 在 cypress.config.js 中设置

javascript 复制代码
// cypress.config.js
export default defineConfig({
  env: {
    apiUrl: 'https://api.example.com',
    username: 'test@example.com',
    password: 'password123'
  }
})

// 使用环境变量
cy.visit(Cypress.env('apiUrl'))
cy.get('#username').type(Cypress.env('username'))

2.2 使用 cypress.env.json 文件(推荐)

json 复制代码
// cypress.env.json
{
  "testUser": {
    "username": "test@example.com",
    "password": "password123",
    "securityCode": "test123"
  },
  "baseUrl": "https://test.example.com",
  "loginUrl": "https://test.example.com/login",
  "apiUrl": "https://api.test.example.com"
}

2.3 多环境配置文件

开发环境配置 (cypress.env.dev.json)

json 复制代码
{
  "testUser": {
    "username": "dev@example.com",
    "password": "dev123",
    "securityCode": "dev123"
  },
  "baseUrl": "https://dev.example.com",
  "loginUrl": "https://dev.example.com/login",
  "apiUrl": "https://api.dev.example.com",
  "environment": "development"
}

测试环境配置 (cypress.env.test.json)

json 复制代码
{
  "testUser": {
    "username": "test@example.com",
    "password": "test123",
    "securityCode": "test123"
  },
  "baseUrl": "https://test.example.com",
  "loginUrl": "https://test.example.com/login",
  "apiUrl": "https://api.test.example.com",
  "environment": "testing"
}

生产环境配置 (cypress.env.prod.json)

json 复制代码
{
  "testUser": {
    "username": "prod@example.com",
    "password": "prod123",
    "securityCode": "prod123"
  },
  "baseUrl": "https://prod.example.com",
  "loginUrl": "https://prod.example.com/login",
  "apiUrl": "https://api.prod.example.com",
  "environment": "production"
}

2.4 动态环境配置

javascript 复制代码
// cypress.config.js
import { defineConfig } from 'cypress'

export default defineConfig({
  e2e: {
    setupNodeEvents(on, config) {
      // 根据环境变量选择配置文件
      const env = config.env.environment || 'test'
      const envFile = `cypress.env.${env}.json`
      
      try {
        const envConfig = require(`./${envFile}`)
        config.env = { ...config.env, ...envConfig }
      } catch (error) {
        console.log(`环境配置文件 ${envFile} 不存在,使用默认配置`)
      }
      
      return config
    },
  },
})

2.5 命令行环境变量

bash 复制代码
# 设置环境变量
CYPRESS_baseUrl=https://test.example.com cypress run

# 设置多个环境变量
CYPRESS_baseUrl=https://test.example.com CYPRESS_username=test@example.com cypress run

# 使用环境文件
cypress run --env-file cypress.env.test.json

2.6 package.json 脚本配置

json 复制代码
{
  "scripts": {
    "cypress:open": "cypress open",
    "cypress:run": "cypress run",
    "cypress:dev": "cypress run --env-file cypress.env.dev.json",
    "cypress:test": "cypress run --env-file cypress.env.test.json",
    "cypress:prod": "cypress run --env-file cypress.env.prod.json",
    "cypress:dev:open": "cypress open --env-file cypress.env.dev.json",
    "cypress:test:open": "cypress open --env-file cypress.env.test.json"
  }
}

2.8 动态环境切换

javascript 复制代码
// cypress/support/e2e.js
const environments = {
  dev: {
    baseUrl: 'https://dev.example.com',
    apiUrl: 'https://api.dev.example.com',
    testUser: {
      username: 'dev@example.com',
      password: 'dev123'
    }
  },
  test: {
    baseUrl: 'https://test.example.com',
    apiUrl: 'https://api.test.example.com',
    testUser: {
      username: 'test@example.com',
      password: 'test123'
    }
  },
  prod: {
    baseUrl: 'https://prod.example.com',
    apiUrl: 'https://api.prod.example.com',
    testUser: {
      username: 'prod@example.com',
      password: 'prod123'
    }
  }
}

// 根据环境变量选择配置
const currentEnv = Cypress.env('environment') || 'test'
const envConfig = environments[currentEnv]

// 设置全局配置
Cypress.config('baseUrl', envConfig.baseUrl)
Cypress.env('apiUrl', envConfig.apiUrl)
Cypress.env('testUser', envConfig.testUser)

2.9 环境变量优先级

Cypress 环境变量的优先级(从高到低):

  1. 命令行参数CYPRESS_baseUrl=https://example.com cypress run
  2. cypress.env.json 文件
  3. cypress.config.js 中的 env 配置

2.10 实际使用示例

javascript 复制代码
// 在测试中使用环境变量
describe('多环境测试', () => {
  beforeEach(() => {
    const baseUrl = Cypress.env('baseUrl')
    const testUser = Cypress.env('testUser')
    
    console.log(`当前环境: ${Cypress.env('environment')}`)
    console.log(`基础URL: ${baseUrl}`)
    console.log(`测试用户: ${testUser.username}`)
    
    cy.visit(baseUrl)
  })

  it('应该能够登录', () => {
    const testUser = Cypress.env('testUser')
    
    cy.get('#username').type(testUser.username)
    cy.get('#password').type(testUser.password)
    cy.get('#securityCode').type(testUser.securityCode)
    cy.get('button[type="submit"]').click()
  })
})

2.11 环境变量验证

javascript 复制代码
// cypress/support/e2e.js
// 验证必需的环境变量
beforeEach(() => {
  const requiredEnvVars = ['baseUrl', 'testUser']
  
  requiredEnvVars.forEach(envVar => {
    if (!Cypress.env(envVar)) {
      throw new Error(`缺少必需的环境变量: ${envVar}`)
    }
  })
  
  // 验证 testUser 对象
  const testUser = Cypress.env('testUser')
  if (!testUser || !testUser.username || !testUser.password) {
    throw new Error('testUser 环境变量配置不完整')
  }
})

2.12 CI/CD 环境变量

yaml 复制代码
# .github/workflows/cypress.yml
name: Cypress Tests
on: [push, pull_request]

jobs:
  cypress-run:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        environment: [dev, test, prod]
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      - run: npm ci
      - run: npx cypress run --env-file cypress.env.${{ matrix.environment }}.json
        env:
          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

2.13 环境变量最佳实践

  1. 敏感信息处理

    javascript 复制代码
    // 不要在代码中硬编码敏感信息
    // 使用环境变量或加密存储
    const apiKey = Cypress.env('apiKey') || 'default-key'
  2. 环境变量文档化

    markdown 复制代码
    ## 环境变量说明
    - `baseUrl`: 应用基础URL
    - `testUser.username`: 测试用户名
    - `testUser.password`: 测试密码
    - `apiUrl`: API接口地址
  3. 环境变量验证

    javascript 复制代码
    // 在测试开始前验证环境变量
    const validateEnv = () => {
      const required = ['baseUrl', 'testUser']
      required.forEach(key => {
        if (!Cypress.env(key)) {
          throw new Error(`Missing required env var: ${key}`)
        }
      })
    }
  4. 环境变量类型安全

    typescript 复制代码
    // cypress/support/index.d.ts
    declare namespace Cypress {
      interface Cypress {
        env(key: 'baseUrl'): string
        env(key: 'testUser'): {
          username: string
          password: string
          securityCode: string
        }
        env(key: 'apiUrl'): string
      }
    }

3. 全局配置 (cypress/support/e2e.js)

javascript 复制代码
// 全局异常处理
Cypress.on('uncaught:exception', (err, runnable) => {
  // 忽略特定错误
  if (err.message.includes('ResizeObserver loop limit exceeded')) {
    return false
  }
  return true
})

// 全局超时设置
Cypress.config('defaultCommandTimeout', 10000)
Cypress.config('requestTimeout', 10000)

// 全局命令
Cypress.Commands.add('login', (username, password) => {
  cy.session([username, password], () => {
    cy.visit('/login')
    cy.get('#username').type(username)
    cy.get('#password').type(password)
    cy.get('button[type="submit"]').click()
    cy.url().should('not.include', '/login')
  })
})

4. 数据驱动测试

javascript 复制代码
// cypress/fixtures/users.json
{
  "users": [
    { "username": "user1", "password": "pass1", "role": "admin" },
    { "username": "user2", "password": "pass2", "role": "user" }
  ]
}

// 使用测试数据
describe('用户登录测试', () => {
  beforeEach(() => {
    cy.fixture('users').as('users')
  })

  it('不同用户登录测试', function() {
    this.users.forEach(user => {
      cy.login(user.username, user.password)
      cy.get('[data-testid="user-role"]').should('contain', user.role)
      cy.logout()
    })
  })
})

5. 并行测试配置

javascript 复制代码
// cypress.config.js
export default defineConfig({
  e2e: {
    setupNodeEvents(on, config) {
      // 并行测试配置
      if (config.env.CI) {
        config.specPattern = [
          'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
          'cypress/e2e/**/*.spec.{js,jsx,ts,tsx}'
        ]
      }
    },
  },
})

最佳实践

1. 测试组织

  • 使用 describeit 组织测试结构
  • 每个测试文件专注于一个功能模块
  • 使用 beforeEachafterEach 进行测试准备和清理

2. 元素选择

  • 优先使用 data-testid 属性
  • 避免使用不稳定的 CSS 选择器
  • 使用语义化的选择器

3. 等待策略

  • 使用 cy.get().should() 而不是 cy.wait()
  • 等待元素可见而不是存在
  • 使用网络请求等待而不是固定时间

4. 测试数据

  • 使用 fixtures 管理测试数据
  • 每个测试使用独立的数据
  • 测试后清理数据

5. 错误处理

  • 使用全局异常处理
  • 提供有意义的错误信息
  • 使用截图和视频辅助调试

给已有项目加上 cypress 测试

1. 安装 cypress

这里注意,如果是 node 版本在 20 以下需要手动指定 cypress 版本,否则会因为版本不兼容导入失败,最新的 cypress 是 15.15.0。这里我的 node 版本是 18.12.0,所以安装 13.15.0 版本。

bash 复制代码
pnpm install cypress@13.15.0 -D
pnpm cypress install

2. 配置脚本

这里不加配置的话,自己选择功能和浏览器。如果需要指定功能和浏览器,可以在命令后面加上 --browser 和 --spec 参数,我的项目是后台管理系统,所以指定 e2e 功能和 chrome 浏览器。

在 package.json 中添加以下脚本:

json 复制代码
"scripts": {
  "e2e": "cypress open --e2e --browser chrome"
}

3. 执行脚本,打开 cypress 测试界面

bash 复制代码
pnpm e2e

执行脚本之后,项目根目录会生成 cypress.config.ts 文件,这个文件是 cypress 的配置文件,可以在这里配置一些全局变量和选项。

js 复制代码
import { defineConfig } from 'cypress';

export default defineConfig({
  // 全局配置 设置浏览器窗口大小、视频录制、截图等
  viewportWidth: 1280,
  viewportHeight: 1500,
  video: true,
  screenshotOnRunFailure: true,
  defaultCommandTimeout: 10000,
  requestTimeout: 10000,
  responseTimeout: 10000,
  pageLoadTimeout: 30000,
  chromeWebSecurity: false,
  // 防止会话数据被清除
  // experimentalSessionAndOrigin: true,

  // E2E 测试配置
  e2e: {
    baseUrl: 'https://nppss-app-management-test1.test.xdf.cn',
    supportFile: 'cypress/support/e2e.js',
    specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
    setupNodeEvents(on, config) {
      // 可以在这里添加插件配置
      // 例如:on('task', { ... })
    },
  },
});

cypress/support/e2e.js 文件中,可以添加一些全局配置,比如超时时间、异常处理等。

js 复制代码
// 全局配置
Cypress.on('uncaught:exception', (err, runnable) => {
  // 防止未捕获的异常导致测试失败
  // 对于 React 应用,这通常是由于开发环境中的热重载导致的
  if (err.message.includes('ResizeObserver loop limit exceeded')) {
    return false;
  }
  if (err.message.includes('Non-Error promise rejection captured')) {
    return false;
  }
  // 返回 false 防止 Cypress 失败
  return false;
});

// 设置全局超时
Cypress.config('defaultCommandTimeout', 10000);
Cypress.config('requestTimeout', 10000);
Cypress.config('responseTimeout', 10000);

同时,根目录会生成 cypress 目录,这个目录是 cypress 的测试文件目录,可以在这里创建测试文件。

比如创建一个简单的测试文件cypress/e2e/home-page.cy.js:

javascript 复制代码
describe('简单测试', () => {
  it('应该能够访问首页', () => {
    cy.visit('https://www.baidu.com');
    cy.get('body').should('be.visible');
    cy.title().should('not.be.empty');
  });
});

4. 拿一个页面试手

拿一个页面试试手,比如教师任务管理页面,cypress/e2e/teacher-task-page.cy.js:

javascript 复制代码
describe('教师任务管理页面测试', () => {
  const pagePath = '/shuangshi-ai/teacher-manage';

  // 公共函数:获取iframe内容
  const getIframeBody = () => {
    // 等待iframe加载完成
    cy.get('iframe', { timeout: 30000 }).should('be.visible');

    // 等待iframe内容完全加载
    cy.get('iframe').should(($iframe) => {
      const iframe = $iframe[0];
      const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
      expect(iframeDoc.body).to.exist;
    });

    return cy
      .get('iframe')
      .its('0.contentDocument.body')
      .should('not.be.empty')
      .then(cy.wrap); // 封装为Cypress可操作对象
  };

  beforeEach(() => {
    // 每个测试前清除存储
    cy.clearAllStorage();

    // 统一处理页面访问和登录
    cy.visit(pagePath);

    // 如果被重定向到登录页面,处理登录流程
    cy.url().then((url) => {
      if (url.includes('/e2/login')) {
        console.log('检测到登录重定向,执行登录流程...');

        // 等待登录页面加载完成
        cy.get('#txtUser').should('be.visible');
        cy.get('#txtPwd').should('be.visible');
        cy.get('#txtTestEnvSecCode').should('be.visible');

        // 输入登录信息
        cy.get('#txtUser').type(Cypress.env('testUser').username);
        cy.get('#txtPwd').type(Cypress.env('testUser').password);
        cy.get('#txtTestEnvSecCode').type(Cypress.env('testUser').securityCode);

        // 点击登录按钮
        cy.get('#btnLogin').click();

        // 等待跳转回目标页面
        cy.url({ timeout: 30000 }).should('include', pagePath);
      }
    });

    // 验证页面内容加载
    cy.get('body').should('be.visible');
    cy.wait(2000); // 等待页面完全加载
  });

  it('应该能够处理创建功能', () => {
    // 监听试卷列表API请求
    cy.intercept('GET', '**/npad/teacher/task/paper/list**').as(
      'paperListRequest',
    );

    getIframeBody().within(() => {
      // 点击创建按钮
      cy.get('.ant-pro-table-list-toolbar-right')
        .contains('button', '创建')
        .should('be.visible')
        .click();

      // 验证创建弹框出现
      cy.get('.ant-drawer').should('be.visible');

      // 选择学校 - 点击选择器,然后选择"上海"
      cy.get('.ant-drawer .ant-select').first().should('be.visible').click();
      cy.get('.ant-select-dropdown').should('be.visible');
      cy.contains('北京').click();

      // 选择部门 - 点击选择器,然后选择"智慧学习部"
      cy.get('.ant-drawer .ant-select').eq(1).should('be.visible').click();
      cy.get('.ant-select-dropdown').should('be.visible');
      // 如果已经有默认值,先清除再选择
      cy.get('.ant-select-dropdown .ant-select-item')
        .contains('智慧学习部')
        .click({ force: true });

      // 选择学科 - 点击选择器,然后选择"英语"
      cy.get('.ant-drawer .ant-select').eq(2).should('be.visible').click();
      cy.get('.ant-select-dropdown').should('be.visible');
      cy.contains('英语').click({ force: true });

      // 输入任务名称
      cy.get('.ant-drawer #name')
        .should('be.visible')
        .clear()
        .type('cypress测试任务');

      // 选择日期
      cy.get('.ant-drawer div[id="type"] label:first-child')
        .should('be.visible')
        .click();
      cy.get('.ant-drawer input[placeholder="开始日期"]')
        .should('be.visible')
        .click();
      cy.get('.ant-picker-panel-container').should('be.visible');
      cy.get(
        '.ant-picker-panel-container .ant-picker-footer .ant-picker-preset',
      )
        .eq(0)
        .should('be.visible')
        .click();

      // 去添加试题,打开弹框
      cy.wait(1000);
      cy.get('.ant-drawer .select-add-container button')
        .should('be.visible')
        .click();

      cy.get('.ant-modal').should('be.visible');

      // 定位部门选择器的输入框
      cy.get('.ant-modal .ant-select').eq(2).should('be.visible').click();
      cy.get('.ant-select-dropdown').should('be.visible');
      // 选择本校
      cy.contains('本校').click({ force: true });
      cy.wait(1000);

      // 查询试题
      cy.get('.ant-modal-body .ant-pro-table-search .ant-btn-primary')
        .scrollIntoView()
        .should('be.visible')
        .click({ force: true });

      // 选择第一个试题
      cy.get('.ant-modal-body .ant-radio-wrapper')
        .eq(0)
        .should('be.visible')
        .click();

      // 点击确定按钮,选择试题完成
      cy.get('.ant-modal-body .footer .confirm-btn')
        .should('be.visible')
        .click();
      // 等待弹框关闭
      cy.get('.ant-modal').should('not.exist');

      // 点击确定按钮,创建任务完成
      cy.get('.ant-drawer .button-box .ant-btn-primary')
        .should('be.visible')
        .click();
      // 关闭弹框,创建任务完成
      cy.get('.ant-drawer .ant-drawer-header-title .ant-drawer-close')
        .should('be.visible')
        .click();
      cy.get('.ant-drawer').should('not.exist');
    });
  });
});
相关推荐
技术小丁5 小时前
uni-app 广告弹窗最佳实践:不扰民、可控制频次、含完整源码
前端·uni-app·1024程序员节
quan26315 小时前
日常开发20251022,传统HTML表格实现图片+视频+预览
前端·javascript·html·html列表实现图片+视频
陶甜也5 小时前
ThreeJS曲线动画:打造炫酷3D路径运动
前端·vue·threejs
楊无好5 小时前
react中的受控组件与非受控组件
前端·react.js
菠萝+冰5 小时前
react虚拟滚动
前端·javascript·react.js
落一落,掉一掉5 小时前
第十三周前端加密绕过
前端
前端初见6 小时前
快速上手TypeScript,TS速通
javascript·ubuntu·typescript
Onlyᝰ6 小时前
前端tree树
javascript·vue.js·elementui
高德开放平台7 小时前
实战案例|借助高德开放平台实现智慧位置服务:路线导航的开发与实践
前端·后端