# 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 (
{children}
);
}
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(Click Me);
cy.get('button').should('contain', 'Click Me');
});
it('calls the onClick handler when clicked', () => {
const onClickSpy = cy.spy().as('onClick');
cy.mount(Click Me);
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.
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'
# 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.
# 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.
# Performance Optimization Standards for Cypress This document outlines the coding standards for optimizing the performance of Cypress tests. Adhering to these standards will result in faster, more reliable, and more efficient tests, leading to quicker feedback cycles and improved overall development velocity. ## 1. General Principles ### 1.1 Minimize Test Scope Tests should be narrowly focused to verify specific functionalities. Broad, end-to-end tests that cover numerous features simultaneously are slow and difficult to debug. * **Do This:** Write focused tests validating single user flows or components. * **Don't Do This:** Create large, monolithic tests covering multiple features in one go. **Why:** Smaller tests execute faster and pinpoint failures more accurately. **Example:** Instead of: """javascript // anti-pattern! it('should complete the entire checkout process', () => { cy.visit('/product/123'); cy.get('[data-testid="add-to-cart"]').click(); cy.visit('/cart'); cy.get('[data-testid="checkout"]').click(); cy.get('[data-testid="name"]').type('John Doe'); cy.get('[data-testid="address"]').type('123 Main St'); cy.get('[data-testid="submit"]').click(); cy.contains('Order Confirmation').should('be.visible'); }); """ Do This: """javascript it('should add a product to the cart', () => { cy.visit('/product/123'); cy.get('[data-testid="add-to-cart"]').click(); cy.visit('/cart'); cy.contains('Product 123').should('be.visible'); }); it('should navigate to the checkout page', () => { cy.visit('/cart'); cy.get('[data-testid="checkout"]').click(); cy.url().should('include', '/checkout'); }); it('should submit the checkout form', () => { cy.visit('/checkout'); // Assuming user already has items in their session/cart cy.get('[data-testid="name"]').type('John Doe'); cy.get('[data-testid="address"]').type('123 Main St'); cy.get('[data-testid="submit"]').click(); cy.contains('Order Confirmation').should('be.visible'); }); """ ### 1.2 Avoid Unnecessary Waits Explicit "cy.wait()" commands should be used sparingly. Prefer assertions that wait implicitly for elements to meet the desired condition. * **Do This:** Use assertions like ".should('be.visible')" or ".should('have.text', 'Expected Text')" which automatically retry until they pass or the timeout is reached. * **Don't Do This:** Use fixed "cy.wait(5000)" without a specific reason. **Why:** Implicit waits based on assertions adapt to varying application response times, making tests more robust and efficient. Fixed waits always introduce a delay, even when the element is already ready. **Example:** Instead of: """javascript // anti-pattern! cy.get('[data-testid="my-element"]').should('exist'); cy.wait(2000); // Unnecessary hardcoded wait cy.get('[data-testid="my-element"]').click(); """ Do This: """javascript cy.get('[data-testid="my-element"]').should('be.visible').click(); """ ### 1.3 Cache Selectors Cache frequently used selectors to avoid repeatedly querying the DOM. * **Do This:** Store the result of "cy.get()" in a variable and reuse it within the scope of the test. * **Don't Do This:** Call "cy.get()" multiple times with the same selector. **Why:** DOM queries are expensive operations. Caching selectors reduces the number of DOM traversals, improving test performance. **Example:** Instead of: """javascript // anti-pattern! it('should update the quantity and total in the cart', () => { cy.visit('/cart'); cy.get('[data-testid="quantity-input"]').clear().type('2'); cy.get('[data-testid="update-button"]').click(); cy.get('[data-testid="total"]').should('contain', '$20.00'); }); """ Do This: """javascript it('should update the quantity and total in the cart', () => { cy.visit('/cart'); const quantityInput = cy.get('[data-testid="quantity-input"]'); const updateButton = cy.get('[data-testid="update-button"]'); const total = cy.get('[data-testid="total"]'); quantityInput.clear().type('2'); updateButton.click(); total.should('contain', '$20.00'); }); """ ### 1.4 Control Network Requests Use "cy.intercept()" to stub or mock network requests to control application state and reduce reliance on backend services. * **Do This:** Mock API responses to ensure consistent test data and avoid unexpected backend behavior. Stub API calls that fetch data critical for the test. * **Don't Do This:** Rely solely on a live backend, especially for critical test scenarios. This makes tests dependent on external factors and can lead to flaky results. **Why:** Stubbing and mocking reduce test flakiness, isolate tests from backend issues, and enable testing of edge cases. It also reduces the overall execution time. **Example:** """javascript it('should display a success message after form submission', () => { cy.intercept('POST', '/api/submit', { statusCode: 200, body: { message: 'Form submitted successfully!' }, }).as('submitForm'); cy.visit('/form'); cy.get('[data-testid="name"]').type('John Doe'); cy.get('[data-testid="email"]').type('john.doe@example.com'); cy.get('[data-testid="submit-button"]').click(); cy.wait('@submitForm').then(() => { cy.contains('Form submitted successfully!').should('be.visible'); }); }); """ ### 1.5 Use "baseUrl" Effectively Configure the "baseUrl" in "cypress.config.js" to avoid repeating the base URL in every "cy.visit()" command. * **Do This:** Set "baseUrl" to the base URL of your application. * **Don't Do This:** Hardcode the full URL in every "cy.visit()" command. **Why:** Reduces redundancy, improves readability, and simplifies maintenance. **Example:** "cypress.config.js": """javascript const { defineConfig } = require("cypress"); module.exports = defineConfig({ e2e: { baseUrl: 'http://localhost:3000', // Replace with your application's base URL setupNodeEvents(on, config) { // implement node event listeners here }, }, }); """ Then in the test: """javascript cy.visit('/products'); // Cypress will visit http://localhost:3000/products """ ### 1.6 Clean Up Test Data Ensure test data does not pollute the application's database or state, potentially affecting other tests. * **Do This:** Use "cy.request()" or "cy.task()" to clean up test data after each test or test suite. Consider using a separate test database or environment. For UI based data creation, consider deleting the data through the UI to ensure that deletion flows are also tested. * **Don't Do This:** Leave test data accumulating, causing conflicts or affecting the application's state. **Why:** Maintaining a clean test environment is essential for test reliability and prevents unexpected side effects. **Example:** """javascript afterEach(() => { cy.request('DELETE', '/api/delete-test-data'); // Assuming an API endpoint for cleanup }); """ ### 1.7 Conditional Testing (Use Sparingly) Use conditional testing with "Cypress.env()" to adjust test behavior based on environment variables. However, avoid excessive conditional logic that makes tests difficult to understand and maintain. * **Do This:** Use environment variables to control test behavior in different environments (e.g., run specific tests only in CI/CD). * **Don't Do This:** Overuse conditional logic within a single test. For vastly different scenarios consider different tests. **Why:** Environment variables allow for flexible test configuration without modifying the test code directly. **Example:** "cypress.config.js": """javascript const { defineConfig } = require("cypress"); module.exports = defineConfig({ e2e: { env: { runExpensiveTests: process.env.RUN_EXPENSIVE_TESTS === 'true', }, setupNodeEvents(on, config) { // implement node event listeners here config.env.runExpensiveTests = process.env.RUN_EXPENSIVE_TESTS === 'true' }, }, }); """ Then in the test: """javascript it('should perform expensive operation if enabled', () => { if (Cypress.env('runExpensiveTests')) { // Expensive test logic cy.log('Running expensive test...'); } else { cy.log('Skipping expensive test...'); cy.skip(); } }); """ ### 1.8 Utilize "cy.session()" Use "cy.session()" to cache and reuse authentication sessions across tests. * **Do This:** Wrap the authentication logic using "cy.session()". * **Don't Do This:** Authenticate the user on every test if it shares the same session context. **Why:** This can dramatically reduce both flakiness and increase speed by authenticating the user only once per session, instead of on every test. **Example:** """javascript beforeEach(() => { cy.session('my-session', () => { cy.visit('/login'); cy.get('[data-testid="username"]').type('user'); cy.get('[data-testid="password"]').type('password'); cy.get('[data-testid="submit"]').click(); cy.contains('Welcome, user').should('be.visible'); }); }); it('should display user profile', () => { cy.visit('/profile'); cy.contains('User Profile Information').should('be.visible'); }); """ ## 2. Advanced Optimization Techniques ### 2.1 Parallelization Run Cypress tests in parallel using Cypress Cloud or other CI/CD tools that support parallelization. * **Do This:** Configure your CI/CD pipeline to split the test suite across multiple machines or containers. * **Don't Do This:** Run all tests sequentially, especially for large test suites. **Why:** Reduces overall test execution time and accelerates feedback. **Example:** (This is CI/CD configuration-specific. Consult your CI/CD provider's documentation for instructions on parallel test execution using Cypress Cloud or similar tools.) ### 2.2 Component Testing Utilize Cypress Component Testing feature for UI components to isolate rendering and interaction behavior. * **Do This:** Isolate and test individual components instead of relying solely on end-to-end tests. Improves test coverage and execution speed. * **Don't Do This:** Avoid component testing altogether, relying solely on slower end-to-end tests. **Why:** Makes writing specific UI tests much faster and easier. **Example:** """javascript // Component test for a button: import React from 'react'; import MyButton from './MyButton'; describe('MyButton Component', () => { it('should call onClick when clicked', () => { const onClickSpy = cy.spy().as('onClick'); cy.mount(<MyButton onClick={onClickSpy}>Click Me</MyButton>); cy.get('button').click(); cy.get('@onClick').should('have.been.calledOnce'); }); }); """ ### 2.3 Optimize Selectors Use efficient CSS selectors to locate elements quickly. * **Do This:** Prefer ID selectors ("#my-element") or attribute selectors ("[data-testid="my-element"]") over complex CSS selectors that rely on DOM structure. * **Don't Do This:** Use overly specific selectors like "div > div > ul > li:nth-child(3) > a" that are brittle and slow. **Why:** Simpler selectors are faster to evaluate, especially in large and complex DOM structures. **Example:** Instead of: """javascript // anti-pattern! cy.get('body > div#content > ul.items > li:nth-child(2) > a'); """ Do This (if possible, add a "data-testid" attribute): """javascript cy.get('[data-testid="item-2-link"]'); """ ### 2.4 Headless Mode Run Cypress tests in headless mode (without a browser UI) in CI/CD environments. * **Do This:** Configure your CI/CD pipeline to execute Cypress tests in "cypress run" mode. * **Don't Do This:** Run tests in headed mode in CI/CD, unless debugging is required. **Why:** Headless mode consumes fewer resources and executes tests faster. **Example:** (This is CI/CD configuration-specific. Consult your CI/CD provider's documentation.) ### 2.5 Video and Screenshot Management Disable video recording and screenshots for successful tests in CI/CD to reduce storage overhead and improve performance. * **Do This:** Configure Cypress to record videos and capture screenshots only for failing tests. * **Don't Do This:** Record videos and capture screenshots for all tests, regardless of their status. **Why:** Reduces storage requirements and minimizes the performance impact of video recording and screenshot capture. "cypress.config.js": """javascript const { defineConfig } = require("cypress"); module.exports = defineConfig({ e2e: { video: false, screenshotOnRunFailure: true, setupNodeEvents(on, config) { // implement node event listeners here }, }, }); """ You can override these values via command line arguments during the test run. ### 2.6 Prevent Log Spam Reduce the amount of console logging in your tests, as excessive logging can slow down test execution. * **Do This:** Log only essential information needed for debugging or reporting. * **Don't Do This:** Log every action or assertion unconditionally. **Why:** Constant writing to the terminal causes overhead and can impact testing time. **Example:** Instead of: """javascript cy.get('[data-testid="my-element"]').then(($el) => { cy.log('Element text:', $el.text()); // Unnecessary logging // ... assertions }); """ Do This: """javascript cy.get('[data-testid="my-element"]').should('have.text', 'Expected value'); """ Use "cy.log" only when you are actively debugging or need to output very specific values relevant to debugging. ## 3. Anti-Patterns and Common Mistakes * **Over-reliance on UI interaction:** Minimize UI interactions by using API requests to set up test data or application state, when possible (using "cy.request" and "cy.task"). * **Ignoring error messages:** Pay close attention to Cypress error messages; they often provide valuable clues about performance bottlenecks or incorrect test logic. * **Blindly increasing timeouts:** Instead of increasing timeouts arbitrarily, investigate the root cause of slow test execution. Longer timeouts mask underlying performance problems. * **Forgetting to target specific elements:** Use element-specific assertions. For example, instead of just determining if ANY element contains some text, ensure the element of interest does. Otherwise, you may have false positives, and increase test duration. ## 4. Staying Up-to-Date * **Read Cypress Release Notes:** Stay aware of new Cypress features and performance improvements by regularly reviewing the release notes on the official Cypress website. This is important as Cypress is actively developed and constantly improving. * **Follow Cypress Best Practices:** Keep up with the latest best practices documented by Cypress. * **Community Resources:** Participate in Cypress community forums and online discussions to learn from other developers' experiences. By following these performance optimization standards, you can create Cypress tests that are fast, reliable, and maintainable, ultimately contributing to a more efficient and effective development process.
# 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.
# 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 <button data-cy="submit-button">Submit</button> cy.get('[data-cy=submit-button]').click(); // Less ideal (but sometimes necessary) <button id="submit">Submit</button> cy.get('#submit').click(); // Avoid <button class="btn btn-primary">Submit</button> 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 = '<script>alert("XSS");</script>'; 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.