QA Testing Guidelines
Overview
The Earth Design System (EDS) provides comprehensive support for testing attributes, including data-testid
and other data attributes. All EDS components are designed to accept consumer props, which include HTML attributes like data attributes, making them fully testable and QA-friendly.
How EDS Components Support Data Attributes
Component Architecture
EDS components are built with a consistent pattern that accepts all standard HTML attributes through "consumer props". This means that any component can accept:
data-testid
attributes for test automationdata-*
attributes for custom data needsaria-*
attributes for accessibility- Standard HTML attributes like
id
,className
, etc.
Implementation Pattern
// All EDS components follow this pattern:
<Button
data-testid="submit-button"
data-analytics="user-action"
data-component="form-submit"
onClick={handleSubmit}
>
Submit
</Button>
<TextInput
data-testid="email-input"
data-field="user-email"
placeholder="Enter your email"
/>
<Field
data-testid="email-field"
label="Email Address"
>
<TextInput data-testid="email-input" />
</Field>
When to Use Data-TestID
Critical User Journey Elements
Add data-testid
to components that are part of critical user flows:
// Login form
<Button data-testid="login-submit">Sign In</Button>
<TextInput data-testid="username-input" />
<TextInput data-testid="password-input" type="password" />
// Booking flow
<Button data-testid="book-appointment">Book Now</Button>
<Select data-testid="service-selector" />
<Button data-testid="confirm-booking">Confirm</Button>
Interactive Elements
Elements that users interact with should have test IDs:
<Button data-testid="add-item">Add Item</Button>
<IconButton data-testid="delete-item" aria-label="Delete" />
<Checkbox data-testid="terms-agreement" />
<Select data-testid="country-selector" />
Dynamic Content Areas
Areas where content changes based on user actions or API responses:
<StatusPanel data-testid="booking-status" />
<Alert data-testid="error-message" />
<Loading data-testid="loading-spinner" />
Form Elements
All form inputs and their containers:
<Field data-testid="email-field">
<TextInput data-testid="email-input" />
</Field>
<Fieldset data-testid="contact-preferences">
<Checkbox data-testid="email-notifications" />
<Checkbox data-testid="sms-notifications" />
</Fieldset>
Navigation Elements
Key navigation components:
<PageHeader data-testid="main-header">
<Button data-testid="back-button">Back</Button>
<Button data-testid="save-button">Save</Button>
</PageHeader>
When to Not to Use Data-TestID
Static Content
Don't add test IDs to purely presentational elements:
// Avoid this
<Text data-testid="welcome-text">Welcome to our service</Text>
<Heading data-testid="page-title">Dashboard</Heading>
// Unless they're used for content verification in tests
Every Single Element
Don't add test IDs to every component - focus on testable interactions:
// Avoid over-testing
<Stack data-testid="container">
{' '}
// ❌ Not needed
<Box data-testid="wrapper">
{' '}
// ❌ Not needed
<Text data-testid="label">Name</Text> // ❌ Not needed
<TextInput data-testid="name-input" /> // ✅ Good
</Box>
</Stack>
Best Practices for Test IDs
Naming Conventions
Use Descriptive, Semantic Names
// Good
<Button data-testid="submit-payment">Pay Now</Button>
<TextInput data-testid="credit-card-number" />
<Select data-testid="expiry-month" />
// Bad
<Button data-testid="btn1">Pay Now</Button>
<TextInput data-testid="input-field" />
<Select data-testid="dropdown" />
Use Kebab-Case
// Consistent casing
<Button data-testid="confirm-appointment">Confirm</Button>
<TextInput data-testid="customer-email" />
<Select data-testid="service-category" />
Include Context When Needed
// When multiple similar elements exist
<Button data-testid="booking-form-submit">Submit Booking</Button>
<Button data-testid="contact-form-submit">Send Message</Button>
// Or use parent container context
<div data-testid="booking-form">
<Button data-testid="submit">Submit</Button>
</div>
Component-Specific Guidelines
Form Components
// Complete form example with accessibility and test IDs
<form data-testid="booking-form">
<Field data-testid="service-field" label="Service">
<Select
data-testid="service-select"
aria-label="Choose service"
options={services}
/>
</Field>
<Field data-testid="customer-name-field" label="Full Name">
<TextInput
data-testid="customer-name-input"
placeholder="Enter your full name"
/>
</Field>
<Field data-testid="email-field" label="Email" errorMessage="Invalid email">
<TextInput
data-testid="email-input"
type="email"
placeholder="Enter your email"
/>
</Field>
<Button data-testid="submit-booking" type="submit">
Book Appointment
</Button>
</form>
Interactive Components
// Navigation and action components
<PageHeader data-testid="appointment-header">
<IconButton
data-testid="back-to-services"
icon={ChevronLeftIcon}
aria-label="Go back to services"
/>
<Heading data-testid="step-title">Select Time</Heading>
<Button data-testid="save-draft">Save Draft</Button>
</PageHeader>
// Status and feedback components
{isLoading && <Loading data-testid="booking-loader" />}
{error && (
<Alert data-testid="booking-error" variant="critical">
{error.message}
</Alert>
)}
{success && (
<StatusPanel
data-testid="booking-confirmation"
title="Booking Confirmed"
icon={CheckIcon}
/>
)}
Lists and Collections
// Dynamic lists with contextual test IDs
<div data-testid="service-list" aria-label="Available services">
{services.map((service) => (
<div key={service.id} data-testid={`service-item-${service.id}`}>
<Button
data-testid={`select-service-${service.id}`}
aria-label={`Select ${service.name}`}
>
Select {service.name}
</Button>
</div>
))}
</div>
Testing Strategies and Query Priority
React Testing Library (RTL) Query Hierarchy
React Testing Library emphasizes accessibility-first testing. Follow this priority order:
Accessibility Queries (Highest Priority)
// Prefer these queries first - they test accessibility
const submitButton = getByRole('button', { name: 'Submit' });
const emailInput = getByLabelText('Email Address');
const heading = getByRole('heading', { name: 'Login Form' });
const textbox = getByRole('textbox', { name: 'Username' });
Semantic Queries
// Use when accessibility queries aren't available
const element = getByText('Welcome back!');
const input = getByDisplayValue('user@example.com');
const element = getByTitle('Close dialog');
Test IDs (Last Resort)
// Only use when semantic queries fail or for complex scenarios
const complexComponent = getByTestId('multi-step-wizard');
const dynamicContent = getByTestId('user-dashboard-stats');
// Combine approaches for robust testing
const submitButton = getByRole('button', { name: 'Submit' }) ||
getByTestId('submit-button');
Unit Testing (React Testing Library)
// ✅ Accessibility-first testing approach
const { getByRole, getByLabelText, getByTestId } = render(<LoginForm />);
// 1. Try accessibility queries first
const submitButton = getByRole('button', { name: 'Sign In' });
const emailInput = getByLabelText('Email');
const passwordInput = getByLabelText('Password');
// 2. Fall back to test IDs for complex components
const complexWidget = getByTestId('login-form-widget');
// 3. Combine approaches for robust testing
const loginButton = getByRole('button', { name: 'Submit' }) ||
getByTestId('submit-button');
expect(submitButton).toBeInTheDocument();
Playwright/E2E Testing
// ✅ Role-based selectors (preferred)
await page.getByRole('button', { name: 'Sign In' }).click();
await page.getByLabel('Email').fill('user@example.com');
await page.getByLabel('Password').fill('password');
// ✅ Test IDs for complex scenarios
await page.locator('[data-testid="multi-step-wizard"]').isVisible();
await page.locator('[data-testid="username-input"]').fill('user@example.com');
// ✅ Combined approaches
await page.locator('[data-testid="service-list"] >> text="Haircut"').click();
await expect(page.locator('[data-testid="booking-confirmation"]')).toBeVisible();
Responsibility and Collaboration
Developer Responsibilities
While EDS components accept data-testid
and other testing attributes through consumer props, it is the product developer's responsibility to add appropriate test IDs when implementing new components or features.
Product Developers Should:
- Add
data-testid
attributes to components during development - Follow the testing hierarchy (accessibility queries first, test IDs as fallback)
- Use semantic, descriptive names for test IDs
- Consider the QA perspective when designing component APIs
- Document any complex testing scenarios for the QA team
// ✅ Developer adds test IDs during implementation
const BookingForm = () => {
return (
<form data-testid="booking-form">
<Field label="Service">
<Select
data-testid="service-selector"
options={services}
aria-label="Choose service"
/>
</Field>
<Button
data-testid="submit-booking"
type="submit"
>
Book Appointment
</Button>
</form>
);
};
QA Team Responsibilities
QA Engineers Should:
- Prioritize accessibility queries in tests (getByRole, getByLabelText)
- Collaborate with developers on test ID naming conventions
- Review component implementations for testability
- Provide feedback on missing or poorly named test IDs
- Document testing patterns for common scenarios
Collaboration Best Practices
During Feature Development:
- Planning Phase: QA and developers discuss testing requirements
- Implementation: Developers add test IDs following agreed conventions
- Review: QA reviews PR for testability and suggests improvements
- Testing: QA writes tests using accessibility-first approach
Communication Guidelines:
- Use descriptive test ID names that both teams understand
- Document complex testing scenarios in component stories or docs
- Establish team conventions for naming patterns
- Regular sync meetings to discuss testing challenges and solutions
// Example of good collaboration
// Developer implements with accessibility and testing in mind:
<Button
data-testid="confirm-payment" // Clear test ID
aria-label="Confirm payment" // Accessible label
onClick={handlePayment}
>
Pay ${amount}
</Button>
// QA can then test using hierarchy:
// 1. First try: getByRole('button', { name: 'Confirm payment' })
// 2. Fallback: getByTestId('confirm-payment')
Additional Data Attributes
Beyond data-testid
, you can use other data attributes for various purposes:
Analytics Tracking
<Button
data-testid="upgrade-button"
data-analytics="upgrade-click"
data-plan="pro"
>
Upgrade to Pro
</Button>
Custom Data
<div data-testid="booking-card" data-booking-id="123" data-status="confirmed">
<StatusPanel title="Booking Confirmed" />
</div>
Feature Flags
<Button
data-testid="new-feature-button"
data-feature="new-checkout"
data-variant="a"
>
Checkout
</Button>
Summary
- All EDS components support
data-testid
and other data attributes - Product developers are responsible for adding test IDs during implementation
- Follow RTL testing hierarchy: accessibility queries first, test IDs as fallback
- Prioritize semantic queries (getByRole, getByLabelText) over test IDs
- Establish collaboration between QA and development teams
- Focus on interactive and critical elements for test ID placement
- Use descriptive, semantic naming for test IDs
- Don't over-test static or purely presentational elements
- Leverage the full power of data attributes for analytics, feature flags, and custom data needs
The EDS provides the foundation for testable components, but successful testing requires collaboration between QA and development teams. Developers should proactively add test IDs during implementation, while QA should prioritize accessibility-first testing approaches and provide feedback on component testability.