import { test, expect } from '@playwright/test'; import { login } from './helpers/auth'; import { epic, feature, severity, description, tag } from './helpers/allure'; import { navigateToTerminals, navigateToManagement } from './helpers/navigation'; test.describe('Terminal Tests', () => { test.setTimeout(90000); test.beforeEach(async ({ page }) => { epic('Terminal Management'); await login(page); await navigateToTerminals(page); await page.waitForTimeout(2000); }); // Test 2: View Terminal Details with Data Matching test('should view terminal details with all information', async ({ page }) => { feature('Terminal Operations'); tag('functional'); severity('critical'); description('View terminal details and verify Terminal Id matches and download button exists'); console.log('Testing terminal details view with data matching...'); // Variables to store data from table let terminalIdValue = ''; await test.step('Capture Terminal Id from first row', async () => { const firstRow = page.locator('table tbody tr:not([aria-hidden="true"])').first(); await expect(firstRow).toBeVisible(); const cells = firstRow.locator('td'); // Extract Terminal Id (column index 5) terminalIdValue = (await cells.nth(5).textContent())?.trim() || ''; console.log('Captured Terminal Id from table:', terminalIdValue); expect(terminalIdValue).not.toBe(''); }); await test.step('Click Details button to view terminal details', async () => { const firstRow = page.locator('table tbody tr:not([aria-hidden="true"])').first(); const detailsButton = firstRow.locator('button:has-text("Details")'); await expect(detailsButton).toBeVisible(); await detailsButton.click(); await page.waitForTimeout(2000); console.log('Details button clicked'); }); await test.step('Verify terminal details panel appears', async () => { const detailsHeading = await page.locator('text=Terminal details').first().isVisible({ timeout: 5000 }); console.log('Terminal details heading visible:', detailsHeading); expect(detailsHeading).toBeTruthy(); }); await test.step('Verify Terminal Id matches in details panel', async () => { if (terminalIdValue && terminalIdValue.trim() !== '' && terminalIdValue !== '-') { const terminalIdLabel = await page.locator('text=Terminal Id :').isVisible({ timeout: 3000 }); console.log('Terminal Id label found:', terminalIdLabel); expect(terminalIdLabel).toBeTruthy(); const terminalIdInDetails = await page.locator(`text=${terminalIdValue}`).first().isVisible({ timeout: 3000 }); console.log('Terminal Id value found in details:', terminalIdInDetails); console.log('✓ Terminal Id matches:', terminalIdValue); expect(terminalIdInDetails).toBeTruthy(); } else { console.log('Terminal Id is empty or dash, skipping verification'); } }); await test.step('Verify Download button exists in details panel', async () => { const downloadButton = page.locator('button:has-text("Download")').last(); const isVisible = await downloadButton.isVisible({ timeout: 3000 }); console.log('Download button in details panel visible:', isVisible); expect(isVisible).toBeTruthy(); // Verify it has the download icon const downloadIcon = downloadButton.locator('[data-icon="download"]'); const iconVisible = await downloadIcon.isVisible().catch(() => false); console.log('Download icon visible:', iconVisible); console.log('✓ Download button verified in terminal details'); }); console.log('Terminal details verification completed - Terminal Id matches and download button exists!'); }); // Test 3: Pagination Test test('should paginate through terminal list', async ({ page }) => { feature('Terminal Operations'); tag('functional'); severity('normal'); description('Test pagination controls navigate to page 2 and load a genuinely different set of terminals'); console.log('Testing terminal pagination...'); // Dismiss any open modals first await page.keyboard.press('Escape').catch(() => {}); await page.waitForTimeout(500); const table = page.locator('table').first(); await table.locator('tbody tr:not([aria-hidden="true"])').first().waitFor({ state: 'visible', timeout: 15000 }); const headers = await table.locator('thead th').allTextContents(); // The first column is a blank icon/expander column - match "ID" exactly so // we don't accidentally grab that blank column or "Terminal Id"/"Cashier ID". const idIdx = headers.findIndex((h) => h.trim().toLowerCase() === 'id'); expect(idIdx, 'An ID column should exist').toBeGreaterThanOrEqual(0); let firstPageId = ''; await test.step('Capture first terminal 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 terminal ID on page 1:', firstPageId); }); await test.step('Click page 2 button', async () => { // Match the exact page-number text so we don't accidentally hit an // unrelated button that merely contains "2" somewhere in its label. const page2Button = page.locator('button').filter({ hasText: /^2$/ }).first(); const hasPage2 = await page2Button.isVisible({ timeout: 10000 }).catch(() => false); test.skip(!hasPage2, 'Not enough terminals in this environment to require a second page'); await page2Button.scrollIntoViewIfNeeded(); await page2Button.click(); console.log('Clicked page 2'); }); await test.step('Verify page 2 loaded a different first row', async () => { let secondPageId = ''; for (let attempt = 0; attempt < 30; attempt++) { const cells = await table.locator('tbody tr:not([aria-hidden="true"])').first().locator('td').allTextContents().catch(() => [] as string[]); secondPageId = (cells[idIdx] || '').trim(); if (secondPageId && secondPageId !== firstPageId) break; await page.waitForTimeout(400); } console.log('First terminal ID on page 2:', secondPageId); expect(secondPageId, 'Page 2 first row should have a valid ID').toMatch(/^\d+$/); expect(secondPageId, 'Page 2 should show a different first row than page 1').not.toBe(firstPageId); const rowCount = await table.locator('tbody tr:not([aria-hidden="true"])').count(); expect(rowCount, 'Page 2 should have rows').toBeGreaterThan(0); }); await test.step('Navigate back to page 1', async () => { const page1Button = page.locator('button').filter({ hasText: /^1$/ }).first(); await expect(page1Button, 'A page 1 button should exist').toBeVisible({ timeout: 10000 }); await page1Button.click({ force: true }); await page.waitForTimeout(2000); console.log('Navigated back to page 1'); }); console.log('Pagination test completed'); }); // Test 4: Download Report Test test('should download report and verify content', async ({ page }) => { feature('Terminal Operations'); tag('functional'); severity('normal'); description('Download terminal report in XSLX, cross-check data against portal table'); console.log('Testing terminal report download with cross-check...'); // Collect table data first const tableRows: string[][] = []; await test.step('Collect terminal table data', async () => { const table = page.locator('table').first(); const headers = await table.locator('thead th').allTextContents(); const idIdx = headers.findIndex((h) => h.trim().toLowerCase() === 'id'); expect(idIdx, 'An ID column should exist').toBeGreaterThanOrEqual(0); const rows = table.locator('tbody tr:not([aria-hidden="true"])'); const rowCount = await rows.count(); console.log(`Terminal rows: ${rowCount}`); for (let i = 0; i < rowCount; i++) { const cells = await rows.nth(i).locator('td').allTextContents(); const cleaned = cells.map(c => c.trim()); // Ant Design renders a "No data" placeholder row with no real ID when // a duplicate/sync copy of the table is present - require a genuine // numeric ID so that phantom row doesn't get counted as a real terminal. if (/^\d+$/.test(cleaned[idIdx] || '')) tableRows.push(cleaned); } console.log(`Collected ${tableRows.length} rows`); expect(tableRows.length).toBeGreaterThan(0); }); let downloadedContent = ''; await test.step('Download XSLX report', async () => { const downloadButton = page.locator('button:has-text("Download report")').first(); await downloadButton.click(); await page.waitForTimeout(1000); // Select XSLX — radio input with value="3" const xlsxRadio = page.locator('input.ant-radio-input[value="3"]').first(); const xlsxVis = await xlsxRadio.isVisible().catch(() => false); if (xlsxVis) { await xlsxRadio.click({ force: true }); await page.waitForTimeout(500); console.log('✓ XSLX selected via radio value=3'); } else { // Fallback to wrapper const wrapper = page.locator('.ant-radio-button-wrapper', { hasText: 'XSLX' }).first(); await wrapper.click(); await page.waitForTimeout(500); console.log('✓ XSLX selected via wrapper'); } const downloadPromise = page.waitForEvent('download', { timeout: 15000 }); const dlBtn = page.locator('button.bg-gradient-to-tr:has-text("Download")').first(); await dlBtn.click(); const download = await downloadPromise; expect(download).toBeTruthy(); const filePath = await download.path(); console.log('✓ Downloaded:', download.suggestedFilename()); if (filePath) { const fs = await import('fs'); const XLSX = await import('xlsx'); const buf = fs.readFileSync(filePath); const wb = XLSX.read(buf, { type: 'buffer' }); const sheet = wb.Sheets[wb.SheetNames[0]]; const rows: any[][] = XLSX.utils.sheet_to_json(sheet, { header: 1 }); downloadedContent = JSON.stringify(rows); console.log(`XSLX rows: ${rows.length}`); console.log('Header:', JSON.stringify(rows[0])?.substring(0, 200)); } }); await test.step('Cross-check portal data against downloaded file', async () => { expect(downloadedContent).toBeTruthy(); const unmatched: string[] = []; for (const row of tableRows) { const key = row[0] || row[1]; // ID or name if (!key || !downloadedContent.includes(key)) unmatched.push(key || '(blank)'); } const matchCount = tableRows.length - unmatched.length; console.log(`Matched ${matchCount}/${tableRows.length} rows in XSLX`); expect(unmatched, `Every table row's ID should appear in the downloaded report (unmatched: ${unmatched.join(', ')})`).toEqual([]); }); console.log('✓ Download report + cross-check completed'); }); // Test 5: Remove Cashier from Terminal test('should remove cashier from terminal', async ({ page }) => { feature('Terminal Operations'); tag('functional'); severity('critical'); description('Remove cashier from terminal and verify it now shows Unassigned in both the details panel and the terminals table'); console.log('Testing cashier removal from terminal...'); const table = page.locator('table').first(); await table.locator('tbody tr:not([aria-hidden="true"])').first().waitFor({ state: 'visible', timeout: 15000 }); const headers = await table.locator('thead th').allTextContents(); const cashierColIdx = headers.findIndex((h) => h.trim().toLowerCase() === 'cashier'); console.log('Table headers:', headers); expect(cashierColIdx, 'A Cashier column should exist').toBeGreaterThanOrEqual(0); let targetRow = -1; let cashierName = ''; await test.step('Find a terminal with an assigned cashier', async () => { const rows = table.locator('tbody tr:not([aria-hidden="true"])'); const rowCount = await rows.count(); console.log(`Total rows: ${rowCount}`); for (let i = 0; i < rowCount; i++) { const cashierText = (await rows.nth(i).locator('td').nth(cashierColIdx).textContent({ timeout: 2000 }))?.trim() || ''; console.log(`Row ${i} cashier: "${cashierText}"`); if (cashierText && cashierText !== 'Unassigned' && cashierText !== '-') { targetRow = i; cashierName = cashierText; console.log(`✓ Found terminal with assigned cashier at row ${i}: "${cashierName}"`); break; } } expect(targetRow, 'A terminal with an assigned cashier should exist').toBeGreaterThanOrEqual(0); await rows.nth(targetRow).locator('button:has-text("Details")').click(); await page.waitForTimeout(2000); }); await test.step('Click Remove Cashier button', async () => { const removeCashierButton = page.locator('button:has-text("Remove Cashier")'); await expect(removeCashierButton, 'Remove Cashier button should be visible').toBeVisible({ timeout: 5000 }); await removeCashierButton.click(); await page.waitForTimeout(2000); console.log('Remove Cashier button clicked'); }); await test.step('Verify the terminals table row shows Unassigned', async () => { // Removing the cashier closes the details panel automatically (same // behavior the reassign-cashier test accounts for) - dismiss defensively // in case that ever changes, then read the row straight from the table. const closeBtn = page.locator('button:has-text("Close"), button.ant-modal-close, [aria-label="Close"]').first(); if (await closeBtn.isVisible({ timeout: 3000 }).catch(() => false)) { await closeBtn.click(); } else { await page.keyboard.press('Escape').catch(() => {}); } await page.waitForTimeout(2000); const cashierText = (await table.locator('tbody tr:not([aria-hidden="true"])').nth(targetRow).locator('td').nth(cashierColIdx).textContent({ timeout: 5000 }))?.trim() || ''; console.log(`Table row ${targetRow} cashier after removal: "${cashierText}" (was "${cashierName}")`); expect(cashierText, 'Table row should show Unassigned after removal').toBe('Unassigned'); }); console.log('Remove cashier test completed'); }); // Test 6: Remove cashier then reassign a different one test('should reassign cashier to terminal', async ({ page }) => { test.setTimeout(90000); feature('Terminal Operations'); tag('functional'); severity('critical'); description('Find terminal with cashier, remove cashier, then reassign a different cashier'); console.log('Testing remove + reassign different cashier...'); let targetTerminalRow = -1; let originalCashier = ''; // Step 1: Find a terminal with an assigned cashier await test.step('Find terminal with assigned cashier', async () => { const headers = await page.locator('table thead th').allTextContents(); console.log('Table headers:', headers); const rows = page.locator('table tbody tr:not([aria-hidden="true"])'); const rowCount = await rows.count(); console.log('Total rows:', rowCount); // Debug: print first few rows if (rowCount > 0) { const firstRowCells = await rows.nth(0).locator('td').allTextContents(); console.log('First row cells:', firstRowCells); } // Try to find a terminal with an assigned cashier let cashierColIdx = -1; for (let i = 0; i < rowCount; i++) { const allCells = await rows.nth(i).locator('td').allTextContents(); // Look for "Unassigned" to identify the cashier column const unassignedIdx = allCells.findIndex(cell => cell.trim() === 'Unassigned'); if (unassignedIdx >= 0) { cashierColIdx = unassignedIdx; console.log('Found cashier column at index:', cashierColIdx); break; } } // Find a terminal with an assigned cashier for (let i = 0; i < rowCount; i++) { const allCells = await rows.nth(i).locator('td').allTextContents(); const cashierText = (allCells[cashierColIdx] || '').trim(); if (cashierText && cashierText !== 'Unassigned' && cashierText !== '-') { targetTerminalRow = i; originalCashier = cashierText; console.log(`✓ Found terminal with assigned cashier at row ${i}: "${originalCashier}"`); break; } } // If no assigned cashier found, use first terminal and assign one first if (targetTerminalRow === -1) { targetTerminalRow = 0; originalCashier = 'Unassigned'; console.log('✓ No assigned cashier found. Will assign one to first terminal first.'); } expect(targetTerminalRow).toBeGreaterThanOrEqual(0); }); // Step 2: Open details and assign cashier if needed, then remove await test.step('Assign cashier if unassigned, then remove', async () => { const row = page.locator('table tbody tr:not([aria-hidden="true"])').nth(targetTerminalRow); await row.locator('button:has-text("Details")').click(); await page.waitForTimeout(2000); // If terminal is unassigned, assign a cashier first if (originalCashier === 'Unassigned') { console.log('Terminal is unassigned. Assigning a cashier first...'); const reassignBtn = page.locator('button:has-text("reassignCashier")'); const reassignVis = await reassignBtn.isVisible({ timeout: 5000 }).catch(() => false); if (reassignVis) { await reassignBtn.click(); await page.waitForTimeout(1000); // Select first available cashier. Scope to the open modal/drawer and // exclude read-only inputs: a separate, always-present, read-only // combobox elsewhere on the page (id="rc_select_0") otherwise causes // a strict-mode violation (2 elements matched) on the plain selector. const detailsPanel = page.locator('.ant-modal-content, .ant-drawer-content, [role="dialog"]').first(); const searchInput = detailsPanel.locator('input.ant-select-selection-search-input[role="combobox"]:not([readonly])').first(); await searchInput.waitFor({ state: 'visible', timeout: 5000 }); await searchInput.click(); await page.waitForTimeout(1500); const option = page.locator('.ant-select-item-option:visible').first(); const optText = await option.textContent(); await option.click(); await page.waitForTimeout(1000); console.log('✓ Assigned cashier:', optText?.trim()); // Click Done const doneBtn = page.locator('button.bg-green-500:has-text("Done")'); await doneBtn.click(); await page.waitForTimeout(3000); console.log('✓ Assignment confirmed'); // Close and reopen to see the assigned cashier const closeBtn = page.locator('button:has-text("Close"), button.ant-modal-close, [aria-label="Close"]').first(); const closeVis = await closeBtn.isVisible({ timeout: 3000 }).catch(() => false); if (closeVis) { await closeBtn.click(); await page.waitForTimeout(2000); } else { await page.keyboard.press('Escape'); await page.waitForTimeout(2000); } // Reopen await row.locator('button:has-text("Details")').click(); await page.waitForTimeout(2000); } } // Now remove the cashier const removeBtn = page.locator('button:has-text("Remove Cashier")'); const removeBtnVis = await removeBtn.isVisible({ timeout: 5000 }).catch(() => false); if (removeBtnVis) { await removeBtn.click(); await page.waitForTimeout(3000); console.log('✓ Cashier removed'); } else { console.log('⚠ Remove button not found'); } // Wait and check for reassignCashier button await page.waitForTimeout(2000); // Try different selectors for the reassign button let reassignBtn = await page.locator('button:has-text("reassignCashier")').isVisible({ timeout: 3000 }).catch(() => false); if (!reassignBtn) { // Try alternative selectors reassignBtn = await page.locator('button:has-text("Reassign")').isVisible({ timeout: 3000 }).catch(() => false); } if (!reassignBtn) { // Try looking for any button with "assign" in text reassignBtn = await page.locator('button:has-text("assign")').isVisible({ timeout: 3000 }).catch(() => false); } console.log('✓ reassignCashier button visible:', reassignBtn); }); // Step 3: Close details, reopen to see reassignCashier button await test.step('Close and reopen terminal details', async () => { // Close the details panel const closeBtn = page.locator('button:has-text("Close"), button.ant-modal-close, [aria-label="Close"]').first(); const closeVis = await closeBtn.isVisible({ timeout: 3000 }).catch(() => false); if (closeVis) { await closeBtn.click(); await page.waitForTimeout(2000); } else { await page.keyboard.press('Escape'); await page.waitForTimeout(2000); } // Reopen the same terminal details const row = page.locator('table tbody tr:not([aria-hidden="true"])').nth(targetTerminalRow); await row.locator('button:has-text("Details")').click(); await page.waitForTimeout(3000); console.log('✓ Terminal details reopened'); }); await test.step('Click reassignCashier', async () => { // Try different selectors for the reassign button let reassignBtn = page.locator('button:has-text("reassignCashier")'); let vis = await reassignBtn.isVisible({ timeout: 3000 }).catch(() => false); if (!vis) { reassignBtn = page.locator('button:has-text("Reassign")'); vis = await reassignBtn.isVisible({ timeout: 3000 }).catch(() => false); } if (!vis) { reassignBtn = page.locator('button:has-text("assign")'); vis = await reassignBtn.isVisible({ timeout: 3000 }).catch(() => false); } if (vis) { await reassignBtn.scrollIntoViewIfNeeded().catch(() => {}); await page.waitForTimeout(500); await reassignBtn.click(); await page.waitForTimeout(1000); console.log('✓ reassignCashier clicked'); } else { console.log('⚠ reassignCashier button not found, trying force click'); try { await reassignBtn.click({ force: true }); await page.waitForTimeout(1000); console.log('✓ reassignCashier clicked (force)'); } catch (e) { console.log('⚠ Force click failed:', e); } } }); await test.step('Select a different cashier', async () => { const detailsPanel = page.locator('.ant-modal-content, .ant-drawer-content, [role="dialog"]').first(); const searchInput = detailsPanel.locator('input.ant-select-selection-search-input[role="combobox"]:not([readonly])').first(); await searchInput.waitFor({ state: 'visible', timeout: 5000 }); // Click to open dropdown and see all available cashiers await searchInput.click(); await page.waitForTimeout(1500); // Get all available options const options = page.locator('.ant-select-item-option:visible'); const optCount = await options.count(); if (optCount > 0) { // Select the first available cashier (skip if it's the original) const firstOption = options.first(); const optText = await firstOption.textContent(); // If first option is the original cashier, try the second one if (optText?.trim() === originalCashier && optCount > 1) { const secondOption = options.nth(1); const secondText = await secondOption.textContent(); await secondOption.click(); await page.waitForTimeout(1000); console.log('✓ Selected new cashier (second option):', secondText?.trim()); } else { await firstOption.click(); await page.waitForTimeout(1000); console.log('✓ Selected new cashier (first option):', optText?.trim()); } } else { console.log('No available cashiers in dropdown'); } }); await test.step('Click Done to confirm reassignment', async () => { // Click the Done button to confirm const doneBtn = page.locator('button:has-text("Done")'); const vis = await doneBtn.isVisible({ timeout: 5000 }).catch(() => false); if (vis) { await doneBtn.click(); await page.waitForTimeout(3000); console.log('✓ Done clicked - cashier reassigned'); } else { console.log('⚠ Done button not found'); } }); await test.step('Verify new cashier assigned in table', async () => { // Close details panel const closeBtn = page.locator('button:has-text("Close"), button.ant-modal-close, [aria-label="Close"]').first(); const closeVis = await closeBtn.isVisible({ timeout: 3000 }).catch(() => false); if (closeVis) { await closeBtn.click(); await page.waitForTimeout(2000); } else { await page.keyboard.press('Escape'); await page.waitForTimeout(2000); } // Check the table row — the cashier column should now show the new cashier. // Poll instead of a single fixed-wait read: under a full suite run the // backend/table refresh can lag more than in isolation, which caused an // intermittent false-negative here. const headers = await page.locator('table thead th').allTextContents(); const cashierColIdx = headers.findIndex(h => h.toLowerCase().includes('cashier') && !h.toLowerCase().includes('id') && !h.toLowerCase().includes('username')); const row = page.locator('table tbody tr:not([aria-hidden="true"])').nth(targetTerminalRow); let cashierText = ''; for (let attempt = 0; attempt < 20; attempt++) { cashierText = (await row.locator('td').nth(cashierColIdx >= 0 ? cashierColIdx : 3).textContent({ timeout: 5000 }).catch(() => ''))?.trim() || ''; if (cashierText && cashierText !== 'Unassigned' && cashierText !== '-' && cashierText !== originalCashier) break; await page.waitForTimeout(500); } console.log(`Table row ${targetTerminalRow} cashier after reassign: "${cashierText}"`); console.log(`Original cashier was: "${originalCashier}"`); const isAssigned = cashierText && cashierText !== 'Unassigned' && cashierText !== '-'; const isDifferent = cashierText !== originalCashier; console.log('Cashier is assigned:', isAssigned); console.log('Cashier is different from original:', isDifferent); expect(isAssigned).toBe(true); expect(isDifferent).toBe(true); }); console.log('✓ Remove + reassign different cashier completed'); }); // Test 7: Add Terminal test('should add a new terminal', async ({ page }) => { feature('Terminal Operations'); tag('functional'); severity('critical'); description('Add a new terminal with Terminal ID, Provider, Terminal Model and Mada Package'); console.log('Testing add terminal...'); // Generate a unique exactly-8-digit terminal ID (10000000–99999999) const newTerminalId = String(10000000 + (Date.now() % 90000000)).slice(0, 8); console.log('New Terminal ID to create:', newTerminalId); await test.step('Click Add terminal button to open form', async () => { // The first "Add terminal" button (anticon-plus) opens/shows the inline form const addTerminalBtn = page.locator('button:has(.anticon-plus):has-text("Add terminal"), button:has([aria-label="plus"]):has-text("Add terminal")').first(); const isVisible = await addTerminalBtn.isVisible({ timeout: 5000 }).catch(() => false); if (isVisible) { await addTerminalBtn.click(); await page.waitForTimeout(2000); console.log('✓ Add terminal form opened'); } else { // Fallback: try all buttons with "Add terminal" text and pick the one with anticon-plus (not anticon-plus-square) const allAddBtns = page.locator('button:has-text("Add terminal")'); const count = await allAddBtns.count(); console.log(`Found ${count} "Add terminal" buttons`); for (let i = 0; i < count; i++) { const btn = allAddBtns.nth(i); const hasPlusSquare = await btn.locator('.anticon-plus-square').isVisible().catch(() => false); if (!hasPlusSquare) { await btn.click(); await page.waitForTimeout(2000); console.log(`✓ Clicked "Add terminal" button ${i} (open form button)`); break; } } } }); await test.step('Fill Terminal ID', async () => { // Scope to the open modal: the page's top-level search box has the // placeholder "Search by Terminal id or cashier name", which also // matches a loose `placeholder*="Terminal Id" i` selector and, being // first in DOM order, silently absorbed the fill instead of the real // form field - leaving every submitted terminal with a blank ID. const modal = page.locator('.ant-modal-content, .ant-drawer-content, [role="dialog"]').first(); const terminalIdInput = modal.locator('input[placeholder="Terminal Id"]').first(); await terminalIdInput.waitFor({ state: 'visible', timeout: 5000 }); await terminalIdInput.fill(newTerminalId); const filledValue = await terminalIdInput.inputValue(); expect(filledValue, 'Terminal ID field should contain the value we filled').toBe(newTerminalId); console.log('✓ Terminal ID filled:', newTerminalId); }); await test.step('Select Terminal Provider', async () => { const providerSelect = page.locator('.ant-select').filter({ hasText: /Select terminal provider/i }).first(); const isVisible = await providerSelect.isVisible().catch(() => false); if (isVisible) { await providerSelect.click(); await page.waitForTimeout(1000); const firstOption = page.locator('.ant-select-dropdown:not(.ant-select-dropdown-hidden) .ant-select-item-option').first(); const optText = await firstOption.textContent(); await firstOption.click({ force: true }); await page.waitForTimeout(500); console.log('✓ Provider selected:', optText?.trim()); } else { console.log('Provider dropdown not found'); } }); await test.step('Select Terminal Model', async () => { const modelSelect = page.locator('.ant-select').filter({ hasText: /Select terminal model/i }).first(); const isVisible = await modelSelect.isVisible().catch(() => false); if (isVisible) { await modelSelect.click(); await page.waitForTimeout(1000); const firstOption = page.locator('.ant-select-dropdown:not(.ant-select-dropdown-hidden) .ant-select-item-option').first(); const optText = await firstOption.textContent(); await firstOption.click({ force: true }); await page.waitForTimeout(500); console.log('✓ Terminal model selected:', optText?.trim()); } else { console.log('Terminal model dropdown not found'); } }); await test.step('Select Mada Package', async () => { const packageSelect = page.locator('.ant-select').filter({ hasText: /Select mada package/i }).first(); const isVisible = await packageSelect.isVisible().catch(() => false); if (isVisible) { await packageSelect.click(); await page.waitForTimeout(1000); const firstOption = page.locator('.ant-select-dropdown:not(.ant-select-dropdown-hidden) .ant-select-item-option').first(); const optText = await firstOption.textContent(); await firstOption.click({ force: true }); await page.waitForTimeout(500); console.log('✓ Mada package selected:', optText?.trim()); } else { console.log('Mada package dropdown not found'); } }); await test.step('Click submit Add terminal button (anticon-plus-square)', async () => { // The second "Add terminal" button (anticon-plus-square) submits the form const submitBtn = page.locator('button:has(.anticon-plus-square):has-text("Add terminal"), button:has([aria-label="plus-square"]):has-text("Add terminal")').first(); const isVisible = await submitBtn.isVisible({ timeout: 3000 }).catch(() => false); if (isVisible) { await submitBtn.click(); await page.waitForTimeout(3000); console.log('✓ Form submitted via Add terminal (plus-square) button'); } else { // Fallback: find the button with anticon-plus-square among all "Add terminal" buttons const allAddBtns = page.locator('button:has-text("Add terminal")'); const count = await allAddBtns.count(); for (let i = 0; i < count; i++) { const btn = allAddBtns.nth(i); const hasPlusSquare = await btn.locator('.anticon-plus-square').isVisible().catch(() => false); if (hasPlusSquare) { await btn.click(); await page.waitForTimeout(3000); console.log(`✓ Submitted via "Add terminal" button ${i} (plus-square)`); break; } } } }); await test.step('Verify new terminal appears in table', async () => { // Dismiss any modals await page.keyboard.press('Escape').catch(() => {}); await page.waitForTimeout(1000); // The list has 200+ pages and a new terminal isn't guaranteed to sort // onto page 1, so search for it directly instead of paging blindly. const searchInput = page.locator('input[placeholder*="Search by Terminal id" i]').first(); await expect(searchInput, 'Terminal search input should be visible').toBeVisible({ timeout: 10000 }); await searchInput.fill(newTerminalId); await page.waitForTimeout(2000); const terminalRow = page.locator('table tbody tr').filter({ hasText: newTerminalId }).first(); await expect(terminalRow, `New terminal ${newTerminalId} should appear in search results`).toBeVisible({ timeout: 10000 }); console.log(`✓ New terminal "${newTerminalId}" found via search`); }); console.log('Add terminal test completed'); }); });