Zypress Cheatsheet¶
Cypress - End-to-End Testing Made Easy
Cypress ist ein vorderes Testwerkzeug der nächsten Generation, das für das moderne Web entwickelt wurde. Es behandelt die wichtigsten Schmerzpunkte Entwickler und QA-Ingenieure Gesicht bei der Prüfung moderner Anwendungen. Cypress ermöglicht es Ihnen, alle Arten von Tests zu schreiben: End-to-End-Tests, Integrationstests, Einzeltests. < p>
Inhaltsverzeichnis¶
- [Installation](LINK_0 -%20(LINK_0)
- (LINK_0)
- Sektoren
- [Assertions](LINK_0_ -%20Netzwerktest
- [File Operationen](LINK_0_ -%20Kundenbefehle
- [Konfiguration](LINK_0__ -%20(LINK_0_)
- [API Testing](LINK_0_ -%20Visuelle%20Prüfung
- [CI/CD Integration](LINK_0__ -%20Beste%20Praktiken
- [Debugging](LINK_0_ -%20[Performance](LINK_0 -%20(LINK_0_)
Installation¶
Einfache Installation¶
# Install Cypress via npm
npm install --save-dev cypress
# Install Cypress via yarn
yarn add --dev cypress
# Open Cypress for the first time
npx cypress open
# Run Cypress in headless mode
npx cypress run
```_
### Projektaufbau
```bash
# Initialize new project with Cypress
mkdir my-cypress-project
cd my-cypress-project
npm init -y
npm install --save-dev cypress
# Open Cypress (creates folder structure)
npx cypress open
```_
### Ordnerstruktur
### Paket.json Scripts
```json
{
"scripts": {
"cypress:open": "cypress open",
"cypress:run": "cypress run",
"cypress:run:chrome": "cypress run --browser chrome",
"cypress:run:firefox": "cypress run --browser firefox",
"cypress:run:edge": "cypress run --browser edge",
"cypress:run:headed": "cypress run --headed",
"cypress:run:record": "cypress run --record --key=your-record-key"
}
}
```_
## Erste Schritte
### Erster Test
```javascript
// cypress/e2e/first-test.cy.js
describe('My First Test', () => {
it('Visits the Kitchen Sink', () => {
cy.visit('https://example.cypress.io')
cy.contains('type').click()
cy.url().should('include', '/commands/actions')
cy.get('.action-email')
.type('fake@email.com')
.should('have.value', 'fake@email.com')
})
})
```_
### Grundteststruktur
```javascript
describe('Test Suite Name', () => {
beforeEach(() => {
// Runs before each test
cy.visit('/login')
})
it('should do something', () => {
// Test implementation
cy.get('[data-cy=username]').type('user@example.com')
cy.get('[data-cy=password]').type('password123')
cy.get('[data-cy=submit]').click()
cy.url().should('include', '/dashboard')
})
it('should do something else', () => {
// Another test
})
})
```_
### Prüfhaken
```javascript
describe('Hooks Example', () => {
before(() => {
// Runs once before all tests in the block
cy.task('seedDatabase')
})
after(() => {
// Runs once after all tests in the block
cy.task('clearDatabase')
})
beforeEach(() => {
// Runs before each test in the block
cy.login('user@example.com', 'password123')
})
afterEach(() => {
// Runs after each test in the block
cy.clearCookies()
cy.clearLocalStorage()
})
it('should test something', () => {
// Test implementation
})
})
```_
## Grundlegende Befehle
### Navigation
```javascript
// Visit a URL
cy.visit('https://example.com')
cy.visit('/relative-path')
cy.visit('/', { timeout: 30000 })
// Navigate browser history
cy.go('back')
cy.go('forward')
cy.go(-1) // Go back one page
cy.go(1) // Go forward one page
// Reload page
cy.reload()
cy.reload(true) // Force reload
```_
### Element Interaktion
```javascript
// Click elements
cy.get('button').click()
cy.get('button').click({ force: true }) // Force click
cy.get('button').dblclick() // Double click
cy.get('button').rightclick() // Right click
// Type text
cy.get('input').type('Hello World')
cy.get('input').type('user@example.com{enter}')
cy.get('input').type('{selectall}New Text')
cy.get('input').clear().type('New Text')
// Select options
cy.get('select').select('Option 1')
cy.get('select').select(['Option 1', 'Option 2']) // Multiple
cy.get('select').select('value', { force: true })
// Check/uncheck
cy.get('input[type="checkbox"]').check()
cy.get('input[type="checkbox"]').uncheck()
cy.get('input[type="radio"]').check()
// File upload
cy.get('input[type="file"]').selectFile('path/to/file.pdf')
cy.get('input[type="file"]').selectFile(['file1.jpg', 'file2.jpg'])
```_
### Element Quers
```javascript
// Get elements
cy.get('.class-name')
cy.get('#id')
cy.get('[data-cy="element"]')
cy.get('button').first()
cy.get('button').last()
cy.get('button').eq(2) // Third button (0-indexed)
// Find within elements
cy.get('.parent').find('.child')
cy.get('.parent').within(() => {
cy.get('.child').click()
})
// Filter elements
cy.get('button').filter('.active')
cy.get('button').not('.disabled')
// Contains text
cy.contains('Submit')
cy.contains('button', 'Submit')
cy.get('button').contains('Submit')
```_
### Traversen
```javascript
// Parent/child relationships
cy.get('.child').parent()
cy.get('.element').parents('.ancestor')
cy.get('.parent').children()
cy.get('.parent').children('.specific-child')
// Siblings
cy.get('.element').siblings()
cy.get('.element').next()
cy.get('.element').prev()
cy.get('.element').nextAll()
cy.get('.element').prevAll()
// Closest
cy.get('.element').closest('.container')
```_
### Warten
```javascript
// Wait for element
cy.get('.loading').should('not.exist')
cy.get('.content').should('be.visible')
// Wait for specific time (not recommended)
cy.wait(1000)
// Wait for network requests
cy.intercept('GET', '/api/users').as('getUsers')
cy.wait('@getUsers')
// Wait for multiple requests
cy.wait(['@getUsers', '@getPosts'])
```_
## Wähler
### CSS-Auswahlen
```javascript
// Basic selectors
cy.get('button') // Tag
cy.get('.btn') // Class
cy.get('#submit') // ID
cy.get('[type="submit"]') // Attribute
// Combinators
cy.get('form button') // Descendant
cy.get('form > button') // Direct child
cy.get('label + input') // Adjacent sibling
cy.get('h2 ~ p') // General sibling
// Pseudo-selectors
cy.get('button:first')
cy.get('button:last')
cy.get('button:nth-child(2)')
cy.get('input:checked')
cy.get('input:disabled')
cy.get('a:contains("Click me")')
```_
### Daten-Attribute (empfohlen)
```javascript
// HTML
// <button data-cy="submit-btn">Submit</button>
// <input data-testid="username" />
// <div data-test="user-profile">...</div>
// Cypress tests
cy.get('[data-cy="submit-btn"]')
cy.get('[data-testid="username"]')
cy.get('[data-test="user-profile"]')
// Custom command for data-cy
Cypress.Commands.add('dataCy', (value) => {
return cy.get(`[data-cy=${value}]`)
})
// Usage
cy.dataCy('submit-btn').click()
```_
### XPath (mit Plugin)
```bash
# Install cypress-xpath plugin
npm install --save-dev cypress-xpath
```_
```javascript
// cypress/support/e2e.js
require('cypress-xpath')
// Usage in tests
cy.xpath('//button[contains(text(), "Submit")]')
cy.xpath('//input[@placeholder="Username"]')
cy.xpath('//div[@class="container"]//button[1]')
```_
### Komplexe Auswähler
```javascript
// Multiple attributes
cy.get('input[type="email"][required]')
// Partial attribute matching
cy.get('[class*="btn"]') // Contains
cy.get('[class^="btn"]') // Starts with
cy.get('[class$="primary"]') // Ends with
// Multiple selectors
cy.get('button, input[type="submit"]')
// Chaining selectors
cy.get('.form-group')
.find('input')
.filter('[required]')
.first()
```_
## Beschwörung
### Sollte es sein
```javascript
// Existence
cy.get('button').should('exist')
cy.get('.loading').should('not.exist')
// Visibility
cy.get('button').should('be.visible')
cy.get('.modal').should('not.be.visible')
// Text content
cy.get('h1').should('contain', 'Welcome')
cy.get('h1').should('have.text', 'Welcome to our site')
cy.get('p').should('include.text', 'partial text')
// Attributes
cy.get('input').should('have.attr', 'placeholder', 'Enter email')
cy.get('button').should('have.class', 'btn-primary')
cy.get('input').should('have.value', 'test@example.com')
// CSS properties
cy.get('button').should('have.css', 'background-color', 'rgb(0, 123, 255)')
cy.get('div').should('have.css', 'display', 'none')
// Length
cy.get('li').should('have.length', 5)
cy.get('li').should('have.length.greaterThan', 3)
cy.get('li').should('have.length.lessThan', 10)
```_
### Erwarten Sie Hilfe
```javascript
// Using expect (Chai)
cy.get('input').then(($input) => {
expect($input).to.have.value('expected value')
expect($input[0].validity.valid).to.be.true
})
// Multiple assertions
cy.get('button').then(($btn) => {
expect($btn).to.have.class('btn')
expect($btn).to.contain('Submit')
expect($btn).to.be.visible
})
// Custom assertions
cy.get('.user-list').then(($list) => {
const users = $list.find('.user-item')
expect(users).to.have.length.above(0)
expect(users.first()).to.contain('John Doe')
})
```_
### Seite nicht gefunden
```javascript
// URL should include
cy.url().should('include', '/dashboard')
cy.url().should('eq', 'https://example.com/dashboard')
// URL should match pattern
cy.url().should('match', /\/users\/\d+/)
// Location assertions
cy.location('pathname').should('eq', '/dashboard')
cy.location('search').should('eq', '?tab=profile')
cy.location('hash').should('eq', '#section1')
```_
### Zollanmeldungen
```javascript
// Add custom assertion
chai.Assertion.addMethod('beValidEmail', function() {
const obj = this._obj
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
this.assert(
emailRegex.test(obj),
'expected #{this} to be a valid email',
'expected #{this} not to be a valid email',
true
)
})
// Usage
cy.get('input[type="email"]')
.invoke('val')
.should('beValidEmail')
```_
## Netzwerktests
### Anträge auf Aufnahme
```javascript
// Basic intercept
cy.intercept('GET', '/api/users').as('getUsers')
cy.visit('/users')
cy.wait('@getUsers')
// Intercept with response modification
cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers')
// Intercept with dynamic response
cy.intercept('GET', '/api/users', (req) => {
req.reply({
statusCode: 200,
body: {
users: [
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Smith' }
]
}
})
}).as('getUsers')
// Intercept POST requests
cy.intercept('POST', '/api/users', {
statusCode: 201,
body: { id: 3, name: 'New User' }
}).as('createUser')
```_
### Antrag auf Zulassung
```javascript
// Wait and assert request
cy.intercept('GET', '/api/users').as('getUsers')
cy.visit('/users')
cy.wait('@getUsers').then((interception) => {
expect(interception.response.statusCode).to.eq(200)
expect(interception.response.body).to.have.property('users')
})
// Assert request was made
cy.intercept('POST', '/api/users').as('createUser')
cy.get('[data-cy="create-user"]').click()
cy.wait('@createUser').its('request.body').should('deep.include', {
name: 'John Doe',
email: 'john@example.com'
})
// Assert request headers
cy.wait('@getUsers').its('request.headers').should('have.property', 'authorization')
```_
### Netzbedingungen
```javascript
// Simulate slow network
cy.intercept('GET', '/api/users', (req) => {
req.reply((res) => {
res.delay(2000) // 2 second delay
res.send({ fixture: 'users.json' })
})
}).as('getUsers')
// Simulate network failure
cy.intercept('GET', '/api/users', { forceNetworkError: true }).as('getUsersError')
// Simulate server error
cy.intercept('GET', '/api/users', {
statusCode: 500,
body: { error: 'Internal Server Error' }
}).as('getUsersError')
```_
### Mehrere Abschnitte
```javascript
// Multiple endpoints
cy.intercept('GET', '/api/users').as('getUsers')
cy.intercept('GET', '/api/posts').as('getPosts')
cy.intercept('GET', '/api/comments').as('getComments')
cy.visit('/dashboard')
// Wait for all requests
cy.wait(['@getUsers', '@getPosts', '@getComments'])
// Or wait individually
cy.wait('@getUsers')
cy.wait('@getPosts')
cy.wait('@getComments')
```_
## Dateioperationen
### Datei hochladen
```javascript
// Upload single file
cy.get('input[type="file"]').selectFile('cypress/fixtures/example.pdf')
// Upload multiple files
cy.get('input[type="file"]').selectFile([
'cypress/fixtures/file1.jpg',
'cypress/fixtures/file2.jpg'
])
// Upload with drag and drop
cy.get('.dropzone').selectFile('cypress/fixtures/example.pdf', {
action: 'drag-drop'
})
// Upload from fixtures
cy.fixture('example.pdf', 'base64').then(fileContent => {
cy.get('input[type="file"]').selectFile({
contents: Cypress.Buffer.from(fileContent, 'base64'),
fileName: 'example.pdf',
mimeType: 'application/pdf'
})
})
```_
### Datei herunterladen
```javascript
// Download file and verify
cy.get('[data-cy="download-btn"]').click()
cy.readFile('cypress/downloads/report.pdf').should('exist')
// Verify downloaded file content
cy.get('[data-cy="download-csv"]').click()
cy.readFile('cypress/downloads/data.csv')
.should('contain', 'Name,Email,Age')
.and('contain', 'John Doe,john@example.com,30')
// Download with custom filename
cy.get('[data-cy="download-btn"]').click()
cy.task('downloadFile', {
url: 'https://example.com/file.pdf',
directory: 'cypress/downloads',
filename: 'custom-name.pdf'
})
```_
### Arbeiten mit Fixtures
```javascript
// Load fixture data
cy.fixture('users.json').then((users) => {
cy.intercept('GET', '/api/users', { body: users }).as('getUsers')
})
// Use fixture in test
cy.fixture('user-data.json').as('userData')
cy.get('@userData').then((data) => {
cy.get('[data-cy="name"]').type(data.name)
cy.get('[data-cy="email"]').type(data.email)
})
// Fixture with alias
cy.fixture('config.json').as('config')
cy.get('@config').its('apiUrl').should('eq', 'https://api.example.com')
```_
### Lesen/Schreiben von Dateien
```javascript
// Read file
cy.readFile('cypress/fixtures/data.json').then((data) => {
expect(data).to.have.property('users')
})
// Write file
cy.writeFile('cypress/fixtures/output.json', {
timestamp: Date.now(),
testResults: 'passed'
})
// Append to file
cy.writeFile('cypress/fixtures/log.txt', 'New log entry\n', { flag: 'a+' })
```_
## Benutzerdefinierte Befehle
### Grundlegende benutzerdefinierte Befehle
```javascript
// cypress/support/commands.js
// Simple command
Cypress.Commands.add('login', (email, password) => {
cy.visit('/login')
cy.get('[data-cy="email"]').type(email)
cy.get('[data-cy="password"]').type(password)
cy.get('[data-cy="submit"]').click()
})
// Command with options
Cypress.Commands.add('login', (email, password, options = {}) => {
const { rememberMe = false } = options
cy.visit('/login')
cy.get('[data-cy="email"]').type(email)
cy.get('[data-cy="password"]').type(password)
if (rememberMe) {
cy.get('[data-cy="remember-me"]').check()
}
cy.get('[data-cy="submit"]').click()
})
// Usage
cy.login('user@example.com', 'password123')
cy.login('user@example.com', 'password123', { rememberMe: true })
```_
### Erweiterte benutzerdefinierte Befehle
```javascript
// Command that returns value
Cypress.Commands.add('getLocalStorage', (key) => {
return cy.window().then((win) => {
return win.localStorage.getItem(key)
})
})
// Command with chaining
Cypress.Commands.add('dataCy', (value) => {
return cy.get(`[data-cy=${value}]`)
})
// Usage with chaining
cy.dataCy('submit-btn').click()
cy.dataCy('username').type('john@example.com')
// Command with retry logic
Cypress.Commands.add('waitForElement', (selector, timeout = 10000) => {
cy.get(selector, { timeout }).should('be.visible')
})
// API command
Cypress.Commands.add('apiLogin', (email, password) => {
cy.request({
method: 'POST',
url: '/api/auth/login',
body: { email, password }
}).then((response) => {
window.localStorage.setItem('authToken', response.body.token)
})
})
```_
### Befehle überschreiben
```javascript
// Overwrite existing command
Cypress.Commands.overwrite('visit', (originalFn, url, options) => {
const domain = Cypress.config('baseUrl')
if (domain) {
const combinedUrl = `${domain}${url}`
return originalFn(combinedUrl, options)
}
return originalFn(url, options)
})
// Overwrite type command to clear first
Cypress.Commands.overwrite('type', (originalFn, element, text, options) => {
if (options && options.clearFirst !== false) {
element.clear()
}
return originalFn(element, text, options)
})
```_
### TypScript Support
```typescript
// cypress/support/commands.ts
declare global {
namespace Cypress {
interface Chainable {
login(email: string, password: string, options?: { rememberMe?: boolean }): Chainable<void>
dataCy(value: string): Chainable<JQuery<HTMLElement>>
apiLogin(email: string, password: string): Chainable<void>
}
}
}
Cypress.Commands.add('login', (email: string, password: string, options = {}) => {
// Implementation
})
```_
## Konfiguration
### Cypress Konfiguration
```javascript
// cypress.config.js
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
// Base URL for cy.visit() and cy.request()
baseUrl: 'http://localhost:3000',
// Viewport settings
viewportWidth: 1280,
viewportHeight: 720,
// Timeouts
defaultCommandTimeout: 10000,
requestTimeout: 10000,
responseTimeout: 30000,
pageLoadTimeout: 30000,
// Test files
specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
excludeSpecPattern: ['**/examples/*', '**/temp/*'],
// Support file
supportFile: 'cypress/support/e2e.js',
// Fixtures
fixturesFolder: 'cypress/fixtures',
// Screenshots and videos
screenshotsFolder: 'cypress/screenshots',
videosFolder: 'cypress/videos',
video: true,
screenshotOnRunFailure: true,
// Browser settings
chromeWebSecurity: false,
// Setup function
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
// Component testing
component: {
devServer: {
framework: 'react',
bundler: 'webpack',
},
},
})
```_
### Umweltvariablen
```javascript
// cypress.config.js
module.exports = defineConfig({
e2e: {
env: {
apiUrl: 'https://api.example.com',
username: 'testuser',
password: 'testpass'
}
}
})
// cypress.env.json
{
"apiUrl": "https://api.example.com",
"username": "testuser",
"password": "testpass"
}
// Usage in tests
cy.visit(Cypress.env('apiUrl'))
const username = Cypress.env('username')
```_
### Mehrere Umgebungen
```javascript
// cypress.config.js
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
| | | | const environment = config.env.environment | | 'development' | | | |
const environments = {
development: {
baseUrl: 'http://localhost:3000',
apiUrl: 'http://localhost:3001'
},
staging: {
baseUrl: 'https://staging.example.com',
apiUrl: 'https://api-staging.example.com'
},
production: {
baseUrl: 'https://example.com',
apiUrl: 'https://api.example.com'
}
}
config.baseUrl = environments[environment].baseUrl
config.env.apiUrl = environments[environment].apiUrl
return config
}
}
})
// Run with specific environment
// npx cypress run --env environment=staging
```_
### Browserkonfiguration
```javascript
// cypress.config.js
module.exports = defineConfig({
e2e: {
// Browser launch options
setupNodeEvents(on, config) {
on('before:browser:launch', (browser = {}, launchOptions) => {
if (browser.name === 'chrome') {
launchOptions.args.push('--disable-dev-shm-usage')
launchOptions.args.push('--no-sandbox')
launchOptions.args.push('--disable-gpu')
}
if (browser.name === 'firefox') {
launchOptions.preferences['media.navigator.permission.disabled'] = true
}
return launchOptions
})
}
}
})
```_
## Seite Objekte
### Hauptseite Objekt
```javascript
// cypress/support/pages/LoginPage.js
class LoginPage {
visit() {
cy.visit('/login')
return this
}
fillEmail(email) {
cy.get('[data-cy="email"]').type(email)
return this
}
fillPassword(password) {
cy.get('[data-cy="password"]').type(password)
return this
}
submit() {
cy.get('[data-cy="submit"]').click()
return this
}
login(email, password) {
this.fillEmail(email)
this.fillPassword(password)
this.submit()
return this
}
// Getters for elements
get emailInput() {
return cy.get('[data-cy="email"]')
}
get passwordInput() {
return cy.get('[data-cy="password"]')
}
get submitButton() {
return cy.get('[data-cy="submit"]')
}
get errorMessage() {
return cy.get('[data-cy="error-message"]')
}
}
export default LoginPage
```_
### Verwendung von Seitenobjekten
```javascript
// cypress/e2e/login.cy.js
import LoginPage from '../support/pages/LoginPage'
describe('Login Tests', () => {
const loginPage = new LoginPage()
beforeEach(() => {
loginPage.visit()
})
it('should login successfully', () => {
loginPage
.login('user@example.com', 'password123')
cy.url().should('include', '/dashboard')
})
it('should show error for invalid credentials', () => {
loginPage
.login('invalid@email.com', 'wrongpassword')
loginPage.errorMessage
.should('be.visible')
.and('contain', 'Invalid credentials')
})
})
```_
### Erweiterte Seite Objekt
```javascript
// cypress/support/pages/DashboardPage.js
class DashboardPage {
constructor() {
this.selectors = {
userMenu: '[data-cy="user-menu"]',
userProfile: '[data-cy="user-profile"]',
notifications: '[data-cy="notifications"]',
searchInput: '[data-cy="search"]',
sidebar: '[data-cy="sidebar"]',
mainContent: '[data-cy="main-content"]'
}
}
visit() {
cy.visit('/dashboard')
this.waitForLoad()
return this
}
waitForLoad() {
cy.get(this.selectors.mainContent).should('be.visible')
cy.get('.loading-spinner').should('not.exist')
return this
}
openUserMenu() {
cy.get(this.selectors.userMenu).click()
return this
}
search(query) {
cy.get(this.selectors.searchInput)
.clear()
.type(`${query}{enter}`)
return this
}
// Navigation methods
navigateToProfile() {
this.openUserMenu()
cy.get(this.selectors.userProfile).click()
return this
}
// Assertion methods
shouldShowWelcomeMessage(username) {
cy.contains(`Welcome, ${username}`).should('be.visible')
return this
}
shouldHaveNotificationCount(count) {
cy.get(this.selectors.notifications)
.find('.badge')
.should('contain', count)
return this
}
}
export default DashboardPage
```_
### Seite Objekt mit API Integration
```javascript
// cypress/support/pages/UsersPage.js
class UsersPage {
visit() {
cy.intercept('GET', '/api/users').as('getUsers')
cy.visit('/users')
cy.wait('@getUsers')
return this
}
createUser(userData) {
cy.intercept('POST', '/api/users').as('createUser')
cy.get('[data-cy="create-user-btn"]').click()
cy.get('[data-cy="name"]').type(userData.name)
cy.get('[data-cy="email"]').type(userData.email)
cy.get('[data-cy="save"]').click()
cy.wait('@createUser')
return this
}
deleteUser(userId) {
cy.intercept('DELETE', `/api/users/${userId}`).as('deleteUser')
cy.get(`[data-cy="user-${userId}"]`)
.find('[data-cy="delete-btn"]')
.click()
cy.get('[data-cy="confirm-delete"]').click()
cy.wait('@deleteUser')
return this
}
shouldShowUser(userData) {
cy.get('[data-cy="users-table"]')
.should('contain', userData.name)
.and('contain', userData.email)
return this
}
}
export default UsersPage
```_
## API Testing
### Grundlegende API Tests
```javascript
describe('API Tests', () => {
it('should get users', () => {
cy.request('GET', '/api/users')
.then((response) => {
expect(response.status).to.eq(200)
expect(response.body).to.have.property('users')
expect(response.body.users).to.be.an('array')
})
})
it('should create user', () => {
const newUser = {
name: 'John Doe',
email: 'john@example.com'
}
cy.request('POST', '/api/users', newUser)
.then((response) => {
expect(response.status).to.eq(201)
expect(response.body).to.have.property('id')
expect(response.body.name).to.eq(newUser.name)
expect(response.body.email).to.eq(newUser.email)
})
})
})
```_
### API Tests mit Authentication
```javascript
describe('Authenticated API Tests', () => {
let authToken
before(() => {
// Login and get token
cy.request({
method: 'POST',
url: '/api/auth/login',
body: {
email: 'admin@example.com',
password: 'password123'
}
}).then((response) => {
authToken = response.body.token
})
})
it('should get protected resource', () => {
cy.request({
method: 'GET',
url: '/api/admin/users',
headers: {
'Authorization': `Bearer ${authToken}`
}
}).then((response) => {
expect(response.status).to.eq(200)
expect(response.body).to.have.property('users')
})
})
it('should fail without token', () => {
cy.request({
method: 'GET',
url: '/api/admin/users',
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(401)
})
})
})
```_
### API Testhilfen
```javascript
// cypress/support/api-helpers.js
Cypress.Commands.add('apiLogin', (email, password) => {
return cy.request({
method: 'POST',
url: '/api/auth/login',
body: { email, password }
}).then((response) => {
window.localStorage.setItem('authToken', response.body.token)
return response.body.token
})
})
Cypress.Commands.add('apiRequest', (method, url, body = null) => {
const token = window.localStorage.getItem('authToken')
return cy.request({
method,
url,
body,
headers: token ? { 'Authorization': `Bearer ${token}` } : {}
})
})
// Usage
cy.apiLogin('admin@example.com', 'password123')
cy.apiRequest('GET', '/api/users')
cy.apiRequest('POST', '/api/users', { name: 'John', email: 'john@example.com' })
```_
### Schemavalidierung
```javascript
// Install ajv for JSON schema validation
// npm install --save-dev ajv
// cypress/support/schema-validation.js
import Ajv from 'ajv'
const ajv = new Ajv()
Cypress.Commands.add('validateSchema', (data, schema) => {
const validate = ajv.compile(schema)
const valid = validate(data)
if (!valid) {
throw new Error(`Schema validation failed: ${JSON.stringify(validate.errors)}`)
}
})
// Usage
const userSchema = {
type: 'object',
properties: {
id: { type: 'number' },
name: { type: 'string' },
email: { type: 'string', format: 'email' }
},
required: ['id', 'name', 'email']
}
cy.request('GET', '/api/users/1')
.then((response) => {
cy.validateSchema(response.body, userSchema)
})
```_
## Visuelle Prüfung
### Screenshot Testing
```javascript
describe('Visual Tests', () => {
it('should match homepage screenshot', () => {
cy.visit('/')
cy.screenshot('homepage')
})
it('should match element screenshot', () => {
cy.visit('/dashboard')
cy.get('[data-cy="user-profile"]').screenshot('user-profile')
})
it('should match full page screenshot', () => {
cy.visit('/products')
cy.screenshot('products-page', { capture: 'fullPage' })
})
})
```_
### Visuelle Regression mit Percy
```bash
# Install Percy
npm install --save-dev @percy/cypress
```_
```javascript
// cypress/support/e2e.js
import '@percy/cypress'
// Usage in tests
describe('Visual Regression Tests', () => {
it('should match visual snapshot', () => {
cy.visit('/')
cy.percySnapshot('Homepage')
})
it('should match responsive snapshots', () => {
cy.visit('/dashboard')
cy.percySnapshot('Dashboard - Desktop', {
widths: [1280]
})
cy.percySnapshot('Dashboard - Mobile', {
widths: [375]
})
})
})
```_
### Benutzerdefinierte visuelle Befehle
```javascript
// cypress/support/commands.js
Cypress.Commands.add('compareSnapshot', (name, options = {}) => {
const { threshold = 0.1, clip } = options
cy.screenshot(name, { clip })
// Custom comparison logic would go here
// This is a simplified example
cy.task('compareImages', {
actualImage: `cypress/screenshots/${name}.png`,
expectedImage: `cypress/snapshots/${name}.png`,
threshold
})
})
// Usage
cy.visit('/login')
cy.compareSnapshot('login-page', { threshold: 0.05 })
```_
### Responsive Prüfung
```javascript
describe('Responsive Tests', () => {
const viewports = [
{ width: 320, height: 568 }, // iPhone SE
{ width: 375, height: 667 }, // iPhone 8
{ width: 768, height: 1024 }, // iPad
{ width: 1024, height: 768 }, // iPad Landscape
{ width: 1280, height: 720 }, // Desktop
]
viewports.forEach((viewport) => {
it(`should display correctly at ${viewport.width}x${viewport.height}`, () => {
cy.viewport(viewport.width, viewport.height)
cy.visit('/')
cy.screenshot(`homepage-${viewport.width}x${viewport.height}`)
// Test responsive behavior
if (viewport.width < 768) {
cy.get('[data-cy="mobile-menu"]').should('be.visible')
cy.get('[data-cy="desktop-menu"]').should('not.be.visible')
} else {
cy.get('[data-cy="mobile-menu"]').should('not.be.visible')
cy.get('[data-cy="desktop-menu"]').should('be.visible')
}
})
})
})
```_
## CI/CD Integration
### GitHub Aktionen
```yaml
# .github/workflows/cypress.yml
name: Cypress Tests
on: [push, pull_request]
jobs:
cypress-run:
runs-on: ubuntu-latest
strategy:
matrix:
browser: [chrome, firefox, edge]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Start application
run: npm start &
- name: Wait for application
run: npx wait-on http://localhost:3000
- name: Run Cypress tests
uses: cypress-io/github-action@v5
with:
browser: ${{ matrix.browser }}
record: true
parallel: true
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload screenshots
uses: actions/upload-artifact@v3
if: failure()
with:
name: cypress-screenshots-${{ matrix.browser }}
path: cypress/screenshots
- name: Upload videos
uses: actions/upload-artifact@v3
if: always()
with:
name: cypress-videos-${{ matrix.browser }}
path: cypress/videos
```_
### Docker Integration
```dockerfile
# Dockerfile.cypress
FROM cypress/included:12.17.0
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
# Run tests
CMD ["npx", "cypress", "run"]
```_
```yaml
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=test
cypress:
build:
context: .
dockerfile: Dockerfile.cypress
depends_on:
- app
environment:
- CYPRESS_baseUrl=http://app:3000
volumes:
- ./cypress/videos:/app/cypress/videos
- ./cypress/screenshots:/app/cypress/screenshots
```_
### Parallele Prüfung
```javascript
// cypress.config.js
module.exports = defineConfig({
e2e: {
// Enable parallel testing
setupNodeEvents(on, config) {
// Split tests across multiple machines
if (config.env.SPLIT_TESTS) {
| | | | const totalMachines = config.env.TOTAL_MACHINES | | 1 | | | |
| | | | const currentMachine = config.env.CURRENT_MACHINE | | 0 | | | |
// Custom logic to split test files
config.specPattern = getSpecsForMachine(currentMachine, totalMachines)
}
return config
}
}
})
function getSpecsForMachine(current, total) {
// Implementation to split specs across machines
const allSpecs = [
'cypress/e2e/auth/*.cy.js',
'cypress/e2e/dashboard/*.cy.js',
'cypress/e2e/users/*.cy.js'
]
return allSpecs.filter((_, index) => index % total === current)
}
```_
### Prüfberichte
```bash
# Install mochawesome reporter
npm install --save-dev mochawesome mochawesome-merge mochawesome-report-generator
```_
```javascript
// cypress.config.js
module.exports = defineConfig({
e2e: {
reporter: 'mochawesome',
reporterOptions: {
reportDir: 'cypress/reports',
overwrite: false,
html: false,
json: true
}
}
})
```_
```json
{
"scripts": {
"test:report": "cypress run && npm run merge:reports && npm run generate:report",
"merge:reports": "mochawesome-merge cypress/reports/*.json > cypress/reports/merged-report.json",
"generate:report": "marge cypress/reports/merged-report.json --reportDir cypress/reports --inline"
}
}
```_
## Best Practices
### Prüforganisation
```javascript
// Good: Descriptive test names
describe('User Authentication', () => {
it('should allow user to login with valid credentials', () => {
// Test implementation
})
it('should display error message for invalid credentials', () => {
// Test implementation
})
it('should redirect to dashboard after successful login', () => {
// Test implementation
})
})
// Good: Group related tests
describe('Shopping Cart', () => {
describe('Adding Items', () => {
it('should add item to cart')
it('should update cart count')
it('should show item in cart sidebar')
})
describe('Removing Items', () => {
it('should remove item from cart')
it('should update cart total')
it('should show empty cart message when no items')
})
})
```_
### Wählen Sie die besten Praktiken
```javascript
// ❌ Bad: Fragile selectors
cy.get('.btn.btn-primary.submit-button')
cy.get('div > form > div:nth-child(2) > input')
cy.get('button:contains("Submit")')
// ✅ Good: Stable selectors
cy.get('[data-cy="submit-button"]')
cy.get('[data-testid="email-input"]')
cy.get('[aria-label="Submit form"]')
// ✅ Good: Custom commands for common patterns
Cypress.Commands.add('getByTestId', (testId) => {
return cy.get(`[data-testid="${testId}"]`)
})
cy.getByTestId('email-input').type('user@example.com')
```_
### Best Practices warten
```javascript
// ❌ Bad: Arbitrary waits
cy.wait(3000)
cy.get('button').click()
// ✅ Good: Wait for specific conditions
cy.get('[data-cy="loading"]').should('not.exist')
cy.get('[data-cy="submit-button"]').should('be.enabled').click()
// ✅ Good: Wait for network requests
cy.intercept('POST', '/api/users').as('createUser')
cy.get('[data-cy="submit"]').click()
cy.wait('@createUser')
Test Data Management¶
```javascript // ✅ Good: Use fixtures for test data cy.fixture('users.json').then((users) => { const testUser = users.validUser cy.login(testUser.email, testUser.password) })
// ✅ Good: Generate dynamic test data
const generateUser = () => ({
name: Test User ${Date.now()},
email: test${Date.now()}@example.com,
password: 'password123'
})
// ✅ Good: Clean up test data afterEach(() => { cy.task('cleanupTestData') }) ```_
Fehlerbehebung¶
```javascript // ✅ Good: Handle expected errors cy.get('[data-cy="submit"]').click() cy.get('[data-cy="error-message"]') .should('be.visible') .and('contain', 'Email is required')
// ✅ Good: Retry flaky operations Cypress.Commands.add('retryableClick', (selector, maxRetries = 3) => { let attempts = 0
const clickWithRetry = () => { attempts++
return cy.get(selector).click().then(() => {
// Verify click was successful
return cy.get(selector).should('have.class', 'clicked')
}).catch((error) => {
if (attempts < maxRetries) {
cy.wait(1000)
return clickWithRetry()
}
throw error
})
}
return clickWithRetry() }) ```_
Debugging¶
Debug Befehle¶
```javascript // Pause test execution cy.pause()
// Debug specific element cy.get('[data-cy="button"]').debug()
// Log values cy.get('[data-cy="input"]').then(($el) => { console.log('Element value:', $el.val()) })
// Take screenshot at specific point cy.screenshot('debug-point-1')
// Log network requests cy.intercept('**', (req) => { console.log('Request:', req.method, req.url) }) ```_
Browser DevTools¶
```javascript // Open DevTools programmatically cy.window().then((win) => { win.debugger })
// Inspect element in DevTools cy.get('[data-cy="element"]').then((\(el) => { debugger // Will pause in DevTools console.log(\)el[0]) // Inspect element })
// Access application state cy.window().its('store').then((store) => { console.log('Redux state:', store.getState()) }) ```_
Benutzerdefinierte Debug-Kommandos¶
```javascript // cypress/support/commands.js Cypress.Commands.add('logElement', (selector) => { cy.get(selector).then(($el) => { console.log('Element:', $el[0]) console.log('Text:', $el.text()) console.log('Value:', $el.val()) console.log('Classes:', $el.attr('class')) }) })
Cypress.Commands.add('debugState', () => { cy.window().then((win) => { if (win.store) { console.log('Redux State:', win.store.getState()) } if (win.localStorage) { console.log('LocalStorage:', win.localStorage) } if (win.sessionStorage) { console.log('SessionStorage:', win.sessionStorage) } }) })
// Usage cy.logElement('[data-cy="user-profile"]') cy.debugState() ```_
Test Debugging Strategien¶
```javascript // Strategy 1: Isolate failing test it.only('should debug this specific test', () => { // Test implementation })
// Strategy 2: Add detailed logging it('should complete user flow', () => { cy.log('Step 1: Visit login page') cy.visit('/login')
cy.log('Step 2: Fill credentials') cy.get('[data-cy="email"]').type('user@example.com') cy.get('[data-cy="password"]').type('password123')
cy.log('Step 3: Submit form') cy.get('[data-cy="submit"]').click()
cy.log('Step 4: Verify redirect') cy.url().should('include', '/dashboard') })
// Strategy 3: Break down complex tests it('should login user', () => { cy.login('user@example.com', 'password123') })
it('should navigate to profile', () => { cy.login('user@example.com', 'password123') cy.get('[data-cy="profile-link"]').click() cy.url().should('include', '/profile') }) ```_
Leistung¶
Prüfleistung¶
```javascript // Optimize test setup describe('Performance Optimized Tests', () => { // Use beforeEach for common setup beforeEach(() => { cy.login('user@example.com', 'password123') })
// Avoid unnecessary visits it('should test multiple features on same page', () => { cy.visit('/dashboard')
// Test multiple things on the same page
cy.get('[data-cy="user-name"]').should('contain', 'John Doe')
cy.get('[data-cy="notification-count"]').should('contain', '3')
cy.get('[data-cy="recent-activity"]').should('be.visible')
}) })
// Use API for setup when possible Cypress.Commands.add('seedData', () => { return cy.request('POST', '/api/test/seed', { users: 10, posts: 50, comments: 100 }) })
// Usage beforeEach(() => { cy.seedData() }) ```_
Parallele Ausführung¶
```bash
Run tests in parallel (requires Cypress Dashboard)¶
npx cypress run --record --parallel
Split tests manually¶
npx cypress run --spec "cypress/e2e/auth//*" npx cypress run --spec "cypress/e2e/dashboard//" npx cypress run --spec "cypress/e2e/users/**/" ```_
Speicheroptimierung¶
```javascript // cypress.config.js module.exports = defineConfig({ e2e: { // Optimize memory usage numTestsKeptInMemory: 5,
// Disable video for faster execution
video: false,
// Reduce screenshot quality
screenshotOnRunFailure: true,
setupNodeEvents(on, config) {
// Clear browser cache between tests
on('before:browser:launch', (browser, launchOptions) => {
if (browser.name === 'chrome') {
launchOptions.args.push('--disable-dev-shm-usage')
launchOptions.args.push('--memory-pressure-off')
}
return launchOptions
})
}
} }) ```_
Fehlerbehebung¶
Gemeinsame Themen¶
```javascript // Issue: Element not found // Solution: Wait for element or check selector cy.get('[data-cy="button"]', { timeout: 10000 }).should('exist')
// Issue: Element not clickable // Solution: Ensure element is visible and enabled cy.get('[data-cy="button"]') .should('be.visible') .and('not.be.disabled') .click()
// Issue: Flaky tests // Solution: Add proper waits and assertions cy.intercept('GET', '/api/data').as('getData') cy.visit('/page') cy.wait('@getData') cy.get('[data-cy="content"]').should('be.visible')
// Issue: Cross-origin errors // Solution: Disable web security (not recommended for production) // cypress.config.js { chromeWebSecurity: false } ```_
Debug Konfiguration¶
```javascript // cypress.config.js module.exports = defineConfig({ e2e: { // Enable debug mode setupNodeEvents(on, config) { // Log all events on('task', { log(message) { console.log(message) return null } })
// Debug failed tests
on('after:spec', (spec, results) => {
if (results && results.stats.failures) {
console.log('Failed spec:', spec.relative)
console.log('Failures:', results.stats.failures)
}
})
}
} })
// Usage in tests cy.task('log', 'Debug message here') ```_
Fehlersuche¶
```javascript // Retry failed commands Cypress.Commands.add('retryCommand', (command, maxRetries = 3) => { let attempts = 0
const executeCommand = () => { attempts++
return command().catch((error) => {
if (attempts < maxRetries) {
cy.wait(1000)
return executeCommand()
}
throw error
})
}
return executeCommand() })
// Usage cy.retryCommand(() => cy.get('[data-cy="flaky-element"]').click()) ```_
--
Zusammenfassung¶
Cypress ist ein leistungsstarkes End-to-End-Test-Framework, das bietet:
- **Developer Experience*: Intuitive API mit Echtzeit-Browservorschau
- ** Automatisches Warten*: Intelligentes Warten auf Elemente und Netzwerkanfragen
- Zeitreisen: Debug-Tests mit Snapshots bei jedem Schritt
- Network Control: Netzwerkanfragen abfangen und ändern
- **Real Browser Testing*: Tests laufen in realen Browsern für genaue Ergebnisse
- **Rich Ecosystem*: Umfangreiches Plugin-Ökosystem und Integrationen
- **CI/CD Ready*: Integrierte Unterstützung für die kontinuierliche Integration
- **Visual Testing*: Screenshots und visuelle Regressionstests
Cypress zeichnet sich durch die Erprobung moderner Webanwendungen aus, deren Fokus auf Entwicklererfahrung, umfassende Debugging-Tools und zuverlässige Testausführung liegt. Die einzigartige Architektur und das Feature-Set machen es zu einer exzellenten Wahl für Teams, die robuste End-to-End-Test-Workflows implementieren möchten.
<= <= <= <================================================================================= Funktion copyToClipboard() {\cHFFFF} const commands = document.querySelectorAll('code'); alle Befehle = ''; Befehle. Für jede(cmd) => alle Befehle += cmd.textContent + '\n'); navigator.clipboard.writeText (allCommands); Alarm ('Alle Befehle, die in die Zwischenablage kopiert werden!'); }
Funktion generierenPDF() { Fenster.print(); }