From 10289e2d928dbac03c209948134dcc79c0e37e39 Mon Sep 17 00:00:00 2001 From: wmj Date: Thu, 13 Aug 2026 18:18:40 +0800 Subject: [PATCH] Verify property/land coverage across all three applicable fast-path channels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No code changes needed — land/property was already correctly wired through Hibah (offer/acceptance/possession now), Waqf (dedicated corpus via Family Waqf Designator), and Trust (via Nomination Registry, added earlier this session). This adds explicit isolated verification since prior tests only exercised one channel per property asset at a time. e2e-property.cjs: three separate property assets, each covered through a different channel, confirming Coverage Dashboard correctly attributes each (Hibah/Waqf/Trust) and reaches 100%, and Death Trigger generates a distinct execution packet for all three. 9/9 passing. Re-verified e2e-uat.cjs (32/32), e2e-fastpath.cjs (16/16), e2e-trust.cjs (12/12), e2e-business.cjs (10/10), e2e-digital-vehicle.cjs (10/10), e2e-info.cjs (31/31) — 132/132 total, no regressions. --- e2e-property.cjs | 113 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 e2e-property.cjs diff --git a/e2e-property.cjs b/e2e-property.cjs new file mode 100644 index 0000000..f724040 --- /dev/null +++ b/e2e-property.cjs @@ -0,0 +1,113 @@ +// Verifies property/land assets are covered through ALL THREE applicable fast-path +// channels: Hibah, Waqf (Family Waqf Designator), and Trust (Nomination Registry). +const { chromium } = require('playwright'); +const BASE = 'https://moslem04.falahos.my/'; +const results = []; +const consoleErrors = []; +function record(name, pass, detail = '') { results.push({ name, pass, detail }); console.log(`${pass ? 'PASS' : 'FAIL'} ${name}${detail ? ' — ' + detail : ''}`); } + +async function main() { + const browser = await chromium.launch(); + const page = await browser.newPage({ viewport: { width: 390, height: 844 } }); + page.on('console', m => { if (m.type() === 'error') consoleErrors.push(m.text()); }); + page.on('pageerror', e => consoleErrors.push(e.message)); + + await page.goto(BASE, { waitUntil: 'networkidle' }); + const clickTab = async label => { await page.locator('nav button.tab', { hasText: label }).click(); await page.waitForTimeout(200); }; + const selectByText = async (locator, text) => { + const val = await locator.evaluate((el, t) => Array.from(el.options).find(o => o.textContent.includes(t))?.value, text); + await locator.selectOption(val); + }; + + // Three separate Property-type assets, one per channel + await clickTab('Assets'); + for (const [desc, val] of [['House A (via Hibah)', '350000'], ['House B (via Waqf)', '600000'], ['House C (via Trust)', '450000']]) { + await page.locator('.field:has-text("Description") input').fill(desc); + await page.locator('.field:has-text("Estimated value") input').fill(val); + // Type defaults to Property — leave as-is + await page.locator('button.btn-primary', { hasText: 'Add asset' }).click(); + await page.waitForTimeout(200); + } + + await clickTab('Coverage'); + const totalRows = await page.locator('.asset-row', { hasText: 'House' }).count(); + record('Coverage: all 3 property assets show as exposed initially', totalRows === 3, `${totalRows} rows`); + const pctInitial = await page.locator('.big-percent').textContent(); + record('Coverage: 0% before covering any property asset', pctInitial.trim() === '0%', pctInitial); + + // Channel 1: Hibah + await clickTab('Hibah'); + await page.locator('.field:has-text("Recipient") input').fill('My Wife'); + await page.locator('.field:has-text("Relation to you") input').fill('wife'); + await page.locator('.field:has-text("Asset / gift description") input').fill('House gift'); + await selectByText(page.locator('select').first(), 'House A'); + await page.locator('button.btn-primary', { hasText: 'Log this hibah' }).click(); + await page.waitForTimeout(200); + await page.locator('.guard-buttons button.btn-secondary', { hasText: 'No' }).click(); + await page.waitForTimeout(200); + await page.locator('button.btn-primary', { hasText: 'Confirm and save' }).click(); + await page.waitForTimeout(300); + + await clickTab('Coverage'); + const houseARow = await page.locator('.asset-row', { hasText: 'House A' }).textContent(); + record('Property via Hibah: House A shows covered', !houseARow.includes('Not covered') && houseARow.includes('Hibah'), houseARow.trim().slice(0, 150)); + + // Channel 2: Waqf + await clickTab('Family Waqf'); + await selectByText(page.locator('select').first(), 'House B'); + await page.waitForTimeout(200); + const corpusVal = await page.locator('select').first().inputValue(); + record('Family Waqf: House B selectable as corpus asset', corpusVal !== ''); + await page.locator('.field:has-text("Mutawalli (trustee)") input').fill('Ahmad bin Ismail'); + await page.waitForTimeout(300); // allow $effect to persist waqfCorpusAssetId + + await clickTab('Coverage'); + const houseBRow = await page.locator('.asset-row', { hasText: 'House B' }).textContent(); + record('Property via Waqf: House B shows covered', !houseBRow.includes('Not covered') && houseBRow.includes('Waqf'), houseBRow.trim().slice(0, 150)); + + // Channel 3: Trust (Nomination Registry) + await clickTab('Nominate'); + await selectByText(page.locator('select').first(), 'House C'); + await page.waitForTimeout(200); + await page.locator('.form-card select').nth(1).selectOption('trust'); + await page.waitForTimeout(200); + const trustInputs = page.locator('.trust-fields .field input'); + await trustInputs.nth(0).fill('Trustee Co'); + await trustInputs.nth(1).fill('Successor Trustee Co'); + await trustInputs.nth(2).fill('Equal split among children'); + await page.locator('button.btn-primary', { hasText: 'Add trust setup' }).click(); + await page.waitForTimeout(300); + + await clickTab('Coverage'); + const houseCRow = await page.locator('.asset-row', { hasText: 'House C' }).textContent(); + record('Property via Trust: House C shows covered', !houseCRow.includes('Not covered') && houseCRow.includes('Trust'), houseCRow.trim().slice(0, 150)); + + const finalPct = await page.locator('.big-percent').textContent(); + record('Coverage: 100% after all 3 property assets covered via 3 different channels', finalPct.trim() === '100%', finalPct); + + // Death Trigger should produce packets for all three + await clickTab('Trigger'); + const attestorInputs = page.locator('.attestor-row input'); + await attestorInputs.nth(0).fill('Executor A'); + await page.locator('.attestor-row .confirm-btn').nth(0).click(); + await attestorInputs.nth(1).fill('Witness B'); + await page.locator('.attestor-row .confirm-btn').nth(1).click(); + await page.locator('.field:has-text("Date of death") input').fill('2026-08-13'); + await page.locator('.field:has-text("Death certificate reference") input').fill('DC-PROP-001'); + await page.waitForTimeout(200); + await page.locator('button.btn-danger-solid').click(); + await page.waitForTimeout(300); + + const packetCount = await page.locator('.packet-row', { hasText: 'House' }).count(); + record('Death Trigger: execution packets generated for all 3 property assets', packetCount === 3, `${packetCount} packets`); + + record('No uncaught JS console errors', consoleErrors.length === 0, consoleErrors.join(' || ')); + + await browser.close(); + const passCount = results.filter(r => r.pass).length; + const failCount = results.length - passCount; + console.log(`\n${passCount} passed, ${failCount} failed, ${results.length} total`); + if (failCount > 0) results.filter(r => !r.pass).forEach(r => console.log(` - ${r.name}: ${r.detail}`)); + process.exit(failCount > 0 ? 1 : 0); +} +main().catch(e => { console.error('SCRIPT ERROR:', e); process.exit(2); });