diff --git a/efile_app/tests/document-classification-sentinel.spec.js b/efile_app/tests/document-classification-sentinel.spec.js index 4b3611a7..a86e2e56 100644 --- a/efile_app/tests/document-classification-sentinel.spec.js +++ b/efile_app/tests/document-classification-sentinel.spec.js @@ -5,7 +5,8 @@ const { const path = require('path'); const { getTestConfig, - loginViaLoginPage + loginViaLoginPage, + continueFromUpload } = require('./test-utils'); const SENTINEL_PDF = path.resolve(__dirname, '../../benchmarking/synthetic/filled_pdfs/flattened/IL-02.pdf'); @@ -32,12 +33,7 @@ test('real uploaded PDF is extracted and classified against live Tyler choices', await expect(page.locator('.document-row')).toHaveCount(1, { timeout: 120000 }); - await Promise.all([ - page.waitForURL(/\/extraction-review\//, { - timeout: 300000 - }), - page.locator('#continue-to-analysis').click(), - ]); + await continueFromUpload(page); await expect(page.getByRole('heading', { name: /Check what we read from your document/i @@ -57,7 +53,9 @@ test('real uploaded PDF is extracted and classified against live Tyler choices', await expect(page.locator('#case_type_name')).toHaveValue('Dissolution (with children)', { timeout: 120000 }); - await expect(page.locator('#filing_type_name')).toHaveValue('Petition Dissolution of Marriage/Civil Union', { + // Lake County names this filing type "Petition" (checked against the live + // code list on 2026-10-08); it was "Petition Dissolution of Marriage/Civil Union". + await expect(page.locator('#filing_type_name')).toHaveValue('Petition', { timeout: 120000 }); await expect(page.locator('input[type="radio"][name="existing_case"][value="new"]')).toBeChecked(); diff --git a/efile_app/tests/reorganized-filing-matrix.spec.js b/efile_app/tests/reorganized-filing-matrix.spec.js index 978a9f8d..06e58eb0 100644 --- a/efile_app/tests/reorganized-filing-matrix.spec.js +++ b/efile_app/tests/reorganized-filing-matrix.spec.js @@ -6,6 +6,9 @@ const path = require('path'); const { getTestConfig, loginViaLoginPage, + continueFromUpload, + fillRequiredInputs, + choosePayByAccount, continueFromExtractionReview, continueFromDocumentChecklist, chooseFilingPath, @@ -155,21 +158,6 @@ async function selectAfterLoad(page, selector, value) { await page.locator(selector).selectOption(value); } -async function fillRequiredInputs(page, values) { - for (const [name, value] of Object.entries(values)) { - const field = page.locator(`[name="${name}"]:visible`).first(); - if (await field.count()) await field.fill(value); - } - const required = page.locator('input[required]:visible'); - for (let index = 0; index < await required.count(); index += 1) { - const input = required.nth(index); - if (await input.inputValue()) continue; - const type = await input.getAttribute('type'); - if (type === 'radio' || type === 'checkbox') continue; - await input.fill(type === 'email' ? 'efile-test@example.com' : type === 'number' ? '1' : 'Test value'); - } -} - async function completeParty(page, ordinal) { const roleRadios = page.locator('input[name="party_type"]'); await expect(roleRadios).not.toHaveCount(0, { @@ -319,11 +307,12 @@ async function finishFiling(page, scenario, ordinal) { await expect(page.locator('#waiver-upload-confirmation')).toBeVisible({ timeout: 120000 }); + // A copy added on Fees is checked here before Review is offered. + await page.getByRole('button', { + name: 'This copy looks right' + }).click(); } else { - await page.locator('input[name="paymentIntent"][value="pay"]').check(); - await expect(page.locator('input[name="paymentMethod"]')).not.toHaveCount(0, { - timeout: 120000 - }); + await choosePayByAccount(page); } const quoteOutcome = await Promise.race([ page.locator('#errorMessage:not([hidden])').waitFor({ @@ -389,10 +378,7 @@ async function runNewCase(page, scenario, ordinal) { await expect(page.locator('.document-row')).toHaveCount(1, { timeout: 180000 }); - await Promise.all([ - page.waitForURL(/\/extraction-review\//), - page.locator('#continue-to-analysis').click(), - ]); + await continueFromUpload(page); console.log(`${scenario.label}: selecting case codes`); await selectGuidedCourt(page, 'illinois', scenario.court); @@ -415,10 +401,7 @@ async function runExistingCase(page, scenario, ordinal) { await expect(page.locator('.document-row')).toHaveCount(1, { timeout: 180000 }); - await Promise.all([ - page.waitForURL(/\/extraction-review\//), - page.locator('#continue-to-analysis').click(), - ]); + await continueFromUpload(page); await selectGuidedCourt(page, 'illinois', scenario.court); await chooseFilingPath(page, 'existing'); diff --git a/efile_app/tests/test-optional-services-e2e.spec.js b/efile_app/tests/test-optional-services-e2e.spec.js index 727003c4..4dcd0a8b 100644 --- a/efile_app/tests/test-optional-services-e2e.spec.js +++ b/efile_app/tests/test-optional-services-e2e.spec.js @@ -6,6 +6,9 @@ const path = require('path'); const { getTestConfig, loginViaLoginPage, + continueFromUpload, + fillRequiredInputs, + choosePayByAccount, continueFromExtractionReview, continueFromDocumentChecklist, chooseFilingPath, @@ -21,23 +24,6 @@ async function selectAfterLoad(page, selector, value) { await page.locator(selector).selectOption(value); } -async function fillRequiredInputs(page, values) { - for (const [name, value] of Object.entries(values)) { - const field = page.locator(`[name="${name}"]:visible`).first(); - if (await field.count()) await field.fill(value); - } - const required = page.locator('input[required]:visible'); - for (let index = 0; index < (await required.count()); index += 1) { - const input = required.nth(index); - if (await input.inputValue()) continue; - const type = await input.getAttribute('type'); - if (type === 'radio' || type === 'checkbox') continue; - await input.fill( - type === 'email' ? 'efile-test@example.com' : type === 'number' ? '1' : 'Test value' - ); - } -} - async function completeParty(page, ordinal) { const roleRadios = page.locator('input[name="party_type"]'); await expect(roleRadios).not.toHaveCount(0, { @@ -95,12 +81,7 @@ test('adding and removing optional services dynamically updates calculated fees await expect(page.locator('.document-row')).toHaveCount(1, { timeout: 120000 }); - await Promise.all([ - page.waitForURL(/\/extraction-review\//, { - timeout: 300000 - }), - page.locator('#continue-to-analysis').click(), - ]); + await continueFromUpload(page); console.log('Step 4: Selecting case codes (Adams County - Small Claims)...'); await selectGuidedCourt(page, 'illinois', 'adams'); @@ -231,8 +212,9 @@ test('adding and removing optional services dynamically updates calculated fees console.log('Step 9: Verifying fee calculation on Payment page WITH optional service...'); await expect(page).toHaveURL(/\/payment\//); - // Select the active BankAccount payment option - const bankAccount = page.locator('input[name="paymentMethod"][data-type="BankAccount"], input[name="paymentMethod"][value="d44fd7ed-6683-48e1-a670-f7964e5bba4d"]'); + // Pay from the active BankAccount payment option + await choosePayByAccount(page); + const bankAccount = page.locator('input[name="paymentMethod"][data-type="BankAccount"]'); await expect(bankAccount.first()).toBeAttached({ timeout: 120000 }); @@ -259,13 +241,13 @@ test('adding and removing optional services dynamically updates calculated fees page.locator('#submitButton').click(), ]); - await expect(page.locator('.review-fee-total')).toBeVisible({ + await expect(page.locator('#fee-quote-amount')).toContainText('262.25', { timeout: 120000 }); - const reviewTotalWithService = await page.locator('.review-fee-total').innerText(); + const reviewTotalWithService = await page.locator('#fee-quote-amount').innerText(); console.log('Review Total WITH Optional Service:\n' + reviewTotalWithService); expect(reviewTotalWithService).toContain('262.25'); - await expect(page.locator('.review-fee-breakdown')).toContainText('Optional Service Fee'); + await expect(page.locator('#fee-quote-breakdown')).toContainText('Optional Service Fee'); console.log('✓ Verified: Review page shows $262.25 total and Optional Service Fee breakdown item.'); // Step 11: Edit documents from Review page to UNCHECK the optional service @@ -299,10 +281,10 @@ test('adding and removing optional services dynamically updates calculated fees // offers Submit only once the new total is on screen. No trip to Payment. console.log('Step 12: Waiting for Review to recalculate the fee WITHOUT optional service...'); await expect(page.locator('#fee-quote')).not.toContainText('262.25'); - await expect(page.locator('.review-fee-total')).toContainText('256.25', { + await expect(page.locator('#fee-quote-amount')).toContainText('256.25', { timeout: 180000 }); - await expect(page.locator('.review-fee-breakdown')).not.toContainText('Optional Service Fee'); + await expect(page.locator('#fee-quote-breakdown')).not.toContainText('Optional Service Fee'); await expect(page.locator('#fee-quote')).toHaveAttribute('data-state', 'current'); await page.locator('#confirm-filing').check(); await expect(page.locator('#submitButton')).toBeEnabled(); diff --git a/efile_app/tests/test-utils.js b/efile_app/tests/test-utils.js index 3983f2c2..8086414e 100644 --- a/efile_app/tests/test-utils.js +++ b/efile_app/tests/test-utils.js @@ -98,6 +98,76 @@ async function loginViaLoginPage(page, config = getTestConfig()) { await waitForLogin(page); } +/** + * Leave Upload once the first file has been read, through Preview to Confirm + * case. Confirm case sends a filer back while analysis is still running, so + * wait for the analysis first, then confirm the prepared PDFs on Preview. + */ +async function continueFromUpload(page, timeout = 300000) { + await expect(page.locator('.status-pill--analyzing')).toHaveCount(0, { + timeout + }); + const next = page.locator('#continue-to-analysis'); + await expect(next).toHaveAttribute('href', /.+/, { + timeout + }); + await Promise.all([ + page.waitForURL(/\/(preview-documents|extraction-review)\//, { + timeout: 120000 + }), + next.click(), + ]); + if (/\/preview-documents\//.test(page.url())) { + await Promise.all([ + page.waitForURL(/\/extraction-review\//, { + timeout: 120000 + }), + page.getByRole('button', { + name: 'Continue', + exact: true + }).click(), + ]); + } +} + +/** + * Fill the named fields, then any other visible required text field. A field + * may be a select (State is), which takes an option value instead of text. + */ +async function fillRequiredInputs(page, values) { + for (const [name, value] of Object.entries(values)) { + const field = page.locator(`[name="${name}"]:visible`).first(); + if (!(await field.count())) continue; + if ((await field.evaluate((element) => element.tagName)) === 'SELECT') await field.selectOption(value); + else await field.fill(value); + } + await fillOtherRequiredInputs(page); +} + +async function fillOtherRequiredInputs(page) { + const required = page.locator('input[required]:visible'); + for (let index = 0; index < (await required.count()); index += 1) { + const input = required.nth(index); + const type = await input.getAttribute('type'); + if (type === 'radio' || type === 'checkbox' || (await input.inputValue())) continue; + await input.fill(type === 'email' ? 'efile-test@example.com' : type === 'number' ? '1' : 'Test value'); + } +} + +/** + * Pay from a saved account on Fees. The intent radios are only wired up once + * the page has loaded the filer's accounts, so wait for that before choosing. + */ +async function choosePayByAccount(page) { + await page.waitForFunction('typeof PaymentPage !== "undefined" && Array.isArray(PaymentPage.accounts)', null, { + timeout: 120000 + }); + await page.locator('input[name="paymentIntent"][value="pay"]').check(); + await expect(page.locator('input[name="paymentMethod"]')).not.toHaveCount(0, { + timeout: 120000 + }); +} + /** * Submit the extraction review after answering its conditional questions. * The filer-side question only appears for case types whose checklist differs @@ -234,6 +304,9 @@ module.exports = { loginUser, loginViaLogout, loginViaLoginPage, + continueFromUpload, + fillRequiredInputs, + choosePayByAccount, continueFromExtractionReview, continueFromDocumentChecklist, chooseFilingPath,