@eidra-umain/greenlight 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.
Files changed (142) hide show
  1. package/README.md +391 -0
  2. package/dist/browser/browser.d.ts +24 -0
  3. package/dist/browser/browser.d.ts.map +1 -0
  4. package/dist/browser/browser.js +44 -0
  5. package/dist/browser/browser.js.map +1 -0
  6. package/dist/cli/index.d.ts +3 -0
  7. package/dist/cli/index.d.ts.map +1 -0
  8. package/dist/cli/index.js +140 -0
  9. package/dist/cli/index.js.map +1 -0
  10. package/dist/cli/run.d.ts +9 -0
  11. package/dist/cli/run.d.ts.map +1 -0
  12. package/dist/cli/run.js +277 -0
  13. package/dist/cli/run.js.map +1 -0
  14. package/dist/config.d.ts +48 -0
  15. package/dist/config.d.ts.map +1 -0
  16. package/dist/config.js +107 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/globals.d.ts +21 -0
  19. package/dist/globals.d.ts.map +1 -0
  20. package/dist/globals.js +24 -0
  21. package/dist/globals.js.map +1 -0
  22. package/dist/parser/loader.d.ts +7 -0
  23. package/dist/parser/loader.d.ts.map +1 -0
  24. package/dist/parser/loader.js +43 -0
  25. package/dist/parser/loader.js.map +1 -0
  26. package/dist/parser/schema.d.ts +42 -0
  27. package/dist/parser/schema.d.ts.map +1 -0
  28. package/dist/parser/schema.js +33 -0
  29. package/dist/parser/schema.js.map +1 -0
  30. package/dist/parser/steps.d.ts +13 -0
  31. package/dist/parser/steps.d.ts.map +1 -0
  32. package/dist/parser/steps.js +44 -0
  33. package/dist/parser/steps.js.map +1 -0
  34. package/dist/parser/variables.d.ts +18 -0
  35. package/dist/parser/variables.d.ts.map +1 -0
  36. package/dist/parser/variables.js +44 -0
  37. package/dist/parser/variables.js.map +1 -0
  38. package/dist/pilot/a11y-parser.d.ts +26 -0
  39. package/dist/pilot/a11y-parser.d.ts.map +1 -0
  40. package/dist/pilot/a11y-parser.js +195 -0
  41. package/dist/pilot/a11y-parser.js.map +1 -0
  42. package/dist/pilot/assertions.d.ts +30 -0
  43. package/dist/pilot/assertions.d.ts.map +1 -0
  44. package/dist/pilot/assertions.js +219 -0
  45. package/dist/pilot/assertions.js.map +1 -0
  46. package/dist/pilot/checkbox.d.ts +12 -0
  47. package/dist/pilot/checkbox.d.ts.map +1 -0
  48. package/dist/pilot/checkbox.js +104 -0
  49. package/dist/pilot/checkbox.js.map +1 -0
  50. package/dist/pilot/executor.d.ts +17 -0
  51. package/dist/pilot/executor.d.ts.map +1 -0
  52. package/dist/pilot/executor.js +462 -0
  53. package/dist/pilot/executor.js.map +1 -0
  54. package/dist/pilot/form-fields.d.ts +34 -0
  55. package/dist/pilot/form-fields.d.ts.map +1 -0
  56. package/dist/pilot/form-fields.js +139 -0
  57. package/dist/pilot/form-fields.js.map +1 -0
  58. package/dist/pilot/llm.d.ts +49 -0
  59. package/dist/pilot/llm.d.ts.map +1 -0
  60. package/dist/pilot/llm.js +188 -0
  61. package/dist/pilot/llm.js.map +1 -0
  62. package/dist/pilot/locator.d.ts +58 -0
  63. package/dist/pilot/locator.d.ts.map +1 -0
  64. package/dist/pilot/locator.js +248 -0
  65. package/dist/pilot/locator.js.map +1 -0
  66. package/dist/pilot/message-builder.d.ts +31 -0
  67. package/dist/pilot/message-builder.d.ts.map +1 -0
  68. package/dist/pilot/message-builder.js +112 -0
  69. package/dist/pilot/message-builder.js.map +1 -0
  70. package/dist/pilot/network.d.ts +23 -0
  71. package/dist/pilot/network.d.ts.map +1 -0
  72. package/dist/pilot/network.js +92 -0
  73. package/dist/pilot/network.js.map +1 -0
  74. package/dist/pilot/pilot.d.ts +27 -0
  75. package/dist/pilot/pilot.d.ts.map +1 -0
  76. package/dist/pilot/pilot.js +249 -0
  77. package/dist/pilot/pilot.js.map +1 -0
  78. package/dist/pilot/prompts.d.ts +8 -0
  79. package/dist/pilot/prompts.d.ts.map +1 -0
  80. package/dist/pilot/prompts.js +163 -0
  81. package/dist/pilot/prompts.js.map +1 -0
  82. package/dist/pilot/providers/anthropic.d.ts +6 -0
  83. package/dist/pilot/providers/anthropic.d.ts.map +1 -0
  84. package/dist/pilot/providers/anthropic.js +45 -0
  85. package/dist/pilot/providers/anthropic.js.map +1 -0
  86. package/dist/pilot/providers/gemini.d.ts +6 -0
  87. package/dist/pilot/providers/gemini.d.ts.map +1 -0
  88. package/dist/pilot/providers/gemini.js +55 -0
  89. package/dist/pilot/providers/gemini.js.map +1 -0
  90. package/dist/pilot/providers/index.d.ts +10 -0
  91. package/dist/pilot/providers/index.d.ts.map +1 -0
  92. package/dist/pilot/providers/index.js +25 -0
  93. package/dist/pilot/providers/index.js.map +1 -0
  94. package/dist/pilot/providers/openai-compatible.d.ts +7 -0
  95. package/dist/pilot/providers/openai-compatible.d.ts.map +1 -0
  96. package/dist/pilot/providers/openai-compatible.js +34 -0
  97. package/dist/pilot/providers/openai-compatible.js.map +1 -0
  98. package/dist/pilot/providers/types.d.ts +12 -0
  99. package/dist/pilot/providers/types.d.ts.map +1 -0
  100. package/dist/pilot/providers/types.js +2 -0
  101. package/dist/pilot/providers/types.js.map +1 -0
  102. package/dist/pilot/response-parser.d.ts +31 -0
  103. package/dist/pilot/response-parser.d.ts.map +1 -0
  104. package/dist/pilot/response-parser.js +188 -0
  105. package/dist/pilot/response-parser.js.map +1 -0
  106. package/dist/pilot/state.d.ts +19 -0
  107. package/dist/pilot/state.d.ts.map +1 -0
  108. package/dist/pilot/state.js +67 -0
  109. package/dist/pilot/state.js.map +1 -0
  110. package/dist/pilot/trace.d.ts +16 -0
  111. package/dist/pilot/trace.d.ts.map +1 -0
  112. package/dist/pilot/trace.js +117 -0
  113. package/dist/pilot/trace.js.map +1 -0
  114. package/dist/planner/hasher.d.ts +14 -0
  115. package/dist/planner/hasher.d.ts.map +1 -0
  116. package/dist/planner/hasher.js +21 -0
  117. package/dist/planner/hasher.js.map +1 -0
  118. package/dist/planner/plan-generator.d.ts +23 -0
  119. package/dist/planner/plan-generator.d.ts.map +1 -0
  120. package/dist/planner/plan-generator.js +56 -0
  121. package/dist/planner/plan-generator.js.map +1 -0
  122. package/dist/planner/plan-runner.d.ts +16 -0
  123. package/dist/planner/plan-runner.d.ts.map +1 -0
  124. package/dist/planner/plan-runner.js +375 -0
  125. package/dist/planner/plan-runner.js.map +1 -0
  126. package/dist/planner/plan-store.d.ts +22 -0
  127. package/dist/planner/plan-store.d.ts.map +1 -0
  128. package/dist/planner/plan-store.js +71 -0
  129. package/dist/planner/plan-store.js.map +1 -0
  130. package/dist/planner/plan-types.d.ts +64 -0
  131. package/dist/planner/plan-types.d.ts.map +1 -0
  132. package/dist/planner/plan-types.js +7 -0
  133. package/dist/planner/plan-types.js.map +1 -0
  134. package/dist/reporter/types.d.ts +130 -0
  135. package/dist/reporter/types.d.ts.map +1 -0
  136. package/dist/reporter/types.js +5 -0
  137. package/dist/reporter/types.js.map +1 -0
  138. package/dist/types.d.ts +51 -0
  139. package/dist/types.d.ts.map +1 -0
  140. package/dist/types.js +23 -0
  141. package/dist/types.js.map +1 -0
  142. package/package.json +64 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hasher.js","sourceRoot":"","sources":["../../src/planner/hasher.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExC;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,QAA6B;IAC5D,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC9C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAC1D,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,OAAO,CAAC,IAAY;IACnC,OAAO,IAAI;SACT,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAA;AACxB,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Plan recorder — hooks into the Pilot loop during a discovery run
