mirror of
https://github.com/AdsPower/adspower-browser.git
synced 2026-09-19 03:08:18 +08:00
feat: 增加 e2e 用例执行器与清理保障
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll until `fn` returns a truthy value or attempts are exhausted (final-consistency window).
|
||||
*/
|
||||
export async function pollUntil<T>(
|
||||
fn: () => Promise<T | undefined | null | false>,
|
||||
options: { maxAttempts: number; delayMs: number },
|
||||
): Promise<T | undefined> {
|
||||
for (let i = 0; i < options.maxAttempts; i++) {
|
||||
const value = await fn();
|
||||
if (value !== undefined && value !== null && value !== false) {
|
||||
return value as T;
|
||||
}
|
||||
await sleep(options.delayMs);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
+36
-1
@@ -1,7 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { readE2EEnv } from './config/env';
|
||||
import { getOptionalFieldsFromSchema } from './config/schemaIntrospector';
|
||||
import { toolMatrix } from './config/toolMatrix';
|
||||
import { runCase } from './runner/caseRunner';
|
||||
import { createMcpClient } from './runner/mcpClient';
|
||||
|
||||
describe('readE2EEnv', () => {
|
||||
@@ -46,3 +47,37 @@ describe('toolMatrix', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('runCase cleanup', () => {
|
||||
it('always runs cleanup after assert', async () => {
|
||||
const cleanup = vi.fn(async () => {});
|
||||
|
||||
await runCase({
|
||||
name: 'x',
|
||||
prepare: async () => ({}),
|
||||
invoke: async () => ({}),
|
||||
assertState: async () => ({ passed: true, details: [] }),
|
||||
cleanup,
|
||||
});
|
||||
|
||||
expect(cleanup).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('runs cleanup when assertState throws', async () => {
|
||||
const cleanup = vi.fn(async () => {});
|
||||
|
||||
await expect(
|
||||
runCase({
|
||||
name: 'fail-assert',
|
||||
prepare: async () => ({ marker: 1 }),
|
||||
invoke: async () => ({}),
|
||||
assertState: async () => {
|
||||
throw new Error('assert boom');
|
||||
},
|
||||
cleanup,
|
||||
}),
|
||||
).rejects.toThrow(/assert boom/);
|
||||
|
||||
expect(cleanup).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { ResourceTracker } from './resourceTracker.js';
|
||||
|
||||
/**
|
||||
* Global/suite cleanup hook. Domain-specific delete logic is wired in later tasks.
|
||||
* Call at end of suite to attempt teardown of everything still in `tracker`.
|
||||
*/
|
||||
export async function runGlobalCleanup(_tracker: ResourceTracker): Promise<void> {
|
||||
// Intentionally empty until Task 5+ registers per-kind cleanup.
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Tracks created resource IDs per kind for suite teardown (Task 5+ will register handlers).
|
||||
*/
|
||||
export class ResourceTracker {
|
||||
private readonly byKind = new Map<string, Set<string>>();
|
||||
|
||||
track(kind: string, id: string): void {
|
||||
if (!this.byKind.has(kind)) {
|
||||
this.byKind.set(kind, new Set());
|
||||
}
|
||||
this.byKind.get(kind)!.add(id);
|
||||
}
|
||||
|
||||
untrack(kind: string, id: string): void {
|
||||
this.byKind.get(kind)?.delete(id);
|
||||
}
|
||||
|
||||
idsFor(kind: string): string[] {
|
||||
return [...(this.byKind.get(kind) ?? [])];
|
||||
}
|
||||
|
||||
kinds(): string[] {
|
||||
return [...this.byKind.keys()];
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.byKind.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
export interface RunCaseDefinition {
|
||||
name: string;
|
||||
prepare: () => Promise<Record<string, unknown>>;
|
||||
invoke: (ctx: Record<string, unknown>) => Promise<unknown>;
|
||||
assertState: (
|
||||
result: unknown,
|
||||
ctx: Record<string, unknown>,
|
||||
) => Promise<{ passed: boolean; details: string[] }>;
|
||||
cleanup: (ctx: Record<string, unknown>) => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Single case pipeline: prepare → invoke → assert → cleanup (cleanup always runs).
|
||||
*/
|
||||
export async function runCase(
|
||||
def: RunCaseDefinition,
|
||||
): Promise<{ passed: boolean; details: string[] }> {
|
||||
const ctx = await def.prepare();
|
||||
try {
|
||||
const invokeResult = await def.invoke(ctx);
|
||||
return await def.assertState(invokeResult, ctx);
|
||||
} finally {
|
||||
await def.cleanup(ctx);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user