Cypress 选择器 #

基本选择器 #

cy.get() #

cy.get() 是最常用的选择器方法,支持 CSS 选择器语法:

javascript
// ID 选择器
cy.get('#username');

// 类选择器
cy.get('.btn-primary');

// 标签选择器
cy.get('button');

// 属性选择器
cy.get('[type="submit"]');

// 组合选择器
cy.get('button.btn-primary[type="submit"]');

选择器类型 #

javascript
// 后代选择器
cy.get('form input');

// 子选择器
cy.get('ul > li');

// 相邻兄弟选择器
cy.get('h1 + p');

// 通用兄弟选择器
cy.get('h1 ~ p');

推荐的选择器策略 #

优先级排序 #

text
1. data-cy / data-test / data-testid  ⭐⭐⭐⭐⭐  最推荐
2. 语义化标签 + 可访问性属性            ⭐⭐⭐⭐
3. ID 选择器                          ⭐⭐⭐
4. 类选择器(稳定的类名)               ⭐⭐
5. CSS 选择器组合                      ⭐
6. 文本内容选择                        ⚠️ 谨慎使用

data 属性选择器(最佳实践) #

html
<!-- 应用代码 -->
<button data-cy="submit-button">提交</button>
<input data-cy="email-input" type="email" />
<div data-cy="error-message">错误信息</div>
javascript
// 测试代码
cy.get('[data-cy="submit-button"]').click();
cy.get('[data-cy="email-input"]').type('test@example.com');
cy.get('[data-cy="error-message"]').should('be.visible');

为什么使用 data 属性? #

text
┌─────────────────────────────────────────────────────────────┐
│                    选择器稳定性对比                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ❌ CSS 类名                                                │
│     .btn-primary → .btn-primary-v2                          │
│     样式重构后测试失败                                       │
│                                                             │
│  ❌ ID                                                      │
│     #submit-btn → #submit-button                            │
│     ID 变更后测试失败                                        │
│                                                             │
│  ❌ DOM 结构                                                │
│     form > div > input → form > fieldset > input            │
│     结构调整后测试失败                                       │
│                                                             │
│  ✅ data 属性                                               │
│     [data-cy="submit"] → [data-cy="submit"]                 │
│     始终稳定,与样式和结构无关                               │
│                                                             │
└─────────────────────────────────────────────────────────────┘

文本选择器 #

cy.contains() #

根据文本内容选择元素:

javascript
// 精确匹配
cy.contains('登录').click();

// 部分匹配
cy.contains('欢迎').should('be.visible');

// 正则表达式
cy.contains(/^用户\d+$/).click();

// 在特定范围内查找
cy.get('.nav').contains('首页').click();

// 忽略大小写
cy.contains('login', { matchCase: false });

contains 选择器 #

javascript
// 使用 :contains 选择器
cy.get('button:contains("提交")');
cy.get('div:contains("错误")');

// 组合使用
cy.get('.message:contains("成功")').should('have.class', 'success');

过滤选择器 #

.filter() #

javascript
// 过滤可见元素
cy.get('.item').filter(':visible');

// 过滤隐藏元素
cy.get('.item').filter(':hidden');

// 过滤特定属性
cy.get('input').filter('[disabled]');

// 过滤函数
cy.get('.product').filter(($el) => {
  return $el.data('price') > 100;
});

.find() #

在已选元素的子元素中查找:

javascript
// 在 form 中查找 input
cy.get('form').find('input');

// 在列表中查找特定项
cy.get('.menu').find('.active');

// 链式调用
cy.get('.container')
  .find('.section')
  .find('.item')
  .first();

.children() #

选择直接子元素:

javascript
// 所有子元素
cy.get('ul').children();

// 特定子元素
cy.get('ul').children('.active');

// 第 N 个子元素
cy.get('ul').children().eq(2);

位置选择器 #

.first() / .last() #

javascript
// 第一个元素
cy.get('.item').first();

// 最后一个元素
cy.get('.item').last();

.eq() #

javascript
// 第 3 个元素(从 0 开始)
cy.get('.item').eq(2);

