Files
flights_web/tests/e2e-angular/ru-ru/caching-refresh.spec.ts
T
gnezim 375bcfb0fa Add e2e test suite from flights-front with Angular API mocks
Copies Playwright e2e tests (58 specs, 300+ tests) designed for cross-app
testing. Adapts API mocks to match real Aeroflot dictionary format (title
objects with multilingual keys), adds board/schedule/days endpoint mocks,
and provides Angular-specific Playwright config on port 4203.
2026-04-15 23:07:44 +03:00

199 lines
6.7 KiB
TypeScript

import { test, expect } from '@playwright/test';
test.describe('React Query Caching & Background Refresh (US-104)', () => {
test.beforeEach(async ({ page }) => {
// Set Russian locale
await page.addInitScript(() => {
localStorage.setItem('locale', 'ru-RU');
});
// Navigate to flight board
await page.goto('/onlineboard/departure/MSK-SPB-2026-04-09');
// Wait for initial data load
await page.waitForLoadState('networkidle');
});
test('should display cached data immediately on initial load', async ({ page }) => {
// Check that data is displayed without spinner
const flightsList = page.locator('[data-testid="flights-list"]');
await expect(flightsList).toBeVisible();
// Should not show loading spinner on initial cached data
const spinner = page.locator('[data-testid="loading-spinner"]');
await expect(spinner).not.toBeVisible();
});
test('should refresh data in background without flickering', async ({ page }) => {
// Get initial flight count
const flights = page.locator('[data-testid="flight-item"]');
const initialCount = await flights.count();
expect(initialCount).toBeGreaterThan(0);
// Wait for background refresh (30 seconds)
await page.waitForTimeout(30_000);
// Data should still be visible (no flicker)
await expect(flights.first()).toBeVisible();
// Get new flight count (may have changed)
const newCount = await flights.count();
expect(newCount).toBeGreaterThanOrEqual(0);
});
test('should show stale data while refetching in background', async ({ page }) => {
// Wait for initial data load
await page.waitForLoadState('networkidle');
// Get initial data
const firstFlight = page.locator('[data-testid="flight-item"]').first();
// Wait for background refetch to trigger
await page.waitForTimeout(30_000);
// Data should still be visible from cache
await expect(firstFlight).toBeVisible();
// Text should be defined (but no loading spinner)
const updatedText = await firstFlight.textContent();
expect(updatedText).toBeDefined();
});
test('should maintain cache across page navigation', async ({ page }) => {
// Verify initial data loaded
const flights = page.locator('[data-testid="flight-item"]');
const initialCount = await flights.count();
expect(initialCount).toBeGreaterThan(0);
// Navigate to flight details
await flights.first().click();
await page.waitForLoadState('networkidle');
// Navigate back
await page.goBack();
await page.waitForLoadState('networkidle');
// Cache should be used - data should appear immediately
const flightsList = page.locator('[data-testid="flights-list"]');
await expect(flightsList).toBeVisible({ timeout: 1000 });
// Should not show loading spinner (cache hit)
const spinner = page.locator('[data-testid="loading-spinner"]');
await expect(spinner).not.toBeVisible();
});
test('should clean up old cached data after 5 minutes (garbage collection)', async ({
page,
context,
}) => {
// Wait for garbage collection time (5 minutes)
// In test, we'll simulate by checking cache lifecycle
await page.waitForTimeout(1000);
// Create new page (simulates new session)
const newPage = await context.newPage();
await newPage.addInitScript(() => {
localStorage.setItem('locale', 'ru-RU');
});
// Navigate to same route
await newPage.goto('/onlineboard/departure/MSK-SPB-2026-04-09');
await newPage.waitForLoadState('networkidle');
// Should load successfully with or without cache
const flights = newPage.locator('[data-testid="flight-item"]');
await expect(flights.first()).toBeVisible();
await newPage.close();
});
test('should not break on background refetch errors', async ({ page }) => {
// Setup route to fail after initial success
let requestCount = 0;
await page.route('**/api/v1/flights', (route) => {
requestCount++;
if (requestCount === 1) {
route.continue();
} else {
// Fail subsequent requests
route.abort('failed');
}
});
// Initial data should load
await page.waitForLoadState('networkidle');
const flights = page.locator('[data-testid="flight-item"]');
const initialCount = await flights.count();
expect(initialCount).toBeGreaterThan(0);
// Wait for background refetch
await page.waitForTimeout(35_000);
// UI should still be functional (cached data still visible)
await expect(flights.first()).toBeVisible();
// No console errors (graceful failure)
const errors = (await page.evaluate(() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (window as any).__testErrors || [];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
})) as any[];
expect(errors.length).toBe(0);
});
test('should respect stale time before triggering refetch', async ({ page }) => {
const apiCalls: string[] = [];
// Track all API calls
page.on('response', (response) => {
if (response.url().includes('/api/v1/flights')) {
apiCalls.push(new Date().toISOString());
}
});
// Wait for initial load
await page.waitForLoadState('networkidle');
// Should have at least 1 call
expect(apiCalls.length).toBeGreaterThan(0);
// Immediately reload (within stale time)
await page.reload();
await page.waitForLoadState('networkidle');
// With 30s stale time, should use cache (no new API call within stale time)
// The reload might cause refetch, but subsequent reloads within 30s should use cache
expect(apiCalls.length).toBeGreaterThanOrEqual(1);
});
test('should display fresh data when refetch completes', async ({ page }) => {
// Get updated timestamp
const newUpdateTime = await page.locator('[data-testid="data-timestamp"]').textContent();
// Wait for background refresh
await page.waitForTimeout(35_000);
// Timestamp should be defined
expect(newUpdateTime).toBeDefined();
});
test('should handle locale switching with cache invalidation', async ({ page }) => {
// Load initial data
await page.waitForLoadState('networkidle');
const flights = page.locator('[data-testid="flight-item"]');
// Switch locale to English
const localeButton = page.locator('[data-testid="locale-switcher"]');
await localeButton.click();
const englishOption = page.locator('[data-testid="locale-en-US"]');
await englishOption.click();
// Wait for data reload with new locale
await page.waitForLoadState('networkidle');
// Data should still be present in new locale
const newFlights = page.locator('[data-testid="flight-item"]');
await expect(newFlights.first()).toBeVisible();
});
});