diff options
| -rw-r--r-- | tools/ark/ui-test.js | 63 | ||||
| -rw-r--r-- | tools/comments-test.js | 23 |
2 files changed, 76 insertions, 10 deletions
diff --git a/tools/ark/ui-test.js b/tools/ark/ui-test.js index 4460cd3..e4e2e5a 100644 --- a/tools/ark/ui-test.js +++ b/tools/ark/ui-test.js @@ -89,6 +89,28 @@ const waitFace = async (label, want, ms = 30000) => { } return false; }; +// An address arrives the way a paste does — whole. Typed a key at a time, +// a re-render mid-typing can drop or reorder characters (the app then +// rightly refuses a bad checksum). +const pasteDest = (text) => page.evaluate((text) => { + const el = document.querySelector('textarea.send-dest'); + el.focus(); + el.value = text; + el.dispatchEvent(new Event('input', { bubbles: true })); +}, text); +// Select a balance card and open its Receive tab. The carousel glides to the +// card and landing on one opens its history — and a glide can settle back +// on the other card — so keep asking until the account sticks, then open +// Receive (the tab a receive's celebration takes over). +const openReceive = async (account) => { + for (let i = 0; i < 20; i++) { + await page.evaluate((a) => uiApp.ctx.setAccount(a), account); + await sleep(750); + if (await page.evaluate((a) => uiApp.ctx.getAccount() === a, account)) break; + } + await sleep(750); + await page.evaluate(() => { uiApp.ui.tab = 'receive'; uiApp.render(); }); +}; const tabPane = () => page.evaluate(() => document.querySelector('.tab-pane')?.innerText || ''); try { @@ -110,14 +132,32 @@ try { await snap('spending'); console.log('\n[2] receive over Ark'); - await page.evaluate(() => uiApp.ctx.setAccount('spending')); + await openReceive('spending'); let arkAddr = ''; for (let i = 0; i < 120 && !arkAddr; i++) { arkAddr = ((await tabPane()).match(/tark1[a-z0-9]+/) || [''])[0]; if (!arkAddr) await sleep(250); } - check('ark address on the Receive tab', !!arkAddr, arkAddr.slice(0, 24) + '…'); + check('ark address on the Receive tab', !!arkAddr, arkAddr ? arkAddr.slice(0, 24) + '…' + : 'tab ' + (await page.evaluate(() => uiApp.ui.tab)) + ': ' + (await tabPane()).slice(0, 200).replace(/\n/g, ' | ')); if (!arkAddr) { await snap('no-ark-address'); throw new Error('ark never connected — is captaind up?'); } + // A new wallet's first mailbox read is its baseline: whatever it finds is + // history and never celebrated. Pay only once that read is behind us, or + // the payment can land inside it and the celebration check is a coin toss. + const baselineDone = () => page.evaluate(() => new Promise((res) => { + const req = indexedDB.open('coinos-ark'); + req.onerror = () => res(false); + req.onsuccess = () => { + try { + const all = req.result.transaction('states').objectStore('states').getAll(); + all.onsuccess = () => res(all.result.some((v) => v && Array.isArray(v.movements) && !v.baselinePending)); + all.onerror = () => res(false); + } catch { res(false); } + }; + })); + let baselined = false; + for (let i = 0; i < 120 && !baselined; i++) { baselined = await baselineDone(); if (!baselined) await sleep(250); } + check('the first mailbox read (the baseline) is done', baselined); sh(`${ALICE} -q send ${arkAddr} "25000 sat"`); check('spending shows 25,000', await waitFace('Spending', 25000), String(await faceSat('Spending'))); check('payment celebration shown', await waitText('Payment received', 10000)); @@ -130,7 +170,7 @@ try { await sleep(500); const aliceAddr = sh(`${ALICE} -q address`); const before = aliceBalance(); - await page.type('textarea.send-dest', aliceAddr); + await pasteDest(aliceAddr); await sleep(800); check('no fee controls for an ark dest', /no mining fee/i.test(await tabPane()) && !/fee rate|coin control/i.test(await tabPane())); await page.type('.tab-pane input[placeholder="0"]', '7000'); @@ -142,16 +182,23 @@ try { await clickText('.tab-pane button.btn-primary', 'Send'); check('sent view', await waitText('Sent!', 15000)); await snap('sent'); - check('alice +7000', aliceBalance() === before + 7000, String(aliceBalance() - before)); + // alice's CLI wallet sees the coin once it has read its mailbox + let aliceGot = 0; + for (let i = 0; i < 30 && aliceGot !== 7000; i++) { aliceGot = aliceBalance() - before; if (aliceGot !== 7000) await sleep(500); } + check('alice +7000', aliceGot === 7000, String(aliceGot)); check('spending shows 18,000', await waitFace('Spending', 18000), String(await faceSat('Spending'))); await clickText('.card', 'Sent!'); // tap anywhere to proceed - await sleep(400); - const hist = await bodyText(); + let hist = ''; + for (let i = 0; i < 40; i++) { + hist = await bodyText(); + if (/received[\s\S]*\+25,000/i.test(hist) && /-7,000/.test(hist)) break; + await sleep(250); + } check('history shows the receive', /received[\s\S]*\+25,000/i.test(hist)); check('history shows the send', /-7,000/.test(hist)); console.log('\n[4] Savings -> Spending (board)'); - await page.evaluate(() => uiApp.ctx.setAccount('savings')); + await openReceive('savings'); let btcAddr = ''; for (let i = 0; i < 40 && !btcAddr; i++) { btcAddr = ((await tabPane()).match(/bcrt1[a-z0-9]+/) || [''])[0]; @@ -165,7 +212,7 @@ try { // sending Savings to your own Spending address opens the move panel instead await clickText('button', 'Send'); await sleep(500); - await page.type('textarea.send-dest', arkAddr); + await pasteDest(arkAddr); check('own Spending address opens the move panel', await waitText('Moving from Savings instead', 10000)); await page.type('.balance-move input', '30000'); await sleep(300); diff --git a/tools/comments-test.js b/tools/comments-test.js index 01d47d5..6801f44 100644 --- a/tools/comments-test.js +++ b/tools/comments-test.js @@ -25,12 +25,31 @@ const html = await buildHtml({ minify: true, pwa: false }); const server = Bun.serve({ port: 5283, fetch: () => new Response(html, { headers: { 'content-type': 'text/html' } }) }); const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome', headless: 'new', args: ['--no-sandbox'] }); const page = await browser.newPage(); -await page.evaluateOnNewDocument(() => { +// The article and its comments, fetched above, are also handed to the app +// on every relay socket that asks for them: the app reaching the same public +// relays as this script (one of them can be refusing this IP) is not what's +// under test. +await page.evaluateOnNewDocument((known) => { window.__sent = []; window.__all = []; + const matches = (f, e) => { + if (f.ids && !f.ids.includes(e.id)) return false; + if (f.kinds && !f.kinds.includes(e.kind)) return false; + if (f.authors && !f.authors.includes(e.pubkey)) return false; + for (const [k, vals] of Object.entries(f)) { + if (k[0] !== '#' || !Array.isArray(vals)) continue; + if (!e.tags.some((t) => t[0] === k.slice(1) && vals.includes(t[1]))) return false; + } + return !!(f.ids || Object.keys(f).some((k) => k[0] === '#')); + }; const send = WebSocket.prototype.send; WebSocket.prototype.send = function (d) { window.__all.push(this.url + ' ' + String(d).slice(0, 60)); + if (typeof d === 'string' && d.startsWith('["REQ"')) { + const [, sub, ...filters] = JSON.parse(d); + const hits = known.filter((e) => filters.some((f) => matches(f, e))); + if (hits.length) setTimeout(() => { for (const e of hits) this.dispatchEvent(new MessageEvent('message', { data: JSON.stringify(['EVENT', sub, e]) })); }, 30); + } if (typeof d === 'string' && d.startsWith('["EVENT"')) { // every event: nothing leaves the test const ev = JSON.parse(d)[1]; window.__sent.push(ev); @@ -40,7 +59,7 @@ await page.evaluateOnNewDocument(() => { } return send.call(this, d); }; -}); +}, [root, ...comments]); const click = (t) => page.evaluate((x) => { const e = [...document.querySelectorAll('button')].find((n) => n.textContent.trim().toLowerCase().includes(x)); if (e) { e.click(); return true; } return false; }, t); const waitText = async (x, ms = 25000) => { 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 waitFor = async (fn, ms = 15000) => { for (let i = 0; i < ms / 200; i++) { if (await page.evaluate(fn)) return true; await sleep(200); } return false; }; |
