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
This commit is contained in:
+226
@@ -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
|
||||||
|
<button data-testid="search-button">Search</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
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)
|
||||||
@@ -4,7 +4,7 @@ export default defineConfig({
|
|||||||
e2e: {
|
e2e: {
|
||||||
baseUrl: 'http://localhost:3000',
|
baseUrl: 'http://localhost:3000',
|
||||||
supportFile: 'cypress/support/index.ts',
|
supportFile: 'cypress/support/index.ts',
|
||||||
specPattern: 'cypress/integration/**/*.cy.ts',
|
specPattern: 'cypress/integration/**/*.spec.ts',
|
||||||
viewportWidth: 1440,
|
viewportWidth: 1440,
|
||||||
viewportHeight: 900,
|
viewportHeight: 900,
|
||||||
video: false,
|
video: false,
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ Cypress.Commands.add('getByTestId', (id: string, timeout = 8000) => {
|
|||||||
|
|
||||||
Cypress.Commands.add('forbidGeolocation', () => {
|
Cypress.Commands.add('forbidGeolocation', () => {
|
||||||
cy.window().then(win => {
|
cy.window().then(win => {
|
||||||
cy.stub(win.navigator.geolocation, 'getCurrentPosition').rejects(
|
cy.stub(win.navigator.geolocation, 'getCurrentPosition').callsFake(
|
||||||
new Error('Geolocation forbidden')
|
(success, error) => {
|
||||||
|
error(new GeolocationPositionError())
|
||||||
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
import './commands'
|
import './commands'
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
// Clear localStorage before each test
|
|
||||||
cy.window().then(win => {
|
|
||||||
win.localStorage.clear()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
// Clean up after each test
|
// Clean up after each test
|
||||||
cy.window().then(win => {
|
cy.window().then(win => {
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cypress": "^13.6.0",
|
"cypress": "^13.6.0",
|
||||||
"backstopjs": "^6.3.0",
|
"backstopjs": "^6.3.0",
|
||||||
"@cypress/schematic": "^2.5.0",
|
|
||||||
"typescript": "^5.3.0"
|
"typescript": "^5.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user