This guide outlines best practices for writing reliable Cypress tests for the AiStudio4 application, based on lessons learned from implementing the service provider management test.
- Use ARIA labels as primary selectors - they improve both accessibility and test reliability
- Fall back to unique IDs or classes only when ARIA labels aren't practical
- Avoid generic text-based selectors when multiple elements might have the same text
Selector Preference Order:
- ARIA labels:
aria-label,aria-labelledby - Semantic roles:
role="button",role="dialog" - Unique IDs:
#component-action-element - Specific classes:
.component-action-element - Text content (last resort)
Examples:
// Best - ARIA label (accessible + testable)
cy.get('button[aria-label="Add new service provider"]').click();
// Good - unique ID when ARIA isn't suitable
cy.get('#service-provider-management-add-provider-button').click();
// Acceptable - specific class
cy.get('.service-provider-delete-button').click();
// Bad - generic, could match multiple elements
cy.contains('button', 'Add Provider').click();- Use
.scrollIntoView()for elements that might be outside the viewport - Check visibility before interactions with
.should('be.visible') - Wait for elements to be ready before proceeding
Example:
cy.get('.grid').contains('.card-base', providerName)
.scrollIntoView()
.within(() => {
cy.get('.service-provider-delete-button').should('be.visible').click();
});Required Changes:
- Close buttons need
aria-label="Close modal"for testing - Use
[role="dialog"]to identify modal containers
Test Pattern:
// Opening modal via command
cy.get('body').type('{ctrl}k');
cy.get('#command-input').type('Modal Name');
cy.get('.command-dropdown-menu').contains('.font-medium', 'Modal Name').click();
// Verifying modal is open
cy.get('[role="dialog"]').contains('h2', 'Modal Title').should('be.visible');
// Closing modal
cy.get('button[aria-label="Close modal"]').click();Required Changes (in order of preference):
- Add descriptive
aria-labelattributes - Use semantic
roleattributes where appropriate - Add unique IDs or class names as fallback
Examples:
// Best - ARIA labels for all action buttons
<Button aria-label="Add new service provider" onClick={...}>
<PlusCircle /> Add Provider
</Button>
<Button aria-label="Edit service provider" onClick={...}>
<Pencil />
</Button>
<Button aria-label="Delete service provider" onClick={...}>
<Trash2 />
</Button>
// Fallback - unique IDs/classes when ARIA isn't sufficient
<Button
id="service-provider-management-add-provider-button"
className="service-provider-delete-button"
onClick={...}
>Test Patterns:
// Preferred - ARIA labels
cy.get('button[aria-label="Add new service provider"]').click();
cy.get('button[aria-label="Delete service provider"]').click();
// Fallback - IDs/classes
cy.get('#service-provider-management-add-provider-button').click();
cy.get('.service-provider-delete-button').click();Test Pattern:
// Text inputs
cy.contains('label', 'Field Name').parent().find('input').type('value');
// Password inputs
cy.contains('label', 'API Key').parent().find('input[type="password"]').type('secret');
// Select dropdowns (shadcn)
cy.contains('label', 'Field Name').parent().find('[role="combobox"]').click();
cy.contains('[role="option"]', 'Option Value').click();
// Submit
cy.contains('button', 'Save').click(); // or use specific submitButtonIdRequired Changes:
- Ensure cards have consistent class names (
.card-base) - Use unique content for identification
Test Pattern:
// Wait for grid to contain item
cy.get('.grid').should('contain', itemName);
// Interact with specific card
cy.get('.grid').contains('.card-base', itemName)
.scrollIntoView()
.within(() => {
// Actions within the card
});describe('Feature Management', () => {
const uniqueName = `Test Item ${Date.now()}`;
beforeEach(() => {
cy.visit('/');
cy.get('.InputBar').should('be.visible');
});
it('should create, verify, and delete an item', () => {
// 1. Navigate to feature
// 2. Create item
// 3. Verify creation
// 4. Delete item
// 5. Verify deletion
// 6. Cleanup/close
});
});Include comments that reference:
- Component names being tested
- Props/methods being exercised
- UI patterns being followed
Example:
// 3. Click the "Add Provider" button (ServiceProviderManagement component) to open the form dialog
cy.get('#service-provider-management-add-provider-button').click();
// 4. Fill out the ServiceProviderForm (uses GenericForm component)
cy.contains('label', 'Friendly Name').parent().find('input').type(providerName);When adding testability to components:
- Add descriptive
aria-labelfor all action buttons (preferred) - Use semantic
roleattributes where appropriate - Include
aria-describedbyfor additional context if needed - Add unique
idorclassNameas fallback only - Ensure ARIA labels are specific: "Delete service provider" not just "Delete"
- Close button has
aria-label="Close modal"or more specific like "Close service provider modal" - Modal container has proper
role="dialog" - Modal has
aria-labelledbypointing to header ID - Modal has
aria-describedbyfor additional context if needed - Headers are properly structured for identification
- Form fields have clear label associations (
aria-labelledbyor<label>) - Submit buttons have descriptive
aria-label: "Save service provider" not just "Save" - Error messages use
aria-describedbyto link to field - Select components use proper ARIA roles (
combobox,listbox,option)
- Container has
role="grid"orrole="list"where appropriate - Items have
role="gridcell"orrole="listitem" - Items have
aria-labelwith descriptive content: "Service provider: OpenAI" - Action buttons within items have specific ARIA labels
- Use
aria-rowindexoraria-setsizefor large lists
cy.get('body').type('{ctrl}k');
cy.get('#command-input').type('Command Name');
cy.get('.command-dropdown-menu').contains('.font-medium', 'Command Name').click();// Text input
cy.contains('label', 'Label Text').parent().find('input').type('value');
// Select dropdown
cy.contains('label', 'Label Text').parent().find('[role="combobox"]').click();
cy.contains('[role="option"]', 'Option Text').click();cy.get('[role="dialog"]').contains('h2', 'Confirm Action').should('be.visible');
cy.contains('button', 'Confirm').click();- Element not found: Add unique selectors to components
- Element not visible: Use
.scrollIntoView()and visibility checks - Multiple elements match: Make selectors more specific
- Timing issues: Add appropriate waits or visibility checks
- Modal/overlay issues: Ensure proper focus management and z-index
- Check the HTML structure in test runner
- Verify CSS classes match expectations
- Confirm element visibility and positioning
- Test selectors in browser console
- Add debug screenshots:
cy.screenshot('debug-point')
- ARIA First: Prefer ARIA labels and roles over IDs/classes - they improve both accessibility and test reliability
- Semantic Selectors: Use
role,aria-label,aria-labelledbyas primary selectors - Descriptive Labels: Make ARIA labels specific - "Delete service provider" not just "Delete"
- Component Comments: Reference React components and their responsibilities
- Visibility Handling: Use
scrollIntoView()for elements that might be off-screen - Complete Flows: Test entire user workflows, not just individual actions
- Unique Data: Use timestamps or unique identifiers to avoid test conflicts
- Cleanup: Ensure tests clean up after themselves (delete created items)
- Fallback Strategy: Use the selector preference order (ARIA → roles → IDs → classes → text)
For Accessibility:
- Screen readers can understand button purposes
- Users with disabilities get better context
- Improves overall app usability
For Testing:
- More reliable than text content (which might change)
- Less brittle than CSS classes (which might be refactored)
- Semantic meaning makes tests self-documenting
- Encourages developers to think about accessibility
Example Impact:
// Fragile - text might change, could match multiple elements
cy.contains('button', 'Delete').click();
// Better - but still could match multiple delete buttons
cy.get('.delete-button').click();
// Best - semantic, specific, accessible
cy.get('button[aria-label="Delete service provider"]').click();Following these patterns will result in more reliable, maintainable tests that accurately reflect user interactions while simultaneously improving the application's accessibility for all users.