From e7ae1a4a8b370ffc94336301857d313ad815632b Mon Sep 17 00:00:00 2001 From: Quinten Steenhuis Date: Thu, 8 Oct 2026 09:15:24 -0400 Subject: [PATCH 1/2] Bring the live-flow end-to-end specs up to date The optional-services, filing-matrix and classification specs had drifted from the app and failed before reaching their checks: - Upload now passes through Preview, and Confirm case sends a filer back while the first file is still being read. continueFromUpload waits for the analysis and confirms the prepared PDFs. - State on Your information is a select. fillRequiredInputs (now shared) selects an option for a select field. - Fees asks how the filer will pay before it lists accounts, and only wires up that choice once the accounts have loaded. choosePayByAccount waits for them, then chooses to pay by account. - Review's total and breakdown are #fee-quote-amount and #fee-quote-breakdown. - A fee waiver copy added on Fees must be confirmed ("This copy looks right") before Review is offered. - Lake County now names the sentinel's filing type "Petition" in its live code list. Checked against a local server on main with the Tyler test account: the optional-services and classification specs pass, and the Adams adoption scenario filed with a fee waiver (envelope 324890). Co-Authored-By: Claude Opus 5.5 --- .../document-classification-sentinel.spec.js | 14 ++-- .../tests/reorganized-filing-matrix.spec.js | 37 +++------- .../tests/test-optional-services-e2e.spec.js | 40 +++------- efile_app/tests/test-utils.js | 73 +++++++++++++++++++ 4 files changed, 100 insertions(+), 64 deletions(-) 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..d08ecbaf 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,7 +212,8 @@ 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 + // Pay from the active BankAccount payment option + await choosePayByAccount(page); const bankAccount = page.locator('input[name="paymentMethod"][data-type="BankAccount"], input[name="paymentMethod"][value="d44fd7ed-6683-48e1-a670-f7964e5bba4d"]'); 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, From df89f6006d13cecaf35bbb7709d892dc29fdce04 Mon Sep 17 00:00:00 2001 From: Quinten Steenhuis Date: Thu, 8 Oct 2026 10:37:27 -0400 Subject: [PATCH 2/2] Find the test account's e-check by type, not a deleted account ID The Tyler test account's old e-check account was declined at submission (Tyler code 121) and has been replaced; the spec now relies only on the account type. Co-Authored-By: Claude Opus 5.5 --- efile_app/tests/test-optional-services-e2e.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/efile_app/tests/test-optional-services-e2e.spec.js b/efile_app/tests/test-optional-services-e2e.spec.js index d08ecbaf..4dcd0a8b 100644 --- a/efile_app/tests/test-optional-services-e2e.spec.js +++ b/efile_app/tests/test-optional-services-e2e.spec.js @@ -214,7 +214,7 @@ test('adding and removing optional services dynamically updates calculated fees // Pay from the active BankAccount payment option await choosePayByAccount(page); - const bankAccount = page.locator('input[name="paymentMethod"][data-type="BankAccount"], input[name="paymentMethod"][value="d44fd7ed-6683-48e1-a670-f7964e5bba4d"]'); + const bankAccount = page.locator('input[name="paymentMethod"][data-type="BankAccount"]'); await expect(bankAccount.first()).toBeAttached({ timeout: 120000 });