633 lines
21 KiB
TypeScript
633 lines
21 KiB
TypeScript
/** Shared prototype contracts. Browser handles never come from model output. */
|
|
export type BrowserAction = "click" | "fill";
|
|
|
|
export interface Candidate {
|
|
id: string;
|
|
role: string;
|
|
name: string;
|
|
actions: BrowserAction[];
|
|
}
|
|
|
|
export interface Observation {
|
|
observationId: string;
|
|
url: string;
|
|
title: string;
|
|
candidates: Candidate[];
|
|
}
|
|
|
|
export type Postcondition =
|
|
| { kind: "url"; value: string }
|
|
| { kind: "text"; value: string };
|
|
|
|
export interface ActRequest {
|
|
observationId: string;
|
|
candidateId: string;
|
|
action: BrowserAction;
|
|
text?: string;
|
|
/** Mandatory for clicks. Filling always verifies the actual field value. */
|
|
postcondition?: Postcondition;
|
|
}
|
|
|
|
export interface ActResult {
|
|
verified: boolean;
|
|
evidence: string;
|
|
}
|
|
|
|
export interface BrowserBridge {
|
|
observe(signal: AbortSignal): Promise<Observation>;
|
|
/** Must revalidate the snapshot's tab/document/target immediately before mutation,
|
|
* consume all old observations, and verify the outcome. Never retry a mutation. */
|
|
act(request: ActRequest, signal: AbortSignal): Promise<ActResult>;
|
|
/** Must terminate owned workers before releasing the caller's browser lane. */
|
|
close(): Promise<void>;
|
|
}
|
|
|
|
export interface Step {
|
|
instruction: string;
|
|
action: BrowserAction;
|
|
/** A literal is inserted exactly; compose asks the auxiliary model for new text. */
|
|
text?: string;
|
|
compose?: string;
|
|
/** Reference to a credential supplied by the initiating session; never sent to JEV. */
|
|
credential?: string;
|
|
postcondition?: Postcondition;
|
|
}
|
|
|
|
export interface Decision {
|
|
candidateId: string | null;
|
|
confidence: number;
|
|
model: string;
|
|
}
|
|
|
|
export interface DecisionInput {
|
|
instruction: string;
|
|
action: BrowserAction;
|
|
observation: Observation;
|
|
}
|
|
|
|
export type CredentialSource = string | { env: string } | { file: string; key?: string };
|
|
export type CredentialBindings = Record<string, CredentialSource>;
|
|
|
|
export interface StepTrace {
|
|
step: number;
|
|
status: "verified" | "abstained" | "unverified" | "error";
|
|
timingsMs: Record<string, number>;
|
|
/** Always emitted by the controller; unknown forbids automatic retry. */
|
|
mutationState?: "not_attempted" | "verified" | "unknown";
|
|
candidateId?: string;
|
|
confidence?: number;
|
|
model?: string;
|
|
evidence?: string;
|
|
}
|
|
|
|
export interface RunResult {
|
|
status: "completed" | "stopped";
|
|
reason: string;
|
|
steps: StepTrace[];
|
|
elapsedMs: number;
|
|
/** True when a dispatched action lacks a verified outcome. Never blindly retry. */
|
|
actionMayHaveExecuted?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Development contracts for the session-scoped runtime. The prototype
|
|
* BrowserBridge above remains the compatibility boundary for observe/act/close;
|
|
* new workers should implement BrowserRuntime rather than adding ad-hoc CDP
|
|
* methods to BrowserBridge.
|
|
*/
|
|
export type MutationState = "not_attempted" | "verified" | "unknown";
|
|
|
|
export type BrowserErrorCode =
|
|
| "aborted"
|
|
| "timeout"
|
|
| "stale"
|
|
| "policy_denied"
|
|
| "invalid_request"
|
|
| "no_tab"
|
|
| "ambiguous_tab"
|
|
| "browser_closed"
|
|
| "unsupported"
|
|
| "worker_failure";
|
|
|
|
/** Safe to serialize to a report. Never include a URL query/fragment, headers,
|
|
* bodies, field values, page text, provider response, or raw exception. */
|
|
export interface BrowserErrorDetails {
|
|
code: BrowserErrorCode;
|
|
operation: BrowserRuntimeOperationName;
|
|
phase: string;
|
|
message: string;
|
|
mutationState: MutationState;
|
|
retry: "never" | "safe";
|
|
}
|
|
|
|
export class BrowserOperationError extends Error {
|
|
readonly details: BrowserErrorDetails;
|
|
override name = "BrowserOperationError";
|
|
|
|
constructor(details: BrowserErrorDetails) {
|
|
super(details.message);
|
|
this.details = details;
|
|
}
|
|
}
|
|
|
|
/** Fixed budgets: implementations may choose a lower internal deadline, but
|
|
* no caller-supplied generic timeout may exceed these ceilings. */
|
|
export const FAST_BROWSER_BUDGETS = {
|
|
browserStartupMs: { default: 10_000, hardCeiling: 10_000 },
|
|
workerReadyMs: { default: 5_000, hardCeiling: 5_000 },
|
|
bridgeRequestMs: { default: 10_000, hardCeiling: 120_000 },
|
|
actionVerificationMs: { default: 3_000, hardCeiling: 3_000 },
|
|
readAssertionWaitMs: { default: 3_000, hardCeiling: 3_000 },
|
|
navigationMs: { default: 10_000, hardCeiling: 10_000 },
|
|
reloadMs: { default: 10_000, hardCeiling: 10_000 },
|
|
popupWaitMs: { default: 5_000, hardCeiling: 5_000 },
|
|
screenshotMs: { default: 10_000, hardCeiling: 60_000 },
|
|
failureEvidenceMs: { default: 2_000, hardCeiling: 2_000 },
|
|
runMs: { default: 60_000, hardCeiling: 120_000 },
|
|
} as const;
|
|
|
|
/** The session base policy is the existing '*' default or a non-empty exact
|
|
* origin list. A runtime policy request may only narrow that base. */
|
|
export type RuntimeOriginPolicy = "*" | readonly string[];
|
|
|
|
/** Per-operation policy context. It is derived from the immutable session base
|
|
* and is not a persistent cross-call mutation. Omit it to use the base. */
|
|
export interface RuntimeOperationContext {
|
|
allowedOrigins: RuntimeOriginPolicy;
|
|
}
|
|
|
|
/** Opaque and stable for the life of a target; never model-created. */
|
|
export type BrowserTabId = string & { readonly __fastBrowserTabId: unique symbol };
|
|
|
|
export interface BrowserTab {
|
|
tabId: BrowserTabId;
|
|
kind: "page" | "popup";
|
|
openerTabId?: BrowserTabId;
|
|
selected: boolean;
|
|
owned: true;
|
|
/** Internal runtime value; public reports must reduce this to origin. */
|
|
url: string;
|
|
origin: string;
|
|
title: string;
|
|
}
|
|
|
|
export interface ReadRequest {
|
|
tabId?: BrowserTabId;
|
|
}
|
|
|
|
/** Read returns rendered visible text only. It never returns form values,
|
|
* password values, hidden text, HTML, frames, or arbitrary script output. */
|
|
export interface PageRead {
|
|
tab: BrowserTab;
|
|
documentId: string;
|
|
url: string;
|
|
title: string;
|
|
visibleText: string;
|
|
visibleTextTruncated: boolean;
|
|
}
|
|
|
|
export type PageAssertion =
|
|
| { kind: "url"; match: "exact" | "origin"; value: string }
|
|
| { kind: "title"; match: "exact" | "contains"; value: string }
|
|
| { kind: "visibleText"; match: "contains"; value: string };
|
|
|
|
export interface AssertRequest {
|
|
tabId?: BrowserTabId;
|
|
assertion: PageAssertion;
|
|
/** "bounded" uses the fixed readAssertionWaitMs budget and performs no
|
|
* mutation; "none" checks one read immediately. */
|
|
wait: "none" | "bounded";
|
|
}
|
|
|
|
export interface AssertResult {
|
|
status: "satisfied" | "not_satisfied" | "timed_out";
|
|
readOnly: true;
|
|
tab: BrowserTab;
|
|
documentId: string;
|
|
}
|
|
|
|
export interface NavigateRequest {
|
|
tabId?: BrowserTabId;
|
|
/** HTTP(S), without credentials; destination origin must be approved. */
|
|
url: string;
|
|
}
|
|
|
|
export interface NavigationResult {
|
|
status: "committed";
|
|
mutationState: "verified";
|
|
tab: BrowserTab;
|
|
documentId: string;
|
|
}
|
|
|
|
export interface ReloadRequest {
|
|
tabId?: BrowserTabId;
|
|
}
|
|
|
|
export interface ReloadResult {
|
|
status: "committed";
|
|
mutationState: "verified";
|
|
tab: BrowserTab;
|
|
documentId: string;
|
|
}
|
|
|
|
export interface SelectTabRequest {
|
|
tabId: BrowserTabId;
|
|
}
|
|
|
|
export interface SelectTabResult {
|
|
selected: true;
|
|
tab: BrowserTab;
|
|
}
|
|
|
|
export interface WaitPopupRequest {
|
|
openerTabId: BrowserTabId;
|
|
}
|
|
|
|
export interface WaitPopupResult {
|
|
status: "found" | "timed_out";
|
|
/** A found popup is returned, but is never selected automatically. */
|
|
tab?: BrowserTab;
|
|
autoSwitched: false;
|
|
}
|
|
|
|
export interface CloseTabRequest {
|
|
tabId: BrowserTabId;
|
|
}
|
|
|
|
export interface CloseTabResult {
|
|
closed: true;
|
|
tabId: BrowserTabId;
|
|
mutationState: "verified";
|
|
}
|
|
|
|
export interface NarrowOriginPolicyRequest {
|
|
allowedOrigins: RuntimeOriginPolicy;
|
|
}
|
|
|
|
export interface ConsoleFailureSummary {
|
|
level: "error" | "warning";
|
|
message: string;
|
|
count: number;
|
|
truncated: boolean;
|
|
}
|
|
|
|
/** Network evidence is deliberately origin-only: no query/fragment, headers,
|
|
* cookies, request/response bodies, or field values. */
|
|
export interface NetworkFailureSummary {
|
|
method: string;
|
|
origin: string;
|
|
status?: number;
|
|
category: "http" | "network" | "blocked";
|
|
}
|
|
|
|
export type PrivateArtifactKind = "screenshot" | "diagnostics";
|
|
|
|
export interface PrivateArtifactOwner {
|
|
sessionId: string;
|
|
runId: string;
|
|
operationId: string;
|
|
}
|
|
|
|
export interface PrivateArtifactMetadata {
|
|
artifactId: string;
|
|
kind: PrivateArtifactKind;
|
|
contentType: "image/png" | "application/json";
|
|
path: string;
|
|
bytes: number;
|
|
owner: PrivateArtifactOwner;
|
|
permissions: "0600";
|
|
localOnly: true;
|
|
modelForwarded: false;
|
|
}
|
|
|
|
/** No caller-selected path: the store owns the path and commits atomically. */
|
|
export interface PrivateArtifactWriter {
|
|
readonly owner: PrivateArtifactOwner;
|
|
readonly kind: PrivateArtifactKind;
|
|
write(chunk: Uint8Array): Promise<void>;
|
|
commit(): Promise<PrivateArtifactMetadata>;
|
|
abort(): Promise<void>;
|
|
}
|
|
|
|
export interface PrivateArtifactStore {
|
|
open(owner: PrivateArtifactOwner, kind: PrivateArtifactKind): Promise<PrivateArtifactWriter>;
|
|
close(): Promise<void>;
|
|
}
|
|
|
|
/** JSONL-safe worker-to-parent bytes. The parent decodes dataBase64 and writes
|
|
* it through PrivateArtifactStore; worker requests never contain caller paths. */
|
|
export interface BrowserTransportArtifactPayload {
|
|
kind: PrivateArtifactKind;
|
|
contentType: "image/png" | "application/json";
|
|
encoding: "base64";
|
|
dataBase64: string;
|
|
byteLength: number;
|
|
truncated: boolean;
|
|
}
|
|
|
|
export interface BrowserTransportEvidence {
|
|
status: "complete" | "partial" | "unavailable";
|
|
screenshot?: BrowserTransportArtifactPayload;
|
|
diagnostics?: BrowserTransportArtifactPayload;
|
|
console: readonly ConsoleFailureSummary[];
|
|
failedRequests: readonly NetworkFailureSummary[];
|
|
}
|
|
|
|
export const FAST_BROWSER_ARTIFACT_LIMITS = {
|
|
screenshotBytes: 8 * 1024 * 1024,
|
|
diagnosticsBytes: 256 * 1024,
|
|
} as const;
|
|
|
|
export const BROWSER_EVIDENCE_OPERATIONS = [
|
|
"observe", "act", "read", "assert", "navigate", "reload", "listTabs",
|
|
"selectTab", "waitPopup", "closeTab", "captureEvidence", "narrowOriginPolicy",
|
|
] as const;
|
|
export type BrowserEvidenceOperationName = (typeof BROWSER_EVIDENCE_OPERATIONS)[number];
|
|
|
|
export interface EvidenceRequest {
|
|
/** Caller/scenario run identity; the runtime creates the operation identity. */
|
|
runId: string;
|
|
reason: "failure" | "requested";
|
|
operation?: BrowserEvidenceOperationName;
|
|
}
|
|
|
|
export interface FailureEvidence {
|
|
status: "complete" | "partial" | "unavailable";
|
|
screenshot?: PrivateArtifactMetadata;
|
|
diagnostics?: PrivateArtifactMetadata;
|
|
console: readonly ConsoleFailureSummary[];
|
|
failedRequests: readonly NetworkFailureSummary[];
|
|
}
|
|
|
|
/** A persistent per-session bridge. Different sessions may run in parallel;
|
|
* all operations on one runtime are serialized. Tab handles/document identity
|
|
* survive calls until the target/document is closed or navigated. */
|
|
export interface BrowserRuntime extends BrowserBridge {
|
|
readonly sessionId: string;
|
|
/** Computes one effective request policy; it never permanently tightens the
|
|
* immutable session base. Pass the returned context to one or more calls in
|
|
* the same explicitly scoped operation, or omit context to restore base. */
|
|
narrowOriginPolicy(request: NarrowOriginPolicyRequest, signal: AbortSignal): Promise<RuntimeOperationContext>;
|
|
observe(signal: AbortSignal, context?: RuntimeOperationContext): Promise<Observation>;
|
|
act(request: ActRequest, signal: AbortSignal, context?: RuntimeOperationContext): Promise<ActResult>;
|
|
read(request: ReadRequest, signal: AbortSignal, context?: RuntimeOperationContext): Promise<PageRead>;
|
|
assert(request: AssertRequest, signal: AbortSignal, context?: RuntimeOperationContext): Promise<AssertResult>;
|
|
navigate(request: NavigateRequest, signal: AbortSignal, context?: RuntimeOperationContext): Promise<NavigationResult>;
|
|
reload(request: ReloadRequest, signal: AbortSignal, context?: RuntimeOperationContext): Promise<ReloadResult>;
|
|
listTabs(signal: AbortSignal, context?: RuntimeOperationContext): Promise<readonly BrowserTab[]>;
|
|
selectTab(request: SelectTabRequest, signal: AbortSignal, context?: RuntimeOperationContext): Promise<SelectTabResult>;
|
|
waitPopup(request: WaitPopupRequest, signal: AbortSignal, context?: RuntimeOperationContext): Promise<WaitPopupResult>;
|
|
closeTab(request: CloseTabRequest, signal: AbortSignal, context?: RuntimeOperationContext): Promise<CloseTabResult>;
|
|
captureEvidence(request: EvidenceRequest, signal: AbortSignal, context?: RuntimeOperationContext): Promise<FailureEvidence>;
|
|
}
|
|
|
|
export interface BrowserRuntimeOptions {
|
|
sessionId: string;
|
|
cdpUrl: string;
|
|
allowedOrigins: RuntimeOriginPolicy;
|
|
/** Explicit interpreter selection for the pinned worker environment. */
|
|
python?: string;
|
|
artifacts: PrivateArtifactStore;
|
|
/** Test/embedding seam; production may use the default transport factory. */
|
|
transportFactory?: BrowserTransportFactory;
|
|
}
|
|
|
|
export interface BrowserRuntimeFactory {
|
|
create(options: BrowserRuntimeOptions, parentSignal?: AbortSignal): BrowserRuntime;
|
|
}
|
|
|
|
/** The transport owns JSONL framing, validation, deadlines, and worker
|
|
* teardown. BrowserRuntime owns persistent tab/policy state and delegates one
|
|
* typed operation at a time; neither layer implements the other's boundary. */
|
|
export interface BrowserTransportRequest<Operation extends BrowserOperation = BrowserOperation> {
|
|
operation: Operation;
|
|
context?: RuntimeOperationContext;
|
|
}
|
|
|
|
export interface BrowserTransportCloseResult {
|
|
closed: true;
|
|
}
|
|
|
|
/** Exact result mapping for every BrowserOperation variant. */
|
|
export type BrowserTransportResultFor<Operation extends BrowserOperation> =
|
|
Operation extends { op: "observe" } ? { op: "observe"; result: Observation } :
|
|
Operation extends { op: "act" } ? { op: "act"; result: ActResult } :
|
|
Operation extends { op: "read" } ? { op: "read"; result: PageRead } :
|
|
Operation extends { op: "assert" } ? { op: "assert"; result: AssertResult } :
|
|
Operation extends { op: "navigate" } ? { op: "navigate"; result: NavigationResult } :
|
|
Operation extends { op: "reload" } ? { op: "reload"; result: ReloadResult } :
|
|
Operation extends { op: "listTabs" } ? { op: "listTabs"; result: readonly BrowserTab[] } :
|
|
Operation extends { op: "selectTab" } ? { op: "selectTab"; result: SelectTabResult } :
|
|
Operation extends { op: "waitPopup" } ? { op: "waitPopup"; result: WaitPopupResult } :
|
|
Operation extends { op: "closeTab" } ? { op: "closeTab"; result: CloseTabResult } :
|
|
Operation extends { op: "captureEvidence" } ? { op: "captureEvidence"; result: BrowserTransportEvidence } :
|
|
Operation extends { op: "narrowOriginPolicy" } ? { op: "narrowOriginPolicy"; result: RuntimeOperationContext } :
|
|
Operation extends { op: "close" } ? { op: "close"; result: BrowserTransportCloseResult } :
|
|
never;
|
|
|
|
export type BrowserTransportResult = BrowserTransportResultFor<BrowserOperation>;
|
|
|
|
export interface BrowserTransport {
|
|
dispatch<Operation extends BrowserOperation>(
|
|
request: BrowserTransportRequest<Operation>,
|
|
signal: AbortSignal,
|
|
): Promise<BrowserTransportResultFor<Operation>>;
|
|
close(): Promise<void>;
|
|
}
|
|
|
|
export interface BrowserTransportOptions {
|
|
cdpUrl: string;
|
|
allowedOrigins: RuntimeOriginPolicy;
|
|
python?: string;
|
|
}
|
|
|
|
export interface BrowserTransportFactory {
|
|
create(options: BrowserTransportOptions, parentSignal?: AbortSignal): BrowserTransport;
|
|
}
|
|
|
|
export const BROWSER_RUNTIME_OPERATIONS = [
|
|
"observe", "act", "read", "assert", "navigate", "reload", "listTabs",
|
|
"selectTab", "waitPopup", "closeTab", "captureEvidence", "narrowOriginPolicy", "close",
|
|
] as const;
|
|
export type BrowserRuntimeOperationName = (typeof BROWSER_RUNTIME_OPERATIONS)[number];
|
|
|
|
/** Explicit JSONL/CLI operation shapes. There is no arbitrary CDP or JS arm. */
|
|
export type BrowserOperation =
|
|
| { op: "observe" }
|
|
| { op: "act"; request: ActRequest }
|
|
| { op: "read"; request: ReadRequest }
|
|
| { op: "assert"; request: AssertRequest }
|
|
| { op: "navigate"; request: NavigateRequest }
|
|
| { op: "reload"; request: ReloadRequest }
|
|
| { op: "listTabs" }
|
|
| { op: "selectTab"; request: SelectTabRequest }
|
|
| { op: "waitPopup"; request: WaitPopupRequest }
|
|
| { op: "closeTab"; request: CloseTabRequest }
|
|
| { op: "captureEvidence"; request: EvidenceRequest }
|
|
| { op: "narrowOriginPolicy"; request: NarrowOriginPolicyRequest }
|
|
| { op: "close" };
|
|
|
|
/** Scenario references are data-only handles resolved by the CLI runner.
|
|
* They cannot contain selectors, expressions, templates, CDP IDs, or JS. */
|
|
/** Scenario files are bounded to 32 operations by the CLI owner. */
|
|
export type ScenarioOperationIndex =
|
|
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
|
|
| 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15
|
|
| 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23
|
|
| 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31;
|
|
|
|
export type ScenarioOperationRef =
|
|
| { index: ScenarioOperationIndex }
|
|
| { id: string };
|
|
|
|
/** Tab list indices are intentionally finite and bounded for JSON scenarios. */
|
|
export type ScenarioTabIndex = ScenarioOperationIndex;
|
|
|
|
/** Initial means the runtime's selected tab at scenario start. Operation
|
|
* references must point to an earlier operation; the runner rejects forward
|
|
* references and duplicate/unknown IDs. */
|
|
export type ScenarioTabReference =
|
|
| { source: "initial"; target: "selected" }
|
|
| {
|
|
source: "operation";
|
|
operation: ScenarioOperationRef;
|
|
result: "selectedTab" | "popup";
|
|
}
|
|
| {
|
|
source: "operation";
|
|
operation: ScenarioOperationRef;
|
|
result: "listedTab";
|
|
index: ScenarioTabIndex;
|
|
};
|
|
|
|
/** An origin selector must resolve to exactly one bounded listed tab. It never
|
|
* changes the selected runtime tab by itself. */
|
|
export type ScenarioTabSelection =
|
|
| { kind: "index"; index: ScenarioTabIndex }
|
|
| { kind: "origin"; origin: string };
|
|
|
|
export interface ScenarioReadRequest {
|
|
tab?: ScenarioTabReference;
|
|
}
|
|
|
|
export interface ScenarioAssertRequest {
|
|
tab?: ScenarioTabReference;
|
|
assertion: PageAssertion;
|
|
wait: "none" | "bounded";
|
|
}
|
|
|
|
export interface ScenarioNavigateRequest {
|
|
tab?: ScenarioTabReference;
|
|
url: string;
|
|
}
|
|
|
|
export interface ScenarioReloadRequest {
|
|
tab?: ScenarioTabReference;
|
|
}
|
|
|
|
export interface ScenarioListTabsRequest {
|
|
/** Lists deterministically and optionally identifies one unique result;
|
|
* selection still requires a later explicit selectTab operation. */
|
|
select?: ScenarioTabSelection;
|
|
}
|
|
|
|
export interface ScenarioSelectTabRequest {
|
|
tab: ScenarioTabReference;
|
|
}
|
|
|
|
export interface ScenarioWaitPopupRequest {
|
|
opener: ScenarioTabReference;
|
|
}
|
|
|
|
export interface ScenarioCloseTabRequest {
|
|
tab: ScenarioTabReference;
|
|
}
|
|
|
|
export interface ScenarioOperationMetadata {
|
|
/** Optional stable local reference for later ScenarioOperationRef{id}. */
|
|
id?: string;
|
|
}
|
|
|
|
/** Scenarios reuse the existing bounded Step instead of inventing browser code. */
|
|
export type ScenarioOperation =
|
|
| ({ op: "step"; step: Step } & ScenarioOperationMetadata)
|
|
| ({ op: "read"; request: ScenarioReadRequest } & ScenarioOperationMetadata)
|
|
| ({ op: "assert"; request: ScenarioAssertRequest } & ScenarioOperationMetadata)
|
|
| ({ op: "navigate"; request: ScenarioNavigateRequest } & ScenarioOperationMetadata)
|
|
| ({ op: "reload"; request: ScenarioReloadRequest } & ScenarioOperationMetadata)
|
|
| ({ op: "listTabs"; request?: ScenarioListTabsRequest } & ScenarioOperationMetadata)
|
|
| ({ op: "selectTab"; request: ScenarioSelectTabRequest } & ScenarioOperationMetadata)
|
|
| ({ op: "waitPopup"; request: ScenarioWaitPopupRequest } & ScenarioOperationMetadata)
|
|
| ({ op: "closeTab"; request: ScenarioCloseTabRequest } & ScenarioOperationMetadata);
|
|
|
|
export const FAST_BROWSER_SCENARIO_FILE_VERSION = 1 as const;
|
|
export const FAST_BROWSER_REPORT_VERSION = 1 as const;
|
|
|
|
export const FAST_BROWSER_SCENARIO_SERVER_BOUNDS = {
|
|
readinessTimeoutMs: { default: 10_000, hardCeiling: 30_000 },
|
|
readinessPollMs: { default: 100, hardCeiling: 1_000 },
|
|
serverShutdownMs: { default: 5_000, hardCeiling: 10_000 },
|
|
} as const;
|
|
|
|
/** Existing scenario hooks use this same trusted argv/cwd/env-reference
|
|
* shape. `start` is long-lived and is supervised until scenario finally; it is
|
|
* not a setup/reset/cleanup hook and has no shell mode. */
|
|
export interface ScenarioProcessHook {
|
|
command: string;
|
|
args: string[];
|
|
cwd?: string;
|
|
env?: Record<string, string | { env: string }>;
|
|
timeoutMs?: number;
|
|
}
|
|
|
|
export interface ScenarioHttpReadiness {
|
|
/** GET-only HTTP(S) URL supplied by scenario/user config, never page data. */
|
|
url: string;
|
|
expectedStatus?: number;
|
|
timeoutMs?: number;
|
|
}
|
|
|
|
export type ScenarioServerConfig =
|
|
| {
|
|
mode: "managed";
|
|
start: ScenarioProcessHook;
|
|
readiness: ScenarioHttpReadiness;
|
|
shutdownTimeoutMs?: number;
|
|
}
|
|
| {
|
|
mode: "external";
|
|
readiness: ScenarioHttpReadiness;
|
|
};
|
|
|
|
export interface ScenarioFixtureConfig {
|
|
/** Explicit HTTP(S) fixture base URL; no credentials or page-derived URL. */
|
|
baseUrl: string;
|
|
server?: ScenarioServerConfig;
|
|
}
|
|
|
|
export interface ScenarioDefinition {
|
|
version: typeof FAST_BROWSER_SCENARIO_FILE_VERSION;
|
|
id: string;
|
|
operations: readonly ScenarioOperation[];
|
|
fixture?: ScenarioFixtureConfig;
|
|
}
|
|
|
|
export interface ScenarioOperationReport {
|
|
index: number;
|
|
op: ScenarioOperation["op"];
|
|
status: "passed" | "failed" | "skipped";
|
|
elapsedMs: number;
|
|
mutationState: MutationState;
|
|
error?: BrowserErrorDetails;
|
|
}
|
|
|
|
export interface ScenarioReport {
|
|
version: typeof FAST_BROWSER_REPORT_VERSION;
|
|
scenarioId: string;
|
|
status: "passed" | "failed" | "skipped";
|
|
elapsedMs: number;
|
|
operations: readonly ScenarioOperationReport[];
|
|
evidence: readonly PrivateArtifactMetadata[];
|
|
cleanup: "complete" | "failed";
|
|
}
|