aboutsummaryrefslogtreecommitdiffstats
path: root/tools/feed-stability-test.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-07 08:40:21 -0700
committerAdam Soltys <[email protected]>2026-10-07 08:40:21 -0700
commit3aff6e4cc9eb97fc0fa488f1f95001f69f24330a (patch)
tree932ed498851f9e2167a1c1419c96d630be0d02e2 /tools/feed-stability-test.js
parent6d288aeb194d2765282cf9fd72bd1f5fdd747a71 (diff)
parent6c51dcf2542314d5fe883f756f0840ef51860d67 (diff)
downloadcoinosv3-3aff6e4cc9eb97fc0fa488f1f95001f69f24330a.tar.gz
coinosv3-3aff6e4cc9eb97fc0fa488f1f95001f69f24330a.zip
Merge branch 'worktree-agent-ad1d843e9e394974e'
Diffstat (limited to 'tools/feed-stability-test.js')
-rw-r--r--tools/feed-stability-test.js53
1 files changed, 32 insertions, 21 deletions
diff --git a/tools/feed-stability-test.js b/tools/feed-stability-test.js
index 8b969f1..1426bba 100644
--- a/tools/feed-stability-test.js
+++ b/tools/feed-stability-test.js
@@ -83,6 +83,8 @@ await page.evaluateOnNewDocument((fixtures) => {
}, { profile, quote });
const click = (text) => page.evaluate((text) => [...document.querySelectorAll('button')].find((e) => e.textContent.toLowerCase().includes(text))?.click(), text);
const row = (id) => `.notes-feed > [data-key="${id}"]`;
+// the Feed is a tab in the bottom nav (it was a row on the chat home)
+const feedTab = () => [...document.querySelectorAll('.app-nav-button')].find((b) => b.getAttribute('aria-label') === 'Feed');
const snapshot = () => page.evaluate(() => [...document.querySelectorAll('.notes-feed > [data-zap-post]')].map((n) => {
const b = n.getBoundingClientRect();
return { id: n.dataset.key, top: b.top, height: b.height, text: n.innerText, avatar: n.querySelector('.note-avatar')?.getAttribute('style') };
@@ -103,15 +105,14 @@ try {
localStorage.setItem(base + ':follows', JSON.stringify({ tags: [['p', pk]], c: '', at: Math.floor(Date.now() / 1000) }));
localStorage.setItem(base + ':feedNotes', JSON.stringify(seeded));
localStorage.setItem(base + ':profiles', JSON.stringify({ [pk]: { name: 'Cached Author', picture: origin + '/avatar.png', t: Date.now() } }));
+ localStorage.setItem('btc-wallet-feed:' + base, 'following'); // a wallet with no feed remembered opens on Popular
}, [base, pk, seeded, origin]);
await page.reload();
await page.waitForFunction(() => document.body.innerText.toLowerCase().includes('receive'));
await sleep(2000);
check('a small feed query starts before opening the feed', await page.evaluate(() =>
- !document.querySelector('.notes-feed') && (window.__feedRequests || []).some((f) => f.kinds?.length === 1 && f.kinds[0] === 1 && f.limit === 10)));
- await page.evaluate(() => [...document.querySelectorAll('button')].find((e) => /message/i.test(e.getAttribute('aria-label') || ''))?.click());
- await page.waitForSelector('.item');
- await page.evaluate(() => [...document.querySelectorAll('.item')].find((e) => /feed/i.test(e.textContent))?.click());
+ !document.querySelector('.notes-feed') && (window.__feedRequests || []).some((f) => f.kinds?.includes(1) && !f.kinds.some((k) => k !== 1 && k !== 1068) && f.limit === 10)));
+ await page.evaluate((tab) => eval(tab)()?.click(), feedTab.toString());
await sleep(1000);
// Cached posts paint at once — never a spinner over posts we hold — and
// settle into their prepared presentation once faces and pictures are in.
@@ -127,21 +128,30 @@ try {
const settledRows = new Map((await snapshot()).map((r) => [r.id, r]));
check('late image responses do not change admitted rows', initial.every((r) => JSON.stringify(r) === JSON.stringify(settledRows.get(r.id))));
- // With the header visible, a prepend must wait: anchoring only the first
- // post would otherwise scroll the header offscreen.
- const headerBefore = await page.evaluate(() => ({ y: scrollY, text: document.querySelector('.chat-page > .row').innerText }));
+ // With the header visible, a new post goes in above the first post, which
+ // is held where it is (the page grows upward; the header scrolls off by
+ // what went in above it).
+ const firstBefore = await page.evaluate(() => { const r = document.querySelector('.notes-feed > [data-zap-post]'); return { id: r.dataset.key, top: r.getBoundingClientRect().top }; });
const fresh = event('A new live post', now + 1);
check('a live subscription received the event', await page.evaluate((e) => window.__inject(e), fresh) > 0);
- await sleep(1000);
- check('live arrival leaves the visible header and posts untouched', await page.evaluate((before) =>
- scrollY === before.y && document.querySelector('.chat-page > .row').innerText === before.text, headerBefore));
- check('new post waits until it can be inserted offscreen', !(await page.$(row(fresh.id))));
+ await page.waitForSelector(row(fresh.id), { timeout: 5000 }).catch(() => {});
+ check('live arrival at the top holds the first post still', await page.evaluate((b) => {
+ const r = document.querySelector('.notes-feed > [data-key="' + b.id + '"]');
+ return !!r && Math.abs(r.getBoundingClientRect().top - b.top) <= 1;
+ }, firstBefore));
+ check('...and goes in above it', await page.evaluate(([f, id]) => {
+ const a = document.querySelector('.notes-feed > [data-key="' + f + '"]'), b = document.querySelector('.notes-feed > [data-key="' + id + '"]');
+ return !!a && !!b && a.getBoundingClientRect().top < b.getBoundingClientRect().top;
+ }, [fresh.id, firstBefore.id]));
- // Once the reader scrolls the header away, preserve every visible row on
- // every animation frame, including the first frame of the prepend.
+ // Once the reader has scrolled the header away, preserve every visible row
+ // on every animation frame, including the first frame of the prepend.
await page.evaluate(() => {
- const first = document.querySelector('.notes-feed > [data-zap-post]');
+ const first = [...document.querySelectorAll('.notes-feed > [data-zap-post]')][1];
scrollBy(0, Math.ceil(first.getBoundingClientRect().top));
+ });
+ await sleep(500);
+ await page.evaluate(() => {
window.__drift = [];
window.__held = [...document.querySelectorAll('.notes-feed > [data-zap-post]')].filter((r) => {
const b = r.getBoundingClientRect(); return b.bottom > 0 && b.top < innerHeight;
@@ -152,11 +162,13 @@ try {
if (window.__sampling) requestAnimationFrame(frame);
}; requestAnimationFrame(frame);
});
- await page.waitForSelector(row(fresh.id));
+ const fresh2 = event('Another live post', now + 2);
+ check('a second live post is received', await page.evaluate((e) => window.__inject(e), fresh2) > 0);
+ await page.waitForSelector(row(fresh2.id));
await sleep(500);
const drift = await page.evaluate(() => { window.__sampling = false; return window.__drift; });
check('offscreen arrival never moves a visible row, even for one frame', !drift.length, JSON.stringify(drift.slice(0, 3)));
- check('new rows do not animate their height', await page.$eval(row(fresh.id), (n) => !n.getAnimations().length));
+ check('new rows do not animate their height', await page.$eval(row(fresh2.id), (n) => !n.getAnimations().length));
const gap = event('Gap inside visible posts', now - 630);
await page.evaluate((e) => {
@@ -206,12 +218,11 @@ try {
}, [base, seeded.filter((_, i) => ![1, 3, 8, 12].includes(i)).slice(0, 10)]);
await page.reload();
await sleep(5500);
- await page.evaluate(() => [...document.querySelectorAll('button')].find((e) => /message/i.test(e.getAttribute('aria-label') || ''))?.click());
- await page.waitForSelector('.item');
- check('first opening after background preparation has rows and no spinner', await page.evaluate(() => {
- [...document.querySelectorAll('.item')].find((e) => /feed/i.test(e.textContent))?.click();
+ await page.waitForSelector('.app-nav-button');
+ check('first opening after background preparation has rows and no spinner', await page.evaluate((tab) => {
+ eval(tab)()?.click();
return !!document.querySelector('.notes-feed > .row') && !document.querySelector('.chat-page .spinner');
- }));
+ }, feedTab.toString()));
check('no browser errors', !errors.length, errors.join(' | '));
} finally { await browser.close(); server.stop(true); }
process.exit(ok ? 0 : 1);