/** * QA-FILTER-021: Filters Tests * * E2E Test Suite for Advanced Filters on Scenarios Page * - Region filter * - Cost filter * - Status filter * - Combined filters * - URL sync with query params * - Clear filters * - Search by name */ import { test, expect } from '@playwright/test'; import { navigateTo, waitForLoading, createScenarioViaAPI, deleteScenarioViaAPI, startScenarioViaAPI, generateTestScenarioName, } from './utils/test-helpers'; import { generateTestUser, loginUserViaUI, registerUserViaAPI, } from './utils/auth-helpers'; import { newScenarioData } from './fixtures/test-scenarios'; // Test data storage let testUser: { email: string; password: string; fullName: string } | null = null; let accessToken: string | null = null; const createdScenarioIds: string[] = []; // Test scenario names for cleanup const scenarioNames = { usEast: generateTestScenarioName('Filter-US-East'), euWest: generateTestScenarioName('Filter-EU-West'), apSouth: generateTestScenarioName('Filter-AP-South'), lowCost: generateTestScenarioName('Filter-Low-Cost'), highCost: generateTestScenarioName('Filter-High-Cost'), running: generateTestScenarioName('Filter-Running'), draft: generateTestScenarioName('Filter-Draft'), searchMatch: generateTestScenarioName('Filter-Search-Match'), }; test.describe('QA-FILTER-021: Filters Setup', () => { test.beforeAll(async ({ request }) => { // Register and login test user testUser = generateTestUser('Filters'); const auth = await registerUserViaAPI( request, testUser.email, testUser.password, testUser.fullName ); accessToken = auth.access_token; // Create test scenarios with different properties const scenarios = [ { name: scenarioNames.usEast, region: 'us-east-1', status: 'draft' }, { name: scenarioNames.euWest, region: 'eu-west-1', status: 'draft' }, { name: scenarioNames.apSouth, region: 'ap-southeast-1', status: 'draft' }, { name: scenarioNames.searchMatch, region: 'us-west-2', status: 'draft' }, ]; for (const scenario of scenarios) { const created = await createScenarioViaAPI(request, { ...newScenarioData, name: scenario.name, region: scenario.region, }); createdScenarioIds.push(created.id); } }); test.afterAll(async ({ request }) => { // Cleanup all created scenarios for (const id of createdScenarioIds) { try { await deleteScenarioViaAPI(request, id); } catch { // Ignore cleanup errors } } }); }); // ============================================ // TEST SUITE: Region Filter // ============================================ test.describe('QA-FILTER-021: Region Filter', () => { test.beforeEach(async ({ page }) => { // Login and navigate await loginUserViaUI(page, testUser!.email, testUser!.password); await navigateTo(page, '/scenarios'); await waitForLoading(page); }); test('should apply region filter and update list', async ({ page }) => { // Find and open region filter const regionFilter = page.getByLabel(/region|select region/i).or( page.locator('[data-testid="region-filter"]').or( page.getByRole('combobox', { name: /region/i }) ) ); if (!await regionFilter.isVisible().catch(() => false)) { test.skip(true, 'Region filter not found'); } // Select US East region await regionFilter.click(); await regionFilter.selectOption?.('us-east-1') || page.getByText('us-east-1').click(); // Apply filter await page.getByRole('button', { name: /apply|filter|search/i }).click(); await page.waitForLoadState('networkidle'); // Verify list updates - should show only us-east-1 scenarios await expect(page.getByText(scenarioNames.usEast)).toBeVisible(); await expect(page.getByText(scenarioNames.euWest)).not.toBeVisible(); await expect(page.getByText(scenarioNames.apSouth)).not.toBeVisible(); }); test('should filter by eu-west-1 region', async ({ page }) => { const regionFilter = page.getByLabel(/region/i).or( page.locator('[data-testid="region-filter"]') ); if (!await regionFilter.isVisible().catch(() => false)) { test.skip(true, 'Region filter not found'); } await regionFilter.click(); await regionFilter.selectOption?.('eu-west-1') || page.getByText('eu-west-1').click(); await page.getByRole('button', { name: /apply|filter/i }).click(); await page.waitForLoadState('networkidle'); await expect(page.getByText(scenarioNames.euWest)).toBeVisible(); await expect(page.getByText(scenarioNames.usEast)).not.toBeVisible(); }); test('should show all regions when no filter selected', async ({ page }) => { // Ensure no region filter is applied const clearButton = page.getByRole('button', { name: /clear|reset/i }); if (await clearButton.isVisible().catch(() => false)) { await clearButton.click(); await page.waitForLoadState('networkidle'); } // All scenarios should be visible await expect(page.getByText(scenarioNames.usEast)).toBeVisible(); await expect(page.getByText(scenarioNames.euWest)).toBeVisible(); await expect(page.getByText(scenarioNames.apSouth)).toBeVisible(); }); }); // ============================================ // TEST SUITE: Cost Filter // ============================================ test.describe('QA-FILTER-021: Cost Filter', () => { test.beforeEach(async ({ page }) => { await loginUserViaUI(page, testUser!.email, testUser!.password); await navigateTo(page, '/scenarios'); await waitForLoading(page); }); test('should apply min cost filter', async ({ page }) => { const minCostInput = page.getByLabel(/min cost|minimum cost|from cost/i).or( page.locator('input[placeholder*="min"], input[name*="min_cost"], [data-testid*="min-cost"]') ); if (!await minCostInput.isVisible().catch(() => false)) { test.skip(true, 'Min cost filter not found'); } await minCostInput.fill('10'); await page.getByRole('button', { name: /apply|filter/i }).click(); await page.waitForLoadState('networkidle'); // Verify filtered results await expect(page.locator('table tbody tr')).toHaveCount(await page.locator('table tbody tr').count()); }); test('should apply max cost filter', async ({ page }) => { const maxCostInput = page.getByLabel(/max cost|maximum cost|to cost/i).or( page.locator('input[placeholder*="max"], input[name*="max_cost"], [data-testid*="max-cost"]') ); if (!await maxCostInput.isVisible().catch(() => false)) { test.skip(true, 'Max cost filter not found'); } await maxCostInput.fill('100'); await page.getByRole('button', { name: /apply|filter/i }).click(); await page.waitForLoadState('networkidle'); // Verify results await expect(page.locator('table tbody')).toBeVisible(); }); test('should apply cost range filter', async ({ page }) => { const minCostInput = page.getByLabel(/min cost/i).or( page.locator('[data-testid*="min-cost"]') ); const maxCostInput = page.getByLabel(/max cost/i).or( page.locator('[data-testid*="max-cost"]') ); if (!await minCostInput.isVisible().catch(() => false) || !await maxCostInput.isVisible().catch(() => false)) { test.skip(true, 'Cost range filters not found'); } await minCostInput.fill('5'); await maxCostInput.fill('50'); await page.getByRole('button', { name: /apply|filter/i }).click(); await page.waitForLoadState('networkidle'); // Verify results are filtered await expect(page.locator('table')).toBeVisible(); }); }); // ============================================ // TEST SUITE: Status Filter // ============================================ test.describe('QA-FILTER-021: Status Filter', () => { test.beforeEach(async ({ page }) => { await loginUserViaUI(page, testUser!.email, testUser!.password); await navigateTo(page, '/scenarios'); await waitForLoading(page); }); test('should filter by draft status', async ({ page }) => { const statusFilter = page.getByLabel(/status/i).or( page.locator('[data-testid="status-filter"]') ); if (!await statusFilter.isVisible().catch(() => false)) { test.skip(true, 'Status filter not found'); } await statusFilter.click(); await statusFilter.selectOption?.('draft') || page.getByText('draft', { exact: true }).click(); await page.getByRole('button', { name: /apply|filter/i }).click(); await page.waitForLoadState('networkidle'); // Verify only draft scenarios are shown const rows = page.locator('table tbody tr'); const count = await rows.count(); for (let i = 0; i < count; i++) { await expect(rows.nth(i)).toContainText('draft'); } }); test('should filter by running status', async ({ page }) => { const statusFilter = page.getByLabel(/status/i).or( page.locator('[data-testid="status-filter"]') ); if (!await statusFilter.isVisible().catch(() => false)) { test.skip(true, 'Status filter not found'); } await statusFilter.click(); await statusFilter.selectOption?.('running') || page.getByText('running', { exact: true }).click(); await page.getByRole('button', { name: /apply|filter/i }).click(); await page.waitForLoadState('networkidle'); // Verify filtered results await expect(page.locator('table')).toBeVisible(); }); }); // ============================================ // TEST SUITE: Combined Filters // ============================================ test.describe('QA-FILTER-021: Combined Filters', () => { test.beforeEach(async ({ page }) => { await loginUserViaUI(page, testUser!.email, testUser!.password); await navigateTo(page, '/scenarios'); await waitForLoading(page); }); test('should combine region and status filters', async ({ page }) => { const regionFilter = page.getByLabel(/region/i); const statusFilter = page.getByLabel(/status/i); if (!await regionFilter.isVisible().catch(() => false) || !await statusFilter.isVisible().catch(() => false)) { test.skip(true, 'Required filters not found'); } // Apply region filter await regionFilter.click(); await regionFilter.selectOption?.('us-east-1') || page.getByText('us-east-1').click(); // Apply status filter await statusFilter.click(); await statusFilter.selectOption?.('draft') || page.getByText('draft').click(); // Apply filters await page.getByRole('button', { name: /apply|filter/i }).click(); await page.waitForLoadState('networkidle'); // Verify combined results await expect(page.locator('table tbody')).toBeVisible(); }); test('should sync filters with URL query params', async ({ page }) => { const regionFilter = page.getByLabel(/region/i); if (!await regionFilter.isVisible().catch(() => false)) { test.skip(true, 'Region filter not found'); } // Apply filter await regionFilter.click(); await regionFilter.selectOption?.('eu-west-1') || page.getByText('eu-west-1').click(); await page.getByRole('button', { name: /apply|filter/i }).click(); await page.waitForLoadState('networkidle'); // Verify URL contains query params await expect(page).toHaveURL(/region=eu-west-1/); }); test('should parse filters from URL on page load', async ({ page }) => { // Navigate with query params await navigateTo(page, '/scenarios?region=us-east-1&status=draft'); await waitForLoading(page); // Verify filters are applied const url = page.url(); expect(url).toContain('region=us-east-1'); expect(url).toContain('status=draft'); // Verify filtered results await expect(page.locator('table')).toBeVisible(); }); test('should handle multiple region filters in URL', async ({ page }) => { // Navigate with multiple regions await navigateTo(page, '/scenarios?region=us-east-1®ion=eu-west-1'); await waitForLoading(page); // Verify URL is preserved await expect(page).toHaveURL(/region=/); }); }); // ============================================ // TEST SUITE: Clear Filters // ============================================ test.describe('QA-FILTER-021: Clear Filters', () => { test.beforeEach(async ({ page }) => { await loginUserViaUI(page, testUser!.email, testUser!.password); await navigateTo(page, '/scenarios'); await waitForLoading(page); }); test('should clear all filters and restore full list', async ({ page }) => { // Apply a filter first const regionFilter = page.getByLabel(/region/i); if (!await regionFilter.isVisible().catch(() => false)) { test.skip(true, 'Region filter not found'); } await regionFilter.click(); await regionFilter.selectOption?.('us-east-1') || page.getByText('us-east-1').click(); await page.getByRole('button', { name: /apply|filter/i }).click(); await page.waitForLoadState('networkidle'); // Get filtered count const filteredCount = await page.locator('table tbody tr').count(); // Clear filters const clearButton = page.getByRole('button', { name: /clear|reset|clear filters/i }); if (!await clearButton.isVisible().catch(() => false)) { test.skip(true, 'Clear filters button not found'); } await clearButton.click(); await page.waitForLoadState('networkidle'); // Verify all scenarios are visible await expect(page.getByText(scenarioNames.usEast)).toBeVisible(); await expect(page.getByText(scenarioNames.euWest)).toBeVisible(); await expect(page.getByText(scenarioNames.apSouth)).toBeVisible(); // Verify URL is cleared await expect(page).toHaveURL(/\/scenarios$/); }); test('should clear individual filter', async ({ page }) => { // Apply filters const regionFilter = page.getByLabel(/region/i); if (!await regionFilter.isVisible().catch(() => false)) { test.skip(true, 'Region filter not found'); } await regionFilter.click(); await regionFilter.selectOption?.('us-east-1'); await page.getByRole('button', { name: /apply|filter/i }).click(); await page.waitForLoadState('networkidle'); // Clear region filter specifically const regionClear = page.locator('[data-testid="clear-region"]').or( page.locator('[aria-label*="clear region"]') ); if (await regionClear.isVisible().catch(() => false)) { await regionClear.click(); await page.waitForLoadState('networkidle'); // Verify filter cleared await expect(page.locator('table tbody')).toBeVisible(); } }); test('should clear filters on page refresh if not persisted', async ({ page }) => { // Apply filter const regionFilter = page.getByLabel(/region/i); if (!await regionFilter.isVisible().catch(() => false)) { test.skip(true, 'Region filter not found'); } await regionFilter.click(); await regionFilter.selectOption?.('us-east-1') || page.getByText('us-east-1').click(); await page.getByRole('button', { name: /apply|filter/i }).click(); await page.waitForLoadState('networkidle'); // Refresh without query params await page.goto('/scenarios'); await waitForLoading(page); // All scenarios should be visible await expect(page.locator('table tbody tr')).toHaveCount( await page.locator('table tbody tr').count() ); }); }); // ============================================ // TEST SUITE: Search by Name // ============================================ test.describe('QA-FILTER-021: Search by Name', () => { test.beforeEach(async ({ page }) => { await loginUserViaUI(page, testUser!.email, testUser!.password); await navigateTo(page, '/scenarios'); await waitForLoading(page); }); test('should search scenarios by name', async ({ page }) => { const searchInput = page.getByPlaceholder(/search|search by name/i).or( page.getByLabel(/search/i).or( page.locator('input[type="search"], [data-testid="search-input"]') ) ); if (!await searchInput.isVisible().catch(() => false)) { test.skip(true, 'Search input not found'); } // Search for specific scenario await searchInput.fill('US-East'); await page.waitForTimeout(500); // Debounce wait // Verify search results await expect(page.getByText(scenarioNames.usEast)).toBeVisible(); }); test('should filter results with partial name match', async ({ page }) => { const searchInput = page.getByPlaceholder(/search/i).or( page.locator('[data-testid="search-input"]') ); if (!await searchInput.isVisible().catch(() => false)) { test.skip(true, 'Search input not found'); } // Partial search await searchInput.fill('Filter-US'); await page.waitForTimeout(500); // Should match US scenarios await expect(page.getByText(scenarioNames.usEast)).toBeVisible(); }); test('should show no results for non-matching search', async ({ page }) => { const searchInput = page.getByPlaceholder(/search/i).or( page.locator('[data-testid="search-input"]') ); if (!await searchInput.isVisible().catch(() => false)) { test.skip(true, 'Search input not found'); } // Search for non-existent scenario await searchInput.fill('xyz-non-existent-scenario-12345'); await page.waitForTimeout(500); // Verify no results or empty state const rows = page.locator('table tbody tr'); const count = await rows.count(); if (count > 0) { await expect(page.getByText(/no results|no.*found|empty/i).first()).toBeVisible(); } }); test('should combine search with other filters', async ({ page }) => { const searchInput = page.getByPlaceholder(/search/i).or( page.locator('[data-testid="search-input"]') ); const regionFilter = page.getByLabel(/region/i); if (!await searchInput.isVisible().catch(() => false) || !await regionFilter.isVisible().catch(() => false)) { test.skip(true, 'Required filters not found'); } // Apply search await searchInput.fill('Filter'); await page.waitForTimeout(500); // Apply region filter await regionFilter.click(); await regionFilter.selectOption?.('us-east-1') || page.getByText('us-east-1').click(); await page.getByRole('button', { name: /apply|filter/i }).click(); await page.waitForLoadState('networkidle'); // Verify combined results await expect(page.locator('table tbody')).toBeVisible(); }); test('should clear search and show all results', async ({ page }) => { const searchInput = page.getByPlaceholder(/search/i).or( page.locator('[data-testid="search-input"]') ); if (!await searchInput.isVisible().catch(() => false)) { test.skip(true, 'Search input not found'); } // Apply search await searchInput.fill('US-East'); await page.waitForTimeout(500); // Clear search const clearButton = page.locator('[data-testid="clear-search"]').or( page.getByRole('button', { name: /clear/i }) ); if (await clearButton.isVisible().catch(() => false)) { await clearButton.click(); } else { await searchInput.fill(''); } await page.waitForTimeout(500); // Verify all scenarios visible await expect(page.locator('table tbody')).toBeVisible(); }); }); // ============================================ // TEST SUITE: Date Range Filter // ============================================ test.describe('QA-FILTER-021: Date Range Filter', () => { test.beforeEach(async ({ page }) => { await loginUserViaUI(page, testUser!.email, testUser!.password); await navigateTo(page, '/scenarios'); await waitForLoading(page); }); test('should filter by created date range', async ({ page }) => { const dateFrom = page.getByLabel(/from|start date|date from/i).or( page.locator('input[type="date"]').first() ); if (!await dateFrom.isVisible().catch(() => false)) { test.skip(true, 'Date filter not found'); } const today = new Date().toISOString().split('T')[0]; await dateFrom.fill(today); await page.getByRole('button', { name: /apply|filter/i }).click(); await page.waitForLoadState('networkidle'); // Verify results await expect(page.locator('table tbody')).toBeVisible(); }); test('should filter by date range with from and to', async ({ page }) => { const dateFrom = page.getByLabel(/from|start date/i); const dateTo = page.getByLabel(/to|end date/i); if (!await dateFrom.isVisible().catch(() => false) || !await dateTo.isVisible().catch(() => false)) { test.skip(true, 'Date range filters not found'); } const today = new Date(); const yesterday = new Date(today); yesterday.setDate(yesterday.getDate() - 1); await dateFrom.fill(yesterday.toISOString().split('T')[0]); await dateTo.fill(today.toISOString().split('T')[0]); await page.getByRole('button', { name: /apply|filter/i }).click(); await page.waitForLoadState('networkidle'); await expect(page.locator('table tbody')).toBeVisible(); }); });