feat: add responsive design e2e tests (60 tests for mobile, tablet, desktop)
This commit is contained in:
@@ -0,0 +1,429 @@
|
||||
import * as moment from 'moment';
|
||||
import { LANGUAGES } from '../../support/fixtures';
|
||||
|
||||
describe('Internationalization (i18n) Tests', () => {
|
||||
// Language codes for all 9 supported languages
|
||||
const LANG_CODES = ['ru', 'en', 'es', 'fr', 'it', 'ja', 'ko', 'zh', 'de'];
|
||||
|
||||
// Locale-specific date formats for validation
|
||||
const DATE_FORMATS = {
|
||||
ru: 'DD.MM.YYYY',
|
||||
en: 'MM/DD/YYYY',
|
||||
es: 'DD/MM/YYYY',
|
||||
fr: 'DD/MM/YYYY',
|
||||
it: 'DD/MM/YYYY',
|
||||
ja: 'YYYY/MM/DD',
|
||||
ko: 'YYYY.MM.DD',
|
||||
zh: 'YYYY/MM/DD',
|
||||
de: 'DD.MM.YYYY',
|
||||
};
|
||||
|
||||
// Decimal and thousand separators by locale
|
||||
const NUMBER_FORMATS = {
|
||||
ru: { decimal: ',', thousand: ' ' },
|
||||
en: { decimal: '.', thousand: ',' },
|
||||
es: { decimal: ',', thousand: '.' },
|
||||
fr: { decimal: ',', thousand: ' ' },
|
||||
it: { decimal: ',', thousand: '.' },
|
||||
ja: { decimal: '.', thousand: ',' },
|
||||
ko: { decimal: '.', thousand: ',' },
|
||||
zh: { decimal: '.', thousand: ',' },
|
||||
de: { decimal: ',', thousand: '.' },
|
||||
};
|
||||
|
||||
// Currency symbols by language
|
||||
const CURRENCY_SYMBOLS = {
|
||||
ru: '₽',
|
||||
en: '$',
|
||||
es: '€',
|
||||
fr: '€',
|
||||
it: '€',
|
||||
ja: '¥',
|
||||
ko: '₩',
|
||||
zh: '¥',
|
||||
de: '€',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
cy.intercept('GET', '**api/flights/**').as('getFlights');
|
||||
cy.forbidGeolocation();
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
describe('Language Switcher Tests', () => {
|
||||
it('Should display language switcher and be accessible', () => {
|
||||
cy.getByTestId('language-selector').should('be.visible');
|
||||
cy.getByTestId('language-selector').should('not.be.disabled');
|
||||
});
|
||||
|
||||
it('Should have all 9 languages available in the language selector', () => {
|
||||
cy.getByTestId('language-selector').click();
|
||||
LANG_CODES.forEach((langCode) => {
|
||||
cy.getByTestId(`language-option-${langCode}`).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('Should set default language to Russian (ru)', () => {
|
||||
cy.window().then((win) => {
|
||||
// Check localStorage for language preference
|
||||
const savedLang = win.localStorage.getItem('language') || win.localStorage.getItem('lang');
|
||||
// Default should be ru if not set
|
||||
expect(['ru', null, undefined]).to.include(savedLang);
|
||||
});
|
||||
});
|
||||
|
||||
it('Should persist language selection after page reload', () => {
|
||||
const testLang = 'en';
|
||||
cy.selectLanguage(testLang);
|
||||
|
||||
// Verify language is saved in localStorage
|
||||
cy.window().then((win) => {
|
||||
const savedLang = win.localStorage.getItem('language') || win.localStorage.getItem('lang');
|
||||
expect(savedLang).to.equal(testLang);
|
||||
});
|
||||
|
||||
// Reload page
|
||||
cy.reload();
|
||||
|
||||
// Verify language is still English after reload
|
||||
cy.selectLanguage(testLang);
|
||||
cy.window().then((win) => {
|
||||
const savedLang = win.localStorage.getItem('language') || win.localStorage.getItem('lang');
|
||||
expect(savedLang).to.equal(testLang);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Date Format Tests', () => {
|
||||
LANG_CODES.forEach((langCode) => {
|
||||
it(`Should display dates in correct format for ${langCode.toUpperCase()}`, () => {
|
||||
cy.selectLanguage(langCode);
|
||||
|
||||
// Get the expected date format for this locale
|
||||
const expectedFormat = DATE_FORMATS[langCode];
|
||||
const testDate = moment().format(expectedFormat);
|
||||
|
||||
// Check date input placeholder or label matches locale format
|
||||
cy.getByTestId('date-input').should('be.visible');
|
||||
|
||||
// Enter a date and verify it's formatted correctly in display
|
||||
const today = moment();
|
||||
const formattedDate = today.clone().locale(langCode).format(expectedFormat);
|
||||
|
||||
cy.getByTestId('date-input').clear().type(testDate).type('{enter}');
|
||||
|
||||
// Verify date displays in correct format
|
||||
cy.getByTestId('date-display').should('contain', formattedDate);
|
||||
});
|
||||
});
|
||||
|
||||
it('Should show date picker with locale-appropriate format', () => {
|
||||
cy.selectLanguage('ru');
|
||||
cy.getByTestId('calendar-input').should('be.visible');
|
||||
|
||||
// Type a date
|
||||
const today = moment().format('DD.MM.YYYY');
|
||||
cy.getByTestId('calendar-input').type(today).type('{enter}');
|
||||
|
||||
// Check that date is displayed in Russian format
|
||||
cy.getByTestId('calendar-input').invoke('val').should('include', '.');
|
||||
});
|
||||
|
||||
it('Should show date display results in locale-appropriate format', () => {
|
||||
const testLang = 'en';
|
||||
cy.selectLanguage(testLang);
|
||||
|
||||
const today = moment().format('MM/DD/YYYY');
|
||||
cy.getByTestId('calendar-input').type(today).type('{enter}');
|
||||
|
||||
// Verify displayed date matches English format
|
||||
cy.getByTestId('board-search-result').should('contain', today);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Number Formatting Tests', () => {
|
||||
it('Russian (ru) should use comma as decimal and space as thousands separator', () => {
|
||||
cy.selectLanguage('ru');
|
||||
|
||||
// Test decimal number: 1,5 (Russian format)
|
||||
const decimalTest = '1,5';
|
||||
const thousandTest = '1 000';
|
||||
|
||||
cy.getByTestId('price').then(($price) => {
|
||||
const priceText = $price.text();
|
||||
// Russian format should use comma for decimals and space for thousands
|
||||
expect(priceText).to.match(/\d[\s,]\d*/);
|
||||
});
|
||||
});
|
||||
|
||||
it('English (en) should use period as decimal and comma as thousands separator', () => {
|
||||
cy.selectLanguage('en');
|
||||
|
||||
// Test decimal number: 1.5 (English format)
|
||||
const decimalTest = '1.5';
|
||||
const thousandTest = '1,000';
|
||||
|
||||
cy.getByTestId('price').then(($price) => {
|
||||
const priceText = $price.text();
|
||||
// English format should use period for decimals and comma for thousands
|
||||
expect(priceText).to.match(/\d[.,]\d*/);
|
||||
});
|
||||
});
|
||||
|
||||
LANG_CODES.forEach((langCode) => {
|
||||
it(`Should format prices correctly for ${langCode.toUpperCase()}`, () => {
|
||||
cy.selectLanguage(langCode);
|
||||
|
||||
const format = NUMBER_FORMATS[langCode];
|
||||
cy.getByTestId('price').should('be.visible').then(($price) => {
|
||||
const priceText = $price.text();
|
||||
// Price should contain a number with appropriate formatting
|
||||
expect(priceText).to.match(/\d+/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Should display currency symbols matching the locale', () => {
|
||||
LANG_CODES.forEach((langCode) => {
|
||||
cy.selectLanguage(langCode);
|
||||
const symbol = CURRENCY_SYMBOLS[langCode];
|
||||
|
||||
cy.getByTestId('price').should('be.visible').then(($price) => {
|
||||
const priceText = $price.text();
|
||||
// Currency symbol should be present in price
|
||||
expect(priceText).to.include.oneOf([symbol, '$', '€', '₽', '¥', '₩']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Should format large numbers with thousands separators in all locales', () => {
|
||||
LANG_CODES.forEach((langCode) => {
|
||||
cy.selectLanguage(langCode);
|
||||
|
||||
cy.getByTestId('price').then(($price) => {
|
||||
const priceText = $price.text();
|
||||
// Should contain formatting for thousands
|
||||
if (priceText.length > 5) {
|
||||
expect(priceText).to.match(/[\d\s,.\s]/);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Text & Translation Tests', () => {
|
||||
it('Should translate UI text when language changes', () => {
|
||||
// Get Russian text
|
||||
cy.selectLanguage('ru');
|
||||
cy.getByTestId('search-button').then(($btn) => {
|
||||
const ruText = $btn.text();
|
||||
expect(ruText).to.not.be.empty;
|
||||
|
||||
// Switch to English and verify text changes
|
||||
cy.selectLanguage('en');
|
||||
cy.getByTestId('search-button').then(($btnEn) => {
|
||||
const enText = $btnEn.text();
|
||||
expect(enText).to.not.be.empty;
|
||||
expect(enText).to.not.equal(ruText);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
LANG_CODES.forEach((langCode) => {
|
||||
it(`Should have translations for all UI elements in ${langCode.toUpperCase()}`, () => {
|
||||
cy.selectLanguage(langCode);
|
||||
|
||||
// Check key UI elements are translated (not showing MISSING_KEY or similar)
|
||||
cy.getByTestId('search-button').then(($el) => {
|
||||
expect($el.text().toLowerCase()).to.not.include('missing');
|
||||
expect($el.text().toLowerCase()).to.not.include('undefined');
|
||||
});
|
||||
|
||||
cy.getByTestId('language-selector').then(($el) => {
|
||||
expect($el.text().toLowerCase()).to.not.include('missing');
|
||||
});
|
||||
|
||||
// Check that labels are present and translated
|
||||
cy.get('[data-testid*="label"]').each(($el) => {
|
||||
const text = $el.text();
|
||||
expect(text.toLowerCase()).to.not.include('missing');
|
||||
expect(text.toLowerCase()).to.not.include('undefined');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Should display placeholder text in correct language', () => {
|
||||
const placeholders = ['city-autocomplete-input', 'date-input'];
|
||||
|
||||
LANG_CODES.forEach((langCode) => {
|
||||
cy.selectLanguage(langCode);
|
||||
|
||||
placeholders.forEach((testId) => {
|
||||
cy.getByTestId(testId).should('have.attr', 'placeholder').then((placeholder) => {
|
||||
expect(placeholder).to.not.be.empty;
|
||||
expect(placeholder.toLowerCase()).to.not.include('missing');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Should localize error messages', () => {
|
||||
cy.selectLanguage('ru');
|
||||
|
||||
// Trigger an error (e.g., search without required fields)
|
||||
cy.getByTestId('search-button').click();
|
||||
|
||||
// Error message should be localized
|
||||
cy.getByTestId('validation-error').then(($error) => {
|
||||
const errorText = $error.text();
|
||||
expect(errorText).to.not.be.empty;
|
||||
expect(errorText.toLowerCase()).to.not.include('missing');
|
||||
});
|
||||
});
|
||||
|
||||
it('Should have no untranslated strings in any language', () => {
|
||||
LANG_CODES.forEach((langCode) => {
|
||||
cy.selectLanguage(langCode);
|
||||
|
||||
// Check entire page for common untranslated indicators
|
||||
cy.get('body').then(($body) => {
|
||||
const bodyText = $body.text();
|
||||
expect(bodyText).to.not.include('MISSING_KEY');
|
||||
expect(bodyText).to.not.include('i18n_');
|
||||
expect(bodyText).to.not.include('[object Object]');
|
||||
expect(bodyText.toLowerCase()).to.not.include('undefined_translation');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Locale-Specific UI Tests', () => {
|
||||
it('Should not overflow text on narrow screens in any language', () => {
|
||||
// Test at narrow viewport
|
||||
cy.viewport(375, 667); // Mobile size
|
||||
|
||||
LANG_CODES.forEach((langCode) => {
|
||||
cy.selectLanguage(langCode);
|
||||
|
||||
// Check buttons fit within viewport
|
||||
cy.getByTestId('search-button').then(($btn) => {
|
||||
const width = $btn.width();
|
||||
expect(width).to.be.lessThan(375);
|
||||
});
|
||||
|
||||
// Check labels don't overflow
|
||||
cy.get('[data-testid*="label"]').each(($el) => {
|
||||
const width = $el.width();
|
||||
expect(width).to.be.lessThan(375);
|
||||
});
|
||||
});
|
||||
|
||||
// Reset viewport
|
||||
cy.viewport(1280, 720);
|
||||
});
|
||||
|
||||
it('Should maintain layout integrity across all locales', () => {
|
||||
LANG_CODES.forEach((langCode) => {
|
||||
cy.selectLanguage(langCode);
|
||||
|
||||
// Check main container is visible and properly sized
|
||||
cy.get('[data-testid="main-content"]').should('be.visible').then(($main) => {
|
||||
const width = $main.width();
|
||||
expect(width).to.be.greaterThan(0);
|
||||
expect(width).to.be.lessThan(1280);
|
||||
});
|
||||
|
||||
// Check key controls are accessible
|
||||
cy.getByTestId('search-button').should('be.visible');
|
||||
cy.getByTestId('date-input').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('Should preserve button accessibility across all languages', () => {
|
||||
LANG_CODES.forEach((langCode) => {
|
||||
cy.selectLanguage(langCode);
|
||||
|
||||
// All interactive elements should be accessible
|
||||
cy.getByTestId('search-button').should('not.be.disabled').should('be.visible');
|
||||
cy.getByTestId('language-selector').should('not.be.disabled').should('be.visible');
|
||||
|
||||
// Check tab order is preserved
|
||||
cy.getByTestId('search-button').should('have.attr', 'tabindex').then((tabindex) => {
|
||||
expect(parseInt(tabindex)).to.be.greaterThanOrEqual(-1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Language Switcher Persistence and Edge Cases', () => {
|
||||
it('Should handle rapid language switching without errors', () => {
|
||||
const languages = ['ru', 'en', 'fr', 'ja'];
|
||||
|
||||
languages.forEach((lang) => {
|
||||
cy.selectLanguage(lang);
|
||||
cy.getByTestId('search-button').should('be.visible');
|
||||
});
|
||||
|
||||
// Final language should be the last one selected
|
||||
cy.window().then((win) => {
|
||||
const currentLang = win.localStorage.getItem('language') || win.localStorage.getItem('lang');
|
||||
expect(currentLang).to.equal('ja');
|
||||
});
|
||||
});
|
||||
|
||||
it('Should correctly apply locale-specific moment formats', () => {
|
||||
const testDate = moment('2026-04-15');
|
||||
|
||||
LANG_CODES.forEach((langCode) => {
|
||||
cy.selectLanguage(langCode);
|
||||
|
||||
const format = DATE_FORMATS[langCode];
|
||||
const formattedDate = testDate.clone().locale(langCode).format(format);
|
||||
|
||||
cy.getByTestId('date-input').clear().type(formattedDate).type('{enter}');
|
||||
cy.getByTestId('date-display').should('contain', formattedDate);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Comprehensive Locale Coverage', () => {
|
||||
LANGUAGES.forEach((language) => {
|
||||
describe(`Locale: ${language.code.toUpperCase()} (${language.nativeName})`, () => {
|
||||
beforeEach(() => {
|
||||
cy.selectLanguage(language.code);
|
||||
});
|
||||
|
||||
it(`Should initialize with ${language.code} selected`, () => {
|
||||
cy.window().then((win) => {
|
||||
const savedLang = win.localStorage.getItem('language') || win.localStorage.getItem('lang');
|
||||
expect(savedLang).to.equal(language.code);
|
||||
});
|
||||
});
|
||||
|
||||
it(`Should display UI in ${language.code}`, () => {
|
||||
cy.getByTestId('search-button').should('be.visible');
|
||||
cy.getByTestId('language-selector').should('be.visible');
|
||||
cy.getByTestId('date-input').should('be.visible');
|
||||
});
|
||||
|
||||
it(`Should use correct date format for ${language.code}`, () => {
|
||||
const format = DATE_FORMATS[language.code];
|
||||
const today = moment().format(format);
|
||||
|
||||
cy.getByTestId('date-input').type(today).type('{enter}');
|
||||
cy.getByTestId('date-display').should('contain', today);
|
||||
});
|
||||
|
||||
it(`Should format numbers correctly for ${language.code}`, () => {
|
||||
const numFormat = NUMBER_FORMATS[language.code];
|
||||
|
||||
cy.getByTestId('price').should('be.visible').then(($price) => {
|
||||
const priceText = $price.text();
|
||||
// Price should be formatted (contains digits and separators)
|
||||
expect(priceText).to.match(/\d+/);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,402 @@
|
||||
import * as moment from 'moment';
|
||||
|
||||
describe('Responsive Design & Mobile Tests', () => {
|
||||
const today = moment().format('DD.MM.YYYY');
|
||||
const testCity = 'Анапа';
|
||||
const testCityCode = 'AAQ';
|
||||
|
||||
// Helper to check no horizontal scrolling
|
||||
const checkNoHorizontalScroll = () => {
|
||||
cy.get('body').then(($body) => {
|
||||
const windowWidth = $body[0].ownerDocument.defaultView.innerWidth;
|
||||
const scrollWidth = $body[0].scrollWidth;
|
||||
expect(scrollWidth).to.equal(windowWidth);
|
||||
});
|
||||
};
|
||||
|
||||
// Helper to check touch target size (minimum 44x44px)
|
||||
const checkTouchTargetSize = (selector: string) => {
|
||||
cy.get(selector).should(($el) => {
|
||||
const rect = $el[0].getBoundingClientRect();
|
||||
expect(rect.width).to.be.at.least(44);
|
||||
expect(rect.height).to.be.at.least(44);
|
||||
});
|
||||
};
|
||||
|
||||
// Helper to check element is not hidden
|
||||
const checkElementVisible = (selector: string) => {
|
||||
cy.get(selector).should('be.visible').should('not.have.css', 'overflow', 'hidden');
|
||||
};
|
||||
|
||||
// Mobile Viewport Tests (375x667 - iPhone SE)
|
||||
describe('Mobile Viewport (375x667 - iPhone SE)', () => {
|
||||
beforeEach(() => {
|
||||
cy.viewport('iphone-se2');
|
||||
cy.intercept('GET', '**api/flights/v1.1/ru/board**').as('getFlights');
|
||||
cy.forbidGeolocation();
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
it('Mobile: Text is readable and not overflowing in filter section', () => {
|
||||
cy.getByTestId('filter-section').should('be.visible');
|
||||
cy.getByTestId('filter-section').then(($section) => {
|
||||
const text = $section.text();
|
||||
expect(text.length).to.be.greaterThan(0);
|
||||
expect($section[0].scrollWidth).to.equal($section[0].clientWidth);
|
||||
});
|
||||
});
|
||||
|
||||
it('Mobile: Search button has minimum touch target size (44x44px)', () => {
|
||||
checkTouchTargetSize('[data-testid="arrival-search-button"]');
|
||||
});
|
||||
|
||||
it('Mobile: City input field has proper touch target size', () => {
|
||||
checkTouchTargetSize('[data-testid="city-autocomplete-input"]');
|
||||
});
|
||||
|
||||
it('Mobile: Calendar input has sufficient touch target size', () => {
|
||||
checkTouchTargetSize('[data-testid="calendar-input"]');
|
||||
});
|
||||
|
||||
it('Mobile: No horizontal scrolling on page load', () => {
|
||||
checkNoHorizontalScroll();
|
||||
});
|
||||
|
||||
it('Mobile: No horizontal scrolling after opening accordion', () => {
|
||||
cy.getByTestId('accordion').should('exist').click();
|
||||
checkNoHorizontalScroll();
|
||||
});
|
||||
|
||||
it('Mobile: Form inputs are not hidden behind keyboard simulation', () => {
|
||||
cy.getByTestId('city-autocomplete-input').should('be.visible').should('not.have.css', 'display', 'none');
|
||||
cy.getByTestId('calendar-input').should('be.visible').should('not.have.css', 'display', 'none');
|
||||
});
|
||||
|
||||
it('Mobile: Filter labels are readable and properly spaced', () => {
|
||||
cy.getByTestId('filter-label').should('be.visible').each(($el) => {
|
||||
const fontSize = window.getComputedStyle($el[0]).fontSize;
|
||||
expect(parseInt(fontSize)).to.be.at.least(14);
|
||||
});
|
||||
});
|
||||
|
||||
it('Mobile: Input fields have adequate padding for mobile interaction', () => {
|
||||
cy.getByTestId('city-autocomplete-input').should(($el) => {
|
||||
const padding = window.getComputedStyle($el[0]).padding;
|
||||
expect(padding).to.not.equal('0px');
|
||||
});
|
||||
});
|
||||
|
||||
it('Mobile: Hamburger menu opens and closes correctly', () => {
|
||||
cy.getByTestId('hamburger-menu').should('exist').click();
|
||||
cy.getByTestId('mobile-nav').should('be.visible');
|
||||
cy.getByTestId('hamburger-menu').click();
|
||||
cy.getByTestId('mobile-nav').should('not.be.visible');
|
||||
});
|
||||
|
||||
it('Mobile: Accordion sections collapse and expand on tap', () => {
|
||||
cy.getByTestId('accordion').should('exist');
|
||||
cy.getByTestId('accordion').click();
|
||||
cy.getByTestId('accordion-content').should('be.visible');
|
||||
cy.getByTestId('accordion').click();
|
||||
cy.getByTestId('accordion-content').should('not.be.visible');
|
||||
});
|
||||
|
||||
it('Mobile: Images scale correctly without distortion', () => {
|
||||
cy.getByTestId('company-logo').should('be.visible').each(($img) => {
|
||||
const width = $img[0].getBoundingClientRect().width;
|
||||
const height = $img[0].getBoundingClientRect().height;
|
||||
expect(width).to.be.greaterThan(0);
|
||||
expect(height).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('Mobile: Button text is visible and not cut off', () => {
|
||||
cy.getByTestId('arrival-search-button').should('be.visible').should(($btn) => {
|
||||
const text = $btn.text();
|
||||
expect(text).to.have.length.greaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('Mobile: No text overflow in flight results', () => {
|
||||
cy.getByTestId('city-autocomplete-input').type(testCity);
|
||||
cy.getByTestId('calendar-input').type(today).type('{enter}');
|
||||
cy.getByTestId('arrival-search-button').click();
|
||||
cy.wait('@getFlights').then(() => {
|
||||
cy.getByTestId('flight-result').first().then(($result) => {
|
||||
expect($result[0].scrollWidth).to.equal($result[0].clientWidth);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Mobile: Touch targets for flight results are appropriately sized', () => {
|
||||
cy.getByTestId('city-autocomplete-input').type(testCity);
|
||||
cy.getByTestId('calendar-input').type(today).type('{enter}');
|
||||
cy.getByTestId('arrival-search-button').click();
|
||||
cy.wait('@getFlights').then(() => {
|
||||
checkTouchTargetSize('[data-testid="flight-result"]');
|
||||
});
|
||||
});
|
||||
|
||||
it('Mobile: Proper spacing between interactive elements', () => {
|
||||
cy.getByTestId('filter-section').should(($section) => {
|
||||
const buttons = $section.find('[data-testid="arrival-search-button"]');
|
||||
expect(buttons.length).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Tablet Viewport Tests (768x1024 - iPad 2)
|
||||
describe('Tablet Viewport (768x1024 - iPad 2)', () => {
|
||||
beforeEach(() => {
|
||||
cy.viewport('ipad-2');
|
||||
cy.intercept('GET', '**api/flights/v1.1/ru/board**').as('getFlights');
|
||||
cy.forbidGeolocation();
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
it('Tablet: Layout is optimized and not stretched', () => {
|
||||
cy.getByTestId('main-content').should('be.visible').then(($content) => {
|
||||
const width = $content[0].getBoundingClientRect().width;
|
||||
expect(width).to.be.lessThan(768);
|
||||
expect(width).to.be.greaterThan(400);
|
||||
});
|
||||
});
|
||||
|
||||
it('Tablet: Layout is not too narrow', () => {
|
||||
cy.getByTestId('filter-section').should('be.visible').then(($section) => {
|
||||
const width = $section[0].getBoundingClientRect().width;
|
||||
expect(width).to.be.greaterThan(500);
|
||||
});
|
||||
});
|
||||
|
||||
it('Tablet: Multi-column layout works correctly', () => {
|
||||
cy.getByTestId('filter-row').should('be.visible');
|
||||
cy.getByTestId('filter-row').then(($row) => {
|
||||
const columns = $row.find('[data-testid*="filter-col"]');
|
||||
expect(columns.length).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('Tablet: Touch interactions work for tapping elements', () => {
|
||||
cy.getByTestId('accordion').should('exist').trigger('touchstart').trigger('touchend');
|
||||
cy.getByTestId('accordion-content').should('be.visible');
|
||||
});
|
||||
|
||||
it('Tablet: Buttons are appropriately sized for tablet interaction', () => {
|
||||
checkTouchTargetSize('[data-testid="arrival-search-button"]');
|
||||
});
|
||||
|
||||
it('Tablet: Spacing between form elements is balanced', () => {
|
||||
cy.getByTestId('filter-section').should(($section) => {
|
||||
const marginBottom = window.getComputedStyle($section[0]).marginBottom;
|
||||
expect(marginBottom).to.not.equal('0px');
|
||||
});
|
||||
});
|
||||
|
||||
it('Tablet: No layout breaking on tablet orientation', () => {
|
||||
checkNoHorizontalScroll();
|
||||
});
|
||||
|
||||
it('Tablet: Forms fit properly within viewport', () => {
|
||||
cy.getByTestId('filter-section').should('be.visible').then(($form) => {
|
||||
const viewportWidth = window.innerWidth;
|
||||
const formWidth = $form[0].getBoundingClientRect().width;
|
||||
expect(formWidth).to.be.lessThan(viewportWidth);
|
||||
});
|
||||
});
|
||||
|
||||
it('Tablet: Input fields display correctly with proper size', () => {
|
||||
cy.getByTestId('city-autocomplete-input').should('be.visible').then(($input) => {
|
||||
const height = $input[0].getBoundingClientRect().height;
|
||||
expect(height).to.be.greaterThan(30);
|
||||
});
|
||||
});
|
||||
|
||||
it('Tablet: Swipe left gesture works on content', () => {
|
||||
cy.getByTestId('main-content').swipeLeft();
|
||||
});
|
||||
|
||||
it('Tablet: Swipe right gesture works on content', () => {
|
||||
cy.getByTestId('main-content').swipeRight();
|
||||
});
|
||||
|
||||
it('Tablet: No horizontal scrolling with all content visible', () => {
|
||||
checkNoHorizontalScroll();
|
||||
});
|
||||
|
||||
it('Tablet: Images scale appropriately for tablet display', () => {
|
||||
cy.getByTestId('company-logo').should('be.visible').each(($img) => {
|
||||
const width = $img[0].getBoundingClientRect().width;
|
||||
expect(width).to.be.greaterThan(20);
|
||||
expect(width).to.be.lessThan(150);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Desktop Viewport Tests (1920x1080)
|
||||
describe('Desktop Viewport (1920x1080)', () => {
|
||||
beforeEach(() => {
|
||||
cy.viewport(1920, 1080);
|
||||
cy.intercept('GET', '**api/flights/v1.1/ru/board**').as('getFlights');
|
||||
cy.forbidGeolocation();
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
it('Desktop: Layout scales correctly without overflow', () => {
|
||||
cy.getByTestId('main-content').should('be.visible').then(($content) => {
|
||||
expect($content[0].scrollWidth).to.equal($content[0].clientWidth);
|
||||
});
|
||||
});
|
||||
|
||||
it('Desktop: No horizontal scrolling on large viewport', () => {
|
||||
checkNoHorizontalScroll();
|
||||
});
|
||||
|
||||
it('Desktop: All content is accessible without zooming', () => {
|
||||
cy.getByTestId('filter-section').should('be.visible');
|
||||
cy.getByTestId('city-autocomplete-input').should('be.visible');
|
||||
cy.getByTestId('calendar-input').should('be.visible');
|
||||
cy.getByTestId('arrival-search-button').should('be.visible');
|
||||
});
|
||||
|
||||
it('Desktop: Multi-column layout is fully utilized', () => {
|
||||
cy.getByTestId('filter-row').should('be.visible').then(($row) => {
|
||||
const width = $row[0].getBoundingClientRect().width;
|
||||
expect(width).to.be.greaterThan(1000);
|
||||
});
|
||||
});
|
||||
|
||||
it('Desktop: Typography is appropriate for large screens', () => {
|
||||
cy.getByTestId('filter-section').should(($section) => {
|
||||
const fontSize = window.getComputedStyle($section[0]).fontSize;
|
||||
expect(parseInt(fontSize)).to.be.at.least(14);
|
||||
});
|
||||
});
|
||||
|
||||
it('Desktop: Buttons are properly proportioned for large screen', () => {
|
||||
cy.getByTestId('arrival-search-button').should('be.visible').then(($btn) => {
|
||||
const width = $btn[0].getBoundingClientRect().width;
|
||||
expect(width).to.be.greaterThan(80);
|
||||
});
|
||||
});
|
||||
|
||||
it('Desktop: Form elements are well-spaced on large viewport', () => {
|
||||
cy.getByTestId('filter-section').should(($section) => {
|
||||
const padding = window.getComputedStyle($section[0]).padding;
|
||||
expect(padding).to.not.equal('0px');
|
||||
});
|
||||
});
|
||||
|
||||
it('Desktop: Hover effects are available on buttons', () => {
|
||||
cy.getByTestId('arrival-search-button').should('be.visible');
|
||||
// Hover effect test - verify element responds to hover state
|
||||
cy.getByTestId('arrival-search-button').trigger('mouseenter');
|
||||
});
|
||||
|
||||
it('Desktop: Accordion content displays correctly on large screen', () => {
|
||||
cy.getByTestId('accordion').should('exist').click();
|
||||
cy.getByTestId('accordion-content').should('be.visible').then(($content) => {
|
||||
const width = $content[0].getBoundingClientRect().width;
|
||||
expect(width).to.be.greaterThan(200);
|
||||
});
|
||||
});
|
||||
|
||||
it('Desktop: Images are properly scaled for desktop display', () => {
|
||||
cy.getByTestId('company-logo').should('be.visible').each(($img) => {
|
||||
const width = $img[0].getBoundingClientRect().width;
|
||||
expect(width).to.be.greaterThan(40);
|
||||
});
|
||||
});
|
||||
|
||||
it('Desktop: Page layout remains optimal with full-width utilization', () => {
|
||||
cy.viewport(1920, 1080);
|
||||
cy.get('body').then(($body) => {
|
||||
const viewportWidth = window.innerWidth;
|
||||
expect(viewportWidth).to.equal(1920);
|
||||
});
|
||||
});
|
||||
|
||||
it('Desktop: All form inputs are visible and accessible', () => {
|
||||
cy.getByTestId('filter-section').find('[data-testid="city-autocomplete-input"]').should('be.visible');
|
||||
cy.getByTestId('filter-section').find('[data-testid="calendar-input"]').should('be.visible');
|
||||
});
|
||||
|
||||
it('Desktop: Navigation elements are properly sized for mouse interaction', () => {
|
||||
cy.getByTestId('hamburger-menu').should('exist').then(($menu) => {
|
||||
const width = $menu[0].getBoundingClientRect().width;
|
||||
expect(width).to.be.greaterThan(30);
|
||||
});
|
||||
});
|
||||
|
||||
it('Desktop: Content does not extend beyond safe viewport margins', () => {
|
||||
cy.get('body').then(($body) => {
|
||||
const bodyWidth = $body[0].getBoundingClientRect().width;
|
||||
const viewportWidth = window.innerWidth;
|
||||
expect(bodyWidth).to.be.lessThanOrEqual(viewportWidth);
|
||||
});
|
||||
});
|
||||
|
||||
it('Desktop: Text remains readable across large viewport', () => {
|
||||
cy.getByTestId('filter-label').should('be.visible').each(($el) => {
|
||||
const lineHeight = window.getComputedStyle($el[0]).lineHeight;
|
||||
const fontSize = window.getComputedStyle($el[0]).fontSize;
|
||||
expect(parseInt(lineHeight)).to.be.greaterThan(parseInt(fontSize));
|
||||
});
|
||||
});
|
||||
|
||||
it('Desktop: Flight search results display correctly on large viewport', () => {
|
||||
cy.getByTestId('city-autocomplete-input').type(testCity);
|
||||
cy.getByTestId('calendar-input').type(today).type('{enter}');
|
||||
cy.getByTestId('arrival-search-button').click();
|
||||
cy.wait('@getFlights').then(() => {
|
||||
cy.getByTestId('board-search-result').should('be.visible');
|
||||
cy.getByTestId('flight-result').should('have.length.at.least', 1);
|
||||
cy.getByTestId('flight-result').first().then(($result) => {
|
||||
const width = $result[0].getBoundingClientRect().width;
|
||||
expect(width).to.be.greaterThan(300);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Cross-viewport Tests
|
||||
describe('Cross-Viewport Responsive Tests', () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept('GET', '**api/flights/v1.1/ru/board**').as('getFlights');
|
||||
cy.forbidGeolocation();
|
||||
});
|
||||
|
||||
it('Responsive: Search works consistently on mobile viewport', () => {
|
||||
cy.viewport('iphone-se2');
|
||||
cy.visit('/');
|
||||
cy.getByTestId('city-autocomplete-input').type(testCity);
|
||||
cy.getByTestId('calendar-input').type(today).type('{enter}');
|
||||
cy.getByTestId('arrival-search-button').click();
|
||||
cy.wait('@getFlights').then(() => {
|
||||
cy.getByTestId('flight-result').should('have.length.at.least', 1);
|
||||
});
|
||||
});
|
||||
|
||||
it('Responsive: Search works consistently on tablet viewport', () => {
|
||||
cy.viewport('ipad-2');
|
||||
cy.visit('/');
|
||||
cy.getByTestId('city-autocomplete-input').type(testCity);
|
||||
cy.getByTestId('calendar-input').type(today).type('{enter}');
|
||||
cy.getByTestId('arrival-search-button').click();
|
||||
cy.wait('@getFlights').then(() => {
|
||||
cy.getByTestId('flight-result').should('have.length.at.least', 1);
|
||||
});
|
||||
});
|
||||
|
||||
it('Responsive: Search works consistently on desktop viewport', () => {
|
||||
cy.viewport(1920, 1080);
|
||||
cy.visit('/');
|
||||
cy.getByTestId('city-autocomplete-input').type(testCity);
|
||||
cy.getByTestId('calendar-input').type(today).type('{enter}');
|
||||
cy.getByTestId('arrival-search-button').click();
|
||||
cy.wait('@getFlights').then(() => {
|
||||
cy.getByTestId('flight-result').should('have.length.at.least', 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user