From e0c989755ec97cc271bce77642248b8623551df3 Mon Sep 17 00:00:00 2001 From: gnezim Date: Sun, 5 Apr 2026 19:09:43 +0300 Subject: [PATCH] Fix code quality issues in E2E Cypress infrastructure - Fix specPattern in cypress.config.ts: change from '*.cy.ts' to '*.spec.ts' to match actual test file naming convention - Fix forbidGeolocation command: use callsFake() instead of rejects() which is not a valid Cypress stub method - Remove redundant localStorage cleanup: keep only afterEach hook, remove beforeEach hook to avoid duplication - Remove unused @cypress/schematic dependency from e2e/package.json - Add comprehensive README.md with E2E testing documentation covering test structure, setup, running tests, custom commands, and best practices --- e2e/README.md | 226 ++++++++++++++++++++++++++++++++ e2e/cypress.config.ts | 2 +- e2e/cypress/support/commands.ts | 6 +- e2e/cypress/support/index.ts | 7 - e2e/package.json | 1 - 5 files changed, 231 insertions(+), 11 deletions(-) create mode 100644 e2e/README.md diff --git a/e2e/README.md b/e2e/README.md new file mode 100644 index 000000000..48a387fc8 --- /dev/null +++ b/e2e/README.md @@ -0,0 +1,226 @@ +# E2E Testing Infrastructure + +This directory contains end-to-end testing infrastructure for the Aeroflot Flights web application, including Cypress tests and visual regression testing with BackstopJS. + +## Directory Structure + +``` +e2e/ +├── cypress/ +│ ├── integration/ # E2E test files organized by feature +│ │ ├── accessibility/ # Accessibility tests +│ │ ├── components/ # Component-level tests +│ │ ├── error-handling/ # Error handling tests +│ │ ├── flight-details/ # Flight details page tests +│ │ ├── flights-map/ # Flight map tests +│ │ ├── i18n/ # Internationalization tests +│ │ ├── integration/ # Cross-feature integration tests +│ │ ├── navigation/ # Navigation flow tests +│ │ ├── online-board/ # Online board tests +│ │ ├── performance/ # Performance tests +│ │ ├── responsive/ # Responsive design tests +│ │ ├── schedule/ # Schedule tests +│ │ └── search-history/ # Search history tests +│ └── support/ +│ ├── index.ts # Global test setup and hooks +│ └── commands.ts # Custom Cypress commands +├── backstop/ # Visual regression testing config +├── cypress.config.ts # Cypress configuration +├── package.json # E2E dependencies +├── tsconfig.json # TypeScript configuration +└── scripts/ # Test utility scripts +``` + +## Test File Naming + +All test files must follow the naming pattern: `*.spec.ts` + +Example test files: +- `cypress/integration/search-history/search-history-filtering.spec.ts` +- `cypress/integration/flight-details/flight-details-display.spec.ts` + +## Setup + +### Install Dependencies + +```bash +npm install +``` + +### Environment Configuration + +The test suite uses the following configuration from `cypress.config.ts`: +- **Base URL**: `http://localhost:3000` (React development server) +- **Viewport**: 1440x900 (desktop) +- **Timeouts**: 10 seconds for requests, responses, and commands +- **Screenshots**: Enabled on test failure +- **Videos**: Disabled (can be enabled in cypress.config.ts if needed) + +## Running Tests + +### Open Cypress Test Runner (Interactive Mode) + +```bash +npm run cypress:open +``` + +This opens the Cypress test runner UI where you can: +- View and run individual tests +- Debug with browser DevTools +- See real-time test execution +- Inspect elements and network calls + +### Run Tests Headlessly + +```bash +npm run cypress:run +``` + +This runs all tests in headless mode and generates a test report. + +### Run Specific Test Suite + +```bash +npx cypress run --spec "cypress/integration/search-history/*.spec.ts" +``` + +### Run Tests with Specific Browser + +```bash +npx cypress run --browser chrome +npx cypress run --browser firefox +npx cypress run --browser edge +``` + +## Custom Commands + +### getByTestId(id: string, timeout?: number) + +Select elements by test ID attribute for more reliable element selection. + +```typescript +cy.getByTestId('search-button').click() +cy.getByTestId('flight-results', 5000).should('exist') +``` + +### forbidGeolocation() + +Mock the geolocation API to return an error, useful for testing geolocation-denied scenarios. + +```typescript +cy.forbidGeolocation() +// Now geolocation calls will fail +``` + +## Test Hooks + +### afterEach Hook + +Automatically clears browser localStorage after each test to ensure test isolation and prevent state leakage between tests. + +```typescript +// Automatically runs after every test +afterEach(() => { + cy.window().then(win => { + win.localStorage.clear() + }) +}) +``` + +## Visual Regression Testing (BackstopJS) + +The e2e folder includes BackstopJS for visual regression testing. + +### Create Reference Screenshots + +```bash +npm run backstop:reference +``` + +This creates baseline screenshots using the Angular version (`backstop/backstop-angular.json`). + +### Run Visual Tests + +```bash +npm run backstop:test +``` + +This compares current React version screenshots against the Angular baseline (`backstop/backstop-react.json`). + +## Test Best Practices + +1. **Use data-testid attributes**: Always add `data-testid` to elements you want to test + ```html + + ``` + +2. **Avoid brittle selectors**: Don't rely on class names or IDs that change frequently + +3. **Use meaningful test names**: Describe what the test verifies + ```typescript + it('should filter search results when filter is applied', () => { + // test code + }) + ``` + +4. **Test user workflows**: Write tests that simulate real user interactions + ```typescript + cy.getByTestId('destination-input').type('Moscow') + cy.getByTestId('search-button').click() + cy.getByTestId('flight-results').should('be.visible') + ``` + +5. **Keep tests independent**: Each test should be able to run in any order + - Use `afterEach` to clean up state (already configured) + - Don't depend on data from other tests + +6. **Handle async operations**: Use proper timeouts and waits + ```typescript + cy.getByTestId('loading-spinner', 10000).should('not.exist') + cy.getByTestId('results-list').should('have.length.greaterThan', 0) + ``` + +7. **Mock API responses when appropriate**: Use Cypress intercept for predictable tests + ```typescript + cy.intercept('GET', '/api/flights/*', { fixture: 'flights.json' }) + ``` + +## Validation Script + +Run the full validation suite (includes e2e tests): + +```bash +npm run validate +``` + +## Troubleshooting + +### Tests timing out +- Increase timeout values in `cypress.config.ts` +- Check if the application is running on http://localhost:3000 +- Verify network connectivity to backend API + +### Element not found errors +- Ensure `data-testid` attributes exist in the React components +- Wait for elements to be visible: `cy.getByTestId('element').should('be.visible')` +- Check browser console for JavaScript errors + +### localStorage not clearing between tests +- The `afterEach` hook should handle this automatically +- Verify the hook is not being overridden in individual test files + +## CI/CD Integration + +For CI/CD pipelines, use: + +```bash +npm run cypress:run # Headless test execution +``` + +Ensure the React development server is running on port 3000 before executing tests. + +## Additional Resources + +- [Cypress Documentation](https://docs.cypress.io) +- [BackstopJS Documentation](https://garris.github.io/BackstopJS/) +- [Testing Best Practices](https://docs.cypress.io/guides/references/best-practices) diff --git a/e2e/cypress.config.ts b/e2e/cypress.config.ts index a01facfd6..653a889f2 100644 --- a/e2e/cypress.config.ts +++ b/e2e/cypress.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ e2e: { baseUrl: 'http://localhost:3000', supportFile: 'cypress/support/index.ts', - specPattern: 'cypress/integration/**/*.cy.ts', + specPattern: 'cypress/integration/**/*.spec.ts', viewportWidth: 1440, viewportHeight: 900, video: false, diff --git a/e2e/cypress/support/commands.ts b/e2e/cypress/support/commands.ts index 22b27be59..80d2c4867 100644 --- a/e2e/cypress/support/commands.ts +++ b/e2e/cypress/support/commands.ts @@ -6,8 +6,10 @@ Cypress.Commands.add('getByTestId', (id: string, timeout = 8000) => { Cypress.Commands.add('forbidGeolocation', () => { cy.window().then(win => { - cy.stub(win.navigator.geolocation, 'getCurrentPosition').rejects( - new Error('Geolocation forbidden') + cy.stub(win.navigator.geolocation, 'getCurrentPosition').callsFake( + (success, error) => { + error(new GeolocationPositionError()) + } ) }) }) diff --git a/e2e/cypress/support/index.ts b/e2e/cypress/support/index.ts index b6ed48891..efdc791c3 100644 --- a/e2e/cypress/support/index.ts +++ b/e2e/cypress/support/index.ts @@ -1,12 +1,5 @@ import './commands' -beforeEach(() => { - // Clear localStorage before each test - cy.window().then(win => { - win.localStorage.clear() - }) -}) - afterEach(() => { // Clean up after each test cy.window().then(win => { diff --git a/e2e/package.json b/e2e/package.json index 6f4ffb519..5471ac4f6 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -12,7 +12,6 @@ "devDependencies": { "cypress": "^13.6.0", "backstopjs": "^6.3.0", - "@cypress/schematic": "^2.5.0", "typescript": "^5.3.0" } }