@anzusystems/common-admin 2.0.0-rc.7 → 2.0.0-rc.8
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/package.json +2 -2
- package/src/e2e/routeWalk.d.mts +2 -1
- package/src/e2e/routeWalk.mjs +37 -26
package/package.json
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"./package.json": "./package.json"
|
|
42
42
|
},
|
|
43
|
-
"version": "2.0.0-rc.
|
|
43
|
+
"version": "2.0.0-rc.8",
|
|
44
44
|
"license": "Apache-2.0",
|
|
45
45
|
"repository": {
|
|
46
46
|
"type": "git",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"release:cleanup": "npx -y --allow-git=all github:anzusystems/release-tools#main cleanup"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
|
-
"@anzusystems/common-admin": "2.0.0-rc.
|
|
83
|
+
"@anzusystems/common-admin": "2.0.0-rc.8",
|
|
84
84
|
"@intlify/unplugin-vue-i18n": "^11.2.8",
|
|
85
85
|
"@mdi/font": "^7.4.47",
|
|
86
86
|
"@sentry/vite-plugin": "11.10.0",
|
package/src/e2e/routeWalk.d.mts
CHANGED
|
@@ -38,6 +38,7 @@ export declare const visitRoute: VisitRoute
|
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* The first record a listing shows: from a link to it, else by clicking the first row and reading where the
|
|
41
|
-
* app went.
|
|
41
|
+
* app went. An answer the listing gives every time (a record, no record, not a page) is kept in a file in
|
|
42
|
+
* `outputDir`, so a record's detail and edit share one visit of the listing; one a failed or empty load gave is not.
|
|
42
43
|
*/
|
|
43
44
|
export declare const firstRecordOf: FirstRecordOf
|
package/src/e2e/routeWalk.mjs
CHANGED
|
@@ -348,8 +348,9 @@ const unresolvedComponents = (page) =>
|
|
|
348
348
|
|
|
349
349
|
/**
|
|
350
350
|
* Opens the route on a fresh document. `about:blank` first, so that what the previous page still logs or
|
|
351
|
-
* requests while it unloads is not blamed on this one. An app that comes up blank
|
|
352
|
-
* boot can hit a transient failure that the next
|
|
351
|
+
* requests while it unloads is not blamed on this one. An app that comes up blank, without a `main` or with
|
|
352
|
+
* only the start-up loader's empty one, is reloaded once: a cold boot can hit a transient failure that the next
|
|
353
|
+
* one does not. A page that stays empty after that is reported as one.
|
|
353
354
|
*
|
|
354
355
|
* @param {Page} page
|
|
355
356
|
* @param {PageWatcher} watcher
|
|
@@ -360,14 +361,13 @@ const openRoute = async (page, watcher, target, timings) => {
|
|
|
360
361
|
await page.goto('about:blank')
|
|
361
362
|
watcher.reset(timings)
|
|
362
363
|
await page.goto(target)
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
}
|
|
364
|
+
const first = await settle(page, watcher, timings).catch((/** @type {Error} */ error) => error)
|
|
365
|
+
if (typeof first === 'boolean' && (await pageState(page))?.hasText) return first
|
|
366
|
+
// A loader that never ends on a page that rendered is the page's failure, not the boot's.
|
|
367
|
+
if (first instanceof Error && (await page.locator('main').count()) > 0) throw first
|
|
368
|
+
watcher.reset(timings)
|
|
369
|
+
await page.reload()
|
|
370
|
+
return settle(page, watcher, timings)
|
|
371
371
|
}
|
|
372
372
|
|
|
373
373
|
/** @type {import('./types.mjs').VisitRoute} */
|
|
@@ -405,12 +405,19 @@ export async function firstRecordOf(
|
|
|
405
405
|
const file = recordFile(outputDir, listing, pattern)
|
|
406
406
|
if (fs.existsSync(file)) return JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
407
407
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
await
|
|
413
|
-
|
|
408
|
+
// `keep`: an answer the listing gives every time. One that a failed load or a page still loading gave is not kept,
|
|
409
|
+
// so the routes after it look again instead of all failing on what went wrong once.
|
|
410
|
+
/** @type {{ lookup: RecordLookup; keep: boolean }} */
|
|
411
|
+
const { lookup, keep } = await (async () => {
|
|
412
|
+
const opened = await openRoute(page, watcher, listing, withDefaults(timings)).then(
|
|
413
|
+
() => undefined,
|
|
414
|
+
(/** @type {Error} */ error) => error
|
|
415
|
+
)
|
|
416
|
+
if (opened) return { lookup: { missing: `${listing} did not load: ${opened.message}` }, keep: false }
|
|
417
|
+
if ((await page.locator(NOT_FOUND_SELECTOR).count()) > 0) {
|
|
418
|
+
return { lookup: { missing: `${listing} is not a page` }, keep: true }
|
|
419
|
+
}
|
|
420
|
+
if (!(await pageState(page))?.hasText) return { lookup: { missing: `${listing} came up empty` }, keep: false }
|
|
414
421
|
|
|
415
422
|
const fromLink = await page.evaluate(
|
|
416
423
|
({ prefix, source }) => {
|
|
@@ -421,15 +428,15 @@ export async function firstRecordOf(
|
|
|
421
428
|
},
|
|
422
429
|
{ prefix: listing, source: pattern.source }
|
|
423
430
|
)
|
|
424
|
-
if (fromLink !== undefined) return { id: fromLink }
|
|
431
|
+
if (fromLink !== undefined) return { lookup: { id: fromLink }, keep: true }
|
|
425
432
|
|
|
426
433
|
const firstRow = page.locator(rowSelector).first()
|
|
427
434
|
if ((await firstRow.count()) === 0) {
|
|
428
435
|
// Empty only when the table says so: a row selector that matches nothing must not pass for no data.
|
|
429
436
|
if (await page.locator(NO_DATA_SELECTOR).first().isVisible()) {
|
|
430
|
-
return { missing: `${listing} shows no record`, empty: /** @type {const} */ (true) }
|
|
437
|
+
return { lookup: { missing: `${listing} shows no record`, empty: /** @type {const} */ (true) }, keep: true }
|
|
431
438
|
}
|
|
432
|
-
return { missing: `nothing in ${listing} matches the row selector` }
|
|
439
|
+
return { lookup: { missing: `nothing in ${listing} matches the row selector` }, keep: false }
|
|
433
440
|
}
|
|
434
441
|
// The corner of the first cell, inside its padding: a cell can hold a link to another record
|
|
435
442
|
// (a site group, a super author), and a click on it would open that one instead.
|
|
@@ -439,15 +446,19 @@ export async function firstRecordOf(
|
|
|
439
446
|
.click({ position: { x: 3, y: 3 } })
|
|
440
447
|
await page.waitForURL((url) => url.pathname.startsWith(`${listing}/`), { timeout: 10_000 }).catch(() => {})
|
|
441
448
|
const candidate = new URL(page.url()).pathname.slice(listing.length + 1).split('/')[0] ?? ''
|
|
442
|
-
if (!pattern.test(candidate))
|
|
449
|
+
if (!pattern.test(candidate)) {
|
|
450
|
+
return { lookup: { missing: `clicking the first row of ${listing} opened ${page.url()}` }, keep: false }
|
|
451
|
+
}
|
|
443
452
|
|
|
444
|
-
return { id: candidate }
|
|
453
|
+
return { lookup: { id: candidate }, keep: true }
|
|
445
454
|
})()
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
455
|
+
if (keep) {
|
|
456
|
+
// Written aside and renamed: another worker reading meanwhile sees the whole file or none.
|
|
457
|
+
fs.mkdirSync(path.dirname(file), { recursive: true })
|
|
458
|
+
const written = `${file}.${process.pid}.tmp`
|
|
459
|
+
fs.writeFileSync(written, JSON.stringify(lookup))
|
|
460
|
+
fs.renameSync(written, file)
|
|
461
|
+
}
|
|
451
462
|
|
|
452
463
|
return lookup
|
|
453
464
|
}
|