@pcamarajr/scout 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 +21 -0
- package/README.md +188 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +157 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +31 -0
- package/dist/config.js +47 -0
- package/dist/config.js.map +1 -0
- package/dist/engine.d.ts +20 -0
- package/dist/engine.js +139 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/server.d.ts +8 -0
- package/dist/mcp/server.js +106 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/report.d.ts +5 -0
- package/dist/report.js +68 -0
- package/dist/report.js.map +1 -0
- package/dist/runner/ai-runner.d.ts +15 -0
- package/dist/runner/ai-runner.js +214 -0
- package/dist/runner/ai-runner.js.map +1 -0
- package/dist/runner/browser.d.ts +68 -0
- package/dist/runner/browser.js +307 -0
- package/dist/runner/browser.js.map +1 -0
- package/dist/runner/script-runner.d.ts +15 -0
- package/dist/runner/script-runner.js +51 -0
- package/dist/runner/script-runner.js.map +1 -0
- package/dist/store.d.ts +33 -0
- package/dist/store.js +119 -0
- package/dist/store.js.map +1 -0
- package/dist/types.d.ts +78 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { chromium } from "playwright";
|
|
3
|
+
const STEP_TIMEOUT = 10_000;
|
|
4
|
+
/**
|
|
5
|
+
* Thin Playwright wrapper shared by the AI runner and the replay runner.
|
|
6
|
+
* Owns ref→element resolution, tracing and screenshots.
|
|
7
|
+
*/
|
|
8
|
+
export class BrowserSession {
|
|
9
|
+
browser;
|
|
10
|
+
context;
|
|
11
|
+
page;
|
|
12
|
+
opts;
|
|
13
|
+
refs;
|
|
14
|
+
screenshotCount = 0;
|
|
15
|
+
screenshots = [];
|
|
16
|
+
constructor(browser, context, page, opts, refs = new Map()) {
|
|
17
|
+
this.browser = browser;
|
|
18
|
+
this.context = context;
|
|
19
|
+
this.page = page;
|
|
20
|
+
this.opts = opts;
|
|
21
|
+
this.refs = refs;
|
|
22
|
+
}
|
|
23
|
+
static async launch(opts) {
|
|
24
|
+
const browser = await chromium.launch({ headless: opts.headless });
|
|
25
|
+
const context = await browser.newContext({
|
|
26
|
+
locale: opts.locale ?? "pt-BR",
|
|
27
|
+
viewport: { width: 390, height: 844 }, // mobile-first; vertical video product
|
|
28
|
+
storageState: opts.storageState,
|
|
29
|
+
});
|
|
30
|
+
await context.tracing.start({ screenshots: true, snapshots: true, sources: false });
|
|
31
|
+
const page = await context.newPage();
|
|
32
|
+
page.setDefaultTimeout(STEP_TIMEOUT);
|
|
33
|
+
return new BrowserSession(browser, context, page, opts);
|
|
34
|
+
}
|
|
35
|
+
resolveUrl(url) {
|
|
36
|
+
return new URL(url, this.opts.baseUrl).toString();
|
|
37
|
+
}
|
|
38
|
+
async navigate(url) {
|
|
39
|
+
await this.page.goto(this.resolveUrl(url), { waitUntil: "domcontentloaded" });
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Builds a numbered map of visible interactive elements (role + accessible
|
|
43
|
+
* name + CSS path) plus a text excerpt. Refs are valid until the next snapshot.
|
|
44
|
+
*/
|
|
45
|
+
async snapshot() {
|
|
46
|
+
await this.page.waitForLoadState("domcontentloaded").catch(() => { });
|
|
47
|
+
const raw = (await this.page.evaluate(snapshotScript));
|
|
48
|
+
this.refs.clear();
|
|
49
|
+
for (const el of raw.elements)
|
|
50
|
+
this.refs.set(el.ref, el);
|
|
51
|
+
return { url: this.page.url(), title: await this.page.title(), ...raw };
|
|
52
|
+
}
|
|
53
|
+
formatSnapshot(snap) {
|
|
54
|
+
const lines = snap.elements.map((e) => {
|
|
55
|
+
const extra = [e.value ? `value="${e.value}"` : "", e.href ? `href="${e.href}"` : ""]
|
|
56
|
+
.filter(Boolean)
|
|
57
|
+
.join(" ");
|
|
58
|
+
return `[${e.ref}] ${e.role} "${e.name}"${extra ? " " + extra : ""}`;
|
|
59
|
+
});
|
|
60
|
+
return [
|
|
61
|
+
`URL: ${snap.url}`,
|
|
62
|
+
`Title: ${snap.title}`,
|
|
63
|
+
``,
|
|
64
|
+
`Interactive elements:`,
|
|
65
|
+
...lines,
|
|
66
|
+
``,
|
|
67
|
+
`Visible text (excerpt):`,
|
|
68
|
+
snap.text,
|
|
69
|
+
].join("\n");
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Resolves a snapshot ref to a locator AND the durable Target recorded for
|
|
73
|
+
* replay: role+name when unique on the page, CSS path otherwise.
|
|
74
|
+
*/
|
|
75
|
+
async resolveRef(ref) {
|
|
76
|
+
const info = this.refs.get(ref);
|
|
77
|
+
if (!info) {
|
|
78
|
+
throw new Error(`Ref [${ref}] desconhecido — tire um novo browser_snapshot, a página mudou.`);
|
|
79
|
+
}
|
|
80
|
+
const description = `${info.role} "${info.name}"`;
|
|
81
|
+
if (info.role && info.name) {
|
|
82
|
+
const byRole = this.page.getByRole(info.role, {
|
|
83
|
+
name: info.name,
|
|
84
|
+
exact: true,
|
|
85
|
+
});
|
|
86
|
+
if ((await byRole.count()) === 1) {
|
|
87
|
+
return { locator: byRole, target: { role: info.role, name: info.name, description } };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { locator: this.page.locator(info.css), target: { css: info.css, description } };
|
|
91
|
+
}
|
|
92
|
+
async click(ref) {
|
|
93
|
+
const { locator, target } = await this.resolveRef(ref);
|
|
94
|
+
await locator.click();
|
|
95
|
+
return target;
|
|
96
|
+
}
|
|
97
|
+
async fill(ref, value) {
|
|
98
|
+
const { locator, target } = await this.resolveRef(ref);
|
|
99
|
+
await locator.fill(value);
|
|
100
|
+
return target;
|
|
101
|
+
}
|
|
102
|
+
async select(ref, value) {
|
|
103
|
+
const { locator, target } = await this.resolveRef(ref);
|
|
104
|
+
await locator.selectOption(value);
|
|
105
|
+
return target;
|
|
106
|
+
}
|
|
107
|
+
async press(key) {
|
|
108
|
+
await this.page.keyboard.press(key);
|
|
109
|
+
}
|
|
110
|
+
async waitForText(text) {
|
|
111
|
+
await this.page.getByText(text).first().waitFor({ state: "visible", timeout: STEP_TIMEOUT });
|
|
112
|
+
}
|
|
113
|
+
async waitForUrl(pattern) {
|
|
114
|
+
await this.page.waitForURL((u) => u.toString().includes(pattern), { timeout: STEP_TIMEOUT });
|
|
115
|
+
}
|
|
116
|
+
async assertVisible(text) {
|
|
117
|
+
await this.page.getByText(text).first().waitFor({ state: "visible", timeout: STEP_TIMEOUT });
|
|
118
|
+
}
|
|
119
|
+
async assertNotVisible(text) {
|
|
120
|
+
// give the page a beat to render, then require absence
|
|
121
|
+
await this.page.waitForTimeout(1000);
|
|
122
|
+
const visible = await this.page
|
|
123
|
+
.getByText(text)
|
|
124
|
+
.first()
|
|
125
|
+
.isVisible()
|
|
126
|
+
.catch(() => false);
|
|
127
|
+
if (visible)
|
|
128
|
+
throw new Error(`Texto "${text}" está visível, mas não deveria estar.`);
|
|
129
|
+
}
|
|
130
|
+
async assertUrl(pattern) {
|
|
131
|
+
const url = this.page.url();
|
|
132
|
+
if (!url.includes(pattern)) {
|
|
133
|
+
throw new Error(`URL atual "${url}" não contém "${pattern}".`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async screenshot(label) {
|
|
137
|
+
this.screenshotCount += 1;
|
|
138
|
+
const safe = label.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 40);
|
|
139
|
+
const file = path.join(this.opts.runDir, `${String(this.screenshotCount).padStart(2, "0")}-${safe}.png`);
|
|
140
|
+
await this.page.screenshot({ path: file, fullPage: false });
|
|
141
|
+
this.screenshots.push(file);
|
|
142
|
+
return file;
|
|
143
|
+
}
|
|
144
|
+
/** Replays one recorded step (used by the deterministic runner). */
|
|
145
|
+
async executeStep(step) {
|
|
146
|
+
switch (step.kind) {
|
|
147
|
+
case "navigate":
|
|
148
|
+
return this.navigate(step.url);
|
|
149
|
+
case "click":
|
|
150
|
+
return void (await this.targetLocator(step.target).click());
|
|
151
|
+
case "fill":
|
|
152
|
+
return void (await this.targetLocator(step.target).fill(resolveEnvValue(step.value)));
|
|
153
|
+
case "select":
|
|
154
|
+
return void (await this.targetLocator(step.target).selectOption(resolveEnvValue(step.value)));
|
|
155
|
+
case "press":
|
|
156
|
+
return this.press(step.key);
|
|
157
|
+
case "waitForText":
|
|
158
|
+
return this.waitForText(step.text);
|
|
159
|
+
case "waitForUrl":
|
|
160
|
+
return this.waitForUrl(step.pattern);
|
|
161
|
+
case "assertVisible":
|
|
162
|
+
return this.assertVisible(step.text);
|
|
163
|
+
case "assertNotVisible":
|
|
164
|
+
return this.assertNotVisible(step.text);
|
|
165
|
+
case "assertUrl":
|
|
166
|
+
return this.assertUrl(step.pattern);
|
|
167
|
+
case "screenshot":
|
|
168
|
+
return void (await this.screenshot(step.label));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
targetLocator(target) {
|
|
172
|
+
if (target.role && target.name) {
|
|
173
|
+
return this.page
|
|
174
|
+
.getByRole(target.role, {
|
|
175
|
+
name: target.name,
|
|
176
|
+
exact: true,
|
|
177
|
+
})
|
|
178
|
+
.first();
|
|
179
|
+
}
|
|
180
|
+
if (target.css)
|
|
181
|
+
return this.page.locator(target.css);
|
|
182
|
+
throw new Error(`Target sem estratégia de localização: ${target.description}`);
|
|
183
|
+
}
|
|
184
|
+
async close() {
|
|
185
|
+
let tracePath;
|
|
186
|
+
try {
|
|
187
|
+
tracePath = path.join(this.opts.runDir, "trace.zip");
|
|
188
|
+
await this.context.tracing.stop({ path: tracePath });
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
tracePath = undefined;
|
|
192
|
+
}
|
|
193
|
+
await this.context.close().catch(() => { });
|
|
194
|
+
await this.browser.close().catch(() => { });
|
|
195
|
+
return tracePath;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/** Replaces $ENV:VAR_NAME placeholders so secrets never live in committed scripts. */
|
|
199
|
+
export function resolveEnvValue(value) {
|
|
200
|
+
return value.replace(/\$ENV:([A-Z0-9_]+)/g, (_, name) => {
|
|
201
|
+
const v = process.env[name];
|
|
202
|
+
if (v === undefined)
|
|
203
|
+
throw new Error(`Env var ${name} (referenciada como $ENV:${name}) não definida.`);
|
|
204
|
+
return v;
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/** Runs inside the page: collects visible interactive elements + text excerpt. */
|
|
208
|
+
const snapshotScript = `(() => {
|
|
209
|
+
const ROLE_BY_TAG = {
|
|
210
|
+
a: "link", button: "button", select: "combobox", textarea: "textbox",
|
|
211
|
+
summary: "button", option: "option",
|
|
212
|
+
};
|
|
213
|
+
const INPUT_ROLES = {
|
|
214
|
+
button: "button", submit: "button", reset: "button", checkbox: "checkbox",
|
|
215
|
+
radio: "radio", range: "slider", search: "searchbox",
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
function isVisible(el) {
|
|
219
|
+
const rect = el.getBoundingClientRect();
|
|
220
|
+
if (rect.width === 0 || rect.height === 0) return false;
|
|
221
|
+
const style = getComputedStyle(el);
|
|
222
|
+
return style.visibility !== "hidden" && style.display !== "none";
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function roleOf(el) {
|
|
226
|
+
const explicit = el.getAttribute("role");
|
|
227
|
+
if (explicit) return explicit;
|
|
228
|
+
const tag = el.tagName.toLowerCase();
|
|
229
|
+
if (tag === "input") {
|
|
230
|
+
const type = (el.getAttribute("type") || "text").toLowerCase();
|
|
231
|
+
return INPUT_ROLES[type] || "textbox";
|
|
232
|
+
}
|
|
233
|
+
if (tag === "a" && !el.hasAttribute("href")) return "";
|
|
234
|
+
return ROLE_BY_TAG[tag] || "";
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function nameOf(el) {
|
|
238
|
+
const aria = el.getAttribute("aria-label");
|
|
239
|
+
if (aria) return aria.trim();
|
|
240
|
+
const labelledBy = el.getAttribute("aria-labelledby");
|
|
241
|
+
if (labelledBy) {
|
|
242
|
+
const ref = document.getElementById(labelledBy.split(/\\s+/)[0]);
|
|
243
|
+
if (ref) return (ref.textContent || "").trim().slice(0, 80);
|
|
244
|
+
}
|
|
245
|
+
if (el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.tagName === "SELECT") {
|
|
246
|
+
if (el.id) {
|
|
247
|
+
const label = document.querySelector('label[for="' + CSS.escape(el.id) + '"]');
|
|
248
|
+
if (label) return (label.textContent || "").trim().slice(0, 80);
|
|
249
|
+
}
|
|
250
|
+
const placeholder = el.getAttribute("placeholder");
|
|
251
|
+
if (placeholder) return placeholder.trim();
|
|
252
|
+
if (el.tagName === "INPUT" && (el.type === "submit" || el.type === "button") && el.value) {
|
|
253
|
+
return el.value.trim();
|
|
254
|
+
}
|
|
255
|
+
return el.getAttribute("name") || "";
|
|
256
|
+
}
|
|
257
|
+
const img = el.querySelector("img[alt]");
|
|
258
|
+
const text = (el.textContent || "").replace(/\\s+/g, " ").trim();
|
|
259
|
+
return (text || (img ? img.getAttribute("alt") : "") || el.getAttribute("title") || "").slice(0, 80);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function cssPath(el) {
|
|
263
|
+
const parts = [];
|
|
264
|
+
let node = el;
|
|
265
|
+
while (node && node.nodeType === 1 && node.tagName !== "HTML") {
|
|
266
|
+
const tag = node.tagName.toLowerCase();
|
|
267
|
+
if (node.id) {
|
|
268
|
+
parts.unshift(tag + "#" + CSS.escape(node.id));
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
let index = 1;
|
|
272
|
+
let sibling = node.previousElementSibling;
|
|
273
|
+
while (sibling) {
|
|
274
|
+
if (sibling.tagName === node.tagName) index += 1;
|
|
275
|
+
sibling = sibling.previousElementSibling;
|
|
276
|
+
}
|
|
277
|
+
parts.unshift(tag + ":nth-of-type(" + index + ")");
|
|
278
|
+
node = node.parentElement;
|
|
279
|
+
}
|
|
280
|
+
return parts.join(" > ");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const selector = 'a[href], button, input, select, textarea, summary, [role="button"], [role="link"], [role="tab"], [role="menuitem"], [role="checkbox"], [role="switch"], [role="option"], [onclick], [tabindex]:not([tabindex="-1"])';
|
|
284
|
+
const nodes = Array.from(document.querySelectorAll(selector)).filter(isVisible);
|
|
285
|
+
const seen = new Set();
|
|
286
|
+
const elements = [];
|
|
287
|
+
let ref = 1;
|
|
288
|
+
for (const el of nodes) {
|
|
289
|
+
if (seen.has(el)) continue;
|
|
290
|
+
seen.add(el);
|
|
291
|
+
const role = roleOf(el);
|
|
292
|
+
const name = nameOf(el);
|
|
293
|
+
if (!role && !name) continue;
|
|
294
|
+
const info = { ref: ref++, role: role || el.tagName.toLowerCase(), name, css: cssPath(el), tag: el.tagName.toLowerCase() };
|
|
295
|
+
if (el.tagName === "INPUT" && el.type !== "password" && el.value) info.value = String(el.value).slice(0, 40);
|
|
296
|
+
if (el.tagName === "A") {
|
|
297
|
+
const href = el.getAttribute("href");
|
|
298
|
+
if (href && !href.startsWith("javascript:")) info.href = href.slice(0, 80);
|
|
299
|
+
}
|
|
300
|
+
elements.push(info);
|
|
301
|
+
if (elements.length >= 120) break;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const text = (document.body.innerText || "").replace(/\\n{3,}/g, "\\n\\n").slice(0, 2500);
|
|
305
|
+
return { elements, text };
|
|
306
|
+
})()`;
|
|
307
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/runner/browser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Step } from "../types.js";
|
|
2
|
+
import type { BrowserSession } from "./browser.js";
|
|
3
|
+
export interface ReplayOutcome {
|
|
4
|
+
passed: boolean;
|
|
5
|
+
failedStep?: string;
|
|
6
|
+
failedIndex?: number;
|
|
7
|
+
error?: string;
|
|
8
|
+
}
|
|
9
|
+
/** Describes a step for reports/errors. */
|
|
10
|
+
export declare function describeStep(step: Step): string;
|
|
11
|
+
/**
|
|
12
|
+
* Deterministic replay of a cached script. No LLM involved — this is the
|
|
13
|
+
* cheap path that runs on every CI push.
|
|
14
|
+
*/
|
|
15
|
+
export declare function replaySteps(session: BrowserSession, steps: Step[]): Promise<ReplayOutcome>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Describes a step for reports/errors. */
|
|
2
|
+
export function describeStep(step) {
|
|
3
|
+
switch (step.kind) {
|
|
4
|
+
case "navigate":
|
|
5
|
+
return `navegar para ${step.url}`;
|
|
6
|
+
case "click":
|
|
7
|
+
return `clicar em ${step.target.description}`;
|
|
8
|
+
case "fill":
|
|
9
|
+
return `preencher ${step.target.description}`;
|
|
10
|
+
case "select":
|
|
11
|
+
return `selecionar "${step.value}" em ${step.target.description}`;
|
|
12
|
+
case "press":
|
|
13
|
+
return `pressionar ${step.key}`;
|
|
14
|
+
case "waitForText":
|
|
15
|
+
return `esperar texto "${step.text}"`;
|
|
16
|
+
case "waitForUrl":
|
|
17
|
+
return `esperar URL conter "${step.pattern}"`;
|
|
18
|
+
case "assertVisible":
|
|
19
|
+
return `verificar texto visível "${step.text}"`;
|
|
20
|
+
case "assertNotVisible":
|
|
21
|
+
return `verificar texto AUSENTE "${step.text}"`;
|
|
22
|
+
case "assertUrl":
|
|
23
|
+
return `verificar URL contém "${step.pattern}"`;
|
|
24
|
+
case "screenshot":
|
|
25
|
+
return `screenshot "${step.label}"`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Deterministic replay of a cached script. No LLM involved — this is the
|
|
30
|
+
* cheap path that runs on every CI push.
|
|
31
|
+
*/
|
|
32
|
+
export async function replaySteps(session, steps) {
|
|
33
|
+
for (let i = 0; i < steps.length; i++) {
|
|
34
|
+
const step = steps[i];
|
|
35
|
+
try {
|
|
36
|
+
await session.executeStep(step);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
await session.screenshot(`replay-falhou-step-${i + 1}`).catch(() => { });
|
|
40
|
+
return {
|
|
41
|
+
passed: false,
|
|
42
|
+
failedIndex: i,
|
|
43
|
+
failedStep: describeStep(step),
|
|
44
|
+
error: error instanceof Error ? error.message : String(error),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
await session.screenshot("estado-final").catch(() => { });
|
|
49
|
+
return { passed: true };
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=script-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"script-runner.js","sourceRoot":"","sources":["../../src/runner/script-runner.ts"],"names":[],"mappings":"AAUA,2CAA2C;AAC3C,MAAM,UAAU,YAAY,CAAC,IAAU;IACrC,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,KAAK,UAAU;YACb,OAAO,gBAAgB,IAAI,CAAC,GAAG,EAAE,CAAC;QACpC,KAAK,OAAO;YACV,OAAO,aAAa,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QAChD,KAAK,MAAM;YACT,OAAO,aAAa,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QAChD,KAAK,QAAQ;YACX,OAAO,eAAe,IAAI,CAAC,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QACpE,KAAK,OAAO;YACV,OAAO,cAAc,IAAI,CAAC,GAAG,EAAE,CAAC;QAClC,KAAK,aAAa;YAChB,OAAO,kBAAkB,IAAI,CAAC,IAAI,GAAG,CAAC;QACxC,KAAK,YAAY;YACf,OAAO,uBAAuB,IAAI,CAAC,OAAO,GAAG,CAAC;QAChD,KAAK,eAAe;YAClB,OAAO,4BAA4B,IAAI,CAAC,IAAI,GAAG,CAAC;QAClD,KAAK,kBAAkB;YACrB,OAAO,4BAA4B,IAAI,CAAC,IAAI,GAAG,CAAC;QAClD,KAAK,WAAW;YACd,OAAO,yBAAyB,IAAI,CAAC,OAAO,GAAG,CAAC;QAClD,KAAK,YAAY;YACf,OAAO,eAAe,IAAI,CAAC,KAAK,GAAG,CAAC;IACxC,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAuB,EAAE,KAAa;IACtE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,CAAC;YACH,MAAM,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,CAAC,UAAU,CAAC,sBAAsB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YACxE,OAAO;gBACL,MAAM,EAAE,KAAK;gBACb,WAAW,EAAE,CAAC;gBACd,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC;gBAC9B,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAC9D,CAAC;QACJ,CAAC;IACH,CAAC;IACD,MAAM,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACzD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AAC1B,CAAC"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { RunResult, Scenario, Step } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Filesystem store inside the target project:
|
|
4
|
+
* .scout/scenarios.json — committed (the suite definition)
|
|
5
|
+
* .scout/scripts/<slug>.json — committed (cached deterministic steps)
|
|
6
|
+
* .scout/runs/<runId>/ — gitignored (artifacts per run)
|
|
7
|
+
* .scout/state/<profile>.json — gitignored (auth storageState)
|
|
8
|
+
*/
|
|
9
|
+
export declare class Store {
|
|
10
|
+
readonly root: string;
|
|
11
|
+
constructor(cwd?: string);
|
|
12
|
+
init(): void;
|
|
13
|
+
get scenariosFile(): string;
|
|
14
|
+
exists(): boolean;
|
|
15
|
+
listScenarios(): Scenario[];
|
|
16
|
+
saveScenarios(scenarios: Scenario[]): void;
|
|
17
|
+
addScenario(input: {
|
|
18
|
+
name: string;
|
|
19
|
+
scenario: string;
|
|
20
|
+
profile?: string;
|
|
21
|
+
notes?: string;
|
|
22
|
+
}): Scenario;
|
|
23
|
+
getScenario(idOrSlug: number | string): Scenario | undefined;
|
|
24
|
+
updateScenario(id: number, patch: Partial<Scenario>): void;
|
|
25
|
+
scriptPath(slug: string): string;
|
|
26
|
+
loadSteps(slug: string): Step[] | undefined;
|
|
27
|
+
saveSteps(slug: string, steps: Step[]): void;
|
|
28
|
+
newRunDir(slug: string): string;
|
|
29
|
+
saveRunResult(result: RunResult): void;
|
|
30
|
+
/** Latest run result per scenario slug (for `scout report`) */
|
|
31
|
+
latestRuns(): Map<string, RunResult>;
|
|
32
|
+
}
|
|
33
|
+
export declare function slugify(name: string): string;
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { SCOUT_DIR } from "./config.js";
|
|
4
|
+
/**
|
|
5
|
+
* Filesystem store inside the target project:
|
|
6
|
+
* .scout/scenarios.json — committed (the suite definition)
|
|
7
|
+
* .scout/scripts/<slug>.json — committed (cached deterministic steps)
|
|
8
|
+
* .scout/runs/<runId>/ — gitignored (artifacts per run)
|
|
9
|
+
* .scout/state/<profile>.json — gitignored (auth storageState)
|
|
10
|
+
*/
|
|
11
|
+
export class Store {
|
|
12
|
+
root;
|
|
13
|
+
constructor(cwd = process.cwd()) {
|
|
14
|
+
this.root = path.join(cwd, SCOUT_DIR);
|
|
15
|
+
}
|
|
16
|
+
init() {
|
|
17
|
+
fs.mkdirSync(path.join(this.root, "scripts"), { recursive: true });
|
|
18
|
+
fs.mkdirSync(path.join(this.root, "runs"), { recursive: true });
|
|
19
|
+
fs.mkdirSync(path.join(this.root, "state"), { recursive: true });
|
|
20
|
+
const gitignore = path.join(this.root, ".gitignore");
|
|
21
|
+
if (!fs.existsSync(gitignore)) {
|
|
22
|
+
fs.writeFileSync(gitignore, "runs/\nstate/\n");
|
|
23
|
+
}
|
|
24
|
+
if (!fs.existsSync(this.scenariosFile)) {
|
|
25
|
+
fs.writeFileSync(this.scenariosFile, "[]\n");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
get scenariosFile() {
|
|
29
|
+
return path.join(this.root, "scenarios.json");
|
|
30
|
+
}
|
|
31
|
+
exists() {
|
|
32
|
+
return fs.existsSync(this.scenariosFile);
|
|
33
|
+
}
|
|
34
|
+
listScenarios() {
|
|
35
|
+
if (!this.exists())
|
|
36
|
+
return [];
|
|
37
|
+
return JSON.parse(fs.readFileSync(this.scenariosFile, "utf8"));
|
|
38
|
+
}
|
|
39
|
+
saveScenarios(scenarios) {
|
|
40
|
+
fs.writeFileSync(this.scenariosFile, JSON.stringify(scenarios, null, 2) + "\n");
|
|
41
|
+
}
|
|
42
|
+
addScenario(input) {
|
|
43
|
+
const scenarios = this.listScenarios();
|
|
44
|
+
const id = scenarios.length ? Math.max(...scenarios.map((s) => s.id)) + 1 : 1;
|
|
45
|
+
const scenario = {
|
|
46
|
+
id,
|
|
47
|
+
slug: slugify(input.name),
|
|
48
|
+
name: input.name,
|
|
49
|
+
scenario: input.scenario,
|
|
50
|
+
profile: input.profile,
|
|
51
|
+
notes: input.notes,
|
|
52
|
+
status: "pending",
|
|
53
|
+
createdAt: new Date().toISOString(),
|
|
54
|
+
};
|
|
55
|
+
scenarios.push(scenario);
|
|
56
|
+
this.saveScenarios(scenarios);
|
|
57
|
+
return scenario;
|
|
58
|
+
}
|
|
59
|
+
getScenario(idOrSlug) {
|
|
60
|
+
return this.listScenarios().find((s) => s.id === Number(idOrSlug) || s.slug === idOrSlug);
|
|
61
|
+
}
|
|
62
|
+
updateScenario(id, patch) {
|
|
63
|
+
const scenarios = this.listScenarios();
|
|
64
|
+
const idx = scenarios.findIndex((s) => s.id === id);
|
|
65
|
+
if (idx === -1)
|
|
66
|
+
throw new Error(`Scenario ${id} not found`);
|
|
67
|
+
scenarios[idx] = { ...scenarios[idx], ...patch };
|
|
68
|
+
this.saveScenarios(scenarios);
|
|
69
|
+
}
|
|
70
|
+
// ---- cached deterministic scripts ----
|
|
71
|
+
scriptPath(slug) {
|
|
72
|
+
return path.join(this.root, "scripts", `${slug}.json`);
|
|
73
|
+
}
|
|
74
|
+
loadSteps(slug) {
|
|
75
|
+
const file = this.scriptPath(slug);
|
|
76
|
+
if (!fs.existsSync(file))
|
|
77
|
+
return undefined;
|
|
78
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
79
|
+
}
|
|
80
|
+
saveSteps(slug, steps) {
|
|
81
|
+
fs.writeFileSync(this.scriptPath(slug), JSON.stringify(steps, null, 2) + "\n");
|
|
82
|
+
}
|
|
83
|
+
// ---- runs ----
|
|
84
|
+
newRunDir(slug) {
|
|
85
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
86
|
+
const dir = path.join(this.root, "runs", `${stamp}-${slug}`);
|
|
87
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
88
|
+
return dir;
|
|
89
|
+
}
|
|
90
|
+
saveRunResult(result) {
|
|
91
|
+
fs.writeFileSync(path.join(result.runDir, "result.json"), JSON.stringify(result, null, 2) + "\n");
|
|
92
|
+
}
|
|
93
|
+
/** Latest run result per scenario slug (for `scout report`) */
|
|
94
|
+
latestRuns() {
|
|
95
|
+
const runsDir = path.join(this.root, "runs");
|
|
96
|
+
const map = new Map();
|
|
97
|
+
if (!fs.existsSync(runsDir))
|
|
98
|
+
return map;
|
|
99
|
+
const dirs = fs.readdirSync(runsDir).sort(); // timestamp prefix → chronological
|
|
100
|
+
for (const dir of dirs) {
|
|
101
|
+
const file = path.join(runsDir, dir, "result.json");
|
|
102
|
+
if (!fs.existsSync(file))
|
|
103
|
+
continue;
|
|
104
|
+
const result = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
105
|
+
map.set(result.slug, result); // later overwrites earlier
|
|
106
|
+
}
|
|
107
|
+
return map;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export function slugify(name) {
|
|
111
|
+
return name
|
|
112
|
+
.toLowerCase()
|
|
113
|
+
.normalize("NFD")
|
|
114
|
+
.replace(/[̀-ͯ]/g, "")
|
|
115
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
116
|
+
.replace(/^-|-$/g, "")
|
|
117
|
+
.slice(0, 60);
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** How a recorded step finds its element on replay. */
|
|
2
|
+
export interface Target {
|
|
3
|
+
/** ARIA role + accessible name — preferred, resilient to DOM refactors */
|
|
4
|
+
role?: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
/** CSS path fallback when role+name is not unique */
|
|
7
|
+
css?: string;
|
|
8
|
+
/** Human-readable label for reports */
|
|
9
|
+
description: string;
|
|
10
|
+
}
|
|
11
|
+
export type Step = {
|
|
12
|
+
kind: "navigate";
|
|
13
|
+
url: string;
|
|
14
|
+
} | {
|
|
15
|
+
kind: "click";
|
|
16
|
+
target: Target;
|
|
17
|
+
} | {
|
|
18
|
+
kind: "fill";
|
|
19
|
+
target: Target;
|
|
20
|
+
value: string;
|
|
21
|
+
} | {
|
|
22
|
+
kind: "select";
|
|
23
|
+
target: Target;
|
|
24
|
+
value: string;
|
|
25
|
+
} | {
|
|
26
|
+
kind: "press";
|
|
27
|
+
key: string;
|
|
28
|
+
} | {
|
|
29
|
+
kind: "waitForText";
|
|
30
|
+
text: string;
|
|
31
|
+
} | {
|
|
32
|
+
kind: "waitForUrl";
|
|
33
|
+
pattern: string;
|
|
34
|
+
} | {
|
|
35
|
+
kind: "assertVisible";
|
|
36
|
+
text: string;
|
|
37
|
+
} | {
|
|
38
|
+
kind: "assertNotVisible";
|
|
39
|
+
text: string;
|
|
40
|
+
} | {
|
|
41
|
+
kind: "assertUrl";
|
|
42
|
+
pattern: string;
|
|
43
|
+
} | {
|
|
44
|
+
kind: "screenshot";
|
|
45
|
+
label: string;
|
|
46
|
+
};
|
|
47
|
+
export type Verdict = "verified" | "failed" | "partial" | "blocked";
|
|
48
|
+
export type ScenarioStatus = Verdict | "pending";
|
|
49
|
+
export interface Scenario {
|
|
50
|
+
id: number;
|
|
51
|
+
slug: string;
|
|
52
|
+
name: string;
|
|
53
|
+
/** Natural-language description of the flow + expected behavior */
|
|
54
|
+
scenario: string;
|
|
55
|
+
/** Auth profile name from scout.config.json (omit = anonymous) */
|
|
56
|
+
profile?: string;
|
|
57
|
+
status: ScenarioStatus;
|
|
58
|
+
createdAt: string;
|
|
59
|
+
lastRun?: string;
|
|
60
|
+
notes?: string;
|
|
61
|
+
}
|
|
62
|
+
export interface RunResult {
|
|
63
|
+
scenarioId: number;
|
|
64
|
+
slug: string;
|
|
65
|
+
/** replay = cached deterministic steps; ai = agent-driven run */
|
|
66
|
+
mode: "replay" | "ai";
|
|
67
|
+
verdict: Verdict;
|
|
68
|
+
reason: string;
|
|
69
|
+
stepCount?: number;
|
|
70
|
+
failedStep?: string;
|
|
71
|
+
runDir: string;
|
|
72
|
+
startedAt: string;
|
|
73
|
+
durationMs: number;
|
|
74
|
+
screenshots: string[];
|
|
75
|
+
trace?: string;
|
|
76
|
+
/** true when this AI run replaced a broken cached script */
|
|
77
|
+
healed?: boolean;
|
|
78
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|