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.
172 lines
6.3 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|