# Code Style and Conventions Standards for Cypress
This document outlines the coding style and conventions standards for Cypress tests. Adhering to these guidelines ensures code readability, maintainability, and consistency across the project. These standards are tailored for modern Cypress development, leveraging the latest features and best practices.
## 1. General Formatting
### 1.1 Indentation
**Do This:** Use 2 spaces for indentation. Avoid tabs.
**Why:** Consistent indentation improves readability and helps prevent errors caused by misaligned code blocks.
**Example:**
"""javascript
// Correct
describe('My Test Suite', () => {
beforeEach(() => {
cy.visit('/example');
});
it('should perform an action', () => {
cy.get('[data-cy=submit-button]').click();
});
});
// Incorrect
describe('My Test Suite', () => {
beforeEach(() => {
cy.visit('/example');
});
it('should perform an action', () => {
cy.get('[data-cy=submit-button]').click();
});
});
"""
### 1.2 Line Length
**Do This:** Limit lines to a maximum of 120 characters.
**Why:** Improves readability, especially on smaller screens, and makes code easier to review.
**How:** Use your editor's settings or linters like ESLint. Break long lines into multiple shorter lines, using proper indentation.
**Example:**
"""javascript
// Correct
it('should verify that the user can successfully submit the form with valid data and is redirected to the confirmation page', () => {
cy.get('[data-cy=name-input]').type('John Doe');
cy.get('[data-cy=email-input]').type('john.doe@example.com');
cy.get('[data-cy=submit-button]').click();
cy.url().should('include', '/confirmation');
});
// Incorrect
it('should verify that the user can successfully submit the form with valid data and is redirected to the confirmation page', () => { cy.get('[data-cy=name-input]').type('John Doe'); cy.get('[data-cy=email-input]').type('john.doe@example.com'); cy.get('[data-cy=submit-button]').click(); cy.url().should('include', '/confirmation'); });
"""
### 1.3 White Space
**Do This:**
* Use blank lines to separate logical sections of code within tests and "describe" blocks.
* Add a space after commas and colons, and around operators.
**Why:** Enhances readability and visual separation, making code easier to understand.
**Example:**
"""javascript
// Correct
it('should submit the form', () => {
cy.get('[data-cy=name-input]').type('John');
cy.get('[data-cy=submit-button]').click();
cy.contains('Thank you').should('be.visible');
});
// Incorrect
it('should submit the form',()=>{
cy.get('[data-cy=name-input]').type('John');
cy.get('[data-cy=submit-button]').click();
cy.contains('Thank you').should('be.visible');
});
"""
### 1.4 Trailing Commas
**Do This:** Use trailing commas in multi-line object literals and arrays.
**Why:** Simplifies adding, removing, or reordering properties without modifying multiple lines, which also reduces diff noise during code reviews.
**Example:**
"""javascript
// Correct
const user = {
name: 'John Doe',
email: 'john.doe@example.com',
age: 30,
};
// Incorrect
const user = {
name: 'John Doe',
email: 'john.doe@example.com',
age: 30
};
"""
## 2. Naming Conventions
### 2.1 Test Suite ("describe") Names
**Do This:** Use descriptive and clear names for test suites. Start with a high-level description of the feature being tested.
**Why:** Provides immediate context about the purpose of tests, aiding in debugging and maintainability.
**Example:**
"""javascript
// Correct
describe('User Registration Form', () => { ... });
// Incorrect
describe('Tests', () => { ... }); // Vague and unhelpful
"""
### 2.2 Test Case ("it") Names
**Do This:** Follow the "Arrange-Act-Assert" pattern in your test case name, clearly stating what the test should do. Start with "should".
**Why:** Clear test case names act as documentation, quickly conveying the test's intent and expected outcome. Aids in debugging by making it easy to identify failing tests.
**Example:**
"""javascript
// Correct
it('should display an error message when the email is invalid', () => { ... });
it('should redirect to the dashboard after successful login', () => { ... });
// Incorrect
it('email test', () => { ... }); // Unclear and lacks context
it('login', () => { ... }); // Unclear and lacks context
"""
### 2.3 Selectors
**Do This:**
* Prefer "data-*" attributes for selectors.
* Use meaningful names that describe the element's purpose.
**Why:**
* "data-*" attributes are less likely to change than class names or IDs, reducing test fragility.
* Clear names make the purpose of the selector immediately obvious.
**Example:**
"""javascript
// Correct
Submit
cy.get('[data-cy=submit-button]').click();
// Less ideal (but sometimes necessary)
Submit
cy.get('#submit').click();
// Avoid
Submit
cy.get('.btn.btn-primary').click(); // Fragile and dependent on styling
"""
### 2.4 Aliases
**Do This:** Use descriptive names for aliases.
**Why:** Improves readability and helps understand what is being aliased.
**Example:**
"""javascript
// Correct
cy.get('[data-cy=username-input]').as('usernameInput');
cy.get('@usernameInput').type('john.doe');
// Incorrect
cy.get('[data-cy=username-input]').as('user'); // Unclear purpose
cy.get('@user').type('john.doe');
"""
### 2.5 Variables
**Do This:**
* Use "camelCase" for variable names.
* Use descriptive names that reflect the variable's purpose.
**Why:** Improves readability and makes the code easier to understand.
**Example:**
"""javascript
// Correct
const userName = 'John Doe';
const submitButton = '[data-cy=submit-button]';
// Incorrect
const user_name = 'John Doe'; // snake_case - not standard JavaScript
const sb = '[data-cy=submit-button]'; // Too short, unclear meaning
"""
## 3. Stylistic Consistency
### 3.1 Quotes
**Do This:** Use single quotes for string literals in Cypress tests.
**Why:** Consistent use of quotes improves readability and reduces visual clutter.
**Example:**
"""javascript
// Correct
cy.get('[data-cy=submit-button]').should('be.visible');
// Incorrect
cy.get("[data-cy=submit-button]").should("be.visible");
"""
### 3.2 Arrow Functions
**Do This:** Use arrow functions for concise and readable code, especially for callbacks. However, be mindful of "this" context within the arrow functions, especially when dealing with Mocha's "this" context.
**Why:** Arrow functions provide a more compact syntax and retain the "this" context from the surrounding code.
**Example:**
"""javascript
// Correct
it('should display a welcome message', () => {
cy.visit('/profile')
.then(() => {
cy.get('[data-cy=welcome-message]').should('be.visible');
});
});
// If you need access to "this" context:
describe('MyComponent', function() {
beforeEach(function() {
cy.fixture('example').then(function(data) {
this.data = data; // Access data using "this.data"
});
});
it('should use the fixture data', function() {
cy.get('[data-cy=name]').should('contain', this.data.name);
});
});
"""
### 3.3 Chaining Commands
**Do This:** Chain Cypress commands to improve readability and avoid unnecessary variables.
**Why:** Chaining makes the flow of your test clearer and reduces the amount of boilerplate code.
**Example:**
"""javascript
// Correct
cy.visit('/login')
.get('[data-cy=username-input]')
.type('john.doe')
.get('[data-cy=password-input]')
.type('password')
.get('[data-cy=submit-button]')
.click();
// Incorrect
cy.visit('/login');
cy.get('[data-cy=username-input]').type('john.doe');
cy.get('[data-cy=password-input]').type('password');
cy.get('[data-cy=submit-button]').click();
"""
### 3.4 Assertions
**Do This:** Use explicit assertions for clear and reliable tests. Avoid implicit assertions as much as possible.
**Why:** Explicit assertions clearly define the expected state, making tests more robust and easier to understand.
**Example:**
"""javascript
// Correct
cy.get('[data-cy=success-message]').should('be.visible').and('contain', 'Success!');
// Implicit Assertion (less clear) - Avoid relying solely on Cypress's built in retry mechanism for complex validations
cy.get('[data-cy=success-message]').contains('Success!'); // Relies on the element eventually containing 'Success!'
"""
### 3.5 Comments
**Do This:** Write clear, concise comments to explain complex logic or non-obvious code sections. Focus on "why" rather than "what".
**Why:** Improves code understanding, especially for other developers or when revisiting code after a long period.
**Example:**
"""javascript
// Correct
// Wait for the API to respond before proceeding
cy.wait('@apiResponse');
// Incorrect
cy.wait('@apiResponse'); // Wait for the API (redundant)
"""
### 3.6 Avoid Hardcoded Waits
**Do This:** Use "cy.wait('@alias')" or "cy.intercept" with aliases instead of "cy.wait(milliseconds)". If a hardcoded wait is unavoidable, document *why*.
**Why:** Hardcoded waits are brittle and can lead to flaky tests. Waiting for specific events (API calls, element visibility, etc.) makes tests more reliable.
**Example:**
"""javascript
// Correct
cy.intercept('POST', '/api/login').as('loginRequest');
cy.get('[data-cy=submit-button]').click();
cy.wait('@loginRequest');
// Incorrect
cy.get('[data-cy=submit-button]').click();
cy.wait(2000); // Hardcoded wait - unreliable
"""
## 4. Modern Approaches and Patterns
### 4.1 Page Object Model (POM)
**Do This:** Implement the Page Object Model pattern to encapsulate UI elements and interactions into reusable objects.
**Why:** POM improves code organization, reduces duplication, and makes tests easier to maintain when UI changes occur.
**Example:**
"""javascript
// page_objects/LoginPage.js
class LoginPage {
getUsernameInput() {
return cy.get('[data-cy=username-input]');
}
getPasswordInput() {
return cy.get('[data-cy=password-input]');
}
getSubmitButton() {
return cy.get('[data-cy=submit-button]');
}
login(username, password) {
this.getUsernameInput().type(username);
this.getPasswordInput().type(password);
this.getSubmitButton().click();
}
}
export default new LoginPage();
// cypress/e2e/login.cy.js
import LoginPage from '../page_objects/LoginPage';
describe('Login', () => {
it('should login with valid credentials', () => {
cy.visit('/login');
LoginPage.login('john.doe', 'password');
cy.url().should('include', '/dashboard');
});
});
"""
### 4.2 Custom Commands
**Do This:** Create custom Cypress commands for reusable actions and assertions.
**Why:** Custom commands simplify test code, improve readability, and reduce duplication.
**Example:**
"""javascript
// cypress/support/commands.js
Cypress.Commands.add('login', (username, password) => {
cy.visit('/login');
cy.get('[data-cy=username-input]').type(username);
cy.get('[data-cy=password-input]').type(password);
cy.get('[data-cy=submit-button]').click();
});
// cypress/e2e/dashboard.cy.js
describe('Dashboard', () => {
it('should display the dashboard after login', () => {
cy.login('john.doe', 'password');
cy.url().should('include', '/dashboard');
});
});
"""
### 4.3 Fixtures
**Do This:** Use fixtures to manage test data effectively.
**Why:** Fixtures provide a structured way to store and reuse test data, making tests more maintainable and readable.
**Example:**
"""json
// cypress/fixtures/user.json
{
"username": "john.doe",
"password": "password"
}
// cypress/e2e/login.cy.js
describe('Login', () => {
it('should login with valid credentials', () => {
cy.fixture('user').then((user) => {
cy.visit('/login');
cy.get('[data-cy=username-input]').type(user.username);
cy.get('[data-cy=password-input]').type(user.password);
cy.get('[data-cy=submit-button]').click();
cy.url().should('include', '/dashboard');
});
});
});
"""
### 4.4 "cy.intercept()" for Network Stubbing
**Do This:** Use "cy.intercept()" to stub API responses and control network behavior effectively. This is preferred over "cy.route()" which is being deprecated.
**Why:** "cy.intercept()" provides a powerful and flexible way to mock API responses, simulate different scenarios, and test edge cases without relying on actual backend systems.
**Example:**
"""javascript
// cypress/e2e/product.cy.js
describe('Product Details', () => {
it('should display product details', () => {
cy.intercept('GET', '/api/products/123', { fixture: 'product.json' }).as('getProduct');
cy.visit('/products/123');
cy.wait('@getProduct'); // Wait for the API call to complete
cy.get('[data-cy=product-name]').should('contain', 'Awesome Product');
cy.get('[data-cy=product-price]').should('contain', '$99.99');
});
it('should handle API errors gracefully', () => {
cy.intercept('GET', '/api/products/123', { statusCode: 500, body: { error: 'Internal Server Error' } }).as('getProductError');
cy.visit('/products/123');
cy.wait('@getProductError');
cy.get('[data-cy=error-message]').should('be.visible').and('contain', 'Failed to load product details.');
});
});
"""
### 4.5 Environment Variables
**Do This:** Use Cypress environment variables for configuration and sensitive data.
**Why:** Environment variables allow you to configure your tests for different environments (development, staging, production) without modifying the code. They also help to keep sensitive information (e.g., API keys) out of your codebase.
**Example:**
"""javascript
// cypress.config.js or cypress.json
module.exports = {
env: {
apiUrl: 'https://api.example.com',
},
};
// cypress/e2e/api.cy.js
describe('API Tests', () => {
it('should fetch data from the API', () => {
cy.request("${Cypress.env('apiUrl')}/data").then((response) => {
expect(response.status).to.eq(200);
});
});
});
"""
## 5. Common Anti-Patterns
### 5.1 Excessive Use of "force: true"
**Don't Do This:** Rely on "force: true" to interact with elements that are not visible or interactable.
**Why:** Using "force: true" masks underlying issues in your application, such as incorrect styling, element overlap, or improper event handling. It leads to tests that pass even when the application is not behaving correctly from a user perspective.
**Do This:** Fix the underlying issue in the application to make the element naturally interactable. If "force: true" is absolutely necessary (e.g., testing hidden functionality), document clearly *why* it is needed.
### 5.2 Overly Long Tests
**Don't Do This:** Write tests that perform too many actions or cover too many scenarios.
**Why:** Overly long tests are difficult to read, debug, and maintain. They also violate the principle of testing one thing at a time.
**Do This:** Break down long tests into smaller, more focused tests that each verify a specific behavior.
### 5.3 Testing Implementation Details
**Don't Do This:** Write tests that depend on the specific implementation details of the UI (e.g., exact CSS class names) rather than the behavior.
**Why:** Tests that rely on implementation details are brittle and will break whenever the implementation changes, even if the behavior remains the same.
**Do This:** Focus on testing the user-visible behavior of the application, using "data-*" attributes or other semantic selectors.
### 5.4 Not Using "beforeEach" or "before" Properly
**Don't Do This:** Repeat setup steps (e.g., visiting a page, logging in) in every test case.
**Why:** Duplicates code, making tests harder to maintain and read.
**Do This:** Use "beforeEach" to perform setup steps that are common to all tests within a "describe" block. Use "before" for setup that should only run once at the beginning of the suite.
## 6. Security Best Practices
### 6.1 Protect Sensitive Information
**Do This:** Store sensitive information such as passwords, API keys, and tokens in environment variables rather than directly in the test code or fixtures.
**Why:** Prevents accidental exposure of sensitive information in your codebase or version control system.
### 6.2 Avoid Testing Production Data Directly
**Don't Do This:** Run tests directly against production databases or APIs with real user data.
**Why:** Can lead to unintended data corruption or exposure of sensitive information.
**Do This:** Use test environments with dedicated test data for automated testing.
### 6.3 Sanitize Inputs
**Do This:** Sanitize and validate inputs when testing forms and APIs to prevent injection attacks or other security vulnerabilities.
**Example:**
"""javascript
it('should prevent XSS attacks', () => {
const maliciousInput = '';
cy.get('[data-cy=input-field]').type(maliciousInput);
cy.get('[data-cy=submit-button]').click();
cy.get('[data-cy=output-field]').should('not.contain', maliciousInput); // Verify the output is sanitized
});
"""
By following these coding style and convention standards, you can create more maintainable, readable, and reliable Cypress tests. Using linters and code formatters as part of your CI/CD pipeline can automate adherence to these standards.
danielsogl
Created Mar 6, 2025
This guide explains how to effectively use .clinerules
with Cline, the AI-powered coding assistant.
The .clinerules
file is a powerful configuration file that helps Cline understand your project's requirements, coding standards, and constraints. When placed in your project's root directory, it automatically guides Cline's behavior and ensures consistency across your codebase.
Place the .clinerules
file in your project's root directory. Cline automatically detects and follows these rules for all files within the project.
# Project Overview project: name: 'Your Project Name' description: 'Brief project description' stack: - technology: 'Framework/Language' version: 'X.Y.Z' - technology: 'Database' version: 'X.Y.Z'
# Code Standards standards: style: - 'Use consistent indentation (2 spaces)' - 'Follow language-specific naming conventions' documentation: - 'Include JSDoc comments for all functions' - 'Maintain up-to-date README files' testing: - 'Write unit tests for all new features' - 'Maintain minimum 80% code coverage'
# Security Guidelines security: authentication: - 'Implement proper token validation' - 'Use environment variables for secrets' dataProtection: - 'Sanitize all user inputs' - 'Implement proper error handling'
Be Specific
Maintain Organization
Regular Updates
# Common Patterns Example patterns: components: - pattern: 'Use functional components by default' - pattern: 'Implement error boundaries for component trees' stateManagement: - pattern: 'Use React Query for server state' - pattern: 'Implement proper loading states'
Commit the Rules
.clinerules
in version controlTeam Collaboration
Rules Not Being Applied
Conflicting Rules
Performance Considerations
# Basic .clinerules Example project: name: 'Web Application' type: 'Next.js Frontend' standards: - 'Use TypeScript for all new code' - 'Follow React best practices' - 'Implement proper error handling' testing: unit: - 'Jest for unit tests' - 'React Testing Library for components' e2e: - 'Cypress for end-to-end testing' documentation: required: - 'README.md in each major directory' - 'JSDoc comments for public APIs' - 'Changelog updates for all changes'
# Advanced .clinerules Example project: name: 'Enterprise Application' compliance: - 'GDPR requirements' - 'WCAG 2.1 AA accessibility' architecture: patterns: - 'Clean Architecture principles' - 'Domain-Driven Design concepts' security: requirements: - 'OAuth 2.0 authentication' - 'Rate limiting on all APIs' - 'Input validation with Zod'
# Security Best Practices Standards for Cypress This document outlines security best practices for writing Cypress tests. It provides specific guidelines, code examples, and explanations to help developers create secure and reliable end-to-end tests. Following these standards minimizes the risk of introducing vulnerabilities into your application during testing. ## 1. General Security Principles ### 1.1. Input Validation and Sanitization **Standard:** Always validate and sanitize any data you use in your Cypress tests, especially if it comes from the application under test or external sources. **Why:** Prevents common injection attacks, such as Cross-Site Scripting (XSS) and SQL Injection, that could be triggered by malicious data included in the application. **Do This:** * Use Cypress commands to assert against expected values instead of directly manipulating data that could be used in selectors or "cy.request" calls. * Use "cy.intercept" to control and validate request and response data, preventing the test from being affected by unexpected or malicious data from the server. **Don't Do This:** * Directly inject unsanitized data into selectors using string concatenation or template literals. * Rely on the application to sanitize data before using it in tests. **Code Example:** """javascript // Good: Validate the response from the server it('should display the correct username', () => { cy.intercept('GET', '/api/user').as('getUser'); cy.visit('/profile'); cy.wait('@getUser').then((interception) => { expect(interception.response.statusCode).to.equal(200); expect(interception.response.body.username).to.be.a('string'); cy.get('[data-testid="username"]').should('contain', interception.response.body.username); }); }); // Bad: Directly using the response to construct a selector - XSS vulnerability it('should display the correct username (INSECURE)', () => { cy.intercept('GET', '/api/user').as('getUser'); cy.visit('/profile'); cy.wait('@getUser').then((interception) => { const username = interception.response.body.username; cy.get("[data-username="${username}"]").should('be.visible'); //Potential XSS vulnerability if username contains malicious code }); }); """ ### 1.2. Principle of Least Privilege **Standard:** Run Cypress tests with the minimum required privileges. **Why:** Limits the potential damage if a test is compromised or contains a vulnerability. **Do This:** * Use dedicated test accounts with minimal permissions to interact with the application under test. * Avoid using administrator or superuser accounts in your tests. **Don't Do This:** * Run Cypress tests with highly privileged accounts. **Code Example:** """javascript // Good: Logging in with a test user that has limited access it('should allow a basic user to view their profile', () => { cy.visit('/login'); cy.get('#username').type('testuser'); cy.get('#password').type('password'); cy.get('#login').click(); cy.url().should('include', '/profile'); }); // Avoid using admin accounts in tests. """ ### 1.3. Secure Data Storage **Standard:** Avoid storing sensitive data (e.g., passwords, API keys) directly in Cypress tests or configuration files. Use environment variables for confidential information. **Why:** Protects sensitive data from being exposed in version control systems or test reports. **Do This:** * Use environment variables to store sensitive data. Retrieve these values within your Cypress tests and configurations. **Don't Do This:** * Hardcode sensitive credentials within your test files or configuration files. * Commit sensitive data to version control. **Code Example:** """javascript // cypress.config.js module.exports = { env: { username: process.env.TEST_USERNAME, password: process.env.TEST_PASSWORD } }; // Cypress test it('should login with credentials from environment variables', () => { cy.visit('/login'); cy.get('#username').type(Cypress.env('username')); cy.get('#password').type(Cypress.env('password')); cy.get('#login').click(); cy.url().should('include', '/dashboard'); }); """ ### 1.4. Clear Sensitive Data **Standard:** For tests dealing with sensitive user information or PII (Personally Identifiable Information), ensure that data is cleared or reset after the test completes. **Why:** Prevents leakage of sensitive information, even if the user is fictional or the data is for testing purposes only. Ensures compliance with privacy regulations. **Do This:** * If possible, delete the test user or data created during the test using API calls or database cleanup scripts triggered by "after" or "afterEach" hooks. **Don't Do This:** * Leave test data in the system, especially if it contains sensitive information. **Code Example:** """javascript // Cypress test describe('User profile tests', () => { let userId; beforeEach(() => { // Create a test user cy.request('POST', '/api/users', { username: 'testuser', email: 'test@example.com' }) .then((response) => { userId = response.body.id; }); }); it('should update user profile', () => { // Test code here cy.visit('/profile'); cy.get('#edit-profile').click(); cy.get('#email').clear().type('newemail@example.com'); cy.get('#save-profile').click(); cy.contains('Profile updated successfully').should('be.visible'); }); afterEach(() => { // Delete the test user after the test cy.request('DELETE', "/api/users/${userId}"); }); }); """ ## 2. Specific Cypress Security Practices ### 2.1. Preventing Cross-Site Scripting (XSS) **Standard:** Be extremely cautious when injecting data into selectors, especially data retrieved from the application. **Why:** Prevents malicious code from being injected into your application through your tests. **Do This:** * Use parameterized queries or prepared statements when interacting with databases via "cy.request". * Encode any data displayed in your application that comes from external sources. **Don't Do This:** * Directly use unsanitized data in selectors, as it can create vulnerabilities. **Code Example:** """javascript //Good: Safely using data in selectors with assertions ONLY. it('should show user in table', () => { cy.intercept('GET', '/api/user').as('getUser'); cy.visit('/users'); cy.wait('@getUser').then((interception) => { const username = interception.response.body.username; cy.get('[data-testid="user-table"]').should('contain', username); // Assertion is safe }); }); // Bad: Building a selector with potentially malicious data it('should show user in table (INSECURE)', () => { cy.intercept('GET', '/api/user').as('getUser'); cy.visit('/users'); cy.wait('@getUser').then((interception) => { const username = interception.response.body.username; cy.get("#user-${username}").should('be.visible'); // Vulnerable to injection }); }); """ ### 2.2. Avoiding SQL Injection **Standard:** When using "cy.request" to interact with APIs that might execute SQL queries, ensure the data you send is properly sanitized and validated. **Why:** Prevents attackers from manipulating database queries through your tests, leading to data breaches or unauthorized access. **Do This:** * Utilize parameterized queries when constructing your "cy.request" parameters, especially when creating test data through API calls. * If directly sending SQL through test setup (development/test environment only), always use parameterized queries. **Don't Do This:** * Directly concatenate user-provided input into SQL queries within API calls. This practice is dangerous in ANY environment, even test. **Code Example:** """javascript // Good: Using parameterized queries in the API (Backend implementation) it('should create a new user via API (Parameterized)', () => { cy.request('POST', '/api/users', { username: 'safeuser', email: 'safeuser@example.com', }).then((response) => { expect(response.status).to.eq(201); }); }); // Demonstrating parameterized queries in test setup (SQL injection prevention) - Example only, not directly Cypress code // Backend code (Node.js with a hypothetical database library) /* const createUser = (username, email) => { const query = 'INSERT INTO users (username, email) VALUES (?, ?)'; // Parameterized query db.query(query, [username, email], (err, results) => { if (err) { console.error(err); } else { console.log(results); } }); }; // Calling the function safely: createUser('safeuser', 'safeuser@example.com'); */ // Bad: Directly concatenating user input into SQL queries (SQL injection vulnerability) - NEVER DO THIS // (Illustrative example of what NOT to do, NOT Cypress code) /* // Vulnerable backend code (Node.js with a hypothetical database library) const createUser = (username, email) => { const query = "INSERT INTO users (username, email) VALUES ('${username}', '${email}')"; // SQL injection vulnerability! db.query(query, (err, results) => { if (err) { console.error(err); } else { console.log(results); } }); }; // NEVER DO THIS: createUser("'; DROP TABLE users; --", "bad@example.com"); // SQL injection attack! */ """ ### 2.3. Handling Sensitive Information in Logs and Reports **Standard:** Prevent sensitive information from being accidentally logged or included in test reports. **Why:** Reduces the risk of sensitive data exposure if reports or logs are compromised. **Do This:** * Avoid logging sensitive data in Cypress tests. Use "cy.log" with caution. Sanitize any sensitive data before logging if it is absolutely necessary. * Configure CI/CD pipelines to securely store test reports and prevent unauthorized access. **Don't Do This:** * Log sensitive data such as passwords, API keys, or PII. * Expose test reports to the public or store them in insecure locations. **Code Example:** """javascript //Good: Avoid logging sensitive data it('should login successfully (no sensitive data in logs)', () => { cy.visit('/login'); cy.get('#username').type(Cypress.env('username')); cy.get('#password').type(Cypress.env('password')); cy.get('#login').click(); cy.url().should('include', '/dashboard'); cy.log('Login successful'); // General log, no sensitive data }); // Bad: Logging sensitive data directly (AVOID!) it('should login successfully (INSECURE)', () => { cy.visit('/login'); const username = Cypress.env('username'); const password = Cypress.env('password'); cy.get('#username').type(username); cy.get('#password').type(password); cy.get('#login').click(); cy.url().should('include', '/dashboard'); cy.log("Username: ${username}, Password: ${password}"); // Sensitive data in logs - BAD! }); """ ### 2.4. Cross-Origin Resource Sharing (CORS) **Standard:** Understand and correctly configure CORS when testing applications that interact with different domains. **Why:** Prevents unexpected behavior and security risks when your Cypress tests make requests to external resources. **Do This:** * Ensure your application's CORS configuration is appropriate for your testing environment. Consider loosening CORS restrictions in test environments if necessary, but NEVER in production. * Use "cy.request" to bypass the browser's CORS restrictions when making API calls directly in your tests. This is ONLY acceptable for testing purposes. The application itself *must* have proper CORS. * Use "cy.origin" to interact with different origins in your tests, while being aware of its limitations. **Don't Do This:** * Disable CORS completely in your application's production environment to make testing easier. This defeats the entire purpose of CORS as a security feature. * Rely on browser extensions to disable CORS, as this can lead to inconsistent test results and security risks. **Code Example:** """javascript // Good: Using cy.request to bypass CORS for API testing in the test environment. it('should fetch data from a different origin via API', () => { cy.request('https://api.example.com/data') .then((response) => { expect(response.status).to.eq(200); expect(response.body).to.not.be.empty; }); }); // Good - Using cy.origin to visit a different origin - Cypress v12+ it('should visit a different origin and interact with elements', () => { cy.visit('https://www.example.com'); cy.origin('https://www.google.com', () => { cy.visit('/'); cy.get("input[name='q']").type('cypress{enter}'); cy.get('#search').should('be.visible'); }); }); // Bad: Disabling webSecurity (NOT RECOMMENDED, only for specific testing scenarios) which can be a HUGE security risk // cypress.config.js - AVOID UNLESS ABSOLUTELY NECESSARY FOR LEGACY SYSTEMS /* module.exports = { e2e: { setupNodeEvents(on, config) { // implement node event listeners here }, webSecurity: false, // Disables web security, including CORS protection. AVOID! }, }; */ """ ### 2.5. Authentication and Authorization Testing **Standard:** Thoroughly test authentication and authorization mechanisms in your application using Cypress, but protect credentials as described above. **Why:** Ensures that only authorized users can access specific resources and functionalities. **Do This:** * Use "cy.request" to programmatically log in and obtain authentication tokens before starting your tests. * Validate that unauthorized users are correctly denied access to protected resources. * Test different roles and permissions to ensure that the authorization logic is working correctly. * Test for common authentication vulnerabilities, such as brute-force attacks and session hijacking. * When using "cy.session", ensure the cache is cleared between test suites where appropriate to prevent state leakage. Consider its security implications carefully. **Don't Do This:** * Rely solely on UI-based authentication for testing your application's security. * Bypass authentication mechanisms in your tests, as this can lead to false positives. **Code Example:** """javascript // Good: Programmatically logging in and testing authorization describe('Authorization Tests', () => { let authToken; before(() => { // Programmatically log in and obtain an authentication token cy.request({ method: 'POST', url: '/api/login', body: { username: Cypress.env('username'), password: Cypress.env('password'), }, }).then((response) => { expect(response.status).to.eq(200); authToken = response.body.token; }); }); it('should allow authorized users to access protected resources', () => { cy.request({ method: 'GET', url: '/api/protected', headers: { Authorization: "Bearer ${authToken}", }, }).then((response) => { expect(response.status).to.eq(200); expect(response.body.message).to.eq('Access granted'); }); }); it('should deny unauthorized users from accessing protected resources', () => { cy.request({ method: 'GET', url: '/api/protected', failOnStatusCode: false, }).then((response) => { expect(response.status).to.eq(401); // Or appropiate error code expect(response.body.error).to.eq('Unauthorized'); }); }); }); // Good: Using cy.session - Ensure session is cleared if needed describe('User profile tests with session', () => { beforeEach(() => { cy.session('user', () => { // Unique key name cy.visit('/login'); cy.get('#username').type(Cypress.env('username')); cy.get('#password').type(Cypress.env('password')); cy.get('#login').click(); cy.url().should('include', '/dashboard'); }); cy.visit('/dashboard'); // Now dashboard is accessible via the session }); it('should display the user profile', () => { cy.get('#profile-name').should('be.visible'); }); // Optional: clear session to avoid conflicts with other test suites potentially using login. after(() => { // Uncomment the code below if you are getting "cy.session() already exists" error // localStorage.clear() // sessionStorage.clear() // cy.clearCookies() // cy.clearLocalStorage() // cy.window().then((win) => { // win.sessionStorage.clear() }); }); """ ### 2.6. Preventing Clickjacking **Standard:** Verify that your application implements defenses against clickjacking attacks. **Why:** Protects users from being tricked into performing actions they did not intend to perform. **Do This:** * Check for the presence of the "X-Frame-Options" header and ensure it is set to "DENY" or "SAMEORIGIN". * Test for the "Content-Security-Policy" header and verify that it restricts framing to trusted origins. **Don't Do This:** * Assume that your application is protected against clickjacking without proper verification. * Use deprecated methods for clickjacking protection. **Code Example:** """javascript // Checking X-Frame-Options header it('should protect against clickjacking with X-Frame-Options', () => { cy.request({ url: '/', // Replace with your application's URL failOnStatusCode: false, }).then((response) => { expect(response.headers['x-frame-options']).to.be.oneOf(['DENY', 'SAMEORIGIN']); }); }); // Checking Content-Security-Policy header for frame-ancestors it('should protect against clickjacking with Content-Security-Policy', () => { cy.request({ url: '/', // Replace with your application's URL failOnStatusCode: false, }).then((response) => { const csp = response.headers['content-security-policy']; expect(csp).to.include("frame-ancestors 'self'"); // Adjust as needed }); }); """ ### 2.7. Secure File Uploads **Standard:** If your application allows file uploads, thoroughly test the security of the upload process. **Why:** Prevents malicious files from being uploaded and executed on your server. **Do This:** * Verify that the server-side validates file types, sizes, and contents. * Test for directory traversal vulnerabilities by attempting to upload files with malicious filenames. * Ensure that uploaded files are stored in a secure location outside of the webroot. **Don't Do This:** * Rely solely on client-side validation for file uploads. * Allow users to upload executable files without proper security measures. * Store uploaded files in the same directory as your application. **Code Example:** """javascript // Good Example - Test file validation. it('should allow uploading of a valid image file', () => { cy.visit('/upload'); const filePath = 'images/valid-image.png'; // Ensure this file exists in your fixtures. cy.get('input[type="file"]').attachFile(filePath); cy.get('#upload-button').click(); cy.contains('File uploaded successfully').should('be.visible'); }); it('should reject uploading of a malicious file', () => { cy.visit('/upload'); const filePath = 'files/malicious.php'; // Attempt to upload a PHP file cy.get('input[type="file"]').then(($input) => { // Programmatically trigger the file selection event const blob = new Blob(['<?php echo shell_exec($_GET["cmd"]); ?>'], { type: 'application/x-php' }); // Malicious PHP Content const file = new File([blob], 'malicious.php', { type: 'application/x-php' }); const dataTransfer = new DataTransfer(); dataTransfer.items.add(file); $input[0].files = dataTransfer.files; }); cy.get('#upload-button').click(); cy.contains('Invalid file type').should('be.visible'); }); """ ### 2.8. Timeout Management **Standard:** Set appropriate timeouts for Cypress commands to prevent tests from hanging indefinitely. **Why:** Prevents tests from running for excessive time, which can waste resources and hide potential performance issues. **Do This:** * Use the "timeout" option to set explicit timeouts for Cypress commands. * Adjust timeouts based on the expected response time of your application. **Don't Do This:** * Set extremely long timeouts for all commands, as this can mask performance problems. * Rely on the default Cypress timeout, which may not be appropriate for all scenarios. **Code Example:** """javascript it('should load data within a reasonable time', () => { cy.visit('/data'); cy.get('#data-table', { timeout: 10000 }).should('be.visible'); // Set an extended timeout of 10 seconds }); """ ## 3. Continuous Integration/Continuous Deployment (CI/CD) Security ### 3.1. Secure Pipeline Configuration **Standard:** Harden your CI/CD pipelines to prevent injection attacks and unauthorized access to test data. **Why:** Your CI/CD pipeline is an integral part of your SDLC. Any compromise here risks all of your processes. **Do This:** * Employ secure coding practices when defining CI/CD configurations. * Use environment variables to mask configuration data. **Don't Do This:** * Commit sensitive data directly into configuration files. ### 3.2. Vulnerability Scanning **Standard:** Regularly scan your Cypress tests and dependencies for known vulnerabilities. **Why:** Identify and address security risks in your test code and third-party libraries. **Do This:** * Integrate vulnerability scanning tools into your CI/CD pipeline. * Update your Cypress dependencies regularly to patch security vulnerabilities. **Don't Do This:** * Ignore vulnerability warnings or postpone security updates. ## 4. Ongoing Security Awareness ### 4.1 Training and Documentation **Standard:** Provide ongoing security training to Cypress developers and maintain up-to-date security documentation. **Why:** Keeps developers informed about the latest security threats and best practices. **Do This:** * Conduct regular security training sessions for your development team. * Maintain a comprehensive security guide for Cypress testing. **Don't Do This:** * Assume that developers are aware of all security best practices. * Neglect to update your security documentation as new threats emerge. ### 4.2. Code Reviews **Standard:** Implement thorough code reviews to identify potential security vulnerabilities early in the development process. **Why:** Harness collective brainpower to uncover vulnerabilities. **Do This:** * Mandate security-focused code reviews for all Cypress tests. * Use automated code analysis tools to assist with code reviews. **Don't Do This:** * Skip code reviews or rush through the process. By implementing these security best practices, you can create more secure and reliable Cypress tests, reducing the risk of introducing vulnerabilities into your application. This document should be reviewed and updated regularly to reflect the latest security threats and best practices.
# Testing Methodologies Standards for Cypress This document outlines the testing methodologies standards for Cypress, focusing on strategies for unit, integration, and end-to-end (E2E) testing. These standards aim to ensure maintainable, performant, and secure Cypress tests. ## 1. General Testing Principles ### 1.1. Test Pyramid **Standard:** Adhere to the test pyramid principles. * **Do This:** Have more unit tests, fewer integration tests, and even fewer E2E tests. * **Don't Do This:** Rely solely on E2E tests, as they are slower and more brittle. **Why:** The test pyramid helps balance test coverage with execution speed and maintainability. Excessive E2E tests can lead to slow test suites and increased maintenance costs. **Code Example (Conceptual):** """javascript // This is a conceptual example, not directly Cypress code. // Imagine a feature requiring comprehensive testing. // Unit Tests: 100+ (Focus on individual components) // Integration Tests: 20-30 (Focus on component interactions) // E2E Tests: 5-10 (Focus on critical user flows) """ ### 1.2. Test-Driven Development (TDD) **Standard:** Consider adopting TDD for new features. * **Do This:** Write failing tests *before* implementing the associated code. * **Don't Do This:** Write code first and tests after. **Why:** TDD promotes better code design, reduces the risk of untested code, and leads to higher test coverage. **Code Example (Conceptual):** 1. Write a failing unit test for a function that validates email format. 2. Implement the email validation function to pass the test. 3. Refactor the code, keeping the test passing. ### 1.3. Behavior-Driven Development (BDD) **Standard:** Use BDD principles to describe test scenarios from a user's perspective. * **Do This:** Write tests that describe the desired behavior of the application, using scenarios with "Given", "When", "Then" blocks. * **Don't Do This:** Write tests that only focus on technical implementation details. **Why:** BDD improves communication between developers, testers, and stakeholders by focusing on the "what" rather than the "how." Cypress's syntax naturally supports BDD. **Code Example:** """javascript // cypress/e2e/login.cy.js describe('Login Functionality', () => { it('allows a user to log in with valid credentials', () => { // Given (Arrange) cy.visit('/login'); // When (Act) cy.get('#username').type('valid_user'); cy.get('#password').type('valid_password'); cy.get('button[type="submit"]').click(); // Then (Assert) cy.url().should('include', '/dashboard'); cy.contains('Welcome, valid_user').should('be.visible'); }); it('displays an error message with invalid credentials', () => { // Given cy.visit('/login'); // When cy.get('#username').type('invalid_user'); cy.get('#password').type('invalid_password'); cy.get('button[type="submit"]').click(); // Then cy.contains('Invalid credentials').should('be.visible'); }); }); """ ## 2. Cypress and Testing Levels ### 2.1. Unit Testing with Cypress (Limited) **Standard:** Primarily use Cypress for integration and E2E testing, and use other tools like Mocha/Chai/Jest for unit tests. * **Do This:** If absolutely necessary, use Cypress "cy.wrap()" to test utility functions or individual components in isolation. * **Don't Do This:** Use Cypress as your primary unit testing framework. It's overkill and slower than dedicated unit testing tools. **Why:** Cypress is designed for browser-based testing, making it less efficient for pure unit testing, which should focus on isolated functions or components. **Code Example (Discouraged):** """javascript // cypress/e2e/utility.cy.js (BAD PRACTICE) //Testing a function that is not related to user flow. Using a simple "assert" is quicker describe('Utility Function Tests', () => { it('validates email format', () => { const isValidEmail = (email) => { // Simplified email validation logic return email.includes('@') && email.includes('.'); }; cy.wrap(isValidEmail('test@example.com')).should('be.true'); cy.wrap(isValidEmail('testexample.com')).should('be.false'); }); }); """ **Better alternative using Mocha's "assert":** """javascript // test/unit/utility.spec.js (using Mocha/Chai) const assert = require('chai').assert; describe('Utility Function Tests', () => { it('validates email format', () => { const isValidEmail = (email) => { return email.includes('@') && email.includes('.'); }; assert.isTrue(isValidEmail('test@example.com')); assert.isFalse(isValidEmail('testexample.com')); }); }); """ ### 2.2. Integration Testing with Cypress **Standard:** Use Cypress to test interactions between components and services. * **Do This:** Mock API calls using "cy.intercept()" to control the data flow and isolate components. * **Don't Do This:** Rely on real APIs for all integration tests, as this can lead to flaky tests and dependencies on external services. **Why:** Integration tests verify that different parts of the application work together correctly. Mocking allows you to test specific scenarios without relying on the availability or stability of external APIs. **Code Example:** """javascript // cypress/e2e/integration.cy.js describe('User List Integration', () => { beforeEach(() => { cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers'); //using fixture data cy.visit('/users'); }); it('displays a list of users from a mocked API', () => { cy.wait('@getUsers'); cy.get('.user-list-item').should('have.length', 3); // Assuming users.json has 3 users }); it('handles an error when fetching users', () => { cy.intercept('GET', '/api/users', { statusCode: 500 }).as('getUsers'); cy.visit('/users'); cy.wait('@getUsers'); cy.contains('Error fetching users').should('be.visible'); }); }); """ **Anti-pattern:** Using "cy.wait(5000)" instead of "cy.wait('@getUsers')". The explicit wait ties test execution to a specific amount of time. If the API call takes longer, tests fail, giving the false impression that the UI has an issue. ### 2.3. End-to-End (E2E) Testing with Cypress **Standard:** Use Cypress to test critical user flows from start to finish. * **Do This:** Focus on key scenarios like login, checkout, and data submission. * **Don't Do This:** Attempt to cover every possible user interaction with E2E tests. **Why:** E2E tests simulate real user behavior, verifying that the application functions correctly across all layers. However, they are more time-consuming to write and maintain. **Code Example:** """javascript // cypress/e2e/checkout.cy.js describe('Checkout Flow', () => { it('allows a user to complete the checkout process', () => { // Visit the product page cy.visit('/product/123'); // Add the product to the cart cy.get('button[data-testid="add-to-cart"]').click(); // Go to cart cy.visit('/cart'); // Proceed to checkout cy.get('button[data-testid="checkout"]').click(); // Fill in shipping information cy.get('#shipping-address').type('123 Main St'); cy.get('#city').type('Anytown'); cy.get('#zip').type('12345'); // Proceed to payment cy.get('button[data-testid="continue-to-payment"]').click(); // Fill in payment information cy.get('#credit-card').type('1111222233334444'); cy.get('#expiration-date').type('12/24'); cy.get('#cvv').type('123'); // Submit the order cy.get('button[data-testid="submit-order"]').click(); // Verify order confirmation cy.contains('Order Confirmation').should('be.visible'); cy.contains('Thank you for your order!').should('be.visible'); }); }); """ ## 3. Cypress Modern Approaches and Patterns ### 3.1. Page Object Model (POM) **Standard:** Implement the Page Object Model to abstract UI elements and interactions. * **Do This:** Create separate classes for each page or component, containing selectors and methods for interacting with elements. * **Don't Do This:** Directly embed selectors and interactions within test files. **Why:** POM improves test maintainability by centralizing UI element definitions. Changes to the UI only require updates in the page object, not in every test referencing that element. **Code Example:** """javascript // cypress/support/page_objects/LoginPage.js class LoginPage { getUsernameField() { return cy.get('#username'); } getPasswordField() { return cy.get('#password'); } getSubmitButton() { return cy.get('button[type="submit"]'); } login(username, password) { this.getUsernameField().type(username); this.getPasswordField().type(password); this.getSubmitButton().click(); } getErrorMessage() { return cy.contains('Invalid credentials'); } } export default LoginPage; """ """javascript // cypress/e2e/login.cy.js import LoginPage from '../../support/page_objects/LoginPage'; describe('Login Functionality', () => { const loginPage = new LoginPage(); //instantiate a new instance of the LoginPage obj. it('allows a user to log in with valid credentials', () => { cy.visit('/login'); loginPage.login('valid_user', 'valid_password'); cy.url().should('include', '/dashboard'); }); it('displays an error message with invalid credentials', () => { cy.visit('/login'); loginPage.login('invalid_user', 'invalid_password'); loginPage.getErrorMessage().should('be.visible'); }); }); """ ### 3.2. Custom Commands **Standard:** Create custom Cypress commands to encapsulate reusable actions. * **Do This:** Define custom commands in "cypress/support/commands.js" for common tasks like logging in, creating users, or navigating to specific pages. * **Don't Do This:** Duplicate code across multiple tests when a single custom command could handle it. **Why:** Custom commands reduce code duplication and make tests more readable. **Code Example:** """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(); }); """ """javascript // cypress/e2e/dashboard.cy.js describe('Dashboard', () => { it('displays the dashboard after successful login', () => { cy.login('valid_user', 'valid_password'); cy.url().should('include', '/dashboard'); }); }); """ ### 3.3. Data Fixtures **Standard:** Use data fixtures for test data. * **Do This:** Store test data in JSON files within the "cypress/fixtures" directory. * **Don't Do This:** Hardcode test data directly in test files. **Why:** Data fixtures improve test readability and maintainability. They also allow you to easily switch between different data sets. **Code Example:** """javascript // cypress/fixtures/user.json { "username": "test_user", "password": "test_password" } """ """javascript // cypress/e2e/login.cy.js describe('Login Functionality', () => { it('allows a user to login with valid credentials using fixture data', () => { cy.fixture('user').then((user) => { cy.visit('/login'); cy.get('#username').type(user.username); cy.get('#password').type(user.password); cy.get('button[type="submit"]').click(); cy.url().should('include', '/dashboard'); }); }); }); """ ### 3.4. Using "cy.session()" (Cypress 10+) **Standard:** Use "cy.session()" to cache and reuse sessions across multiple tests. * **Do This:** Define sessions for authentication or other expensive operations. * **Don't Do This:** Login before *every* test, as this significantly increases test execution time. **Why:** "cy.session()" drastically improve test performance by avoiding redundant setup steps. **Code Example:** """javascript // cypress/e2e/session.cy.js describe('Session Management', () => { beforeEach(() => { cy.session('login', () => { cy.visit('/login'); cy.get('#username').type('valid_user'); cy.get('#password').type('valid_password'); cy.get('button[type="submit"]').click(); cy.url().should('include', '/dashboard'); }); }); it('displays the dashboard for an authenticated user', () => { cy.visit('/dashboard'); cy.contains('Welcome, valid_user').should('be.visible'); }); it('allows the user to view profile information', () => { cy.visit('/profile'); cy.contains('Profile Information').should('be.visible'); }); }); """ ### 3.5. Component Testing with Cypress **Standard:** Use modern Cypress component testing capabilities. * **Do This:** Write specific component tests in isolation to verify that each component functions correctly. Use "cy.mount()" or similar function to achieve this. * **Don't Do This:** Rely solely on end-to-end tests to verify the behavior of smaller components. **Why:** Component testing can greatly improve confidence and the granular nature of debugging. **Code Example:** """javascript // src/components/Button.jsx import React from 'react'; function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); } export default Button; """ """javascript // cypress/component/Button.cy.js import Button from '../../src/components/Button'; describe('Button Component', () => { it('renders a button with the correct text', () => { cy.mount(<Button>Click Me</Button>); cy.get('button').should('contain', 'Click Me'); }); it('calls the onClick handler when clicked', () => { const onClickSpy = cy.spy().as('onClick'); cy.mount(<Button onClick={onClickSpy}>Click Me</Button>); cy.get('button').click(); cy.get('@onClick').should('have.been.called'); }); }); """ ## 4. Performance Considerations ### 4.1. Minimize Test Setup **Standard:** Optimize test setup to minimize execution time. * **Do This:** Use "cy.session()" to cache sessions, mock API calls to avoid unnecessary network requests, and seed the database with test data only once. * **Don't Do This:** Repeat the same setup steps for every test. This slows down your test suite unnecessarily. **Why:** Reducing test setup time dramatically improves the overall speed of the test suite. ### 4.2. Limit Network Requests **Standard:** Minimize the number of network requests made during tests. * **Do This:** Mock API calls whenever possible, especially for read-only data. Use "cy.intercept()" as previously shown. * **Don't Do This:** Rely on real APIs for every test interaction. **Why:** Network requests are a major bottleneck in E2E tests. Mocking significantly speeds up execution. ### 4.3. Test Isolation **Standard:** Ensure tests are isolated from each other. * **Do This:** Clear browser cookies and local storage before each test, and reset the database to a known state using "cy.task()". * **Don't Do This:** Allow tests to influence each other's state. **Why:** Test isolation prevents flaky tests and ensures reliable results. ## 5. Security Best Practices ### 5.1. Protect Sensitive Data **Standard:** Do not store sensitive data (e.g., passwords, API keys) directly in test files. * **Do This:** Use environment variables or Cypress configuration files to store sensitive data, and access them using "Cypress.env()" or "Cypress.config()". * **Don't Do This:** Commit sensitive data to the repository. **Why:** Protecting sensitive data prevents accidental exposure and security breaches. **Code Example:** """javascript // cypress.config.js module.exports = defineConfig({ env: { api_url: 'https://api.example.com', auth_token: process.env.AUTH_TOKEN // Set env variable on system }, e2e: { setupNodeEvents(on, config) { // implement node event listeners here }, }, }) """ """javascript // cypress/e2e/api.cy.js describe('API Tests', () => { it('fetches data from the API', () => { cy.request({ url: Cypress.env('api_url') + '/data', headers: { Authorization: "Bearer ${Cypress.env('auth_token')}" } }).then((response) => { expect(response.status).to.eq(200); }); }); }); """ ### 5.2. Validate API Responses **Standard:** Validate API responses to prevent vulnerabilities. * **Do This:** Check the status code, headers, and body of API responses to ensure they match the expected format and content. * **Don't Do This:** Assume that API calls always succeed without validation. **Why:** Validating API responses helps detect security vulnerabilities and ensures data integrity. ### 5.3. Sanitize Input **Standard:** When possible, sanitize input within your tests. * **Do This:** Before entering data into fields, particularly those which post to external APIs or databases, use Regex or similar means to sanitize inputs. * **Don't Do This:** Assume safe inputs. ## 6. Continuous Integration ### 6.1. Run Tests in CI **Standard:** Integrate Cypress tests into your CI/CD pipeline. * **Do This:** Configure your CI system (e.g., Jenkins, CircleCI, GitHub Actions) to run Cypress tests automatically on every code commit or pull request. * **Don't Do This:** Rely on manual test execution. **Why:** Automated testing in CI/CD provides early feedback on code changes and helps prevent regressions. ### 6.2. Parallelization **Standard:** Employ parallelization through Cypress Cloud or similar. * **Do This:** Use Cypress Cloud to efficiently parallelize your test execution. * **Don't Do This:** Run tests sequentially if parallelization is possible. By adhering to these coding standards, you can ensure that your Cypress tests are maintainable, performant, and secure, contributing to the overall quality and reliability of your application.
# Core Architecture Standards for Cypress This document outlines the core architectural standards for Cypress testing, focusing on project structure, organization, and design patterns to ensure maintainable, performant, and reliable tests. These standards are designed for use with the latest versions of Cypress. ## 1. Project Structure and Organization ### 1.1. Directory Structure **Standard:** Follow a modular directory structure to separate concerns and enhance readability. **Do This:** """ cypress/ ├── e2e/ # End-to-End tests │ ├── components/ # Tests for individual components │ │ ├── Button.cy.js │ │ └── Input.cy.js │ ├── integration/ # Feature/Scenario based tests │ │ ├── authentication/ │ │ │ ├── login.cy.js │ │ │ └── logout.cy.js │ │ └── products/ │ │ ├── add_to_cart.cy.js │ │ └── view_product.cy.js │ └── utils/ # Reusable test utilities │ ├── auth.js # Authentication helpers │ └── commands.js # Custom Cypress commands ├── support/ # Support files │ ├── e2e.js # Commands and configuration for E2E tests │ └── component.js # Commands and configuration for component tests ├── fixtures/ # Test data fixtures │ ├── user.json │ └── product.json cypress.config.js # Cypress configuration file """ **Don't Do This:** * Flat directory structures with all tests in a single folder. * Mixing component and E2E tests within the same directory. * Ignoring the separation of concerns (e.g., placing utilities directly in test files). **Why:** A well-structured project improves navigation, simplifies maintenance, and promotes code reuse. Separating component and integration tests allows for focused and efficient testing strategies. ### 1.2. Configuration Management **Standard:** Utilize "cypress.config.js" to manage environment-specific configurations and avoid hardcoding values. **Do This:** """javascript // cypress.config.js const { defineConfig } = require('cypress'); module.exports = defineConfig({ e2e: { baseUrl: 'https://example.com', env: { api_url: 'https://api.example.com', username: 'testuser', password: 'testpassword' }, setupNodeEvents(on, config) { // implement node event listeners here }, }, component: { devServer: { framework: 'react', bundler: 'webpack', }, }, }); """ """javascript // In a test file: describe('Login', () => { it('should login successfully', () => { cy.visit('/'); cy.get('#username').type(Cypress.env('username')); cy.get('#password').type(Cypress.env('password')); cy.get('#login-button').click(); cy.url().should('include', '/dashboard'); }); }); """ **Don't Do This:** * Hardcoding environment-specific values like URLs, usernames, or passwords directly in test files. * Failing to utilize the "env" section in "cypress.config.js" for environment variables. * Overly complex "setupNodeEvents" without proper modularization (see section 2.3.1 for enhancement through plugins) **Why:** This approach allows tests to be run in different environments without modifying the test code, promoting reusability and simplifying deployment. ### 1.3. Fixture Management **Standard:** Store test data in fixture files (".json", ".csv") and load them using "cy.fixture()". **Do This:** """json // cypress/fixtures/user.json { "username": "testuser", "password": "testpassword" } """ """javascript // In a test file: describe('Login', () => { it('should login successfully', () => { cy.fixture('user').then((user) => { cy.visit('/'); cy.get('#username').type(user.username); cy.get('#password').type(user.password); cy.get('#login-button').click(); cy.url().should('include', '/dashboard'); }); }); }); """ **Don't Do This:** * Hardcoding data directly in the test files. * Creating excessively large fixture files that are difficult to manage. * Using fixtures for settings data that should be stored in Cypress config files **Why:** Fixtures decouple test data from test logic, allowing for easy updates to data without modifying the tests themselves. ## 2. Architectural Patterns ### 2.1. Page Object Model (POM) **Standard:** Implement the Page Object Model (POM) to encapsulate UI elements and interactions on specific pages. **Do This:** """javascript // cypress/page_objects/LoginPage.js class LoginPage { visit() { cy.visit('/login'); } getUsernameField() { return cy.get('#username'); } getPasswordField() { return cy.get('#password'); } getLoginButton() { return cy.get('#login-button'); } login(username, password) { this.getUsernameField().type(username); this.getPasswordField().type(password); this.getLoginButton().click(); } } export default LoginPage; """ """javascript // In a test file: import LoginPage from '../page_objects/LoginPage'; describe('Login', () => { const loginPage = new LoginPage(); it('should login successfully', () => { loginPage.visit(); loginPage.login('testuser', 'testpassword'); cy.url().should('include', '/dashboard'); }); }); """ **Don't Do This:** * Directly referencing UI elements within test files. * Mixing UI element interactions with test assertions. * Creating 'God' Page Objects containing functionality for multiple pages **Why:** POM improves test readability, reduces code duplication, and simplifies maintenance by centralizing UI element references and interactions. Using multiple smaller page objects improve maintainability. ### 2.2. Custom Commands **Standard:** Create custom Cypress commands for reusable actions and assertions. **Do This:** """javascript // cypress/support/e2e.js Cypress.Commands.add('login', (username, password) => { cy.visit('/login'); cy.get('#username').type(username); cy.get('#password').type(password); cy.get('#login-button').click(); cy.url().should('include', '/dashboard'); }); Cypress.Commands.add('assertToastMessage', (message) => { cy.get('.toast').should('contain', message); }); """ """javascript // In a test file: describe('Login', () => { it('should login successfully', () => { cy.login('testuser', 'testpassword'); cy.url().should('include', '/dashboard'); }); it('should display a welcome message', () => { cy.login('testuser', 'testpassword'); cy.assertToastMessage('Welcome, testuser!'); }); }); """ **Don't Do This:** * Duplicating code for common actions across multiple test files. * Creating overly complex custom commands that perform multiple unrelated tasks. * Using custom commands as a substitute for proper architecture, such as page objects. **Why:** Custom commands reduce code duplication, improve test readability, and provide a consistent interface for common actions. ### 2.3. Plugins for Complex Tasks **Standard**: Use Cypress plugins to handle complex tasks that cannot be easily managed within the test code itself, or needs to interact with the OS. Typically configured inside "cypress.config.js" **Do This:** * Utilize plugins for tasks like: * Database seeding/resetting * Code coverage reporting * Mocking services * Modifying browser behavior * Interacting with file system """javascript // cypress.config.js const { defineConfig } = require("cypress"); const dbReset = require('./cypress/plugins/db-reset'); //Example of custom one module.exports = defineConfig({ e2e: { setupNodeEvents(on, config) { on('task', { dbReset: () => { return dbReset() }, }) }, }, }); """ """javascript // cypress/plugins/db-reset.js // Example of a db reset plugin using knex.js const Knex = require('knex') const db = Knex({ client: 'pg', connection: { host : '127.0.0.1', port : 5432, user : 'your_db_user', password : 'your_db_password', database : 'your_db_name' } }) module.exports = () => { return db.raw('TRUNCATE TABLE your_table CASCADE') } """ """javascript // In a test file: describe('Tests with DB', () => { it('Should start with a cleaned DB', () => { cy.task('dbReset') // ... rest of test actions }) }) """ **Don't Do This:** * Overloading plugins with unnecessary functionality. Stick to single responsibility principle. * Performing actions better handled within the test context (e.g., simple API calls). * Neglecting to keep plugins updated as Cypress versions increment. **Why:** Plugins allow for seamless integration with external tools and resources, expanding Cypress' capabilities and handling complex setups without polluting the test code. ## 3. Asynchronous Handling and Synchronization ### 3.1. Cypress Commands and Chaining **Standard:** Leverage Cypress's built-in command chaining and retry mechanisms to handle asynchronous operations. **Do This:** """javascript it('loads data and displays it', () => { cy.intercept('GET', '/api/data', { fixture: 'data.json' }).as('getData'); cy.visit('/data-page'); cy.wait('@getData'); //Explicit wait for route. cy.get('#data-list').should('be.visible').and('have.length.greaterThan', 0); }); """ **Don't Do This:** * Using "setTimeout" or other manual waiting mechanisms. * Assuming that elements are immediately available after a UI update. * Creating overly long command chains that are difficult to debug. **Why:** Cypress automatically handles asynchronous operations and retries assertions, ensuring that tests are robust and reliable. Using explicit "cy.wait()" commands on aliased routes improves reliability over "cy.wait(number)" in many situations. ### 3.2. Promises and Async/Await **Standard:** Avoid direct use of "Promise" and "async/await" within Cypress test code outside "setupNodeEvents" or plugins. Rely on Cypress' internal handling of asynchronous operations. **Do This:** """javascript //Preferred Cypress way it('should perform an action after a request completes', () => { cy.request('https://api.example.com/data').then((response) => { expect(response.status).to.eq(200); cy.get('#element').type(response.body.value); }); }); """ **Don't Do This:** * Outside "setupNodeEvents" in cypress config, using "async/await" directly within tests can lead to unexpected behavior. Use Cypress' built-in command handling or ".then()" callbacks. * Manually resolving or rejecting promises outside of plugin/task contexts. * Unnecessary nesting of "then()" calls. **Why:** Cypress manages its command queue and asynchronous operations automatically, and direct use of "Promises" can lead to race conditions. ## 4. Data Management and Handling ### 4.1. Data Factories **Standard:** Utilize a data factory pattern to generate dynamic and consistent test data. This works particularly well when setting up database states to be tested. **Do This:** """javascript // cypress/support/factories.js const faker = require('faker'); const createUser = (overrides = {}) => { return { firstName: faker.name.firstName(), lastName: faker.name.lastName(), email: faker.internet.email(), password: 'password123', ...overrides, }; }; Cypress.Commands.add('createUser', createUser); """ """javascript // In a test file: describe('User Registration', () => { it('registers a new user', () => { const user = cy.createUser({ email: 'custom@example.com' }); cy.visit('/register'); cy.get('#firstName').type(user.firstName); cy.get('#lastName').type(user.lastName); cy.get('#email').type(user.email); cy.get('#password').type(user.password); cy.get('#registerButton').click(); cy.url().should('include', '/dashboard'); }); }); """ **Don't Do This:** * Duplicating data generation logic across multiple test files. * Relying on static data that can become outdated. * Avoid using faker.js in sensitive contexts. **Why:** Data factories ensure tests use consistent and realistic data, improving test reliability and reducing the risk of false positives. ### 4.2. API Interaction for Data Setup **Standard:** Use Cypress' "cy.request()" to interact with APIs directly to set up test data or validate application state. **Do This:** """javascript // Before each test: beforeEach(() => { cy.request('POST', '/api/users', { username: 'testuser', password: 'testpassword', }).then((response) => { expect(response.status).to.eq(201); cy.wrap(response.body.id).as('userId'); // Alias for later use }); }); it('performs an action with the created user', () => { cy.get('@userId').then((userId) => { cy.visit("/user/${userId}/profile"); // ... rest of the test }); }); """ **Don't Do This:** * Setting up test data through the UI. * Ignoring API responses and assuming success. * Not cleaning up data after tests (use "afterEach" hook and "DELETE" requests if applicable). **Why:** API interaction allows for fast and reliable data setup and validation, bypassing the UI and reducing test execution time. ## 5. Error Handling and Resilience ### 5.1. Test Retries **Standard:** Configure test retries in Cypress to handle intermittent failures due to network issues or flaky UI elements. This is configured in "cypress.config.js". **Do This:** """javascript // cypress.config.js const { defineConfig } = require('cypress') module.exports = defineConfig({ retries: { runMode: 2, openMode: 0, }, e2e: { // We'll configure the baseUrl here as well. baseUrl: 'http://localhost:8080', }, }) """ **Don't Do This:** * Relying solely on retries to fix flaky tests. Address the root cause of the flakiness. * Setting excessively high retry counts, masking underlying issues. * Not logging or monitoring retried tests. **Why:** Test retries can mitigate intermittent failures, improving test stability and reducing false positives. ### 5.2. Uncaught Exception Handling **Standard:** Use "Cypress.on('uncaught:exception', ...)" to handle uncaught exceptions gracefully. This should only be used to allow tests to gracefully proceed, and not to avoid test failures. **Do This:** """javascript // cypress/support/e2e.js Cypress.on('uncaught:exception', (err, runnable) => { // returning false here prevents Cypress from // failing the test console.log('Uncaught Exception: ', err.message); return false }); """ **Don't Do This:** * Ignoring uncaught exceptions, which can lead to misleading test results. * Using uncaught exception handling to mask actual errors in the application. * Only use this approach if you fully understand why the exception happens, and are confident that it won't impact your test outcome. **Why:** Proper error handling ensures that tests fail gracefully, providing valuable information about application errors without crashing the test run. ## 6. Component Testing Architecture ### 6.1. Component Folder Structure **Standard**: Mirror your React/Vue/etc. component folder structure in your Cypress tests. This makes it easy to locate tests for specific components. **Do This:** """ src/ components/ Button/ Button.jsx Button.css Input/ Input.jsx Input.css cypress/ component/ components/ Button/ Button.cy.jsx Input/ Input.cy.jsx """ **Don't Do This:** * Putting all component tests in one flat directory * Mixing component and E2E tests in the same folder **Why**: Clear separation and mirroring of component structure allows for easy navigation. ### 6.2. Component Mounting **Standard**: Use "cy.mount()" to mount your components for testing. **Do This:** """jsx // Button.cy.jsx import Button from './Button'; import React from 'react'; it('renders', () => { cy.mount(<Button>Click me</Button>); cy.get('button').should('contain', 'Click me'); }); """ **Don't Do This:** * Mounting entire applications or large parts of the application when component testing. * Using "ReactDOM.render" directly. Use "cy.mount()". **Why**: "cy.mount()" sets up the correct environment and handles necessary cleanup. ## 7. Security Considerations ### 7.1. Sensitive Data **Standard:** Avoid storing sensitive data (API keys, passwords, etc.) directly in test code or configuration files. **Do This:** * Use environment variables to store sensitive data. * Utilize secrets management tools for secure storage and retrieval of credentials. * Make sure environment variables configured in CI/CD pipelines are properly secured. **Don't Do This:** * Committing sensitive data to version control. * Logging sensitive data in test reports. **Why:** Protecting sensitive data is crucial to prevent unauthorized access and potential security breaches. ### 7.2. Cross-Site Scripting (XSS) **Standard:** Ensure that tests do not introduce XSS vulnerabilities by sanitizing input data and validating output. Use "Cypress.Commands.addQuery('clean', ...)" to help sanitize data. **Do This:** * Escape special characters in input data. * Validate that output data does not contain malicious code. * Use code analysis tools to identify potential security risks. **Don't Do This:** * Trusting unsanitized input data. * Ignoring potential XSS vulnerabilities. **Why:** Preventing XSS vulnerabilities protects against malicious attacks and ensures the security of the application. ## 8. Code Style and Formatting ### 8.1. Consistent Formatting **Standard:** Use a code formatter (e.g., Prettier) to ensure consistent code style and formatting. """javascript // .prettierrc.js module.exports = { semi: true, trailingComma: 'all', singleQuote: true, printWidth: 120, tabWidth: 2, }; """ **Do This:** * Configure Prettier in your project and integrate it with your IDE. * Run Prettier automatically on commit or before pushing code. **Don't Do This:** * Inconsistent indentation, spacing, or line breaks. * Ignoring code formatting guidelines. **Why:** Consistent formatting improves code readability and reduces the cognitive load on developers. ### 8.2. Meaningful Names **Standard:** Use clear and descriptive names for variables, functions, and test cases. **Do This:** * Choose names that accurately reflect the purpose and functionality of the code. * Avoid abbreviations or acronyms that may be unclear to others. **Don't Do This:** * Using generic names like "data", "item", or "temp". * Omitting important details in names. **Why:** Meaningful names improve code understanding and reduce the risk of misinterpretation.
# Component Design Standards for Cypress This document outlines the coding standards for component design in Cypress, aiming to promote reusable, maintainable, and testable components. These standards should be used as a guide for developers and AI coding assistants alike to ensure consistency and quality in Cypress projects. This document targets best practices that are relevant as of the current version of Cypress. ## 1. General Principles of Component Design ### 1.1. Reusability **Standard:** Components should be designed to be reusable across multiple tests and applications. * **Do This:** Create generic components that accept props for customization. * **Don't Do This:** Bake in specific test cases or application logic directly into the component's code. **Why:** Reusable components reduce code duplication, improve maintainability, and make tests easier to understand. They provide a consistent API for interacting with UI elements. **Example:** """javascript // Good: Reusable button component // my-button.jsx import React from 'react'; function MyButton({ label, onClick, disabled, className }) { return ( <button className={"my-button ${className}"} onClick={onClick} disabled={disabled} > {label} </button> ); } export default MyButton; """ """javascript // Test usage: import MyButton from './my-button'; describe('MyButton Component', () => { it('should display the correct label', () => { cy.mount(<MyButton label="Click Me" />); cy.get('button').should('contain', 'Click Me'); }); it('should call onClick when clicked', () => { const onClickSpy = cy.spy().as('onClick'); cy.mount(<MyButton label="Click Me" onClick={onClickSpy} />); cy.get('button').click(); cy.get('@onClick').should('have.been.called'); }); }); """ """javascript // Anti-pattern: Button Component with hardcoded test logic. // DON'T DO THIS import React from 'react'; function MyButton() { const handleClick = () => { // NEVER EVER MIX APPLICATION AND TEST DOMAIN cy.log("This should never be inside an application component") } return ( <button className="my-button" onClick={handleClick} > Click Me </button> ); } export default MyButton; """ ### 1.2. Maintainability **Standard:** Components should be easy to understand, modify, and debug. * **Do This:** Use clear and descriptive names for components, props, and methods. Keep components small and focused. Write useful propTypes when using React * **Don't Do This:** Create large, complex components with intertwined logic. Avoid deeply nested structures or overly clever code. **Why:** Maintainable components reduce the cost of long-term maintenance and make it easier for new team members to contribute. **Example:** """javascript // Good: Small, focused component // my-input.jsx import React from 'react'; import PropTypes from 'prop-types'; function MyInput({ label, value, onChange, type = "text", placeholder }) { return ( <div> <label htmlFor="my-input">{label}</label> <input type={type} id="my-input" value={value} onChange={onChange} placeholder={placeholder} /> </div> ); } MyInput.propTypes = { label: PropTypes.string.isRequired, value: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, type: PropTypes.string, placeholder: PropTypes.string, } export default MyInput; """ """javascript // Anti-pattern: Huge, complex component. DON'T DO THIS function MegaComponent() { // ... 500 lines of spaghetti code ... return ( <div> {/* ... lots of confusing JSX ... */} </div> ); } """ ### 1.3. Testability **Standard:** Components should be easy to test in isolation with Cypress. * **Do This:** Use data attributes for targeting elements in tests. Avoid tight coupling between components. Make sure the UI elements have unique ID's where appropriate, especially in forms. * **Don't Do This:** Rely on implementation details that are likely to change. Make components depend on global state or external services without mocking. **Why:** Testable components allow for thorough testing, which reduces the risk of bugs and improves the overall quality of the application. **Example:** """javascript // Good: Data attribute for targeting function MyComponent({dataId, text}) { return ( <div data-testid={dataId}> {text} </div> ); } export default MyComponent; """ """javascript // Test usage import MyComponent from './my-component'; describe('MyComponent', () => { it('should display the text', () => { cy.mount(<MyComponent dataId="my-component" text="Hello, world!" />); cy.get('[data-testid="my-component"]').should('contain', 'Hello, world!'); }); }); """ """javascript // Anti-pattern: Relying on implementation details. DON'T DO THIS // <button onClick={() => document.getElementById("some-other-element").innerText = "Clicked!"}>Click me</button> // In the test, you are trying to find the text inside another element // IT WILL BE VERY HARD to test in the future. Instead // Use application event or something like 'onClick' to communicate with the internal button. """ ## 2. React Component Structure ### 2.1. Functional Components with Hooks **Standard:** Prefer functional components with hooks for managing state and side effects. * **Do This:** Use "useState", "useEffect", and custom hooks to manage component logic. * **Don't Do This:** Use class-based components unless there's a compelling reason. **Why:** Functional components with hooks are more concise, easier to understand, and promote code reuse through custom hooks. They align with modern React development practices. **Example:** """javascript // Good: Functional component with useState import React, { useState } from 'react'; function Counter() { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); } export default Counter; """ """javascript // Anti-pattern: Class-based component (unnecessary) DON'T DO THIS import React, { Component } from 'react'; class Counter extends Component { constructor(props) { super(props); this.state = { count: 0 }; } increment = () => { this.setState({ count: this.state.count + 1 }); }; render() { return ( <div> <p>Count: {this.state.count}</p> <button onClick={this.increment}>Increment</button> </div> ); } } export default Counter; """ ### 2.2. Prop Types **Standard:** Use PropTypes to define the expected types of component props. * **Do This:** Define PropTypes for all props that a component accepts. * **Don't Do This:** Omit PropTypes or use "any" as the prop type. **Why:** PropTypes help catch type-related errors during development, improve code readability, and provide documentation for component usage. They significantly improve the feedback loop, leading to quicker debugging. **Example:** """javascript // Good: PropTypes definition import React from 'react'; import PropTypes from 'prop-types'; function Greeting({ name, age , children }) { return ( <div> Hello, {name}! You are {age} years old. {children} </div> ); } Greeting.propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number.isRequired, children: PropTypes.node }; export default Greeting; """ """javascript // Anti-pattern: Missing PropTypes. DON'T DO THIS function Greeting({ name, age }) { return ( <div> Hello, {name}! You are {age} years old. </div> ); } export default Greeting; """ ### 2.3. Component Composition **Standard:** Favor component composition over inheritance. * **Do This:** Use props.children to pass content to a component. Create higher-order components (HOCs) or render props for shared logic. * **Don't Do This:** Rely on inheritance to reuse code between components. **Why:** Component composition provides more flexibility and avoids the tight coupling associated with inheritance. It promotes modularity and reusability. **Example:** """javascript // Good: Component composition using props.children // layout.jsx import React from 'react'; function Layout({ children }) { return ( <div className="layout"> <header>Header</header> <main>{children}</main> <footer>Footer</footer> </div> ); } export default Layout; """ """javascript // Usage layout.jsx // In a page or other component: import Layout from './layout'; function HomePage() { return ( <Layout> <h1>Welcome to the Home Page!</h1> <p>Some content here.</p> </Layout> ); } export default HomePage; """ """javascript // Anti-pattern: Using inheritance. DON'T DO THIS. This pattern is more prominent in class based components. class BaseComponent extends React.Component { sharedMethod() { // ... shared logic ... } render() { return <div>Base Component</div>; } } class DerivedComponent extends BaseComponent { render() { return <div>Derived Component</div>; } } """ ## 3. Cypress-Specific Component Design Considerations ### 3.1. Data Attributes for Selectors **Standard:** Use "data-*" attributes for selecting elements in your Cypress tests. * **Do This:** Add "data-testid", "data-cy", or similar attributes to elements you want to target in tests. * **Don't Do This:** Use CSS classes or IDs unless they are specifically for styling or functional purposes, not solely for testing, because CSS class names are much more likely to change during development cycles. **Why:** "data-*" attributes provide a stable and reliable way to select elements, even if the underlying CSS or HTML structure changes. They decouple tests from styling, increasing test stability. **Example:** """javascript // Good: Using data-testid function MyComponent({ text }) { return <div data-testid="my-component">{text}</div>; } export default MyComponent; """ """javascript // Cypress Test import MyComponent from './my-component'; describe('MyComponent', () => { it('should display the text', () => { cy.mount(<MyComponent text="Hello, world!" />); cy.get('[data-testid="my-component"]').should('contain', 'Hello, world!'); }); }); """ """javascript // Anti-pattern: Relying on CSS classes for selection. DON'T DO THIS function MyComponent({ text }) { return <div className="my-component">{text}</div>; } """ ### 3.2. Component Isolation **Standard:** Ensure components can be tested in complete isolation. * **Do This:** Use Cypress component testing to mount an individual component and test its behavior. Mock any external dependencies. * **Don't Do This:** Rely on integration tests to test component behavior. **Why:** Testing components in isolation makes it easier to identify and fix bugs. It also allows you to test components more thoroughly and efficiently. **Example:** """javascript // Good: Testing a component in isolation // counter.jsx import React, { useState } from 'react'; function Counter() { const [count, setCount] = useState(0); return ( <div> <p data-testid="counter-value">Count: {count}</p> <button data-testid="increment-button" onClick={() => setCount(count + 1)}>Increment</button> </div> ); } export default Counter; """ """javascript // Cypress Component Test // counter.cy.jsx import Counter from './counter'; describe('Counter Component', () => { it('should increment the count when the button is clicked', () => { cy.mount(<Counter />); cy.get('[data-testid="increment-button"]').click(); cy.get('[data-testid="counter-value"]').should('contain', 'Count: 1'); }); }); """ ### 3.3. Avoid Direct DOM Manipulation **Standard:** Avoid direct DOM manipulation within components beyond what is needed with standard React practices. * **Do This:** Rely on React's state management and rendering to update the UI. If other libraries manipulate the DOM, ensure they do not cause unexpected side effects during testing. * **Don't Do This:** Use "document.getElementById" or similar methods to directly modify the DOM within a component, except when it's crucial and well-controlled. **Why:** Direct DOM manipulation can make components harder to test and maintain, and can lead to inconsistencies between the component's state and the actual DOM. **Example:** """javascript // Anti-pattern: Direct DOM manipulation, DON'T DO THIS import React, { useRef, useEffect } from 'react'; function MyComponent() { const myRef = useRef(null); useEffect(() => { if (myRef.current) { myRef.current.textContent = 'Hello, world!'; // Direct DOM manipulation. Bad practice! } }, []); return <div ref={myRef}></div>; } export default MyComponent; """ Instead, use a "useState" hook: """javascript //Recommended Practice import React, { useState } from 'react'; function MyComponent() { const [text, setText] = useState(''); useEffect(() => { setText('Hello, world!'); }, []); return <div>{text}</div>; } export default MyComponent; """ ### 3.4 Clean Cypress Test Code **Standard:** Every test should check one, and only one, logical component pathway. Also, Cypress tests should not contain application source code. * **Do This:** Have a single "it()" block only check one specific function to limit the blast radius of any failing tests. * **Don't Do This:** Never add application source code and import components directly into Cypress files for testing. **Why:** If you add any application code into Cypress, there is now a high risk you have muddied the waters when it comes to testing. It should *only* orchestrate application code in tests. **Example:** """javascript // Anti-pattern: Application code imported into the test suite. DON'T DO THIS import { formatDate } from '../../src/utils'; // Importing application code describe('MyComponent', () => { it('should display the formatted date', () => { const date = new Date(); const formattedDate = formatDate(date); // Using application code cy.mount(<MyComponent date={date} />); cy.get('[data-testid="date"]').should('contain', formattedDate); }); }); """ In the example above, "formatDate" is part of application code and should only be called *by* the application and *invoked* by the test. """javascript //Recommended Practice - Mocking and intercepting describe('API Call Component', () => { it('should display the fetched data', () => { cy.intercept('GET', '/api/data', { fixture: 'data.json' }).as('getData'); // Mock API cy.mount(<ApiCallComponent />); cy.wait('@getData'); // Verify API call cy.get('[data-testid="data-display"]').should('contain', 'Mocked Data'); // Check against fake data }); }); """ ## 4. Optimizing Component Performance ### 4.1. Memoization **Standard:** Use "React.memo" to prevent unnecessary re-renders of components. * **Do This:** Wrap pure functional components with "React.memo". Provide a custom comparison function if the default shallow comparison is not sufficient. * **Don't Do This:** Memoize components indiscriminately. Only memoize components that are likely to re-render with the same props. **Why:** Memoization can significantly improve performance by preventing unnecessary re-renders. This is particularly important for complex components or components that are frequently re-rendered. **Example:** """javascript // Good: Using React.memo import React from 'react'; const MyComponent = React.memo(function MyComponent({ value }) { console.log('Rendering MyComponent with value:', value); return <div>Value: {value}</div>; }); export default MyComponent; """ """javascript // Example with a custom comparison function: import React from 'react'; const MyComponent = React.memo( function MyComponent({ data }) { console.log('Rendering MyComponent with data:', data); return <div>Data: {data.name}</div>; }, (prevProps, nextProps) => { // Custom comparison function return prevProps.data.id === nextProps.data.id; } ); export default MyComponent; """ ### 4.2. Code Splitting **Standard:** Use code splitting to reduce the initial load time of the application. * **Do This:** Use "React.lazy" and "Suspense" to load components on demand. Split large components into smaller chunks. * **Don't Do This:** Load all components upfront. **Why:** Code splitting reduces the amount of JavaScript that needs to be downloaded and parsed initially, which improves the initial load time and responsiveness of the application. **Example:** """javascript // Good: Using React.lazy and Suspense import React, { lazy, Suspense } from 'react'; const MyLazyComponent = lazy(() => import('./my-lazy-component')); function MyPage() { return ( <div> <h1>My Page</h1> <Suspense fallback={<div>Loading...</div>}> <MyLazyComponent /> </Suspense> </div> ); } export default MyPage; """ ### 4.3. Virtualization **Standard:** Use virtualization techniques for rendering large lists or tables. * **Do This:** Use libraries like "react-window" or "react-virtualized" to render only the visible items in a large list or table. * **Don't Do This:** Render all items in a large list or table at once. **Why:** Virtualization significantly improves performance when rendering large lists or tables by reducing the number of DOM elements that need to be created and updated. **Example:** """javascript // Good: Using react-window import React from 'react'; import { FixedSizeList } from 'react-window'; const Row = ({ index, style }) => ( <div style={style}>Row {index}</div> ); function MyList() { return ( <FixedSizeList height={400} width={300} itemSize={30} itemCount={1000}> {Row} </FixedSizeList> ); } export default MyList; """ ## 5. Security Considerations ### 5.1. Preventing Cross-Site Scripting (XSS) **Standard:** Sanitize user-provided input to prevent XSS attacks. * **Do This:** Use a library like "DOMPurify" to sanitize any user-provided input that is rendered in the component. * **Don't Do This:** Render unsanitized user input directly into the DOM. **Why:** XSS attacks can allow malicious users to inject arbitrary JavaScript code into your application, which can compromise user data and security. **Example:** """javascript // Good: Sanitizing user input with DOMPurify import React from 'react'; import DOMPurify from 'dompurify'; function MyComponent({ userInput }) { const sanitizedInput = DOMPurify.sanitize(userInput); return <div dangerouslySetInnerHTML={{ __html: sanitizedInput }}></div>; } export default MyComponent; """ ### 5.2. Avoiding Insecure Dependencies **Standard:** Keep dependencies up-to-date and scan for vulnerabilities. * **Do This:** Regularly update dependencies to the latest versions. Use tools like "npm audit" or "yarn audit" to scan for vulnerabilities. * **Don't Do This:** Use outdated dependencies with known vulnerabilities. **Why:** Insecure dependencies can introduce security vulnerabilities into your application. ## 6. Accessibility Considerations ### 6.1. Semantic HTML **Standard:** Use semantic HTML elements to improve accessibility. * **Do This:** Use elements like "<article>", "<nav>", "<aside>", and "<footer>" to structure the content of your components. Use labels in forms. * **Don't Do This:** Use "<div>" elements for everything. **Why:** Semantic HTML elements provide structure and meaning to the content of your components, which improves accessibility for users with disabilities. **Example:** """javascript // Good: Using semantic HTML function MyArticle({ title, content }) { return ( <article> <h1>{title}</h1> <p>{content}</p> </article> ); } export default MyArticle; """ """javascript // Anti-pattern: Using divs. DON'T DO THIS function MyArticle({ title, content }) { return ( <div> <div>{title}</div> <div>{content}</div> </div> ); } export default MyArticle; """ ### 6.2. ARIA Attributes **Standard:** Use ARIA attributes to provide additional information to assistive technologies. * **Do This:** Use attributes like "aria-label", "aria-describedby", and "aria-hidden" to provide additional context for users with disabilities. * **Don't Do This:** Overuse ARIA attributes or use them incorrectly. Rely on semantic HTML elements whenever possible. **Why:** ARIA attributes allow you to provide additional information to assistive technologies, which can improve the accessibility of your components. **Example:** """javascript // Good: Using aria label import React from 'react'; function MyComponent() { return ( <button aria-label="Close dialog"> X </button> ); } export default MyComponent; """ Adhering to these guidelines will help to create robust, maintainable, and testable Cypress components. The examples illustrate the practical application of each standard, enabling developers and AI coding assistants to produce high-quality code.
# State Management Standards for Cypress This document outlines the coding standards for state management in Cypress tests. Effective state management is crucial for writing robust, maintainable, and performant end-to-end tests. These standards promote clear data flow, minimize flaky tests, and improve overall test suite reliability. ## 1. Principles of State Management in Cypress State management in Cypress refers to how test data, application state, and context are handled across different test cases and within a single test. Due to the nature of end-to-end testing, direct manipulation of application state is often necessary. However, indiscriminate manipulation can lead to unpredictable and unreliable results. * **Explicit State Setup:** Tests should explicitly set up the application state needed for their execution. Implicit dependencies on previous tests or a specific initial state should be avoided. * **Isolated Test Cases:** Each test case should operate independently. It should clean up or reset any state it modifies to avoid interfering with subsequent tests. * **Data Fixtures for Predictability:** Use data fixtures to provide consistent and well-defined data for tests. This ensures tests are not reliant on dynamically generated or changing data, making them more deterministic. * **Minimize Global State:** Keep the use of global variables or shared state to a minimum. When shared state is necessary, manage it carefully using Cypress's built-in features or custom commands. * **Controlled Side Effects:** Be aware of the side effects that test actions may have on the application state. Ensure these side effects are predictable and do not inadvertently affect other tests. ## 2. Setting Up Initial Application State ### 2.1. Using "cy.request" for Database Seeding or API Calls For complex applications, seeding the database or making API calls before running a test or suite of tests is often the most efficient way to set up the required state. **Do This:** Use "cy.request" to seed the database or make API calls that create, modify, or delete data. **Why:** Provides a clean and predictable application state for each test, reducing the risk of flaky tests due to inconsistent data. """javascript // Seed the database before running a suite of tests before(() => { cy.request('POST', '/api/seed', { users: 10, products: 50 }); }); // Create a specific user before each test beforeEach(() => { cy.request('POST', '/api/users', { name: 'John Doe', email: 'john.doe@example.com' }); }); it('should display the user profile', () => { cy.visit('/profile/john.doe@example.com'); cy.contains('John Doe').should('be.visible'); }); """ **Don't Do This:** Rely on the UI to set up complex application states. It is slow and can be unpredictable. **Anti-pattern:** """javascript // Avoid this approach it('should create a new user through the UI', () => { cy.visit('/register'); cy.get('#name').type('John Doe'); cy.get('#email').type('john.doe@example.com'); cy.get('#password').type('password'); cy.get('button[type="submit"]').click(); cy.url().should('include', '/profile'); }); """ ### 2.2. Using Fixtures for Static Data When you need to use static data for testing, such as user credentials or product information, use fixtures. **Do This:** Create a fixture file (".json") and load it using "cy.fixture()". **Why:** Keeps test data separate from test logic, making tests easier to read and maintain. Fixtures enable you to easily change test data without modifying the test code directly. """json // fixtures/user.json { "name": "Jane Doe", "email": "jane.doe@example.com", "password": "password123" } """ """javascript // Load the fixture in your test beforeEach(() => { cy.fixture('user').as('user'); }); it('should log in with fixture data', () => { cy.visit('/login'); cy.get('@user').then((user) => { cy.get('#email').type(user.email); cy.get('#password').type(user.password); cy.get('button[type="submit"]').click(); cy.url().should('include', '/profile'); }); }); """ **Don't Do This:** Hardcode data directly in the tests or generate it dynamically without a clear strategy. **Anti-pattern:** """javascript // Avoid this approach it('should log in with hardcoded data', () => { cy.visit('/login'); cy.get('#email').type('jane.doe@example.com'); cy.get('#password').type('password123'); cy.get('button[type="submit"]').click(); cy.url().should('include', '/profile'); }); """ ### 2.3. Using "cy.session()" to Persist Authentication State If your application requires authentication, use "cy.session()" to cache and reuse sessions between tests, which significantly speeds up test execution. **Do This:** Define a session with the necessary steps to log in and then reuse it across multiple tests. **Why:** Reduces the overhead of logging in repeatedly in each test, speeding up test execution and making tests more efficient. """javascript // Define a session for logging in beforeEach(() => { cy.session('login', () => { cy.visit('/login'); cy.get('#email').type('jane.doe@example.com'); cy.get('#password').type('password123'); cy.get('button[type="submit"]').click(); cy.url().should('include', '/profile'); cy.contains('Jane Doe').should('be.visible'); // Add assertion to confirm login }); }); it('should display the user profile', () => { cy.visit('/profile'); // No login steps needed cy.contains('Jane Doe').should('be.visible'); }); it('should navigate to settings', () => { cy.visit('/settings'); // No login steps needed cy.contains('Settings').should('be.visible'); }); """ **Don't Do This:** Log in repeatedly in each test using UI interactions. **Anti-pattern:** """javascript // Avoid this approach it('should display the user profile (repeated login)', () => { cy.visit('/login'); cy.get('#email').type('jane.doe@example.com'); cy.get('#password').type('password123'); cy.get('button[type="submit"]').click(); cy.url().should('include', '/profile'); cy.contains('Jane Doe').should('be.visible'); }); """ ## 3. Managing Application State During Tests ### 3.1. Using "cy.intercept" to Mock API Responses Mocking API responses using "cy.intercept" allows you to control the data your application receives, making tests more predictable and faster. **Do This:** Intercept API requests and provide predefined responses. **Why:** Decouples tests from the backend, allowing you to test UI behavior under various scenarios without relying on a live API. Speeds up test execution by avoiding network latency. Allows for testing edge cases or error conditions that might be difficult to reproduce with a live API. """javascript it('should display a list of products', () => { cy.intercept('GET', '/api/products', { fixture: 'products.json' }).as('getProducts'); cy.visit('/products'); cy.wait('@getProducts'); // Optionally wait for the route to complete cy.get('.product-item').should('have.length', 3); // Assuming products.json has 3 items }); """ **Don't Do This:** Depend on the live API for critical data during tests. **Anti-pattern:** """javascript // Avoid this approach if the API is unreliable it('should display a list of products (relying on live API)', () => { cy.visit('/products'); cy.get('.product-item').should('have.length.greaterThan', 0); }); """ ### 3.2. Clearing Application Data with "cy.clearCookies", "cy.clearLocalStorage", and "cy.clearAllSessionStorage" To ensure test isolation, it is crucial to clear cookies and local/session storage between tests. **Do This:** Use "cy.clearCookies()", "cy.clearLocalStorage()", and "cy.clearAllSessionStorage()" in "beforeEach" or "afterEach" hooks. **Why:** Prevents state from leaking between tests, ensuring each test starts from a clean slate. """javascript beforeEach(() => { cy.clearCookies(); cy.clearLocalStorage(); cy.clearAllSessionStorage(); }); it('should perform an action in an isolated session', () => { cy.visit('/'); // ... }); """ **Don't Do This:** Forget to clear cookies or storage, leading to unpredictable test behavior. ### 3.3. Using "cy.reload" for Specific Scenarios In certain cases, you might need to reload the page to reflect changes in the application state. **Do This:** Use "cy.reload()" to refresh the page and update the UI. **Why:** Allows the test to reflect changes made to the application state programmatically or through external actions. Note: Extensive use often indicates potential state mismanagement issues. """javascript it('should update the UI after reloading the page', () => { cy.visit('/settings'); cy.get('#theme-toggle').click(); cy.reload(); cy.get('body').should('have.class', 'dark-theme'); }); """ **Don't Do This:** Use "cy.reload()" indiscriminately, as it can slow down tests and indicate a problem in how the application state is being managed or tested. ### 3.4. Using "cy.task" for Backend Interactions Interact with the backend (e.g., database, file system) using "cy.task". **Do This:** Define tasks in "cypress.config.js" and call them from your tests. **Why:** Provides a secure and controlled way to interact with the backend, separating test logic from backend interactions. """javascript // cypress.config.js module.exports = defineConfig({ e2e: { setupNodeEvents(on, config) { on('task', { resetDatabase: () => { // Code to reset the database return null; }, createUser: (user) => { // Code to create a user in the database return null; }, }); }, }, }); """ """javascript // In your test beforeEach(() => { cy.task('resetDatabase'); cy.task('createUser', { name: 'John Doe', email: 'john.doe@example.com' }); }); it('should display the user profile', () => { cy.visit('/profile/john.doe@example.com'); cy.contains('John Doe').should('be.visible'); }); """ **Don't Do This:** Directly interact with the backend from your tests, which can expose sensitive information and make tests harder to maintain. ### 3.5. Avoid DOM-based State Management Cypress automatically retries commands until assertions pass, which is incredibly powerful but can be problematic if you're relying on the DOM *itself* as the source of truth for state. **Example Anti-Pattern (Avoid this):** """javascript // ANTI-PATTERN: Relying on the DOM to determine state. Fragile & slow. it('should disable the button after two clicks', () => { cy.get('#myButton').should('not.be.disabled'); cy.get('#myButton').click(); // First click (perhaps increments a counter) cy.get('#myButton').click(); // Second click (disables the button) cy.get('#myButton').should('be.disabled'); // Hope the logic is fast enough! }); """ **Why it's bad:** * **Race conditions:** The DOM might not update *immediately* after the clicks, leading to intermittent failures. Cypress will keep retrying the ".should('be.disabled')" assertion, but if the button gets disabled *just* after Cypress checks, you'll get a false negative. * **Test brittleness:** Changes to the UI rendering (e.g., CSS transitions, animations) can affect the timing and break the test. * **Slow tests:** Cypress spends extra time retrying because it doesn't have a reliable understanding of when the underlying state has converged. **Do This: Query the Application State Directly** Ideally, the application should expose endpoints or mechanisms to retrieve the underlying state directly, rather than relying on the DOM. "cy.request" or "cy.intercept" provide far more robust means of verifying application behavior. **Better Example:** """javascript it('should disable the button after two clicks', () => { // Stub the API call that the 'button' makes to track # of clicks cy.intercept('POST', '/api/buttonClick', (req) => { req.reply({statusCode: 200, body: { clickCount: req.body.clickCount + 1} }); }).as('buttonClick'); // Giving stub an Alias // Stub a GET that returns the status of the button cy.intercept('GET', '/api/buttonStatus', {disabled: false}).as('getButtonState'); cy.get('#myButton').should('not.be.disabled'); cy.get('#myButton').click(); cy.wait('@buttonClick'); // Wait for the stubbed POST cy.intercept('GET', '/api/buttonStatus', {disabled: false}).as('getButtonState'); // Restub so that next 'wait retrieves correct value cy.get('#myButton').click(); cy.wait('@buttonClick'); // Wait for this call to complete // Now, assert button is disabled - by verifying what state the app returns cy.request('GET', '/api/buttonStatus').then((response) => { expect(response.body.disabled).to.eq(true); }); }); """ **Explanation:** 1. **Stub Endpoints:** The example uses "cy.intercept" to stub out the API call for the button to report how many clicks have occurred. 2. **Direct App State Check:** Instead of waiting for the DOM to update, "cy.request" is used to make a secure call to find out exactly how many clicks have occurred. The test then compares the stubbed return to a known value. **Benefits:** * **Reliability:** No more race conditions or timing issues. The test directly verifies the underlying application state. * **Speed:** Cypress doesn't waste time retrying DOM-based assertions. * **Maintainability:** The test is robust against UI changes. It focuses on the core application logic. ## 4. Custom Commands for State Management ### 4.1. Creating Custom Commands You can encapsulate common state management operations into custom commands for reusability. **Do This:** Define custom commands in "cypress/support/commands.js". **Why:** Reduces code duplication, making tests easier to read and maintain. Provides a consistent interface for interacting with the application state. """javascript // cypress/support/commands.js Cypress.Commands.add('seedDatabase', (data) => { cy.request('POST', '/api/seed', data); }); Cypress.Commands.add('createUser', (user) => { cy.request('POST', '/api/users', user); }); // In your test beforeEach(() => { cy.seedDatabase({ users: 5, products: 20 }); cy.createUser({ name: 'John Doe', email: 'john.doe@example.com' }); }); it('should display the user profile', () => { cy.visit('/profile/john.doe@example.com'); cy.contains('John Doe').should('be.visible'); }); """ ### 4.2. Using Custom Commands to Wrap Complex Logic Wrap complex state management tasks, such as authentication flows or data setup, into custom commands. **Why:** Simplifies tests and abstracts away implementation details, improving readability and maintainability. """javascript Cypress.Commands.add('login', (email, password) => { cy.visit('/login'); cy.get('#email').type(email); cy.get('#password').type(password); cy.get('button[type="submit"]').click(); cy.url().should('include', '/profile'); cy.contains('Welcome').should('be.visible'); }); it('should log in with valid credentials', () => { cy.login('jane.doe@example.com', 'password123'); cy.contains('Jane Doe').should('be.visible'); }); """ ## 5. Advanced State Management Techniques ### 5.1. Using "Cypress.env" for Configuration Store configuration settings, such as API endpoints or authentication details, in "cypress.config.js" and access them using "Cypress.env". **Do This:** Define environment variables in "cypress.config.js" and access them in your tests. **Why:** Makes tests more portable and allows you to easily switch between different environments (e.g., development, staging, production). """javascript // cypress.config.js module.exports = defineConfig({ env: { apiUrl: 'http://localhost:3000/api', adminEmail: 'admin@example.com', }, e2e: { //... }, }); """ """javascript // In your test it('should fetch data from the API', () => { cy.request("${Cypress.env('apiUrl')}/products").then((response) => { expect(response.status).to.eq(200); }); }); """ **Don't Do This:** Hardcode configuration settings directly in the tests. ### 5.2. Using State Management Libraries (with Caution) While Cypress is primarily an end-to-end testing tool, you can integrate state management libraries like Redux or Vuex if needed. However, this should be done with caution and only when necessary. **Do This:** If your application heavily relies on a state management library, consider using it in your tests as well, but be mindful of the added complexity and potential for slower test execution. **Why:** Allows you to test the interactions between your UI components and the application state more comprehensively. """javascript // Example: Accessing Redux store from Cypress it('should update the Redux state', () => { cy.window().its('store').invoke('dispatch', { type: 'INCREMENT' }); cy.window().its('store').invoke('getState').its('count').should('eq', 1); }); """ **Don't Do This:** Overcomplicate your tests by unnecessarily introducing state management libraries. Aim to keep your tests as simple and focused as possible. End-to-end tests are better used for testing *outcomes* rather than the internal implementation of the state management system. ## 6. Conclusion Adhering to these state management standards will lead to more robust, maintainable, and performant Cypress tests. By explicitly managing application state, isolating test cases, and using fixtures and mocking techniques, you can ensure that your tests are reliable and provide valuable feedback on your application's behavior. Remember that the goal is to create tests that accurately reflect real-world user interactions and provide confidence in your application's functionality.