fix(admin): the gallery starts no thumbnail status read while it leaves
The gallery's startup thumbnail check runs one second after load. When that second ended while the page was leaving (CI, WebKit: the workflow test navigates from the gallery to the admin page), WebKit failed the new read with "Fetch API cannot load ... due to access control checks." and Playwright reported a page error. Like the admin page (#290), the gallery now has a page lifetime that ends on beforeunload and pagehide (and starts again on a back/forward restore). The check starts no read once it has ended and passes it as the read's signal. A page-clock workflow test that leaves before the second ends failed on both events, in Chromium and WebKit, before this change.
This commit is contained in:
@@ -74,6 +74,24 @@ test('a page whose leave was called off reads its list again on the next input',
|
||||
await expect(page.locator('#resultsTitle')).toHaveText('Recent Prompts');
|
||||
});
|
||||
|
||||
// The gallery's startup thumbnail check runs a second after load. When that second ends while the page is leaving,
|
||||
// the check must start no read: WebKit fails it with "Fetch API cannot load … due to access control checks." and
|
||||
// Playwright reports a page error (seen in CI on PR #289). The page clock makes the second end after the leave began.
|
||||
for (const leave of ['beforeunload', 'pagehide']) {
|
||||
test(`the gallery startup check starts no read after ${leave}`, async ({ page }) => {
|
||||
await page.clock.install();
|
||||
const statusReads = [];
|
||||
page.on('request', req => { if (req.url().includes('/images/thumbnails/status')) statusReads.push(req.url()); });
|
||||
await page.goto('/prompt_manager/gallery');
|
||||
await expect(page.locator('#galleryGrid .image-item')).toHaveCount(2);
|
||||
await page.evaluate(type => window.dispatchEvent(new Event(type)), leave);
|
||||
await page.clock.runFor(1500);
|
||||
// A read made after the clock ran is listed, so the listener saw every status read.
|
||||
await page.evaluate(() => fetch('/prompt_manager/images/thumbnails/status?probe=1'));
|
||||
expect(statusReads.map(url => new URL(url).search)).toEqual(['?probe=1']);
|
||||
});
|
||||
}
|
||||
|
||||
test('actual gallery review generates and persists tags for the selected root', async ({ page, request }) => {
|
||||
await page.goto('/prompt_manager/gallery');
|
||||
await expect(page.locator('#galleryGrid .image-item')).toHaveCount(2);
|
||||
|
||||
@@ -73,6 +73,28 @@ test('Not Now persists the collection state and suppresses the unchanged prompt'
|
||||
assert.equal(f.prompts.length, 1);
|
||||
});
|
||||
|
||||
// WebKit fails a fetch a leaving page starts ("Fetch API cannot load … due to access control checks.") and logs it
|
||||
// as a page error: the delayed startup check starts no read once the page has begun to leave (as admin.js, #290).
|
||||
test('the startup check starts no read once the page is leaving', async () => {
|
||||
const f = fixture({ success: true, total_images: 100, pending: 30, collection_id: 'a' });
|
||||
f.manager.pageLifetime = new AbortController();
|
||||
f.manager.pageLifetime.abort();
|
||||
await f.check();
|
||||
assert.equal(f.requests.length, 0);
|
||||
assert.equal(f.prompts.length, 0);
|
||||
});
|
||||
|
||||
test('the startup check passes the page lifetime to its read', async () => {
|
||||
const f = fixture({ success: true, total_images: 100, pending: 30, collection_id: 'a' });
|
||||
const signals = [];
|
||||
f.manager.pageLifetime = new AbortController();
|
||||
const get = f.manager.api.get;
|
||||
f.manager.api.get = async (url, options) => { signals.push(options?.signal); return get(url); };
|
||||
await f.check();
|
||||
assert.deepEqual(signals, [f.manager.pageLifetime.signal]);
|
||||
assert.equal(f.prompts.length, 1);
|
||||
});
|
||||
|
||||
test('disabled checks do not request collection status', async () => {
|
||||
const f = fixture({}, { checkThumbnailsAtStartup: false });
|
||||
await f.check();
|
||||
|
||||
+12
-1
@@ -17,6 +17,14 @@
|
||||
this.dupResults = null;
|
||||
|
||||
this.api = ApiClient.createApiClient();
|
||||
// The page's lifetime ends when it starts to leave: WebKit fails a fetch a leaving page starts
|
||||
// ("Fetch API cannot load … due to access control checks.") and logs it as a page error.
|
||||
this.pageLifetime = new AbortController();
|
||||
window.addEventListener("beforeunload", () => this.pageLifetime.abort());
|
||||
window.addEventListener("pagehide", () => this.pageLifetime.abort());
|
||||
window.addEventListener("pageshow", (e) => {
|
||||
if (e.persisted && this.pageLifetime.signal.aborted) this.pageLifetime = new AbortController();
|
||||
});
|
||||
// Only the newest page request may update the grid
|
||||
this.imagesRequest = ApiClient.latestOnly();
|
||||
|
||||
@@ -2534,8 +2542,11 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
return;
|
||||
}
|
||||
|
||||
// A leaving page starts no read, and one it aborts is not reported.
|
||||
const signal = this.pageLifetime?.signal;
|
||||
if (signal?.aborted) return;
|
||||
try {
|
||||
const data = await this.api.get('/prompt_manager/images/thumbnails/status');
|
||||
const data = await this.api.get('/prompt_manager/images/thumbnails/status', { signal });
|
||||
const total = data.total_images;
|
||||
const pending = data.pending;
|
||||
if (!data.success || !Number.isSafeInteger(total) ||
|
||||
|
||||
Reference in New Issue
Block a user