android: the phone turned sideways — the player beside itself, the chrome cut down
The phone UI was drawn portrait-first and had never been looked at in
landscape. The player sheet was the casualty: its cover is a square sized by
width, so at 915×411 it came out 879px tall and the title, the scrubber, the
transport and the face buttons all sat below the bottom of a sheet that does
not scroll — a corner of the artwork and no way to pause. Sideways the sheet
is two columns now, the cover sized by height beside the controls, with a
lyrics or queue face taking the cover's column; the nav and the mini bar come
down to 48 and 60dp, the artist hero becomes the banner the desktop's hero has
always been rather than 300px of a 411px screen, the record head puts the
cover beside the title, and the shell reads the side insets the cutout and the
gesture pill take when the phone is on its side. The walk turns the phone over
and back on its own: `{ view: [w, h] }` is a step kind now, and the steps
assert what used to be off the screen.
This commit is contained in:
1 parent
aff29aba5c
commit
9df162360e
5 files changed
+228
-3
No files matched your search
@@ -68,6 +68,11 @@ window.__settle = function () {
|
||||
/* what the page shows, read back after every still */
|
||||
const PROBE = `(function () {
|
||||
var e = document.querySelector('.screen');
|
||||
/* geometry, for the orientation steps: box() is the rect or null, and
|
||||
inside() asks whether every pixel of it is within the viewport —
|
||||
which, sideways, is the whole question */
|
||||
function box(sel) { var x = document.querySelector(sel); return x ? x.getBoundingClientRect() : null; }
|
||||
function inside(b) { return !!b && b.height > 0 && b.top >= -1 && b.bottom <= innerHeight + 1 && b.left >= -1 && b.right <= innerWidth + 1; }
|
||||
return {
|
||||
screen: e ? e.className : '',
|
||||
title: (document.querySelector('.gb-t') || {}).textContent || '',
|
||||
@@ -183,6 +188,27 @@ const PROBE = `(function () {
|
||||
halfSub: ((document.querySelector('.halfsheet .sheet-head .m') || {}).textContent || '').trim(),
|
||||
halfSw: [].filter.call(document.querySelectorAll('.halfsheet .row'), function (r) { return !!r.querySelector('.sw.on'); }).map(function (r) { return ((r.querySelector('.t') || {}).textContent || '').trim(); }).join('|'),
|
||||
contactsLoaded: (function () { try { return __ss.contacts.loaded ? 'yes' : (__ss.contacts.error ? 'err' : 'no'); } catch (e) { return ''; } })(),
|
||||
vp: innerWidth + 'x' + innerHeight,
|
||||
/* the sheet's controls: any one of them off the viewport is the landscape bug */
|
||||
npOut: [].filter.call(document.querySelectorAll('.sheet.up .np-frame, .sheet.up .np-meta, .sheet.up .np-scrub, .sheet.up .np-transport, .sheet.up .np-foot'), function (x) { var b = x.getBoundingClientRect(); return b.height > 0 && !inside(b); }).length,
|
||||
/* stacked = the portrait column; beside = the cover left of the controls; face = a face has the column and the thumbnail is gone */
|
||||
npCols: (function () { var a = box('.sheet.up .np-frame'), b = box('.sheet.up .np-transport'); if (!a || !b) return ''; if (!a.height) return 'face'; return a.bottom <= b.top + 1 ? 'stacked' : 'beside'; })(),
|
||||
faceH: Math.round((box('.sheet.up .np-face') || { height: 0 }).height),
|
||||
/* WHICH face: the queue's own, up whether or not anything is after this one */
|
||||
qFace: !!document.querySelector('.sheet.up .q-face'),
|
||||
/* a half sheet: how tall it is allowed to be, that it is all on screen, and that its body still reaches its last row */
|
||||
halfH: Math.round((box('.halfsheet.up') || { height: 0 }).height),
|
||||
halfIn: inside(box('.halfsheet.up')),
|
||||
hsScroll: (function () { var x = document.querySelector('.halfsheet.up .hs-body'); return x ? getComputedStyle(x).overflowY : ''; })(),
|
||||
hsTail: (function () { var x = document.querySelector('.halfsheet.up .hs-body'); return x ? x.scrollHeight - x.clientHeight : -1; })(),
|
||||
heroH: Math.round((box('.screen .hero') || { height: 0 }).height),
|
||||
/* what a page offers first: Play on an artist hero, Play on a record head */
|
||||
actsIn: inside(box('.screen .hero-acts') || box('.screen .rec-acts')),
|
||||
recCols: (function () { var c = box('.screen .rec-cover'), m = box('.screen .rec-meta'); return c && m ? (c.bottom <= m.top + 1 ? 'stacked' : 'beside') : ''; })(),
|
||||
/* the glass bar above plus the floating bars below, as a share of the screen */
|
||||
chromePct: (function () { var g = box('.gbar'), low = box('.mini') || box('.nav'); return Math.round((((g ? g.height : 0) + (low ? innerHeight - low.top : 0)) / innerHeight) * 100); })(),
|
||||
miniLeft: Math.round((box('.mini') || { left: -1 }).left),
|
||||
screenPadL: (function () { var x = document.querySelector('.screen'); return x ? Math.round(parseFloat(getComputedStyle(x).paddingLeft)) : -1; })(),
|
||||
};
|
||||
})()`;
|
||||
|
||||
@@ -542,6 +568,38 @@ const MOCK_STEPS = [
|
||||
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, name: "home-more", wait: 700 },
|
||||
{ do: `__click('.halfsheet .row', 'Sleep timer')`, name: "sleep-sheet", wait: 700 },
|
||||
{ do: `__click('.halfsheet .chip', '30 min')`, name: "sleep-set", wait: 700 },
|
||||
/* ── the phone turned over. A resize, so the app keeps playing and keeps
|
||||
its stack; what changes is the layout, and every one of these asserts
|
||||
something that was clipped or unreachable at 915x411 before. ────── */
|
||||
{ view: [915, 411], name: "landscape", wait: 900 },
|
||||
{ do: `document.querySelector('.mini').click()`, name: "landscape-sheet", wait: 1200 },
|
||||
{ do: `document.querySelectorAll('.fbtn')[1].click()`, name: "landscape-lyrics", wait: 1200 },
|
||||
{ do: `document.querySelectorAll('.fbtn')[2].click()`, name: "landscape-queue", wait: 900 },
|
||||
{ do: `document.querySelectorAll('.fbtn')[0].click()`, wait: 500 },
|
||||
{ do: `__ss.closeSheet()`, wait: 800 },
|
||||
/* a record sideways: the cover beside the title, Play in view without a
|
||||
scroll, and a long sheet over it — the track's, 473px of rows and
|
||||
facts — capped taller than portrait's 76% and still scrolling to its
|
||||
last row. (Home's own overflow sheet is 212px tall at either cap and
|
||||
would have proved nothing.) */
|
||||
{ do: `document.querySelectorAll('.nav button')[2].click()`, wait: 800 },
|
||||
{ do: `document.querySelectorAll('.segtabs button')[0].click()`, wait: 500 },
|
||||
{ do: `document.querySelectorAll('.grid2 .release')[2].click()`, name: "landscape-album", wait: 1100 },
|
||||
{ do: `document.querySelector('.trk .r').click()`, name: "landscape-half", wait: 900 },
|
||||
{ do: `document.querySelector('.scrim').click()`, wait: 700 },
|
||||
/* and the artist: the hero a banner rather than 300px of a 411px screen,
|
||||
so it stops covering its own Play button with the mini bar */
|
||||
{ do: `document.querySelector('.rec-artist').click()`, name: "landscape-artist", wait: 1400 },
|
||||
{ do: `document.querySelectorAll('.nav button')[0].click()`, wait: 800 },
|
||||
/* the side insets, faked: sideways the cutout and the gesture pill come
|
||||
in from the edges, and env() cannot be emulated here — the plumbing can */
|
||||
{ do: `document.documentElement.style.setProperty('--inset-left', '28px'); document.documentElement.style.setProperty('--inset-right', '28px'); true`, name: "landscape-insets", wait: 700 },
|
||||
{ do: `document.documentElement.style.removeProperty('--inset-left'); document.documentElement.style.removeProperty('--inset-right'); true`, wait: 500 },
|
||||
/* and back, which is also the override reset: portrait must be exactly
|
||||
what it was before any of this */
|
||||
{ view: [411, 914], wait: 800 },
|
||||
{ do: `document.querySelector('.mini').click()`, name: "portrait-again", wait: 1200 },
|
||||
{ do: `__ss.closeSheet()`, wait: 600 },
|
||||
];
|
||||
const MOCK_EXPECT = {
|
||||
tracks: (i) => i.title.startsWith("Your library"),
|
||||
@@ -588,6 +646,25 @@ const MOCK_EXPECT = {
|
||||
"search-recent-pick": (i) => i.title.startsWith("Search") && (i.songs >= 4 || !!i.top),
|
||||
"sleep-sheet": (i) => i.half.includes("up"),
|
||||
"sleep-set": (i) => !i.half.includes("up"),
|
||||
/* sideways: the chrome budget, then every surface that was clipped. The
|
||||
before-value of each is in the rig's now.json — 46% of the height as
|
||||
chrome, a 879px cover with the transport 687px below the fold, a
|
||||
lyrics face collapsed to nothing, a 300px hero, and the record's own
|
||||
Play button off the bottom of a 411px screen. */
|
||||
landscape: (i) => i.vp === "915x411" && i.mini && i.chromePct <= 42 && /^(Good |Still)/.test(i.title),
|
||||
"landscape-sheet": (i) => i.vp === "915x411" && i.sheet.includes("up") && i.npCols === "beside" && i.npOut === 0,
|
||||
"landscape-lyrics": (i) => i.sheet.includes("up") && i.npCols === "face" && i.faceH >= 240 && i.npOut === 0,
|
||||
/* the queue face by name, not by its rows: the track this walk is on is
|
||||
the LAST queueable row of Late night drive (the missing and the
|
||||
unresolved ones never reach the queue), so there is nothing up next
|
||||
and `qrows` is 0 here — the assertion is that the queue's own face has
|
||||
the cover's column, not that something is after this song */
|
||||
"landscape-queue": (i) => i.sheet.includes("up") && i.npCols === "face" && i.qFace && i.faceH >= 240 && i.npOut === 0,
|
||||
"landscape-album": (i) => i.screen.includes("tr-push") && i.recCols === "beside" && i.actsIn,
|
||||
"landscape-half": (i) => i.half.includes("up") && i.halfIn && i.halfH >= 340 && i.halfH <= 380 && i.hsScroll === "auto" && i.hsTail > 0,
|
||||
"landscape-artist": (i) => i.heroH > 0 && i.heroH <= 200 && i.actsIn,
|
||||
"landscape-insets": (i) => i.miniLeft >= 34 && i.screenPadL >= 26,
|
||||
"portrait-again": (i) => i.vp === "411x914" && i.sheet.includes("up") && i.npCols === "stacked" && i.npOut === 0 && i.chromePct <= 24,
|
||||
home: (i) => /^(Good |Still)/.test(i.title),
|
||||
"hold-sheet": (i) => i.half.includes("up") && /Re-fetch the cover/.test(i.halfText),
|
||||
"cover-refetched": (i) => !i.half.includes("up") && /Cover re-fetched/.test(i.notice),
|
||||
@@ -1088,6 +1165,21 @@ async function main() {
|
||||
if (s.adb) {
|
||||
try { adb(...s.adb); } catch (e) { problems.push("ADB " + (s.name || s.adb.join(" ")) + ": " + e.message); }
|
||||
}
|
||||
/* a mock step can turn the phone over: the viewport, through the
|
||||
same override the walk boots with. A real device's orientation
|
||||
is not the harness's to set, so a view step on the phone walk
|
||||
is a failure rather than a no-op. send() resolves with the
|
||||
whole reply and does not reject on a protocol error, so a
|
||||
refused override has to be read out of it. */
|
||||
if (s.view) {
|
||||
if (PHONE) problems.push("VIEW " + (s.name || s.view.join("x")) + ": the phone walk must not set the viewport");
|
||||
else {
|
||||
try {
|
||||
const r = await cdp.send("Emulation.setDeviceMetricsOverride", { width: s.view[0], height: s.view[1], deviceScaleFactor: 2, mobile: true });
|
||||
if (r.error) throw new Error(r.error.message || JSON.stringify(r.error));
|
||||
} catch (e) { problems.push("VIEW " + (s.name || s.view.join("x")) + ": " + e.message); }
|
||||
}
|
||||
}
|
||||
if (s.do) {
|
||||
try {
|
||||
/* the helpers live in the page; a reload step wipes them */
|
||||
|
||||
Reference in new issue
Block a user