auto-feedback 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 (90) hide show
  1. package/README.md +180 -0
  2. package/build/capture/console-collector.d.ts +16 -0
  3. package/build/capture/console-collector.js +43 -0
  4. package/build/capture/error-collector.d.ts +15 -0
  5. package/build/capture/error-collector.js +47 -0
  6. package/build/capture/network-collector.d.ts +16 -0
  7. package/build/capture/network-collector.js +76 -0
  8. package/build/capture/process-collector.d.ts +16 -0
  9. package/build/capture/process-collector.js +48 -0
  10. package/build/capture/types.d.ts +61 -0
  11. package/build/capture/types.js +5 -0
  12. package/build/index.d.ts +6 -0
  13. package/build/index.js +41 -0
  14. package/build/interaction/selectors.d.ts +26 -0
  15. package/build/interaction/selectors.js +84 -0
  16. package/build/interaction/types.d.ts +56 -0
  17. package/build/interaction/types.js +5 -0
  18. package/build/process/cleanup.d.ts +23 -0
  19. package/build/process/cleanup.js +50 -0
  20. package/build/process/launcher.d.ts +22 -0
  21. package/build/process/launcher.js +54 -0
  22. package/build/process/monitor.d.ts +14 -0
  23. package/build/process/monitor.js +67 -0
  24. package/build/process/types.d.ts +84 -0
  25. package/build/process/types.js +5 -0
  26. package/build/screenshot/auto-capture.d.ts +14 -0
  27. package/build/screenshot/auto-capture.js +38 -0
  28. package/build/screenshot/capture.d.ts +21 -0
  29. package/build/screenshot/capture.js +48 -0
  30. package/build/screenshot/optimize.d.ts +19 -0
  31. package/build/screenshot/optimize.js +28 -0
  32. package/build/screenshot/types.d.ts +43 -0
  33. package/build/screenshot/types.js +4 -0
  34. package/build/server.d.ts +10 -0
  35. package/build/server.js +18 -0
  36. package/build/session-manager.d.ts +119 -0
  37. package/build/session-manager.js +284 -0
  38. package/build/tools/check-port.d.ts +10 -0
  39. package/build/tools/check-port.js +40 -0
  40. package/build/tools/click-element.d.ts +13 -0
  41. package/build/tools/click-element.js +118 -0
  42. package/build/tools/get-console-logs.d.ts +7 -0
  43. package/build/tools/get-console-logs.js +55 -0
  44. package/build/tools/get-element-state.d.ts +14 -0
  45. package/build/tools/get-element-state.js +116 -0
  46. package/build/tools/get-errors.d.ts +7 -0
  47. package/build/tools/get-errors.js +40 -0
  48. package/build/tools/get-network-logs.d.ts +7 -0
  49. package/build/tools/get-network-logs.js +58 -0
  50. package/build/tools/get-process-output.d.ts +7 -0
  51. package/build/tools/get-process-output.js +55 -0
  52. package/build/tools/get-screenshot.d.ts +7 -0
  53. package/build/tools/get-screenshot.js +32 -0
  54. package/build/tools/index.d.ts +9 -0
  55. package/build/tools/index.js +117 -0
  56. package/build/tools/launch-electron.d.ts +13 -0
  57. package/build/tools/launch-electron.js +97 -0
  58. package/build/tools/launch-web-server.d.ts +13 -0
  59. package/build/tools/launch-web-server.js +88 -0
  60. package/build/tools/launch-windows-exe.d.ts +13 -0
  61. package/build/tools/launch-windows-exe.js +81 -0
  62. package/build/tools/navigate.d.ts +13 -0
  63. package/build/tools/navigate.js +137 -0
  64. package/build/tools/run-workflow.d.ts +14 -0
  65. package/build/tools/run-workflow.js +207 -0
  66. package/build/tools/screenshot-desktop.d.ts +13 -0
  67. package/build/tools/screenshot-desktop.js +80 -0
  68. package/build/tools/screenshot-electron.d.ts +13 -0
  69. package/build/tools/screenshot-electron.js +72 -0
  70. package/build/tools/screenshot-web.d.ts +13 -0
  71. package/build/tools/screenshot-web.js +129 -0
  72. package/build/tools/stop-process.d.ts +14 -0
  73. package/build/tools/stop-process.js +41 -0
  74. package/build/tools/type-text.d.ts +13 -0
  75. package/build/tools/type-text.js +137 -0
  76. package/build/tools/wait-for-element.d.ts +14 -0
  77. package/build/tools/wait-for-element.js +93 -0
  78. package/build/types/index.d.ts +31 -0
  79. package/build/types/index.js +4 -0
  80. package/build/utils/errors.d.ts +26 -0
  81. package/build/utils/errors.js +62 -0
  82. package/build/utils/shutdown.d.ts +16 -0
  83. package/build/utils/shutdown.js +34 -0
  84. package/build/workflow/assertions.d.ts +25 -0
  85. package/build/workflow/assertions.js +326 -0
  86. package/build/workflow/executor.d.ts +34 -0
  87. package/build/workflow/executor.js +269 -0
  88. package/build/workflow/types.d.ts +95 -0
  89. package/build/workflow/types.js +6 -0
  90. package/package.json +36 -0
