aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-07 08:34:13 -0700
committerAdam Soltys <[email protected]>2026-10-07 08:34:13 -0700
commita6bae186071e7e07136a37f63864d0bf578edd16 (patch)
tree6fa8a1086f5b30093f7c9085b6afe06d8c69aa60 /tools
parent7067ed8024cf8d4694fe260df90897c5ebecb245 (diff)
parentdfb017c1b10cb52c24f4f0bcf745c6abea11b98f (diff)
downloadcoinosv3-a6bae186071e7e07136a37f63864d0bf578edd16.tar.gz
coinosv3-a6bae186071e7e07136a37f63864d0bf578edd16.zip
Merge branch 'worktree-agent-a4c5f0908bc7757bf'
Diffstat (limited to 'tools')
-rw-r--r--tools/compose-preview-test.js11
-rw-r--r--tools/composer-media-test.js10
-rw-r--r--tools/image-tap-test.js3
-rw-r--r--tools/link-preview-test.js5
-rw-r--r--tools/post-actions-test.js19
-rw-r--r--tools/quote-card-test.js3
-rw-r--r--tools/thread-reply-test.js16
-rw-r--r--tools/video-autoplay-test.js5
-rw-r--r--tools/video-compress-test.js4
-rw-r--r--tools/video-unmute-test.js6
-rw-r--r--tools/youtube-embed-test.js9
-rw-r--r--tools/zap-deep-profile-test.js2
12 files changed, 60 insertions, 33 deletions
diff --git a/tools/compose-preview-test.js b/tools/compose-preview-test.js
index 2138ea6..c943655 100644
--- a/tools/compose-preview-test.js
+++ b/tools/compose-preview-test.js
@@ -53,6 +53,8 @@ try {
await page.evaluate(([k, n]) => {
localStorage.setItem(k + ':follows', JSON.stringify({ tags: [['p', n.pubkey]], c: '', at: Math.floor(Date.now() / 1000) }));
localStorage.setItem(k + ':feedNotes', JSON.stringify([n]));
+ localStorage.setItem(k + ':profiles', JSON.stringify({ [n.pubkey]: { name: 'fiatjaf', t: Date.now() } }));
+ localStorage.setItem('btc-wallet-feed:' + k, 'following'); // new wallets open on Popular
}, [base, {
id: 'f'.repeat(63) + '1', pubkey: '3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d',
kind: 1, created_at: Math.floor(Date.now() / 1000) - 60, content: 'a post to answer', tags: [],
@@ -61,10 +63,8 @@ try {
await waitText('receive', 20000);
// into the feed, where posts are written
- 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(); });
- await sleep(2500);
+ await page.evaluate(() => document.querySelector('.app-nav-button[aria-label="Feed"]').click());
+ for (let i = 0; i < 60 && !(await page.$('.notes-feed:not([data-booting]) > .row')); i++) await sleep(250);
const opened = await page.evaluate(() => { const b = [...document.querySelectorAll('button')].find((n) => (n.getAttribute('aria-label') || n.textContent).trim().toLowerCase() === 'new post'); if (b) { b.click(); return true; } return false; });
check('the composer opens', opened && !!(await page.$('coinos-text')));
@@ -98,8 +98,9 @@ try {
/plus a word/.test(await page.evaluate(() => (document.querySelector('.draft-preview') || {}).innerText || '')));
// and it puts itself away
+ // (the same Preview button, pressed: a toggle)
await page.evaluate(() => [...document.querySelectorAll('button')]
- .find((b) => /hide preview/i.test(b.getAttribute('aria-label') || ''))?.click());
+ .find((b) => /preview/i.test(b.getAttribute('aria-label') || '') && b.getAttribute('aria-pressed') === 'true')?.click());
await sleep(500);
check('and it closes again', !(await page.$('.draft-preview')));
diff --git a/tools/composer-media-test.js b/tools/composer-media-test.js
index 9b2b41c..6f30995 100644
--- a/tools/composer-media-test.js
+++ b/tools/composer-media-test.js
@@ -17,16 +17,19 @@ const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome
const page = await browser.newPage();
const uploads = [];
await page.setRequestInterception(true);
+// every public media host the upload may try first (nothing leaves the machine)
+const HOSTS = /blossom\.ditto\.pub|blossom\.primal\.net|nostr\.download|nostr\.build/;
page.on('request', (req) => {
const u = req.url();
- if (/nostr\.download|nostr\.build/.test(u) && req.method() === 'PUT') {
+ if (HOSTS.test(u) && req.method() === 'PUT') {
uploads.push(u);
return req.respond({ status: 200, contentType: 'application/json', headers: { 'access-control-allow-origin': '*' },
body: JSON.stringify({ url: 'https://nostr.download/' + 'f'.repeat(64) + '.gif', sha256: 'f'.repeat(64), size: 10, type: 'image/gif' }) });
}
- if (/nostr\.download|nostr\.build/.test(u) && req.method() === 'OPTIONS') {
+ if (HOSTS.test(u) && req.method() === 'OPTIONS') {
return req.respond({ status: 204, headers: { 'access-control-allow-origin': '*', 'access-control-allow-methods': 'PUT, HEAD, GET', 'access-control-allow-headers': '*' } });
}
+ if (HOSTS.test(u) && req.method() === 'HEAD') return req.respond({ status: 200, headers: { 'access-control-allow-origin': '*' } });
req.continue();
});
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);
@@ -50,10 +53,11 @@ try {
localStorage.setItem(k + ':follows', JSON.stringify({ tags: [['p', a]], c: '', at: now }));
localStorage.setItem(k + ':feedNotes', JSON.stringify([{ id: 'c'.repeat(64), pubkey: a, kind: 1, created_at: now, content: 'a post to answer', tags: [] }]));
localStorage.setItem(k + ':profiles', JSON.stringify({ [a]: { name: 'Poster', t: Date.now() } }));
+ localStorage.setItem('btc-wallet-feed:' + k, 'following'); // new wallets open on Popular
}, [base, A]);
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.querySelector('.app-nav-button[aria-label="Feed"]').click());
await waitText('a post to answer', 20000);
await page.evaluate(() => [...document.querySelectorAll('.notes-feed [data-key]')].find((n) => n.innerText.includes('a post to answer')).querySelector('button[aria-label="Reply"]').click());
check('the reply box appears', await waitFor(() => !!document.querySelector('.thread-reply-input'), 10000));
diff --git a/tools/image-tap-test.js b/tools/image-tap-test.js
index c531bba..5ed182e 100644
--- a/tools/image-tap-test.js
+++ b/tools/image-tap-test.js
@@ -38,10 +38,11 @@ try {
localStorage.setItem(k + ':follows', JSON.stringify({ tags: [['p', a]], c: '', at: now }));
localStorage.setItem(k + ':feedNotes', JSON.stringify([{ id: 'c'.repeat(64), pubkey: a, kind: 1, created_at: now, content: 'a tall one http://localhost:5281/tall.png', tags: [] }]));
localStorage.setItem(k + ':profiles', JSON.stringify({ [a]: { name: 'Poster', t: Date.now() } }));
+ localStorage.setItem('btc-wallet-feed:' + k, 'following'); // new wallets open on Popular
}, [base, A]);
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.querySelector('.app-nav-button[aria-label="Feed"]').click());
check('the picture is in the post', await waitFor(() => { const i = document.querySelector('.notes-feed img.note-img'); return i && i.complete && i.naturalWidth > 0; }, 20000));
// on the phone the box spans the column and the picture sits in its middle
// (object-fit: contain); desktop Chrome sizes the box to the picture, so
diff --git a/tools/link-preview-test.js b/tools/link-preview-test.js
index d90dbd2..0b9e863 100644
--- a/tools/link-preview-test.js
+++ b/tools/link-preview-test.js
@@ -89,12 +89,11 @@ try {
await page.evaluate(([k, pk]) => {
localStorage.setItem(k + ':follows', JSON.stringify({ tags: [['p', pk]], c: '', at: Math.floor(Date.now() / 1000) }));
localStorage.setItem(k + ':profiles', JSON.stringify({ [pk]: { name: 'Linker', t: Date.now() } }));
+ localStorage.setItem('btc-wallet-feed:' + k, 'following'); // new wallets open on Popular
}, [base, APK]);
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(500);
- await page.evaluate(() => { const e = [...document.querySelectorAll('.item')].find((n) => /feed/i.test(n.textContent)); if (e) e.click(); });
+ await page.evaluate(() => document.querySelector('.app-nav-button[aria-label="Feed"]').click());
await page.waitForSelector('.notes-feed:not([data-booting]) > .row', { timeout: 30000 });
await sleep(800);
const rows = await page.evaluate(() => [...document.querySelectorAll('.notes-feed > .row')].map((r) => ({
diff --git a/tools/post-actions-test.js b/tools/post-actions-test.js
index 17f8372..4c5d92e 100644
--- a/tools/post-actions-test.js
+++ b/tools/post-actions-test.js
@@ -16,7 +16,10 @@ 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; };
const html = await buildHtml({ minify: true, pwa: false });
-const png = await Bun.file('/tmp/claude-1000/-home-adam-coinosv3/61dea255-d26d-411d-be8b-92dfb0a75887/scratchpad/big.png').arrayBuffer().catch(() => null);
+// a picture served locally (a remote one that fails to load stays a link)
+const PNG = '/tmp/coinos-post-actions-test.png';
+Bun.spawnSync(['ffmpeg', '-y', '-loglevel', 'error', '-f', 'lavfi', '-i', 'color=c=green:s=600x400', '-frames:v', '1', PNG]);
+const png = await Bun.file(PNG).arrayBuffer().catch(() => null);
const server = Bun.serve({ port: 5258, async fetch(req) {
const u = new URL(req.url);
if (u.pathname === '/pic.png' && png) return new Response(png, { headers: { 'content-type': 'image/png' } });
@@ -54,12 +57,12 @@ try {
]));
// a known face: a row is admitted once its author's picture has been tried
localStorage.setItem(k + ':profiles', JSON.stringify({ [pk]: { name: 'Flower Poster', t: Date.now() } }));
+ localStorage.setItem('btc-wallet-feed:' + k, 'following'); // new wallets open on Popular
}, [base, AUTHOR, NOTE_ID]);
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
+ await page.evaluate(() => document.querySelector('.app-nav-button[aria-label="Feed"]').click());
for (let i = 0; i < 48; i++) { if (await page.$('.note-act')) break; await sleep(250); }
await sleep(600);
@@ -105,7 +108,7 @@ try {
await sleep(1500);
// the ellipsis sheet, and muting from it
- await page.evaluate(() => { const b = [...document.querySelectorAll('.btn-sm')].find((x) => x.textContent.trim() === '⋯'); if (b) b.click(); });
+ await page.evaluate(() => { const b = [...document.querySelectorAll('.notes-feed .btn-sm')].find((x) => x.textContent.trim() === '⋯'); if (b) b.click(); });
await sleep(600);
const sheet = await page.evaluate(() => { const s = document.querySelector('.confirm-pop'); return s ? s.innerText.replace(/\n+/g, ' | ') : ''; });
check('the ellipsis offers mute and block', /Mute/.test(sheet) && /Block/.test(sheet), sheet.slice(0, 70));
@@ -139,7 +142,11 @@ try {
return 'opened';
});
await sleep(1200);
- const img = await page.evaluate(() => { const i = document.querySelector('.chat-bubble img.note-img'); if (!i) return null; i.click(); return true; });
+ // a real tap on the picture itself (a tap beside a fitted picture is a tap on the post)
+ for (let i = 0; i < 20 && !(await page.evaluate(() => { const i = document.querySelector('.chat-bubble img.note-img'); return i && i.complete && i.naturalWidth > 0; })); i++) await sleep(250);
+ const at = await page.evaluate(() => { const i = document.querySelector('.chat-bubble img.note-img'); if (!i) return null; i.scrollIntoView({ block: 'center' }); const b = i.getBoundingClientRect(); return { x: b.left + b.width / 2, y: b.top + b.height / 2 }; });
+ const img = !!at;
+ if (at) await page.mouse.click(at.x, at.y);
await sleep(600);
check('an image in a DM opens full screen too', !!img && await page.evaluate(() => !!document.querySelector('.lightbox')), img ? 'tapped' : 'no image in the bubble (' + opened + ')');
diff --git a/tools/quote-card-test.js b/tools/quote-card-test.js
index d2d7d7e..2cb9d41 100644
--- a/tools/quote-card-test.js
+++ b/tools/quote-card-test.js
@@ -68,11 +68,12 @@ try {
{ id: 'e'.repeat(64), pubkey: pk, kind: 1, created_at: Math.floor(Date.now()/1000) - 60, content: 'I remember this.\n\nnostr:' + FAR, tags: [] },
]));
localStorage.setItem(k + ':profiles', JSON.stringify({ [pk]: { name: 'Quoter', t: Date.now() } }));
+ localStorage.setItem('btc-wallet-feed:' + k, 'following'); // new wallets open on Popular
}, [base, A, nevent, FAR]);
if (process.argv.includes('--slow')) await page.evaluate(() => localStorage.setItem('__slowQuote', '1'));
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.querySelector('.app-nav-button[aria-label="Feed"]').click());
await sleep(7000);
if (process.argv.includes('--slow')) {
for (let i = 0; i < 40 && !(await page.evaluate(() => !!document.querySelector('.quote-card'))); i++) await sleep(250);
diff --git a/tools/thread-reply-test.js b/tools/thread-reply-test.js
index 4362468..16b6f50 100644
--- a/tools/thread-reply-test.js
+++ b/tools/thread-reply-test.js
@@ -7,6 +7,7 @@ const dom = app.slice(app.indexOf('function h(tag,'), app.indexOf('// ----------
const entry = `
import { messagesFeature } from './src/features/messages.js';
import { createThreadStore } from './src/thread-cache.js';
+import './src/rich-text.js'; // <coinos-text>, the reply field (app.js registers it)
${dom}
const author = 'a'.repeat(64), mine = 'b'.repeat(64), rootId = 'c'.repeat(64);
const root = { id: rootId, pubkey: author, kind: 1, content: 'Root post', tags: [], created_at: 1 };
@@ -14,10 +15,12 @@ const reply = { ...root, id: 'd'.repeat(64), content: 'The reply you tapped', ta
const other = { ...reply, id: 'e'.repeat(64), content: 'Another reply not returned by the relay', created_at: 3 };
const ui = { screen: 'wallet' }, waiting = [];
window.sent = [];
-window.query = filter => filter.kinds?.[0] === 1 && (filter.ids || filter['#e']) ? new Promise(resolve => waiting.push({ filter, resolve })) : Promise.resolve([]);
+// the root (asked for by id, any kind) and its replies are held until the test lets them go
+window.query = filter => filter.ids || (filter.kinds?.[0] === 1 && filter['#e']) ? new Promise(resolve => waiting.push({ filter, resolve })) : Promise.resolve([]);
const signer = { signEvent: async e => ({ ...e, id: 'f'.repeat(64), pubkey: mine }) };
let feature;
-const wallet = { loaded: true, nostrRelays: () => [], loadFeatureState: (k, d) => d, saveFeatureState() {} };
+// (a wallet is open: without one the app is a visitor's, and Reply asks you to sign in)
+const wallet = { xpub: 'test-wallet', loaded: true, nostrRelays: () => [], loadFeatureState: (k, d) => d, saveFeatureState() {} };
function render() {
if (!feature) return;
document.querySelector('#app').replaceChildren(feature.screenView() || feature.row(author, reply, 'Someone'));
@@ -26,6 +29,7 @@ feature = messagesFeature({ h, wallet, ui, render, toast() {}, brandHeader: () =
? h('div', { style: 'height:60px' }) : null,
hook: k => k === 'nostrLoginIdentity' ? { pubkey: mine, signer } : null });
window.test = { ui, reply, other, root, render, waiting, showNav: false,
+ held: () => waiting.some(q => q.filter.ids) && waiting.some(q => q.filter['#e']),
stable: () => feature.testStable(),
seedFace: () => createThreadStore().save({ root, replies: [reply] }, reply.id, {
[author]: { name: 'Nostrich', picture: 'https://example.com/nostrich.webp', eventAt: 10, t: Date.now(),
@@ -75,10 +79,12 @@ try {
check('Reply immediately shows its composer beneath the tapped post', await inline());
check('the input receives focus before relay queries finish', await page.evaluate(() => document.activeElement.matches('.thread-reply-input')));
await page.type('.thread-reply-input', 'A reply before the root loads');
- await page.click('.thread-reply-send');
+ // (one atomic tap: a repaint landing between finding the button and clicking it replaces it)
+ await page.waitForFunction(() => !document.querySelector('.thread-reply-send')?.disabled);
+ await page.evaluate(() => document.querySelector('.thread-reply-send').click());
await page.waitForFunction(() => sent.length === 1);
check('publishing targets the selected reply and its original root', await page.evaluate(() => sent[0].tags.some(t => t[0] === 'e' && t[1] === test.reply.id && t[3] === 'reply') && sent[0].tags.some(t => t[0] === 'e' && t[1] === test.root.id && t[3] === 'root')));
- await page.waitForFunction(() => test.waiting.length === 2);
+ await page.waitForFunction(() => test.held());
await page.evaluate(() => test.release());
await page.waitForFunction(() => document.body.innerText.includes('Root post'));
// the composer folds away once the reply is sent; the tapped reply itself stays
@@ -90,7 +96,7 @@ try {
check('Reply leaves the covering profile and inserts an uncached reply into the thread', await inline() && await page.evaluate(() => !test.ui.profOverThread && document.activeElement.matches('.thread-reply-input')));
await page.evaluate(() => test.missing());
await page.click('#outside .note-act');
- await page.waitForFunction(() => test.waiting.length === 2);
+ await page.waitForFunction(() => test.held());
await page.evaluate(() => test.empty());
await page.waitForFunction(() => !document.querySelector('#app .spinner'));
check('an unavailable root still leaves the selected post replyable', await inline());
diff --git a/tools/video-autoplay-test.js b/tools/video-autoplay-test.js
index 583c908..93453ee 100644
--- a/tools/video-autoplay-test.js
+++ b/tools/video-autoplay-test.js
@@ -62,12 +62,11 @@ 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: 'Clip Poster', t: Date.now() } }));
+ localStorage.setItem('btc-wallet-feed:' + k, 'following'); // new wallets 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(800);
- await page.evaluate(() => { const e = [...document.querySelectorAll('.item')].find((n) => (n.querySelector('.chat-name') || {}).textContent === 'Feed'); if (e) e.click(); });
+ await page.evaluate(() => document.querySelector('.app-nav-button[aria-label="Feed"]').click());
const players = () => ytRequests.filter((u) => /youtube-nocookie\.com\/embed/.test(u)).length; // the poster still comes from ytimg, as before
console.log('\n[a video in a post]');
diff --git a/tools/video-compress-test.js b/tools/video-compress-test.js
index ce1a936..c1175f8 100644
--- a/tools/video-compress-test.js
+++ b/tools/video-compress-test.js
@@ -40,7 +40,7 @@ await page.evaluateOnNewDocument(() => {
const open = XMLHttpRequest.prototype.open, send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function (m, url) { this.__m = m; this.__url = String(url); return open.apply(this, arguments); };
XMLHttpRequest.prototype.send = function (body) {
- if (this.__m === 'PUT' && /nostr\.build|nostr\.download/.test(this.__url) && body instanceof Blob) {
+ if (this.__m === 'PUT' && /blossom\.ditto\.pub|blossom\.primal\.net|nostr\.build|nostr\.download/.test(this.__url) && body instanceof Blob) {
body.arrayBuffer().then((buf) => { window.__put = { size: body.size, type: body.type, buf }; });
}
return send.apply(this, arguments);
@@ -56,7 +56,7 @@ await page.evaluateOnNewDocument(() => {
await page.setRequestInterception(true);
page.on('request', (req) => {
const u = req.url();
- if (!/nostr\.build|nostr\.download/.test(u)) return req.continue();
+ if (!/blossom\.ditto\.pub|blossom\.primal\.net|nostr\.build|nostr\.download/.test(u)) return req.continue();
const cors = { 'access-control-allow-origin': '*', 'access-control-allow-methods': 'PUT, HEAD, GET', 'access-control-allow-headers': '*' };
if (req.method() === 'OPTIONS') return req.respond({ status: 204, headers: cors });
if (req.method() === 'HEAD') return req.respond({ status: 200, headers: cors });
diff --git a/tools/video-unmute-test.js b/tools/video-unmute-test.js
index 16cb56f..fec3955 100644
--- a/tools/video-unmute-test.js
+++ b/tools/video-unmute-test.js
@@ -16,6 +16,10 @@ const server = Bun.serve({ port: 5302, fetch(req) {
// Hold autoplay until after repaint to make the race deterministic.
window.IntersectionObserver = undefined;
const t = key => key;
+ // what videoNode reaches for outside the slice: no cached stills or sizes,
+ // and no clip over the data budget
+ const videoThumbs = new Map(), videoInfo = new Map(), tappedClips = new Set();
+ const feedMedia = () => null, rememberVideo = () => {}, probeVideo = async () => {}, videoHeavy = () => false, heavyLabel = () => '';
${dom}
${video}
const url = '/clip.webm';
@@ -24,6 +28,8 @@ const server = Bun.serve({ port: 5302, fetch(req) {
const originalButton = live.querySelector('button');
const fresh = videoNode(url);
morph(live, fresh);
+ // (a clip loads nothing until it comes into view; this one is in view)
+ live.querySelector('video').preload = 'auto';
window.test = { live, fresh, originalButton, rebuild() {
const next = videoNode(url); live.replaceWith(next); return next.querySelector('video').muted;
} };
diff --git a/tools/youtube-embed-test.js b/tools/youtube-embed-test.js
index 1c66676..5d50b35 100644
--- a/tools/youtube-embed-test.js
+++ b/tools/youtube-embed-test.js
@@ -40,13 +40,14 @@ await page.evaluate(([k, pk]) => {
{ id: 'f'.repeat(64), pubkey: pk, kind: 1, created_at: now - 120, content: 'a shorts one https://www.youtube.com/shorts/abc123XYZ_-', tags: [] },
{ id: '0'.repeat(64), pubkey: pk, kind: 1, created_at: now - 180, content: 'not a video https://example.com/watch?v=nope', tags: [] },
]));
+ localStorage.setItem(k + ':profiles', JSON.stringify({ [pk]: { name: 'Tuber', t: Date.now() } }));
+ localStorage.setItem('btc-wallet-feed:' + k, 'following'); // new wallets open on Popular
}, [base, A]);
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(); });
-await sleep(3500);
+await page.evaluate(() => document.querySelector('.app-nav-button[aria-label="Feed"]').click());
+for (let i = 0; i < 60 && !(await page.$('.notes-feed:not([data-booting]) > .row')); i++) await sleep(250);
+await sleep(1500);
let ok = true;
const check = (n, c, d = '') => { console.log(` ${c ? '✓' : '✗'} ${n}${d ? ' — ' + d : ''}`); if (!c) ok = false; };
diff --git a/tools/zap-deep-profile-test.js b/tools/zap-deep-profile-test.js
index 555b54c..55c7b5d 100644
--- a/tools/zap-deep-profile-test.js
+++ b/tools/zap-deep-profile-test.js
@@ -41,6 +41,8 @@ try {
const zapBtn = await until(() => page.$('.note-actions .zap, button.zap, [aria-label*="zap" i]'), 20000);
check('a post with a zap button', !!zapBtn);
if (zapBtn) {
+ // (to the middle of the screen: at the bottom edge the tap lands on the bottom nav)
+ await zapBtn.evaluate((b) => b.scrollIntoView({ block: 'center' }));
await zapBtn.click();
// whatever the zap ends in, it says so in a toast or on the zap screen
const said = await until(() => page.evaluate(() => {