8.9 KiB
8.9 KiB
Run E2E Tests Command
Set up and execute end-to-end tests using Cypress or Playwright for Angular applications.
Command Usage
/angular-testing:run-e2e [cypress|playwright] [setup|run|debug]
Natural Language Examples
- "Set up Cypress for E2E testing"
- "Create E2E tests for login flow"
- "Run Playwright tests in headless mode"
- "Debug failing E2E tests"
What This Command Does
- Framework Setup - Install and configure Cypress/Playwright
- Test Generation - Create E2E test files for user flows
- Custom Commands - Reusable helpers and utilities
- CI/CD Integration - GitHub Actions/GitLab CI configuration
- Debugging - Tools and strategies for flaky tests
Cypress Setup
Installation
npm install --save-dev cypress @cypress/schematic
ng add @cypress/schematic
Configuration
// cypress.config.ts
import { defineConfig } from 'cypress';
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:4200',
supportFile: 'cypress/support/e2e.ts',
specPattern: 'cypress/e2e/**/*.cy.ts',
viewportWidth: 1280,
viewportHeight: 720,
video: true,
screenshotOnRunFailure: true,
retries: {
runMode: 2,
openMode: 0
},
env: {
apiUrl: 'http://localhost:3000/api'
}
}
});
Custom Commands
// cypress/support/commands.ts
declare global {
namespace Cypress {
interface Chainable {
login(email: string, password: string): Chainable<void>;
logout(): Chainable<void>;
getBySel(selector: string): Chainable<JQuery<HTMLElement>>;
checkAccessibility(): Chainable<void>;
}
}
}
// Login command
Cypress.Commands.add('login', (email: string, password: string) => {
cy.session([email, password], () => {
cy.visit('/login');
cy.getBySel('email-input').type(email);
cy.getBySel('password-input').type(password);
cy.getBySel('login-button').click();
cy.url().should('include', '/dashboard');
cy.window().its('localStorage.token').should('exist');
});
});
// Logout command
Cypress.Commands.add('logout', () => {
cy.clearLocalStorage();
cy.clearCookies();
});
// Get by data-cy attribute
Cypress.Commands.add('getBySel', (selector: string) => {
return cy.get(`[data-cy="${selector}"]`);
});
// Accessibility check
Cypress.Commands.add('checkAccessibility', () => {
cy.injectAxe();
cy.checkA11y();
});
Example E2E Test
// cypress/e2e/login.cy.ts
describe('Login Flow', () => {
beforeEach(() => {
cy.visit('/login');
});
it('should display login form', () => {
cy.getBySel('email-input').should('be.visible');
cy.getBySel('password-input').should('be.visible');
cy.getBySel('login-button').should('be.visible');
});
it('should show validation errors for empty fields', () => {
cy.getBySel('login-button').click();
cy.getBySel('email-error').should('contain', 'Email is required');
cy.getBySel('password-error').should('contain', 'Password is required');
});
it('should show error for invalid email format', () => {
cy.getBySel('email-input').type('invalid-email');
cy.getBySel('password-input').type('password123');
cy.getBySel('login-button').click();
cy.getBySel('email-error').should('contain', 'Invalid email format');
});
it('should successfully login with valid credentials', () => {
cy.intercept('POST', '/api/auth/login', {
statusCode: 200,
body: {
token: 'fake-jwt-token',
user: { id: 1, email: 'test@example.com' }
}
}).as('loginRequest');
cy.getBySel('email-input').type('test@example.com');
cy.getBySel('password-input').type('password123');
cy.getBySel('login-button').click();
cy.wait('@loginRequest');
cy.url().should('include', '/dashboard');
cy.window().its('localStorage.token').should('exist');
});
it('should show error message for invalid credentials', () => {
cy.intercept('POST', '/api/auth/login', {
statusCode: 401,
body: { message: 'Invalid credentials' }
}).as('loginRequest');
cy.getBySel('email-input').type('test@example.com');
cy.getBySel('password-input').type('wrong-password');
cy.getBySel('login-button').click();
cy.wait('@loginRequest');
cy.getBySel('error-message').should('contain', 'Invalid credentials');
cy.url().should('include', '/login');
});
it('should toggle password visibility', () => {
cy.getBySel('password-input').should('have.attr', 'type', 'password');
cy.getBySel('toggle-password').click();
cy.getBySel('password-input').should('have.attr', 'type', 'text');
cy.getBySel('toggle-password').click();
cy.getBySel('password-input').should('have.attr', 'type', 'password');
});
it('should remember me functionality', () => {
cy.getBySel('remember-me-checkbox').check();
cy.login('test@example.com', 'password123');
cy.reload();
cy.url().should('include', '/dashboard');
});
});
Running Cypress Tests
# Open Cypress Test Runner
npx cypress open
# Run all tests headless
npx cypress run
# Run specific test
npx cypress run --spec "cypress/e2e/login.cy.ts"
# Run with specific browser
npx cypress run --browser chrome
Playwright Setup
Installation
npm install --save-dev @playwright/test
npx playwright install
Configuration
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:4200',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure'
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] }
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] }
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] }
}
],
webServer: {
command: 'npm start',
url: 'http://localhost:4200',
reuseExistingServer: !process.env.CI
}
});
Example Playwright Test
// e2e/login.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Login Flow', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login');
});
test('should display login form', async ({ page }) => {
await expect(page.getByTestId('email-input')).toBeVisible();
await expect(page.getByTestId('password-input')).toBeVisible();
await expect(page.getByTestId('login-button')).toBeVisible();
});
test('should login successfully', async ({ page }) => {
await page.route('**/api/auth/login', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
token: 'fake-jwt-token',
user: { id: 1, email: 'test@example.com' }
})
});
});
await page.fill('[data-testid="email-input"]', 'test@example.com');
await page.fill('[data-testid="password-input"]', 'password123');
await page.click('[data-testid="login-button"]');
await expect(page).toHaveURL(/.*dashboard/);
const token = await page.evaluate(() => localStorage.getItem('token'));
expect(token).toBeTruthy();
});
});
Running Playwright Tests
# Run all tests
npx playwright test
# Run with UI
npx playwright test --ui
# Debug mode
npx playwright test --debug
# Generate report
npx playwright show-report
CI/CD Integration
GitHub Actions
# .github/workflows/e2e.yml
name: E2E Tests
on: [push, pull_request]
jobs:
cypress:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Start Angular dev server
run: npm start &
- name: Wait for server
run: npx wait-on http://localhost:4200
- name: Run Cypress tests
run: npm run cypress:run
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v3
with:
name: cypress-screenshots
path: cypress/screenshots
Best Practices
- Use data-cy/data-testid attributes - For stable selectors
- Avoid waits - Use Cypress auto-retry
- Test user flows - Not individual features
- Mock external APIs - For consistency
- Keep tests independent - No shared state
- Use fixtures - For test data
- Clean up after tests - Reset state
Usage
/angular-testing:run-e2e
# Examples
"Set up Cypress for testing login flow"
"Create E2E tests for product CRUD operations"
"Run E2E tests in CI/CD pipeline"
Comprehensive E2E testing made easy! 🎭