diff --git a/tests/mobile/smoke.mjs b/tests/mobile/smoke.mjs index e7a34a5..329087a 100644 --- a/tests/mobile/smoke.mjs +++ b/tests/mobile/smoke.mjs @@ -721,15 +721,17 @@ const MOCK_STEPS = [ { do: `history.back()`, wait: 800 }, { do: `document.querySelector('.cat-song').click()`, name: "net-playing", wait: 5500 }, { do: `document.querySelector('.mini').click()`, name: "net-sheet", wait: 3000 }, - { name: "net-arrived", wait: 7000 }, + /* the mock hands the finished file over on its own timer, with a still + page in front of it the whole way — nothing here can shorten this */ + { name: "net-arrived", wait: 7000, hold: true }, /* the now-playing sheet's ⋯: the streamed track saved into the library (its cache is complete by now, so it files at once) */ { do: `document.querySelectorAll('.sheet .np-head .iconbtn')[1].click()`, name: "np-more", wait: 700 }, /* the same cache, copied out as a plain file into a picked folder — nothing downloaded again, no library row */ - { do: `__click('.halfsheet .row', 'Copy to a folder')`, name: "np-copied", wait: 900 }, + { do: `__click('.halfsheet .row', 'Copy to a folder')`, name: "np-copied", wait: 900, hold: true }, { do: `document.querySelectorAll('.sheet .np-head .iconbtn')[1].click()`, wait: 700 }, - { do: `__click('.halfsheet .row', 'Save to library')`, name: "np-saved", wait: 900 }, + { do: `__click('.halfsheet .row', 'Save to library')`, name: "np-saved", wait: 900, hold: true }, /* the raw peers: a peer's sheet → their shares browsed → a folder opened → one of its files streams */ { do: `__settle()`, wait: 800 }, @@ -2774,7 +2776,19 @@ async function main() { var t = document.querySelector('.toast'); var ss = window.__ss || {}; var pl = ss.player || {}, rs = ss.resolver || {}, nt = ss.net || {}, lb = ss.lib || {}; - return [ + /* a catalog lookup never touches any of those: it sits in + catalog.artists[name].status until it turns 'ready', and + the artist page is waiting on exactly that. */ + var ct = ss.catalog || {}, looking = 0; + ['artists', 'albums', 'extras', 'profiles'].forEach(function (k) { + var m = ct[k] || {}; + for (var n in m) { if (m[n] && m[n].status === 'loading') looking++; } + }); + /* what is still running, as its own answer. A search that + has sent nothing back yet holds every counter still, so a + settled-looking page is not the same as a settled one. */ + var busy = (rs.active || nt.searching || looking > 0) ? 1 : 0; + return busy + '|' + [ document.querySelectorAll('*').length, document.images.length, (document.querySelector('.screen') || {}).className || '', @@ -2784,32 +2798,46 @@ async function main() { document.title, pl.status, pl.phase, Math.round(pl.position || 0), (pl.queue || []).length, pl.qi, - rs.active ? 1 : 0, nt.busy ? 1 : 0, lb.loading ? 1 : 0, + rs.active ? 1 : 0, nt.searching ? 1 : 0, looking, + nt.responses || 0, nt.files || 0, (nt.groups || []).length, + lb.loaded ? 1 : 0, (lb.albums || []).length, (ss.transfers && ss.transfers.items ? ss.transfers.items.length : 0) ].join('|'); - } catch (e) { return 'unreadable'; } + } catch (e) { return '1|unreadable'; } })()`; /* two identical readings 90ms apart, and nothing has moved */ const STABLE_READINGS = 2; const POLL_MS = 90; const FLOOR = 0.34; + /* Off unless asked for. The declared waits are what round 42 was + verified on, and what every expectation was written against; the + short ones are an optimisation that has not earned the default + yet. SS_ADAPTIVE=1 turns them on — and turning it on and off is + the A/B this was missing. */ + const ADAPTIVE = process.env.SS_ADAPTIVE === "1"; let waited = 0, saved = 0; /* what the walk actually spent, and what it did not */ async function settle(want, step) { waited += want; - if (step && step.hold) { await sleep(want); return; } + if (!ADAPTIVE || (step && step.hold)) { await sleep(want); return; } const began = Date.now(); await sleep(Math.min(want, Math.max(200, Math.round(want * FLOOR)))); let last = null, stable = 0; while (Date.now() - began < want) { let fp; try { fp = await cdp.evaluate(FINGERPRINT); } catch { break; } - if (fp === last) { if (++stable >= STABLE_READINGS) break; } else { stable = 0; last = fp; } + /* the leading field is the busy flag: while anything the + app admits to is still running, a still page does not + count as a settled one. The declared wait is still the + ceiling, so this can only ever spend what it was given. */ + const working = typeof fp === 'string' && fp.charAt(0) === '1'; + if (fp === last && !working) { if (++stable >= STABLE_READINGS) break; } else { stable = 0; last = fp; } const left = want - (Date.now() - began); if (left <= 0) break; await sleep(Math.min(POLL_MS, left)); } saved += Math.max(0, want - (Date.now() - began)); } + console.log(ADAPTIVE ? "adaptive waits: on (SS_ADAPTIVE=1)" : "adaptive waits: off — sleeping every declared wait"); for (const s of steps) { /* a phone step can be an adb command — an intent, a key */ let adbOut = "";