.claude/skills/cypress-selectors/SKILL.md
3-level selector system for testing: CORE_SELECTORS + BLOCK_SELECTORS + THEME_SELECTORS. Provides helpers sel(), cySelector(), entitySelectors() for components and POMs. Use this skill when working with data-cy attributes, POMs, or Cypress tests.
npx skillsauth add NextSpark-js/nextspark cypress-selectorsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Centralized data-cy selector system for Cypress testing.
┌─────────────────────────────────────────┐
│ CORE (Read-Only) │
│ core/lib/test/core-selectors.ts │
│ core/lib/test/selector-factory.ts │
└─────────────────┬───────────────────────┘
│ imports
▼
┌─────────────────────────────────────────┐
│ THEME (Editable) │
│ contents/themes/{theme}/lib/selectors.ts│
│ ├── BLOCK_SELECTORS │
│ ├── DEVTOOLS_SELECTORS │
│ ├── THEME_SELECTORS = { │
│ │ ...CORE_SELECTORS, │
│ │ blocks: BLOCK_SELECTORS, │
│ │ devtools: DEVTOOLS_SELECTORS │
│ │ } │
│ └── exports: sel, cySelector, etc. │
└─────────────────┬───────────────────────┘
│ re-exports
▼
┌─────────────────────────────────────────┐
│ tests/cypress/src/selectors.ts │
│ (re-exports for test files) │
└─────────────────┬───────────────────────┘
│ imports
┌─────────┴─────────┐
▼ ▼
┌───────────────┐ ┌───────────────┐
│ Components │ │ POMs │
│ sel('x.y') │ │ cySelector() │
└───────────────┘ └───────────────┘
data-cy attributes to componentssel(path: string, replacements?: object)Use in components to get the selector string for data-cy:
// In block component: contents/themes/{theme}/blocks/{block-name}/component.tsx
import { sel } from '../../lib/selectors'
// Static selector - note the 'blocks.' prefix for block selectors
<section data-cy={sel('blocks.hero.container')}>
<h2 data-cy={sel('blocks.hero.title')}>{title}</h2>
</section>
// Dynamic selector with replacement
{items.map((item, index) => (
<div data-cy={sel('blocks.faqAccordion.item', { index: String(index) })}>
{item.content}
</div>
))}
// Core selectors (no 'blocks.' prefix)
<button data-cy={sel('auth.login.submit')}>Login</button>
cySelector(path: string, replacements?: object)Use in POMs and Cypress tests to get the CSS selector [data-cy="..."]:
// In POM: contents/themes/{theme}/tests/cypress/src/features/MyPOM.ts
import { BasePOM } from '../core/BasePOM'
import { cySelector } from '../selectors'
export class HeroPOM extends BasePOM {
get selectors() {
return {
container: cySelector('blocks.hero.container'),
title: cySelector('blocks.hero.title'),
}
}
// Dynamic selectors
getItem(index: number) {
return cy.get(cySelector('blocks.faqAccordion.item', { index: String(index) }))
}
// Factory method
static create(): HeroPOM {
return new HeroPOM()
}
}
// In test file: contents/themes/{theme}/tests/cypress/e2e/blocks/hero.cy.ts
import { cySelector } from '../../src/selectors'
describe('Hero Block', () => {
it('should display the hero', () => {
cy.get(cySelector('blocks.hero.container')).should('be.visible')
})
})
entitySelectors(slug: string)Get all selectors for a specific entity:
import { entitySelectors } from '../../src/selectors'
const taskSelectors = entitySelectors('tasks')
// Returns: { table, row, createBtn, editBtn, ... }
IMPORTANT: Block selectors use the blocks. prefix because BLOCK_SELECTORS is nested under blocks: in THEME_SELECTORS:
// Block selectors - use 'blocks.' prefix
sel('blocks.hero.container') // → 'block-hero'
sel('blocks.faqAccordion.item', { index: '0' }) // → 'faq-item-0'
sel('blocks.pricingTable.plan', { index: '1' }) // → 'pricing-plan-1'
// Core selectors - no prefix
sel('auth.login.form') // → 'login-form'
sel('entity.table', { slug: 'tasks' }) // → 'tasks-table'
// Devtools selectors - use 'devtools.' prefix
sel('devtools.scheduledActions.table') // → 'scheduled-actions-table'
domain-element
Examples:
block-hero (block container)login-form (core component)nav-main (navigation element){placeholder}-element-{placeholder}
Placeholders:
{index} - Array index (most common for blocks){slug} - Entity slug (tasks, customers, pages){id} - Record ID{name} - Field name{action} - Action name (edit, delete)Examples:
faq-item-{index} → faq-item-0{slug}-row-{id} → tasks-row-123field-{name} → field-email| Type | Location | Who Modifies |
|------|----------|--------------|
| Core selectors | core/lib/test/core-selectors.ts | Core maintainers only |
| Selector factory | core/lib/test/selector-factory.ts | Core maintainers only |
| Block selectors | contents/themes/{theme}/lib/selectors.ts | Theme developers |
| Test re-exports | contents/themes/{theme}/tests/cypress/src/selectors.ts | Auto (re-exports) |
| POMs | contents/themes/{theme}/tests/cypress/src/features/*POM.ts | Test developers |
Edit contents/themes/{theme}/lib/selectors.ts, add to BLOCK_SELECTORS:
export const BLOCK_SELECTORS = {
// ... existing blocks
myNewBlock: {
container: 'block-my-new-block',
title: 'my-block-title',
item: 'my-block-item-{index}',
}
}
Then use in component with blocks. prefix:
// contents/themes/{theme}/blocks/my-new-block/component.tsx
import { sel } from '../../lib/selectors'
<section data-cy={sel('blocks.myNewBlock.container')}>
<h2 data-cy={sel('blocks.myNewBlock.title')}>{title}</h2>
</section>
Edit core/lib/test/core-selectors.ts:
export const CORE_SELECTORS = {
// ... existing selectors
myNewFeature: {
container: 'my-feature-container',
list: 'my-feature-list',
item: 'my-feature-item-{id}',
}
}
Edit contents/themes/{theme}/lib/selectors.ts, add to DEVTOOLS_SELECTORS:
export const DEVTOOLS_SELECTORS = {
// ... existing selectors
myDevTool: {
page: 'devtools-my-tool-page',
table: 'my-tool-table',
}
}
// ❌ NEVER: Hardcoded data-cy in component
<button data-cy="submit-btn">
// ❌ NEVER: Direct selector string in test
cy.get('[data-cy="submit-btn"]')
// ❌ NEVER: Missing 'blocks.' prefix for block selectors
sel('hero.container') // Wrong!
sel('blocks.hero.container') // Correct!
// ❌ NEVER: Import from core in theme components
import { sel } from '@/core/lib/test' // Wrong!
import { sel } from '../../lib/selectors' // Correct!
// ❌ NEVER: Add selector without UI element
// Only add selectors for existing UI elements
python .claude/skills/cypress-selectors/scripts/validate-selectors.py
python .claude/skills/cypress-selectors/scripts/extract-missing.py --path contents/themes/default/blocks/
# Basic generation
python .claude/skills/cypress-selectors/scripts/generate-block-selectors.py --block my-new-block
# Analyze existing block and generate full example
python .claude/skills/cypress-selectors/scripts/generate-block-selectors.py --block faq-accordion --analyze --full
# Just show selector entry without instructions
python .claude/skills/cypress-selectors/scripts/generate-block-selectors.py --block my-block --dry-run
Before committing UI changes:
data-cy={sel('...')}sel('blocks.{blockName}.{element}') pathcySelector()_selectors/*.cy.tsdata-cy strings in componentsreferences/architecture.md for detailed architecturereferences/naming-conventions.md for naming rulesreferences/anti-patterns.md for common mistakesdevelopment
Zod validation patterns for this Next.js application. Covers schema definition, API validation, form integration, error formatting, and type inference. Use this skill when implementing validation for APIs, forms, or entity schemas.
development
Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices".
testing
Test coverage metrics and registry system for this Next.js application. Covers FEATURE_REGISTRY, FLOW_REGISTRY, TAGS_REGISTRY, and coverage metrics interpretation. Use this skill when evaluating test coverage, identifying gaps, or planning testing priorities.
development
TanStack Query (React Query) patterns for data fetching in this Next.js application. Covers useQuery, useMutation, optimistic updates, cache invalidation, and anti-patterns. Use this skill when implementing data fetching or state management with server data.