testdriverai 6.1.0 → 6.1.1-canary.613bfa3.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/agent/events.js CHANGED
@@ -1,5 +1,4 @@
1
1
  const { EventEmitter2 } = require("eventemitter2");
2
- const { censorSensitiveDataDeep } = require("./lib/censorship");
3
2
 
4
3
  // Factory function to create a new emitter instance with censoring middleware
5
4
  const createEmitter = () => {
@@ -13,14 +12,6 @@ const createEmitter = () => {
13
12
  ignoreErrors: false,
14
13
  });
15
14
 
16
- // Override emit to censor sensitive data before emitting
17
- const originalEmit = emitter.emit.bind(emitter);
18
- emitter.emit = function (event, ...args) {
19
- // Censor all arguments passed to emit
20
- const censoredArgs = args.map(censorSensitiveDataDeep);
21
- return originalEmit(event, ...censoredArgs);
22
- };
23
-
24
15
  return emitter;
25
16
  };
26
17
 
@@ -46,7 +37,7 @@ const events = {
46
37
  status: "status",
47
38
  log: {
48
39
  markdown: {
49
- static: "log:markdown:static",
40
+ static: "log:markdown",
50
41
  start: "log:markdown:start",
51
42
  chunk: "log:markdown:chunk",
52
43
  end: "log:markdown:end",
package/agent/index.js CHANGED
@@ -63,11 +63,11 @@ class TestDriverAgent extends EventEmitter2 {
63
63
  // Derive properties from cliArgs
64
64
  const flags = cliArgs.options || {};
65
65
  const firstArg = cliArgs.args && cliArgs.args[0];
66
-
66
+
67
67
  // All commands (run, edit, generate) use the same pattern:
68
68
  // first argument is the main file to work with
69
69
  this.thisFile = firstArg || this.config.TD_DEFAULT_TEST_FILE;
70
-
70
+
71
71
  this.resultFile = flags.resultFile || null;
72
72
  this.newSandbox = flags.newSandbox || false;
73
73
  this.healMode = flags.healMode || flags.heal || false;
@@ -91,8 +91,6 @@ class TestDriverAgent extends EventEmitter2 {
91
91
  }
92
92
  }
93
93
 
94
-
95
-
96
94
  // Create parser instance with this agent's emitter
97
95
  this.parser = createParser(this.emitter);
98
96
 
@@ -433,6 +431,7 @@ class TestDriverAgent extends EventEmitter2 {
433
431
 
434
432
  // Log current execution position for debugging
435
433
  if (this.sourceMapper.currentFileSourceMap) {
434
+ this.emitter.emit(events.log.log, "");
436
435
  this.emitter.emit(
437
436
  events.log.log,
438
437
  theme.dim(`${this.sourceMapper.getCurrentPositionDescription()}`),
@@ -490,14 +489,13 @@ class TestDriverAgent extends EventEmitter2 {
490
489
  sourcePosition: sourcePosition,
491
490
  });
492
491
 
493
- await this.haveAIResolveError(
492
+ return await this.haveAIResolveError(
494
493
  error,
495
494
  yaml.dump({ commands: [yml] }),
496
495
  depth,
497
496
  true,
498
497
  shouldSave,
499
498
  );
500
- throw error;
501
499
  }
502
500
  }
503
501
 
@@ -894,9 +892,6 @@ commands:
894
892
  // it will generate files that contain only "prompts"
895
893
  // @todo revit the generate command
896
894
  async generate(count = 1) {
897
-
898
- console.log('generate being called with count:', count)
899
-
900
895
  this.emitter.emit(events.log.debug, `generate called with count: ${count}`);
901
896
 
902
897
  await this.runLifecycle("prerun");
@@ -914,12 +909,12 @@ commands:
914
909
  let message = await this.sdk.req(
915
910
  "generate",
916
911
  {
917
- prompt: 'make sure to do a spellcheck',
912
+ prompt: "make sure to do a spellcheck",
918
913
  image,
919
914
  mousePosition: mouse,
920
915
  activeWindow: activeWindow,
921
916
  count,
922
- stream: false
917
+ stream: false,
923
918
  },
924
919
  (chunk) => {
925
920
  if (chunk.type === "data") {
@@ -932,8 +927,6 @@ commands:
932
927
 
933
928
  let testPrompts = await this.parser.findGenerativePrompts(message.data);
934
929
 
935
- console.log(testPrompts)
936
-
937
930
  // for each testPrompt
938
931
  for (const testPrompt of testPrompts) {
939
932
  // with the contents of the testPrompt
@@ -955,9 +948,9 @@ commands:
955
948
  const generateDir = path.join(this.workingDir, "testdriver", "generate");
956
949
  if (!fs.existsSync(generateDir)) {
957
950
  fs.mkdirSync(generateDir);
958
- console.log('Created generate directory:', generateDir);
951
+ console.log("Created generate directory:", generateDir);
959
952
  } else {
960
- console.log('Generate directory already exists:', generateDir);
953
+ console.log("Generate directory already exists:", generateDir);
961
954
  }
962
955
 
963
956
  let list = testPrompt.steps;
@@ -967,8 +960,7 @@ commands:
967
960
  steps: list,
968
961
  });
969
962
 
970
-
971
- console.log('writing file', path1, contents)
963
+ this.emitter.emit(events.log.debug, `writing file ${path1} ${contents}`);
972
964
 
973
965
  fs.writeFileSync(path1, contents);
974
966
  }
@@ -1525,6 +1517,8 @@ ${regression}
1525
1517
  }
1526
1518
 
1527
1519
  async embed(file, depth, pushToHistory) {
1520
+ let inputFile = JSON.parse(JSON.stringify(file));
1521
+
1528
1522
  this.analytics.track("embed", { file });
1529
1523
 
1530
1524
  this.emitter.emit(
@@ -1534,7 +1528,7 @@ ${regression}
1534
1528
 
1535
1529
  depth = depth + 1;
1536
1530
 
1537
- this.emitter.emit(events.log.log, `${file} (start)`);
1531
+ this.emitter.emit(events.log.log, `${inputFile} (start)`);
1538
1532
 
1539
1533
  // Use the new helper method to resolve file paths relative to testdriver directory
1540
1534
  const currentFilePath = this.sourceMapper.currentFilePath || this.thisFile;
@@ -1587,7 +1581,7 @@ ${regression}
1587
1581
  this.sourceMapper.restoreContext(previousContext);
1588
1582
  }
1589
1583
 
1590
- this.emitter.emit(events.log.log, `${file} (end)`);
1584
+ this.emitter.emit(events.log.log, `${inputFile} (end)`);
1591
1585
  }
1592
1586
 
1593
1587
  // Returns sandboxId to use (either from file if recent, or null)
@@ -1794,10 +1788,6 @@ ${regression}
1794
1788
  // Start the debugger server as early as possible to ensure event listeners are attached
1795
1789
  if (!debuggerStarted) {
1796
1790
  debuggerStarted = true; // Prevent multiple starts, especially when running test in parallel
1797
- this.emitter.emit(
1798
- events.log.narration,
1799
- theme.green(`Starting debugger server...`),
1800
- );
1801
1791
  debuggerProcess = await createDebuggerProcess(
1802
1792
  this.config,
1803
1793
  this.emitter,
@@ -1805,6 +1795,7 @@ ${regression}
1805
1795
  }
1806
1796
  this.debuggerUrl = debuggerProcess.url || null; // Store the debugger URL
1807
1797
  this.emitter.emit(events.log.log, `This is beta software!`);
1798
+ this.emitter.emit(events.log.log, ``);
1808
1799
  this.emitter.emit(
1809
1800
  events.log.log,
1810
1801
  theme.yellow(`Join our Discord for help`),
@@ -1813,6 +1804,7 @@ ${regression}
1813
1804
  events.log.log,
1814
1805
  `https://discord.com/invite/cWDFW8DzPm`,
1815
1806
  );
1807
+ this.emitter.emit(events.log.log, ``);
1816
1808
 
1817
1809
  // make testdriver directory if it doesn't exist
1818
1810
  let testdriverFolder = path.join(this.workingDir);
@@ -1826,7 +1818,10 @@ ${regression}
1826
1818
  }
1827
1819
 
1828
1820
  // if the directory for thisFile doesn't exist, create it
1829
- if (this.cliArgs.command !== "sandbox" && this.cliArgs.command !== "generate") {
1821
+ if (
1822
+ this.cliArgs.command !== "sandbox" &&
1823
+ this.cliArgs.command !== "generate"
1824
+ ) {
1830
1825
  const dir = path.dirname(this.thisFile);
1831
1826
  if (!fs.existsSync(dir)) {
1832
1827
  fs.mkdirSync(dir, { recursive: true });
@@ -1851,7 +1846,10 @@ ${regression}
1851
1846
  await this.sdk.auth();
1852
1847
  }
1853
1848
 
1854
- if (this.cliArgs.command !== "sandbox" && this.cliArgs.command !== "generate") {
1849
+ if (
1850
+ this.cliArgs.command !== "sandbox" &&
1851
+ this.cliArgs.command !== "generate"
1852
+ ) {
1855
1853
  this.emitter.emit(
1856
1854
  events.log.log,
1857
1855
  theme.dim(`Working on ${this.thisFile}`),
@@ -2045,21 +2043,23 @@ Please check your network connection, TD_API_KEY, or the service status.`,
2045
2043
  }
2046
2044
 
2047
2045
  async runLifecycle(lifecycleName) {
2048
-
2049
- console.log("Running lifecycle:", lifecycleName);
2050
-
2051
2046
  // Use the current file path from sourceMapper to find the lifecycle directory
2052
2047
  // If sourceMapper doesn't have a current file, use thisFile which should be the file being run
2053
2048
  let currentFilePath = this.sourceMapper.currentFilePath || this.thisFile;
2054
2049
 
2055
- console.log("Current file path:", currentFilePath);
2056
-
2050
+ this.emitter.emit(events.log.log, ``);
2051
+ this.emitter.emit(events.log.log, "Running lifecycle: " + lifecycleName);
2052
+
2057
2053
  // If we still don't have a currentFilePath, fall back to the default testdriver directory
2058
2054
  if (!currentFilePath) {
2059
- currentFilePath = path.join(this.workingDir, "testdriver", "testdriver.yaml");
2055
+ currentFilePath = path.join(
2056
+ this.workingDir,
2057
+ "testdriver",
2058
+ "testdriver.yaml",
2059
+ );
2060
2060
  console.log("No currentFilePath found, using fallback:", currentFilePath);
2061
2061
  }
2062
-
2062
+
2063
2063
  // Ensure we have an absolute path
2064
2064
  if (currentFilePath && !path.isAbsolute(currentFilePath)) {
2065
2065
  currentFilePath = path.resolve(this.workingDir, currentFilePath);
@@ -2097,7 +2097,7 @@ Please check your network connection, TD_API_KEY, or the service status.`,
2097
2097
  }
2098
2098
  }
2099
2099
 
2100
- console.log(lifecycleFile)
2100
+ this.emitter.emit(events.log.log, lifecycleFile);
2101
2101
 
2102
2102
  if (lifecycleFile) {
2103
2103
  // Store current source mapping state before running lifecycle file
@@ -80,21 +80,21 @@ commands:
80
80
  // this will actually interpret the command and execute it
81
81
  switch (object.command) {
82
82
  case "type":
83
- emitter.emit(events.log.narration, `typing ${object.text}`);
84
83
  emitter.emit(events.log.log, generator.jsonToManual(object));
84
+ emitter.emit(events.log.narration, `typing ${object.text}`);
85
85
  response = await commands.type(object.text, object.delay);
86
86
  break;
87
87
  case "press-keys":
88
+ emitter.emit(events.log.log, generator.jsonToManual(object));
88
89
  emitter.emit(
89
90
  events.log.narration,
90
- `pressing keys ${object.keys.join(",")}`,
91
+ `pressing keys: ${Array.isArray(object.keys) ? object.keys.join(", ") : object.keys}`,
91
92
  );
92
- emitter.emit(events.log.log, generator.jsonToManual(object));
93
93
  response = await commands["press-keys"](object.keys);
94
94
  break;
95
95
  case "scroll":
96
- emitter.emit(events.log.narration, `scrolling ${object.direction}`);
97
96
  emitter.emit(events.log.log, generator.jsonToManual(object));
97
+ emitter.emit(events.log.narration, `scrolling ${object.direction}`);
98
98
  response = await commands.scroll(
99
99
  object.direction,
100
100
  object.amount,
@@ -102,21 +102,21 @@ commands:
102
102
  );
103
103
  break;
104
104
  case "wait":
105
+ emitter.emit(events.log.log, generator.jsonToManual(object));
105
106
  emitter.emit(
106
107
  events.log.narration,
107
108
  `waiting ${object.timeout} seconds`,
108
109
  );
109
- emitter.emit(events.log.log, generator.jsonToManual(object));
110
110
  response = await commands.wait(object.timeout);
111
111
  break;
112
112
  case "click":
113
- emitter.emit(events.log.narration, `${object.action}`);
114
113
  emitter.emit(events.log.log, generator.jsonToManual(object));
114
+ emitter.emit(events.log.narration, `${object.action}`);
115
115
  response = await commands["click"](object.x, object.y, object.action);
116
116
  break;
117
117
  case "hover":
118
- emitter.emit(events.log.narration, `moving mouse`);
119
118
  emitter.emit(events.log.log, generator.jsonToManual(object));
119
+ emitter.emit(events.log.narration, `moving mouse`);
120
120
  response = await commands["hover"](object.x, object.y);
121
121
  break;
122
122
  case "drag":
@@ -124,11 +124,11 @@ commands:
124
124
  response = await commands["drag"](object.x, object.y);
125
125
  break;
126
126
  case "hover-text":
127
+ emitter.emit(events.log.log, generator.jsonToManual(object));
127
128
  emitter.emit(
128
129
  events.log.narration,
129
130
  `searching for ${object.description}`,
130
131
  );
131
- emitter.emit(events.log.log, generator.jsonToManual(object));
132
132
  response = await commands["hover-text"](
133
133
  object.text,
134
134
  object.description,
@@ -138,11 +138,11 @@ commands:
138
138
  );
139
139
  break;
140
140
  case "hover-image":
141
+ emitter.emit(events.log.log, generator.jsonToManual(object));
141
142
  emitter.emit(
142
143
  events.log.narration,
143
144
  `searching for image of ${object.description}`,
144
145
  );
145
- emitter.emit(events.log.log, generator.jsonToManual(object));
146
146
  response = await commands["hover-image"](
147
147
  object.description,
148
148
  object.action,
@@ -157,19 +157,19 @@ commands:
157
157
  response = await commands["match-image"](object.path, object.action);
158
158
  break;
159
159
  case "wait-for-image":
160
+ emitter.emit(events.log.log, generator.jsonToManual(object));
160
161
  emitter.emit(
161
162
  events.log.narration,
162
163
  `waiting for ${object.description}`,
163
164
  );
164
- emitter.emit(events.log.log, generator.jsonToManual(object));
165
165
  response = await commands["wait-for-image"](
166
166
  object.description,
167
167
  object.timeout,
168
168
  );
169
169
  break;
170
170
  case "wait-for-text":
171
- emitter.emit(events.log.narration, `waiting for ${object.text}`);
172
171
  emitter.emit(events.log.log, generator.jsonToManual(object));
172
+ emitter.emit(events.log.narration, `waiting for ${object.text}`);
173
173
  copy.text = "*****";
174
174
  response = await commands["wait-for-text"](
175
175
  object.text,
@@ -178,8 +178,8 @@ commands:
178
178
  );
179
179
  break;
180
180
  case "scroll-until-text":
181
- emitter.emit(events.log.narration, `scrolling until ${object.text}`);
182
181
  emitter.emit(events.log.log, generator.jsonToManual(object));
182
+ emitter.emit(events.log.narration, `scrolling until ${object.text}`);
183
183
  copy.text = "*****";
184
184
  response = await commands["scroll-until-text"](
185
185
  object.text,
@@ -191,8 +191,8 @@ commands:
191
191
  break;
192
192
  case "scroll-until-image": {
193
193
  const needle = object.description || object.path;
194
- emitter.emit(events.log.narration, `scrolling until ${needle}`);
195
194
  emitter.emit(events.log.log, generator.jsonToManual(object));
195
+ emitter.emit(events.log.narration, `scrolling until ${needle}`);
196
196
  response = await commands["scroll-until-image"](
197
197
  object.description,
198
198
  object.direction,
@@ -203,8 +203,8 @@ commands:
203
203
  break;
204
204
  }
205
205
  case "focus-application":
206
- emitter.emit(events.log.narration, `focusing ${object.name}`);
207
206
  emitter.emit(events.log.log, generator.jsonToManual(object));
207
+ emitter.emit(events.log.narration, `focusing ${object.name}`);
208
208
  response = await commands["focus-application"](object.name);
209
209
  break;
210
210
  case "remember": {
@@ -215,12 +215,12 @@ commands:
215
215
  break;
216
216
  }
217
217
  case "assert":
218
- emitter.emit(events.log.narration, `asserting ${object.expect}`);
219
218
  emitter.emit(events.log.log, generator.jsonToManual(object));
219
+ emitter.emit(events.log.narration, `asserting ${object.expect}`);
220
220
  response = await commands.assert(object.expect, object.async);
221
+
221
222
  break;
222
223
  case "exec":
223
- emitter.emit(events.log.narration, `exec`);
224
224
  emitter.emit(
225
225
  events.log.log,
226
226
  generator.jsonToManual({
@@ -176,7 +176,7 @@ const createCommands = (
176
176
  }
177
177
 
178
178
  const handleAssertResponse = (response) => {
179
- emitter.emit(events.log.markdown.static, response);
179
+ emitter.emit(events.log.log, response);
180
180
 
181
181
  if (response.indexOf("The task passed") > -1) {
182
182
  return true;
@@ -727,14 +727,14 @@ const createCommands = (
727
727
  `Command failed with exit code ${result.out.returncode}: ${result.out.stderr}`,
728
728
  );
729
729
  } else {
730
- if (!silent) {
731
- emitter.emit(events.log.log, theme.dim(`Command stdout:`), true);
730
+ if (!silent && result.out?.stdout) {
731
+ emitter.emit(events.log.log, theme.dim(`stdout:`), true);
732
732
  emitter.emit(events.log.log, `${result.out.stdout}`, true);
733
+ }
733
734
 
734
- if (result.out.stderr) {
735
- emitter.emit(events.log.log, theme.dim(`Command stderr:`), true);
736
- emitter.emit(events.log.log, `${result.out.stderr}`, true);
737
- }
735
+ if (!silent && result.out.stderr) {
736
+ emitter.emit(events.log.log, theme.dim(`stderr:`), true);
737
+ emitter.emit(events.log.log, `${result.out.stderr}`, true);
738
738
  }
739
739
 
740
740
  return result.out?.stdout?.trim();
@@ -65,11 +65,6 @@ function createDebuggerServer(config = {}) {
65
65
 
66
66
  ws.on("close", () => {
67
67
  clients.delete(ws);
68
-
69
- // If no clients connected, we can optionally shut down
70
- if (clients.size === 0) {
71
- console.log("No clients connected, keeping server alive");
72
- }
73
68
  });
74
69
 
75
70
  ws.on("error", (error) => {
@@ -23,7 +23,7 @@ const createSandbox = (emitter, analytics) => {
23
23
  if (this.socket) {
24
24
  this.messageId++;
25
25
  message.requestId = `${this.uniqueId}-${this.messageId}`;
26
-
26
+
27
27
  // Start timing for this message
28
28
  const timingKey = `sandbox-${message.type}`;
29
29
  marky.mark(timingKey);
@@ -263,7 +263,7 @@ class SourceMapper {
263
263
  let description = `${fileName}:${(sourcePosition.step.startLine || 0) + 1}`;
264
264
 
265
265
  if (sourcePosition.command) {
266
- description += `:${(sourcePosition.command.startLine || 0) + 1} (${sourcePosition.command.command || "unknown command"})`;
266
+ description += `:${(sourcePosition.command.startLine || 0) + 1}`;
267
267
  } else {
268
268
  description += ` (step ${sourcePosition.step.stepIndex + 1})`;
269
269
  }