feat(e2e): add smart test caching and error toast catching
- results-db.ts: TestPassCache with consecutivePasses counter, recordTestResult(), shouldSkip(threshold=3), resetCache() - wdio.conf.js: afterTest hook catches unexpected .toast.error/.load-error elements, records results to smart cache. FULL_RESCAN=1 bypasses caching
This commit is contained in:
parent
0803dc3844
commit
46f51d7941
2 changed files with 120 additions and 3 deletions
|
|
@ -33,9 +33,16 @@ export interface TestStepRow {
|
|||
created_at: string;
|
||||
}
|
||||
|
||||
export interface TestPassCache {
|
||||
testKey: string;
|
||||
consecutivePasses: number;
|
||||
lastPassedAt: string;
|
||||
}
|
||||
|
||||
interface ResultsStore {
|
||||
runs: TestRunRow[];
|
||||
steps: TestStepRow[];
|
||||
passCache: TestPassCache[];
|
||||
}
|
||||
|
||||
export class ResultsDb {
|
||||
|
|
@ -51,12 +58,13 @@ export class ResultsDb {
|
|||
private load(): ResultsStore {
|
||||
if (existsSync(this.filePath)) {
|
||||
try {
|
||||
return JSON.parse(readFileSync(this.filePath, 'utf-8'));
|
||||
const data = JSON.parse(readFileSync(this.filePath, 'utf-8'));
|
||||
return { runs: data.runs ?? [], steps: data.steps ?? [], passCache: data.passCache ?? [] };
|
||||
} catch {
|
||||
return { runs: [], steps: [] };
|
||||
return { runs: [], steps: [], passCache: [] };
|
||||
}
|
||||
}
|
||||
return { runs: [], steps: [] };
|
||||
return { runs: [], steps: [], passCache: [] };
|
||||
}
|
||||
|
||||
private save(): void {
|
||||
|
|
@ -110,4 +118,61 @@ export class ResultsDb {
|
|||
getStepsForRun(runId: string): TestStepRow[] {
|
||||
return this.store.steps.filter(s => s.run_id === runId);
|
||||
}
|
||||
|
||||
// ── Pass Cache ──
|
||||
|
||||
private static makeTestKey(specFile: string, testTitle: string): string {
|
||||
return `${specFile}::${testTitle}`;
|
||||
}
|
||||
|
||||
recordTestResult(specFile: string, testTitle: string, passed: boolean): void {
|
||||
const key = ResultsDb.makeTestKey(specFile, testTitle);
|
||||
const entry = this.store.passCache.find(e => e.testKey === key);
|
||||
|
||||
if (passed) {
|
||||
if (entry) {
|
||||
entry.consecutivePasses += 1;
|
||||
entry.lastPassedAt = new Date().toISOString();
|
||||
} else {
|
||||
this.store.passCache.push({
|
||||
testKey: key,
|
||||
consecutivePasses: 1,
|
||||
lastPassedAt: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (entry) {
|
||||
entry.consecutivePasses = 0;
|
||||
}
|
||||
}
|
||||
this.save();
|
||||
}
|
||||
|
||||
shouldSkip(specFile: string, testTitle: string, threshold = 3): boolean {
|
||||
const key = ResultsDb.makeTestKey(specFile, testTitle);
|
||||
const entry = this.store.passCache.find(e => e.testKey === key);
|
||||
return entry !== undefined && entry.consecutivePasses >= threshold;
|
||||
}
|
||||
|
||||
resetCache(): void {
|
||||
this.store.passCache = [];
|
||||
this.save();
|
||||
}
|
||||
|
||||
getCacheStats(threshold = 3): { total: number; skippable: number; threshold: number } {
|
||||
const total = this.store.passCache.length;
|
||||
const skippable = this.store.passCache.filter(e => e.consecutivePasses >= threshold).length;
|
||||
return { total, skippable, threshold };
|
||||
}
|
||||
}
|
||||
|
||||
// ── Lazy singleton for use in wdio hooks ──
|
||||
|
||||
let _singleton: ResultsDb | null = null;
|
||||
|
||||
export function getResultsDb(): ResultsDb {
|
||||
if (!_singleton) {
|
||||
_singleton = new ResultsDb();
|
||||
}
|
||||
return _singleton;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue