Cover Now Playing returns after browsing in both layouts
This commit is contained in:
36
docs/now-playing-return-regression.md
Normal file
36
docs/now-playing-return-regression.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Now Playing list return regression
|
||||||
|
|
||||||
|
The shared browsing list retains the last page independently of the live playback
|
||||||
|
queue. Search home hides `#listPane`; the mini-player previously only removed
|
||||||
|
`home-over` and scrolled. It did not reset that hidden flag or replace the previous
|
||||||
|
page with the playing playlist. At 390 px the original code fails in both Classic
|
||||||
|
and Glass Stage with `#listPane` still hidden after tapping the mini-player.
|
||||||
|
The separate `#upnext` card remained intact in this reproduction.
|
||||||
|
|
||||||
|
Returning now rebuilds the source playlist and Up next. If its source playlist
|
||||||
|
was removed (or playback came from search/channel/history), the list displays the
|
||||||
|
live queue, without replacing it with the unrelated editable saved queue.
|
||||||
|
Mini-player, close, Player jump, user-picked track/deep-link reveal and browser
|
||||||
|
history returns share the restoration. Auto-advance still skips the reveal hook.
|
||||||
|
History entries retain browsing views, and Settings section entries remain owned
|
||||||
|
by Settings. Repeated restoration does not add duplicate player history entries.
|
||||||
|
|
||||||
|
Run `npx playwright test --config playwright.now-playing.config.js`.
|
||||||
|
The 20 cases cover both themes at 390/1440 px, return controls, Back/Forward,
|
||||||
|
missing source playlists, stale Up next DOM, repeated returns and an exhausted
|
||||||
|
queue. Synthetic tracks avoid dependence on YouTube decoding/network; the
|
||||||
|
track-reveal case exercises the shared post-load hook, rather than real media
|
||||||
|
loading. Headless Chromium exposes the mini-player via test CSS because it is
|
||||||
|
not an installed phone PWA. The Player jump is tested only at phone width,
|
||||||
|
where that navigation exists.
|
||||||
|
|
||||||
|
Validation: 115 frontend unit tests, app syntax check, server build and 20 browser
|
||||||
|
cases pass. Replaying the 390 px mini-player tests with the original app script
|
||||||
|
fails in both themes at the hidden list assertion; the patched script passes.
|
||||||
|
No server implementation or tests changed.
|
||||||
|
|
||||||
|
On a real iPhone, confirm the same browse/return sequence in Safari and the
|
||||||
|
installed PWA, plus Back/Forward and an actual shared video deep link. Confirm
|
||||||
|
playback keeps its position and the page scrolls to the player without shifting
|
||||||
|
the document viewport. Desktop checks cover both themes; no playback/background,
|
||||||
|
playlist action layout or device-export implementation was changed.
|
||||||
2
playwright.now-playing.config.js
Normal file
2
playwright.now-playing.config.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
const { defineConfig } = require('@playwright/test');
|
||||||
|
module.exports = defineConfig({ ...require('./playwright.classic.config'), testMatch: /now-playing-return\.spec\.js/ });
|
||||||
75
tests/now-playing-return.spec.js
Normal file
75
tests/now-playing-return.spec.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
const { test, expect } = require('@playwright/test');
|
||||||
|
const { openClassic, showPlayingFixture } = require('./helpers/classic-fixture');
|
||||||
|
|
||||||
|
for (const layout of ['classic', 'glass-stage']) {
|
||||||
|
for (const width of [390, 1440]) {
|
||||||
|
for (const route of ['mini player', 'close', 'player jump', 'browser back', 'track reveal'].filter(route => width === 390 || route !== 'player jump')) {
|
||||||
|
test(`${layout} ${width}: ${route} restores the playing list`, async ({ page }) => {
|
||||||
|
await page.setViewportSize({ width, height: 844 });
|
||||||
|
await openClassic(page, { populated: true, settings: { autoPreload: false } });
|
||||||
|
await showPlayingFixture(page);
|
||||||
|
await page.evaluate(layout => {
|
||||||
|
document.documentElement.dataset.layout = layout;
|
||||||
|
for (const type of ['search', 'history', 'saved', 'downloads', 'channel', 'settings', 'search']) {
|
||||||
|
view = { type };
|
||||||
|
if (type === 'search') setHomeOverPlayer(true);
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
showMiniBar();
|
||||||
|
}, layout);
|
||||||
|
// Chromium is not an installed phone PWA. Expose its mini-player
|
||||||
|
// chrome while exercising the actual click listeners (no forced click).
|
||||||
|
await page.addStyleTag({ content: '#miniBar { display:flex !important; }' });
|
||||||
|
if (route === 'close') await page.locator('#miniCloseBtn').click();
|
||||||
|
else if (route === 'track reveal') {
|
||||||
|
// User-picked tracks/deep links share this reveal hook after loading;
|
||||||
|
// avoid testing network/media decoding with synthetic fixture ids.
|
||||||
|
await page.evaluate(() => scrollPlayerIntoViewPortrait());
|
||||||
|
} else {
|
||||||
|
await page.locator('#miniTitle').click();
|
||||||
|
if (route === 'player jump') {
|
||||||
|
await page.evaluate(() => { view = { type: 'history' }; render(); });
|
||||||
|
await page.locator('#sectionRail [data-sec="player"]').click();
|
||||||
|
} else if (route === 'browser back') {
|
||||||
|
await page.evaluate(() => { view = { type: 'history' }; render(); });
|
||||||
|
await page.goBack();
|
||||||
|
await expect(page.locator('#listTitle')).toHaveText('Studio sessions');
|
||||||
|
await page.goBack();
|
||||||
|
await expect(page.locator('#playerPane')).toHaveClass(/home-over/);
|
||||||
|
await page.goForward();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await expect(page.locator('#upnext')).toBeVisible();
|
||||||
|
await expect(page.locator('#upnextList .upnext-item')).toHaveCount(11);
|
||||||
|
await expect(page.locator('#listPane')).toBeVisible();
|
||||||
|
await expect(page.locator('#listTitle')).toHaveText('Studio sessions');
|
||||||
|
await expect(page.locator('#cards .card')).toHaveCount(12);
|
||||||
|
expect(await page.evaluate(() => ({ source: queueSource, index: queueIndex, size: queue.length, id: current.meta.id })))
|
||||||
|
.toEqual({ source: 'playlist:studio', index: 0, size: 12, id: 'preview0000' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
test(`${layout}: missing source playlist falls back to the live queue`, async ({ page }) => {
|
||||||
|
await page.setViewportSize({ width: 390, height: 844 });
|
||||||
|
await openClassic(page, { populated: true, settings: { autoPreload: false } });
|
||||||
|
await showPlayingFixture(page);
|
||||||
|
await page.evaluate(layout => {
|
||||||
|
document.documentElement.dataset.layout = layout;
|
||||||
|
data.playlists = [];
|
||||||
|
data.queue = [];
|
||||||
|
view = { type: 'downloads' }; render();
|
||||||
|
scrollPlayerIntoViewPortrait();
|
||||||
|
}, layout);
|
||||||
|
await expect(page.locator('#listTitle')).toHaveText('Now playing');
|
||||||
|
await expect(page.locator('#cards .card')).toHaveCount(12);
|
||||||
|
await expect(page.locator('#upnextList .upnext-item')).toHaveCount(11);
|
||||||
|
const historyLength = await page.evaluate(() => history.length);
|
||||||
|
// Re-entry must repair stale DOM even when home-over is already false.
|
||||||
|
await page.evaluate(() => { document.getElementById('upnextList').innerHTML = ''; scrollPlayerIntoViewPortrait(); });
|
||||||
|
await expect(page.locator('#upnextList .upnext-item')).toHaveCount(11);
|
||||||
|
expect(await page.evaluate(() => history.length)).toBe(historyLength);
|
||||||
|
await page.evaluate(() => { queueIndex = queue.length - 1; scrollPlayerIntoViewPortrait(); });
|
||||||
|
await expect(page.locator('#upnext')).toBeHidden();
|
||||||
|
await expect(page.locator('#cards .card')).toHaveCount(12);
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user