🎯 Exemplos recomendados
Balanced sample collections from various categories for you to explore
Framework de Testes E2E Cypress
Exemplos abrangentes de testes E2E Cypress incluindo configuração de testes, modelos de objeto de página, testes de API, testes de regressão visual e padrões E2E avançados para aplicações web modernas
💻 Configuração de Projeto Cypress javascript
Configuração completa de projeto Cypress com arquivos de configuração, estrutura de testes, comandos personalizados e melhores práticas de testes E2E
// 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"
}
}
💻 Interações Avançadas de Usuário e Asserções javascript
Exemplos complexos de interações de usuário incluindo drag and drop, upload de arquivos, atalhos de teclado, efeitos hover e estratégias avançadas de asserção
// 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');
});
});
💻 Testes de API e Interceptação de Rede javascript
Exemplos abrangentes de testes de API incluindo interceptação de request/response, stubbing, autenticação e testes de performance com 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 });
});
});
💻 Testes de Regressão Visual e Cross-Browser javascript
Estratégias de testes visuais incluindo comparação de screenshots, testes cross-browser, verificação de design responsivo e fluxos de trabalho de regressão visual com 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;