@restormel/testing-runner 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Allotment Technology Ltd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,29 @@
1
+ import type { PlaywrightTestingSessionOptions, TestingBrowserSession } from "@restormel/testing-browser-playwright";
2
+ import type { GoalRunRecord, KeysModelMeta, TestGoal, TraceEvent } from "@restormel/testing-core";
3
+ import type { RetryPolicy } from "@restormel/testing-core";
4
+ import { type KeysModelAdapterOptions } from "@restormel/testing-keys-adapter";
5
+ export interface RunBrowserGoalOptions {
6
+ runId: string;
7
+ goal: TestGoal;
8
+ baseUrl: string;
9
+ timeoutMs: number;
10
+ retryPolicy: RetryPolicy;
11
+ artifactDir?: string;
12
+ captureScreenshotOnFailure: boolean;
13
+ headless?: boolean;
14
+ /** Playwright storage state JSON path (from environment auth_ref). */
15
+ storageStatePath?: string;
16
+ createBrowserSession?: (opts?: PlaywrightTestingSessionOptions) => Promise<TestingBrowserSession>;
17
+ resolvedKeys: Record<string, string>;
18
+ keysAdapterOptions?: KeysModelAdapterOptions;
19
+ startingStepIndex: number;
20
+ }
21
+ export interface RunBrowserGoalResult {
22
+ goalRecord: GoalRunRecord;
23
+ traces: TraceEvent[];
24
+ nextStepIndex: number;
25
+ warnings: string[];
26
+ keysModelMetaFragments: KeysModelMeta[];
27
+ }
28
+ export declare function runBrowserGoal(options: RunBrowserGoalOptions): Promise<RunBrowserGoalResult>;
29
+ //# sourceMappingURL=browser-goal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-goal.d.ts","sourceRoot":"","sources":["../src/browser-goal.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,+BAA+B,EAC/B,qBAAqB,EACtB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAClG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAgB,KAAK,uBAAuB,EAAsB,MAAM,iCAAiC,CAAC;AAMjH,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0BAA0B,EAAE,OAAO,CAAC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oBAAoB,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,+BAA+B,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAClG,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,kBAAkB,CAAC,EAAE,uBAAuB,CAAC;IAC7C,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,aAAa,CAAC;IAC1B,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,sBAAsB,EAAE,aAAa,EAAE,CAAC;CACzC;AAkCD,wBAAsB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAgOlG"}
@@ -0,0 +1,240 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdir } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { browserTracesToCoreEvents, createPlaywrightTestingSession } from "@restormel/testing-browser-playwright";
5
+ import { resolveModel } from "@restormel/testing-keys-adapter";
6
+ import { evaluateBrowserSuccessCriteria } from "./evaluate-criteria.js";
7
+ import { judgeLogicalRefForCriteria } from "./judge-ref.js";
8
+ import { runGoalAttempts } from "./retries.js";
9
+ import { TimeoutError, withTimeout } from "./timeout.js";
10
+ function nowIso() {
11
+ return new Date().toISOString();
12
+ }
13
+ function toKeysModelMeta(model, invocations) {
14
+ return {
15
+ logicalRef: model.meta.logicalRef,
16
+ provider: model.meta.provider,
17
+ model: model.meta.model,
18
+ resolutionSource: model.meta.resolutionSource,
19
+ invocationCount: invocations,
20
+ };
21
+ }
22
+ async function captureScreenshot(session, artifactDir, goalId, attemptIndex) {
23
+ const dir = join(artifactDir, "goals", goalId);
24
+ await mkdir(dir, { recursive: true });
25
+ const name = `attempt-${attemptIndex}.png`;
26
+ const abs = join(dir, name);
27
+ try {
28
+ await session.screenshot(abs);
29
+ return join("goals", goalId, name);
30
+ }
31
+ catch {
32
+ return undefined;
33
+ }
34
+ }
35
+ export async function runBrowserGoal(options) {
36
+ const { goal, runId } = options;
37
+ const warnings = [];
38
+ const keysModelMetaFragments = [];
39
+ const allTraces = [];
40
+ const evidenceRefs = [];
41
+ let stepCursor = options.startingStepIndex;
42
+ const pushTrace = (partial) => {
43
+ allTraces.push({
44
+ id: randomUUID(),
45
+ runId,
46
+ goalId: goal.id,
47
+ stepIndex: stepCursor++,
48
+ timestamp: nowIso(),
49
+ ...partial,
50
+ });
51
+ };
52
+ let judgeModel;
53
+ if (goal.successCriteria.judgeRubric) {
54
+ const ref = judgeLogicalRefForCriteria(goal.successCriteria, options.resolvedKeys);
55
+ if (!ref) {
56
+ warnings.push(`Goal "${goal.id}": judge_rubric has no model_ref and no llm_primary / judge key in environment`);
57
+ }
58
+ else {
59
+ const res = await resolveModel(ref, options.keysAdapterOptions ?? {});
60
+ if (res.ok) {
61
+ warnings.push(...res.warnings);
62
+ judgeModel = res.model;
63
+ }
64
+ else {
65
+ warnings.push(`Goal "${goal.id}": model resolution failed (${res.error.code}): ${res.error.message}`);
66
+ }
67
+ }
68
+ }
69
+ const sessionFactory = options.createBrowserSession ?? createPlaywrightTestingSession;
70
+ const attemptResult = await runGoalAttempts({
71
+ maxRetries: options.retryPolicy.maxRetries,
72
+ backoffMs: options.retryPolicy.backoffMs,
73
+ runAttempt: async (attemptIndex) => {
74
+ pushTrace({
75
+ kind: "observation",
76
+ summary: `Attempt ${attemptIndex + 1} start`,
77
+ metadata: { phase: "attempt_start", attemptIndex },
78
+ });
79
+ let session;
80
+ try {
81
+ session = await sessionFactory({
82
+ headless: options.headless ?? true,
83
+ timeoutMs: options.timeoutMs,
84
+ ...(options.storageStatePath !== undefined && options.storageStatePath.length > 0
85
+ ? { storageState: options.storageStatePath }
86
+ : {}),
87
+ });
88
+ }
89
+ catch (e) {
90
+ const msg = e instanceof Error ? e.message : String(e);
91
+ pushTrace({
92
+ kind: "error",
93
+ summary: `Browser session failed: ${msg}`,
94
+ metadata: { attemptIndex },
95
+ });
96
+ return {
97
+ kind: "stop",
98
+ verdict: "failed",
99
+ reasonCode: "ADAPTER_ERROR",
100
+ summary: `Browser adapter could not start a session: ${msg}`,
101
+ retryable: false,
102
+ };
103
+ }
104
+ try {
105
+ await withTimeout(session.navigate(options.baseUrl, { timeoutMs: options.timeoutMs, waitUntil: "load" }), options.timeoutMs + 2000, "navigation");
106
+ pushTrace({
107
+ kind: "observation",
108
+ summary: `Navigated to ${options.baseUrl}`,
109
+ metadata: { phase: "post_navigate", attemptIndex },
110
+ });
111
+ const evalResult = await withTimeout(evaluateBrowserSuccessCriteria(session.page, goal.successCriteria, {
112
+ judgeModel,
113
+ }), options.timeoutMs + 2000, "evaluation");
114
+ const drained = session.drainTraceEntries();
115
+ const mapped = browserTracesToCoreEvents(drained, {
116
+ runId,
117
+ goalId: goal.id,
118
+ startingStepIndex: stepCursor,
119
+ });
120
+ stepCursor += mapped.length;
121
+ allTraces.push(...mapped);
122
+ if (judgeModel && (evalResult.judgeModelInvocations ?? 0) > 0) {
123
+ keysModelMetaFragments.push(toKeysModelMeta(judgeModel, evalResult.judgeModelInvocations ?? 1));
124
+ }
125
+ pushTrace({
126
+ kind: "assertion",
127
+ summary: evalResult.summary,
128
+ metadata: {
129
+ verdict: evalResult.verdict,
130
+ reasonCode: evalResult.reasonCode,
131
+ attemptIndex,
132
+ },
133
+ });
134
+ if (evalResult.verdict === "passed") {
135
+ return {
136
+ kind: "stop",
137
+ verdict: "passed",
138
+ reasonCode: evalResult.reasonCode,
139
+ summary: evalResult.summary,
140
+ };
141
+ }
142
+ if (evalResult.verdict === "indeterminate") {
143
+ if (options.captureScreenshotOnFailure &&
144
+ options.artifactDir &&
145
+ (evalResult.reasonCode === "JUDGE_UNCERTAIN" ||
146
+ evalResult.reasonCode === "JUDGE_HTTP_ERROR" ||
147
+ evalResult.reasonCode === "JUDGE_PARSE_ERROR")) {
148
+ const rel = await captureScreenshot(session, options.artifactDir, goal.id, attemptIndex);
149
+ if (rel)
150
+ evidenceRefs.push(rel);
151
+ }
152
+ return {
153
+ kind: "stop",
154
+ verdict: "indeterminate",
155
+ reasonCode: evalResult.reasonCode,
156
+ summary: evalResult.summary,
157
+ };
158
+ }
159
+ if (options.captureScreenshotOnFailure && options.artifactDir) {
160
+ const rel = await captureScreenshot(session, options.artifactDir, goal.id, attemptIndex);
161
+ if (rel)
162
+ evidenceRefs.push(rel);
163
+ }
164
+ return {
165
+ kind: "retry",
166
+ verdict: "failed",
167
+ reasonCode: evalResult.reasonCode,
168
+ summary: evalResult.summary,
169
+ retryable: true,
170
+ };
171
+ }
172
+ catch (e) {
173
+ const drained = session.drainTraceEntries();
174
+ const mapped = browserTracesToCoreEvents(drained, {
175
+ runId,
176
+ goalId: goal.id,
177
+ startingStepIndex: stepCursor,
178
+ });
179
+ stepCursor += mapped.length;
180
+ allTraces.push(...mapped);
181
+ if (e instanceof TimeoutError) {
182
+ pushTrace({
183
+ kind: "error",
184
+ summary: e.message,
185
+ metadata: { attemptIndex, code: e.code },
186
+ });
187
+ if (options.captureScreenshotOnFailure && options.artifactDir) {
188
+ const rel = await captureScreenshot(session, options.artifactDir, goal.id, attemptIndex);
189
+ if (rel)
190
+ evidenceRefs.push(rel);
191
+ }
192
+ return {
193
+ kind: "retry",
194
+ verdict: "failed",
195
+ reasonCode: "TIMEOUT",
196
+ summary: e.message,
197
+ retryable: true,
198
+ };
199
+ }
200
+ const msg = e instanceof Error ? e.message : String(e);
201
+ pushTrace({
202
+ kind: "error",
203
+ summary: msg,
204
+ metadata: { attemptIndex },
205
+ });
206
+ if (options.captureScreenshotOnFailure && options.artifactDir) {
207
+ const rel = await captureScreenshot(session, options.artifactDir, goal.id, attemptIndex);
208
+ if (rel)
209
+ evidenceRefs.push(rel);
210
+ }
211
+ return {
212
+ kind: "retry",
213
+ verdict: "failed",
214
+ reasonCode: "BROWSER_ERROR",
215
+ summary: msg,
216
+ retryable: true,
217
+ };
218
+ }
219
+ finally {
220
+ await session.dispose().catch(() => undefined);
221
+ }
222
+ },
223
+ });
224
+ const goalRecord = {
225
+ goalId: goal.id,
226
+ verdict: attemptResult.verdict,
227
+ reasonCode: attemptResult.reasonCode,
228
+ summary: attemptResult.summary,
229
+ retriesUsed: attemptResult.retriesUsed,
230
+ evidenceRefs: [...evidenceRefs],
231
+ };
232
+ return {
233
+ goalRecord,
234
+ traces: allTraces,
235
+ nextStepIndex: stepCursor,
236
+ warnings,
237
+ keysModelMetaFragments,
238
+ };
239
+ }
240
+ //# sourceMappingURL=browser-goal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-goal.js","sourceRoot":"","sources":["../src/browser-goal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { Page } from "playwright";
2
+ import type { SuccessCriteria, Verdict } from "@restormel/testing-core";
3
+ import type { ResolvedModel } from "@restormel/testing-keys-adapter";
4
+ export interface CriteriaEvaluation {
5
+ verdict: Verdict;
6
+ reasonCode: string;
7
+ summary: string;
8
+ /** Set when an OpenAI-compatible judge request was sent (for Keys model meta). */
9
+ judgeModelInvocations?: number;
10
+ }
11
+ /**
12
+ * Deterministic checks: URL, DOM visibility, text present/absent, minimal structured checks.
13
+ * Judge rubric (optional) runs after deterministics all pass.
14
+ */
15
+ export declare function evaluateBrowserSuccessCriteria(page: Page, criteria: SuccessCriteria, options?: {
16
+ judgeModel?: ResolvedModel;
17
+ }): Promise<CriteriaEvaluation>;
18
+ //# sourceMappingURL=evaluate-criteria.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluate-criteria.d.ts","sourceRoot":"","sources":["../src/evaluate-criteria.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAe,eAAe,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AACrF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAErE,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,kFAAkF;IAClF,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAUD;;;GAGG;AACH,wBAAsB,8BAA8B,CAClD,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,eAAe,EACzB,OAAO,CAAC,EAAE;IACR,UAAU,CAAC,EAAE,aAAa,CAAC;CAC5B,GACA,OAAO,CAAC,kBAAkB,CAAC,CA8G7B"}
@@ -0,0 +1,243 @@
1
+ function normalizeUrl(u) {
2
+ try {
3
+ return new URL(u).href;
4
+ }
5
+ catch {
6
+ return u;
7
+ }
8
+ }
9
+ /**
10
+ * Deterministic checks: URL, DOM visibility, text present/absent, minimal structured checks.
11
+ * Judge rubric (optional) runs after deterministics all pass.
12
+ */
13
+ export async function evaluateBrowserSuccessCriteria(page, criteria, options) {
14
+ const url = page.url();
15
+ if (criteria.urlMatches !== undefined) {
16
+ const patterns = Array.isArray(criteria.urlMatches) ? criteria.urlMatches : [criteria.urlMatches];
17
+ const nu = normalizeUrl(url);
18
+ for (const p of patterns) {
19
+ if (!nu.includes(p) && !url.includes(p)) {
20
+ return {
21
+ verdict: "failed",
22
+ reasonCode: "URL_MISMATCH",
23
+ summary: `Expected URL to match ${JSON.stringify(p)}; got ${truncate(url, 200)}`,
24
+ };
25
+ }
26
+ }
27
+ }
28
+ if (criteria.domSignals !== undefined && criteria.domSignals.length > 0) {
29
+ for (const sel of criteria.domSignals) {
30
+ const loc = page.locator(sel).first();
31
+ const n = await locatorCountSafe(loc);
32
+ if (n === 0) {
33
+ return {
34
+ verdict: "failed",
35
+ reasonCode: "DOM_SIGNAL_MISSING",
36
+ summary: `Expected element for selector ${JSON.stringify(sel)}`,
37
+ };
38
+ }
39
+ const visible = await loc.isVisible().catch(() => false);
40
+ if (!visible) {
41
+ return {
42
+ verdict: "failed",
43
+ reasonCode: "DOM_NOT_VISIBLE",
44
+ summary: `Selector ${JSON.stringify(sel)} exists but is not visible`,
45
+ };
46
+ }
47
+ }
48
+ }
49
+ const bodyText = await page.locator("body").innerText().catch(() => "");
50
+ const hay = bodyText.toLowerCase();
51
+ if (criteria.textPresent !== undefined && criteria.textPresent.length > 0) {
52
+ for (const t of criteria.textPresent) {
53
+ if (!hay.includes(t.toLowerCase())) {
54
+ return {
55
+ verdict: "failed",
56
+ reasonCode: "TEXT_NOT_FOUND",
57
+ summary: `Expected text ${JSON.stringify(truncate(t, 80))} on page`,
58
+ };
59
+ }
60
+ }
61
+ }
62
+ if (criteria.textAbsent !== undefined && criteria.textAbsent.length > 0) {
63
+ for (const t of criteria.textAbsent) {
64
+ if (hay.includes(t.toLowerCase())) {
65
+ return {
66
+ verdict: "failed",
67
+ reasonCode: "TEXT_UNEXPECTED",
68
+ summary: `Did not expect text ${JSON.stringify(truncate(t, 80))}`,
69
+ };
70
+ }
71
+ }
72
+ }
73
+ if (criteria.structuredChecks !== undefined && criteria.structuredChecks.length > 0) {
74
+ for (const check of criteria.structuredChecks) {
75
+ const parsed = parseStructuredPath(check.path);
76
+ if (!parsed) {
77
+ return {
78
+ verdict: "indeterminate",
79
+ reasonCode: "STRUCTURED_PATH_UNKNOWN",
80
+ summary: `Unsupported structured check path ${JSON.stringify(truncate(check.path, 120))}`,
81
+ };
82
+ }
83
+ if (parsed.kind === "css") {
84
+ const text = await page.locator(parsed.selector).first().innerText().catch(() => "");
85
+ if (!structuredExpectOk(text, check.expect)) {
86
+ return {
87
+ verdict: "failed",
88
+ reasonCode: "STRUCTURED_MISMATCH",
89
+ summary: `Structured check ${check.id ?? parsed.selector} did not match expected value`,
90
+ };
91
+ }
92
+ }
93
+ }
94
+ }
95
+ if (criteria.judgeRubric !== undefined) {
96
+ if (!options?.judgeModel) {
97
+ return {
98
+ verdict: "indeterminate",
99
+ reasonCode: "JUDGE_NO_MODEL",
100
+ summary: "judge_rubric present but model could not be resolved via Keys",
101
+ };
102
+ }
103
+ const j = await runJudgeRubric(page, criteria, options.judgeModel);
104
+ return j;
105
+ }
106
+ if (!hasAnyCriterion(criteria)) {
107
+ return {
108
+ verdict: "indeterminate",
109
+ reasonCode: "NO_CRITERIA",
110
+ summary: "No evaluable success criteria",
111
+ };
112
+ }
113
+ return { verdict: "passed", reasonCode: "OK", summary: "All criteria satisfied" };
114
+ }
115
+ function hasAnyCriterion(c) {
116
+ return (c.urlMatches != null ||
117
+ (c.domSignals != null && c.domSignals.length > 0) ||
118
+ (c.textPresent != null && c.textPresent.length > 0) ||
119
+ (c.textAbsent != null && c.textAbsent.length > 0) ||
120
+ (c.structuredChecks != null && c.structuredChecks.length > 0) ||
121
+ c.judgeRubric != null);
122
+ }
123
+ function parseStructuredPath(path) {
124
+ if (path.startsWith("css:")) {
125
+ return { kind: "css", selector: path.slice("css:".length).trim() };
126
+ }
127
+ return null;
128
+ }
129
+ function structuredExpectOk(text, expect) {
130
+ if (expect === undefined)
131
+ return text.trim().length > 0;
132
+ return text.trim() === String(expect);
133
+ }
134
+ async function locatorCountSafe(loc) {
135
+ try {
136
+ return await loc.count();
137
+ }
138
+ catch {
139
+ return 0;
140
+ }
141
+ }
142
+ function truncate(s, n) {
143
+ return s.length <= n ? s : `${s.slice(0, n)}…`;
144
+ }
145
+ const MAX_JUDGE_SAMPLE_CHARS = 8000;
146
+ async function sampleTextForJudge(page, rubric) {
147
+ if (rubric.contextSelector !== undefined && rubric.contextSelector.trim() !== "") {
148
+ const t = await page.locator(rubric.contextSelector).first().innerText().catch(() => "");
149
+ return truncate(t.trim(), MAX_JUDGE_SAMPLE_CHARS);
150
+ }
151
+ const mainText = await page.locator("main").first().innerText().catch(() => "");
152
+ if (mainText.trim().length > 0) {
153
+ return truncate(mainText.trim(), MAX_JUDGE_SAMPLE_CHARS);
154
+ }
155
+ const bodyText = await page.locator("body").innerText().catch(() => "");
156
+ return truncate(bodyText.trim(), MAX_JUDGE_SAMPLE_CHARS);
157
+ }
158
+ function redactForLog(s) {
159
+ return s.replace(/\bBearer\s+[\w-_.]+\b/gi, "Bearer [redacted]").replace(/\bsk-[a-zA-Z0-9]{10,}\b/g, "sk-[redacted]");
160
+ }
161
+ async function runJudgeRubric(page, criteria, model) {
162
+ const rubric = criteria.judgeRubric;
163
+ if (!rubric) {
164
+ return { verdict: "passed", reasonCode: "OK", summary: "No rubric" };
165
+ }
166
+ const sample = await sampleTextForJudge(page, rubric);
167
+ const base = model.providerBaseUrl?.replace(/\/?$/, "") ?? "https://api.openai.com/v1";
168
+ const url = `${base}/chat/completions`;
169
+ const system = 'You are a test oracle. Reply with a single JSON object only: {"verdict":"pass"|"fail"|"uncertain"} matching whether the page satisfies the rubric.';
170
+ const user = `Rubric id: ${rubric.id}\nSummary: ${rubric.summary ?? "(none)"}\n\nPage text:\n${sample}`;
171
+ try {
172
+ const res = await fetch(url, {
173
+ method: "POST",
174
+ headers: {
175
+ "content-type": "application/json",
176
+ authorization: `Bearer ${model.credentials.apiKey}`,
177
+ },
178
+ body: JSON.stringify({
179
+ model: model.modelId,
180
+ messages: [
181
+ { role: "system", content: system },
182
+ { role: "user", content: user },
183
+ ],
184
+ max_tokens: 80,
185
+ response_format: { type: "json_object" },
186
+ }),
187
+ });
188
+ if (!res.ok) {
189
+ const t = await res.text().catch(() => "");
190
+ return {
191
+ verdict: "indeterminate",
192
+ reasonCode: "JUDGE_HTTP_ERROR",
193
+ summary: `Judge request failed: HTTP ${res.status} ${truncate(redactForLog(t), 80)}`,
194
+ judgeModelInvocations: 1,
195
+ };
196
+ }
197
+ const data = (await res.json());
198
+ const raw = data.choices?.[0]?.message?.content ?? "{}";
199
+ let parsed;
200
+ try {
201
+ parsed = JSON.parse(raw);
202
+ }
203
+ catch {
204
+ return {
205
+ verdict: "indeterminate",
206
+ reasonCode: "JUDGE_PARSE_ERROR",
207
+ summary: "Judge response was not valid JSON",
208
+ judgeModelInvocations: 1,
209
+ };
210
+ }
211
+ const v = (parsed.verdict ?? "").toLowerCase();
212
+ if (v === "pass") {
213
+ return {
214
+ verdict: "passed",
215
+ reasonCode: "JUDGE_PASS",
216
+ summary: "Judge rubric passed",
217
+ judgeModelInvocations: 1,
218
+ };
219
+ }
220
+ if (v === "fail") {
221
+ return {
222
+ verdict: "failed",
223
+ reasonCode: "JUDGE_FAIL",
224
+ summary: "Judge rubric failed",
225
+ judgeModelInvocations: 1,
226
+ };
227
+ }
228
+ return {
229
+ verdict: "indeterminate",
230
+ reasonCode: "JUDGE_UNCERTAIN",
231
+ summary: "Judge returned uncertain",
232
+ judgeModelInvocations: 1,
233
+ };
234
+ }
235
+ catch (e) {
236
+ return {
237
+ verdict: "indeterminate",
238
+ reasonCode: "JUDGE_ERROR",
239
+ summary: `Judge error: ${e instanceof Error ? e.message : String(e)}`,
240
+ };
241
+ }
242
+ }
243
+ //# sourceMappingURL=evaluate-criteria.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluate-criteria.js","sourceRoot":"","sources":["../src/evaluate-criteria.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @restormel/testing-runner — local suite execution (MVP).
3
+ */
4
+ export { runBrowserGoal } from "./browser-goal.js";
5
+ export type { RunBrowserGoalOptions, RunBrowserGoalResult } from "./browser-goal.js";
6
+ export { evaluateBrowserSuccessCriteria } from "./evaluate-criteria.js";
7
+ export type { CriteriaEvaluation } from "./evaluate-criteria.js";
8
+ export { runGoalAttempts } from "./retries.js";
9
+ export type { AttemptOutcome } from "./retries.js";
10
+ export { runLocalSuite, runSuiteFromConfig } from "./run-suite.js";
11
+ export type { RunLocalSuiteOptions, RunSuiteExecutionOptions, RunSuiteResult } from "./types.js";
12
+ export { withTimeout, TimeoutError } from "./timeout.js";
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACrF,OAAO,EAAE,8BAA8B,EAAE,MAAM,wBAAwB,CAAC;AACxE,YAAY,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACnE,YAAY,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjG,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @restormel/testing-runner — local suite execution (MVP).
3
+ */
4
+ export { runBrowserGoal } from "./browser-goal.js";
5
+ export { evaluateBrowserSuccessCriteria } from "./evaluate-criteria.js";
6
+ export { runGoalAttempts } from "./retries.js";
7
+ export { runLocalSuite, runSuiteFromConfig } from "./run-suite.js";
8
+ export { withTimeout, TimeoutError } from "./timeout.js";
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,EAAE,8BAA8B,EAAE,MAAM,wBAAwB,CAAC;AAExE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEnE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { SuccessCriteria } from "@restormel/testing-core";
2
+ /**
3
+ * Logical ref for Keys resolution when a goal uses `judge_rubric`.
4
+ * Order: explicit `model_ref` on the rubric, then common slots on the merged environment keys map.
5
+ */
6
+ export declare function judgeLogicalRefForCriteria(criteria: SuccessCriteria, resolvedKeys: Record<string, string>): string | undefined;
7
+ //# sourceMappingURL=judge-ref.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"judge-ref.d.ts","sourceRoot":"","sources":["../src/judge-ref.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE/D;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,eAAe,EACzB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACnC,MAAM,GAAG,SAAS,CAWpB"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Logical ref for Keys resolution when a goal uses `judge_rubric`.
3
+ * Order: explicit `model_ref` on the rubric, then common slots on the merged environment keys map.
4
+ */
5
+ export function judgeLogicalRefForCriteria(criteria, resolvedKeys) {
6
+ const jr = criteria.judgeRubric;
7
+ if (!jr)
8
+ return undefined;
9
+ if (jr.modelRef)
10
+ return jr.modelRef;
11
+ return (resolvedKeys.llm_primary ??
12
+ resolvedKeys.llmPrimary ??
13
+ resolvedKeys.judge ??
14
+ resolvedKeys.llm_judge ??
15
+ resolvedKeys.llmJudge);
16
+ }
17
+ //# sourceMappingURL=judge-ref.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"judge-ref.js","sourceRoot":"","sources":["../src/judge-ref.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CACxC,QAAyB,EACzB,YAAoC;IAEpC,MAAM,EAAE,GAAG,QAAQ,CAAC,WAAW,CAAC;IAChC,IAAI,CAAC,EAAE;QAAE,OAAO,SAAS,CAAC;IAC1B,IAAI,EAAE,CAAC,QAAQ;QAAE,OAAO,EAAE,CAAC,QAAQ,CAAC;IACpC,OAAO,CACL,YAAY,CAAC,WAAW;QACxB,YAAY,CAAC,UAAU;QACvB,YAAY,CAAC,KAAK;QAClB,YAAY,CAAC,SAAS;QACtB,YAAY,CAAC,QAAQ,CACtB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,38 @@
1
+ import type { Verdict } from "@restormel/testing-core";
2
+ export type AttemptOutcome = {
3
+ kind: "stop";
4
+ verdict: "passed";
5
+ reasonCode: string;
6
+ summary: string;
7
+ } | {
8
+ kind: "stop";
9
+ verdict: "indeterminate";
10
+ reasonCode: string;
11
+ summary: string;
12
+ } | {
13
+ kind: "stop";
14
+ verdict: "failed";
15
+ reasonCode: string;
16
+ summary: string;
17
+ retryable: false;
18
+ } | {
19
+ kind: "retry";
20
+ verdict: "failed";
21
+ reasonCode: string;
22
+ summary: string;
23
+ retryable: true;
24
+ };
25
+ /**
26
+ * `maxRetries` = extra attempts after the first (e.g. 2 → 3 total tries).
27
+ */
28
+ export declare function runGoalAttempts(options: {
29
+ maxRetries: number;
30
+ backoffMs?: number;
31
+ runAttempt: (attemptIndex: number) => Promise<AttemptOutcome>;
32
+ }): Promise<{
33
+ verdict: Verdict;
34
+ reasonCode: string;
35
+ summary: string;
36
+ retriesUsed: number;
37
+ }>;
38
+ //# sourceMappingURL=retries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retries.d.ts","sourceRoot":"","sources":["../src/retries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAEvD,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACxE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,eAAe,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC/E;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,KAAK,CAAA;CAAE,GAC1F;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,IAAI,CAAA;CAAE,CAAC;AAE/F;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;CAC/D,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC,CAoC1F"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * `maxRetries` = extra attempts after the first (e.g. 2 → 3 total tries).
3
+ */
4
+ export async function runGoalAttempts(options) {
5
+ const maxAttempts = Math.max(1, options.maxRetries + 1);
6
+ let lastFail;
7
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
8
+ if (attempt > 0 && options.backoffMs !== undefined && options.backoffMs > 0) {
9
+ await sleep(options.backoffMs);
10
+ }
11
+ const out = await options.runAttempt(attempt);
12
+ if (out.kind === "stop") {
13
+ return {
14
+ verdict: out.verdict,
15
+ reasonCode: out.reasonCode,
16
+ summary: out.summary,
17
+ retriesUsed: attempt,
18
+ };
19
+ }
20
+ lastFail = { reasonCode: out.reasonCode, summary: out.summary };
21
+ if (!out.retryable || attempt === maxAttempts - 1) {
22
+ return {
23
+ verdict: "failed",
24
+ reasonCode: out.reasonCode,
25
+ summary: out.summary,
26
+ retriesUsed: attempt,
27
+ };
28
+ }
29
+ }
30
+ return {
31
+ verdict: "failed",
32
+ reasonCode: lastFail?.reasonCode ?? "UNKNOWN",
33
+ summary: lastFail?.summary ?? "Goal failed",
34
+ retriesUsed: maxAttempts - 1,
35
+ };
36
+ }
37
+ function sleep(ms) {
38
+ return new Promise((r) => setTimeout(r, ms));
39
+ }
40
+ //# sourceMappingURL=retries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retries.js","sourceRoot":"","sources":["../src/retries.ts"],"names":[],"mappings":"AAQA;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAIrC;IACC,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;IACxD,IAAI,QAA6D,CAAC;IAElE,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC;QACvD,IAAI,OAAO,GAAG,CAAC,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS,IAAI,OAAO,CAAC,SAAS,GAAG,CAAC,EAAE,CAAC;YAC5E,MAAM,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACjC,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QAC9C,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACxB,OAAO;gBACL,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,UAAU,EAAE,GAAG,CAAC,UAAU;gBAC1B,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,WAAW,EAAE,OAAO;aACrB,CAAC;QACJ,CAAC;QAED,QAAQ,GAAG,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC;QAChE,IAAI,CAAC,GAAG,CAAC,SAAS,IAAI,OAAO,KAAK,WAAW,GAAG,CAAC,EAAE,CAAC;YAClD,OAAO;gBACL,OAAO,EAAE,QAAQ;gBACjB,UAAU,EAAE,GAAG,CAAC,UAAU;gBAC1B,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,WAAW,EAAE,OAAO;aACrB,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO;QACL,OAAO,EAAE,QAAQ;QACjB,UAAU,EAAE,QAAQ,EAAE,UAAU,IAAI,SAAS;QAC7C,OAAO,EAAE,QAAQ,EAAE,OAAO,IAAI,aAAa;QAC3C,WAAW,EAAE,WAAW,GAAG,CAAC;KAC7B,CAAC;AACJ,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { RunLocalSuiteOptions, RunSuiteExecutionOptions, RunSuiteResult } from "./types.js";
2
+ /**
3
+ * Execute one suite from an already-loaded config (local CLI / programmatic use).
4
+ */
5
+ export declare function runSuiteFromConfig(options: RunSuiteExecutionOptions): Promise<RunSuiteResult>;
6
+ /**
7
+ * Load config from disk, then run one suite.
8
+ */
9
+ export declare function runLocalSuite(options: RunLocalSuiteOptions): Promise<RunSuiteResult>;
10
+ //# sourceMappingURL=run-suite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-suite.d.ts","sourceRoot":"","sources":["../src/run-suite.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAqBjG;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,cAAc,CAAC,CAqInG;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,cAAc,CAAC,CA0B1F"}
@@ -0,0 +1,179 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { formatConfigErrors, isSafeHttpUrl, loadConfigFromFile, resolvedKeysForEnvironment, resolveEnvironmentProfile, resolveSuite, } from "@restormel/testing-config";
3
+ import { resolve } from "node:path";
4
+ import { runBrowserGoal } from "./browser-goal.js";
5
+ import { resolveStorageStatePath } from "./storage-state.js";
6
+ function aggregateVerdict(goalVerdicts) {
7
+ if (goalVerdicts.some((v) => v === "failed"))
8
+ return "failed";
9
+ if (goalVerdicts.some((v) => v === "indeterminate"))
10
+ return "indeterminate";
11
+ return "passed";
12
+ }
13
+ function mergeKeysMeta(fragments) {
14
+ const byRef = new Map();
15
+ for (const f of fragments) {
16
+ const prev = byRef.get(f.logicalRef);
17
+ if (!prev) {
18
+ byRef.set(f.logicalRef, { ...f });
19
+ }
20
+ else {
21
+ prev.invocationCount = (prev.invocationCount ?? 0) + (f.invocationCount ?? 0);
22
+ }
23
+ }
24
+ return [...byRef.values()];
25
+ }
26
+ /**
27
+ * Execute one suite from an already-loaded config (local CLI / programmatic use).
28
+ */
29
+ export async function runSuiteFromConfig(options) {
30
+ const traces = [];
31
+ const warnings = [];
32
+ const errors = [];
33
+ const startedAt = new Date().toISOString();
34
+ const suiteRes = resolveSuite(options.config, options.suiteId);
35
+ if (!suiteRes.ok) {
36
+ return { ok: false, errors: [formatConfigErrors(suiteRes.errors)], traces, warnings };
37
+ }
38
+ const suite = suiteRes.suite;
39
+ const envId = options.environmentId ?? suite.environment;
40
+ const envRes = resolveEnvironmentProfile(options.config, envId);
41
+ if (!envRes.ok) {
42
+ return { ok: false, errors: [formatConfigErrors(envRes.errors)], traces, warnings };
43
+ }
44
+ let profile = envRes.profile;
45
+ const override = options.targetUrlOverride?.trim();
46
+ if (override !== undefined && override.length > 0) {
47
+ const urlCheck = isSafeHttpUrl(override);
48
+ if (!urlCheck.ok) {
49
+ return {
50
+ ok: false,
51
+ errors: [`Invalid target_url override: ${urlCheck.reason}`],
52
+ traces,
53
+ warnings,
54
+ };
55
+ }
56
+ profile = { ...profile, baseUrl: override };
57
+ }
58
+ let goals = suite.goals;
59
+ if (options.goalIds !== undefined && options.goalIds.length > 0) {
60
+ const suiteGoalIds = new Set(suite.goals.map((g) => g.id));
61
+ const missing = options.goalIds.filter((id) => !suiteGoalIds.has(id));
62
+ if (missing.length > 0) {
63
+ errors.push(`Unknown goal id(s) for suite "${suite.id}": ${missing.join(", ")}`);
64
+ return { ok: false, errors, traces, warnings };
65
+ }
66
+ const pick = new Set(options.goalIds);
67
+ goals = suite.goals.filter((g) => pick.has(g.id));
68
+ }
69
+ const resolvedKeys = resolvedKeysForEnvironment(options.config, envId);
70
+ const storageStatePath = options.configFilePath !== undefined
71
+ ? resolveStorageStatePath(profile, options.configFilePath)
72
+ : undefined;
73
+ const retryPolicy = suite.retryPolicy ?? { maxRetries: 0 };
74
+ const timeoutMs = suite.defaultTimeoutMs ?? options.config.defaults?.defaultTimeoutMs ?? 30_000;
75
+ const artifactPolicy = suite.artifactPolicy ?? options.config.defaults?.artifactPolicy;
76
+ const shots = artifactPolicy?.screenshots ?? "on_failure";
77
+ const captureScreenshotOnFailure = shots === "on_failure" || shots === "always";
78
+ const runId = randomUUID();
79
+ const trigger = options.trigger ?? "local";
80
+ const goalRuns = [];
81
+ let stepIndex = 0;
82
+ const allKeysFragments = [];
83
+ for (const goal of goals) {
84
+ if (goal.type !== "browser") {
85
+ goalRuns.push({
86
+ goalId: goal.id,
87
+ verdict: "indeterminate",
88
+ reasonCode: "GOAL_TYPE_UNSUPPORTED",
89
+ summary: `Goal type "${goal.type}" is not supported by the local runner (browser only in this slice)`,
90
+ retriesUsed: 0,
91
+ evidenceRefs: [],
92
+ });
93
+ continue;
94
+ }
95
+ const bg = await runBrowserGoal({
96
+ runId,
97
+ goal,
98
+ baseUrl: profile.baseUrl,
99
+ timeoutMs,
100
+ retryPolicy,
101
+ artifactDir: options.artifactDir,
102
+ captureScreenshotOnFailure,
103
+ headless: options.headless,
104
+ storageStatePath,
105
+ createBrowserSession: options.createBrowserSession,
106
+ resolvedKeys,
107
+ keysAdapterOptions: options.keysAdapterOptions,
108
+ startingStepIndex: stepIndex,
109
+ });
110
+ stepIndex = bg.nextStepIndex;
111
+ traces.push(...bg.traces);
112
+ warnings.push(...bg.warnings);
113
+ goalRuns.push(bg.goalRecord);
114
+ allKeysFragments.push(...bg.keysModelMetaFragments);
115
+ }
116
+ const verdict = aggregateVerdict(goalRuns.map((g) => g.verdict));
117
+ const mergedMeta = mergeKeysMeta(allKeysFragments);
118
+ const judgeInvocations = mergedMeta.reduce((n, m) => n + (m.invocationCount ?? 0), 0);
119
+ let costEstimate;
120
+ if (judgeInvocations > 0) {
121
+ costEstimate = {
122
+ tokenEstimate: { input: 0, output: judgeInvocations * 120 },
123
+ };
124
+ }
125
+ const run = {
126
+ id: runId,
127
+ suiteId: suite.id,
128
+ environmentId: envId,
129
+ trigger,
130
+ commitSha: options.commitSha,
131
+ repository: options.repository,
132
+ startedAt,
133
+ endedAt: new Date().toISOString(),
134
+ verdict,
135
+ goalRuns,
136
+ keysModelMeta: mergedMeta.length > 0 ? mergedMeta : undefined,
137
+ judgeInvocationCount: judgeInvocations,
138
+ costEstimate,
139
+ };
140
+ const suiteMeta = {
141
+ id: suite.id,
142
+ description: suite.description,
143
+ tags: suite.tags,
144
+ environmentId: envId,
145
+ goalCount: goals.length,
146
+ };
147
+ return { ok: true, run, traces, warnings, errors, suiteMeta };
148
+ }
149
+ /**
150
+ * Load config from disk, then run one suite.
151
+ */
152
+ export async function runLocalSuite(options) {
153
+ const loaded = await loadConfigFromFile(options.configPath);
154
+ if (!loaded.ok) {
155
+ return {
156
+ ok: false,
157
+ errors: [formatConfigErrors(loaded.errors)],
158
+ traces: [],
159
+ warnings: [],
160
+ };
161
+ }
162
+ const configFilePath = resolve(process.cwd(), options.configPath);
163
+ return runSuiteFromConfig({
164
+ config: loaded.config,
165
+ suiteId: options.suiteId,
166
+ environmentId: options.environmentId,
167
+ targetUrlOverride: options.targetUrlOverride,
168
+ goalIds: options.goalIds,
169
+ trigger: options.trigger,
170
+ commitSha: options.commitSha,
171
+ repository: options.repository,
172
+ artifactDir: options.artifactDir,
173
+ keysAdapterOptions: options.keysAdapterOptions,
174
+ headless: options.headless,
175
+ createBrowserSession: options.createBrowserSession,
176
+ configFilePath,
177
+ });
178
+ }
179
+ //# sourceMappingURL=run-suite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-suite.js","sourceRoot":"","sources":["../src/run-suite.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { EnvironmentProfile } from "@restormel/testing-core";
2
+ /**
3
+ * Resolve Playwright storage state path for `auth_mode: storage_state`.
4
+ * - `auth_ref: env:VAR` — path read from `process.env.VAR` (trimmed).
5
+ * - Otherwise — path relative to the config file directory, or absolute if `auth_ref` is absolute.
6
+ */
7
+ export declare function resolveStorageStatePath(profile: EnvironmentProfile, configFilePath: string): string | undefined;
8
+ //# sourceMappingURL=storage-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-state.d.ts","sourceRoot":"","sources":["../src/storage-state.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAElE;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,kBAAkB,EAC3B,cAAc,EAAE,MAAM,GACrB,MAAM,GAAG,SAAS,CAapB"}
@@ -0,0 +1,22 @@
1
+ import { dirname, isAbsolute, join } from "node:path";
2
+ /**
3
+ * Resolve Playwright storage state path for `auth_mode: storage_state`.
4
+ * - `auth_ref: env:VAR` — path read from `process.env.VAR` (trimmed).
5
+ * - Otherwise — path relative to the config file directory, or absolute if `auth_ref` is absolute.
6
+ */
7
+ export function resolveStorageStatePath(profile, configFilePath) {
8
+ if (profile.authMode !== "storage_state" || profile.authRef === undefined || profile.authRef === "") {
9
+ return undefined;
10
+ }
11
+ const ref = profile.authRef.trim();
12
+ if (ref.startsWith("env:")) {
13
+ const varName = ref.slice("env:".length).trim();
14
+ if (!/^[A-Z][A-Z0-9_]*$/.test(varName))
15
+ return undefined;
16
+ const p = process.env[varName]?.trim();
17
+ return p && p.length > 0 ? p : undefined;
18
+ }
19
+ const base = dirname(configFilePath);
20
+ return isAbsolute(ref) ? ref : join(base, ref);
21
+ }
22
+ //# sourceMappingURL=storage-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-state.js","sourceRoot":"","sources":["../src/storage-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGtD;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAA2B,EAC3B,cAAsB;IAEtB,IAAI,OAAO,CAAC,QAAQ,KAAK,eAAe,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,OAAO,KAAK,EAAE,EAAE,CAAC;QACpG,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IACnC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QAChD,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,OAAO,SAAS,CAAC;QACzD,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC;QACvC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3C,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IACrC,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AACjD,CAAC"}
@@ -0,0 +1,6 @@
1
+ export declare class TimeoutError extends Error {
2
+ readonly code: "TIMEOUT";
3
+ constructor(message: string);
4
+ }
5
+ export declare function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T>;
6
+ //# sourceMappingURL=timeout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeout.d.ts","sourceRoot":"","sources":["../src/timeout.ts"],"names":[],"mappings":"AAAA,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAG,SAAS,CAAU;gBACvB,OAAO,EAAE,MAAM;CAI5B;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAmBzF"}
@@ -0,0 +1,25 @@
1
+ export class TimeoutError extends Error {
2
+ code = "TIMEOUT";
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "TimeoutError";
6
+ }
7
+ }
8
+ export function withTimeout(promise, ms, label) {
9
+ if (!Number.isFinite(ms) || ms <= 0) {
10
+ return promise;
11
+ }
12
+ return new Promise((resolve, reject) => {
13
+ const t = setTimeout(() => {
14
+ reject(new TimeoutError(`${label} exceeded ${ms}ms`));
15
+ }, ms);
16
+ promise.then((v) => {
17
+ clearTimeout(t);
18
+ resolve(v);
19
+ }, (e) => {
20
+ clearTimeout(t);
21
+ reject(e);
22
+ });
23
+ });
24
+ }
25
+ //# sourceMappingURL=timeout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeout.js","sourceRoot":"","sources":["../src/timeout.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,YAAa,SAAQ,KAAK;IAC5B,IAAI,GAAG,SAAkB,CAAC;IACnC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED,MAAM,UAAU,WAAW,CAAI,OAAmB,EAAE,EAAU,EAAE,KAAa;IAC3E,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;QACpC,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACxC,MAAM,CAAC,GAAG,UAAU,CAAC,GAAG,EAAE;YACxB,MAAM,CAAC,IAAI,YAAY,CAAC,GAAG,KAAK,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC;QACxD,CAAC,EAAE,EAAE,CAAC,CAAC;QACP,OAAO,CAAC,IAAI,CACV,CAAC,CAAC,EAAE,EAAE;YACJ,YAAY,CAAC,CAAC,CAAC,CAAC;YAChB,OAAO,CAAC,CAAC,CAAC,CAAC;QACb,CAAC,EACD,CAAC,CAAC,EAAE,EAAE;YACJ,YAAY,CAAC,CAAC,CAAC,CAAC;YAChB,MAAM,CAAC,CAAC,CAAC,CAAC;QACZ,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,57 @@
1
+ import type { KeysModelAdapterOptions } from "@restormel/testing-keys-adapter";
2
+ import type { PlaywrightTestingSessionOptions } from "@restormel/testing-browser-playwright";
3
+ import type { RestormelTestingConfig } from "@restormel/testing-config";
4
+ import type { RunRecord, RunTrigger, SuiteReportSlice, TraceEvent } from "@restormel/testing-core";
5
+ import type { TestingBrowserSession } from "@restormel/testing-browser-playwright";
6
+ export interface RunLocalSuiteOptions {
7
+ /** Path to restormel-testing.yaml / .json */
8
+ configPath: string;
9
+ suiteId: string;
10
+ /** Defaults to the suite’s configured environment. */
11
+ environmentId?: string;
12
+ /**
13
+ * When set, replaces the resolved environment `base_url` (e.g. CI preview deploy URL).
14
+ * Must be a safe http(s) URL with no embedded credentials.
15
+ */
16
+ targetUrlOverride?: string;
17
+ goalIds?: string[];
18
+ trigger?: RunTrigger;
19
+ commitSha?: string;
20
+ repository?: string;
21
+ /** Directory for screenshots and future artefacts (created as needed). */
22
+ artifactDir?: string;
23
+ keysAdapterOptions?: KeysModelAdapterOptions;
24
+ headless?: boolean;
25
+ /**
26
+ * Test / advanced: override browser session creation (e.g. mocks).
27
+ * Default: Playwright Chromium via `createPlaywrightTestingSession`.
28
+ */
29
+ createBrowserSession?: (opts?: PlaywrightTestingSessionOptions) => Promise<TestingBrowserSession>;
30
+ }
31
+ export interface RunSuiteExecutionOptions {
32
+ config: RestormelTestingConfig;
33
+ suiteId: string;
34
+ environmentId?: string;
35
+ targetUrlOverride?: string;
36
+ goalIds?: string[];
37
+ trigger?: RunTrigger;
38
+ commitSha?: string;
39
+ repository?: string;
40
+ artifactDir?: string;
41
+ keysAdapterOptions?: KeysModelAdapterOptions;
42
+ headless?: boolean;
43
+ createBrowserSession?: (opts?: PlaywrightTestingSessionOptions) => Promise<TestingBrowserSession>;
44
+ configFilePath?: string;
45
+ }
46
+ export interface RunSuiteResult {
47
+ /** False when config could not be loaded or suite/environment invalid. */
48
+ ok: boolean;
49
+ run?: RunRecord;
50
+ traces: TraceEvent[];
51
+ warnings: string[];
52
+ /** Human-readable failures (config path, validation, missing env). */
53
+ errors: string[];
54
+ /** Present when `ok` and the suite was resolved (for report generators). */
55
+ suiteMeta?: SuiteReportSlice;
56
+ }
57
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAC/E,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,uCAAuC,CAAC;AAC7F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACnG,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAEnF,MAAM,WAAW,oBAAoB;IACnC,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,uBAAuB,CAAC;IAC7C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,CACrB,IAAI,CAAC,EAAE,+BAA+B,KACnC,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,sBAAsB,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,uBAAuB,CAAC;IAC7C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oBAAoB,CAAC,EAAE,CACrB,IAAI,CAAC,EAAE,+BAA+B,KACnC,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACpC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,0EAA0E;IAC1E,EAAE,EAAE,OAAO,CAAC;IACZ,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,sEAAsE;IACtE,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,gBAAgB,CAAC;CAC9B"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@restormel/testing-runner",
3
+ "version": "0.1.0",
4
+ "description": "Suite execution: browser goals, Keys-backed judges, retries, artefacts.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/Allotment-Technology-Ltd/restormel-testing.git",
9
+ "directory": "packages/runner"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/Allotment-Technology-Ltd/restormel-testing/issues"
13
+ },
14
+ "homepage": "https://github.com/Allotment-Technology-Ltd/restormel-testing/tree/main/packages/runner#readme",
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "type": "module",
19
+ "main": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/index.js"
25
+ }
26
+ },
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "engines": {
31
+ "node": ">=20"
32
+ },
33
+ "dependencies": {
34
+ "playwright": "^1.49.0",
35
+ "@restormel/testing-browser-playwright": "0.1.0",
36
+ "@restormel/testing-config": "0.1.0",
37
+ "@restormel/testing-core": "0.1.0",
38
+ "@restormel/testing-keys-adapter": "0.1.0"
39
+ },
40
+ "devDependencies": {
41
+ "typescript": "^5.7.0"
42
+ },
43
+ "scripts": {
44
+ "build": "tsc -b tsconfig.json",
45
+ "typecheck": "tsc -b tsconfig.json"
46
+ }
47
+ }