// 倒数第 2 个元素
cy.get('.item').eq(-2);

.next() / .prev() #

javascript
// 下一个兄弟元素
cy.get('.active').next();

// 上一个兄弟元素
cy.get('.active').prev();

// 后面所有兄弟元素
cy.get('.active').nextAll();

// 前面所有兄弟元素
cy.get('.active').prevAll();

// 所有兄弟元素
cy.get('.active').siblings();

.parent() / .parents() #

javascript
// 直接父元素
cy.get('input').parent();

// 所有祖先元素
cy.get('input').parents();

// 特定祖先元素
cy.get('input').parents('form');

// 直到某个祖先(不包含)
cy.get('input').parentsUntil('.container');

.closest() #

javascript
// 最近的匹配祖先
cy.get('input').closest('form');
cy.get('.item').closest('.container');

状态选择器 #

可见性 #

javascript
// 可见元素
cy.get('.item:visible');

// 隐藏元素
cy.get('.item:hidden');

// 判断可见性
cy.get('.modal').should('be.visible');
cy.get('.hidden').should('not.be.visible');

表单状态 #

javascript
// 禁用状态
cy.get('button:disabled');
cy.get('input:enabled');

// 选中状态
cy.get('input:checked');
cy.get('input:unchecked');

// 聚焦状态
cy.get('input:focus');
cy.get('input:focused');

内容状态 #

javascript
// 空元素
cy.get('input:empty');

// 包含文本
cy.get('div:contains("文本")');

// 有子元素
cy.get('div:has(p)');

属性选择器 #

基本属性 #

javascript
// 有某个属性
cy.get('[disabled]');

// 属性等于值
cy.get('[type="email"]');

// 属性不等于值
cy.get('[type!="email"]');

// 属性包含值
cy.get('[class*="btn"]');

// 属性以值开头
cy.get('[class^="btn-"]');

// 属性以值结尾
cy.get('[class$="-primary"]');

多属性组合 #

javascript
// 同时满足多个属性
cy.get('[type="submit"][disabled]');

// data 属性组合
cy.get('[data-cy="form"][data-valid="true"]');

表单选择器 #

输入类型 #

javascript
// 文本输入
cy.get('input[type="text"]');
cy.get(':text');

// 密码输入
cy.get('input[type="password"]');
cy.get(':password');

// 邮箱输入
cy.get('input[type="email"]');
cy.get(':email');

// 数字输入
cy.get('input[type="number"]');
cy.get(':number');

// 复选框
cy.get('input[type="checkbox"]');
cy.get(':checkbox');

// 单选框
cy.get('input[type="radio"]');
cy.get(':radio');

// 文件上传
cy.get('input[type="file"]');
cy.get(':file');

表单状态 #

javascript
// 必填字段
cy.get('input:required');

// 只读字段
cy.get('input:read-only');

// 有效字段
cy.get('input:valid');

// 无效字段
cy.get('input:invalid');

高级选择器 #

.within() #

在特定范围内执行命令:

javascript
cy.get('.login-form').within(() => {
  // 这里的选择器只在 .login-form 范围内查找
  cy.get('input[name="username"]').type('user');
  cy.get('input[name="password"]').type('pass');
  cy.get('button').click();
});

.root() #

选择根元素:

javascript
// 获取 document 根元素
cy.root();

// 在 within 中获取当前上下文
cy.get('.form').within(() => {
  cy.root().should('have.class', 'form');
});

.shadow() #

访问 Shadow DOM:

javascript
// 进入 Shadow DOM
cy.get('my-component').shadow().find('.inner-element');

// 链式操作
cy.get('custom-card')
  .shadow()
  .find('.card-title')
  .should('contain', 'Hello');

选择器最佳实践 #

1. 使用 data 属性 #

html
<!-- ✅ 好的做法 -->
<button data-cy="add-to-cart" data-product-id="123">
  加入购物车
</button>

<!-- ❌ 不好的做法 -->
<button class="btn btn-primary btn-lg mt-3">
  加入购物车
</button>
javascript
// ✅ 好的做法
cy.get('[data-cy="add-to-cart"]').click();

