import { test, expect } from '@playwright/test'; import { login } from './helpers/auth'; import { epic, feature, severity, description, tag } from './helpers/allure'; import { navigateToManagement, navigateToTerminals } from './helpers/navigation'; import * as fs from 'fs'; test.describe('Management Tests', () => { test.setTimeout(120000); test.beforeEach(async ({ page }) => { epic('Management'); await login(page); await navigateToManagement(page); await page.waitForTimeout(2000); }); test('should add a new cashier user', async ({ page }) => { test.setTimeout(120000); feature('Management Operations'); tag('functional'); severity('critical'); description('Add a new cashier user by filling the form, selecting branch, role, and an unassociated terminal ID'); const timestamp = Date.now(); const fullName = `Test Cashier ${timestamp}`; const username = `cashier${timestamp}`; const password = 'Mav@1234'; // First, go to terminals page to find an unassociated terminal ID let freeTerminalId = ''; await test.step('Find unassociated terminal from Terminals page', async () => { // Navigate to terminals page to find a free terminal await navigateToTerminals(page); await page.waitForTimeout(5000); const tableVisible = await page.locator('table tbody tr').first().isVisible({ timeout: 15000 }).catch(() => false); if (!tableVisible) { console.log('Terminal table not loaded, skipping terminal lookup'); return; } const headers = await page.locator('table thead th').allTextContents(); console.log('Terminal table headers:', headers); // Find Cashier column index and Terminal Id column index const cashierColIdx = headers.findIndex(h => h.toLowerCase().includes('cashier') && !h.toLowerCase().includes('id') && !h.toLowerCase().includes('username')); const terminalIdColIdx = headers.findIndex(h => h.toLowerCase().includes('terminal id')); console.log(`Cashier col: ${cashierColIdx}, Terminal Id col: ${terminalIdColIdx}`); // Search through pages for a terminal with no cashier for (let pageNum = 1; pageNum <= 3 && !freeTerminalId; pageNum++) { if (pageNum > 1) { const pageBtn = page.locator(`button:has-text("${pageNum}")`).first(); const vis = await pageBtn.isVisible().catch(() => false); if (vis) { await pageBtn.click(); await page.waitForTimeout(2000); } else break; } const rows = page.locator('table tbody tr'); const rowCount = await rows.count(); console.log(`Page ${pageNum}: ${rowCount} rows`); for (let i = 0; i < rowCount; i++) { const cells = rows.nth(i).locator('td'); const cashierText = (await cells.nth(cashierColIdx >= 0 ? cashierColIdx : 3).textContent({ timeout: 2000 }).catch(() => ''))?.trim() || ''; const termIdText = (await cells.nth(terminalIdColIdx >= 0 ? terminalIdColIdx : 7).textContent({ timeout: 2000 }).catch(() => ''))?.trim() || ''; console.log(`Row ${i}: cashier="${cashierText}" terminalId="${termIdText}"`); // Unassociated = cashier cell is empty or "Unassigned" if ((!cashierText || cashierText === 'Unassigned' || cashierText === '-') && termIdText && termIdText !== '-' && /^\d{8,}$/.test(termIdText)) { freeTerminalId = termIdText; console.log(`✓ Found free terminal ID (8+ digits): ${freeTerminalId}`); break; } } } if (!freeTerminalId) { console.log('⚠ No unassociated terminal found — run "remove cashier" test first'); } }); await test.step('Navigate back to Management', async () => { await navigateToManagement(page); await page.waitForTimeout(3000); }); await test.step('Click Add User button', async () => { await page.waitForSelector('table tbody tr', { timeout: 10000 }).catch(() => {}); await page.waitForTimeout(1000); const addUserBtn = page.locator('button:has-text("Add User")').first(); await addUserBtn.waitFor({ state: 'visible', timeout: 10000 }); await addUserBtn.click(); await page.waitForTimeout(2000); console.log('✓ Add User modal opened'); }); await test.step('Fill Full Name', async () => { await page.waitForTimeout(1000); const fullNameInput = page.locator('#fullName').first(); await fullNameInput.waitFor({ state: 'visible', timeout: 10000 }); await fullNameInput.fill(fullName); console.log('✓ Full name filled:', fullName); }); await test.step('Fill Username', async () => { const usernameInput = page.locator('#username').first(); await usernameInput.fill(username); console.log('✓ Username filled:', username); }); await test.step('Fill Password', async () => { const passwordInput = page.locator('#password').first(); await passwordInput.fill(password); console.log('✓ Password filled'); }); await test.step('Fill Confirm Password', async () => { const confirmInput = page.locator('#confirmPassword').first(); await confirmInput.fill(password); console.log('✓ Confirm password filled'); }); await test.step('Select Branch from dropdown', async () => { const branchSelect = page.locator('.ant-select').filter({ hasText: /Select a branch/i }).first(); const isVisible = await branchSelect.isVisible().catch(() => false); if (isVisible) { await branchSelect.click(); await page.waitForTimeout(1000); console.log('✓ Branch dropdown opened'); const options = page.locator('.ant-select-item-option'); const optionCount = await options.count(); console.log(`Found ${optionCount} branch options`); for (let i = 0; i < optionCount; i++) { const optionText = (await options.nth(i).textContent())?.trim(); if (optionText && optionText !== '-' && optionText.length > 1) { await options.nth(i).click(); await page.waitForTimeout(500); console.log('✓ Branch selected:', optionText); break; } } } else { console.log('Branch dropdown not found'); } }); await test.step('Select Role: CASHIER', async () => { await page.waitForTimeout(500); const roleSelect = page.locator('.ant-select:has(.anticon-solution)').first(); const roleSelectSelector = roleSelect.locator('.ant-select-selector'); await roleSelectSelector.click(); await page.waitForTimeout(1000); console.log('✓ Role dropdown clicked'); const cashierOption = page.locator('.ant-select-item-option-content', { hasText: 'Cashier' }).first(); await cashierOption.waitFor({ state: 'visible', timeout: 5000 }); await cashierOption.click(); await page.waitForTimeout(1000); console.log('✓ CASHIER role selected'); }); await test.step('Select unassociated Terminal ID', async () => { await page.waitForTimeout(2000); const terminalLabel = page.locator('label[for="terminalId"]'); const terminalLabelVisible = await terminalLabel.isVisible({ timeout: 5000 }).catch(() => false); console.log('Terminal label visible:', terminalLabelVisible); if (terminalLabelVisible) { const terminalSelect = page.locator('.ant-select:has(.anticon-mobile)').first(); const terminalSelector = terminalSelect.locator('.ant-select-selector'); await terminalSelector.click(); await page.waitForTimeout(1500); console.log('✓ Terminal dropdown opened'); await page.waitForSelector('.ant-select-dropdown:not(.ant-select-dropdown-hidden) .ant-select-item-option', { timeout: 5000 }).catch(() => {}); const dropdownOptions = page.locator('.ant-select-dropdown:not(.ant-select-dropdown-hidden) .ant-select-item-option'); const count = await dropdownOptions.count(); console.log(`Visible terminal options: ${count}`); if (count > 0 && freeTerminalId) { // Try to find the specific free terminal we identified let selected = false; for (let i = 0; i < count; i++) { const optText = (await dropdownOptions.nth(i).textContent())?.trim(); console.log(`Option ${i}: "${optText}"`); if (optText?.includes(freeTerminalId)) { await dropdownOptions.nth(i).click({ force: true }); await page.waitForTimeout(500); console.log('✓ Terminal selected (matched free ID):', optText); selected = true; break; } } if (!selected) { // Pick first option with 8+ digit ID for (let i = 0; i < count; i++) { const optText = (await dropdownOptions.nth(i).textContent())?.trim(); const idMatch = optText?.match(/Id:\s*(\d+)/i); if (idMatch && idMatch[1].length >= 8) { await dropdownOptions.nth(i).click({ force: true }); await page.waitForTimeout(500); console.log('✓ Terminal selected (8+ digit fallback):', optText); selected = true; break; } } } if (!selected) { console.log('⚠ No terminal with 8+ digit ID found in dropdown'); } } else if (count > 0) { // No freeTerminalId from lookup — pick first with 8+ digit ID let selected = false; for (let i = 0; i < count; i++) { const optText = (await dropdownOptions.nth(i).textContent())?.trim(); console.log(`Option ${i}: "${optText}"`); const idMatch = optText?.match(/Id:\s*(\d+)/i); if (idMatch && idMatch[1].length >= 8) { await dropdownOptions.nth(i).click({ force: true }); await page.waitForTimeout(500); console.log('✓ Terminal selected (8+ digit):', optText); selected = true; break; } } if (!selected) { console.log('⚠ No terminal with 8+ digit ID found'); } } else { console.log('No terminal options — run terminals test first to free up a terminal'); } } else { console.log('Terminal label not visible — CASHIER role may not have been selected'); } }); await test.step('Submit the form', async () => { // The submit button in the modal footer has text "Add User" with UserAddOutlined icon const submitBtn = page.locator('[role="dialog"] button:has-text("Add User"), .ant-modal button:has-text("Add User")').last(); const isVisible = await submitBtn.isVisible().catch(() => false); if (isVisible) { console.log('Clicking Add User submit button...'); await submitBtn.click(); await page.waitForTimeout(3000); console.log('✓ Form submitted'); } else { console.log('Submit button not found'); } }); await test.step('Verify new cashier appears in table', async () => { // Wait for modal to close and reload page await page.waitForTimeout(3000); await navigateToManagement(page); await page.waitForTimeout(3000); // Check full page body for the username const bodyText = await page.locator('body').textContent().catch(() => ''); let userFound = bodyText?.includes(username) || false; if (!userFound) { // Try checking through a couple pages for (let p = 0; p < 3 && !userFound; p++) { const nextBtn = page.locator('.ant-pagination-next:not(.ant-pagination-disabled)').first(); const nextVis = await nextBtn.isVisible().catch(() => false); if (nextVis) { await nextBtn.click(); await page.waitForTimeout(1500); const text = await page.locator('body').textContent().catch(() => ''); if (text?.includes(username)) { userFound = true; } } else break; } } console.log(`User "${username}" found:`, userFound); expect(userFound).toBeTruthy(); }); console.log('✓ Add cashier test completed'); }); test('should add a new finance user', async ({ page }) => { test.setTimeout(120000); feature('Management Operations'); tag('functional'); severity('critical'); description('Add a new finance user by filling the form, selecting branch and Finance role, then verify in table'); const timestamp = Date.now(); const fullName = `Test Finance ${timestamp}`; const username = `finance${timestamp}`; const email = `finance${timestamp}@test.com`; const password = 'Mav@1234'; await test.step('Click Add User button', async () => { await page.waitForSelector('table tbody tr', { timeout: 20000 }).catch(() => {}); await page.waitForTimeout(1000); // Find the Add User button - it has the user-add icon and "Add User" text const addUserBtn = page.locator('button').filter({ hasText: 'Add User' }).first(); await addUserBtn.waitFor({ state: 'visible', timeout: 20000 }); await addUserBtn.click({ force: true }); await page.waitForTimeout(2000); console.log('✓ Add User modal opened'); }); await test.step('Fill Full Name', async () => { await page.waitForTimeout(1000); const fullNameInput = page.locator('#fullName').first(); await fullNameInput.waitFor({ state: 'visible', timeout: 10000 }); await fullNameInput.fill(fullName); console.log('✓ Full name filled:', fullName); }); await test.step('Fill Username', async () => { const usernameInput = page.locator('#username').first(); await usernameInput.fill(email); console.log('✓ Username (email) filled:', email); }); await test.step('Fill Password', async () => { const passwordInput = page.locator('#password').first(); await passwordInput.fill(password); console.log('✓ Password filled'); }); await test.step('Fill Confirm Password', async () => { const confirmInput = page.locator('#confirmPassword').first(); await confirmInput.fill(password); console.log('✓ Confirm password filled'); }); await test.step('Select Branch from dropdown', async () => { const branchSelect = page.locator('.ant-select').filter({ hasText: /Select a branch/i }).first(); const isVisible = await branchSelect.isVisible().catch(() => false); if (isVisible) { await branchSelect.click(); await page.waitForTimeout(1000); console.log('✓ Branch dropdown opened'); const options = page.locator('.ant-select-item-option'); const optionCount = await options.count(); for (let i = 0; i < optionCount; i++) { const optionText = (await options.nth(i).textContent())?.trim(); if (optionText && optionText !== '-' && optionText.length > 1) { await options.nth(i).click(); await page.waitForTimeout(500); console.log('✓ Branch selected:', optionText); break; } } } }); await test.step('Select Role: FINANCE', async () => { await page.waitForTimeout(500); const roleSelect = page.locator('.ant-select:has(.anticon-solution)').first(); const roleSelectSelector = roleSelect.locator('.ant-select-selector'); await roleSelectSelector.click(); await page.waitForTimeout(1000); console.log('✓ Role dropdown clicked'); const financeOption = page.locator('.ant-select-item-option-content', { hasText: 'Finance' }).first(); await financeOption.waitFor({ state: 'visible', timeout: 5000 }); await financeOption.click(); await page.waitForTimeout(1000); console.log('✓ FINANCE role selected'); }); await test.step('Submit the form', async () => { const submitBtn = page.locator('.ant-modal button:has-text("Add User")').last(); const isVisible = await submitBtn.isVisible().catch(() => false); if (isVisible) { await submitBtn.click(); await page.waitForTimeout(3000); console.log('✓ Form submitted'); } }); await test.step('Verify new finance user appears in table', async () => { // Wait for modal to fully close before interacting with the page await page.waitForSelector('.ant-modal', { state: 'hidden', timeout: 15000 }).catch(() => {}); await page.waitForTimeout(2000); // Click the Finance button - find all buttons with "Finance" text and filter out the one that also has "Cashier" const allButtons = page.locator('button').filter({ hasText: 'Finance' }); const count = await allButtons.count(); console.log('Buttons with "Finance" text:', count); let financeBtn = null; for (let i = 0; i < count; i++) { const text = await allButtons.nth(i).textContent(); console.log(`Button ${i} text: "${text}"`); if (text?.includes('Finance') && !text?.includes('Cashier')) { financeBtn = allButtons.nth(i); break; } } if (!financeBtn) { // Fallback: just use the last button with Finance text (should be Finance button) financeBtn = allButtons.last(); } const visible = await financeBtn.isVisible({ timeout: 5000 }).catch(() => false); console.log('Finance button visible:', visible); if (visible) { // Use force: true to bypass modal overlay that's intercepting clicks await financeBtn.click({ force: true }); console.log('✓ Finance button clicked'); // Wait for the table to reload with Finance users await page.waitForTimeout(3000); // Check if Finance filter is active by looking for active button state const financeBtnAfterClick = page.locator('button').filter({ hasText: 'Finance' }).first(); const btnClass = await financeBtnAfterClick.getAttribute('class'); console.log('Finance button class after click:', btnClass); await page.waitForSelector('table tbody tr', { timeout: 10000 }).catch(() => {}); } // Search across all pages for the new user let userFound = false; for (let p = 0; p < 10 && !userFound; p++) { const allText = await page.locator('table').first().textContent().catch(() => ''); if (p === 0) console.log('Table content (first 400 chars):', allText?.substring(0, 400)); if (allText?.includes(fullName)) { userFound = true; console.log('✓ User found in Finance table'); break; } const nextBtn = page.locator('.ant-pagination-next:not(.ant-pagination-disabled)').first(); const hasNext = await nextBtn.isVisible().catch(() => false); if (!hasNext) break; // Use force: true to bypass any modal overlays await nextBtn.click({ force: true }); await page.waitForTimeout(1500); } console.log(`Looking for: "${fullName}"`); console.log('User found:', userFound); expect(userFound).toBeTruthy(); }); console.log('✓ Add finance user test completed'); }); test('should add a new terminal manager user', async ({ page }) => { test.setTimeout(120000); feature('Management Operations'); tag('functional'); severity('critical'); description('Add a new terminal manager user by filling the form, selecting branch and Terminal Manager role, then verify in table'); const timestamp = Date.now(); const fullName = `Test Terminal Manager ${timestamp}`; const email = `tmanager${timestamp}@test.com`; const password = 'Mav@1234'; await test.step('Click Add User button', async () => { await page.waitForSelector('table tbody tr', { timeout: 10000 }).catch(() => {}); await page.waitForTimeout(1000); const addUserBtn = page.locator('button').filter({ hasText: 'Add User' }).first(); await addUserBtn.waitFor({ state: 'visible', timeout: 10000 }); await addUserBtn.click({ force: true }); await page.waitForTimeout(2000); console.log('✓ Add User modal opened'); }); await test.step('Fill Full Name', async () => { await page.waitForTimeout(1000); const fullNameInput = page.locator('#fullName').first(); await fullNameInput.waitFor({ state: 'visible', timeout: 10000 }); await fullNameInput.fill(fullName); console.log('✓ Full name filled:', fullName); }); await test.step('Fill Username', async () => { const usernameInput = page.locator('#username').first(); await usernameInput.fill(email); console.log('✓ Username (email) filled:', email); }); await test.step('Fill Password', async () => { const passwordInput = page.locator('#password').first(); await passwordInput.fill(password); console.log('✓ Password filled'); }); await test.step('Fill Confirm Password', async () => { const confirmInput = page.locator('#confirmPassword').first(); await confirmInput.fill(password); console.log('✓ Confirm password filled'); }); await test.step('Select Role: TERMINAL MANAGER', async () => { await page.waitForTimeout(500); const roleSelect = page.locator('.ant-select:has(.anticon-solution)').first(); const roleSelectSelector = roleSelect.locator('.ant-select-selector'); await roleSelectSelector.click(); await page.waitForTimeout(1000); console.log('✓ Role dropdown clicked'); const terminalManagerOption = page.locator('.ant-select-item-option-content', { hasText: 'Terminal Manager' }).first(); await terminalManagerOption.waitFor({ state: 'visible', timeout: 5000 }); await terminalManagerOption.click(); await page.waitForTimeout(1000); console.log('✓ TERMINAL MANAGER role selected'); }); await test.step('Select Branch from dropdown', async () => { const branchSelect = page.locator('.ant-select').filter({ hasText: /Select a branch/i }).first(); const isVisible = await branchSelect.isVisible().catch(() => false); if (isVisible) { await branchSelect.click(); await page.waitForTimeout(1000); console.log('✓ Branch dropdown opened'); const options = page.locator('.ant-select-item-option'); const optionCount = await options.count(); for (let i = 0; i < optionCount; i++) { const optionText = (await options.nth(i).textContent())?.trim(); if (optionText && optionText !== '-' && optionText.length > 1) { await options.nth(i).click(); await page.waitForTimeout(500); console.log('✓ Branch selected:', optionText); break; } } } else { console.log('Branch dropdown not shown for this role'); } }); await test.step('Submit the form', async () => { const submitBtn = page.locator('.ant-modal button:has-text("Add User")').last(); const isVisible = await submitBtn.isVisible().catch(() => false); if (isVisible) { await submitBtn.click(); await page.waitForTimeout(3000); console.log('✓ Form submitted'); } }); await test.step('Verify new terminal manager user appears in table', async () => { await page.waitForSelector('.ant-modal', { state: 'hidden', timeout: 15000 }).catch(() => {}); await page.waitForTimeout(2000); // Switch to the Terminal Managers tab (distinct from Cashiers/Finance) const allButtons = page.locator('button').filter({ hasText: 'Terminal Managers' }); const count = await allButtons.count(); console.log('Buttons with "Terminal Managers" text:', count); expect(count, 'A "Terminal Managers" tab button should exist').toBeGreaterThan(0); const terminalManagersBtn = allButtons.first(); await terminalManagersBtn.click({ force: true }); console.log('✓ Terminal Managers tab clicked'); await page.waitForTimeout(3000); await page.waitForSelector('table tbody tr', { timeout: 10000 }).catch(() => {}); // Search across all pages for the new user let userFound = false; for (let p = 0; p < 10 && !userFound; p++) { const allText = await page.locator('table').first().textContent().catch(() => ''); if (p === 0) console.log('Table content (first 400 chars):', allText?.substring(0, 400)); if (allText?.includes(fullName)) { userFound = true; console.log('✓ User found in Terminal Managers table'); break; } const nextBtn = page.locator('.ant-pagination-next:not(.ant-pagination-disabled)').first(); const hasNext = await nextBtn.isVisible().catch(() => false); if (!hasNext) break; await nextBtn.click({ force: true }); await page.waitForTimeout(1500); } console.log(`Looking for: "${fullName}"`); console.log('User found:', userFound); expect(userFound, 'Newly created terminal manager should appear in the Terminal Managers table').toBeTruthy(); }); console.log('✓ Add terminal manager user test completed'); }); test('should switch to Branches tab', async ({ page }) => { feature('Management Operations'); tag('functional'); severity('normal'); description('Verify switching between Users and Branches tabs works correctly and Branches table displays data'); await test.step('Click Branches tab', async () => { const branchesTab = page.locator('[role="tab"]:has-text("Branches")').first(); await branchesTab.waitFor({ state: 'visible', timeout: 20000 }); await branchesTab.click(); await page.waitForTimeout(2000); console.log('✓ Clicked Branches tab'); }); await test.step('Verify Branches table is displayed', async () => { await page.waitForSelector('table tbody tr', { timeout: 10000 }).catch(() => {}); const headers = await page.locator('table thead th').allTextContents(); console.log('Branches table headers:', headers.join(' | ')); const hasId = headers.some(h => h.toLowerCase().includes('id')); const hasBranchName = headers.some(h => h.toLowerCase().includes('branch name')); const hasAddress = headers.some(h => h.toLowerCase().includes('address')); console.log(`Has ID: ${hasId}, Has Branch name: ${hasBranchName}, Has Address: ${hasAddress}`); expect(hasId || hasBranchName).toBeTruthy(); const rows = await page.locator('table tbody tr').count(); console.log('Branch rows:', rows); expect(rows).toBeGreaterThan(0); }); await test.step('Verify Add Branch button is visible', async () => { const addBranchBtn = page.locator('button:has-text("Add Branch")').first(); const isVisible = await addBranchBtn.isVisible().catch(() => false); console.log('Add Branch button visible:', isVisible); expect(isVisible).toBeTruthy(); }); await test.step('Switch back to Users tab', async () => { const usersTab = page.locator('[role="tab"]:has-text("Users")').first(); await usersTab.click(); await page.waitForTimeout(2000); console.log('✓ Switched back to Users tab'); const rows = await page.locator('table tbody tr').count(); console.log('Users rows after switching back:', rows); expect(rows).toBeGreaterThan(0); }); console.log('✓ Tab switching verified'); }); test('should add a new branch', async ({ page }) => { test.setTimeout(120000); feature('Management Operations'); tag('functional'); severity('critical'); description('Add a new branch by filling branch name and address, then verify it appears in the Branches table'); const timestamp = Date.now(); const branchName = `Test Branch ${timestamp}`; const branchAddress = `Test Address ${timestamp}`; await test.step('Switch to Branches tab', async () => { const branchesTab = page.locator('[role="tab"]:has-text("Branches")').first(); await branchesTab.waitFor({ state: 'visible', timeout: 20000 }); await branchesTab.click(); await page.waitForTimeout(2000); console.log('✓ Switched to Branches tab'); }); await test.step('Click Add Branch button', async () => { const addBranchBtn = page.locator('button:has-text("Add Branch")').first(); await addBranchBtn.waitFor({ state: 'visible', timeout: 10000 }); await addBranchBtn.click(); await page.waitForTimeout(2000); console.log('✓ Add Branch modal opened'); }); await test.step('Fill Branch Name', async () => { // The modal has labels: "Branch", "Branch address", "Region", "City" const nameInput = page.locator('#branchName').first(); const visible = await nameInput.isVisible({ timeout: 5000 }).catch(() => false); if (visible) { await nameInput.fill(branchName); } else { // Try by placeholder as fallback const byPlaceholder = page.locator('#branchName'); await byPlaceholder.fill(branchName); } console.log('✓ Branch name filled:', branchName); }); await test.step('Fill Branch Address', async () => { const addressInput = page.locator('#username'); await addressInput.fill(branchAddress); console.log('✓ Branch address filled:', branchAddress); }); await test.step('Select Region', async () => { // Wait a moment for all fields to render await page.waitForTimeout(1000); // Click on the region select div to open dropdown const regionSelect = page.locator('.ant-select').filter({ hasText: 'Select a region' }).first(); await regionSelect.click(); await page.waitForTimeout(1000); console.log('✓ Region dropdown opened'); // Type "Asir" to search/filter the region await page.keyboard.type('Asir'); await page.waitForTimeout(1000); console.log('✓ Typed "Asir"'); // Click on the visible option const regionOption = page.locator('.ant-select-item-option-content', { hasText: 'Asir' }).first(); await regionOption.click(); await page.waitForTimeout(1000); console.log('✓ Region "Asir" selected'); }); await test.step('Select City', async () => { // Wait for region selection to complete await page.waitForTimeout(1000); // Click on the city select div to open dropdown const citySelect = page.locator('.ant-select').filter({ hasText: 'Select a city' }).first(); await citySelect.click(); await page.waitForTimeout(1000); console.log('✓ City dropdown opened'); // Type first letter to select first city option or type a specific city name // Get first city option that appears const firstCityOption = page.locator('.ant-select-item-option-content').first(); const cityName = await firstCityOption.textContent(); console.log('Selecting city:', cityName?.trim()); // Type the city name or just press Enter for first option await page.keyboard.press('Enter'); await page.waitForTimeout(1000); console.log('✓ City selected:', cityName?.trim()); }); await test.step('Add District', async () => { await page.waitForTimeout(500); // Click "Add new district" button to reveal the input const addNewDistrictBtn = page.locator('button:has-text("Add new district")').first(); const btnVisible = await addNewDistrictBtn.isVisible({ timeout: 5000 }).catch(() => false); if (btnVisible) { await addNewDistrictBtn.click(); await page.waitForTimeout(1000); console.log('✓ Clicked Add new district'); // Fill the district name in the text input that appears const districtInput = page.locator('input.ant-input[type="text"]:not([role="combobox"])').last(); await districtInput.waitFor({ state: 'visible', timeout: 5000 }); const districtName = `District ${Date.now()}`; await districtInput.fill(districtName); await page.waitForTimeout(500); console.log('✓ District name filled:', districtName); // Click "Add" button (primary button with plus icon) const addBtn = page.locator('button.ant-btn-primary:has-text("Add")').first(); await addBtn.click(); await page.waitForTimeout(1000); console.log('✓ District added'); } else { // Fallback: just select from existing district dropdown (rc_select_7) const districtSelect = page.locator('#rc_select_7'); await districtSelect.click(); await page.waitForTimeout(800); const option = page.locator('[role="option"]').first(); await option.waitFor({ state: 'visible', timeout: 5000 }); const optText = await option.textContent(); await option.click(); await page.waitForTimeout(500); console.log('✓ District selected:', optText?.trim()); } }); await test.step('Submit the form', async () => { // Click the "Add Branch" button with plus-square icon const submitBtn = page.locator('button:has(.anticon-plus-square):has-text("Add Branch")').first(); const isVisible = await submitBtn.isVisible({ timeout: 5000 }).catch(() => false); if (isVisible) { await submitBtn.click(); await page.waitForTimeout(3000); console.log('✓ Form submitted'); } else { // Fallback const fallback = page.locator('button:has-text("Add branch")').last(); await fallback.click(); await page.waitForTimeout(3000); console.log('✓ Form submitted (fallback)'); } }); await test.step('Verify new branch appears in table', async () => { // Wait for modal to close await page.waitForTimeout(5000); // Check the page body for the branch name const bodyText = await page.locator('body').textContent().catch(() => ''); console.log(`Looking for: "${branchName}"`); const branchFound = bodyText?.includes(branchName) || false; console.log('Branch found on page:', branchFound); expect(branchFound).toBeTruthy(); }); console.log('✓ Add branch test completed'); }); test('should download report from Users tab and verify content', async ({ page }) => { test.setTimeout(120000); feature('Management Operations'); tag('functional'); severity('critical'); description('Download CSV report from Users tab and cross-check content against the table on the portal'); const tableRows: string[][] = []; let tableHeaders: string[] = []; await test.step('Collect ALL table data from portal (all pages)', async () => { await page.waitForSelector('table tbody tr', { timeout: 10000 }); tableHeaders = await page.locator('table thead th').allTextContents(); console.log('Table headers:', tableHeaders.join(' | ')); // Use only the first visible table const table = page.locator('table').first(); // This table only grows (every "add user" test run creates a real, // permanent record), so an unbounded loop eventually times out the // whole test - cap it like the refunds export test does. const maxPages = 20; let pageNum = 1; while (true) { const rows = table.locator('tbody tr:not(.ant-table-placeholder)'); const rowCount = await rows.count(); for (let i = 0; i < rowCount; i++) { const cells = await rows.nth(i).locator('td').allTextContents(); const rowData = cells.map(c => c.trim()); // Skip rows where all cells are empty const hasContent = rowData.some(c => c.length > 0 && !c.match(/^No data$/i)); if (hasContent) { tableRows.push(rowData); } } if (pageNum >= maxPages) { console.log(`⚠ Reached safety cap of ${maxPages} pages - stopping early, table may have more rows`); break; } const nextBtn = page.locator('.ant-pagination-next:not(.ant-pagination-disabled)').first(); const hasNext = await nextBtn.isVisible().catch(() => false); if (hasNext) { await nextBtn.click(); await page.waitForTimeout(1500); pageNum++; } else { break; } } console.log(`Total rows collected across ${pageNum} pages: ${tableRows.length}`); console.log('First row:', tableRows[0]?.join(' | ')); expect(tableRows.length).toBeGreaterThan(0); }); let csvContent = ''; await test.step('Download CSV report', async () => { const downloadBtn = page.locator('button:has-text("Download Report")').first(); await downloadBtn.click(); await page.waitForTimeout(2000); const csvOption = page.locator('text=CSV').first(); await csvOption.click(); await page.waitForTimeout(500); const downloadPromise = page.waitForEvent('download', { timeout: 15000 }); const dlBtn = page.locator('.ant-modal-wrap button span:text-is("Download")').first(); await dlBtn.click(); const download = await downloadPromise; expect(download).toBeTruthy(); console.log('✓ Downloaded:', download.suggestedFilename()); const filePath = await download.path(); if (filePath) { csvContent = fs.readFileSync(filePath, 'utf-8'); console.log('CSV lines:', csvContent.split('\n').length); console.log('CSV first 300 chars:', csvContent.substring(0, 300)); } }); await test.step('Cross-check table data against CSV', async () => { expect(csvContent).toBeTruthy(); const csvLines = csvContent.split('\n').filter((l: string) => l.trim()); const csvDataLines = csvLines.length - 1; // minus header console.log('CSV header:', csvLines[0]); console.log(`Portal total rows: ${tableRows.length}`); console.log(`CSV data rows: ${csvDataLines}`); let matchCount = 0; const unmatched: string[] = []; for (const row of tableRows) { const keyValue = row[1] || row[0]; if (keyValue && csvContent.includes(keyValue)) { matchCount++; } else { unmatched.push(keyValue); } } console.log(`Matched ${matchCount}/${tableRows.length} portal rows found in CSV`); if (unmatched.length > 0) console.log('Unmatched:', unmatched.join(', ')); expect(matchCount).toBeGreaterThan(0); }); console.log('✓ Users download + cross-check completed'); }); test('should download finance report and verify content', async ({ page }) => { test.setTimeout(120000); feature('Management Operations'); tag('functional'); severity('critical'); description('Download Finance CSV report and cross-check content against the finance table on the portal'); const tableRows: string[][] = []; await test.step('Switch to Finance view and collect ALL data', async () => { await page.waitForSelector('table tbody tr', { timeout: 20000 }).catch(() => {}); const financeBtn = page.locator('button span:text-is("Finance")').first(); await financeBtn.waitFor({ state: 'visible', timeout: 20000 }); await financeBtn.click({ force: true }); await page.waitForTimeout(3000); await page.waitForSelector('table tbody tr', { timeout: 10000 }); const headers = await page.locator('table thead th').allTextContents(); console.log('Finance table headers:', headers.join(' | ')); const table = page.locator('table').first(); let pageNum = 1; while (true) { const rows = table.locator('tbody tr:not(.ant-table-placeholder)'); const rowCount = await rows.count(); for (let i = 0; i < rowCount; i++) { const cells = await rows.nth(i).locator('td').allTextContents(); const rowData = cells.map(c => c.trim()); const hasContent = rowData.some(c => c.length > 0 && !c.match(/^No data$/i)); if (hasContent) { tableRows.push(rowData); } } const nextBtn = page.locator('.ant-pagination-next:not(.ant-pagination-disabled)').first(); const hasNext = await nextBtn.isVisible().catch(() => false); if (hasNext) { await nextBtn.click(); await page.waitForTimeout(1500); pageNum++; } else { break; } } console.log(`Total finance rows collected across ${pageNum} pages: ${tableRows.length}`); expect(tableRows.length).toBeGreaterThan(0); }); let csvContent = ''; await test.step('Download Finance CSV report', async () => { const cashiersBtn = page.locator('button span:text-is("Cashiers")').first(); const cashiersVisible = await cashiersBtn.isVisible().catch(() => false); if (cashiersVisible) { await cashiersBtn.click(); await page.waitForTimeout(1000); } const downloadBtn = page.locator('button:has-text("Download Report")').first(); await downloadBtn.click(); await page.waitForTimeout(2000); const financeToggle = page.locator('.ant-modal-wrap button span:text-is("Finance")').first(); await financeToggle.click(); await page.waitForTimeout(500); const csvOption = page.locator('text=CSV').first(); await csvOption.click(); await page.waitForTimeout(500); const downloadPromise = page.waitForEvent('download', { timeout: 15000 }); const dlBtn = page.locator('.ant-modal-wrap button span:text-is("Download")').first(); await dlBtn.click(); const download = await downloadPromise; expect(download).toBeTruthy(); console.log('✓ Downloaded:', download.suggestedFilename()); const filePath = await download.path(); if (filePath) { csvContent = fs.readFileSync(filePath, 'utf-8'); console.log('CSV lines:', csvContent.split('\n').length); console.log('CSV first 300 chars:', csvContent.substring(0, 300)); } }); await test.step('Cross-check finance data against CSV', async () => { expect(csvContent).toBeTruthy(); const csvLines = csvContent.split('\n').filter((l: string) => l.trim()); const csvDataLines = csvLines.length - 1; console.log('CSV header:', csvLines[0]); console.log(`Portal total rows: ${tableRows.length}`); console.log(`CSV data rows: ${csvDataLines}`); let matchCount = 0; const unmatched: string[] = []; for (const row of tableRows) { const keyValue = row[1] || row[0]; if (keyValue && csvContent.includes(keyValue)) { matchCount++; } else { unmatched.push(keyValue); } } console.log(`Matched ${matchCount}/${tableRows.length} finance rows found in CSV`); if (unmatched.length > 0) console.log('Unmatched:', unmatched.join(', ')); expect(matchCount).toBeGreaterThan(0); }); console.log('✓ Finance download + cross-check completed'); }); test('should download report from Branches tab and verify content', async ({ page }) => { test.setTimeout(120000); feature('Management Operations'); tag('functional'); severity('critical'); description('Download CSV report from Branches tab and cross-check content against the table on the portal'); const tableRows: string[][] = []; await test.step('Switch to Branches and collect ALL data', async () => { const branchesTab = page.locator('[role="tab"]:has-text("Branches")').first(); await branchesTab.click(); await page.waitForTimeout(3000); // Get the visible table (Branches tab table, not the hidden Users table) const tables = page.locator('table'); const tableCount = await tables.count(); console.log(`Tables in DOM: ${tableCount}`); // Find the table that has branch-related headers let branchTable = tables.first(); for (let t = 0; t < tableCount; t++) { const hdrs = await tables.nth(t).locator('thead th').allTextContents(); const hdrText = hdrs.join(' '); if (hdrText.includes('Branch') && hdrText.includes('Address')) { branchTable = tables.nth(t); console.log(`Using table ${t} with headers: ${hdrs.join(' | ')}`); break; } } let pageNum = 1; while (true) { const rows = branchTable.locator('tbody tr:not(.ant-table-placeholder)'); const rowCount = await rows.count(); for (let i = 0; i < rowCount; i++) { const cells = await rows.nth(i).locator('td').allTextContents(); const rowData = cells.map(c => c.trim()); const hasContent = rowData.some(c => c.length > 0 && !c.match(/^No data$/i)); if (hasContent) { tableRows.push(rowData); } } const nextBtn = page.locator('.ant-pagination-next:not(.ant-pagination-disabled)').first(); const hasNext = await nextBtn.isVisible().catch(() => false); if (hasNext) { await nextBtn.click(); await page.waitForTimeout(1500); pageNum++; } else { break; } } console.log(`Total branch rows collected across ${pageNum} pages: ${tableRows.length}`); if (tableRows.length > 0) console.log('First row:', tableRows[0]?.join(' | ')); expect(tableRows.length).toBeGreaterThan(0); }); let csvContent = ''; await test.step('Download Branches CSV report', async () => { const downloadBtn = page.locator('button:has-text("Download Report")').first(); await downloadBtn.click(); await page.waitForTimeout(2000); const csvOption = page.locator('text=CSV').first(); await csvOption.click(); await page.waitForTimeout(500); const downloadPromise = page.waitForEvent('download', { timeout: 15000 }); const dlBtn = page.locator('.ant-modal-wrap button span:text-is("Download")').first(); await dlBtn.click(); const download = await downloadPromise; expect(download).toBeTruthy(); console.log('✓ Downloaded:', download.suggestedFilename()); const filePath = await download.path(); if (filePath) { csvContent = fs.readFileSync(filePath, 'utf-8'); console.log('CSV lines:', csvContent.split('\n').length); console.log('CSV first 300 chars:', csvContent.substring(0, 300)); } }); await test.step('Cross-check branches data against CSV', async () => { expect(csvContent).toBeTruthy(); const csvLines = csvContent.split('\n').filter((l: string) => l.trim()); const csvDataLines = csvLines.length - 1; console.log('CSV header:', csvLines[0]); console.log(`Portal total rows: ${tableRows.length}`); console.log(`CSV data rows: ${csvDataLines}`); let matchCount = 0; const unmatched: string[] = []; for (const row of tableRows) { const keyValue = row[1] || row[0]; if (keyValue && csvContent.includes(keyValue)) { matchCount++; } else { unmatched.push(keyValue); } } console.log(`Matched ${matchCount}/${tableRows.length} branch rows found in CSV`); if (unmatched.length > 0) console.log('Unmatched:', unmatched.join(', ')); expect(matchCount).toBeGreaterThan(0); }); console.log('✓ Branches download + cross-check completed'); }); test('should paginate through cashier list', async ({ page }) => { feature('Management Operations'); tag('functional'); severity('normal'); description('Test pagination controls navigate to page 2 and load a genuinely different set of cashiers'); const table = page.locator('table').first(); await table.locator('tbody tr:not([aria-hidden="true"])').first().waitFor({ state: 'visible', timeout: 25000 }); const headers = await table.locator('thead th').allTextContents(); const idIdx = headers.findIndex((h) => h.trim().toLowerCase() === 'cashier id'); expect(idIdx, 'A Cashier ID column should exist').toBeGreaterThanOrEqual(0); let firstPageId = ''; await test.step('Capture first row ID on page 1', async () => { const cells = await table.locator('tbody tr:not([aria-hidden="true"])').first().locator('td').allTextContents(); firstPageId = (cells[idIdx] || '').trim(); expect(firstPageId, 'Page 1 first row should have a valid ID').toMatch(/^\d+$/); console.log('First page first row ID:', firstPageId); }); await test.step('Navigate to page 2', async () => { // Ant Design renders page numbers as