22ad077dab
New tab under the Giving hub, alongside Zakat/Sadaqah/Khairat. Two parts: Donations: amount presets + custom amount, four frequencies (One-Time, Monthly, Quarterly, Yearly), each mapped to its own live Polar.sh checkout link (merchant of record — this app never touches payment details, same 'log intent, don't move money' principle used everywhere else). Design/copy modeled on the sibling moslem03.falahos.my app's existing Support tab, per direct reference. No donation product existed on the Polar account yet, so created one via the Polar API with the user's explicit go-ahead: 'Support Nur Falah' as 4 pay-what-you-want products (one-time + monthly/quarterly/ yearly recurring, quarterly via recurring_interval=month with count=3 since Polar has no native quarterly interval), plus a hosted checkout link for each. Verified all four resolve to genuinely distinct, live Stripe-backed checkout sessions before shipping — moslem03's own checkout.polar.sh URL pattern turned out to be stale/non-resolving; the current correct domain is buy.polar.sh via the /v1/checkout-links API, discovered from Polar's live OpenAPI spec rather than guessed. Partnership section: co-branding and white-label pitch with the VP Sales contact info given directly — info@falahos.my and WhatsApp +60132250691 — reachable via a 'View Partnership Opportunities' link from the donation card, matching moslem03's UX pattern of surfacing partnership discovery from the support flow. Covered by e2e-support.cjs (14/14) — including opening all four checkout links live and confirming each is a genuinely distinct, resolving Stripe checkout session, not a dead or placeholder link. Full regression: all suites pass (one confirmed transient flake on rerun, unrelated to this change).
87 lines
4.6 KiB
JavaScript
87 lines
4.6 KiB
JavaScript
// Verifies the Support tab: amount/frequency selection, and that "Support
|
|
// Now" actually opens a real, live Polar.sh checkout page (not a dead or
|
|
// placeholder link) — one check per frequency, since each maps to a
|
|
// different Polar product.
|
|
const { chromium } = require('playwright');
|
|
const { signInFreshFamily, gotoTab } = require('./e2e-auth-helper.cjs');
|
|
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 signInFreshFamily(page, BASE, 'e2e-support');
|
|
await gotoTab(page, 'Support');
|
|
await page.waitForTimeout(600);
|
|
|
|
record('Support: intro copy renders', await page.locator('h3', { hasText: 'Support Nur Falah' }).isVisible().catch(() => false));
|
|
|
|
// Amount presets
|
|
await page.locator('.pill', { hasText: '$25' }).click();
|
|
await page.waitForTimeout(200);
|
|
const presetActive = await page.locator('.pill.active', { hasText: '$25' }).isVisible().catch(() => false);
|
|
record('Support: selecting a preset amount highlights it', presetActive);
|
|
|
|
// Custom amount overrides preset
|
|
await page.locator('.custom-input').fill('17');
|
|
await page.waitForTimeout(200);
|
|
const customActive = await page.locator('.pill-custom.active').isVisible().catch(() => false);
|
|
record('Support: entering a custom amount switches selection to custom', customActive);
|
|
const btnShowsCustom = await page.locator('.donate-btn', { hasText: '$17' }).isVisible().catch(() => false);
|
|
record('Support: button reflects the custom amount', btnShowsCustom);
|
|
await page.locator('.custom-input').fill('');
|
|
|
|
// Each frequency should open a distinct, real, live Polar checkout page
|
|
const frequencies = [
|
|
{ label: 'One-Time', btnText: 'Support Now' },
|
|
{ label: 'Monthly 🌙', btnText: 'Subscribe Monthly' },
|
|
{ label: 'Quarterly', btnText: 'Subscribe Quarterly' },
|
|
{ label: 'Yearly', btnText: 'Subscribe Yearly' }
|
|
];
|
|
const openedUrls = new Set();
|
|
for (const freq of frequencies) {
|
|
await page.locator('.frequency-toggle button', { hasText: freq.label }).click();
|
|
await page.waitForTimeout(200);
|
|
await page.locator('.pill', { hasText: '$10' }).click();
|
|
await page.waitForTimeout(200);
|
|
|
|
const [popup] = await Promise.all([
|
|
page.waitForEvent('popup'),
|
|
page.locator('.donate-btn').click()
|
|
]);
|
|
await popup.waitForLoadState('domcontentloaded').catch(() => {});
|
|
await page.waitForTimeout(1500);
|
|
const finalUrl = popup.url();
|
|
openedUrls.add(finalUrl);
|
|
const reachedPolar = /polar\.sh|stripe\.com/i.test(finalUrl);
|
|
record(`Support (${freq.label}): opens a real, live checkout page`, reachedPolar, finalUrl);
|
|
await popup.close();
|
|
}
|
|
record('Support: each frequency opens a genuinely distinct checkout session', openedUrls.size === frequencies.length, `${openedUrls.size} distinct URLs`);
|
|
|
|
// Partnership section
|
|
await page.locator('a', { hasText: 'View Partnership Opportunities' }).click();
|
|
await page.waitForTimeout(500);
|
|
record('Support: partnership section is reachable', await page.locator('h3', { hasText: 'Co-Branding' }).isVisible().catch(() => false));
|
|
const emailLinkVisible = await page.locator('a[href^="mailto:info@falahos.my"]').isVisible().catch(() => false);
|
|
record('Support: shows the correct VP sales email contact', emailLinkVisible);
|
|
const whatsappLinkVisible = await page.locator('a[href="https://wa.me/60132250691"]').isVisible().catch(() => false);
|
|
record('Support: shows the correct WhatsApp contact', whatsappLinkVisible);
|
|
record('Support: mentions white-labeling', (await page.locator('.partner-tiers').innerText()).toLowerCase().includes('white-label'));
|
|
|
|
record('No uncaught JS console errors during full session', consoleErrors.length === 0, consoleErrors.slice(0, 5).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); });
|