3
+ * to capture concrete actions and produce a HeuristicPlan.
4
+ */
5
+ import type { Action, ExecutionResult } from "../reporter/types.js";
6
+ import type { HeuristicPlan } from "./plan-types.js";
7
+ /** Records concrete actions during a discovery run. */
8
+ export interface PlanRecorder {
9
+ /** Record a successful step execution. */
10
+ recordStep(step: string, action: Action, result: ExecutionResult, postState: {
11
+ url: string;
12
+ title: string;
13
+ }): void;
14
+ /** Produce the final heuristic plan from all recorded steps. */
15
+ finalize(): HeuristicPlan;
16
+ }
17
+ /**
18
+ * Create a plan recorder for a single test case.
19
+ * Call recordStep() after each successful step during the discovery run.
20
+ * Call finalize() after the test passes to get the cached plan.
21
+ */
22
+ export declare function createPlanRecorder(suiteSlug: string, testSlug: string, sourceHash: string, model: string): PlanRecorder;
23
+ //# sourceMappingURL=plan-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-generator.d.ts","sourceRoot":"","sources":["../../src/planner/plan-generator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAiB,MAAM,iBAAiB,CAAA;AAEnE,uDAAuD;AACvD,MAAM,WAAW,YAAY;IAC5B,0CAA0C;IAC1C,UAAU,CACT,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,eAAe,EACvB,SAAS,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GACvC,IAAI,CAAA;IACP,gEAAgE;IAChE,QAAQ,IAAI,aAAa,CAAA;CACzB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CACjC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,GACX,YAAY,CAsDd"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Plan recorder — hooks into the Pilot loop during a discovery run
3
+ * to capture concrete actions and produce a HeuristicPlan.
4
+ */
5
+ /**
6
+ * Create a plan recorder for a single test case.
7
+ * Call recordStep() after each successful step during the discovery run.
8
+ * Call finalize() after the test passes to get the cached plan.
9
+ */
10
+ export function createPlanRecorder(suiteSlug, testSlug, sourceHash, model) {
11
+ const steps = [];
12
+ return {
13
+ recordStep(step, action, result, postState) {
14
+ const hStep = {
15
+ originalStep: step,
16
+ action: action.action,
17
+ postStepFingerprint: {
18
+ url: postState.url,
19
+ title: postState.title,
20
+ },
21
+ };
22
+ // Store the resolved selector (role+name or CSS) if available
23
+ if (result.resolvedSelector) {
24
+ hStep.selector = { ...result.resolvedSelector };
25
+ }
26
+ if (action.value !== undefined) {
27
+ hStep.value = action.value;
28
+ }
29
+ if (action.option !== undefined) {
30
+ hStep.option = action.option;
31
+ }
32
+ if (action.rememberAs !== undefined) {
33
+ hStep.rememberAs = action.rememberAs;
34
+ }
35
+ if (action.compare) {
36
+ hStep.compare = { ...action.compare };
37
+ }
38
+ if (action.assertion) {
39
+ hStep.assertion = { ...action.assertion };
40
+ }
41
+ steps.push(hStep);
42
+ },
43
+ finalize() {
44
+ return {
45
+ suiteSlug,
46
+ testSlug,
47
+ sourceHash,
48
+ model,
49
+ generatedAt: new Date().toISOString(),
50
+ greenlightVersion: "0.1.0",
51
+ steps,
52
+ };
53
+ },
54
+ };
55
+ }
56
+ //# sourceMappingURL=plan-generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-generator.js","sourceRoot":"","sources":["../../src/planner/plan-generator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAkBH;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CACjC,SAAiB,EACjB,QAAgB,EAChB,UAAkB,EAClB,KAAa;IAEb,MAAM,KAAK,GAAoB,EAAE,CAAA;IAEjC,OAAO;QACN,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS;YACzC,MAAM,KAAK,GAAkB;gBAC5B,YAAY,EAAE,IAAI;gBAClB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,mBAAmB,EAAE;oBACpB,GAAG,EAAE,SAAS,CAAC,GAAG;oBAClB,KAAK,EAAE,SAAS,CAAC,KAAK;iBACtB;aACD,CAAA;YAED,8DAA8D;YAC9D,IAAI,MAAM,CAAC,gBAAgB,EAAE,CAAC;gBAC7B,KAAK,CAAC,QAAQ,GAAG,EAAE,GAAG,MAAM,CAAC,gBAAgB,EAAE,CAAA;YAChD,CAAC;YAED,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBAChC,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAA;YAC3B,CAAC;YAED,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBACjC,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAA;YAC7B,CAAC;YAED,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;gBACrC,KAAK,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,CAAA;YACrC,CAAC;YAED,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,KAAK,CAAC,OAAO,GAAG,EAAE,GAAG,MAAM,CAAC,OAAO,EAAE,CAAA;YACtC,CAAC;YAED,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;gBACtB,KAAK,CAAC,SAAS,GAAG,EAAE,GAAG,MAAM,CAAC,SAAS,EAAE,CAAA;YAC1C,CAAC;YAED,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAClB,CAAC;QAED,QAAQ;YACP,OAAO;gBACN,SAAS;gBACT,QAAQ;gBACR,UAAU;gBACV,KAAK;gBACL,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACrC,iBAAiB,EAAE,OAAO;gBAC1B,KAAK;aACL,CAAA;QACF,CAAC;KACD,CAAA;AACF,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Replays a cached heuristic plan directly via Playwright — no LLM calls.
3
+ * For actions with stored selectors (click, type, select), builds locators
4
+ * from the selector. For other actions, delegates to the regular executor.
5
+ */
6
+ import type { Page } from "playwright";
7
+ import type { TestCaseResult } from "../reporter/types.js";
8
+ import type { HeuristicPlan } from "./plan-types.js";
9
+ /**
10
+ * Replay a cached heuristic plan against the browser.
11
+ * Returns a TestCaseResult with mode "cached" and a drifted flag.
12
+ */
13
+ export declare function runCachedPlan(page: Page, plan: HeuristicPlan, testName: string, options?: {
14
+ waitForNetworkIdle?: () => Promise<void>;
15
+ }): Promise<TestCaseResult>;
16
+ //# sourceMappingURL=plan-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-runner.d.ts","sourceRoot":"","sources":["../../src/planner/plan-runner.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAW,MAAM,YAAY,CAAA;AAC/C,OAAO,KAAK,EAAsB,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC9E,OAAO,KAAK,EAAE,aAAa,EAAoC,MAAM,iBAAiB,CAAA;AAwStF;;;GAGG;AACH,wBAAsB,aAAa,CAClC,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,aAAa,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;IAAE,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,GACpD,OAAO,CAAC,cAAc,CAAC,CAiFzB"}
@@ -0,0 +1,375 @@
1
+ /**
2
+ * Replays a cached heuristic plan directly via Playwright — no LLM calls.
3
+ * For actions with stored selectors (click, type, select), builds locators
4
+ * from the selector. For other actions, delegates to the regular executor.
5
+ */
6
+ import { executeAction, runWithNavigationHandling } from "../pilot/executor.js";
7
+ import { checkCheckbox } from "../pilot/checkbox.js";
8
+ import { globals } from "../globals.js";
9
+ /** Build a Playwright locator from a stored heuristic selector. */
10
+ function buildLocator(page, selector) {
11
+ if (selector.css) {
12
+ return page.locator(selector.css);
13
+ }
14
+ if (selector.role) {
15
+ const role = selector.role;
16
+ let locator = selector.name
17
+ ? page.getByRole(role, { name: selector.name })
18
+ : page.getByRole(role);
19
+ // When multiple elements match the same role+name, use the recorded index
20
+ if (selector.nth != null) {
21
+ locator = locator.nth(selector.nth);
22
+ }
23
+ return locator;
24
+ }
25
+ throw new Error("Heuristic selector has neither role nor css");
26
+ }
27
+ /**
28
+ * Execute a single heuristic step.
29
+ * Actions with selectors (click, type, select) use stored selectors directly.
30
+ * Other actions delegate to the regular executor.
31
+ */
32
+ async function executeHeuristicStep(page, step) {
33
+ const start = performance.now();
34
+ try {
35
+ switch (step.action) {
36
+ case "click": {
37
+ if (!step.selector)
38
+ throw new Error("click step requires a selector");
39
+ const locator = buildLocator(page, step.selector);
40
+ await runWithNavigationHandling(page, () => locator.click());
41
+ break;
42
+ }
43
+ case "type": {
44
+ if (!step.value)
45
+ throw new Error("type step requires a value");
46
+ if (!step.selector)
47
+ throw new Error("type step requires a selector");
48
+ const locator = buildLocator(page, step.selector);
49
+ await locator.fill(step.value);
50
+ break;
51
+ }
52
+ case "select": {
53
+ if (!step.value)
54
+ throw new Error("select step requires a value");
55
+ if (!step.selector)
56
+ throw new Error("select step requires a selector");
57
+ const locator = buildLocator(page, step.selector);
58
+ const tag = await locator.evaluate((el) => el.tagName).catch(() => "");
59
+ if (tag === "SELECT") {
60
+ await locator.selectOption({ label: step.value });
61
+ }
62
+ else {
63
+ // Custom dropdown: click to open, find option in popup
64
+ await locator.click();
65
+ await page.waitForTimeout(500);
66
+ let clicked = false;
67
+ // Search in menu/listbox containers first
68
+ const popupSelectors = [
69
+ "[role='menu']", "[role='listbox']",
70
+ "[data-part='content']",
71
+ "[data-state='open'][data-scope='menu']",
72
+ ];
73
+ for (const sel of popupSelectors) {
74
+ if (clicked)
75
+ break;
76
+ try {
77
+ const containers = page.locator(sel);
78
+ const count = await containers.count();
79
+ for (let i = 0; i < count && !clicked; i++) {
80
+ const container = containers.nth(i);
81
+ if (!await container.isVisible().catch(() => false))
82
+ continue;
83
+ const opt = container.getByText(step.value, { exact: true });
84
+ if (await opt.first().isVisible().catch(() => false)) {
85
+ await opt.first().click();
86
+ clicked = true;
87
+ }
88
+ }
89
+ }
90
+ catch { /* try next */ }
91
+ }
92
+ // Fallback: role-based page-wide
93
+ if (!clicked) {
94
+ const roles = ["menuitem", "menuitemradio", "option"];
95
+ for (const role of roles) {
96
+ try {
97
+ const opt = page.getByRole(role, { name: step.value });
98
+ if (await opt.first().isVisible().catch(() => false)) {
99
+ await opt.first().click();
100
+ clicked = true;
101
+ break;
102
+ }
103
+ }
104
+ catch { /* try next */ }
105
+ }
106
+ }
107
+ if (!clicked) {
108
+ throw new Error(`Could not find option "${step.value}" in custom dropdown`);
109
+ }
110
+ }
111
+ break;
112
+ }
113
+ case "autocomplete": {
114
+ if (!step.value)
115
+ throw new Error("autocomplete step requires a value");
116
+ if (!step.selector)
117
+ throw new Error("autocomplete step requires a selector");
118
+ const locator = buildLocator(page, step.selector);
119
+ await locator.click();
120
+ await locator.fill("");
121
+ await locator.pressSequentially(step.value, { delay: 50 });
122
+ if (globals.debug) {
123
+ console.log(` [cached:autocomplete] Typed "${step.value}", waiting for suggestions...`);
124
+ }
125
+ // Wait for suggestions to appear
126
+ const suggestionPatterns = [
127
+ page.locator("[role='option']"),
128
+ page.locator("[role='listbox'] > *"),
129
+ page.locator(".autocomplete-results > *, .suggestions > *, .dropdown-menu > *"),
130
+ ];
131
+ let suggestions;
132
+ for (const loc of suggestionPatterns) {
133
+ try {
134
+ await loc.first().waitFor({ state: "visible", timeout: 5000 });
135
+ suggestions = loc;
136
+ break;
137
+ }
138
+ catch { /* try next */ }
139
+ }
140
+ if (!suggestions) {
141
+ throw new Error("Autocomplete suggestions did not appear");
142
+ }
143
+ if (step.option) {
144
+ const candidates = [
145
+ suggestions.filter({ hasText: step.option }).first(),
146
+ page.getByRole("option", { name: step.option }),
147
+ page.getByText(step.option, { exact: true }),
148
+ page.getByText(step.option),
149
+ ];
150
+ let clicked = false;
151
+ for (const opt of candidates) {
152
+ try {
153
+ if (await opt.isVisible()) {
154
+ await opt.click();
155
+ clicked = true;
156
+ break;
157
+ }
158
+ }
159
+ catch { /* try next */ }
160
+ }
161
+ if (!clicked) {
162
+ throw new Error(`Autocomplete option "${step.option}" not found`);
163
+ }
164
+ }
165
+ else {
166
+ await suggestions.first().click();
167
+ }
168
+ break;
169
+ }
170
+ case "check": {
171
+ if (!step.selector)
172
+ throw new Error("check step requires a selector");
173
+ const locator = buildLocator(page, step.selector);
174
+ await checkCheckbox(page, locator, true);
175
+ break;
176
+ }
177
+ case "uncheck": {
178
+ if (!step.selector)
179
+ throw new Error("uncheck step requires a selector");
180
+ const locator = buildLocator(page, step.selector);
181
+ await checkCheckbox(page, locator, false);
182
+ break;
183
+ }
184
+ case "remember": {
185
+ const varName = step.rememberAs ?? step.value ?? "";
186
+ let capturedText;
187
+ if (step.selector) {
188
+ const locator = buildLocator(page, step.selector);
189
+ capturedText = (await locator.textContent() ?? "").trim();
190
+ }
191
+ else {
192
+ // No selector stored — use innerText (preserves visual
193
+ // line breaks) and search for text matching keywords
194
+ const keywords = varName
195
+ .replace(/_/g, " ")
196
+ .split(" ")
197
+ .filter((w) => w.length > 2);
198
+ const innerText = await page.locator("body").innerText();
199
+ // Split on newlines/tabs to get visual text segments
200
+ const segments = innerText
201
+ .split(/[\n\t]+/)
202
+ .map((s) => s.trim())
203
+ .filter(Boolean);
204
+ let best = "";
205
+ for (const seg of segments) {
206
+ if (!/\d/.test(seg))
207
+ continue;
208
+ const lower = seg.toLowerCase();
209
+ if (keywords.some((kw) => lower.includes(kw.toLowerCase()))) {
210
+ if (!best || seg.length < best.length) {
211
+ best = seg;
212
+ }
213
+ }
214
+ }
215
+ if (!best) {
216
+ throw new Error("remember: no selector stored and could not find matching value on page");
217
+ }
218
+ capturedText = best;
219
+ }
220
+ if (globals.debug) {
221
+ console.log(` [cached:remember] Captured "${capturedText}" as "${varName}"`);
222
+ }
223
+ globals.valueStore.set(varName, capturedText);
224
+ return { success: true, duration: performance.now() - start };
225
+ }
226
+ case "scroll": {
227
+ if (step.selector) {
228
+ const locator = buildLocator(page, step.selector);
229
+ await locator.scrollIntoViewIfNeeded();
230
+ }
231
+ else {
232
+ const delta = step.value === "up" ? -500 : 500;
233
+ await page.mouse.wheel(0, delta);
234
+ }
235
+ break;
236
+ }
237
+ default: {
238
+ // navigate, press, wait, assert → delegate to regular executor
239
+ const action = {
240
+ action: step.action,
241
+ value: step.value,
242
+ assertion: step.assertion,
243
+ };
244
+ // Compare asserts: override the assertion type so the executor
245
+ // performs a live numeric comparison instead of an exact text match.
246
+ // During discovery the LLM may have stored a contains_text with
247
+ // a hardcoded value (e.g. "42 resultat"), but the cached run
248
+ // must compare dynamically against the remembered value.
249
+ if (step.compare) {
250
+ action.compare = {
251
+ variable: step.compare.variable,
252
+ operator: step.compare.operator,
253
+ };
254
+ action.assertion = { type: "compare", expected: step.originalStep };
255
+ }
256
+ if (step.selector) {
257
+ // For compare asserts that need an element ref to read current value
258
+ if (step.selector.role) {
259
+ action.text = step.selector.name;
260
+ }
261
+ else if (step.selector.css) {
262
+ action.text = step.selector.css;
263
+ }
264
+ }
265
+ if (step.rememberAs) {
266
+ action.rememberAs = step.rememberAs;
267
+ }
268
+ const result = await executeAction(page, action, []);
269
+ return {
270
+ success: result.success,
271
+ duration: performance.now() - start,
272
+ error: result.error,
273
+ };
274
+ }
275
+ }
276
+ return { success: true, duration: performance.now() - start };
277
+ }
278
+ catch (err) {
279
+ return {
280
+ success: false,
281
+ duration: performance.now() - start,
282
+ error: err instanceof Error ? err.message : String(err),
283
+ };
284
+ }
285
+ }
286
+ /** Check if a URL path drift occurred (ignoring query params). */
287
+ function hasPathDrift(expectedUrl, actualUrl) {
288
+ try {
289
+ const expectedPath = new URL(expectedUrl).pathname;
290
+ const actualPath = new URL(actualUrl).pathname;
291
+ return expectedPath !== actualPath;
292
+ }
293
+ catch {
294
+ // If URL parsing fails, compare as strings
295
+ return expectedUrl !== actualUrl;
296
+ }
297
+ }
298
+ /**
299
+ * Replay a cached heuristic plan against the browser.
300
+ * Returns a TestCaseResult with mode "cached" and a drifted flag.
301
+ */
302
+ export async function runCachedPlan(page, plan, testName, options) {
303
+ const startTime = performance.now();
304
+ const stepResults = [];
305
+ let drifted = false;
306
+ globals.valueStore.clear();
307
+ for (const step of plan.steps) {
308
+ const stepStart = performance.now();
309
+ // Wait for async content to settle before interacting
310
+ if (options?.waitForNetworkIdle) {
311
+ await options.waitForNetworkIdle();
312
+ }
313
+ const result = await executeHeuristicStep(page, step);
314
+ if (!result.success) {
315
+ drifted = true;
316
+ stepResults.push({
317
+ step: step.originalStep,
318
+ action: {
319
+ action: step.action,
320
+ value: step.value,
321
+ assertion: step.assertion,
322
+ },
323
+ status: "failed",
324
+ duration: performance.now() - stepStart,
325
+ error: `Plan drift: ${result.error ?? "unknown error"}`,
326
+ });
327
+ break;
328
+ }
329
+ // Check URL path fingerprint for drift.
330
+ // Skip for assert steps — they don't change the URL, and after a
331
+ // navigation-triggering action the URL may have changed legitimately
332
+ // before the assert runs. The fingerprint from discovery may reflect
333
+ // a pre-navigation snapshot.
334
+ const currentUrl = page.url();
335
+ const isAssert = step.action === "assert";
336
+ if (!isAssert && hasPathDrift(step.postStepFingerprint.url, currentUrl)) {
337
+ drifted = true;
338
+ stepResults.push({
339
+ step: step.originalStep,
340
+ action: {
341
+ action: step.action,
342
+ value: step.value,
343
+ assertion: step.assertion,
344
+ },
345
+ status: "failed",
346
+ duration: performance.now() - stepStart,
347
+ error: `Plan drift: expected URL path "${new URL(step.postStepFingerprint.url).pathname}" but got "${new URL(currentUrl).pathname}"`,
348
+ });
349
+ break;
350
+ }
351
+ stepResults.push({
352
+ step: step.originalStep,
353
+ action: {
354
+ action: step.action,
355
+ value: step.value,
356
+ assertion: step.assertion,
357
+ },
358
+ status: "passed",
359
+ duration: performance.now() - stepStart,
360
+ });
361
+ }
362
+ const allPassed = stepResults.every((s) => s.status === "passed");
363
+ const status = allPassed && stepResults.length === plan.steps.length
364
+ ? "passed"
365
+ : "failed";
366
+ return {
367
+ name: testName,
368
+ status,
369
+ steps: stepResults,
370
+ duration: performance.now() - startTime,
371
+ mode: "cached",
372
+ drifted,
373
+ };
374
+ }
375
+ //# sourceMappingURL=plan-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-runner.js","sourceRoot":"","sources":["../../src/planner/plan-runner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Reads and writes cached heuristic plans and the hash index
3
+ * in the .greenlight/ directory.
4
+ */
5
+ import type { HeuristicPlan } from "./plan-types.js";
6
+ /** Load the hash index mapping test keys to their source hashes. */
7
+ export declare function loadHashIndex(projectRoot: string): Promise<Record<string, string>>;
8
+ /** Save the hash index to disk. */
9
+ export declare function saveHashIndex(projectRoot: string, index: Record<string, string>): Promise<void>;
10
+ /** Load a cached plan for a specific test case. Returns null if not found. */
11
+ export declare function loadPlan(projectRoot: string, suiteSlug: string, testSlug: string): Promise<HeuristicPlan | null>;
12
+ /** Save a heuristic plan to disk. Creates directories as needed. */
13
+ export declare function savePlan(projectRoot: string, plan: HeuristicPlan): Promise<void>;
14
+ /** Delete a cached plan file. No-op if the file doesn't exist. */
15
+ export declare function deletePlan(projectRoot: string, suiteSlug: string, testSlug: string): Promise<void>;
16
+ /**
17
+ * Ensure .greenlight/ is in .gitignore.
18
+ * Appends the entry if .gitignore exists but doesn't contain it.
19
+ * No-op if .gitignore doesn't exist.
20
+ */
21
+ export declare function ensureGitignore(projectRoot: string): Promise<void>;
22
+ //# sourceMappingURL=plan-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-store.d.ts","sourceRoot":"","sources":["../../src/planner/plan-store.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAMpD,oEAAoE;AACpE,wBAAsB,aAAa,CAClC,WAAW,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAOjC;AAED,mCAAmC;AACnC,wBAAsB,aAAa,CAClC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,8EAA8E;AAC9E,wBAAsB,QAAQ,CAC7B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAQ/B;AAED,oEAAoE;AACpE,wBAAsB,QAAQ,CAC7B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,aAAa,GACjB,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,kEAAkE;AAClE,wBAAsB,UAAU,CAC/B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAOf;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWxE"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Reads and writes cached heuristic plans and the hash index
3
+ * in the .greenlight/ directory.
4
+ */
5
+ import { readFile, writeFile, mkdir, unlink } from "node:fs/promises";
6
+ import { join } from "node:path";
7
+ const GREENLIGHT_DIR = ".greenlight";
8
+ const PLANS_DIR = ".greenlight/plans";
9
+ const HASH_FILE = ".greenlight/hashes.json";
10
+ /** Load the hash index mapping test keys to their source hashes. */
11
+ export async function loadHashIndex(projectRoot) {
12
+ try {
13
+ const raw = await readFile(join(projectRoot, HASH_FILE), "utf-8");
14
+ return JSON.parse(raw);
15
+ }
16
+ catch {
17
+ return {};
18
+ }
19
+ }
20
+ /** Save the hash index to disk. */
21
+ export async function saveHashIndex(projectRoot, index) {
22
+ await mkdir(join(projectRoot, GREENLIGHT_DIR), { recursive: true });
23
+ await writeFile(join(projectRoot, HASH_FILE), JSON.stringify(index, null, 2) + "\n");
24
+ }
25
+ /** Load a cached plan for a specific test case. Returns null if not found. */
26
+ export async function loadPlan(projectRoot, suiteSlug, testSlug) {
27
+ try {
28
+ const path = join(projectRoot, PLANS_DIR, suiteSlug, `${testSlug}.json`);
29
+ const raw = await readFile(path, "utf-8");
30
+ return JSON.parse(raw);
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ /** Save a heuristic plan to disk. Creates directories as needed. */
37
+ export async function savePlan(projectRoot, plan) {
38
+ const dir = join(projectRoot, PLANS_DIR, plan.suiteSlug);
39
+ await mkdir(dir, { recursive: true });
40
+ const path = join(dir, `${plan.testSlug}.json`);
41
+ await writeFile(path, JSON.stringify(plan, null, 2) + "\n");
42
+ }
43
+ /** Delete a cached plan file. No-op if the file doesn't exist. */
44
+ export async function deletePlan(projectRoot, suiteSlug, testSlug) {
45
+ try {
46
+ const path = join(projectRoot, PLANS_DIR, suiteSlug, `${testSlug}.json`);
47
+ await unlink(path);
48
+ }
49
+ catch {
50
+ // File doesn't exist — that's fine
51
+ }
52
+ }
53
+ /**
54
+ * Ensure .greenlight/ is in .gitignore.
55
+ * Appends the entry if .gitignore exists but doesn't contain it.
56
+ * No-op if .gitignore doesn't exist.
57
+ */
58
+ export async function ensureGitignore(projectRoot) {
59
+ const gitignorePath = join(projectRoot, ".gitignore");
60
+ try {
61
+ const content = await readFile(gitignorePath, "utf-8");
62
+ if (!content.includes(".greenlight")) {
63
+ const entry = "\n# GreenLight cached plans\n.greenlight/\n";
64
+ await writeFile(gitignorePath, content + entry);
65
+ }
66
+ }
67
+ catch {
68
+ // No .gitignore — skip
69
+ }
70
+ }
71
+ //# sourceMappingURL=plan-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-store.js","sourceRoot":"","sources":["../../src/planner/plan-store.ts"],"names":[],"mappings":"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"}