@hover-dev/core 0.16.0 → 0.17.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/README.md +26 -55
- package/dist/agentDirectives.d.ts +55 -0
- package/dist/agentDirectives.d.ts.map +1 -0
- package/dist/agentDirectives.js +276 -0
- package/dist/agents/claude.d.ts.map +1 -1
- package/dist/agents/claude.js +28 -3
- package/dist/agents/codex.d.ts.map +1 -1
- package/dist/agents/codex.js +29 -14
- package/dist/agents/invoke.d.ts.map +1 -1
- package/dist/agents/invoke.js +3 -6
- package/dist/agents/registry.d.ts.map +1 -1
- package/dist/agents/registry.js +0 -4
- package/dist/agents/types.d.ts +19 -11
- package/dist/agents/types.d.ts.map +1 -1
- package/dist/engine.d.ts +53 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +78 -0
- package/dist/mcp/actuateServer.d.ts +3 -0
- package/dist/mcp/actuateServer.d.ts.map +1 -0
- package/dist/mcp/actuateServer.js +594 -0
- package/dist/mcp/sourceFence.d.ts.map +1 -1
- package/dist/mcp/sourceFence.js +4 -0
- package/dist/mcp/sourceServer.js +75 -0
- package/dist/memory/businessMemory.d.ts +29 -0
- package/dist/memory/businessMemory.d.ts.map +1 -0
- package/dist/memory/businessMemory.js +125 -0
- package/dist/modes.d.ts +39 -0
- package/dist/modes.d.ts.map +1 -0
- package/dist/modes.js +34 -0
- package/dist/playwright/cdpStatus.d.ts +0 -15
- package/dist/playwright/cdpStatus.d.ts.map +1 -1
- package/dist/playwright/cdpStatus.js +0 -67
- package/dist/playwright/launchChrome.d.ts +18 -0
- package/dist/playwright/launchChrome.d.ts.map +1 -1
- package/dist/playwright/launchChrome.js +46 -3
- package/dist/playwright/resolveMcpConfig.d.ts +7 -1
- package/dist/playwright/resolveMcpConfig.d.ts.map +1 -1
- package/dist/playwright/resolveMcpConfig.js +22 -4
- package/dist/plugin-api.d.ts +28 -26
- package/dist/plugin-api.d.ts.map +1 -1
- package/dist/plugin-api.js +2 -2
- package/dist/qa/candidates.d.ts +32 -0
- package/dist/qa/candidates.d.ts.map +1 -0
- package/dist/qa/candidates.js +20 -0
- package/dist/qa/classify.d.ts +38 -0
- package/dist/qa/classify.d.ts.map +1 -0
- package/dist/qa/classify.js +138 -0
- package/dist/qa/intensity.d.ts +33 -0
- package/dist/qa/intensity.d.ts.map +1 -0
- package/dist/qa/intensity.js +25 -0
- package/dist/qa/qaReport.d.ts +19 -0
- package/dist/qa/qaReport.d.ts.map +1 -0
- package/dist/qa/qaReport.js +50 -0
- package/dist/runSession.d.ts +14 -3
- package/dist/runSession.d.ts.map +1 -1
- package/dist/runSession.js +26 -11
- package/dist/service/cdpHandlers.d.ts +1 -21
- package/dist/service/cdpHandlers.d.ts.map +1 -1
- package/dist/service/cdpHandlers.js +4 -39
- package/dist/service/cdpHint.d.ts +21 -28
- package/dist/service/cdpHint.d.ts.map +1 -1
- package/dist/service/cdpHint.js +106 -164
- package/dist/service/relayHandlers.d.ts +28 -0
- package/dist/service/relayHandlers.d.ts.map +1 -0
- package/dist/service/relayHandlers.js +105 -0
- package/dist/service/saveHandlers.d.ts +1 -3
- package/dist/service/saveHandlers.d.ts.map +1 -1
- package/dist/service/saveHandlers.js +17 -15
- package/dist/service/types.d.ts +108 -8
- package/dist/service/types.d.ts.map +1 -1
- package/dist/service.d.ts +7 -3
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +907 -200
- package/dist/sessions/sessions.d.ts +125 -0
- package/dist/sessions/sessions.d.ts.map +1 -0
- package/dist/sessions/sessions.js +175 -0
- package/dist/specs/authFixture.d.ts +30 -0
- package/dist/specs/authFixture.d.ts.map +1 -0
- package/dist/specs/authFixture.js +145 -0
- package/dist/specs/businessMap.d.ts +29 -0
- package/dist/specs/businessMap.d.ts.map +1 -0
- package/dist/specs/businessMap.js +95 -0
- package/dist/specs/detectSharedFlows.d.ts +1 -1
- package/dist/specs/detectSharedFlows.d.ts.map +1 -1
- package/dist/specs/detectSharedFlows.js +20 -21
- package/dist/specs/generatePageObject.d.ts +1 -1
- package/dist/specs/generatePageObject.d.ts.map +1 -1
- package/dist/specs/healPrompt.d.ts +19 -0
- package/dist/specs/healPrompt.d.ts.map +1 -0
- package/dist/specs/healPrompt.js +48 -0
- package/dist/specs/humanSteps.d.ts +4 -8
- package/dist/specs/humanSteps.d.ts.map +1 -1
- package/dist/specs/humanSteps.js +6 -1
- package/dist/specs/optimizeSpec.d.ts +15 -8
- package/dist/specs/optimizeSpec.d.ts.map +1 -1
- package/dist/specs/optimizeSpec.js +71 -41
- package/dist/specs/optimizeSpecWithAgent.d.ts +0 -2
- package/dist/specs/optimizeSpecWithAgent.d.ts.map +1 -1
- package/dist/specs/optimizeSpecWithAgent.js +0 -1
- package/dist/specs/pageObjectManifest.d.ts +3 -1
- package/dist/specs/pageObjectManifest.d.ts.map +1 -1
- package/dist/specs/pageObjectManifest.js +13 -9
- package/dist/specs/replayGrounded.d.ts +45 -0
- package/dist/specs/replayGrounded.d.ts.map +1 -0
- package/dist/specs/replayGrounded.js +155 -0
- package/dist/specs/runFailures.d.ts +34 -0
- package/dist/specs/runFailures.d.ts.map +1 -0
- package/dist/specs/runFailures.js +93 -0
- package/dist/specs/seeds.d.ts +16 -15
- package/dist/specs/seeds.d.ts.map +1 -1
- package/dist/specs/seeds.js +86 -54
- package/dist/specs/sidecar.d.ts +34 -6
- package/dist/specs/sidecar.d.ts.map +1 -1
- package/dist/specs/sidecar.js +79 -9
- package/dist/specs/specStep.d.ts +21 -0
- package/dist/specs/specStep.d.ts.map +1 -0
- package/dist/specs/specStep.js +1 -0
- package/dist/specs/text.d.ts +8 -6
- package/dist/specs/text.d.ts.map +1 -1
- package/dist/specs/text.js +10 -7
- package/dist/specs/writeSpec.d.ts +62 -1
- package/dist/specs/writeSpec.d.ts.map +1 -1
- package/dist/specs/writeSpec.js +596 -21
- package/package.json +6 -9
- package/dist/agents/aider.d.ts +0 -16
- package/dist/agents/aider.d.ts.map +0 -1
- package/dist/agents/aider.js +0 -161
- package/dist/agents/cursor.d.ts +0 -18
- package/dist/agents/cursor.d.ts.map +0 -1
- package/dist/agents/cursor.js +0 -220
- package/dist/playwright/raiseWindow.d.ts +0 -10
- package/dist/playwright/raiseWindow.d.ts.map +0 -1
- package/dist/playwright/raiseWindow.js +0 -158
- package/dist/scripts/bench-multi-tab.d.ts +0 -2
- package/dist/scripts/bench-multi-tab.d.ts.map +0 -1
- package/dist/scripts/bench-multi-tab.js +0 -192
- package/dist/scripts/bench-ttfb.d.ts +0 -2
- package/dist/scripts/bench-ttfb.d.ts.map +0 -1
- package/dist/scripts/bench-ttfb.js +0 -127
- package/dist/scripts/start-chrome.d.ts +0 -3
- package/dist/scripts/start-chrome.d.ts.map +0 -1
- package/dist/scripts/start-chrome.js +0 -23
- package/dist/skills/writeSkill.d.ts +0 -27
- package/dist/skills/writeSkill.d.ts.map +0 -1
- package/dist/skills/writeSkill.js +0 -13
- package/dist/specs/listSpecs.d.ts +0 -52
- package/dist/specs/listSpecs.d.ts.map +0 -1
- package/dist/specs/listSpecs.js +0 -139
- package/dist/specs/optimizationSuggestion.d.ts +0 -26
- package/dist/specs/optimizationSuggestion.d.ts.map +0 -1
- package/dist/specs/optimizationSuggestion.js +0 -28
- package/dist/specs/writeCaseCsv.d.ts +0 -28
- package/dist/specs/writeCaseCsv.d.ts.map +0 -1
- package/dist/specs/writeCaseCsv.js +0 -134
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
* same signature even though their typed values differ — value vs. structure
|
|
18
18
|
* separation is mechanical (D4).
|
|
19
19
|
*/
|
|
20
|
-
import { readdir
|
|
20
|
+
import { readdir } from 'node:fs/promises';
|
|
21
21
|
import { join } from 'node:path';
|
|
22
|
-
import { sidecarDir } from './sidecar.js';
|
|
22
|
+
import { sidecarDir, legacySidecarDir, parseSidecarFile } from './sidecar.js';
|
|
23
23
|
import { humanStep } from './humanSteps.js';
|
|
24
24
|
/**
|
|
25
25
|
* Reduce one captured step to a signature string: the tool plus its structural
|
|
@@ -60,31 +60,30 @@ export function stepSignature(tool, rawInput) {
|
|
|
60
60
|
return null;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
-
/** Read and parse every sidecar under `.hover
|
|
64
|
-
*
|
|
63
|
+
/** Read and parse every sidecar under `.hover/sidecars/`, unioned with any
|
|
64
|
+
* still in the legacy `__vibe_tests__/.hover/` home (current home wins on a
|
|
65
|
+
* slug collision). Malformed files are skipped (better to detect across the
|
|
66
|
+
* valid ones than fail because one is broken). */
|
|
65
67
|
async function readSidecars(devRoot) {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
entries = await readdir(dir);
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
return [];
|
|
73
|
-
}
|
|
74
|
-
const out = [];
|
|
75
|
-
for (const entry of entries) {
|
|
76
|
-
if (!entry.endsWith('.json'))
|
|
77
|
-
continue;
|
|
68
|
+
const bySlug = new Map();
|
|
69
|
+
for (const dir of [legacySidecarDir(devRoot), sidecarDir(devRoot)]) {
|
|
70
|
+
let entries;
|
|
78
71
|
try {
|
|
79
|
-
|
|
80
|
-
if (Array.isArray(sc.steps) && typeof sc.slug === 'string')
|
|
81
|
-
out.push(sc);
|
|
72
|
+
entries = await readdir(dir);
|
|
82
73
|
}
|
|
83
74
|
catch {
|
|
84
|
-
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
if (!entry.endsWith('.json'))
|
|
79
|
+
continue;
|
|
80
|
+
const sc = await parseSidecarFile(join(dir, entry));
|
|
81
|
+
if (sc && Array.isArray(sc.steps) && typeof sc.slug === 'string') {
|
|
82
|
+
bySlug.set(sc.slug, sc); // later dir (current home) overwrites
|
|
83
|
+
}
|
|
85
84
|
}
|
|
86
85
|
}
|
|
87
|
-
return
|
|
86
|
+
return [...bySlug.values()];
|
|
88
87
|
}
|
|
89
88
|
/** Project a sidecar's steps to (signature, prose) lists, dropping
|
|
90
89
|
* non-flow steps. */
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* emitters with a `this.page` page variable, so a Page Object's selectors match
|
|
12
12
|
* the crystallized specs exactly.
|
|
13
13
|
*/
|
|
14
|
-
import type { SkillStep } from '../
|
|
14
|
+
import type { SkillStep } from '../specs/specStep.js';
|
|
15
15
|
export interface PageObjectResult {
|
|
16
16
|
/** PascalCase class name, e.g. `LoginPage`. */
|
|
17
17
|
className: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generatePageObject.d.ts","sourceRoot":"","sources":["../../src/specs/generatePageObject.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"generatePageObject.d.ts","sourceRoot":"","sources":["../../src/specs/generatePageObject.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAUtD,MAAM,WAAW,gBAAgB;IAC/B,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,SAAS,EAAE,EAClB,QAAQ,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GACzD,gBAAgB,CAoElB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-heal Stage 2 — the heal prompt.
|
|
3
|
+
*
|
|
4
|
+
* When a saved spec fails on replay (the app changed → a locator no longer
|
|
5
|
+
* matches), healing re-performs the flow against the LIVE app and fixes the
|
|
6
|
+
* broken step(s). This builds the instruction that drives that run: the agent
|
|
7
|
+
* gets the spec's intended flow (its source) + exactly what broke (the parsed
|
|
8
|
+
* failures), and re-locates via the grounded control tools + source reader.
|
|
9
|
+
*
|
|
10
|
+
* The heal then crystallizes through the normal candidate flow (record_candidate
|
|
11
|
+
* / fallback) — deterministic re-render, human-reviewed; the agent re-locates,
|
|
12
|
+
* it does not author the spec. Pure: prompt-building only.
|
|
13
|
+
*/
|
|
14
|
+
import type { RunFailure } from './runFailures.js';
|
|
15
|
+
/** A short, chat-friendly label for the heal run (the user bubble), vs the full
|
|
16
|
+
* prompt the agent receives. */
|
|
17
|
+
export declare function healLabel(slug: string): string;
|
|
18
|
+
export declare function buildHealPrompt(slug: string, specSource: string, failures: RunFailure[]): string;
|
|
19
|
+
//# sourceMappingURL=healPrompt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"healPrompt.d.ts","sourceRoot":"","sources":["../../src/specs/healPrompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD;iCACiC;AACjC,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,MAAM,CA2ChG"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** A short, chat-friendly label for the heal run (the user bubble), vs the full
|
|
2
|
+
* prompt the agent receives. */
|
|
3
|
+
export function healLabel(slug) {
|
|
4
|
+
return `🏥 Heal "${slug}" — re-running the flow to fix what changed`;
|
|
5
|
+
}
|
|
6
|
+
export function buildHealPrompt(slug, specSource, failures) {
|
|
7
|
+
const failureLines = failures.length > 0
|
|
8
|
+
? failures.map(f => {
|
|
9
|
+
const what = f.failingLocator
|
|
10
|
+
? `${f.failingAction ?? 'a step'} on \`${f.failingLocator}\` no longer matches`
|
|
11
|
+
: (f.error || 'a step failed');
|
|
12
|
+
return ` - ${what}`;
|
|
13
|
+
}).join('\n')
|
|
14
|
+
: ' - (no structured failure captured — re-run the whole flow and fix whatever no longer works)';
|
|
15
|
+
return [
|
|
16
|
+
`You are REPAIRING a saved Playwright test that no longer passes because the`,
|
|
17
|
+
`app under test changed. Re-perform its flow against the LIVE app and fix only`,
|
|
18
|
+
`the step(s) that broke — do not invent new behavior or add unrelated steps.`,
|
|
19
|
+
``,
|
|
20
|
+
`Test: "${slug}"`,
|
|
21
|
+
``,
|
|
22
|
+
`What it does (the saved spec — this is the intended flow to reproduce):`,
|
|
23
|
+
`\`\`\`ts`,
|
|
24
|
+
specSource.trim(),
|
|
25
|
+
`\`\`\``,
|
|
26
|
+
``,
|
|
27
|
+
`What broke on replay:`,
|
|
28
|
+
failureLines,
|
|
29
|
+
``,
|
|
30
|
+
`How to repair it:`,
|
|
31
|
+
` - Open the app and walk the SAME flow, interacting through the grounded`,
|
|
32
|
+
` control tools (click_control / fill_control / select_control / …) so the`,
|
|
33
|
+
` repaired selectors stay replayable.`,
|
|
34
|
+
` - Where a step's old selector no longer matches, find the element that step`,
|
|
35
|
+
` INTENDED and operate that instead. Read the component source if you are`,
|
|
36
|
+
` unsure why it moved or what replaced it.`,
|
|
37
|
+
` - JUDGE broke-vs-changed: if a failure is because the app INTENTIONALLY`,
|
|
38
|
+
` changed (the feature now works differently — not a regression), adapt the`,
|
|
39
|
+
` flow to the new correct behavior and say so in your summary. If it looks`,
|
|
40
|
+
` like a real regression (the app is wrong), report it as a finding and heal`,
|
|
41
|
+
` to what the test originally intended.`,
|
|
42
|
+
` - Keep dynamic content dynamic: if a step grounds on data that varies`,
|
|
43
|
+
` run-to-run, flag it dynamic — don't freeze this run's value.`,
|
|
44
|
+
``,
|
|
45
|
+
`When the flow works end to end, call record_candidate with the test's name so`,
|
|
46
|
+
`the repaired version can be saved. Do not write any file yourself.`,
|
|
47
|
+
].join('\n');
|
|
48
|
+
}
|
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Translate the captured `browser_*` tool calls into plain English.
|
|
3
3
|
*
|
|
4
|
-
* Used by
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* `getByRole(...)`.
|
|
8
|
-
* - writeCaseCsv.ts — to populate the Step column of an
|
|
9
|
-
* Xray-compatible test case CSV, so the same prose travels into
|
|
10
|
-
* Jira / Xray / Zephyr.
|
|
4
|
+
* Used by writeSpec.ts to enrich the generated `.spec.ts` JSDoc with a
|
|
5
|
+
* numbered "Steps:" block that QA / PMs can read without grokking
|
|
6
|
+
* `getByRole(...)`.
|
|
11
7
|
*
|
|
12
8
|
* Mirrors the tool dispatch table in writeSpec.ts:translateStep — when
|
|
13
9
|
* a new replayable browser action is added there, add it here too.
|
|
14
10
|
*/
|
|
15
|
-
import type { SkillStep } from '../
|
|
11
|
+
import type { SkillStep } from '../specs/specStep.js';
|
|
16
12
|
/** A single human-readable line for one tool call, or null to skip. */
|
|
17
13
|
export declare function humanStep(tool: string, rawInput: unknown): string | null;
|
|
18
14
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"humanSteps.d.ts","sourceRoot":"","sources":["../../src/specs/humanSteps.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"humanSteps.d.ts","sourceRoot":"","sources":["../../src/specs/humanSteps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEtD,uEAAuE;AACvE,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAwDxE;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,MAAM,EAAE,CAmBvD"}
|
package/dist/specs/humanSteps.js
CHANGED
|
@@ -91,7 +91,12 @@ function describe(raw) {
|
|
|
91
91
|
const s = String(raw ?? '').trim();
|
|
92
92
|
return s.length > 0 ? s : 'the target element';
|
|
93
93
|
}
|
|
94
|
-
/** Wrap in double-quotes for prose; escape internal quotes.
|
|
94
|
+
/** Wrap in double-quotes for prose; escape internal quotes. A redacted
|
|
95
|
+
* credential (stored as a `process.env.X …` expression) shows as the masked
|
|
96
|
+
* `$X` instead — the prose, like the code, never reveals the secret. */
|
|
95
97
|
function quote(s) {
|
|
98
|
+
const env = /^process\.env\.([A-Za-z0-9_]+)/.exec(s);
|
|
99
|
+
if (env)
|
|
100
|
+
return `$${env[1]}`;
|
|
96
101
|
return `"${s.replace(/"/g, '\\"')}"`;
|
|
97
102
|
}
|
|
@@ -5,6 +5,20 @@ export declare class OptimizeError extends Error {
|
|
|
5
5
|
}
|
|
6
6
|
/** Runs the codegen LLM on a prompt and returns its raw text output. */
|
|
7
7
|
export type RunCodegen = (prompt: string) => Promise<string>;
|
|
8
|
+
/** Project context fed to the refinement pass so the candidate FITS the existing
|
|
9
|
+
* suite: the team's conventions + the reusable Page Objects to prefer over raw
|
|
10
|
+
* locators. Relevant files only (POMs + conventions), NOT the whole suite — the
|
|
11
|
+
* refinement is a cheap pass, keep the context bounded. */
|
|
12
|
+
export interface SuiteContext {
|
|
13
|
+
conventions?: string;
|
|
14
|
+
pages: {
|
|
15
|
+
name: string;
|
|
16
|
+
source: string;
|
|
17
|
+
}[];
|
|
18
|
+
}
|
|
19
|
+
/** Best-effort gather of the suite context (conventions.md + __vibe_tests__/pages
|
|
20
|
+
* Page Objects). Missing files → empty; never throws. */
|
|
21
|
+
export declare function gatherSuiteContext(devRoot: string): Promise<SuiteContext>;
|
|
8
22
|
export interface OptimizeResult {
|
|
9
23
|
/** Absolute path of the written candidate (never the original spec). */
|
|
10
24
|
candidatePath: string;
|
|
@@ -20,7 +34,7 @@ export declare function optimizeSpec(devRoot: string, slug: string, runCodegen:
|
|
|
20
34
|
* same rules the deterministic path enforces (semantic selectors, no XPath, no
|
|
21
35
|
* waitForTimeout, keep the test.step shape).
|
|
22
36
|
*/
|
|
23
|
-
export declare function buildOptimizePrompt(draft: string, sidecar: SpecSidecar | null, seeds?: SeedRule[]): string;
|
|
37
|
+
export declare function buildOptimizePrompt(draft: string, sidecar: SpecSidecar | null, seeds?: SeedRule[], suite?: SuiteContext): string;
|
|
24
38
|
/** Strip a ```ts fence if the model wrapped its output in one. */
|
|
25
39
|
export declare function extractCode(raw: string): string;
|
|
26
40
|
/**
|
|
@@ -32,11 +46,4 @@ export declare function validateSpecCode(code: string): {
|
|
|
32
46
|
ok: boolean;
|
|
33
47
|
errors: string[];
|
|
34
48
|
};
|
|
35
|
-
/** Promote an optimization candidate to the real spec (overwriting it) and
|
|
36
|
-
* remove the candidate. Returns the written spec path. The human's "Use
|
|
37
|
-
* optimized" / `mv` action. */
|
|
38
|
-
export declare function promoteOptimized(devRoot: string, slug: string): Promise<string>;
|
|
39
|
-
/** Discard an optimization candidate (delete the .draft, leave the spec). The
|
|
40
|
-
* human's "Keep original". */
|
|
41
|
-
export declare function discardOptimized(devRoot: string, slug: string): Promise<void>;
|
|
42
49
|
//# sourceMappingURL=optimizeSpec.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"optimizeSpec.d.ts","sourceRoot":"","sources":["../../src/specs/optimizeSpec.ts"],"names":[],"mappings":"AAeA,OAAO,
|
|
1
|
+
{"version":3,"file":"optimizeSpec.d.ts","sourceRoot":"","sources":["../../src/specs/optimizeSpec.ts"],"names":[],"mappings":"AAeA,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAgC,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGzE,qBAAa,aAAc,SAAQ,KAAK;gBAC1B,OAAO,EAAE,MAAM;CAI5B;AAED,wEAAwE;AACxE,MAAM,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAE7D;;;4DAG4D;AAC5D,MAAM,WAAW,YAAY;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC3C;AAOD;0DAC0D;AAC1D,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAgB/E;AAED,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,aAAa,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb;2EACuE;IACvE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,cAAc,CAAC,CAwCzB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,WAAW,GAAG,IAAI,EAC3B,KAAK,GAAE,QAAQ,EAAO,EACtB,KAAK,GAAE,YAA4B,GAClC,MAAM,CAkFR;AAED,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI/C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAWhF"}
|
|
@@ -4,17 +4,17 @@
|
|
|
4
4
|
* Reads a deterministic draft spec + its sidecar, asks an LLM (the codegen
|
|
5
5
|
* mode — no browser, no MCP) to improve it (chiefly: add assertions for the
|
|
6
6
|
* feedback the session observed), validates the result, and writes it as a
|
|
7
|
-
* CANDIDATE at `.hover/optimized/<slug>.spec.ts.draft` — never overwriting the
|
|
7
|
+
* CANDIDATE at `.hover/cache/optimized/<slug>.spec.ts.draft` — never overwriting the
|
|
8
8
|
* original (D10). A human promotes or discards it via diff.
|
|
9
9
|
*
|
|
10
10
|
* The LLM call is injected (`runCodegen`) so callers wire their own agent and
|
|
11
11
|
* tests run deterministically without spawning anything.
|
|
12
12
|
*/
|
|
13
|
-
import { readFile, mkdir, writeFile,
|
|
13
|
+
import { readFile, mkdir, writeFile, readdir } from 'node:fs/promises';
|
|
14
14
|
import { join } from 'node:path';
|
|
15
15
|
import { Project } from 'ts-morph';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
16
|
+
import { readSidecar } from './sidecar.js';
|
|
17
|
+
import { BUILTIN_SEEDS, relevantSeeds } from './seeds.js';
|
|
18
18
|
import { softBatch } from './softBatch.js';
|
|
19
19
|
export class OptimizeError extends Error {
|
|
20
20
|
constructor(message) {
|
|
@@ -22,6 +22,32 @@ export class OptimizeError extends Error {
|
|
|
22
22
|
this.name = 'OptimizeError';
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
+
/** Total Page-Object source budget injected into the prompt (chars). Keeps the
|
|
26
|
+
* refinement context bounded on a large suite; extra POMs are dropped (logged in
|
|
27
|
+
* the prompt) rather than blowing the window. */
|
|
28
|
+
const POM_CONTEXT_BUDGET = 16_000;
|
|
29
|
+
/** Best-effort gather of the suite context (conventions.md + __vibe_tests__/pages
|
|
30
|
+
* Page Objects). Missing files → empty; never throws. */
|
|
31
|
+
export async function gatherSuiteContext(devRoot) {
|
|
32
|
+
const conventions = (await readFile(join(devRoot, '.hover', 'conventions.md'), 'utf-8').catch(() => ''))
|
|
33
|
+
.trim() || undefined;
|
|
34
|
+
const pages = [];
|
|
35
|
+
let used = 0;
|
|
36
|
+
try {
|
|
37
|
+
const dir = join(devRoot, '__vibe_tests__', 'pages');
|
|
38
|
+
for (const f of (await readdir(dir)).sort()) {
|
|
39
|
+
if (!f.endsWith('.ts'))
|
|
40
|
+
continue;
|
|
41
|
+
const source = (await readFile(join(dir, f), 'utf-8').catch(() => '')).trim();
|
|
42
|
+
if (!source || used + source.length > POM_CONTEXT_BUDGET)
|
|
43
|
+
continue;
|
|
44
|
+
used += source.length;
|
|
45
|
+
pages.push({ name: f, source });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch { /* no pages dir — plain spec, no POMs to reuse */ }
|
|
49
|
+
return { conventions, pages };
|
|
50
|
+
}
|
|
25
51
|
export async function optimizeSpec(devRoot, slug, runCodegen) {
|
|
26
52
|
const specPath = join(devRoot, '__vibe_tests__', `${slug}.spec.ts`);
|
|
27
53
|
let draft;
|
|
@@ -31,18 +57,14 @@ export async function optimizeSpec(devRoot, slug, runCodegen) {
|
|
|
31
57
|
catch {
|
|
32
58
|
throw new OptimizeError(`spec not found: ${slug} (looked at ${specPath})`);
|
|
33
59
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
sidecar = JSON.parse(await readFile(join(sidecarDir(devRoot), `${slug}.json`), 'utf-8'));
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
/* no sidecar — optimize from the draft alone */
|
|
40
|
-
}
|
|
60
|
+
// Legacy-aware read; null → no sidecar, optimize from the draft alone.
|
|
61
|
+
const sidecar = await readSidecar(devRoot, slug);
|
|
41
62
|
const specTools = new Set((sidecar?.steps ?? [])
|
|
42
63
|
.filter(s => s.kind === 'step' && s.tool)
|
|
43
64
|
.map(s => s.tool));
|
|
44
|
-
const seeds = relevantSeeds(
|
|
45
|
-
const
|
|
65
|
+
const seeds = relevantSeeds(BUILTIN_SEEDS, specTools);
|
|
66
|
+
const suite = await gatherSuiteContext(devRoot);
|
|
67
|
+
const raw = await runCodegen(buildOptimizePrompt(draft, sidecar, seeds, suite));
|
|
46
68
|
const llmCode = extractCode(raw);
|
|
47
69
|
const check = validateSpecCode(llmCode);
|
|
48
70
|
if (!check.ok) {
|
|
@@ -52,7 +74,9 @@ export async function optimizeSpec(devRoot, slug, runCodegen) {
|
|
|
52
74
|
// applies the safe mechanical rewrite (trailing run of independent assertions
|
|
53
75
|
// → expect.soft) surgically on its output. See softBatch.ts for the guard.
|
|
54
76
|
const code = softBatch(llmCode).code;
|
|
55
|
-
|
|
77
|
+
// Candidates are disposable derived artifacts → `.hover/cache/` (always
|
|
78
|
+
// gitignored). Losing one costs a re-run of the optimization, nothing more.
|
|
79
|
+
const dir = join(devRoot, '.hover', 'cache', 'optimized');
|
|
56
80
|
await mkdir(dir, { recursive: true });
|
|
57
81
|
// `.spec.ts.draft`, never `*.spec.ts` — Playwright's glob must not collect a
|
|
58
82
|
// candidate before a human reviews it.
|
|
@@ -65,7 +89,7 @@ export async function optimizeSpec(devRoot, slug, runCodegen) {
|
|
|
65
89
|
* same rules the deterministic path enforces (semantic selectors, no XPath, no
|
|
66
90
|
* waitForTimeout, keep the test.step shape).
|
|
67
91
|
*/
|
|
68
|
-
export function buildOptimizePrompt(draft, sidecar, seeds = []) {
|
|
92
|
+
export function buildOptimizePrompt(draft, sidecar, seeds = [], suite = { pages: [] }) {
|
|
69
93
|
const done = sidecar?.steps.find(s => s.kind === 'done');
|
|
70
94
|
const stepsJson = sidecar
|
|
71
95
|
? JSON.stringify(sidecar.steps.filter(s => s.kind === 'step'), null, 2)
|
|
@@ -78,8 +102,29 @@ export function buildOptimizePrompt(draft, sidecar, seeds = []) {
|
|
|
78
102
|
`Improve it WITHOUT changing what it tests:`,
|
|
79
103
|
` - Add assertions for the success/error feedback the session OBSERVED —`,
|
|
80
104
|
` e.g. await expect(page.getByText('Invalid email')).toBeVisible(), a`,
|
|
81
|
-
` success toast, a
|
|
105
|
+
` success toast, a confirmation. Use the captured steps + the outcome`,
|
|
82
106
|
` summary below to know what to assert.`,
|
|
107
|
+
` - ASSERT THE INVARIANT, NOT THIS RUN'S VALUE. When the asserted content`,
|
|
108
|
+
` varies run-to-run (a generated id, an order number, a date, a counter, a`,
|
|
109
|
+
` drawn word — and ANY step whose captured input has "dynamic": true),`,
|
|
110
|
+
` assert the contract with a pattern, never the literal: expect(loc).not`,
|
|
111
|
+
` .toHaveText('') or .toContainText(/…/), or .toHaveCount(n). Reserve an`,
|
|
112
|
+
` exact-literal assertion for genuinely fixed text (a heading, a fixed`,
|
|
113
|
+
` confirmation). Any captured assert_visible step already encodes this —`,
|
|
114
|
+
` preserve its matcher/intent; don't tighten a dynamic one back to a literal.`,
|
|
115
|
+
` - DE-LITERALIZE VOLATILE VALUES — even ones NOT pre-flagged. Scan every`,
|
|
116
|
+
` selector and assertion in the spec for values that are this run's DATA, not`,
|
|
117
|
+
` stable UI: a word/title/name/id/order number/date/price/count drawn from`,
|
|
118
|
+
` app content. Judge from what this app IS (the conventions + Page Objects`,
|
|
119
|
+
` below tell you) and the captured values. For each volatile one:`,
|
|
120
|
+
` getByRole(role, { name: "<value>" }) -> a stable anchor (getByTestId, or`,
|
|
121
|
+
` getByRole(role).first(), or a Page Object method); toHaveText("<value>") ->`,
|
|
122
|
+
` .not.toHaveText('') or .toContainText(/.../). When unsure if a value is`,
|
|
123
|
+
` volatile, PREFER the invariant — over-asserting a changing value is the`,
|
|
124
|
+
` failure we are fixing. But NEVER newly hard-code a value that wasn't there.`,
|
|
125
|
+
` - REUSE the project's Page Objects + conventions (below). If a step sequence`,
|
|
126
|
+
` matches a Page Object method, CALL it instead of re-emitting raw locators,`,
|
|
127
|
+
` and follow the naming / structure the existing suite uses.`,
|
|
83
128
|
` - Keep semantic selectors: getByRole / getByLabel / getByText. NEVER emit`,
|
|
84
129
|
` XPath or CSS-id selectors. NEVER use waitForTimeout (Playwright`,
|
|
85
130
|
` auto-waits).`,
|
|
@@ -104,6 +149,16 @@ export function buildOptimizePrompt(draft, sidecar, seeds = []) {
|
|
|
104
149
|
``,
|
|
105
150
|
`=== CAPTURED STEPS ===`,
|
|
106
151
|
stepsJson,
|
|
152
|
+
...(suite.conventions
|
|
153
|
+
? [``, `=== PROJECT CONVENTIONS (follow these) ===`, suite.conventions]
|
|
154
|
+
: []),
|
|
155
|
+
...(suite.pages.length > 0
|
|
156
|
+
? [
|
|
157
|
+
``,
|
|
158
|
+
`=== REUSABLE PAGE OBJECTS (prefer calling these over raw locators) ===`,
|
|
159
|
+
...suite.pages.map(p => `// ${p.name}\n${p.source}`),
|
|
160
|
+
]
|
|
161
|
+
: []),
|
|
107
162
|
...(seeds.length > 0
|
|
108
163
|
? [
|
|
109
164
|
``,
|
|
@@ -161,28 +216,3 @@ function hasSyntaxError(code) {
|
|
|
161
216
|
const sf = project.createSourceFile('__candidate.ts', code, { overwrite: true });
|
|
162
217
|
return project.getProgram().getSyntacticDiagnostics(sf).length > 0;
|
|
163
218
|
}
|
|
164
|
-
function candidatePathFor(devRoot, slug) {
|
|
165
|
-
return join(devRoot, '__vibe_tests__', '.hover', 'optimized', `${slug}.spec.ts.draft`);
|
|
166
|
-
}
|
|
167
|
-
/** Promote an optimization candidate to the real spec (overwriting it) and
|
|
168
|
-
* remove the candidate. Returns the written spec path. The human's "Use
|
|
169
|
-
* optimized" / `mv` action. */
|
|
170
|
-
export async function promoteOptimized(devRoot, slug) {
|
|
171
|
-
const candidate = candidatePathFor(devRoot, slug);
|
|
172
|
-
const specPath = join(devRoot, '__vibe_tests__', `${slug}.spec.ts`);
|
|
173
|
-
let code;
|
|
174
|
-
try {
|
|
175
|
-
code = await readFile(candidate, 'utf-8');
|
|
176
|
-
}
|
|
177
|
-
catch {
|
|
178
|
-
throw new OptimizeError(`no optimization candidate to promote for "${slug}"`);
|
|
179
|
-
}
|
|
180
|
-
await writeFile(specPath, code, 'utf-8');
|
|
181
|
-
await rm(candidate, { force: true });
|
|
182
|
-
return specPath;
|
|
183
|
-
}
|
|
184
|
-
/** Discard an optimization candidate (delete the .draft, leave the spec). The
|
|
185
|
-
* human's "Keep original". */
|
|
186
|
-
export async function discardOptimized(devRoot, slug) {
|
|
187
|
-
await rm(candidatePathFor(devRoot, slug), { force: true });
|
|
188
|
-
}
|
|
@@ -3,8 +3,6 @@ export interface OptimizeAgentOptions {
|
|
|
3
3
|
agentId: string;
|
|
4
4
|
model?: string;
|
|
5
5
|
maxBudgetUsd?: number;
|
|
6
|
-
/** Optional model API key, injected into the spawned CLI's env. */
|
|
7
|
-
apiKey?: string;
|
|
8
6
|
signal?: AbortSignal;
|
|
9
7
|
}
|
|
10
8
|
export declare function optimizeSpecWithAgent(devRoot: string, slug: string, opts: OptimizeAgentOptions): Promise<OptimizeResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"optimizeSpecWithAgent.d.ts","sourceRoot":"","sources":["../../src/specs/optimizeSpecWithAgent.ts"],"names":[],"mappings":"AAUA,OAAO,EAAgB,KAAK,cAAc,EAAmB,MAAM,mBAAmB,CAAC;AAEvF,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,
|
|
1
|
+
{"version":3,"file":"optimizeSpecWithAgent.d.ts","sourceRoot":"","sources":["../../src/specs/optimizeSpecWithAgent.ts"],"names":[],"mappings":"AAUA,OAAO,EAAgB,KAAK,cAAc,EAAmB,MAAM,mBAAmB,CAAC;AAEvF,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,cAAc,CAAC,CA2BzB"}
|
|
@@ -15,6 +15,8 @@ export interface PageObjectManifest {
|
|
|
15
15
|
pages: PageObjectEntry[];
|
|
16
16
|
}
|
|
17
17
|
export declare function writePageObjectManifest(devRoot: string, pages: PageObjectEntry[]): Promise<string>;
|
|
18
|
-
/** Read the manifest, or null when none exists (no extraction has run).
|
|
18
|
+
/** Read the manifest, or null when none exists (no extraction has run).
|
|
19
|
+
* Falls back to the legacy `__vibe_tests__/.hover/` home for manifests
|
|
20
|
+
* written before the `.hover/sidecars/` relocation. */
|
|
19
21
|
export declare function readPageObjectManifest(devRoot: string): Promise<PageObjectManifest | null>;
|
|
20
22
|
//# sourceMappingURL=pageObjectManifest.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pageObjectManifest.d.ts","sourceRoot":"","sources":["../../src/specs/pageObjectManifest.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,eAAe,EAAE,CAAC;CAC1B;AAMD,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,eAAe,EAAE,GACvB,OAAO,CAAC,MAAM,CAAC,CAOjB;AAED
|
|
1
|
+
{"version":3,"file":"pageObjectManifest.d.ts","sourceRoot":"","sources":["../../src/specs/pageObjectManifest.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,eAAe,EAAE,CAAC;CAC1B;AAMD,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,eAAe,EAAE,GACvB,OAAO,CAAC,MAAM,CAAC,CAOjB;AAED;;wDAEwD;AACxD,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAUhG"}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
15
15
|
import { join } from 'node:path';
|
|
16
|
-
import { sidecarDir } from './sidecar.js';
|
|
16
|
+
import { sidecarDir, legacySidecarDir } from './sidecar.js';
|
|
17
17
|
export const MANIFEST_VERSION = 1;
|
|
18
18
|
function manifestPath(devRoot) {
|
|
19
19
|
return join(sidecarDir(devRoot), 'page-objects.json');
|
|
@@ -26,15 +26,19 @@ export async function writePageObjectManifest(devRoot, pages) {
|
|
|
26
26
|
await writeFile(path, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
|
|
27
27
|
return path;
|
|
28
28
|
}
|
|
29
|
-
/** Read the manifest, or null when none exists (no extraction has run).
|
|
29
|
+
/** Read the manifest, or null when none exists (no extraction has run).
|
|
30
|
+
* Falls back to the legacy `__vibe_tests__/.hover/` home for manifests
|
|
31
|
+
* written before the `.hover/sidecars/` relocation. */
|
|
30
32
|
export async function readPageObjectManifest(devRoot) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
for (const path of [manifestPath(devRoot), join(legacySidecarDir(devRoot), 'page-objects.json')]) {
|
|
34
|
+
try {
|
|
35
|
+
const m = JSON.parse(await readFile(path, 'utf-8'));
|
|
36
|
+
if (Array.isArray(m.pages))
|
|
37
|
+
return m;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
/* no manifest / malformed — try next */
|
|
41
|
+
}
|
|
38
42
|
}
|
|
39
43
|
return null;
|
|
40
44
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type Locator, type Page } from 'playwright-core';
|
|
2
|
+
export interface GroundedTarget {
|
|
3
|
+
role?: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
text?: string;
|
|
6
|
+
testId?: string;
|
|
7
|
+
within?: {
|
|
8
|
+
role?: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export interface ReplayStep {
|
|
13
|
+
kind?: string;
|
|
14
|
+
tool?: string;
|
|
15
|
+
input?: unknown;
|
|
16
|
+
}
|
|
17
|
+
export interface ReplayFailure {
|
|
18
|
+
index: number;
|
|
19
|
+
tool: string;
|
|
20
|
+
target: string;
|
|
21
|
+
error: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ReplayResult {
|
|
24
|
+
ok: boolean;
|
|
25
|
+
/** Grounded actions that ran successfully. */
|
|
26
|
+
ran: number;
|
|
27
|
+
/** Total grounded actions in the flow. */
|
|
28
|
+
total: number;
|
|
29
|
+
failures: ReplayFailure[];
|
|
30
|
+
}
|
|
31
|
+
export declare function groundedLocate(page: Page, g: GroundedTarget): Locator | null;
|
|
32
|
+
/** Replay ONE grounded step on a page. Returns 'skipped' for non-actuation
|
|
33
|
+
* steps; throws on a failed locate / action (the caller records it). */
|
|
34
|
+
export declare function applyGroundedStep(page: Page, step: ReplayStep): Promise<'ok' | 'skipped'>;
|
|
35
|
+
/** Replay a flow's grounded steps on a given page (injected, so it's testable
|
|
36
|
+
* without a real browser). Navigates to `devUrl` first for a consistent start,
|
|
37
|
+
* then runs each grounded action, stopping at the first failure. */
|
|
38
|
+
export declare function replayOnPage(page: Page, devUrl: string, steps: ReplayStep[]): Promise<ReplayResult>;
|
|
39
|
+
/** Connect to the debug Chrome over CDP and replay a flow's grounded steps. */
|
|
40
|
+
export declare function replayGroundedSteps(opts: {
|
|
41
|
+
cdpUrl: string;
|
|
42
|
+
devUrl: string;
|
|
43
|
+
steps: ReplayStep[];
|
|
44
|
+
}): Promise<ReplayResult>;
|
|
45
|
+
//# sourceMappingURL=replayGrounded.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replayGrounded.d.ts","sourceRoot":"","sources":["../../src/specs/replayGrounded.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAkBlF,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3C;AAGD,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AACD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AACD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B;AA+BD,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,cAAc,GAAG,OAAO,GAAG,IAAI,CAS5E;AAED;yEACyE;AACzE,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC,CA4B/F;AAED;;qEAEqE;AACrE,wBAAsB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAqBzG;AAuBD,+EAA+E;AAC/E,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAA;CAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAoB9H"}
|