Files
flights_web/tests/e2e-angular/ru-ru/text-ellipsis.spec.ts
T
gnezim 20c19d15f4
CI / ci (push) Failing after 23s
Deploy / build-and-deploy (push) Failing after 5s
Add standalone API proxy via curl (bypasses WAF TLS fingerprinting)
Modern.js SSR intercepts all routes before any Express middleware,
so the API proxy runs as a separate Express server on port 8080.
Modern.js runs on 8081. The proxy uses curl subprocesses which go
through the system HTTPS proxy (GOST) with a proper TLS fingerprint
that the Aeroflot WAF accepts.

Usage: node scripts/dev-server.mjs (replaces pnpm dev for full-stack)

Also: remove stray e2e-angular test directory, fix env default to
same-origin /api.
2026-04-15 23:04:24 +03:00

252 lines
9.4 KiB
TypeScript

import { test, expect } from '@playwright/test';
test.describe('TextEllipsis Component - Text Truncation & Tooltips', () => {
test.beforeEach(async ({ page }) => {
// Navigate to the app
await page.goto('/');
});
test('should display long Russian city names with ellipsis', async ({ page }) => {
// Fill departure city with a long Russian name
const citySelectorDeparture = page.locator('input[placeholder*="Откуда"]').first();
await citySelectorDeparture.click();
await citySelectorDeparture.fill('Санкт-Петербург');
// Wait for autocomplete to appear
await page.waitForTimeout(300);
// Select first option
const firstOption = page.locator('[role="option"]').first();
await firstOption.click();
// Verify city name is displayed (may be truncated depending on viewport width)
const cityDisplay = page.locator('input[placeholder*="Откуда"]').first();
const value = await cityDisplay.inputValue();
expect(value).toContain('Санкт-Петербург');
});
test('should display long airport names with ellipsis', async ({ page }) => {
// Navigate to flight details where airport names are displayed
const departureCityInput = page.locator('input[placeholder*="Откуда"]').first();
await departureCityInput.click();
await departureCityInput.fill('Москва');
// Wait for suggestions
await page.waitForTimeout(300);
const firstSuggestion = page.locator('[role="option"]').first();
await firstSuggestion.click();
// Set arrival city
const arrivalCityInput = page.locator('input[placeholder*="Куда"]').first();
await arrivalCityInput.click();
await arrivalCityInput.fill('Санкт-Петербург');
await page.waitForTimeout(300);
const arrivalSuggestion = page.locator('[role="option"]').first();
await arrivalSuggestion.click();
// Submit search
await page.locator('button:has-text("Найти")').click();
// Wait for results to load
await page.waitForLoadState('networkidle');
// Verify no console errors
const consoleMessages: string[] = [];
page.on('console', (msg) => {
if (msg.type() === 'error' || msg.type() === 'warning') {
consoleMessages.push(`${msg.type()}: ${msg.text()}`);
}
});
// Check for flight results
const flightResults = page.locator('[data-testid*="flight"]').first();
if (await flightResults.isVisible()) {
// Verify results are displayed without truncation issues
expect(consoleMessages).not.toContain(jasmine.stringMatching(/error/i));
}
});
test('should show tooltip on hover for long names (desktop)', async ({ page }) => {
// Set viewport to desktop size
await page.setViewportSize({ width: 1024, height: 768 });
// Navigate to a page with flight information
const departureCityInput = page.locator('input[placeholder*="Откуда"]').first();
await departureCityInput.click();
await departureCityInput.fill('Москва');
await page.waitForTimeout(300);
const firstSuggestion = page.locator('[role="option"]').first();
await firstSuggestion.click();
const arrivalCityInput = page.locator('input[placeholder*="Куда"]').first();
await arrivalCityInput.click();
await arrivalCityInput.fill('Екатеринбург');
await page.waitForTimeout(300);
const arrivalSuggestion = page.locator('[role="option"]').first();
await arrivalSuggestion.click();
// Search for flights
await page.locator('button:has-text("Найти")').click();
await page.waitForLoadState('networkidle');
// Look for elements with title attributes (tooltip indicators)
const elementsWithTitle = await page.locator('[title]').count();
expect(elementsWithTitle).toBeGreaterThan(0);
});
test('should handle text truncation on mobile layout', async ({ page }) => {
// Set mobile viewport
await page.setViewportSize({ width: 375, height: 667 });
// Navigate and search
const departureCityInput = page.locator('input[placeholder*="Откуда"]').first();
await departureCityInput.click();
await departureCityInput.fill('Санкт-Петербург');
await page.waitForTimeout(300);
const firstSuggestion = page.locator('[role="option"]').first();
await firstSuggestion.click();
const arrivalCityInput = page.locator('input[placeholder*="Куда"]').first();
await arrivalCityInput.click();
await arrivalCityInput.fill('Новосибирск');
await page.waitForTimeout(300);
const arrivalSuggestion = page.locator('[role="option"]').first();
await arrivalSuggestion.click();
// Submit search
await page.locator('button:has-text("Найти")').click();
await page.waitForLoadState('networkidle');
// Verify page is usable and no layout shifts
const mainContent = page.locator('main').first();
expect(await mainContent.isVisible()).toBe(true);
// Check for console errors
let hasErrors = false;
page.on('console', (msg) => {
if (msg.type() === 'error') {
hasErrors = true;
}
});
await page.waitForTimeout(500);
expect(hasErrors).toBe(false);
});
test('should not cause layout issues with very long names', async ({ page }) => {
await page.setViewportSize({ width: 768, height: 1024 });
// Navigate to schedule or flight board page
const scheduleButton = page.locator('a:has-text("Расписание")');
if (await scheduleButton.isVisible()) {
await scheduleButton.click();
await page.waitForLoadState('networkidle');
// Verify layout is not broken
const mainContent = page.locator('main');
const boundingBox = await mainContent.boundingBox();
expect(boundingBox).not.toBeNull();
expect(boundingBox?.width).toBeGreaterThan(0);
}
});
test('should support Unicode characters in truncated text', async ({ page }) => {
// Test with various Unicode scripts
const testCities = [
'Санкт-Петербург', // Russian Cyrillic
'Москва', // Russian Cyrillic
'Екатеринбург', // Russian Cyrillic
];
for (const city of testCities) {
const citySelectorInput = page.locator('input[placeholder*="Откуда"]').first();
await citySelectorInput.click();
await citySelectorInput.clear();
await citySelectorInput.fill(city);
// Wait for autocomplete
await page.waitForTimeout(300);
const firstOption = page.locator('[role="option"]').first();
if (await firstOption.isVisible()) {
await firstOption.click();
// Verify text is entered correctly
const value = await citySelectorInput.inputValue();
expect(value).toContain(city.charAt(0)); // At least first character present
}
}
});
test('should maintain text accessibility with ellipsis', async ({ page }) => {
// Verify that full text is still available for screen readers via title attribute
const departureCityInput = page.locator('input[placeholder*="Откуда"]').first();
await departureCityInput.click();
await departureCityInput.fill('Санкт-Петербург');
await page.waitForTimeout(300);
const firstSuggestion = page.locator('[role="option"]').first();
await firstSuggestion.click();
// Check that page is accessible
// Simple check: verify page has proper semantic elements
const mainElement = page.locator('main').first();
expect(await mainElement.isVisible()).toBe(true);
// Verify inputs have labels or aria-labels
const inputElements = page.locator('input').first();
const ariaLabel = await inputElements.getAttribute('aria-label');
const placeholder = await inputElements.getAttribute('placeholder');
expect(ariaLabel || placeholder).toBeTruthy();
});
test('should handle rapid viewport resizing without layout breaks', async ({ page }) => {
const departureCityInput = page.locator('input[placeholder*="Откуда"]').first();
await departureCityInput.click();
await departureCityInput.fill('Москва');
await page.waitForTimeout(300);
const firstSuggestion = page.locator('[role="option"]').first();
await firstSuggestion.click();
// Rapidly change viewport sizes
const sizes = [
{ width: 375, height: 667 }, // Mobile
{ width: 768, height: 1024 }, // Tablet
{ width: 1920, height: 1080 }, // Desktop
{ width: 375, height: 667 }, // Back to mobile
];
for (const size of sizes) {
await page.setViewportSize(size);
await page.waitForTimeout(200);
// Verify page is still visible
const mainContent = page.locator('main').first();
expect(await mainContent.isVisible()).toBe(true);
}
});
test('should not create horizontal scroll with long names', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
// Navigate through app with narrow viewport
const departureCityInput = page.locator('input[placeholder*="Откуда"]').first();
await departureCityInput.click();
await departureCityInput.fill('Санкт-Петербург Пулково Международный');
await page.waitForTimeout(300);
// Check for horizontal scroll
const scrollWidth = await page.evaluate(() => document.documentElement.scrollWidth);
const clientWidth = await page.evaluate(() => document.documentElement.clientWidth);
expect(scrollWidth).toBeLessThanOrEqual(clientWidth + 1); // Allow 1px tolerance
});
});