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(''); 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(''); 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); }); }); });