20c19d15f4
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.
118 lines
3.6 KiB
TypeScript
118 lines
3.6 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('US-101: Persistent State Management', () => {
|
|
test('should persist search form state across page reload', async ({ page, context }) => {
|
|
await page.goto('http://localhost:3002/ru-ru/schedule');
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Fill search form
|
|
await page
|
|
.locator('input[placeholder*="city"], [aria-label*="город отправления"]')
|
|
.first()
|
|
.fill('Moscow');
|
|
await page
|
|
.locator('input[placeholder*="city"], [aria-label*="город прибытия"]')
|
|
.nth(1)
|
|
.fill('St Petersburg');
|
|
|
|
// Reload page
|
|
await page.reload();
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Check that form values are still there
|
|
const fromInput = await page.locator('input').first().inputValue();
|
|
const toInput = await page.locator('input').nth(1).inputValue();
|
|
|
|
expect(fromInput).toBe('Moscow');
|
|
expect(toInput).toBe('St Petersburg');
|
|
});
|
|
|
|
test('should respect 30-day expiration for persisted state', async ({ page }) => {
|
|
await page.goto('http://localhost:3002/ru-ru/schedule');
|
|
|
|
// Store data with old timestamp (31 days ago)
|
|
await page.evaluate(() => {
|
|
const thirtyOneDaysAgo = Date.now() - 31 * 24 * 60 * 60 * 1000;
|
|
const data = JSON.stringify({
|
|
value: { test: 'data' },
|
|
timestamp: thirtyOneDaysAgo,
|
|
});
|
|
localStorage.setItem('aeroflot_expiredTest', data);
|
|
});
|
|
|
|
// Reload and check if expired data is gone
|
|
await page.reload();
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const expiredData = await page.evaluate(() => {
|
|
return localStorage.getItem('aeroflot_expiredTest');
|
|
});
|
|
|
|
expect(expiredData).toBeNull();
|
|
});
|
|
|
|
test('should handle localStorage quota gracefully', async ({ page }) => {
|
|
await page.goto('http://localhost:3002/ru-ru/schedule');
|
|
|
|
// Try to fill with large data (may trigger quota exceeded)
|
|
const largeData = 'x'.repeat(10000);
|
|
|
|
// Should not crash, should cleanup or fail gracefully
|
|
const result = await page.evaluate(async (data) => {
|
|
try {
|
|
localStorage.setItem('aeroflot_largeData', data);
|
|
return { success: true };
|
|
} catch (error) {
|
|
// Quota exceeded is acceptable
|
|
return { success: false, quotaExceeded: true };
|
|
}
|
|
}, largeData);
|
|
|
|
// Either succeeds or fails gracefully with quota exceeded
|
|
expect(result.success || result.quotaExceeded).toBe(true);
|
|
});
|
|
|
|
test('should clear state when requested', async ({ page }) => {
|
|
await page.goto('http://localhost:3002/ru-ru/schedule');
|
|
|
|
// Store data
|
|
await page.evaluate(() => {
|
|
localStorage.setItem(
|
|
'aeroflot_clearTest',
|
|
JSON.stringify({
|
|
value: { test: 'data' },
|
|
timestamp: Date.now(),
|
|
}),
|
|
);
|
|
});
|
|
|
|
// Clear it
|
|
await page.evaluate(() => {
|
|
localStorage.removeItem('aeroflot_clearTest');
|
|
});
|
|
|
|
// Verify it's gone
|
|
const cleared = await page.evaluate(() => {
|
|
return localStorage.getItem('aeroflot_clearTest');
|
|
});
|
|
|
|
expect(cleared).toBeNull();
|
|
});
|
|
|
|
test('should console have zero errors', async ({ page }) => {
|
|
const errors: string[] = [];
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error') errors.push(msg.text());
|
|
});
|
|
|
|
await page.goto('http://localhost:3002/ru-ru/schedule');
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Interact with persistent state
|
|
await page.locator('input').first().fill('Moscow');
|
|
await page.reload();
|
|
|
|
expect(errors).toHaveLength(0);
|
|
});
|
|
});
|