🎯 empfohlene Sammlungen
Balanced sample collections from various categories for you to explore
Cypress E2E Testing Framework
Umfassende Cypress E2E-Testbeispiele einschließlich Testkonfiguration, Page Object Models, API-Tests, visueller Regressionstests und fortgeschrittene E2E-Patterns für moderne Webanwendungen
💻 Cypress-Projekteinrichtung und Konfiguration javascript
Vollständige Cypress-Projekteinrichtung mit Konfigurationsdateien, Teststruktur, benutzerdefinierten Befehlen und bewährten Praktiken für E2E-Tests
// Cypress Project Setup and Configuration
// 1. cypress.config.js - Main configuration
const { defineConfig } = require('cypress');
module.exports = defineConfig({
// Project configuration
projectId: 'your-project-id',
// Browser configuration
e2e: {
baseUrl: 'http://localhost:3000',
supportFile: 'cypress/support/e2e.js',
specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
excludeSpecPattern: ['**/node_modules/**', '**/dist/**'],
// Viewport settings
viewportWidth: 1280,
viewportHeight: 720,
// Video settings
video: true,
videoCompression: 32,
// Screenshot settings
screenshotOnRunFailure: true,
// Default command timeout
defaultCommandTimeout: 10000,
// Request timeout
requestTimeout: 10000,
// Response timeout
responseTimeout: 10000,
// Environment variables
env: {
username: 'testuser',
password: 'testpass',
apiUrl: 'http://localhost:4000/api'
},
// Page load timeout
pageLoadTimeout: 30000,
// retries
retries: {
runMode: 2,
openMode: 0
},
// Browser settings
chromeWebSecurity: false,
// Experimental features
experimentalStudio: true,
experimentalWebKitSupport: true
},
// Component testing configuration
component: {
devServer: {
framework: 'create-react-app',
bundler: 'webpack'
}
}
});
// 2. package.json dependencies
{
"devDependencies": {
"cypress": "^13.6.0",
"@cypress/xhr": "^2.5.1",
"cypress-mochawesome-reporter": "^3.6.0",
"cypress-visual-regression": "^2.0.0",
"cypress-real-events": "^1.10.4",
"cypress-plugin-tab": "^1.0.5",
"cypress-localstorage-commands": "^2.2.2",
"cypress-wait-until": "^1.7.2"
},
"scripts": {
"cy:open": "cypress open",
"cy:run": "cypress run",
"cy:run:headed": "cypress run --headed",
"cy:run:chrome": "cypress run --browser chrome",
"cy:run:record": "cypress run --record",
"cy:run:ci": "cypress run --record --browser chrome --headless",
"cy:report": "cypress run --reporter cypress-mochawesome-reporter"
}
}
// 3. cypress/support/e2e.js - Global setup and commands
// Import commands file using ES2015 syntax:
import './commands';
// Alternatively you can use CommonJS syntax:
// require('./commands')
// Global beforeEach hook
beforeEach(() => {
// Clear local storage before each test
cy.clearLocalStorage();
// Clear cookies before each test
cy.clearCookies();
});
// Global afterEach hook
afterEach(() => {
// Take screenshot on failure
cy.screenshot({ capture: 'runner' });
});
// Global error handling
Cypress.on('uncaught:exception', (err, runnable) => {
// Return false to prevent Cypress from failing the test
if (err.message.includes('ResizeObserver loop limit exceeded')) {
return false;
}
// Return false to prevent Cypress from failing the test
return true;
});
// 4. cypress/support/commands.js - Custom commands
// Custom login command
Cypress.Commands.add('login', (username, password) => {
cy.visit('/login');
cy.get('[data-cy=username]').type(username);
cy.get('[data-cy=password]').type(password);
cy.get('[data-cy=login-button]').click();
cy.url().should('not.include', '/login');
});
// Custom API login command
Cypress.Commands.add('apiLogin', (username, password) => {
cy.request({
method: 'POST',
url: '/api/auth/login',
body: { username, password }
}).then((response) => {
window.localStorage.setItem('authToken', response.body.token);
});
});
// Custom command to wait for API response
Cypress.Commands.add('waitForApi', (alias, timeout = 10000) => {
cy.wait(`@${alias}`, { timeout });
});
// Custom command to check element visibility
Cypress.Commands.add('shouldBeVisible', { prevSubject: true }, (subject, options = {}) => {
cy.wrap(subject).should('be.visible');
return cy.wrap(subject);
});
// Custom command for data attributes
Cypress.Commands.add('getDataCy', (selector) => {
return cy.get(`[data-cy=${selector}]`);
});
// Custom command for file upload
Cypress.Commands.add('uploadFile', (selector, fileName, fileType = '') => {
cy.get(selector).then(subject => {
cy.fixture(fileName).then(fileContent => {
const blob = new Blob([fileContent], { type: fileType });
const file = new File([blob], fileName, { type: fileType });
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
subject[0].files = dataTransfer.files;
});
});
});
// 5. Basic test structure
// cypress/e2e/login.cy.js
describe('Authentication', () => {
beforeEach(() => {
cy.visit('/login');
});
it('should display login form', () => {
cy.getDataCy('username').should('be.visible');
cy.getDataCy('password').should('be.visible');
cy.getDataCy('login-button').should('be.visible');
cy.get('h1').should('contain', 'Login');
});
it('should show error for invalid credentials', () => {
cy.getDataCy('username').type('invaliduser');
cy.getDataCy('password').type('invalidpass');
cy.getDataCy('login-button').click();
cy.getDataCy('error-message').should('be.visible');
cy.getDataCy('error-message').should('contain', 'Invalid credentials');
cy.url().should('include', '/login');
});
it('should login successfully with valid credentials', () => {
cy.getDataCy('username').type(Cypress.env('username'));
cy.getDataCy('password').type(Cypress.env('password'));
cy.getDataCy('login-button').click();
cy.url().should('not.include', '/login');
cy.getDataCy('user-menu').should('be.visible');
});
it('should handle empty form submission', () => {
cy.getDataCy('login-button').click();
cy.getDataCy('username').should('have.class', 'error');
cy.getDataCy('password').should('have.class', 'error');
cy.getDataCy('error-message').should('contain', 'Please fill in all fields');
});
it('should toggle password visibility', () => {
cy.getDataCy('password').type('password123');
cy.getDataCy('password').should('have.attr', 'type', 'password');
cy.getDataCy('toggle-password').click();
cy.getDataCy('password').should('have.attr', 'type', 'text');
cy.getDataCy('toggle-password').click();
cy.getDataCy('password').should('have.attr', 'type', 'password');
});
});
// 6. Page Object Model example
// cypress/e2e/page-objects/LoginPage.js
class LoginPage {
visit() {
cy.visit('/login');
}
getUsernameField() {
return cy.getDataCy('username');
}
getPasswordField() {
return cy.getDataCy('password');
}
getLoginButton() {
return cy.getDataCy('login-button');
}
getErrorMessage() {
return cy.getDataCy('error-message');
}
login(username, password) {
this.getUsernameField().type(username);
this.getPasswordField().type(password);
this.getLoginButton().click();
}
verifyLoginPage() {
cy.url().should('include', '/login');
this.getUsernameField().should('be.visible');
this.getPasswordField().should('be.visible');
this.getLoginButton().should('be.visible');
}
verifyLoginError() {
this.getErrorMessage().should('be.visible');
cy.url().should('include', '/login');
}
}
export default LoginPage;
// cypress/e2e/authentication-with-pom.cy.js
import LoginPage from '../page-objects/LoginPage';
describe('Authentication with Page Object Model', () => {
const loginPage = new LoginPage();
beforeEach(() => {
loginPage.visit();
});
it('should login successfully using POM', () => {
loginPage.login(Cypress.env('username'), Cypress.env('password'));
cy.url().should('not.include', '/login');
cy.getDataCy('user-menu').should('be.visible');
});
it('should show error using POM', () => {
loginPage.login('invalid', 'credentials');
loginPage.verifyLoginError();
loginPage.getErrorMessage().should('contain', 'Invalid credentials');
});
});
// 7. Test organization with fixtures
// cypress/fixtures/users.json
{
"validUser": {
"username": "testuser",
"password": "testpass123",
"email": "[email protected]",
"firstName": "Test",
"lastName": "User"
},
"invalidUser": {
"username": "invaliduser",
"password": "wrongpass"
},
"adminUser": {
"username": "admin",
"password": "adminpass",
"role": "administrator"
}
}
// cypress/fixtures/products.json
{
"products": [
{
"id": 1,
"name": "Laptop",
"price": 999.99,
"category": "Electronics",
"inStock": true
},
{
"id": 2,
"name": "Book",
"price": 19.99,
"category": "Books",
"inStock": true
}
]
}
// cypress/e2e/using-fixtures.cy.js
describe('Using Fixtures in Tests', () => {
let userData;
beforeEach(() => {
cy.fixture('users').then((users) => {
userData = users;
});
});
it('should login with valid user from fixtures', () => {
cy.visit('/login');
cy.getDataCy('username').type(userData.validUser.username);
cy.getDataCy('password').type(userData.validUser.password);
cy.getDataCy('login-button').click();
cy.url().should('not.include', '/login');
});
it('should fail login with invalid user from fixtures', () => {
cy.visit('/login');
cy.getDataCy('username').type(userData.invalidUser.username);
cy.getDataCy('password').type(userData.invalidUser.password);
cy.getDataCy('login-button').click();
cy.getDataCy('error-message').should('be.visible');
});
});
// 8. Environment-specific configuration
// cypress.config.env.js
module.exports = (on, config) => {
// Get environment from command line or use default
const environment = config.env.environment || 'development';
// Load environment-specific configuration
const environmentConfig = require(`./cypress/environments/${environment}.json`);
// Merge environment config with default config
Object.assign(config, environmentConfig);
// Return final config
return config;
};
// cypress/environments/development.json
{
"baseUrl": "http://localhost:3000",
"env": {
"apiUrl": "http://localhost:4000/api",
"username": "devuser",
"password": "devpass"
}
}
// cypress/environments/staging.json
{
"baseUrl": "https://staging.example.com",
"env": {
"apiUrl": "https://api-staging.example.com/api",
"username": "staginguser",
"password": "stagingpass"
}
}
// cypress/environments/production.json
{
"baseUrl": "https://example.com",
"env": {
"apiUrl": "https://api.example.com/api",
"username": "produser",
"password": "prodpass"
}
}
💻 Fortgeschrittene Benutzerinteraktionen und Assertions javascript
Komplexe Benutzerinteraktionsbeispiele einschließlich Drag and Drop, Datei-Uploads, Tastaturkürzel, Hover-Effekte und fortgeschrittene Assert-Strategien
// Advanced User Interactions with Cypress
// 1. Complex Form Interactions
// cypress/e2e/advanced-forms.cy.js
describe('Advanced Form Interactions', () => {
beforeEach(() => {
cy.visit('/forms/advanced');
});
it('should handle multi-step form with validation', () => {
// Step 1: Personal Information
cy.getDataCy('step-1').should('be.visible');
cy.getDataCy('first-name').type('John').should('have.value', 'John');
cy.getDataCy('last-name').type('Doe').should('have.value', 'Doe');
cy.getDataCy('email').type('[email protected]');
// Email validation
cy.getDataCy('email').blur();
cy.getDataCy('email-error').should('not.exist');
// Invalid email test
cy.getDataCy('email').clear().type('invalid-email');
cy.getDataCy('email').blur();
cy.getDataCy('email-error').should('be.visible')
.and('contain', 'Please enter a valid email');
// Correct email
cy.getDataCy('email').clear().type('[email protected]');
cy.getDataCy('next-step').click();
// Step 2: Address Information
cy.getDataCy('step-2').should('be.visible');
cy.getDataCy('address').type('123 Main St');
cy.getDataCy('city').type('New York');
// Dynamic dropdown
cy.getDataCy('state').click();
cy.getDataCy('state-option').contains('New York').click();
cy.getDataCy('state').should('contain', 'New York');
// Step 3: Review and Submit
cy.getDataCy('next-step').click();
cy.getDataCy('step-3').should('be.visible');
// Verify all information is displayed correctly
cy.getDataCy('review-first-name').should('contain', 'John');
cy.getDataCy('review-last-name').should('contain', 'Doe');
cy.getDataCy('review-email').should('contain', '[email protected]');
cy.getDataCy('review-address').should('contain', '123 Main St');
// Submit form
cy.getDataCy('submit-form').click();
cy.getDataCy('success-message').should('be.visible')
.and('contain', 'Form submitted successfully');
});
it('should handle conditional form fields', () => {
cy.getDataCy('has-experience').check();
cy.getDataCy('experience-section').should('be.visible');
cy.getDataCy('years-experience').select('5-10');
cy.getDataCy('previous-companies').type(['Company A', 'Company B']);
// Dynamic field addition
cy.getDataCy('add-company').click();
cy.getDataCy('company-input').should('have.length', 3);
cy.getDataCy('has-experience').uncheck();
cy.getDataCy('experience-section').should('not.exist');
});
});
// 2. Drag and Drop Functionality
// cypress/e2e/drag-drop.cy.js
describe('Drag and Drop Interactions', () => {
beforeEach(() => {
cy.visit('/drag-drop');
});
it('should drag items between containers', () => {
const dataTransfer = new DataTransfer();
// Get source and target elements
cy.getDataCy('source-item-1')
.trigger('dragstart', { dataTransfer })
.trigger('dragleave', { dataTransfer });
cy.getDataCy('target-container')
.trigger('dragover', { dataTransfer })
.trigger('drop', { dataTransfer })
.trigger('dragend', { dataTransfer });
cy.getDataCy('target-container').within(() => {
cy.getDataCy('source-item-1').should('exist');
});
cy.getDataCy('source-container').within(() => {
cy.getDataCy('source-item-1').should('not.exist');
});
});
it('should handle sortable lists', () => {
// Using cypress-real-events plugin for better drag support
cy.getDataCy('sortable-item-1').realMouseDown();
cy.getDataCy('sortable-item-3').realMouseMove({ clientX: 100, clientY: 200 });
cy.getDataCy('sortable-item-3').realMouseUp();
// Verify order has changed
cy.getDataCy('sortable-list').within(() => {
cy.getDataCy('sortable-item').eq(0).should('contain', 'Item 2');
cy.getDataCy('sortable-item').eq(1).should('contain', 'Item 3');
cy.getDataCy('sortable-item').eq(2).should('contain', 'Item 1');
});
});
it('should handle file upload via drag and drop', () => {
// Intercept file upload request
cy.intercept('POST', '/api/upload', {
statusCode: 200,
body: { success: true, filename: 'test-file.pdf' }
}).as('fileUpload');
// Create a fake file
cy.fixture('test-file.pdf').then(fileContent => {
cy.get('[data-cy=drop-zone]').selectFile({
contents: Cypress.Buffer.from(fileContent),
fileName: 'test-file.pdf',
mimeType: 'application/pdf'
}, {
force: true,
dragEnter: true,
dragLeave: true,
dragOver: true,
drop: true
});
});
cy.wait('@fileUpload');
cy.getDataCy('upload-success').should('be.visible');
cy.getDataCy('uploaded-file').should('contain', 'test-file.pdf');
});
});
// 3. Keyboard and Accessibility Testing
// cypress/e2e/keyboard-accessibility.cy.js
describe('Keyboard Navigation and Accessibility', () => {
beforeEach(() => {
cy.visit('/accessibility');
});
it('should be fully keyboard navigable', () => {
// Tab navigation
cy.get('body').tab();
cy.focused().should('have.attr', 'data-cy', 'skip-link');
// Continue tabbing through interactive elements
cy.focused().tab();
cy.focused().should('have.attr', 'data-cy', 'main-navigation');
cy.focused().tab();
cy.focused().should('have.attr', 'data-cy', 'search-input');
// Test form navigation
cy.get('[data-cy=search-input]').type('test query');
cy.focused().tab();
cy.focused().should('have.attr', 'data-cy', 'search-button');
// Enter to submit
cy.focused().type('{enter}');
cy.getDataCy('search-results').should('be.visible');
});
it('should handle keyboard shortcuts', () {
// Test custom keyboard shortcuts
cy.get('body').type('{ctrl}k'); // Open search modal
cy.getDataCy('search-modal').should('be.visible');
cy.focused().should('have.attr', 'data-cy', 'search-input');
// Escape to close modal
cy.get('body').type('{esc}');
cy.getDataCy('search-modal').should('not.exist');
// Test slash for quick search
cy.get('body').type('/');
cy.focused().should('have.attr', 'data-cy', 'search-input');
});
it('should have proper ARIA labels and roles', () => {
// Check ARIA attributes
cy.getDataCy('main-navigation').should('have.attr', 'role', 'navigation');
cy.getDataCy('search-button').should('have.attr', 'aria-label', 'Search');
cy.getDataCy('menu-button').should('have.attr', 'aria-expanded', 'false');
// Test ARIA live regions
cy.getDataCy('status-message').should('have.attr', 'aria-live', 'polite');
// Trigger status update
cy.getDataCy('update-status').click();
cy.getDataCy('status-message').should('contain', 'Status updated');
});
it('should support screen readers', () => {
// Check for proper semantic HTML
cy.get('h1').should('exist');
cy.get('main').should('exist');
cy.get('nav').should('exist');
// Test alt text for images
cy.getDataCy('product-image').should('have.attr', 'alt').and('not.be.empty');
// Test form labels
cy.getDataCy('form-input').should('have.attr', 'aria-labelledby');
cy.getDataCy('input-label').should('exist');
});
});
// 4. Advanced Assertions and Chaining
// cypress/e2e/advanced-assertions.cy.js
describe('Advanced Assertions and Chaining', () => {
beforeEach(() => {
cy.visit('/advanced-assertions');
});
it('should use complex assertions', () => {
// Multiple assertions on single element
cy.getDataCy('user-card')
.should('be.visible')
.and('have.class', 'active')
.and('have.css', 'background-color', 'rgb(59, 130, 246)')
.and('contain.text', 'John Doe');
// Assertions on multiple elements
cy.getDataCy('product-item')
.should('have.length', 5)
.each(($el, index) => {
cy.wrap($el).should('have.attr', 'data-product-id');
if (index < 3) {
cy.wrap($el).should('have.class', 'featured');
}
});
// Custom assertions with callbacks
cy.getDataCy('price-display').should(($el) => {
const text = $el.text();
const price = parseFloat(text.replace('$', ''));
expect(price).to.be.greaterThan(0);
expect(price).to.be.lessThan(1000);
});
// Chain assertions with different subjects
cy.getDataCy('product-container')
.find('[data-cy=product-item]')
.first()
.find('[data-cy=product-name]')
.should('not.be.empty')
.parents('[data-cy=product-item]')
.find('[data-cy=product-price]')
.should('match', /\$\d+\.\d{2}/);
});
it('should handle timing and等待', () => {
// Wait for element with custom timeout
cy.getDataCy('slow-loading-content', { timeout: 10000 }).should('be.visible');
// Wait for text to change
cy.getDataCy('dynamic-text').should('not.contain', 'Loading...');
cy.getDataCy('dynamic-text').should('contain', 'Content loaded');
// Wait for API call
cy.intercept('GET', '/api/data').as('getData');
cy.getDataCy('load-data-button').click();
cy.wait('@getData', { timeout: 15000 }).its('response.statusCode').should('eq', 200);
// Wait for number of elements
cy.getDataCy('list-item').should('have.length.greaterThan', 0);
// Wait for element to become visible
cy.getDataCy('hidden-element').scrollIntoView().should('be.visible');
});
it('should use assertions on window and document', () => {
// Window assertions
cy.window().should('have.property', 'localStorage');
cy.window().its('localStorage').should('have.property', 'setItem');
// Document assertions
cy.document().its('title').should('include', 'Advanced Testing');
cy.document().should('have.property', 'readyState', 'complete');
// Location assertions
cy.location().should((location) => {
expect(location.pathname).to.include('/advanced-assertions');
expect(location.search).to.be.empty;
});
// URL assertions
cy.url().should('include', '/advanced-assertions');
cy.url().should('not.include', 'error');
});
});
// 5. Real User Events Simulation
// cypress/e2e/real-events.cy.js
describe('Real User Events Simulation', () => {
beforeEach(() => {
cy.visit('/real-events');
});
it('should simulate real mouse movements', () => {
// Using cypress-real-events for more realistic interactions
cy.getDataCy('interactive-button').realHover();
cy.getDataCy('tooltip').should('be.visible');
cy.getDataCy('interactive-button').realMouseMove(10, 10);
cy.getDataCy('interactive-button').realMouseMove(20, 5);
cy.getDataCy('interactive-button').realClick();
cy.getDataCy('click-feedback').should('be.visible');
});
it('should simulate touch events on mobile', () => {
cy.viewport('iphone-x');
cy.getDataCy('touch-button').realTouch();
cy.getDataCy('touch-feedback').should('be.visible');
// Swipe gesture
cy.getDataCy('swipe-container')
.realTouchStart(0, 100)
.realTouchMove(100, 100)
.realTouchEnd(100, 100);
cy.getDataCy('swipe-result').should('contain', 'Swiped right');
});
it('should simulate complex form input', () => {
// Realistic typing with delays
cy.getDataCy('name-input').realType('John Doe', { delay: 100 });
cy.getDataCy('email-input').realType('[email protected]', { delay: 50 });
// Select dropdown with real click
cy.getDataCy('country-dropdown').realClick();
cy.get('[data-value="US"]').realClick();
cy.getDataCy('country-dropdown').should('contain', 'United States');
// Real file selection
cy.getDataCy('file-input').realClick();
// Tab navigation
cy.get('body').realPress('Tab');
cy.get('body').realPress('Tab');
cy.focused().should('have.attr', 'data-cy', 'email-input');
});
it('should simulate keyboard shortcuts and modifiers', () => {
// Ctrl+C copy
cy.getDataCy('text-to-copy').selectText();
cy.get('body').realPress(['Control', 'c']);
cy.getDataCy('copy-feedback').should('contain', 'Copied');
// Ctrl+V paste
cy.getDataCy('paste-target').focus();
cy.get('body').realPress(['Control', 'v']);
// Shift+Click for multi-select
cy.getDataCy('selectable-item-1').realClick();
cy.getDataCy('selectable-item-3').realClick({ shiftKey: true });
cy.getDataCy('selected-count').should('contain', '3');
});
});
💻 API-Tests und Netzwerk-Interception javascript
Umfassende API-Testbeispiele einschließlich Request/Response-Interception, Stubbing, Authentifizierung und Performance-Tests mit Cypress
// API Testing and Network Interception with Cypress
// 1. Basic API Testing
// cypress/e2e/api/basic-api.cy.js
describe('Basic API Testing', () => {
const baseUrl = Cypress.env('apiUrl');
beforeEach(() => {
// Reset any network stubs
cy.intercept('GET', '**/api/**').as('apiCalls');
});
it('should test GET requests', () => {
cy.request('GET', `${baseUrl}/users`).then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.be.an('array');
expect(response.body).to.have.length.greaterThan(0);
// Validate user object structure
const user = response.body[0];
expect(user).to.have.property('id');
expect(user).to.have.property('name');
expect(user).to.have.property('email');
});
});
it('should test POST requests', () => {
const newUser = {
name: 'John Doe',
email: '[email protected]',
role: 'user'
};
cy.request('POST', `${baseUrl}/users`, newUser).then((response) => {
expect(response.status).to.eq(201);
expect(response.body).to.include(newUser);
expect(response.body).to.have.property('id');
});
});
it('should test PUT requests', () => {
const updatedUser = {
name: 'Jane Doe',
email: '[email protected]'
};
cy.request('PUT', `${baseUrl}/users/1`, updatedUser).then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.include(updatedUser);
});
});
it('should test DELETE requests', () => {
cy.request('DELETE', `${baseUrl}/users/1`).then((response) => {
expect(response.status).to.eq(204);
// Verify deletion
cy.request({
method: 'GET',
url: `${baseUrl}/users/1`,
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(404);
});
});
});
it('should handle API errors', () => {
cy.request({
method: 'GET',
url: `${baseUrl}/users/999`,
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(404);
expect(response.body).to.have.property('error');
});
});
});
// 2. Network Interception and Stubbing
// cypress/e2e/api/network-interception.cy.js
describe('Network Interception and Stubbing', () => {
beforeEach(() => {
cy.visit('/dashboard');
});
it('should intercept and stub API responses', () => {
// Intercept GET users request
cy.intercept('GET', '/api/users', {
statusCode: 200,
body: [
{ id: 1, name: 'John Doe', email: '[email protected]' },
{ id: 2, name: 'Jane Smith', email: '[email protected]' }
]
}).as('getUsers');
// Intercept POST user request
cy.intercept('POST', '/api/users', {
statusCode: 201,
body: { id: 3, name: 'New User', email: '[email protected]' }
}).as('createUser');
// Trigger API calls
cy.getDataCy('load-users').click();
cy.wait('@getUsers');
// Verify UI is updated with stubbed data
cy.getDataCy('user-list').should('contain', 'John Doe');
cy.getDataCy('user-list').should('contain', 'Jane Smith');
// Create new user
cy.getDataCy('add-user').click();
cy.wait('@createUser');
// Verify new user appears in list
cy.getDataCy('user-list').should('contain', 'New User');
});
it('should simulate API failures', () => {
// Intercept with error response
cy.intercept('GET', '/api/users', {
statusCode: 500,
body: { error: 'Internal server error' }
}).as('getUsersError');
cy.getDataCy('load-users').click();
cy.wait('@getUsersError');
// Verify error handling
cy.getDataCy('error-message').should('be.visible');
cy.getDataCy('error-message').should('contain', 'Failed to load users');
});
it('should use dynamic response based on request', () => {
cy.intercept('POST', '/api/auth/login', (req) => {
const { username, password } = req.body;
if (username === 'admin' && password === 'password') {
req.reply({
statusCode: 200,
body: {
token: 'fake-jwt-token',
user: { id: 1, username: 'admin', role: 'admin' }
}
});
} else {
req.reply({
statusCode: 401,
body: { error: 'Invalid credentials' }
});
}
}).as('login');
// Test successful login
cy.getDataCy('username').type('admin');
cy.getDataCy('password').type('password');
cy.getDataCy('login-button').click();
cy.wait('@login');
cy.url().should('not.include', '/login');
cy.getDataCy('user-info').should('contain', 'admin');
});
it('should handle network delays', () => {
cy.intercept('GET', '/api/slow-data', {
statusCode: 200,
body: { data: 'slow response data' },
delay: 2000 // 2 second delay
}).as('slowData');
cy.getDataCy('load-slow-data').click();
cy.getDataCy('loading-indicator').should('be.visible');
cy.wait('@slowData');
cy.getDataCy('loading-indicator').should('not.exist');
cy.getDataCy('data-display').should('contain', 'slow response data');
});
it('should intercept file uploads', () => {
cy.intercept('POST', '/api/upload', (req) => {
expect(req.headers['content-type']).to.include('multipart/form-data');
req.reply({
statusCode: 200,
body: {
success: true,
filename: 'test-file.pdf',
size: 1024
}
});
}).as('fileUpload');
cy.fixture('test-file.pdf').then(fileContent => {
cy.getDataCy('file-input').selectFile({
contents: Cypress.Buffer.from(fileContent),
fileName: 'test-file.pdf',
mimeType: 'application/pdf'
});
});
cy.getDataCy('upload-button').click();
cy.wait('@fileUpload');
cy.getDataCy('upload-success').should('be.visible');
cy.getDataCy('uploaded-filename').should('contain', 'test-file.pdf');
});
});
// 3. API Authentication Testing
// cypress/e2e/api/api-auth.cy.js
describe('API Authentication Testing', () => {
let authToken;
beforeEach(() => {
// Get auth token before each test
cy.request({
method: 'POST',
url: '/api/auth/login',
body: {
username: Cypress.env('apiUsername'),
password: Cypress.env('apiPassword')
}
}).then((response) => {
authToken = response.body.token;
});
});
it('should test authenticated endpoints', () => {
cy.request({
method: 'GET',
url: '/api/users/profile',
headers: {
'Authorization': `Bearer ${authToken}`
}
}).then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.have.property('user');
});
});
it('should reject requests without auth token', () => {
cy.request({
method: 'GET',
url: '/api/users/profile',
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(401);
expect(response.body).to.have.property('error');
});
});
it('should reject requests with invalid token', () => {
cy.request({
method: 'GET',
url: '/api/users/profile',
headers: {
'Authorization': 'Bearer invalid-token'
},
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(401);
});
});
it('should handle token refresh', () => {
// Initial request with expired token
cy.request({
method: 'GET',
url: '/api/users/profile',
headers: {
'Authorization': 'Bearer expired-token'
},
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(401);
});
// Token refresh flow
cy.request({
method: 'POST',
url: '/api/auth/refresh',
body: {
refreshToken: 'valid-refresh-token'
}
}).then((response) => {
expect(response.status).to.eq(200);
const newToken = response.body.token;
// Retry with new token
return cy.request({
method: 'GET',
url: '/api/users/profile',
headers: {
'Authorization': `Bearer ${newToken}`
}
});
}).then((response) => {
expect(response.status).to.eq(200);
});
});
});
// 4. API Performance Testing
// cypress/e2e/api/api-performance.cy.js
describe('API Performance Testing', () => {
it('should measure response times', () => {
const startTime = Date.now();
cy.request('GET', '/api/users').then((response) => {
const endTime = Date.now();
const responseTime = endTime - startTime;
expect(response.status).to.eq(200);
expect(responseTime).to.be.lessThan(1000); // Should respond within 1 second
cy.log(`Response time: ${responseTime}ms`);
});
});
it('should test API under load', () => {
const requests = [];
const numRequests = 10;
// Make multiple concurrent requests
for (let i = 0; i < numRequests; i++) {
requests.push(
cy.request({
method: 'GET',
url: '/api/data',
headers: { 'X-Request-ID': i }
})
);
}
// Wait for all requests to complete
Promise.all(requests).then((responses) => {
responses.forEach((response, index) => {
expect(response.status).to.eq(200);
expect(response.body).to.have.property('requestId', index);
});
cy.log(`Successfully completed ${numRequests} concurrent requests`);
});
});
it('should handle API rate limiting', () => {
const requests = [];
const numRequests = 20;
// Make requests rapidly to test rate limiting
for (let i = 0; i < numRequests; i++) {
requests.push(
cy.request({
method: 'GET',
url: '/api/rate-limited',
failOnStatusCode: false
})
);
}
Promise.all(requests).then((responses) => {
const successCount = responses.filter(r => r.status === 200).length;
const rateLimitCount = responses.filter(r => r.status === 429).length;
expect(successCount).to.be.greaterThan(0);
expect(rateLimitCount).to.be.greaterThan(0);
cy.log(`Success: ${successCount}, Rate limited: ${rateLimitCount}`);
});
});
});
// 5. GraphQL API Testing
// cypress/e2e/api/graphql-api.cy.js
describe('GraphQL API Testing', () => {
const graphqlUrl = '/graphql';
it('should test GraphQL queries', () => {
const query = {
query: `
query GetUsers {
users {
id
name
email
posts {
id
title
}
}
}
`
};
cy.request({
method: 'POST',
url: graphqlUrl,
body: query,
headers: {
'Content-Type': 'application/json'
}
}).then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.have.property('data');
expect(response.body.data).to.have.property('users');
expect(response.body.data.users).to.be.an('array');
const users = response.body.data.users;
if (users.length > 0) {
expect(users[0]).to.have.property('id');
expect(users[0]).to.have.property('name');
expect(users[0]).to.have.property('posts');
}
});
});
it('should test GraphQL mutations', () => {
const mutation = {
query: `
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
name
email
}
}
`,
variables: {
input: {
name: 'Test User',
email: '[email protected]'
}
}
};
cy.request({
method: 'POST',
url: graphqlUrl,
body: mutation,
headers: {
'Content-Type': 'application/json'
}
}).then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.have.property('data');
expect(response.body.data).to.have.property('createUser');
const createdUser = response.body.data.createUser;
expect(createdUser.name).to.eq('Test User');
expect(createdUser.email).to.eq('[email protected]');
expect(createdUser).to.have.property('id');
});
});
it('should handle GraphQL errors', () => {
const invalidQuery = {
query: `
query GetInvalidField {
users {
invalidField
}
}
`
};
cy.request({
method: 'POST',
url: graphqlUrl,
body: invalidQuery,
headers: {
'Content-Type': 'application/json'
},
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(400);
expect(response.body).to.have.property('errors');
expect(response.body.errors).to.be.an('array');
expect(response.body.errors[0]).to.have.property('message');
});
});
});
// 6. WebSockets Testing
// cypress/e2e/api/websockets.cy.js
describe('WebSocket Testing', () => {
it('should test WebSocket connections', () => {
cy.visit('/websocket-test');
// Wait for WebSocket connection to establish
cy.getDataCy('connection-status').should('contain', 'Connected');
// Send message through WebSocket
cy.getDataCy('message-input').type('Hello WebSocket!');
cy.getDataCy('send-button').click();
// Verify message is echoed back
cy.getDataCy('message-log').should('contain', 'Hello WebSocket!');
});
it('should handle WebSocket reconnection', () => {
cy.visit('/websocket-test');
// Simulate connection loss
cy.window().then((win) => {
win.websocket.close();
});
// Verify disconnection status
cy.getDataCy('connection-status').should('contain', 'Disconnected');
// Wait for reconnection
cy.getDataCy('connection-status').should('contain', 'Connected', { timeout: 10000 });
});
});
💻 Visuelle Regressionstests und Multi-Browser-Tests javascript
Visuelle Teststrategien einschließlich Screenshot-Vergleiche, Multi-Browser-Tests, Responsive-Design-Überprüfung und visuelle Regressions-Workflows mit Cypress
// Visual Regression Testing with Cypress
// 1. Basic Visual Testing Setup
// cypress.config.js - Visual testing configuration
const { defineConfig } = require('cypress');
module.exports = defineConfig({
e2e: {
// Visual testing configuration
screenshotOnRunFailure: true,
trashAssetsBeforeRuns: false,
video: false,
// Viewports for responsive testing
viewportWidth: 1280,
viewportHeight: 720,
// Environment variables for visual testing
env: {
// cypress-visual-regression plugin config
visualRegressionType: 'regression',
visualRegressionBaseDirectory: 'cypress/snapshots/base',
visualRegressionDiffDirectory: 'cypress/snapshots/diff',
visualRegressionGenerateDiff: {
viewport: true,
errorThreshold: 0.1
}
}
}
});
// cypress/support/commands.js - Visual testing commands
import { compareSnapshotCommand } from 'cypress-visual-regression/dist/command';
// Add visual comparison command
compareSnapshotCommand();
// Custom visual testing commands
Cypress.Commands.add('checkVisualRegression', (name, options = {}) => {
cy.get('body').compareSnapshot(name, {
capture: 'fullPage',
errorThreshold: 0.1,
...options
});
});
Cypress.Commands.add('checkElementVisualRegression', (selector, name, options = {}) => {
cy.get(selector).compareSnapshot(name, {
capture: 'viewport',
errorThreshold: 0.05,
...options
});
});
// Custom responsive testing command
Cypress.Commands.add('testResponsive', (callback, viewports = null) => {
const defaultViewports = [
{ name: 'mobile', width: 375, height: 667 },
{ name: 'tablet', width: 768, height: 1024 },
{ name: 'desktop', width: 1280, height: 720 },
{ name: 'large', width: 1920, height: 1080 }
];
const viewportsToTest = viewports || defaultViewports;
viewportsToTest.forEach((viewport) => {
cy.viewport(viewport.width, viewport.height);
cy.log(`Testing ${viewport.name} (${viewport.width}x${viewport.height})`);
if (typeof callback === 'function') {
callback(viewport);
}
});
});
// 2. Basic Visual Regression Tests
// cypress/e2e/visual/basic-visual.cy.js
describe('Basic Visual Regression Testing', () => {
beforeEach(() => {
cy.visit('/home');
});
it('should match homepage snapshot', () => {
cy.checkVisualRegression('homepage');
});
it('should match header element snapshot', () => {
cy.checkElementVisualRegression('[data-cy=header]', 'header-component');
});
it('should match hero section snapshot', () => {
cy.checkElementVisualRegression('[data-cy=hero-section]', 'hero-section');
});
it('should match product grid snapshot', () => {
cy.checkElementVisualRegression('[data-cy=product-grid]', 'product-grid');
});
it('should handle dynamic content with waiting', () => {
// Wait for dynamic content to load
cy.getDataCy('featured-products').should('be.visible');
cy.getDataCy('loading-spinner').should('not.exist');
cy.checkElementVisualRegression('[data-cy=featured-products]', 'featured-products');
});
it('should exclude dynamic elements from comparison', () => {
// Hide dynamic elements before taking snapshot
cy.getDataCy='current-time').hide();
cy.getDataCy('random-quote').hide();
cy.getDataCy('user-count').hide();
cy.checkVisualRegression('homepage-without-dynamic-content');
});
});
// 3. Responsive Design Testing
// cypress/e2e/visual/responsive-design.cy.js
describe('Responsive Design Testing', () => {
beforeEach(() => {
cy.visit('/responsive-test');
});
it('should look correct on all viewport sizes', () => {
cy.testResponsive((viewport) => {
cy.checkVisualRegression(`responsive-${viewport.name}`);
// Verify key elements are visible and properly positioned
cy.getDataCy('navigation-menu').should('be.visible');
cy.getDataCy('main-content').should('be.visible');
cy.getDataCy('footer').should('be.visible');
// Viewport-specific assertions
if (viewport.name === 'mobile') {
cy.getDataCy('mobile-menu-button').should('be.visible');
cy.getDataCy('desktop-menu').should('not.exist');
} else if (viewport.name === 'desktop') {
cy.getDataCy('desktop-menu').should('be.visible');
cy.getDataCy('mobile-menu-button').should('not.exist');
}
});
});
it('should handle navigation correctly across devices', () => {
cy.testResponsive((viewport) => {
cy.checkElementVisualRegression('[data-cy=navigation]', `navigation-${viewport.name}`);
// Test navigation interactions
if (viewport.name === 'mobile') {
cy.getDataCy('mobile-menu-button').click();
cy.getDataCy('mobile-menu').should('be.visible');
cy.checkElementVisualRegression('[data-cy=mobile-menu]', `mobile-menu-open-${viewport.name}`);
cy.getDataCy('mobile-menu-button').click();
} else {
cy.getDataCy('nav-link').first().realHover();
cy.checkElementVisualRegression('[data-cy=dropdown-menu]', `dropdown-${viewport.name}`);
}
});
});
it('should adapt card layouts appropriately', () => {
// Load test data
cy.intercept('GET', '/api/cards', { fixture: 'cards.json' }).as('getCards');
cy.wait('@getCards');
cy.testResponsive((viewport) => {
cy.checkElementVisualRegression('[data-cy=card-grid]', `card-grid-${viewport.name}`);
// Verify card count and layout
cy.getDataCy('card').should('have.length', 6);
if (viewport.name === 'mobile') {
// Should be single column
cy.getDataCy('card-grid').should('have.css', 'grid-template-columns', '1fr');
} else if (viewport.name === 'tablet') {
// Should be 2 columns
cy.getDataCy('card-grid').should('have.css', 'grid-template-columns', '1fr 1fr');
} else {
// Should be 3 columns
cy.getDataCy('card-grid').should('have.css', 'grid-template-columns', '1fr 1fr 1fr');
}
});
});
});
// 4. Cross-Browser Testing
// cypress/e2e/visual/cross-browser.cy.js
describe('Cross-Browser Visual Testing', () => {
const browsers = ['chrome', 'firefox', 'edge'];
browsers.forEach((browser) => {
describe(`${browser.charAt(0).toUpperCase() + browser.slice(1)} Browser`, () => {
beforeEach(() => {
cy.visit('/cross-browser-test');
});
it('should render consistently across browsers', () => {
cy.checkVisualRegression(`cross-browser-${browser}`);
// Browser-specific assertions if needed
if (browser === 'firefox') {
// Firefox-specific rendering quirks
cy.getDataCy('firefox-specific-element').should('exist');
}
});
it('should handle CSS Grid correctly', () => {
cy.checkElementVisualRegression('[data-cy=grid-layout]', `grid-${browser}`);
});
it('should handle Flexbox correctly', () => {
cy.checkElementVisualRegression('[data-cy=flex-layout]', `flex-${browser}`);
});
it('should handle custom fonts correctly', () => {
// Wait for fonts to load
cy.document().its('fonts.ready').should('be.true');
cy.checkElementVisualRegression('[data-cy=typography]', `typography-${browser}`);
});
});
});
});
// 5. Interactive Component Visual Testing
// cypress/e2e/visual/interactive-components.cy.js
describe('Interactive Component Visual Testing', () => {
beforeEach(() => {
cy.visit('/interactive-components');
});
it('should capture button states', () => {
const button = cy.getDataCy('interactive-button');
// Default state
button.compareSnapshot('button-default');
// Hover state
button.realHover();
button.compareSnapshot('button-hover');
// Active/pressed state
button.realClick();
button.compareSnapshot('button-active');
// Focus state
button.focus();
button.compareSnapshot('button-focus');
// Disabled state
cy.getDataCy('disable-button').click();
button.should('be.disabled');
button.compareSnapshot('button-disabled');
});
it('should capture form field states', () => {
const input = cy.getDataCy('text-input');
// Empty state
input.compareSnapshot('input-empty');
// Focused state
input.focus();
input.compareSnapshot('input-focused');
// With text
input.type('Test input value');
input.compareSnapshot('input-with-text');
// Error state
input.blur();
cy.getDataCy('submit-form').click();
cy.getDataCy('input-error').should('be.visible');
input.compareSnapshot('input-error');
});
it('should capture modal/dialog states', () => {
// Before opening
cy.checkVisualRegression('modal-closed');
// Open modal
cy.getDataCy('open-modal').click();
cy.getDataCy('modal').should('be.visible');
cy.checkElementVisualRegression('[data-cy=modal]', 'modal-open');
// With content loaded
cy.wait(1000); // Wait for any animations
cy.checkElementVisualRegression('[data-cy=modal-content]', 'modal-with-content');
// Close modal
cy.getDataCy('close-modal').click();
cy.getDataCy('modal').should('not.exist');
cy.checkVisualRegression('modal-after-close');
});
it('should capture loading states', () => {
cy.intercept('GET', '/api/data', {
statusCode: 200,
body: { data: 'test' },
delay: 2000
}).as('getData');
// Initial state
cy.checkVisualRegression('loading-initial');
// Click to trigger loading
cy.getDataCy('load-data-button').click();
// Loading state
cy.getDataCy('loading-spinner').should('be.visible');
cy.checkElementVisualRegression('[data-cy=loading-spinner]', 'loading-spinner');
// After loading completes
cy.wait('@getData');
cy.getDataCy('loading-spinner').should('not.exist');
cy.getDataCy('data-content').should('be.visible');
cy.checkElementVisualRegression('[data-cy=data-content]', 'data-loaded');
});
});
// 6. Dark Mode and Theme Testing
// cypress/e2e/visual/theme-testing.cy.js
describe('Theme and Dark Mode Testing', () => {
const themes = ['light', 'dark', 'auto'];
themes.forEach((theme) => {
describe(`${theme.charAt(0).toUpperCase() + theme.slice(1)} Theme`, () => {
beforeEach(() => {
cy.visit('/theme-test');
// Set theme
if (theme !== 'auto') {
cy.getDataCy('theme-toggle').click();
cy.getDataCy(`theme-${theme}`).click();
}
});
it('should match snapshot for theme', () => {
cy.checkVisualRegression(`theme-${theme}`);
});
it('should handle color scheme correctly', () => {
// Test color scheme meta tag
if (theme === 'dark') {
cy.get('html').should('have.class', 'dark');
cy.get('meta[name="color-scheme"]').should('have.attr', 'content', 'dark');
} else {
cy.get('html').should('not.have.class', 'dark');
cy.get('meta[name="color-scheme"]').should('have.attr', 'content', 'light');
}
cy.checkElementVisualRegression('[data-cy=color-scheme-demo]', `colors-${theme}`);
});
it('should handle images and icons in different themes', () => {
cy.checkElementVisualRegression('[data-cy=icon-set]', `icons-${theme}`);
cy.checkElementVisualRegression('[data-cy=image-gallery]', `images-${theme}`);
});
it('should handle text readability', () => {
// Check contrast ratios (visual check through snapshot)
cy.checkElementVisualRegression('[data-cy=text-content]', `text-${theme}`);
// Ensure text is legible (basic automated check)
cy.getDataCy('heading').should('be.visible');
cy.getDataCy('body-text').should('be.visible');
cy.getDataCy('small-text').should('be.visible');
});
});
});
});
// 7. Performance-Related Visual Testing
// cypress/e2e/visual/performance-visual.cy.js
describe('Performance-Related Visual Testing', () => {
it('should handle lazy loaded images', () => {
cy.visit('/lazy-loading');
// Before images load
cy.checkVisualRegression('lazy-loading-before');
// Scroll to trigger lazy loading
cy.getDataCy('lazy-images-container').scrollIntoView();
// Wait for images to load
cy.getDataCy('lazy-image').each(($img) => {
cy.wrap($img).should('be.visible').and('have.prop', 'naturalWidth').and('be.greaterThan', 0);
});
// After images load
cy.checkVisualRegression('lazy-loading-after');
});
it('should handle skeleton loading states', () => {
cy.visit('/skeleton-loading');
// Initial skeleton state
cy.checkElementVisualRegression('[data-cy=skeleton-loader]', 'skeleton-loading');
// Wait for content to load
cy.intercept('GET', '/api/content', { fixture: 'content.json', delay: 1500 }).as('getContent');
cy.wait('@getContent');
// Content loaded state
cy.getDataCy('skeleton-loader').should('not.exist');
cy.getDataCy('loaded-content').should('be.visible');
cy.checkElementVisualRegression('[data-cy=loaded-content]', 'content-loaded');
});
it('should handle progressive image loading', () => {
cy.visit('/progressive-images');
// Low-quality placeholder
cy.checkElementVisualRegression('[data-cy=progressive-image]', 'image-placeholder');
// Wait for high-quality image
cy.getDataCy('progressive-image').should('have.attr', 'data-loaded', 'true');
// High-quality final image
cy.checkElementVisualRegression('[data-cy=progressive-image]', 'image-final');
});
});
// 8. Advanced Visual Testing Utilities
// cypress/support/visual-utils.js
export class VisualTestingUtils {
// Helper to hide dynamic elements
static hideDynamicElements() {
const dynamicSelectors = [
'[data-cy=current-time]',
'[data-cy=random-number]',
'[data-cy=user-avatar]',
'.ad-banner',
'.notification-toast'
];
dynamicSelectors.forEach(selector => {
cy.get(selector).invoke('hide').should('be.hidden');
});
}
// Helper to wait for images to load
static waitForImagesToLoad() {
cy.get('img').each(($img) => {
cy.wrap($img)
.should('have.attr', 'src')
.and('not.have.attr', 'src', '')
.and('have.prop', 'complete', true)
.and('have.prop', 'naturalWidth').and('be.greaterThan', 0);
});
}
// Helper to test color schemes
static testColorScheme(theme) {
cy.document().its('documentElement.style')
.should('include', theme === 'dark' ? 'background-color: rgb(17, 24, 39)' : 'background-color: rgb(255, 255, 255)');
}
// Helper to capture multiple screenshots for animation testing
static captureAnimationFrames(selector, name, duration = 1000, interval = 100) {
const frames = Math.ceil(duration / interval);
for (let i = 0; i < frames; i++) {
cy.wait(interval);
cy.get(selector).compareSnapshot(`${name}-frame-${String(i).padStart(3, '0')}`);
}
}
// Helper to compare across viewports
static compareAcrossViewports(selector, name, viewports) {
viewports.forEach((viewport, index) => {
cy.viewport(viewport.width, viewport.height);
cy.get(selector).compareSnapshot(`${name}-viewport-${index}`);
});
}
// Helper for accessibility visual testing
static checkAccessibilityVisuals() {
// Focus states
cy.getDataCy('focusable-elements').each(($el) => {
cy.wrap($el).focus();
cy.wrap($el).compareSnapshot(`focus-${$el[0].tagName.toLowerCase()}-${$el.attr('data-cy')}`);
cy.wrap($el).blur();
});
// High contrast mode
cy.window().then((win) => {
win.matchMedia('(prefers-contrast: high)').matches = true;
win.dispatchEvent(new Event('change'));
});
cy.checkVisualRegression('high-contrast-mode');
}
}
// Export for use in tests
window.VisualTestingUtils = VisualTestingUtils;