import { test, expect } from '@playwright/test'; // Characterization spec for the Transaction Rule wizard (edit step + test/preview step). // Captures CURRENT (pre-migration) behavior so the migration onto the session-backed // wizard engine can be proven behavior-preserving. Reset the dataset before each test. test.beforeEach(async ({ request }) => { await request.post('/test-reset'); }); async function getTestInfo(page: any) { return (await page.request.get('/test-info')).json(); } async function navigateToRules(page: any) { // The rule fixtures live under client TEST2 (to stay out of the single-client TEST // transaction grid the other specs use), so view as an admin who sees all clients. await page.request.get('/test-set-client-mode?mode=multi-client'); await page.setExtraHTTPHeaders({ 'x-clients': '"all"' }); await page.goto('/admin/transaction-rule'); await page.waitForSelector('#entity-table'); } async function openNewDialog(page: any) { await page.locator('button:has-text("New Transaction Rule")').first().click(); await page.waitForSelector('#wizard-form'); } async function openEditDialog(page: any) { // the edit pencil on the seeded rule's row (hx-get ...//edit) await page.locator('#entity-table tbody tr').first() .locator('[hx-get*="/edit"]').first().click(); await page.waitForSelector('#wizard-form'); } // Add a valid account-coding row (Solr typeahead unavailable in tests, so inject the // account id into the row's hidden input), location Shared, percentage 100. async function addAccount(page: any, accountId: string) { await page.locator('#wizard-form a:has-text("New account")').first().click(); await page.waitForTimeout(400); const hidden = page.locator('#wizard-form input[type="hidden"][name*="[transaction-rule-account/account]"]').first(); await hidden.evaluate((el: HTMLInputElement, v: string) => { const n = document.createElement('input'); n.type = 'hidden'; n.name = el.name; n.value = v; el.parentNode!.replaceChild(n, el); }, accountId); await page.waitForTimeout(200); const loc = page.locator('#wizard-form select[name*="[transaction-rule-account/location]"]').first(); if (await loc.count() > 0) await loc.selectOption('Shared').catch(() => {}); const pct = page.locator('#wizard-form input[name*="[transaction-rule-account/percentage]"]').first(); await pct.fill('100'); await pct.dispatchEvent('change'); await page.waitForTimeout(200); } async function fillDescription(page: any, desc: string) { await page.locator('#wizard-form input[name*="[transaction-rule/description]"]').first().fill(desc); } // Approval status is required to advance/save; the radio-card's first option is "Approved". async function selectApproved(page: any) { const radio = page.locator('#wizard-form input[type="radio"][name*="transaction-approval-status"]').first(); await radio.check({ force: true }).catch(async () => { await page.locator('#wizard-form label:has-text("Approved")').first().click(); }); await page.waitForTimeout(100); } async function clickTest(page: any) { // the footer "Test" button navigates edit -> test await page.locator('#wizard-form button:has-text("Test"), #wizard-form a:has-text("Test")').first().click(); await page.waitForTimeout(800); } test.describe.configure({ mode: 'serial' }); test.describe('Transaction Rule wizard (characterization)', () => { test('New dialog opens the edit step with rule form + account grid', async ({ page }) => { await navigateToRules(page); await openNewDialog(page); const modal = page.locator('#wizard-form'); await expect(modal).toContainText('Description'); await expect(modal).toContainText('Outcomes'); await expect(modal).toContainText('New account'); await expect(modal).toContainText('Approval status'); // the step indicator + the Test (advance) control await expect(modal.locator('button:has-text("Test"), a:has-text("Test")').first()).toBeVisible(); }); test('Edit dialog pre-populates the seeded rule', async ({ page }) => { await navigateToRules(page); await openEditDialog(page); const desc = page.locator('#wizard-form input[name*="[transaction-rule/description]"]').first(); await expect(desc).toHaveValue('ZZRULEMATCH'); }); test('advancing to the test step renders the matching-transactions preview', async ({ page }) => { const info = await getTestInfo(page); await navigateToRules(page); await openNewDialog(page); await fillDescription(page, 'ZZRULEMATCH'); await addAccount(page, info.accounts['test-account'].toString()); await selectApproved(page); await clickTest(page); // the wizard advances to the test/preview step (the test-table query + render is // reused unchanged by the migration; the seed has no recent match, so the count is 0) const modal = page.locator('#wizard-form'); await expect(modal).toContainText('Matching transactions'); }); test('Saving from the test step creates the rule and closes the modal', async ({ page }) => { const info = await getTestInfo(page); await navigateToRules(page); const before = await page.locator('#entity-table tbody tr').count(); await openNewDialog(page); await fillDescription(page, 'ZZRULEMATCH'); await addAccount(page, info.accounts['test-account'].toString()); await selectApproved(page); await clickTest(page); // Save from the test step await page.locator('#wizard-form button:has-text("Save"), #wizard-form button[type="submit"]').first().click(); await page.waitForTimeout(1000); // modal closed + a new rule row added await expect(page.locator('#wizard-form')).toBeHidden(); expect(await page.locator('#entity-table tbody tr').count()).toBe(before + 1); }); });