// ❌ 不好的做法
cy.get('.btn.btn-primary.btn-lg').click();

2. 避免过于具体的选择器 #

javascript
// ❌ 太具体,容易因结构变化而失败
cy.get('body > div.container > div.row > div.col-md-6 > form > button');

// ✅ 使用 data 属性,简洁稳定
cy.get('[data-cy="submit-button"]');

3. 使用语义化选择器 #

javascript
// ✅ 结合语义标签
cy.get('nav').find('[data-cy="menu-item"]');
cy.get('article').find('[data-cy="title"]');
cy.get('aside').find('[data-cy="widget"]');

// ✅ 使用 ARIA 属性
cy.get('[role="button"]');
cy.get('[aria-label="关闭"]');
cy.get('[aria-labelledby="dialog-title"]');

4. 创建选择器常量 #

javascript
// cypress/support/selectors.js
export const SELECTORS = {
  LOGIN: {
    FORM: '[data-cy="login-form"]',
    USERNAME: '[data-cy="username-input"]',
    PASSWORD: '[data-cy="password-input"]',
    SUBMIT: '[data-cy="login-button"]',
    ERROR: '[data-cy="error-message"]'
  },
  CART: {
    CONTAINER: '[data-cy="cart-container"]',
    ITEM: '[data-cy="cart-item"]',
    TOTAL: '[data-cy="cart-total"]',
    CHECKOUT: '[data-cy="checkout-button"]'
  }
};

// 使用
import { SELECTORS } from '../support/selectors';

cy.get(SELECTORS.LOGIN.USERNAME).type('user');
cy.get(SELECTORS.LOGIN.SUBMIT).click();

5. 动态选择器 #

javascript
// 使用变量构建选择器
const productId = 123;
cy.get(`[data-product-id="${productId}"]`);

// 使用函数生成选择器
const getItemSelector = (id) => `[data-cy="item-${id}"]`;
cy.get(getItemSelector(1)).click();

// 使用模板字符串
const section = 'profile';
cy.get(`[data-section="${section}"]`);

选择器调试 #

打印匹配元素 #

javascript
cy.get('.item').then(($items) => {
  console.log(`找到 ${$items.length} 个元素`);
  $items.each((index, el) => {
    console.log(index, el.textContent);
  });
});

验证选择器 #

javascript
// 检查元素数量
cy.get('.item').should('have.length', 5);

// 检查元素存在
cy.get('.item').should('exist');

// 检查元素可见
cy.get('.item').should('be.visible');

使用选择器测试工具 #

javascript
// 在浏览器控制台中测试选择器
// 打开 DevTools,在 Console 中输入:
document.querySelectorAll('[data-cy="submit-button"]');

// 或使用 Cypress 命令
cy.get('[data-cy="submit-button"]').debug();

完整示例 #

javascript
describe('选择器示例', () => {
  beforeEach(() => {
    cy.visit('/products');
  });

  it('使用各种选择器策略', () => {
    // data 属性选择器(推荐)
    cy.get('[data-cy="product-list"]').should('exist');
    
    // 文本选择器
    cy.contains('商品列表').should('be.visible');
    
    // 组合选择器
    cy.get('.product-card[data-cy="featured"]').first().click();
    
    // 过滤选择器
    cy.get('.product-item').filter(':visible').should('have.length.gt', 0);
    
    // 在范围内查找
    cy.get('[data-cy="product-list"]').within(() => {
      cy.get('[data-cy="product-item"]').first().click();
    });
    
    // 相对位置选择
    cy.get('[data-cy="product-item"]').first()
      .next()
      .should('have.attr', 'data-cy', 'product-item');
    
    // 表单选择器
    cy.get('[data-cy="search-form"]').within(() => {
      cy.get('input[type="text"]').type('手机');
      cy.get('button[type="submit"]').click();
    });
    
    // 状态选择器
    cy.get('button:disabled').should('exist');
    cy.get('input:focus').should('have.attr', 'name', 'search');
  });
});

下一步 #

现在你已经掌握了 Cypress 选择器的使用方法,接下来学习 断言 了解如何验证测试结果!

最后更新:2026-03-28