feat: add cypress e2e test infrastructure and support files
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
baseUrl: 'http://localhost:4200',
|
||||
viewportWidth: 1280,
|
||||
viewportHeight: 720,
|
||||
defaultCommandTimeout: 5000,
|
||||
requestTimeout: 10000,
|
||||
responseTimeout: 10000,
|
||||
pageLoadTimeout: 30000,
|
||||
chromeWebSecurity: false,
|
||||
video: true,
|
||||
screenshotOnRunFailure: true,
|
||||
specPattern: 'cypress/integration/**/*.ts',
|
||||
supportFile: 'cypress/support/index.ts',
|
||||
videosFolder: 'cypress/videos',
|
||||
screenshotsFolder: 'cypress/screenshots',
|
||||
fixturesFolder: 'cypress/fixtures',
|
||||
setupNodeEvents(on, config) {
|
||||
// implement node event listeners here
|
||||
},
|
||||
},
|
||||
component: {
|
||||
specPattern: 'cypress/component/**/*.ts',
|
||||
supportFile: 'cypress/support/index.ts',
|
||||
devServer: {
|
||||
framework: 'angular',
|
||||
bundler: 'webpack',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"integrationFolder": "cypress/integration",
|
||||
"supportFile": "cypress/support/index.ts",
|
||||
"videosFolder": "cypress/videos",
|
||||
"screenshotsFolder": "cypress/screenshots",
|
||||
"pluginsFile": "cypress/plugins/index.ts",
|
||||
"fixturesFolder": "cypress/fixtures",
|
||||
"baseUrl": "http://localhost:4200",
|
||||
"screenshotOnRunFailure": false,
|
||||
"video": false,
|
||||
"viewportHeight": 768,
|
||||
"viewportWidth": 1366,
|
||||
"chromeWebSecurity": false,
|
||||
"env": {
|
||||
"browserPermissions": {
|
||||
"notifications": "allow",
|
||||
"geolocation": "block",
|
||||
"camera": "block",
|
||||
"microphone": "block",
|
||||
"images": "allow",
|
||||
"javascript": "allow",
|
||||
"popups": "ask",
|
||||
"plugins": "ask",
|
||||
"cookies": "allow"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +1,8 @@
|
||||
/// <reference types="." />
|
||||
|
||||
// ***********************************************
|
||||
// This example namespace declaration will help
|
||||
// with Intellisense and code completion in your
|
||||
// IDE or Text Editor.
|
||||
// ***********************************************
|
||||
// declare namespace Cypress {
|
||||
// interface Chainable<Subject = any> {
|
||||
// customCommand(param: any): typeof customCommand;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// function customCommand(param: any): void {
|
||||
// console.warn(param);
|
||||
// }
|
||||
//
|
||||
// NOTE: You can use it like so:
|
||||
// Cypress.Commands.add('customCommand', customCommand);
|
||||
//
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add("login", (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
/**
|
||||
* Custom Cypress commands for Aeroflot Flights Web testing
|
||||
*/
|
||||
|
||||
Cypress.Commands.add('getByTestId', (id: string, timeout = 8000) => {
|
||||
return cy.get(`[data-testid="${id}"]`, { timeout });
|
||||
@@ -71,3 +31,74 @@ Cypress.Commands.add('forbidGeolocation', () => {
|
||||
cy.stub(window.navigator.geolocation, 'watchPosition').callsFake(callback);
|
||||
});
|
||||
});
|
||||
|
||||
// Select arrival city by name
|
||||
Cypress.Commands.add('selectArrivalCity', (cityName: string) => {
|
||||
cy.getByTestId('city-autocomplete-input-arrival').clear().type(cityName);
|
||||
cy.getByTestId('city-dropdown-option').contains(cityName).click();
|
||||
});
|
||||
|
||||
// Select departure city by name
|
||||
Cypress.Commands.add('selectDepartureCity', (cityName: string) => {
|
||||
cy.getByTestId('city-autocomplete-input-departure').clear().type(cityName);
|
||||
cy.getByTestId('city-dropdown-option').contains(cityName).click();
|
||||
});
|
||||
|
||||
// Set arrival date using date picker
|
||||
Cypress.Commands.add('setArrivalDate', (date: string) => {
|
||||
cy.getByTestId('arrival-date-input').clear().type(date).type('{enter}');
|
||||
});
|
||||
|
||||
// Set departure date using date picker
|
||||
Cypress.Commands.add('setDepartureDate', (date: string) => {
|
||||
cy.getByTestId('departure-date-input').clear().type(date).type('{enter}');
|
||||
});
|
||||
|
||||
// Click search button
|
||||
Cypress.Commands.add('clickSearchButton', () => {
|
||||
cy.getByTestId('search-button').click();
|
||||
});
|
||||
|
||||
// Get all flight results
|
||||
Cypress.Commands.add('getFlightResults', () => {
|
||||
return cy.getByTestId('flight-result');
|
||||
});
|
||||
|
||||
// Get first flight result
|
||||
Cypress.Commands.add('getFirstFlightResult', () => {
|
||||
return cy.getByTestId('flight-result').first();
|
||||
});
|
||||
|
||||
// Assert validation error is displayed
|
||||
Cypress.Commands.add('shouldShowValidationError', (message: string) => {
|
||||
cy.getByTestId('validation-error').should('contain', message);
|
||||
});
|
||||
|
||||
// Select language by code
|
||||
Cypress.Commands.add('selectLanguage', (langCode: string) => {
|
||||
cy.getByTestId('language-selector').click();
|
||||
cy.getByTestId(`language-option-${langCode}`).click();
|
||||
});
|
||||
|
||||
// Get current language
|
||||
Cypress.Commands.add('getCurrentLanguage', () => {
|
||||
return cy.getByTestId('language-selector').invoke('text');
|
||||
});
|
||||
|
||||
// Swipe right (for mobile navigation)
|
||||
Cypress.Commands.add('swipeRight', { prevSubject: 'element' }, (subject) => {
|
||||
cy.wrap(subject)
|
||||
.trigger('touchstart', { which: 1, pageX: 0, pageY: 0 })
|
||||
.trigger('touchmove', { which: 1, pageX: 100, pageY: 0 })
|
||||
.trigger('touchend', { which: 1, pageX: 100, pageY: 0 });
|
||||
return cy.wrap(subject);
|
||||
});
|
||||
|
||||
// Swipe left (for mobile navigation)
|
||||
Cypress.Commands.add('swipeLeft', { prevSubject: 'element' }, (subject) => {
|
||||
cy.wrap(subject)
|
||||
.trigger('touchstart', { which: 1, pageX: 100, pageY: 0 })
|
||||
.trigger('touchmove', { which: 1, pageX: 0, pageY: 0 })
|
||||
.trigger('touchend', { which: 1, pageX: 0, pageY: 0 });
|
||||
return cy.wrap(subject);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* Cypress test fixtures for Aeroflot Flights Web application
|
||||
*/
|
||||
|
||||
export const CITIES = {
|
||||
arrival: [
|
||||
{
|
||||
name: 'Москва',
|
||||
code: 'MOW',
|
||||
latitude: 55.7558,
|
||||
longitude: 37.6173,
|
||||
},
|
||||
{
|
||||
name: 'Санкт-Петербург',
|
||||
code: 'LED',
|
||||
latitude: 59.8011,
|
||||
longitude: 30.2642,
|
||||
},
|
||||
{
|
||||
name: 'Анапа',
|
||||
code: 'AAQ',
|
||||
latitude: 44.8972,
|
||||
longitude: 37.3426,
|
||||
},
|
||||
{
|
||||
name: 'Екатеринбург',
|
||||
code: 'SVX',
|
||||
latitude: 56.7365,
|
||||
longitude: 60.8025,
|
||||
},
|
||||
{
|
||||
name: 'Новосибирск',
|
||||
code: 'OVB',
|
||||
latitude: 55.0077,
|
||||
longitude: 82.9484,
|
||||
},
|
||||
],
|
||||
departure: [
|
||||
{
|
||||
name: 'Москва',
|
||||
code: 'MOW',
|
||||
latitude: 55.7558,
|
||||
longitude: 37.6173,
|
||||
},
|
||||
{
|
||||
name: 'Сочи',
|
||||
code: 'AER',
|
||||
latitude: 43.4391,
|
||||
longitude: 39.9566,
|
||||
},
|
||||
{
|
||||
name: 'Казань',
|
||||
code: 'KZN',
|
||||
latitude: 55.6084,
|
||||
longitude: 49.2808,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const MOCK_FLIGHTS_ARRIVAL = [
|
||||
{
|
||||
carrier: 'SU',
|
||||
number: '001',
|
||||
aircraft: 'A320',
|
||||
estimatedTime: '10:15',
|
||||
actualTime: '10:20',
|
||||
status: 'Landed',
|
||||
terminal: 'A',
|
||||
gate: '12',
|
||||
checkIn: '09:15-10:15',
|
||||
},
|
||||
{
|
||||
carrier: 'SU',
|
||||
number: '002',
|
||||
aircraft: 'A330',
|
||||
estimatedTime: '14:30',
|
||||
actualTime: '14:28',
|
||||
status: 'Landed',
|
||||
terminal: 'B',
|
||||
gate: '24',
|
||||
checkIn: '13:30-14:30',
|
||||
},
|
||||
{
|
||||
carrier: 'SU',
|
||||
number: '003',
|
||||
aircraft: 'B737',
|
||||
estimatedTime: '22:45',
|
||||
actualTime: null,
|
||||
status: 'On Schedule',
|
||||
terminal: 'A',
|
||||
gate: '15',
|
||||
checkIn: '21:45-22:45',
|
||||
},
|
||||
];
|
||||
|
||||
export const MOCK_FLIGHTS_DEPARTURE = [
|
||||
{
|
||||
carrier: 'SU',
|
||||
number: '101',
|
||||
aircraft: 'A320',
|
||||
estimatedTime: '08:00',
|
||||
actualTime: '08:05',
|
||||
status: 'Departed',
|
||||
terminal: 'A',
|
||||
gate: '5',
|
||||
checkIn: '06:00-07:45',
|
||||
},
|
||||
{
|
||||
carrier: 'SU',
|
||||
number: '102',
|
||||
aircraft: 'A330',
|
||||
estimatedTime: '12:30',
|
||||
actualTime: null,
|
||||
status: 'Boarding',
|
||||
terminal: 'B',
|
||||
gate: '18',
|
||||
checkIn: '10:30-12:15',
|
||||
},
|
||||
];
|
||||
|
||||
export const POPULAR_REQUESTS = [
|
||||
{
|
||||
departure: 'Москва',
|
||||
departureCode: 'MOW',
|
||||
arrival: 'Анапа',
|
||||
arrivalCode: 'AAQ',
|
||||
frequency: 'High',
|
||||
},
|
||||
{
|
||||
departure: 'Москва',
|
||||
departureCode: 'MOW',
|
||||
arrival: 'Сочи',
|
||||
arrivalCode: 'AER',
|
||||
frequency: 'High',
|
||||
},
|
||||
{
|
||||
departure: 'Санкт-Петербург',
|
||||
departureCode: 'LED',
|
||||
arrival: 'Москва',
|
||||
arrivalCode: 'MOW',
|
||||
frequency: 'Medium',
|
||||
},
|
||||
];
|
||||
|
||||
export const LANGUAGES = [
|
||||
{
|
||||
code: 'ru',
|
||||
name: 'Русский',
|
||||
nativeName: 'Русский',
|
||||
},
|
||||
{
|
||||
code: 'en',
|
||||
name: 'English',
|
||||
nativeName: 'English',
|
||||
},
|
||||
{
|
||||
code: 'es',
|
||||
name: 'Spanish',
|
||||
nativeName: 'Español',
|
||||
},
|
||||
{
|
||||
code: 'fr',
|
||||
name: 'French',
|
||||
nativeName: 'Français',
|
||||
},
|
||||
{
|
||||
code: 'it',
|
||||
name: 'Italian',
|
||||
nativeName: 'Italiano',
|
||||
},
|
||||
{
|
||||
code: 'ja',
|
||||
name: 'Japanese',
|
||||
nativeName: '日本語',
|
||||
},
|
||||
{
|
||||
code: 'ko',
|
||||
name: 'Korean',
|
||||
nativeName: '한국어',
|
||||
},
|
||||
{
|
||||
code: 'zh',
|
||||
name: 'Chinese',
|
||||
nativeName: '中文',
|
||||
},
|
||||
{
|
||||
code: 'de',
|
||||
name: 'German',
|
||||
nativeName: 'Deutsch',
|
||||
},
|
||||
];
|
||||
|
||||
export const TEST_USERS = {
|
||||
guest: {
|
||||
username: null,
|
||||
displayName: 'Guest',
|
||||
},
|
||||
authenticated: {
|
||||
username: 'testuser@example.com',
|
||||
displayName: 'Test User',
|
||||
},
|
||||
};
|
||||
Vendored
+13
-1
@@ -3,6 +3,18 @@ declare namespace Cypress {
|
||||
interface Chainable {
|
||||
getByTestId(id: string, timeout?: number): Chainable;
|
||||
mockGeolocation({ latitude, longitude }): void;
|
||||
forbidGeolocation();
|
||||
forbidGeolocation(): void;
|
||||
selectArrivalCity(cityName: string): Chainable;
|
||||
selectDepartureCity(cityName: string): Chainable;
|
||||
setArrivalDate(date: string): Chainable;
|
||||
setDepartureDate(date: string): Chainable;
|
||||
clickSearchButton(): Chainable;
|
||||
getFlightResults(): Chainable;
|
||||
getFirstFlightResult(): Chainable;
|
||||
shouldShowValidationError(message: string): Chainable;
|
||||
selectLanguage(langCode: string): Chainable;
|
||||
getCurrentLanguage(): Chainable;
|
||||
swipeRight(): Chainable;
|
||||
swipeLeft(): Chainable;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
// This support file is processed and loaded automatically
|
||||
// before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// When a command from ./commands is ready to use, import with `import './commands'` syntax
|
||||
import './commands';
|
||||
|
||||
// Clear application state before each test
|
||||
beforeEach(() => {
|
||||
cy.window().then((win) => {
|
||||
// Clear localStorage
|
||||
win.localStorage.clear();
|
||||
// Clear sessionStorage
|
||||
win.sessionStorage.clear();
|
||||
// Clear IndexedDB if available
|
||||
if (win.indexedDB && typeof win.indexedDB.databases === 'function') {
|
||||
win.indexedDB.databases().then((dbs: any[]) => {
|
||||
dbs.forEach(db => {
|
||||
win.indexedDB.deleteDatabase(db.name);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Generated
+25956
-15895
File diff suppressed because it is too large
Load Diff
@@ -15,11 +15,16 @@
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
|
||||
"test": "ng test --code-coverage",
|
||||
"test:ci": "ng test --watch=false --reporters=teamcity",
|
||||
"test:e2e": "cypress run",
|
||||
"pretty": "prettier --write \"./**/*.{ts,html}\"",
|
||||
"analyze": "webpack-bundle-analyzer dist/stats.json",
|
||||
"docs:json": "compodoc -p ./tsconfig.json -e json -d .",
|
||||
"storybook": "npm run docs:json && start-storybook -p 6006",
|
||||
"build-storybook": "npm run docs:json && build-storybook"
|
||||
"build-storybook": "npm run docs:json && build-storybook",
|
||||
"cypress:open": "cypress open",
|
||||
"cypress:run": "cypress run",
|
||||
"cypress:run:all": "cypress run --spec 'cypress/integration/**/*.ts'",
|
||||
"cypress:run:feature": "cypress run --spec 'cypress/integration/**/*.ts' --headed"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "~12.2.13",
|
||||
@@ -64,10 +69,11 @@
|
||||
"@storybook/testing-library": "0.0.9",
|
||||
"@types/jasmine": "^3.10.2",
|
||||
"@types/leaflet": "^1.7.1",
|
||||
"@types/node": "^12.11.1",
|
||||
"@types/node": "^12.20.55",
|
||||
"@typescript-eslint/eslint-plugin": "^5.4.0",
|
||||
"@typescript-eslint/parser": "^5.4.0",
|
||||
"babel-loader": "^8.2.4",
|
||||
"cypress": "^13.17.0",
|
||||
"eslint": "^8.2.0",
|
||||
"eslint-config-prettier": "8.3.0",
|
||||
"eslint-plugin-storybook": "^0.5.7",
|
||||
@@ -82,6 +88,7 @@
|
||||
"prettier": "2.4.1",
|
||||
"start-server-and-test": "~1.14.0",
|
||||
"timezone-mock": "^1.3.2",
|
||||
"ts-loader": "^9.5.7",
|
||||
"typescript": "~4.3.5",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user