mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 14:55:16 +00:00
The SavedFilters incident shape — a page.goto fired while the previous heavy RSC navigation was still in flight (net::ERR_ABORTED) — gets a build-level guard: two gotos with no settle between them (await expect / waitForURL / click / reload / login / catch) fail the suite. Measured baseline on the current tree: 58 gotos across 17 specs, 0 unsettled pairs — the six SOFT pairs from the miss audit all settle on awaited assertions, so the guard starts clean without churning them. TESTING.md records the convention the miss asked for: goto for cold loads and URL contracts; in-shell navigation for continuity scenarios; interactive-state waits before dynamic clicks; a dev-mode caveat for the campaignLeaderships chip spec (deterministic dev failure at the suggestion/commit step, prod green — ledgered).
86 lines
3 KiB
TypeScript
86 lines
3 KiB
TypeScript
import { readdirSync, readFileSync } from 'node:fs'
|
|
import { relative, resolve } from 'node:path'
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
// Miss #54 (2026-07-30): a `page.goto` fired while the previous heavy RSC
|
|
// navigation was still in flight aborted with `net::ERR_ABORTED`
|
|
// (campaignSavedFilters, prod build). `goto` stays legal for cold loads and
|
|
// URL contracts — the anti-pattern is two gotos with NOTHING between them
|
|
// that settles the first navigation. A settle is any awaited interaction
|
|
// with the landed page (assertion, fill, click, reload, login…): it proves
|
|
// the previous route rendered before the next one starts.
|
|
//
|
|
// Miss #53 (2026-07-30): the biometrics probe is one-shot per island mount,
|
|
// so a ceremony that starts before the CDP virtual authenticator answers
|
|
// never sees the enrollment UI. Specs registering a virtual authenticator
|
|
// must gate on `expectCampaignBiometricsReady` first.
|
|
|
|
const e2eRoot = resolve(process.cwd(), 'tests/e2e')
|
|
|
|
const specFiles = readdirSync(e2eRoot, { recursive: true, withFileTypes: true })
|
|
.filter((entry) => entry.isFile() && entry.name.endsWith('.e2e.spec.ts'))
|
|
.map((entry) => resolve(entry.parentPath, entry.name))
|
|
|
|
const SETTLE_TOKENS = [
|
|
'await expect(',
|
|
'waitForURL(',
|
|
'.click(',
|
|
'.check(',
|
|
'.fill(',
|
|
'.press(',
|
|
'.hover(',
|
|
'clearCookies(',
|
|
'.reload(',
|
|
'campaign.login(',
|
|
'.catch(',
|
|
'await request.',
|
|
'checkRadixWhenHydrated(',
|
|
'expectCampaignBiometricsReady(',
|
|
] as const
|
|
|
|
describe('e2e navigation discipline (miss #54)', () => {
|
|
it('never fires two page.goto without settling the first navigation', () => {
|
|
const offenders: string[] = []
|
|
|
|
for (const file of specFiles) {
|
|
const lines = readFileSync(file, 'utf8').split('\n')
|
|
let lastGoto: number | null = null
|
|
let settledSince = false
|
|
|
|
for (const [index, line] of lines.entries()) {
|
|
if (SETTLE_TOKENS.some((token) => line.includes(token))) settledSince = true
|
|
if (!line.includes('page.goto(')) continue
|
|
if (lastGoto !== null && !settledSince) {
|
|
offenders.push(
|
|
`${relative(process.cwd(), file)}:${index + 1} (goto at ${lastGoto + 1} unsettled)`,
|
|
)
|
|
}
|
|
lastGoto = index
|
|
settledSince = false
|
|
}
|
|
}
|
|
|
|
expect(
|
|
offenders,
|
|
'settle the first navigation (await expect/waitForURL/click…) before the next page.goto, or navigate in-shell (miss #54)',
|
|
).toEqual([])
|
|
})
|
|
})
|
|
|
|
describe('e2e WebAuthn virtual authenticator (miss #53)', () => {
|
|
it('gates every virtual-authenticator spec on expectCampaignBiometricsReady', () => {
|
|
const offenders: string[] = []
|
|
|
|
for (const file of specFiles) {
|
|
const source = readFileSync(file, 'utf8')
|
|
if (!source.includes('WebAuthn.addVirtualAuthenticator')) continue
|
|
if (source.includes('expectCampaignBiometricsReady(')) continue
|
|
offenders.push(relative(process.cwd(), file))
|
|
}
|
|
|
|
expect(
|
|
offenders,
|
|
'call expectCampaignBiometricsReady(page) before the surface whose island probes the authenticator',
|
|
).toEqual([])
|
|
})
|
|
})
|