End-to-End (E2E) Tests simulieren echte Benutzerinteraktionen und testen die gesamte Anwendung von der Benutzeroberfläche bis zur Datenebene. Cypress hat sich als führendes Framework für E2E-Testing in modernen Webanwendungen etabliert und bietet eine entwicklerfreundliche Alternative zu traditionellen Selenium-basierten Lösungen.
End-to-End Tests validieren komplette Benutzerszenarien und überprüfen, ob alle Komponenten einer Anwendung korrekt zusammenarbeiten. Sie testen die Anwendung aus der Perspektive des Endbenutzers und decken dabei Frontend, Backend, Datenbank und externe Services ab.
E2E Tests bilden die Spitze der Testpyramide. Sie sind langsamer und wartungsintensiver als Unit Tests oder Integration Tests, bieten aber die höchste Konfidenz, dass die Anwendung wie erwartet funktioniert.
// Typisches E2E-Szenario: Benutzer-Registrierung
// 1. Benutzer öffnet Registrierungsseite
// 2. Füllt Formular aus
// 3. Klickt auf "Registrieren"
// 4. System erstellt Account
// 5. Benutzer wird zur Dashboard-Seite weitergeleitet
// 6. Bestätigungs-E-Mail wird versendetCypress unterscheidet sich fundamental von traditionellen E2E-Tools durch seine Architektur und Entwicklererfahrung.
Im Gegensatz zu Selenium läuft Cypress direkt im Browser und kommuniziert mit der Anwendung über eine native JavaScript-Umgebung. Dies ermöglicht direkten Zugriff auf DOM-Elemente, Netzwerk-Traffic und Browser-APIs.
npm install --save-dev cypressGrundlegende package.json-Konfiguration:
{
"scripts": {
"cypress:open": "cypress open",
"cypress:run": "cypress run",
"e2e": "cypress run --spec 'cypress/e2e/**/*.cy.js'"
},
"devDependencies": {
"cypress": "^13.0.0"
}
}Cypress-Projektstruktur nach der Initialisierung:
cypress/
├── e2e/
│ └── spec.cy.js
├── fixtures/
│ └── example.json
├── support/
│ ├── commands.js
│ └── e2e.js
└── cypress.config.js
// cypress.config.js
const { defineConfig } = require('cypress');
module.exports = defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
viewportWidth: 1280,
viewportHeight: 720,
video: true,
screenshotOnRunFailure: true,
setupNodeEvents(on, config) {
// Task-Handler für Backend-Operationen
on('task', {
seedDatabase() {
// Datenbank für Tests vorbereiten
return null;
},
clearDatabase() {
// Testdaten nach Tests aufräumen
return null;
}
});
},
},
});// basic-interactions.cy.js
describe('Basic User Interactions', () => {
beforeEach(() => {
cy.visit('/login');
});
it('should log in user successfully', () => {
// Eingabefelder finden und ausfüllen
cy.get('[data-testid="email-input"]').type('user@example.com');
cy.get('[data-testid="password-input"]').type('securepassword');
// Submit-Button klicken
cy.get('[data-testid="login-button"]').click();
// Weiterleitung überprüfen
cy.url().should('include', '/dashboard');
cy.contains('Welcome back!').should('be.visible');
});
it('should show error for invalid credentials', () => {
cy.get('[data-testid="email-input"]').type('invalid@example.com');
cy.get('[data-testid="password-input"]').type('wrongpassword');
cy.get('[data-testid="login-button"]').click();
cy.get('[data-testid="error-message"]')
.should('be.visible')
.and('contain', 'Invalid credentials');
});
});// form-testing.cy.js
describe('Contact Form', () => {
it('should submit contact form successfully', () => {
cy.visit('/contact');
// Formular ausfüllen
cy.get('#name').type('John Doe');
cy.get('#email').type('john@example.com');
cy.get('#subject').select('General Inquiry');
cy.get('#message').type('This is a test message from Cypress.');
// Checkbox aktivieren
cy.get('#newsletter').check();
// Formular absenden
cy.get('form').submit();
// Erfolgsbestätigung überprüfen
cy.get('.success-message')
.should('be.visible')
.and('contain', 'Thank you for your message');
// Formular sollte zurückgesetzt sein
cy.get('#name').should('have.value', '');
});
it('should validate required fields', () => {
cy.visit('/contact');
// Formular ohne Eingaben absenden
cy.get('form').submit();
// Validierungsfehler überprüfen
cy.get('#name:invalid').should('exist');
cy.get('#email:invalid').should('exist');
// Fehlertooltips
cy.get('#name').then(($input) => {
expect($input[0].validationMessage).to.contain('Please fill out this field');
});
});
});// navigation.cy.js
describe('Application Navigation', () => {
it('should navigate through main sections', () => {
cy.visit('/');
// Hauptnavigation testen
cy.get('[data-testid="nav-products"]').click();
cy.url().should('include', '/products');
cy.get('h1').should('contain', 'Our Products');
cy.get('[data-testid="nav-about"]').click();
cy.url().should('include', '/about');
cy.get('h1').should('contain', 'About Us');
// Breadcrumb-Navigation
cy.get('.breadcrumb').should('contain', 'Home > About');
// Browser-Navigation
cy.go('back');
cy.url().should('include', '/products');
});
it('should handle deep linking correctly', () => {
// Direkter Aufruf einer Unterseite
cy.visit('/products/laptop-123');
cy.get('[data-testid="product-title"]').should('be.visible');
cy.get('[data-testid="product-price"]').should('contain', '$');
cy.get('[data-testid="add-to-cart"]').should('be.enabled');
});
});// api-testing.cy.js
describe('API Integration', () => {
beforeEach(() => {
// API-Aufrufe abfangen und mocken
cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers');
cy.intercept('POST', '/api/users', {
statusCode: 201,
body: { id: 123, name: 'New User' }
}).as('createUser');
});
it('should load user list from API', () => {
cy.visit('/users');
// Warten auf API-Aufruf
cy.wait('@getUsers');
// UI-Aktualisierung überprüfen
cy.get('[data-testid="user-list"]').should('be.visible');
cy.get('[data-testid="user-item"]').should('have.length', 3);
});
it('should create new user via API', () => {
cy.visit('/users/new');
cy.get('#name').type('John Doe');
cy.get('#email').type('john@example.com');
cy.get('form').submit();
cy.wait('@createUser').then((interception) => {
expect(interception.request.body).to.deep.include({
name: 'John Doe',
email: 'john@example.com'
});
});
cy.get('.success-notification').should('be.visible');
});
});// cypress/support/commands.js
Cypress.Commands.add('login', (email, password) => {
cy.session([email, password], () => {
cy.visit('/login');
cy.get('[data-testid="email-input"]').type(email);
cy.get('[data-testid="password-input"]').type(password);
cy.get('[data-testid="login-button"]').click();
cy.url().should('include', '/dashboard');
});
});
Cypress.Commands.add('addToCart', (productId, quantity = 1) => {
cy.visit(`/products/${productId}`);
if (quantity > 1) {
cy.get('[data-testid="quantity-input"]').clear().type(quantity.toString());
}
cy.get('[data-testid="add-to-cart"]').click();
cy.get('[data-testid="cart-count"]').should('contain', quantity);
});
Cypress.Commands.add('clearCart', () => {
cy.visit('/cart');
cy.get('body').then(($body) => {
if ($body.find('[data-testid="clear-cart"]').length > 0) {
cy.get('[data-testid="clear-cart"]').click();
cy.get('[data-testid="confirm-clear"]').click();
}
});
});// e-commerce-flow.cy.js
describe('E-Commerce User Journey', () => {
beforeEach(() => {
cy.clearCart();
cy.login('customer@example.com', 'password123');
});
it('should complete purchase flow', () => {
// Produkte zum Warenkorb hinzufügen
cy.addToCart('laptop-123', 1);
cy.addToCart('mouse-456', 2);
// Zum Checkout
cy.visit('/cart');
cy.get('[data-testid="checkout-button"]').click();
// Lieferadresse eingeben
cy.get('#shipping-address').type('123 Main St, City, State 12345');
cy.get('#shipping-method').select('standard');
// Zahlungsinformationen
cy.get('#card-number').type('4111111111111111');
cy.get('#expiry').type('12/25');
cy.get('#cvv').type('123');
// Bestellung abschließen
cy.get('[data-testid="place-order"]').click();
// Bestätigung überprüfen
cy.url().should('include', '/order-confirmation');
cy.get('[data-testid="order-number"]').should('be.visible');
cy.get('[data-testid="order-total"]').should('contain', '$');
});
});// file-operations.cy.js
describe('File Operations', () => {
it('should upload profile picture', () => {
cy.visit('/profile');
// Datei-Upload
cy.get('input[type="file"]').selectFile('cypress/fixtures/profile-pic.jpg');
cy.get('[data-testid="upload-button"]').click();
// Upload-Fortschritt
cy.get('.upload-progress').should('be.visible');
cy.get('.upload-success', { timeout: 10000 }).should('be.visible');
// Hochgeladenes Bild überprüfen
cy.get('[data-testid="profile-image"]')
.should('be.visible')
.and('have.attr', 'src')
.and('include', 'profile-pic');
});
it('should download report file', () => {
cy.visit('/reports');
// Download auslösen
cy.get('[data-testid="download-report"]').click();
// Datei-Download überprüfen
cy.readFile('cypress/downloads/report.pdf').should('exist');
});
});// cypress/support/pages/LoginPage.js
class LoginPage {
visit() {
cy.visit('/login');
return this;
}
fillEmail(email) {
cy.get('[data-testid="email-input"]').type(email);
return this;
}
fillPassword(password) {
cy.get('[data-testid="password-input"]').type(password);
return this;
}
submit() {
cy.get('[data-testid="login-button"]').click();
return this;
}
shouldShowError(message) {
cy.get('[data-testid="error-message"]')
.should('be.visible')
.and('contain', message);
return this;
}
shouldRedirectToDashboard() {
cy.url().should('include', '/dashboard');
return this;
}
}
export default LoginPage;// login-with-page-object.cy.js
import LoginPage from '../support/pages/LoginPage';
describe('Login with Page Object', () => {
const loginPage = new LoginPage();
it('should login successfully', () => {
loginPage
.visit()
.fillEmail('user@example.com')
.fillPassword('password123')
.submit()
.shouldRedirectToDashboard();
});
it('should show error for invalid credentials', () => {
loginPage
.visit()
.fillEmail('invalid@example.com')
.fillPassword('wrongpassword')
.submit()
.shouldShowError('Invalid credentials');
});
});// cypress/fixtures/testData.js
export const users = {
admin: {
email: 'admin@example.com',
password: 'admin123',
role: 'administrator'
},
customer: {
email: 'customer@example.com',
password: 'customer123',
role: 'customer'
},
guest: {
email: 'guest@example.com',
password: 'guest123',
role: 'guest'
}
};
export const products = {
laptop: {
id: 'laptop-123',
name: 'Gaming Laptop',
price: 1299.99,
category: 'Electronics'
},
book: {
id: 'book-456',
name: 'JavaScript Guide',
price: 29.99,
category: 'Books'
}
};// data-driven-tests.cy.js
import { users, products } from '../fixtures/testData';
describe('Data-Driven E2E Tests', () => {
Object.entries(users).forEach(([userType, userData]) => {
it(`should allow ${userType} to browse products`, () => {
cy.login(userData.email, userData.password);
cy.visit('/products');
cy.get('[data-testid="product-grid"]').should('be.visible');
cy.get('[data-testid="product-item"]').should('have.length.greaterThan', 0);
if (userData.role === 'admin') {
cy.get('[data-testid="admin-panel"]').should('be.visible');
}
});
});
});// performance-optimized.cy.js
describe('Performance Optimized Tests', () => {
before(() => {
// Einmalige Setup-Operationen
cy.task('seedDatabase');
});
beforeEach(() => {
// Session-basierte Authentifizierung für schnellere Tests
cy.session('user-session', () => {
cy.login('user@example.com', 'password123');
});
});
it('should load dashboard quickly', () => {
cy.visit('/dashboard');
// Performance-Metriken
cy.window().its('performance').then((performance) => {
const loadTime = performance.timing.loadEventEnd - performance.timing.navigationStart;
expect(loadTime).to.be.lessThan(3000); // Max 3 Sekunden
});
// Kritische Elemente sollten schnell laden
cy.get('[data-testid="main-content"]', { timeout: 2000 }).should('be.visible');
});
});// stable-tests.cy.js
describe('Stable Test Practices', () => {
it('should wait for dynamic content properly', () => {
cy.visit('/dynamic-content');
// Warten auf asynchrone Daten
cy.get('[data-testid="loading-spinner"]').should('exist');
cy.get('[data-testid="loading-spinner"]').should('not.exist');
// Explizite Wartezeiten für Animationen
cy.get('[data-testid="animated-element"]')
.should('be.visible')
.wait(500); // Animation abwarten
// Retry-fähige Assertions
cy.get('[data-testid="dynamic-list"]')
.should('exist')
.and('contain.text', 'Item 1');
});
it('should handle race conditions properly', () => {
cy.intercept('GET', '/api/data', { delay: 1000, fixture: 'data.json' }).as('getData');
cy.visit('/data-page');
cy.wait('@getData'); // Warten auf API-Response
cy.get('[data-testid="data-table"]').should('be.visible');
});
});// debugging.cy.js
describe('Debugging Examples', () => {
it('should provide debugging information', () => {
cy.visit('/complex-page');
// Debug-Ausgaben
cy.get('[data-testid="user-info"]').then(($el) => {
cy.log('User info element:', $el.text());
});
// Breakpoints für interaktives Debugging
cy.get('[data-testid="problem-element"]').debug();
// Screenshots für Fehleranalyse
cy.screenshot('before-action');
cy.get('[data-testid="action-button"]').click();
cy.screenshot('after-action');
// Pause für manuelle Inspektion (nur bei cy.open())
// cy.pause();
});
});// error-handling.cy.js
describe('Error Handling', () => {
it('should handle network errors gracefully', () => {
// Netzwerkfehler simulieren
cy.intercept('GET', '/api/critical-data', { forceNetworkError: true }).as('networkError');
cy.visit('/page-with-api-dependency');
// Fehlerbehandlung der Anwendung testen
cy.get('[data-testid="error-message"]')
.should('be.visible')
.and('contain', 'Unable to load data');
cy.get('[data-testid="retry-button"]').should('be.visible');
});
it('should recover from errors', () => {
cy.intercept('GET', '/api/data', { statusCode: 500 }).as('serverError');
cy.visit('/data-page');
cy.wait('@serverError');
// Error-State überprüfen
cy.get('[data-testid="error-state"]').should('be.visible');
// Recovery-Mechanismus testen
cy.intercept('GET', '/api/data', { fixture: 'data.json' }).as('successResponse');
cy.get('[data-testid="retry-button"]').click();
cy.wait('@successResponse');
cy.get('[data-testid="data-content"]').should('be.visible');
});
});Cypress Vorteile:
Selenium Vorteile:
// Cypress: Natürliche JavaScript-Syntax
cy.get('[data-testid="button"]').click();
cy.url().should('include', '/success');
// Selenium WebDriver: Mehr Boilerplate
// const button = await driver.findElement(By.css('[data-testid="button"]'));
// await button.click();
// const currentUrl = await driver.getCurrentUrl();
// assert(currentUrl.includes('/success'));# .github/workflows/e2e-tests.yml
name: E2E Tests
on: [push, pull_request]
jobs:
e2e-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Start application
run: npm start &
- name: Wait for server
run: npx wait-on http://localhost:3000
- name: Run Cypress tests
uses: cypress-io/github-action@v5
with:
wait-on: 'http://localhost:3000'
wait-on-timeout: 120
- name: Upload screenshots
uses: actions/upload-artifact@v3
if: failure()
with:
name: cypress-screenshots
path: cypress/screenshots