375bcfb0fa
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.
240 lines
7.5 KiB
TypeScript
240 lines
7.5 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
const BASE_URL = process.env.BASE_URL || 'http://localhost:3001';
|
|
|
|
test.describe('US-103: Large Dataset Handling', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
// Navigate to a page that uses VirtualizedList (schedule page with large datasets)
|
|
await page.goto(`${BASE_URL}/ru-ru/schedule`, { waitUntil: 'networkidle' });
|
|
await page.waitForLoadState('networkidle');
|
|
});
|
|
|
|
test('should render large list efficiently without freezing', async ({ page }) => {
|
|
// Check for virtualized list presence
|
|
const listContainer = page.getByRole('list');
|
|
await expect(listContainer).toBeVisible();
|
|
|
|
// Measure initial rendering performance
|
|
const performanceTiming = await page.evaluate(() => {
|
|
const timing = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
|
|
return {
|
|
loadEventEnd: timing.loadEventEnd,
|
|
loadEventStart: timing.loadEventStart,
|
|
domInteractive: timing.domInteractive,
|
|
domContentLoadedEventEnd: timing.domContentLoadedEventEnd,
|
|
};
|
|
});
|
|
|
|
// Initial page load should complete
|
|
expect(performanceTiming.loadEventEnd).toBeGreaterThan(0);
|
|
});
|
|
|
|
test('should maintain smooth scrolling without console errors', async ({ page }) => {
|
|
// Capture console messages
|
|
const consoleMessages: { type: string; message: string }[] = [];
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error' || msg.type() === 'warning') {
|
|
consoleMessages.push({ type: msg.type(), message: msg.text() });
|
|
}
|
|
});
|
|
|
|
const listContainer = page.getByRole('list');
|
|
await expect(listContainer).toBeVisible();
|
|
|
|
// Scroll down multiple times to simulate large dataset navigation
|
|
for (let i = 0; i < 5; i++) {
|
|
await page.evaluate(() => {
|
|
const scrollable = document.querySelector('[role="list"]');
|
|
if (scrollable) {
|
|
scrollable.scrollTop += 200;
|
|
}
|
|
});
|
|
|
|
// Allow time for scroll events to process
|
|
await page.waitForTimeout(100);
|
|
}
|
|
|
|
// Verify no console errors were logged
|
|
const errors = consoleMessages.filter((m) => m.type === 'error');
|
|
expect(errors).toHaveLength(0);
|
|
});
|
|
|
|
test('should support keyboard navigation Home key', async ({ page }) => {
|
|
const listContainer = page.getByRole('list');
|
|
await expect(listContainer).toBeVisible();
|
|
|
|
// Focus the list
|
|
await listContainer.focus();
|
|
|
|
// Press Home key
|
|
await page.keyboard.press('Home');
|
|
|
|
// Verify list is still visible and in focus
|
|
await expect(listContainer).toBeFocused();
|
|
});
|
|
|
|
test('should support keyboard navigation End key', async ({ page }) => {
|
|
const listContainer = page.getByRole('list');
|
|
await expect(listContainer).toBeVisible();
|
|
|
|
// Focus the list
|
|
await listContainer.focus();
|
|
|
|
// Press End key
|
|
await page.keyboard.press('End');
|
|
|
|
// Verify list is still visible and in focus
|
|
await expect(listContainer).toBeFocused();
|
|
});
|
|
|
|
test('should maintain >60 FPS during scroll', async ({ page }) => {
|
|
// Enable performance monitoring
|
|
// frameMetrics tracked during scroll
|
|
|
|
const listContainer = page.getByRole('list');
|
|
await expect(listContainer).toBeVisible();
|
|
|
|
// Measure frame times during scroll
|
|
const metricsPromise = page.evaluateHandle(() => {
|
|
return new Promise<number>((resolve) => {
|
|
let frameCount = 0;
|
|
let startTime = performance.now();
|
|
const frameTimes: number[] = [];
|
|
|
|
function measureFrame() {
|
|
frameCount++;
|
|
const now = performance.now();
|
|
const frameDuration = now - startTime;
|
|
frameTimes.push(frameDuration);
|
|
|
|
if (frameCount < 30) {
|
|
// Measure 30 frames
|
|
requestAnimationFrame(measureFrame);
|
|
} else {
|
|
const avgFrameTime = frameTimes.reduce((a, b) => a + b, 0) / frameTimes.length;
|
|
resolve(avgFrameTime);
|
|
}
|
|
|
|
startTime = now;
|
|
}
|
|
|
|
requestAnimationFrame(measureFrame);
|
|
});
|
|
});
|
|
|
|
// Perform scrolling
|
|
await page.evaluate(() => {
|
|
const scrollable = document.querySelector('[role="list"]');
|
|
if (scrollable) {
|
|
let scrollAmount = 0;
|
|
const scrollInterval = setInterval(() => {
|
|
scrollable.scrollTop += 50;
|
|
scrollAmount += 50;
|
|
if (scrollAmount > 1000) {
|
|
clearInterval(scrollInterval);
|
|
}
|
|
}, 16); // ~60 FPS
|
|
}
|
|
});
|
|
|
|
const avgFrameTime = await metricsPromise;
|
|
|
|
// Frame time should be < 16ms for 60 FPS, allow some tolerance
|
|
expect(avgFrameTime).toBeLessThan(17);
|
|
});
|
|
|
|
test('should be accessible with proper ARIA attributes', async ({ page }) => {
|
|
const listContainer = page.getByRole('list');
|
|
await expect(listContainer).toBeVisible();
|
|
|
|
// Verify ARIA label exists
|
|
const ariaLabel = await listContainer.getAttribute('aria-label');
|
|
expect(ariaLabel).toBeTruthy();
|
|
|
|
// List items should be keyboard accessible
|
|
const listItems = page.locator('[role="button"]').first();
|
|
await expect(listItems).toBeVisible();
|
|
});
|
|
|
|
test('should handle rapid scroll events', async ({ page }) => {
|
|
const consoleErrors: string[] = [];
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error') {
|
|
consoleErrors.push(msg.text());
|
|
}
|
|
});
|
|
|
|
const listContainer = page.getByRole('list');
|
|
await expect(listContainer).toBeVisible();
|
|
|
|
// Perform rapid scrolling
|
|
await page.evaluate(async () => {
|
|
const scrollable = document.querySelector('[role="list"]');
|
|
if (scrollable) {
|
|
for (let i = 0; i < 20; i++) {
|
|
scrollable.scrollTop += 100;
|
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
}
|
|
}
|
|
});
|
|
|
|
// No errors should occur during rapid scrolling
|
|
expect(consoleErrors).toHaveLength(0);
|
|
});
|
|
|
|
test('should render visible items dynamically', async ({ page }) => {
|
|
const listContainer = page.getByRole('list');
|
|
await expect(listContainer).toBeVisible();
|
|
|
|
// Get initial visible item count
|
|
const initialVisibleItems = await page
|
|
.locator('[role="button"][aria-selected="false"]')
|
|
.count();
|
|
expect(initialVisibleItems).toBeGreaterThan(0);
|
|
|
|
// Scroll down
|
|
await page.evaluate(() => {
|
|
const scrollable = document.querySelector('[role="list"]');
|
|
if (scrollable) {
|
|
scrollable.scrollTop += 500;
|
|
}
|
|
});
|
|
|
|
// Wait for re-render
|
|
await page.waitForTimeout(200);
|
|
|
|
// Visible items should still exist (virtualization working)
|
|
const visibleItemsAfterScroll = await page.locator('[role="button"]').count();
|
|
expect(visibleItemsAfterScroll).toBeGreaterThan(0);
|
|
});
|
|
|
|
test('should not have memory leaks during extended scrolling', async ({ page }) => {
|
|
const consoleErrors: string[] = [];
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error') {
|
|
consoleErrors.push(msg.text());
|
|
}
|
|
});
|
|
|
|
const listContainer = page.getByRole('list');
|
|
await expect(listContainer).toBeVisible();
|
|
|
|
// Perform extended scrolling simulation
|
|
await page.evaluate(async () => {
|
|
const scrollable = document.querySelector('[role="list"]');
|
|
if (scrollable) {
|
|
for (let i = 0; i < 50; i++) {
|
|
scrollable.scrollTop += 50;
|
|
if (i % 10 === 0) {
|
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// No memory-related errors should occur
|
|
expect(consoleErrors).toHaveLength(0);
|
|
await expect(listContainer).toBeVisible();
|
|
});
|
|
});
|