diff options
| -rw-r--r-- | tools/feed-custom-test.js | 27 | ||||
| -rw-r--r-- | tools/feed-follow-test.js | 43 | ||||
| -rw-r--r-- | tools/feed-live-test.js | 37 | ||||
| -rw-r--r-- | tools/feed-window-dom-test.js | 5 | ||||
| -rw-r--r-- | tools/moderation-test.js | 14 | ||||
| -rw-r--r-- | tools/poll-test.js | 3 |
6 files changed, 73 insertions, 56 deletions
diff --git a/tools/feed-custom-test.js b/tools/feed-custom-test.js index 039b166..c2c94de 100644 --- a/tools/feed-custom-test.js +++ b/tools/feed-custom-test.js @@ -73,7 +73,10 @@ const errs = []; page.on('pageerror', (e) => errs.push(String(e))); 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 chips = () => page.evaluate(() => [...document.querySelectorAll('.feed-chip')].map((c) => (c.classList.contains('on') ? '*' : '') + c.textContent.trim())); +// the feeds' own chips (not the All / Images / … view switch under them) +const chips = () => page.evaluate(() => [...document.querySelectorAll('.feed-chips > .feed-chip')].map((c) => (c.classList.contains('on') ? '*' : '') + c.textContent.trim())); +// the Feed is a tab in the bottom nav (it was a row on the chat home) +const openFeed = () => page.evaluate(() => [...document.querySelectorAll('.app-nav-button')].find((b) => b.getAttribute('aria-label') === 'Feed')?.click()); const type = async (sel, text) => { await page.evaluate((q) => { const i = document.querySelector(q); i.value = ''; i.dispatchEvent(new Event('input', { bubbles: true })); }, sel); await page.click(sel); await page.type(sel, text); @@ -99,12 +102,11 @@ try { await page.evaluate(([k, pk, ns]) => { localStorage.setItem(k + ':follows', JSON.stringify({ tags: [['p', pk]], c: '', at: Math.floor(Date.now() / 1000) })); localStorage.setItem(k + ':feedNotes', JSON.stringify(ns)); + localStorage.setItem('btc-wallet-feed:' + k, 'following'); // a wallet with no feed remembered opens on Popular }, [base, AUTHOR, seeded]); await page.reload({ waitUntil: 'domcontentloaded' }); await waitText('receive', 20000); - await page.evaluate(() => { const b = [...document.querySelectorAll('button')].find((e) => /message/i.test(e.getAttribute('aria-label') || '')); if (b) b.click(); }); - await sleep(800); - await page.evaluate(() => { const e = [...document.querySelectorAll('.item')].find((n) => /feed/i.test(n.textContent)); if (e) e.click(); }); + await openFeed(); await sleep(1200); check('the feed opens on Following, with a + chip', JSON.stringify(await chips()) === JSON.stringify(['*Following', 'Popular', '#gardenstr', '+']), JSON.stringify(await chips())); @@ -112,7 +114,7 @@ try { await page.click('.feed-chip.add'); await sleep(300); check('the editor opens', await waitText('new feed', 3000)); await type('input[placeholder="e.g. Bitcoin builders"]', 'Coin talk'); - await page.click('input[type=checkbox]'); + await page.click('input[type=checkbox]:not(.feed-all-toggle)'); // everyone I follow (the first box is the relays' every-post one) await type('input[placeholder="#bitcoin #nostr"]', '#Bitcoin, lightning'); await click('save'); await sleep(1500); check('the new feed is on screen and selected', JSON.stringify(await chips()) === JSON.stringify(['Following', 'Popular', '#gardenstr', '*Coin talk', '+']), JSON.stringify(await chips())); @@ -157,15 +159,18 @@ try { console.log('\n[the feeds survive a reload; a feed can be deleted]'); await page.reload({ waitUntil: 'domcontentloaded' }); await waitText('receive', 20000); - await page.evaluate(() => { const b = [...document.querySelectorAll('button')].find((e) => /message/i.test(e.getAttribute('aria-label') || '')); if (b) b.click(); }); await sleep(800); - await page.evaluate(() => { const e = [...document.querySelectorAll('.item')].find((n) => /feed/i.test(n.textContent)); if (e) e.click(); }); await sleep(1200); - check('the saved feeds are still there; the session topic is not', JSON.stringify(await chips()) === JSON.stringify(['*Following', 'Popular', '#gardenstr', 'Coin talk', 'Pack feed', '+']), JSON.stringify(await chips())); - check('the home list shows the pack feed with its pack', await page.evaluate(() => { const b = document.querySelector('.chat-back'); if (b) b.click(); return !!b; }) && await waitText('test pack', 3000)); - await page.evaluate(() => { const e = [...document.querySelectorAll('.item')].find((n) => (n.querySelector('.chat-name') || {}).textContent === 'Feed'); if (e) e.click(); }); await sleep(800); + await openFeed(); await sleep(1200); + // (the feed you were on is remembered: the pack feed, saved last) + check('the saved feeds are still there; the session topic is not', JSON.stringify(await chips()) === JSON.stringify(['Following', 'Popular', '#gardenstr', 'Coin talk', '*Pack feed', '+']), JSON.stringify(await chips())); + // (the chat home no longer lists the feed with its summary: its editor shows the pack) + await page.click('[aria-label="Feed options"]'); await sleep(300); + await page.evaluate(() => [...document.querySelectorAll('.msg-sheet button')].find((b) => /edit feed/i.test(b.textContent)).click()); await sleep(400); + check('the pack feed kept its pack', await waitText('test pack', 3000)); + await page.click('.chat-back'); await sleep(600); await page.evaluate(() => [...document.querySelectorAll('.feed-chip')].find((c) => c.textContent.trim() === 'Coin talk').click()); await sleep(800); await page.click('[aria-label="Feed options"]'); await sleep(300); // the editor lives in the feed's ⋯ menu now await page.evaluate(() => [...document.querySelectorAll('.msg-sheet button')].find((b) => /edit feed/i.test(b.textContent)).click()); await sleep(400); - check('the editor shows what was saved', await page.evaluate(() => document.querySelector('input[placeholder="#bitcoin #nostr"]').value === '#bitcoin #lightning' && document.querySelector('input[type=checkbox]').checked)); + check('the editor shows what was saved', await page.evaluate(() => document.querySelector('input[placeholder="#bitcoin #nostr"]').value === '#bitcoin #lightning' && document.querySelector('input[type=checkbox]:not(.feed-all-toggle)').checked)); await click('delete feed'); await sleep(1000); check('deleting it lands back on Following', JSON.stringify(await chips()) === JSON.stringify(['*Following', 'Popular', '#gardenstr', 'Pack feed', '+']), JSON.stringify(await chips())); check('no page errors', errs.length === 0, errs.slice(0, 2).join(' | ')); diff --git a/tools/feed-follow-test.js b/tools/feed-follow-test.js index 50a572b..bae06a9 100644 --- a/tools/feed-follow-test.js +++ b/tools/feed-follow-test.js @@ -20,7 +20,9 @@ const server = Bun.serve({ port: 5237, fetch: () => new Response(html, { headers const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome', headless: 'new', args: ['--no-sandbox'] }); const page = await browser.newPage(); 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 clickItem = (t) => page.evaluate((x) => { const e = [...document.querySelectorAll('.item, button')].find((n) => n.textContent.trim().toLowerCase().includes(x)); if (e) { e.click(); return true; } return false; }, t); +// the Feed is a tab in the bottom nav (it used to be offered on the messages home) +const openFeed = () => page.evaluate(() => { const b = [...document.querySelectorAll('.app-nav-button')].find((e) => e.getAttribute('aria-label') === 'Feed'); if (b) b.click(); return !!b; }); +const chip = (name) => page.evaluate((n) => { const e = [...document.querySelectorAll('.feed-chips > .feed-chip')].find((b) => b.textContent.trim() === n); if (e) e.click(); return !!e; }, name); const text = () => page.evaluate(() => document.body.innerText); const waitText = async (x, ms = 25000) => { for (let i = 0; i < ms / 250; i++) { if ((await text()).toLowerCase().includes(x)) return true; await sleep(250); } return false; }; const FOLLOWED = 'e'.repeat(63) + '1'; @@ -40,12 +42,10 @@ try { await waitText('receive', 20000); const base = cacheKeyFor(mnemonic + '\n'); - // chat → the feed is offered before the conversations - await page.evaluate(() => { const b = [...document.querySelectorAll('button')].find((e) => /message/i.test(e.getAttribute('aria-label') || '')); if (b) b.click(); }); - await sleep(1200); - check('the feed is on the messages home', /follow people to fill this/i.test(await text()), (await text()).split('\n').slice(0, 6).join(' | ')); - - await clickItem('feed'); + check('the bottom nav has a Feed tab', await openFeed()); + await sleep(1000); + // a new wallet opens on Popular; Following is the people you follow + check('...whose chips include Following', await chip('Following')); await sleep(1000); check('a wallet that follows nobody is told so', /don't follow anyone yet/i.test(await text())); check('...and offered somewhere to start', /find people/i.test(await text())); @@ -56,9 +56,11 @@ try { }, [base, FOLLOWED]); await page.reload({ waitUntil: 'domcontentloaded' }); await waitText('receive', 20000); - await page.evaluate(() => { const b = [...document.querySelectorAll('button')].find((e) => /message/i.test(e.getAttribute('aria-label') || '')); if (b) b.click(); }); + await openFeed(); await sleep(1200); - check('the follow count shows on the messages home', /posts from the one person you follow/i.test(await text()), (await text()).match(/Posts from[^\n]*/)?.[0] || 'no line'); + // (the feed you were on, Following, is remembered) + check('Following no longer says you follow nobody', /\bFollowing\b/.test(await page.evaluate(() => document.querySelector('.feed-chips > .feed-chip.on')?.textContent || '')) + && !/don't follow anyone yet/i.test(await text())); // and their own profile page knows we follow them await page.goto('http://localhost:5237/' + npubEncode(FOLLOWED), { waitUntil: 'domcontentloaded' }); @@ -89,9 +91,7 @@ try { }, [base, FOLLOWED, OTHER]); await page.reload({ waitUntil: 'domcontentloaded' }); await waitText('receive', 20000); - await page.evaluate(() => { const b = [...document.querySelectorAll('button')].find((e) => /message/i.test(e.getAttribute('aria-label') || '')); if (b) b.click(); }); - await sleep(1000); - await clickItem('feed'); + await openFeed(); await sleep(6000); const dialled = await page.evaluate(() => window.__dialled || []); check('each author is read on their own write relay', @@ -114,35 +114,32 @@ try { }, [base, FOLLOWED]); await page.reload({ waitUntil: 'domcontentloaded' }); await waitText('receive', 20000); - await page.evaluate(() => { const b = [...document.querySelectorAll('button')].find((e) => /message/i.test(e.getAttribute('aria-label') || '')); if (b) b.click(); }); - await sleep(1000); - await clickItem('feed'); + await openFeed(); await sleep(6000); const wide = await page.evaluate(() => window.__dialled || []); const hit = [1, 2, 3, 4, 5, 6].filter((n) => wide.some((u) => u.includes('r' + n + '.example.invalid'))); check('an author who publishes to six relays is read on all six', hit.length === 6, 'reached ' + (hit.join(',') || 'none')); - // The feed opens at twenty posts and grows as you reach the bottom, however - // many it is holding. + // The feed opens at ten posts and grows as you reach the bottom, however + // many it is holding. (Tall posts: a page of short ones doesn't reach the + // distance from the bottom where the next page is read.) await page.evaluate((k) => { const notes = []; const now = Math.floor(Date.now() / 1000); - for (let i = 0; i < 60; i++) notes.push({ id: String(i).padStart(64, '0'), pubkey: 'f'.repeat(64), kind: 1, created_at: now - i * 60, content: 'post ' + i, tags: [] }); + for (let i = 0; i < 60; i++) notes.push({ id: String(i).padStart(64, '0'), pubkey: 'f'.repeat(64), kind: 1, created_at: now - i * 60, content: 'post ' + i + ' ' + 'with enough words to stand tall '.repeat(30), tags: [] }); localStorage.setItem(k + ':feedNotes', JSON.stringify(notes)); }, base); await page.reload({ waitUntil: 'domcontentloaded' }); await waitText('receive', 20000); - await page.evaluate(() => { const b = [...document.querySelectorAll('button')].find((e) => /message/i.test(e.getAttribute('aria-label') || '')); if (b) b.click(); }); - await sleep(1000); - await clickItem('feed'); + await openFeed(); await sleep(1500); const first = await page.evaluate(() => document.querySelectorAll('.notes-feed > .row').length); - check('the feed opens at twenty posts, not sixty', first === 20, first + ' rows'); + check('the feed opens at ten posts, not sixty', first === 10, first + ' rows'); await page.evaluate(() => window.scrollTo(0, document.documentElement.scrollHeight)); await sleep(1200); const second = await page.evaluate(() => document.querySelectorAll('.notes-feed > .row').length); - check('reaching the bottom shows twenty more', second === 40, second + ' rows'); + check('reaching the bottom shows ten more', second === 20, second + ' rows'); } finally { await browser.close(); server.stop(true); } console.log(ok ? '\n✅ follows and feed' : '\n❌ failed'); process.exit(ok ? 0 : 1); diff --git a/tools/feed-live-test.js b/tools/feed-live-test.js index 9f98fde..8f80d46 100644 --- a/tools/feed-live-test.js +++ b/tools/feed-live-test.js @@ -105,12 +105,12 @@ try { localStorage.setItem(k + ':follows', JSON.stringify({ tags: [['p', pk]], c: '', at: Math.floor(Date.now() / 1000) })); localStorage.setItem(k + ':feedNotes', JSON.stringify(ns)); localStorage.setItem(k + ':profiles', JSON.stringify({ [pk]: { name: 'Test Author', t: Date.now() } })); + localStorage.setItem('btc-wallet-feed:' + k, 'following'); // a new wallet would open on Popular }, [base, AUTHOR, seeded]); await page.reload({ waitUntil: 'domcontentloaded' }); await waitText('receive', 20000); - await page.evaluate(() => { const b = [...document.querySelectorAll('button')].find((e) => /message/i.test(e.getAttribute('aria-label') || '')); if (b) b.click(); }); - await sleep(1000); - await page.evaluate(() => { const e = [...document.querySelectorAll('.item')].find((n) => /feed/i.test(n.textContent)); if (e) e.click(); }); + // the Feed is a tab in the bottom nav (it was a row on the chat home) + await page.evaluate(() => [...document.querySelectorAll('.app-nav-button')].find((b) => b.getAttribute('aria-label') === 'Feed')?.click()); await sleep(4000); check('the feed has posts in it', (await page.evaluate(() => document.querySelectorAll('.notes-feed > .row').length)) > 0); @@ -144,8 +144,12 @@ try { await page.evaluate(() => document.querySelector('.feed-new-pill').click()); await sleep(600); - const opened = await page.evaluate(() => ({ y: Math.round(window.scrollY), pill: !!document.querySelector('.feed-new-pill') })); - check('tapping it takes you up to the post', opened.y < 120, 'scrollY ' + opened.y); + // the pill lands on the (oldest) new post, at the top of the screen + const opened = await page.evaluate(() => { + const r = [...document.querySelectorAll('.notes-feed > .row')].find((n) => /BRAND NEW POST/.test(n.innerText)); + return { top: r ? Math.round(r.getBoundingClientRect().top) : null, pill: !!document.querySelector('.feed-new-pill') }; + }); + check('tapping it takes you up to the post', opened.top !== null && opened.top >= 0 && opened.top < 120, 'post top ' + opened.top); check('...and the pill is done', !opened.pill); console.log('\n[scrolling back up on your own]'); @@ -238,6 +242,12 @@ try { const r = [...document.querySelectorAll('.notes-feed > .row')].find((n) => eval(label)(n) === text); return r ? { text, top: Math.round(r.getBoundingClientRect().top), y: Math.round(window.scrollY) } : null; }, label.toString(), text); + // up to the top the way a reader goes, a screen at a time (one jump from + // far down lands before the rows up there are mounted again) + const scrollUp = async () => { + while (await page.evaluate(() => { scrollBy(0, -Math.min(scrollY, innerHeight)); return scrollY > 0; })) await sleep(80); + await sleep(600); + }; const topRow = () => page.evaluate(() => (document.querySelector('.notes-feed > .row') || {}).innerText || ''); console.log('\n[coming back after being away, a few new posts]'); @@ -260,23 +270,20 @@ try { await page.evaluate(() => window.scrollTo(0, document.documentElement.scrollHeight)); await sleep(600); check('...older posts that merely missed the cache went in quietly too', /not in the cache/.test(await feedText())); - await page.evaluate(() => window.scrollTo(0, 0)); - await sleep(600); + await scrollUp(); check('...scrolling up reaches the newest post, and clears the pill', /CATCH-UP 1/.test(await topRow()) && !(await page.$('.feed-new-pill')), (await topRow()).slice(0, 40)); console.log('\n[coming back while sitting at the top]'); const wasTop = await anchor(); await away(() => [signed('AT-THE-TOP A', 0), signed('AT-THE-TOP B', 1)]); const a2 = await rowAt(wasTop.text); - check('the header and first post stay in place', !!wasTop && !!a2 && a2.text === wasTop.text && Math.abs(a2.top - wasTop.top) <= 2 && a2.y === wasTop.y, + // new posts go in above the first post, which is held where it was (the + // page grows upward, scrolled by what went in above it) + check('the first post stays in place', !!wasTop && !!a2 && a2.text === wasTop.text && Math.abs(a2.top - wasTop.top) <= 2, JSON.stringify({ was: wasTop, now: a2 })); - check('...new posts wait while the header is visible', !/AT-THE-TOP A/.test(await topRow())); - await page.evaluate(() => { - const first = document.querySelector('.notes-feed > .row'); - scrollBy(0, Math.ceil(first.getBoundingClientRect().top)); - }); - await sleep(600); - check('...then arrive offscreen above it, with a notice', /AT-THE-TOP A/.test(await topRow()) && /2 new posts/i.test(await page.evaluate(() => document.querySelector('.feed-new-pill')?.textContent || ''))); + const atTop = await rowAt('AT-THE-TOP A'); + check('...the new posts went in above it', /AT-THE-TOP A/.test(await topRow()) && !!atTop && !!a2 && atTop.top < a2.top, JSON.stringify(atTop)); + check('...with a notice', /2 new posts/i.test(await page.evaluate(() => document.querySelector('.feed-new-pill')?.textContent || ''))); console.log('\n[coming back after a long time away]'); await page.evaluate(() => window.scrollTo(0, 900)); diff --git a/tools/feed-window-dom-test.js b/tools/feed-window-dom-test.js index 7f89785..72ca535 100644 --- a/tools/feed-window-dom-test.js +++ b/tools/feed-window-dom-test.js @@ -87,11 +87,12 @@ try { localStorage.setItem(k + ':follows', JSON.stringify({ tags: [['p', pk]], c: '', at: Math.floor(Date.now() / 1000) })); localStorage.setItem(k + ':feedNotes', JSON.stringify(ns)); localStorage.setItem(k + ':profiles', JSON.stringify({ [pk]: { name: 'Prolific Poster', t: Date.now() } })); + localStorage.setItem('btc-wallet-feed:' + k, 'following'); // a wallet with no feed remembered opens on Popular }, [base, AUTHOR, seeded]); await page.reload({ waitUntil: 'domcontentloaded' }); await waitText('receive', 20000); - await page.evaluate(() => { const b = [...document.querySelectorAll('button')].find((e) => /message/i.test(e.getAttribute('aria-label') || '')); if (b) b.click(); }); await sleep(800); - await page.evaluate(() => { const e = [...document.querySelectorAll('.item')].find((n) => (n.querySelector('.chat-name') || {}).textContent === 'Feed'); if (e) e.click(); }); + // the Feed is a tab in the bottom nav (it was a row on the chat home) + await page.evaluate(() => [...document.querySelectorAll('.app-nav-button')].find((b) => b.getAttribute('aria-label') === 'Feed')?.click()); await sleep(2500); console.log('\n[a long scroll]'); check('the feed opens on its first posts', (await dom()).first === 'post number 1', JSON.stringify(await dom())); diff --git a/tools/moderation-test.js b/tools/moderation-test.js index 1f4deb8..d6df879 100644 --- a/tools/moderation-test.js +++ b/tools/moderation-test.js @@ -33,7 +33,12 @@ const REPLIES = [reply(OTHER, 'reply from ok person', 200), reply(MUTED, 'reply const LONG = 'buy the dip now, best exchange ever, sign up with my link and get free sats today!!!'; const html = await buildHtml({ minify: true, pwa: false }); -const server = Bun.serve({ port: 5299, fetch: () => new Response(html, { headers: { 'content-type': 'text/html' } }) }); +// nos.lol (one of the relays the mute list is read from) is a stand-in +// served here — an empty relay — so a real one that won't connect can't hold +// the list back past the checks +const server = Bun.serve({ port: 5299, + fetch: (req, srv) => (new URL(req.url).pathname === '/relay' && srv.upgrade(req) ? undefined : new Response(html, { headers: { 'content-type': 'text/html' } })), + websocket: { message: (ws, data) => { try { const m = JSON.parse(data); if (m[0] === 'REQ') ws.send(JSON.stringify(['EOSE', m[1]])); } catch {} } } }); const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome', headless: 'new', args: ['--no-sandbox'] }); const page = await browser.newPage(); await page.evaluateOnNewDocument((fx) => { @@ -46,6 +51,7 @@ await page.evaluateOnNewDocument((fx) => { ws.dispatchEvent(new MessageEvent('message', { data: JSON.stringify(['EOSE', id]) })); }, 0); window.WebSocket = function (...a) { + if (/^wss:\/\/nos\.lol\/?$/.test(String(a[0]))) a[0] = 'ws://localhost:5299/relay'; const ws = new Real(...a); const send = ws.send.bind(ws); ws.send = (data) => { @@ -96,9 +102,8 @@ const waitText = async (x, ms = 25000) => { for (let i = 0; i < ms / 250; i++) { const waitFor = async (fn, ms = 10000) => { for (let i = 0; i < ms / 250; i++) { if (await page.evaluate(fn)) return true; await sleep(250); } return false; }; const has = async (x) => (await text()).includes(x); const openFeed = async () => { - await page.evaluate(() => { const b = [...document.querySelectorAll('button')].find((e) => /message/i.test(e.getAttribute('aria-label') || '')); if (b) b.click(); }); - await sleep(800); - await page.evaluate(() => { const e = [...document.querySelectorAll('.item')].find((n) => (n.querySelector('.chat-name') || {}).textContent === 'Feed'); if (e) e.click(); }); + // the Feed is a tab in the bottom nav (it was a row on the chat home) + await page.evaluate(() => [...document.querySelectorAll('.app-nav-button')].find((b) => b.getAttribute('aria-label') === 'Feed')?.click()); await sleep(1200); }; @@ -122,6 +127,7 @@ try { await page.evaluate(([k, pk, ns]) => { localStorage.setItem(k + ':follows', JSON.stringify({ tags: [['p', pk]], c: '', at: Math.floor(Date.now() / 1000) })); localStorage.setItem(k + ':feedNotes', JSON.stringify(ns)); + localStorage.setItem('btc-wallet-feed:' + k, 'following'); // a wallet with no feed remembered opens on Popular }, [base, AUTHOR.pk, seeded]); await page.reload({ waitUntil: 'domcontentloaded' }); await waitText('receive', 20000); diff --git a/tools/poll-test.js b/tools/poll-test.js index b88a4db..bc1715e 100644 --- a/tools/poll-test.js +++ b/tools/poll-test.js @@ -52,11 +52,12 @@ try { const quoting = { id: 'd'.repeat(64), pubkey: q, kind: 1, created_at: now, content: 'vote on this nostr:' + ref, tags: [] }; localStorage.setItem(k + ':follows', JSON.stringify({ tags: [['p', poll.pubkey], ['p', q]], c: '', at: now })); localStorage.setItem(k + ':feedNotes', JSON.stringify([quoting, poll])); + localStorage.setItem('btc-wallet-feed:' + k, 'following'); // a wallet with no feed remembered opens on Popular localStorage.setItem(k + ':profiles', JSON.stringify({ [poll.pubkey]: { name: 'Poll Author', t: Date.now() }, [q]: { name: 'Quoter', t: Date.now() } })); }, [base, poll, Q, neventOf(poll.id, poll.pubkey)]); await page.reload({ waitUntil: 'domcontentloaded' }); await waitText('receive', 20000); - await page.evaluate(() => document.querySelector('.app-bottom-nav .app-nav-button').click()); + await page.evaluate(() => [...document.querySelectorAll('.app-nav-button')].find((b) => b.getAttribute('aria-label') === 'Feed').click()); await waitFor(() => document.querySelectorAll('.notes-feed .poll').length >= 2, 20000); const feedPoll = await page.evaluate(() => { |
