@jbrowse/capture 5.0.0-beta.8 → 5.0.0-beta.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -25
- package/esm/args.d.ts +2 -1
- package/esm/args.js +2 -1
- package/esm/bin.js +52 -29
- package/esm/browser.d.ts +0 -1
- package/esm/browser.js +5 -9
- package/esm/capture.d.ts +9 -8
- package/esm/capture.js +41 -57
- package/esm/catalog.d.ts +40 -0
- package/esm/catalog.js +103 -0
- package/esm/hub.d.ts +10 -0
- package/esm/hub.js +31 -16
- package/esm/imagePath.d.ts +1 -0
- package/esm/imagePath.js +8 -0
- package/esm/index.d.ts +8 -8
- package/esm/index.js +7 -7
- package/esm/instanceVersion.d.ts +8 -0
- package/esm/instanceVersion.js +24 -0
- package/esm/poll.d.ts +13 -0
- package/esm/poll.js +29 -0
- package/esm/ready.d.ts +20 -9
- package/esm/ready.js +37 -27
- package/esm/session.d.ts +13 -5
- package/esm/session.js +31 -12
- package/esm/sessionGate.d.ts +52 -43
- package/esm/sessionGate.js +82 -83
- package/esm/sessionOverflow.d.ts +11 -0
- package/esm/sessionOverflow.js +24 -0
- package/esm/url.d.ts +16 -6
- package/esm/url.js +34 -17
- package/esm/waits.d.ts +13 -16
- package/esm/waits.js +49 -81
- package/package.json +3 -3
package/esm/waits.js
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
// screenshot generator so the helper isn't redefined per consumer.
|
|
4
|
-
export const delay = (ms) => new Promise(resolve => {
|
|
5
|
-
setTimeout(resolve, ms);
|
|
6
|
-
});
|
|
1
|
+
import { holdTrue } from './poll.js';
|
|
2
|
+
import { describePendingDisplays, pendingDisplayStatesInPage, tooLargeDisplaysInPage, } from './sessionGate.js';
|
|
7
3
|
// Every best-effort wait below runs through this. They swallow their own timeout
|
|
8
4
|
// on purpose — a slow-but-finishing page should not be failed for being slow,
|
|
9
5
|
// and a display in a terminal state publishes no attribute to wait on at all —
|
|
@@ -37,6 +33,14 @@ export const LOADING_OVERLAY = '[data-testid="loading-overlay"]';
|
|
|
37
33
|
* capture taken in it is a picture of an empty browser.
|
|
38
34
|
*/
|
|
39
35
|
export const APP_READY = '[data-app-phase="ready"]';
|
|
36
|
+
/**
|
|
37
|
+
* A display drawing a transition between two settled pictures: canvas's row
|
|
38
|
+
* morph, a multi-way lane re-aligning. The app is finished working and every
|
|
39
|
+
* display reads `ready`, so neither the marker nor the phase can say so; the
|
|
40
|
+
* display publishes it itself, and a frame taken while it reads `true` is a
|
|
41
|
+
* picture no settled state ever shows.
|
|
42
|
+
*/
|
|
43
|
+
export const ANIMATING_DISPLAYS = '[data-display-animating="true"]';
|
|
40
44
|
/**
|
|
41
45
|
* How long `ready` has to HOLD before an interaction's work counts as finished.
|
|
42
46
|
*
|
|
@@ -123,11 +127,8 @@ export const PENDING_DISPLAYS = '[data-display-drawn="false"]';
|
|
|
123
127
|
* finished badly and is showing a banner, and `ready` is a display claiming it
|
|
124
128
|
* finished without drawing — a bug in the display rather than in the wait.
|
|
125
129
|
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* publish neither attribute and appear here as nothing at all: a wait on such a
|
|
129
|
-
* display can never resolve and can never be reported. Nothing pending means
|
|
130
|
-
* either that, or a selector that was never a display's.
|
|
130
|
+
* A display showing "too much data" is not pending, and a wait on its body
|
|
131
|
+
* can never resolve, so the census names those too.
|
|
131
132
|
*/
|
|
132
133
|
export async function waitForSelectorAttributed(page, selector, timeoutMs) {
|
|
133
134
|
try {
|
|
@@ -148,9 +149,18 @@ export async function waitForSelectorAttributed(page, selector, timeoutMs) {
|
|
|
148
149
|
export async function describePendingDisplaysNow(page) {
|
|
149
150
|
try {
|
|
150
151
|
const pending = await page.evaluate(pendingDisplayStatesInPage);
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
const tooLarge = await page.evaluate(tooLargeDisplaysInPage);
|
|
153
|
+
const found = [
|
|
154
|
+
pending.length
|
|
155
|
+
? `${pending.length} display(s) had not painted: ${describePendingDisplays(pending)}`
|
|
156
|
+
: undefined,
|
|
157
|
+
tooLarge.length
|
|
158
|
+
? `${tooLarge.length} display(s) show "too much data" in place of their features: ${describePendingDisplays(tooLarge)}`
|
|
159
|
+
: undefined,
|
|
160
|
+
].filter(Boolean);
|
|
161
|
+
return found.length
|
|
162
|
+
? found.join('; ')
|
|
163
|
+
: 'no display reported itself unpainted, so the selector was not on a display';
|
|
154
164
|
}
|
|
155
165
|
catch {
|
|
156
166
|
return 'the page could not be queried afterwards (context gone)';
|
|
@@ -176,8 +186,6 @@ export const displayPainted = (testid) => `[data-testid="${testid}"][data-displa
|
|
|
176
186
|
* on the phase instead.
|
|
177
187
|
*/
|
|
178
188
|
export const displaySettled = (testid) => `[data-testid="${testid}"][data-display-phase="ready"]`;
|
|
179
|
-
/** One display, by its config's `displayId` rather than by type. */
|
|
180
|
-
export const displayById = (displayId) => `[data-display-id="${displayId}"]`;
|
|
181
189
|
// Wait until no display is still pending its first paint AND could still reach
|
|
182
190
|
// it: a pending display publishing `loading`, or publishing no phase at all (a
|
|
183
191
|
// build older than the attribute), is still coming; a terminal phase is not. The
|
|
@@ -210,10 +218,8 @@ export function waitForDisplaysDone(page, timeoutMs) {
|
|
|
210
218
|
// DisplayPhase, whose `loading` term covers the entire fetch, so "nothing is
|
|
211
219
|
// loading" is a direct read rather than an inference.
|
|
212
220
|
//
|
|
213
|
-
// Terminal phases (`tooLarge`, `renderError`)
|
|
214
|
-
//
|
|
215
|
-
// are finished, not pending. They're caught as *content* by the caller's own
|
|
216
|
-
// settled check, not by this wait.
|
|
221
|
+
// Terminal phases (`tooLarge`, `renderError`) resolve immediately here, since
|
|
222
|
+
// they are finished, not pending. The caller's census reports them.
|
|
217
223
|
//
|
|
218
224
|
// Best-effort like its neighbours: a display that never leaves `loading` should
|
|
219
225
|
// fail loudly through that settled check, with the frame to look at, rather than
|
|
@@ -221,26 +227,25 @@ export function waitForDisplaysDone(page, timeoutMs) {
|
|
|
221
227
|
export function waitForDisplayPhases(page, timeoutMs) {
|
|
222
228
|
return settled(page.waitForFunction(() => document.querySelector('[data-display-phase="loading"]') === null, { timeout: timeoutMs, polling: 'mutation' }));
|
|
223
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* A view whose MODEL is initialized but whose lazily-imported React component
|
|
232
|
+
* has not arrived: ViewWrapper's Suspense spinner. The app marker cannot see
|
|
233
|
+
* it, since the model it reads is finished, so a view clicked into existence
|
|
234
|
+
* reads `ready` for as long as its chunk takes to load.
|
|
235
|
+
*/
|
|
236
|
+
const VIEW_COMPONENT_PENDING = '[data-view-component-pending]';
|
|
224
237
|
// The view-level counterpart: ViewContainer publishes `data-view-phase` from the
|
|
225
238
|
// view model's own phase, and `loading` means the view is still waiting on its
|
|
226
239
|
// assembly (or on init's navigation) and has mounted no displays at all. Every
|
|
227
240
|
// display-level wait above is silent in that state — there is nothing to be
|
|
228
241
|
// loading yet — so a capture taken then lands on a bare spinner.
|
|
229
242
|
//
|
|
230
|
-
// The `data-view-component-pending` half is the case the phase attribute cannot
|
|
231
|
-
// report: a view whose MODEL is initialized but whose lazily-imported React
|
|
232
|
-
// component has not arrived, so ViewContainer publishes a non-loading phase over
|
|
233
|
-
// a body that is still ViewWrapper's Suspense spinner. A session loaded at page
|
|
234
|
-
// load has usually won that race by the time anything else settles; a view
|
|
235
|
-
// CLICKED into existence fetches its chunk only then, and its frame is the one a
|
|
236
|
-
// launch-dialog figure is about.
|
|
237
|
-
//
|
|
238
243
|
// NOT best-effort, unlike its neighbours: a view that never leaves `loading` has
|
|
239
244
|
// no content to fall through to, so the timeout IS the diagnosis and the caller
|
|
240
245
|
// should surface it.
|
|
241
246
|
export function waitForViewPhases(page, timeoutMs) {
|
|
242
|
-
return page.waitForFunction(() => document.querySelector('[data-view-phase="loading"]') === null &&
|
|
243
|
-
document.querySelector(
|
|
247
|
+
return page.waitForFunction((pending) => document.querySelector('[data-view-phase="loading"]') === null &&
|
|
248
|
+
document.querySelector(pending) === null, { timeout: timeoutMs, polling: 'mutation' }, VIEW_COMPONENT_PENDING);
|
|
244
249
|
}
|
|
245
250
|
/**
|
|
246
251
|
* Everything the app publishes to say it is working, as one selector.
|
|
@@ -255,12 +260,14 @@ export function waitForViewPhases(page, timeoutMs) {
|
|
|
255
260
|
* wherever it tells a user it is working
|
|
256
261
|
* `data-display-phase` one display's own fetch, newer builds
|
|
257
262
|
* `data-view-phase` a view still resolving its assembly, newer builds
|
|
263
|
+
* `data-display-animating` a display drawing a morph, newer builds
|
|
258
264
|
*/
|
|
259
265
|
export const BUSY_SELECTOR = [
|
|
260
266
|
LOADING_OVERLAY,
|
|
261
267
|
'[data-busy="true"]',
|
|
262
268
|
'[data-display-phase="loading"]',
|
|
263
269
|
'[data-view-phase="loading"]',
|
|
270
|
+
ANIMATING_DISPLAYS,
|
|
264
271
|
].join(', ');
|
|
265
272
|
/**
|
|
266
273
|
* Is the app doing anything right now?
|
|
@@ -294,33 +301,12 @@ export function isPageBusyInPage(busySelector) {
|
|
|
294
301
|
* "nothing is in flight right now", which is what a probe or a benchmark wants
|
|
295
302
|
* between two measured actions.
|
|
296
303
|
*
|
|
297
|
-
* Polled from node rather than in-page: chrome throttles in-page timers and rAF
|
|
298
|
-
* once the tab is not visible, which is the state a headless capture sits in.
|
|
299
|
-
*
|
|
300
304
|
* Returns false on timeout rather than throwing, like its neighbours.
|
|
301
305
|
*/
|
|
302
|
-
export
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
// A page that navigates or closes under us fails the evaluate; treat that
|
|
307
|
-
// as busy and let the deadline decide, rather than reporting quiet.
|
|
308
|
-
const busy = await page
|
|
309
|
-
.evaluate(isPageBusyInPage, BUSY_SELECTOR)
|
|
310
|
-
.catch(() => true);
|
|
311
|
-
const now = Date.now();
|
|
312
|
-
if (busy) {
|
|
313
|
-
quietSince = undefined;
|
|
314
|
-
}
|
|
315
|
-
else {
|
|
316
|
-
quietSince ??= now;
|
|
317
|
-
if (now - quietSince >= quietMs) {
|
|
318
|
-
return true;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
await delay(pollMs);
|
|
322
|
-
}
|
|
323
|
-
return false;
|
|
306
|
+
export function waitForQuietPeriod(page, { quietMs = 1500, timeout = 30000, pollMs = 250, } = {}) {
|
|
307
|
+
// a page that navigates or closes under us fails the evaluate, which reads
|
|
308
|
+
// as busy and leaves the deadline to decide
|
|
309
|
+
return holdTrue(() => page.evaluate(isPageBusyInPage, BUSY_SELECTOR).then(busy => !busy, () => false), { holdMs: quietMs, timeout, pollMs });
|
|
324
310
|
}
|
|
325
311
|
/**
|
|
326
312
|
* Wait for the FIRST frame the app says it has finished.
|
|
@@ -336,8 +322,7 @@ export async function waitForQuietPeriod(page, { quietMs = 1500, timeout = 30000
|
|
|
336
322
|
export function waitForAppReady(page, { timeout = 30000 } = {}) {
|
|
337
323
|
return settled(page.waitForSelector(APP_READY, { timeout, visible: false }));
|
|
338
324
|
}
|
|
339
|
-
|
|
340
|
-
export function hasAppReadyMarker(page) {
|
|
325
|
+
function hasAppReadyMarker(page) {
|
|
341
326
|
return page.evaluate(() => document.querySelector('[data-app-phase]') !== null);
|
|
342
327
|
}
|
|
343
328
|
/**
|
|
@@ -364,9 +349,8 @@ export function hasAppReadyMarker(page) {
|
|
|
364
349
|
* replaced. Requiring the idle to HOLD costs the hold and no more, and catches
|
|
365
350
|
* the same late-starting work.
|
|
366
351
|
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
* is not visible, which is exactly the state a headless capture sits in.
|
|
352
|
+
* The hold also requires no view body to be waiting on its lazy component and
|
|
353
|
+
* no display to be animating, neither of which the marker can see.
|
|
370
354
|
*
|
|
371
355
|
* Throws on a build too old for the marker rather than falling back — see the
|
|
372
356
|
* body.
|
|
@@ -382,25 +366,9 @@ export async function waitForAppSettled(page, { timeout = 30000, holdMs = APP_SE
|
|
|
382
366
|
'to wait for — every other readiness attribute is an absence an app ' +
|
|
383
367
|
'that has not started also satisfies.');
|
|
384
368
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
const ready = await page
|
|
391
|
-
.evaluate(selector => document.querySelector(selector) !== null, APP_READY)
|
|
392
|
-
.catch(() => false);
|
|
393
|
-
const now = Date.now();
|
|
394
|
-
if (ready) {
|
|
395
|
-
readySince ??= now;
|
|
396
|
-
if (now - readySince >= holdMs) {
|
|
397
|
-
return true;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
else {
|
|
401
|
-
readySince = undefined;
|
|
402
|
-
}
|
|
403
|
-
await delay(pollMs);
|
|
404
|
-
}
|
|
405
|
-
return false;
|
|
369
|
+
return holdTrue(() => page
|
|
370
|
+
.evaluate((ready, pending, animating) => document.querySelector(ready) !== null &&
|
|
371
|
+
document.querySelector(pending) === null &&
|
|
372
|
+
document.querySelector(animating) === null, APP_READY, VIEW_COMPONENT_PENDING, ANIMATING_DISPLAYS)
|
|
373
|
+
.catch(() => false), { holdMs, timeout, pollMs });
|
|
406
374
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/capture",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.9",
|
|
4
4
|
"description": "Drive a JBrowse 2 instance with Puppeteer and screenshot it once it has actually finished rendering",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jbrowse",
|
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
],
|
|
32
32
|
"sideEffects": false,
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"puppeteer": "^25.
|
|
34
|
+
"puppeteer": "^25.11.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@jbrowse/core": "^5.0.0-beta.
|
|
37
|
+
"@jbrowse/core": "^5.0.0-beta.9"
|
|
38
38
|
},
|
|
39
39
|
"publishConfig": {
|
|
40
40
|
"access": "public"
|