import { test, expect } from '@playwright/test' import { loginAsOperator } from './helpers/auth.js' import { listFailedPairings, ensureAtLeastOneFailedPairing } from './helpers/backend-api.js' /** * Failed Pairings page e2e tests. * * NOTE: There is currently no public test-seed endpoint for failed_pairings. * Production rows are inserted by the backend pairing service when a real * pairing fails. These tests therefore rely on the database already containing * at least one row (any cause). If the table is empty the tests are skipped * with a clear TODO so the user can either: * - run the real pairing flow once to generate real fixtures, or * - add a backend test-only seed route (see helpers/backend-api.js). */ test.describe('Failed Pairings page', () => { test.beforeEach(async ({ page }) => { await loginAsOperator(page) }) test('renders the table when at least one row exists', async ({ page }) => { let seeded try { seeded = await ensureAtLeastOneFailedPairing() } catch (e) { test.skip(true, `No failed-pairings rows in DB. ${e.message}`) return } await page.goto('/failed-pairings') await expect(page.getByRole('heading', { name: 'Failed Pairings', level: 1 })).toBeVisible() // The table headers should always render once data has loaded. await expect(page.getByRole('columnheader', { name: 'Created' })).toBeVisible() await expect(page.getByRole('columnheader', { name: 'Cause' })).toBeVisible() await expect(page.getByRole('columnheader', { name: 'Operator Action' })).toBeVisible() // At least one data row (skip the header row). const dataRows = page.locator('table tbody tr') const count = await dataRows.count() expect(count).toBeGreaterThanOrEqual(1) // The "empty" placeholder row should NOT be visible when we have data. await expect(page.getByText('Belum ada data failed pairings.')).not.toBeVisible() // Sanity check — total label matches what the backend reports. await expect(page.getByText(`(${seeded.total} total)`)).toBeVisible() }) test('cause-tag filter narrows the visible rows', async ({ page }) => { // We need rows of at least 2 distinct cause tags to make a meaningful // assertion about filtering. If the DB has fewer, skip with a clear // message rather than producing a fake-positive. const all = await listFailedPairings({ limit: 200 }) const distinctCauses = [...new Set((all?.rows ?? []).map((r) => r.cause_tag))] if (distinctCauses.length < 2) { test.skip( true, `Need >=2 distinct cause_tags in failed_pairings to test filter. Found: ${distinctCauses.join(', ') || '(none)'}.`, ) return } const targetCause = distinctCauses[0] const expectedFiltered = await listFailedPairings({ causeTags: [targetCause], limit: 200 }) await page.goto('/failed-pairings') await expect(page.getByRole('heading', { name: 'Failed Pairings', level: 1 })).toBeVisible() // Capture pre-filter total — must be >= filtered total or assertion is bogus. const totalBefore = all.total expect(totalBefore).toBeGreaterThan(expectedFiltered.total) // Tick the cause checkbox. The filter section labels the checkbox with the // human label from PairingFailureCauseLabel — find it by its associated text. // Map the cause_tag to its label by re-importing the constants. const { PairingFailureCauseLabel } = await import( '../../src/core/constants.js' ) const targetLabel = PairingFailureCauseLabel[targetCause] await page.getByRole('checkbox', { name: targetLabel }).check() // Expect the new total to match the API-computed expectation. await expect(page.getByText(`(${expectedFiltered.total} total)`)).toBeVisible() // And every visible row should reflect the chosen cause tag. const causeCells = page.locator('table tbody tr td:nth-child(4)') const visibleCount = await causeCells.count() expect(visibleCount).toBeGreaterThan(0) for (let i = 0; i < visibleCount; i++) { await expect(causeCells.nth(i)).toHaveText(targetLabel) } }) })