Files
flights_web/tests/e2e-angular/ru-ru/input-validation.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

172 lines
6.3 KiB
TypeScript

import { test, expect } from '@playwright/test';
test.describe('Input Validation and XSS Prevention (US-89)', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
// Accept any cookie consent if present
const cookieButton = page.locator('button:has-text("Accept")').first();
if (await cookieButton.isVisible({ timeout: 1000 }).catch(() => false)) {
await cookieButton.click();
}
});
test.describe('Flight Search - Valid Input Handling', () => {
test('should accept valid Cyrillic city names', async ({ page }) => {
const departureContainer = page.locator('[data-testid="filter-route-departure-input"]');
const departureInput = departureContainer.locator('input').first();
// Enter valid city name
await departureInput.fill('Москва');
const inputValue = await departureInput.inputValue();
expect(inputValue).toBe('Москва');
});
test('should accept valid Latin city names', async ({ page }) => {
const arrivalContainer = page.locator('[data-testid="filter-route-arrival-input"]');
const arrivalInput = arrivalContainer.locator('input').first();
// Enter valid city name
await arrivalInput.fill('Paris');
const inputValue = await arrivalInput.inputValue();
expect(inputValue).toBe('Paris');
});
test('should trim whitespace from city input', async ({ page }) => {
const departureContainer = page.locator('[data-testid="filter-route-departure-input"]');
const departureInput = departureContainer.locator('input').first();
// Enter city with extra whitespace
await departureInput.fill(' Москва ');
await departureInput.blur();
const inputValue = await departureInput.inputValue();
// After sanitization, whitespace should be trimmed
expect(inputValue.trim()).toBe('Москва');
});
});
test.describe('No Console Errors on Valid Input', () => {
test('should not produce XSS-related console errors when handling valid input', async ({
page,
}) => {
// Listen for console errors that indicate XSS attempts
const xssErrors: string[] = [];
page.on('console', (msg) => {
const text = msg.text();
if (
msg.type() === 'error' &&
(text.includes('script') || text.includes('xss') || text.includes('alert'))
) {
xssErrors.push(text);
}
});
const departureContainer = page.locator('[data-testid="filter-route-departure-input"]');
const departureInput = departureContainer.locator('input').first();
// Enter valid city names
const testInputs = ['Москва', 'Paris', 'London'];
for (const input of testInputs) {
await departureInput.clear();
await departureInput.fill(input);
await departureInput.blur();
await page.waitForTimeout(50);
}
// Should not have any XSS-related console errors
expect(xssErrors).toHaveLength(0);
});
});
test.describe('XSS Attack Prevention', () => {
test('should not execute injected script tags', async ({ page }) => {
let scriptExecuted = false;
page.on('dialog', () => {
scriptExecuted = true;
});
const departureContainer = page.locator('[data-testid="filter-route-departure-input"]');
const departureInput = departureContainer.locator('input').first();
// Try to inject script
await departureInput.fill('<script>alert("xss")</script>');
await departureInput.blur();
await page.waitForTimeout(200);
// Alert dialog should not appear
expect(scriptExecuted).toBe(false);
});
test('should not execute event handler injections', async ({ page }) => {
let eventTriggered = false;
page.on('dialog', () => {
eventTriggered = true;
});
const departureContainer = page.locator('[data-testid="filter-route-departure-input"]');
const departureInput = departureContainer.locator('input').first();
// Try event handler injection via attribute
await departureInput.fill('City" onload="alert(1)');
await departureInput.blur();
await page.waitForTimeout(200);
// Event should not fire
expect(eventTriggered).toBe(false);
});
test('should not execute onerror handlers in IMG tags', async ({ page }) => {
let errorTriggered = false;
page.on('dialog', () => {
errorTriggered = true;
});
const departureContainer = page.locator('[data-testid="filter-route-departure-input"]');
const departureInput = departureContainer.locator('input').first();
// Try to inject img with onerror
await departureInput.fill('<img src=invalid onerror="alert(1)">');
await departureInput.blur();
await page.waitForTimeout(200);
// Alert should not fire
expect(errorTriggered).toBe(false);
});
});
test.describe('Search Functionality Preserved', () => {
test('should preserve functionality with valid city input', async ({ page }) => {
const departureContainer = page.locator('[data-testid="filter-route-departure-input"]');
const departureInput = departureContainer.locator('input').first();
const arrivalContainer = page.locator('[data-testid="filter-route-arrival-input"]');
const arrivalInput = arrivalContainer.locator('input').first();
// Enter valid cities
await departureInput.fill('Москва');
await arrivalInput.fill('Paris');
expect(await departureInput.inputValue()).toBe('Москва');
expect(await arrivalInput.inputValue()).toBe('Paris');
});
test('should allow search button to be clicked after valid input', async ({ page }) => {
const departureContainer = page.locator('[data-testid="filter-route-departure-input"]');
const departureInput = departureContainer.locator('input').first();
const arrivalContainer = page.locator('[data-testid="filter-route-arrival-input"]');
const arrivalInput = arrivalContainer.locator('input').first();
const searchBtn = page.locator('[data-testid="filter-route-search"]');
// Enter valid cities
await departureInput.fill('Москва');
await arrivalInput.fill('Paris');
// Verify search button is visible and clickable
expect(await searchBtn.isVisible()).toBe(true);
expect(await searchBtn.isEnabled()).toBe(true);
});
});
});