// Settings → Wallet lists Spending and Savings; each row opens the page the // balance card's gear opens. "I simply cannot find this option" (Nils, // 2026-11-12) — the Spending gear was the only way in. // Run: bun tools/balance-settings-row-test.js (NO_BUILD=1 to reuse dist/) import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js'; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); let ok = true; const check = (n, c, d = '') => { console.log(` ${c ? '✓' : '✗'} ${n}${d ? ' — ' + d : ''}`); if (!c) ok = false; }; if (!process.env.NO_BUILD && Bun.spawnSync(['bun', 'build.js']).exitCode !== 0) throw new Error('build failed'); const server = Bun.serve({ port: 0, fetch: async (req) => { const path = new URL(req.url).pathname; const f = Bun.file('dist' + path); return path.includes('.') && await f.exists() ? new Response(f) : new Response(Bun.file('dist/index.html')); } }); const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome', headless: 'new', args: ['--no-sandbox'] }); const page = await browser.newPage(); const errs = []; // mainnet so Spending turns on (the Ark server's info is read); every other // outside request is refused, so nothing is claimed or published await page.setRequestInterception(true); page.on('request', (r) => { const u = new URL(r.url()); (u.origin === server.url.origin || u.hostname === 'ark.coinos.io') ? r.continue() : r.abort(); }); page.on('pageerror', (e) => errs.push(String(e))); const click = (x) => page.evaluate((s) => { const e = [...document.querySelectorAll('button')].find((n) => n.textContent.trim().toLowerCase().includes(s)); if (e) e.click(); return !!e; }, x); const waitText = async (x, ms = 20000) => { for (let i = 0; i < ms / 250; i++) { if ((await page.evaluate(() => document.body.innerText)).toLowerCase().includes(x)) return true; await sleep(250); } return false; }; const openWalletPage = async () => { await page.evaluate(() => [...document.querySelectorAll('button')].find((e) => e.getAttribute('aria-label') === 'Settings').click()); await sleep(500); await page.evaluate(() => [...document.querySelectorAll('.settings-tile')].find((e) => /^Wallet/.test(e.textContent.trim())).click()); await sleep(500); }; try { await page.setViewport({ width: 390, height: 844 }); await page.goto(server.url.href, { waitUntil: 'domcontentloaded' }); await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'mainnet')); await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400); await click('create a new wallet'); await sleep(600); await seedWords(page); await skipSeedCheck(page); if (await waitText('claim a username', 8000)) { await click('not now'); await sleep(800); } await waitText('receive'); await sleep(1500); check('savings-only wallet lists Savings alone', await (async () => { await openWalletPage(); const v = await page.$$eval('.balance-settings-row', (l) => l.map((e) => e.dataset.view)); await page.goBack(); await page.goBack(); await sleep(500); return v.join() === 'savings'; })()); await click('set up a spending account'); await sleep(800); if (await waitText('claim a username', 5000)) { await click('not now'); await sleep(800); } await waitText('receive'); await sleep(800); if (process.env.DEBUG) console.log((await page.evaluate(() => document.body.innerText)).slice(0, 600)); const gears = await page.$$eval('.balance-gear', (l) => l.length); await openWalletPage(); if (process.env.SHOT) { await page.evaluate(() => document.querySelector('.balance-settings-row').closest('.card').scrollIntoView()); await page.screenshot({ path: process.env.SHOT }); } const views = await page.$$eval('.balance-settings-row', (l) => l.map((e) => e.dataset.view)); check('Settings → Wallet lists the balances', views.length >= 1, views.join(',')); check('Spending is listed when the card shows it', views.includes('spending'), `gears on the wallet: ${gears}`); await page.evaluate(() => document.querySelector('.balance-settings-row[data-view="spending"]')?.click()); await sleep(600); const st = await page.evaluate(() => ({ screen: history.state.nav.screen, view: history.state.nav.settingsView, h3: [...document.querySelectorAll('h3')].map((e) => e.textContent) })); check('the Spending row opens the Spending settings', st.screen === 'accountSettings' && st.view === 'spending' && st.h3.includes('Spending'), JSON.stringify(st.h3.slice(0, 4))); await page.goBack(); await sleep(600); check('Back returns to Settings → Wallet', !!(await page.$('.balance-settings-row'))); await page.evaluate(() => document.querySelector('.balance-settings-row[data-view="savings"]')?.click()); await sleep(600); const sv = await page.evaluate(() => ({ screen: history.state.nav.screen, view: history.state.nav.settingsView })); check('the Savings row opens the Savings settings', sv.screen === 'accountSettings' && sv.view === 'savings'); check('no page errors', errs.length === 0, errs.join(' | ')); } finally { await browser.close(); server.stop(true); } console.log(ok ? 'PASS' : 'FAIL'); process.exit(ok ? 0 : 1);