@@ -0,0 +1,269 @@
1
+ /**
2
+ * Workflow step executor
3
+ * Sequentially runs actions against a Playwright page with per-step
4
+ * screenshot capture and console/error log delta tracking.
5
+ *
6
+ * Stop-on-error semantics: failed steps still capture screenshot and
7
+ * log deltas for debugging context, then execution halts.
8
+ */
9
+ import { resolveSelector } from "../interaction/selectors.js";
10
+ import { capturePlaywrightPage } from "../screenshot/capture.js";
11
+ import { optimizeScreenshot } from "../screenshot/optimize.js";
12
+ import { evaluateAssertion } from "./assertions.js";
13
+ /**
14
+ * Validate a workflow step has all required fields for its action type.
15
+ *
16
+ * @param step - The workflow step to validate
17
+ * @param index - Zero-based step index (for error messages)
18
+ * @returns null if valid, or a descriptive error string
19
+ */
20
+ export function validateStep(step, index) {
21
+ switch (step.action) {
22
+ case "click":
23
+ if (!step.selector) {
24
+ return `Step ${index}: 'click' requires a 'selector' field`;
25
+ }
26
+ break;
27
+ case "type":
28
+ if (!step.selector) {
29
+ return `Step ${index}: 'type' requires a 'selector' field`;
30
+ }
31
+ if (step.text === undefined || step.text === null) {
32
+ return `Step ${index}: 'type' requires a 'text' field`;
33
+ }
34
+ break;
35
+ case "navigate":
36
+ if (!step.url) {
37
+ return `Step ${index}: 'navigate' requires a 'url' field`;
38
+ }
39
+ break;
40
+ case "wait":
41
+ if (!step.selector) {
42
+ return `Step ${index}: 'wait' requires a 'selector' field`;
43
+ }
44
+ break;
45
+ case "screenshot":
46
+ // No required fields
47
+ break;
48
+ case "assert": {
49
+ if (!step.selector) {
50
+ return `Step ${index}: 'assert' requires a 'selector' field`;
51
+ }
52
+ if (!step.assertType) {
53
+ return `Step ${index}: 'assert' requires an 'assertType' field`;
54
+ }
55
+ const needsExpected = ["text-equals", "text-contains", "value-equals", "attribute-equals"];
56
+ if (needsExpected.includes(step.assertType) && step.expected === undefined) {
57
+ return `Step ${index}: '${step.assertType}' assertion requires an 'expected' field`;
58
+ }
59
+ const needsAttribute = ["has-attribute", "attribute-equals"];
60
+ if (needsAttribute.includes(step.assertType) && !step.attribute) {
61
+ return `Step ${index}: '${step.assertType}' assertion requires an 'attribute' field`;
62
+ }
63
+ break;
64
+ }
65
+ default:
66
+ return `Step ${index}: unknown action '${step.action}'`;
67
+ }
68
+ return null;
69
+ }
70
+ /**
71
+ * Execute a workflow — sequential steps against a Playwright page.
72
+ *
73
+ * Each step: validate -> execute action -> capture screenshot -> capture log deltas.
74
+ * On error: capture screenshot + log deltas for debugging, then stop.
75
+ *
76
+ * @returns WorkflowResult with per-step results, counts, and optional failedStep index
77
+ */
78
+ export async function executeWorkflow(params) {
79
+ const { page, steps, sessionManager, sessionId } = params;
80
+ let pageIdentifier = params.pageIdentifier;
81
+ const results = [];
82
+ let lastConsoleCount = 0;
83
+ let lastErrorCount = 0;
84
+ for (let i = 0; i < steps.length; i++) {
85
+ const step = steps[i];
86
+ const result = {
87
+ stepIndex: i,
88
+ action: step.action,
89
+ success: false,
90
+ timestamp: new Date().toISOString(),
91
+ consoleDelta: [],
92
+ errorDelta: [],
93
+ };
94
+ // Validate step before execution
95
+ const validationError = validateStep(step, i);
96
+ if (validationError) {
97
+ result.error = validationError;
98
+ results.push(result);
99
+ break;
100
+ }
101
+ try {
102
+ // Execute action via switch dispatch
103
+ switch (step.action) {
104
+ case "click": {
105
+ const locator = resolveSelector(page, step.selector);
106
+ await locator.click({
107
+ button: step.button ?? undefined,
108
+ clickCount: step.clickCount ?? undefined,
109
+ timeout: step.timeout ?? 30000,
110
+ });
111
+ // Post-click stability wait (matches click-element.ts pattern)
112
+ await Promise.race([
113
+ page.waitForLoadState("load").catch(() => { }),
114
+ new Promise((resolve) => setTimeout(resolve, 2000)),
115
+ ]);
116
+ break;
117
+ }
118
+ case "type": {
119
+ const locator = resolveSelector(page, step.selector);
120
+ const effectiveTimeout = step.timeout ?? 30000;
121
+ if (step.pressSequentially) {
122
+ // Clear first, then type char-by-char
123
+ await locator.fill("", { timeout: effectiveTimeout });
124
+ await locator.pressSequentially(step.text, {
125
+ delay: 50,
126
+ timeout: effectiveTimeout,
127
+ });
128
+ }
129
+ else if (step.clear === false) {
130
+ // Append mode: click to focus, then insertText
131
+ await locator.click({ timeout: effectiveTimeout });
132
+ await page.keyboard.insertText(step.text);
133
+ }
134
+ else {
135
+ // Default: fill() clears and types in one step
136
+ await locator.fill(step.text, { timeout: effectiveTimeout });
137
+ }
138
+ break;
139
+ }
140
+ case "navigate": {
141
+ await page.goto(step.url, {
142
+ waitUntil: "load",
143
+ timeout: step.timeout ?? 30000,
144
+ });
145
+ // Update PageReference URL (matches navigate.ts lines 120-128)
146
+ const oldRef = sessionManager.getPageRef(sessionId, pageIdentifier);
147
+ if (oldRef) {
148
+ sessionManager.removePageRef(sessionId, pageIdentifier);
149
+ sessionManager.setPageRef(sessionId, step.url, {
150
+ ...oldRef,
151
+ url: step.url,
152
+ });
153
+ }
154
+ // Update local identifier for subsequent steps
155
+ pageIdentifier = step.url;
156
+ break;
157
+ }
158
+ case "screenshot": {
159
+ // No-op: screenshot is captured in the post-step phase below
160
+ break;
161
+ }
162
+ case "wait": {
163
+ const locator = resolveSelector(page, step.selector);
164
+ await locator.waitFor({
165
+ state: step.state ?? "visible",
166
+ timeout: step.timeout ?? 30000,
167
+ });
168
+ break;
169
+ }
170
+ case "assert": {
171
+ const effectiveTimeout = step.timeout ?? 30000;
172
+ const assertionResult = await evaluateAssertion(page, step, effectiveTimeout);
173
+ result.assertion = assertionResult;
174
+ if (!assertionResult.passed) {
175
+ // Failed assertion: capture screenshot + log deltas, then stop workflow
176
+ const rawBuffer = await capturePlaywrightPage(page, { fullPage: step.fullPage ?? false });
177
+ const optimized = await optimizeScreenshot(rawBuffer, { maxWidth: 1024, quality: 60 });
178
+ result.screenshotBase64 = optimized.data.toString("base64");
179
+ result.screenshotMimeType = optimized.mimeType;
180
+ // Capture log deltas
181
+ const allConsole = sessionManager.getConsoleCollectors(sessionId).flatMap((c) => [...c.getEntries()]);
182
+ const allErrors = sessionManager.getErrorCollectors(sessionId).flatMap((c) => [...c.getEntries()]);
183
+ result.consoleDelta = allConsole.slice(lastConsoleCount);
184
+ result.errorDelta = allErrors.slice(lastErrorCount);
185
+ lastConsoleCount = allConsole.length;
186
+ lastErrorCount = allErrors.length;
187
+ results.push(result);
188
+ break; // Break out of switch
189
+ }
190
+ break; // Switch break for passed assertions (falls through to normal screenshot/log capture)
191
+ }
192
+ }
193
+ // Stop-on-error for failed assertions
194
+ if (step.action === "assert" && result.assertion && !result.assertion.passed) {
195
+ break; // Break the for loop
196
+ }
197
+ // Capture screenshot (aggressive optimization for workflows)
198
+ const rawBuffer = await capturePlaywrightPage(page, {
199
+ fullPage: step.fullPage ?? false,
200
+ });
201
+ const optimized = await optimizeScreenshot(rawBuffer, {
202
+ maxWidth: 1024,
203
+ quality: 60,
204
+ });
205
+ result.screenshotBase64 = optimized.data.toString("base64");
206
+ result.screenshotMimeType = optimized.mimeType;
207
+ // Capture log deltas (spread copy for mutation safety)
208
+ const allConsole = sessionManager
209
+ .getConsoleCollectors(sessionId)
210
+ .flatMap((c) => [...c.getEntries()]);
211
+ const allErrors = sessionManager
212
+ .getErrorCollectors(sessionId)
213
+ .flatMap((c) => [...c.getEntries()]);
214
+ result.consoleDelta = allConsole.slice(lastConsoleCount);
215
+ result.errorDelta = allErrors.slice(lastErrorCount);
216
+ lastConsoleCount = allConsole.length;
217
+ lastErrorCount = allErrors.length;
218
+ result.success = true;
219
+ results.push(result);
220
+ }
221
+ catch (error) {
222
+ // Set error message
223
+ result.error =
224
+ error instanceof Error ? error.message : String(error);
225
+ // Best-effort screenshot capture on failure
226
+ try {
227
+ const rawBuffer = await capturePlaywrightPage(page, {
228
+ fullPage: step.fullPage ?? false,
229
+ });
230
+ const optimized = await optimizeScreenshot(rawBuffer, {
231
+ maxWidth: 1024,
232
+ quality: 60,
233
+ });
234
+ result.screenshotBase64 = optimized.data.toString("base64");
235
+ result.screenshotMimeType = optimized.mimeType;
236
+ }
237
+ catch {
238
+ // Silently skip if screenshot capture fails
239
+ }
240
+ // Still capture log deltas for debugging context
241
+ try {
242
+ const allConsole = sessionManager
243
+ .getConsoleCollectors(sessionId)
244
+ .flatMap((c) => [...c.getEntries()]);
245
+ const allErrors = sessionManager
246
+ .getErrorCollectors(sessionId)
247
+ .flatMap((c) => [...c.getEntries()]);
248
+ result.consoleDelta = allConsole.slice(lastConsoleCount);
249
+ result.errorDelta = allErrors.slice(lastErrorCount);
250
+ lastConsoleCount = allConsole.length;
251
+ lastErrorCount = allErrors.length;
252
+ }
253
+ catch {
254
+ // Silently skip if log capture fails
255
+ }
256
+ results.push(result);
257
+ break; // Stop-on-error: halt after first failure
258
+ }
259
+ }
260
+ // Build workflow result
261
+ const completedSteps = results.filter((r) => r.success).length;
262
+ const failedResult = results.find((r) => !r.success);
263
+ return {
264
+ steps: results,
265
+ totalSteps: steps.length,
266
+ completedSteps,
267
+ failedStep: failedResult ? failedResult.stepIndex : undefined,
268
+ };
269
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Workflow execution types
3
+ * Defines step schemas, per-step results, and overall workflow results
4
+ * for the sequential action executor (QA-01/QA-02).
5
+ */
6
+ import type { ConsoleEntry, ErrorEntry } from "../capture/types.js";
7
+ /**
8
+ * A single workflow step — flat schema with action-specific optional fields.
9
+ *
10
+ * Required fields per action:
11
+ * - click: selector
12
+ * - type: selector, text (text can be empty string but must be defined)
13
+ * - navigate: url
14
+ * - wait: selector
15
+ * - screenshot: (no required fields)
16
+ * - assert: selector, assertType (plus expected/attribute depending on assertType)
17
+ */
18
+ export interface WorkflowStep {
19
+ /** Action to perform */
20
+ action: "click" | "type" | "navigate" | "screenshot" | "wait" | "assert";
21
+ /** Element selector — required for click, type, wait */
22
+ selector?: string;
23
+ /** Text to type — required for type action */
24
+ text?: string;
25
+ /** URL to navigate to — required for navigate action */
26
+ url?: string;
27
+ /** Mouse button for click (default: left) */
28
+ button?: "left" | "right" | "middle";
29
+ /** Number of clicks for click action (1-3, e.g. 2 for double-click) */
30
+ clickCount?: number;
31
+ /** Type one character at a time instead of fill/paste (default: false) */
32
+ pressSequentially?: boolean;
33
+ /** Clear field before typing (default: true). Set false to append. */
34
+ clear?: boolean;
35
+ /** Capture full scrollable page for screenshot step */
36
+ fullPage?: boolean;
37
+ /** Wait state for wait action (default: visible) */
38
+ state?: "visible" | "hidden" | "attached" | "detached";
39
+ /** Assertion type — required for assert action */
40
+ assertType?: "exists" | "not-exists" | "visible" | "hidden" | "text-equals" | "text-contains" | "has-attribute" | "attribute-equals" | "enabled" | "disabled" | "checked" | "not-checked" | "value-equals";
41
+ /** Expected value for text/attribute assertions */
42
+ expected?: string;
43
+ /** Attribute name for attribute assertions */
44
+ attribute?: string;
45
+ /** Per-step timeout in ms (default: 30000) */
46
+ timeout?: number;
47
+ }
48
+ /**
49
+ * Result of a single workflow step execution.
50
+ * Always includes log deltas for debugging context.
51
+ * Screenshot is captured even on failure (best-effort).
52
+ */
53
+ export interface StepResult {
54
+ /** Zero-based index of this step in the workflow */
55
+ stepIndex: number;
56
+ /** Action that was executed */
57
+ action: string;
58
+ /** Whether the step completed successfully */
59
+ success: boolean;
60
+ /** ISO timestamp when step execution started */
61
+ timestamp: string;
62
+ /** Base64-encoded screenshot captured after step (WebP, quality 60, max 1024px wide) */
63
+ screenshotBase64?: string;
64
+ /** MIME type of the screenshot (image/webp) */
65
+ screenshotMimeType?: string;
66
+ /** Console log entries captured during this step */
67
+ consoleDelta: ConsoleEntry[];
68
+ /** Error entries captured during this step */
69
+ errorDelta: ErrorEntry[];
70
+ /** Error message if step failed */
71
+ error?: string;
72
+ /** Structured assertion result — only present for assert steps */
73
+ assertion?: {
74
+ passed: boolean;
75
+ assertType: string;
76
+ selector: string;
77
+ expected: string | null;
78
+ actual: string | null;
79
+ message: string;
80
+ };
81
+ }
82
+ /**
83
+ * Overall workflow execution result.
84
+ * Contains all step results and summary counts.
85
+ */
86
+ export interface WorkflowResult {
87
+ /** Results for each executed step (may be fewer than totalSteps on failure) */
88
+ steps: StepResult[];
89
+ /** Total number of steps in the input workflow */
90
+ totalSteps: number;
91
+ /** Number of steps that completed successfully */
92
+ completedSteps: number;
93
+ /** Index of the failed step, if any */
94
+ failedStep?: number;
95
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Workflow execution types
3
+ * Defines step schemas, per-step results, and overall workflow results
4
+ * for the sequential action executor (QA-01/QA-02).
5
+ */
6
+ export {};
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "auto-feedback",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "MCP server for GUI testing and feedback - gives Claude Code eyes and hands",
6
+ "bin": {
7
+ "auto-feedback": "./build/index.js"
8
+ },
9
+ "files": [
10
+ "build"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "dev": "tsx src/index.ts",
15
+ "prepublishOnly": "npm run build"
16
+ },
17
+ "dependencies": {
18
+ "@modelcontextprotocol/sdk": "^1.0.0",
19
+ "detect-port": "^2.1.0",
20
+ "node-screenshots": "^0.2.1",
21
+ "playwright": "^1.58.2",
22
+ "sharp": "^0.34.5",
23
+ "tree-kill": "^1.2.2",
24
+ "wait-on": "^9.0.3",
25
+ "zod": "^3.25.0"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^20.0.0",
29
+ "@types/wait-on": "^5.3.4",
30
+ "tsx": "^4.0.0",
31
+ "typescript": "^5.0.0"
32
+ },
33
+ "engines": {
34
+ "node": ">=18.0.0"
35
+ }
36
+ }