testdriverai 6.1.10 → 6.2.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 (67) hide show
  1. package/.github/workflows/acceptance-tests.yml +0 -2
  2. package/.github/workflows/acceptance-v6.yml +0 -2
  3. package/.github/workflows/lint.yml +1 -4
  4. package/.github/workflows/publish-canary.yml +0 -2
  5. package/.github/workflows/publish-latest.yml +0 -1
  6. package/.prettierignore +0 -1
  7. package/.vscode/settings.json +1 -4
  8. package/agent/events.js +10 -1
  9. package/agent/index.js +76 -104
  10. package/agent/interface.js +6 -43
  11. package/agent/lib/censorship.js +10 -15
  12. package/agent/lib/commander.js +18 -31
  13. package/agent/lib/commands.js +63 -81
  14. package/agent/lib/debugger-server.js +5 -0
  15. package/agent/lib/generator.js +2 -2
  16. package/agent/lib/redraw.js +1 -0
  17. package/agent/lib/sandbox.js +2 -0
  18. package/agent/lib/sdk.js +1 -2
  19. package/agent/lib/source-mapper.js +1 -1
  20. package/agent/lib/system.js +6 -1
  21. package/docs/account/enterprise.mdx +12 -8
  22. package/docs/account/pricing.mdx +2 -2
  23. package/docs/account/projects.mdx +0 -5
  24. package/docs/cli/overview.mdx +6 -6
  25. package/docs/commands/assert.mdx +0 -1
  26. package/docs/commands/hover-text.mdx +1 -3
  27. package/docs/commands/match-image.mdx +4 -5
  28. package/docs/commands/press-keys.mdx +8 -6
  29. package/docs/commands/scroll-until-image.mdx +7 -8
  30. package/docs/commands/scroll-until-text.mdx +6 -7
  31. package/docs/commands/wait-for-image.mdx +4 -5
  32. package/docs/commands/wait-for-text.mdx +5 -6
  33. package/docs/docs.json +40 -42
  34. package/docs/getting-started/vscode.mdx +56 -67
  35. package/docs/guide/environment-variables.mdx +5 -5
  36. package/docs/overview/comparison.mdx +39 -22
  37. package/docs/overview/quickstart.mdx +32 -84
  38. package/docs/styles.css +1 -10
  39. package/interfaces/cli/lib/base.js +6 -27
  40. package/interfaces/cli/utils/factory.js +4 -17
  41. package/interfaces/logger.js +5 -4
  42. package/interfaces/readline.js +1 -1
  43. package/package.json +3 -3
  44. package/schema.json +2 -22
  45. package/testdriver/acceptance/hover-text.yaml +1 -2
  46. package/testdriver/acceptance/prompt.yaml +1 -4
  47. package/testdriver/acceptance/scroll-until-image.yaml +0 -5
  48. package/testdriver/{lifecycle/prerun.yaml → examples/web/lifecycle/provision.yaml} +0 -6
  49. package/testdriver/lifecycle/provision.yaml +20 -0
  50. package/.github/workflows/self-hosted.yml +0 -102
  51. package/docs/apps/tauri-apps.mdx +0 -361
  52. package/docs/getting-started/playwright.mdx +0 -342
  53. package/docs/getting-started/self-hosting.mdx +0 -370
  54. package/docs/guide/dashcam.mdx +0 -118
  55. package/docs/images/content/self-hosted/launchtemplateid.png +0 -0
  56. package/docs/images/content/vscode/ide-full.png +0 -0
  57. package/docs/images/content/vscode/running.png +0 -0
  58. package/interfaces/cli/commands/generate.js +0 -3
  59. package/setup/aws/cloudformation.yaml +0 -463
  60. package/setup/aws/spawn-runner.sh +0 -190
  61. package/testdriver/edge-cases/js-exception.yaml +0 -8
  62. package/testdriver/edge-cases/js-promise.yaml +0 -19
  63. package/testdriver/edge-cases/lifecycle/postrun.yaml +0 -10
  64. package/testdriver/edge-cases/success-test.yaml +0 -9
  65. package/testdriver/examples/web/lifecycle/postrun.yaml +0 -7
  66. package/testdriver/examples/web/lifecycle/prerun.yaml +0 -17
  67. package/testdriver/lifecycle/postrun.yaml +0 -7
@@ -7,8 +7,6 @@ on:
7
7
  # So that we don't do expensive tests until approved
8
8
  push:
9
9
  branches: [main]
10
- paths-ignore:
11
- - "docs/**"
12
10
  # So that we can manually trigger tests when there's flake
13
11
  workflow_dispatch:
14
12
 
@@ -5,8 +5,6 @@ on:
5
5
  push:
6
6
  branches:
7
7
  - main
8
- paths-ignore:
9
- - "docs/**"
10
8
  pull_request:
11
9
  branches:
12
10
  - main
@@ -1,10 +1,7 @@
1
1
  # Ensure affected code follows standards and is formatted correctly. Otherwise, automatic formatting in future changes will cause larger diffs.
2
2
  name: Lint + Prettier
3
3
 
4
- on:
5
- push:
6
- paths-ignore:
7
- - "docs/**"
4
+ on: push
8
5
 
9
6
  jobs:
10
7
  lint:
@@ -10,8 +10,6 @@ on:
10
10
  # So that we publish for every push to `main`, despite tests
11
11
  push:
12
12
  branches: [main]
13
- paths-ignore:
14
- - "docs/**"
15
13
  workflow_dispatch:
16
14
 
17
15
  jobs:
@@ -2,7 +2,6 @@
2
2
  name: Publish @latest to NPM
3
3
 
4
4
  on:
5
- workflow_dispatch:
6
5
  workflow_run:
7
6
  workflows: ["Acceptance Tests"]
8
7
  branches: [main]
package/.prettierignore CHANGED
@@ -1,4 +1,3 @@
1
1
  agent/lib/subimage/opencv.js
2
2
  node_modules
3
3
  schema.json
4
- docs
@@ -3,8 +3,5 @@
3
3
  "source.organizeImports": "explicit"
4
4
  },
5
5
  "editor.formatOnSave": true,
6
- "editor.defaultFormatter": "esbenp.prettier-vscode",
7
- "yaml.schemas": {
8
- "https://raw.githubusercontent.com/testdriverai/testdriverai/main/schema.json": "file:///Users/kid/Desktop/td/internal/testdriverai/testdriver.yaml"
9
- }
6
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
10
7
  }
package/agent/events.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const { EventEmitter2 } = require("eventemitter2");
2
+ const { censorSensitiveDataDeep } = require("./lib/censorship");
2
3
 
3
4
  // Factory function to create a new emitter instance with censoring middleware
4
5
  const createEmitter = () => {
@@ -12,6 +13,14 @@ const createEmitter = () => {
12
13
  ignoreErrors: false,
13
14
  });
14
15
 
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
+
15
24
  return emitter;
16
25
  };
17
26
 
@@ -37,7 +46,7 @@ const events = {
37
46
  status: "status",
38
47
  log: {
39
48
  markdown: {
40
- static: "log:markdown",
49
+ static: "log:markdown:static",
41
50
  start: "log:markdown:start",
42
51
  chunk: "log:markdown:chunk",
43
52
  end: "log:markdown:end",
package/agent/index.js CHANGED
@@ -63,18 +63,13 @@ 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
-
67
- // All commands (run, edit, generate) use the same pattern:
68
- // first argument is the main file to work with
69
66
  this.thisFile = firstArg || this.config.TD_DEFAULT_TEST_FILE;
70
-
71
67
  this.resultFile = flags.resultFile || null;
72
68
  this.newSandbox = flags.newSandbox || false;
73
69
  this.healMode = flags.healMode || flags.heal || false;
74
70
  this.sandboxId = flags["sandbox-id"] || null;
75
71
  this.sandboxAmi = flags["sandbox-ami"] || null;
76
72
  this.sandboxInstance = flags["sandbox-instance"] || null;
77
- this.ip = flags.ip || null;
78
73
  this.workingDir = flags.workingDir || process.cwd();
79
74
 
80
75
  // Resolve thisFile to absolute path with proper extension
@@ -227,15 +222,7 @@ class TestDriverAgent extends EventEmitter2 {
227
222
  if (skipPostrun) {
228
223
  this.exit(true);
229
224
  } else {
230
- try {
231
- await this.summarize(error.message);
232
- } catch (summarizeError) {
233
- // If summarization fails, log it but don't let it prevent postrun from running
234
- this.emitter.emit(
235
- events.log.warn,
236
- theme.yellow(`Failed to summarize: ${summarizeError.message}`),
237
- );
238
- }
225
+ await this.summarize(error.message);
239
226
  // Always run postrun lifecycle script, even for fatal errors
240
227
  return await this.exit(true, false, true);
241
228
  }
@@ -432,7 +419,6 @@ class TestDriverAgent extends EventEmitter2 {
432
419
 
433
420
  // Log current execution position for debugging
434
421
  if (this.sourceMapper.currentFileSourceMap) {
435
- this.emitter.emit(events.log.log, "");
436
422
  this.emitter.emit(
437
423
  events.log.log,
438
424
  theme.dim(`${this.sourceMapper.getCurrentPositionDescription()}`),
@@ -490,13 +476,14 @@ class TestDriverAgent extends EventEmitter2 {
490
476
  sourcePosition: sourcePosition,
491
477
  });
492
478
 
493
- return await this.haveAIResolveError(
479
+ await this.haveAIResolveError(
494
480
  error,
495
481
  yaml.dump({ commands: [yml] }),
496
482
  depth,
497
483
  true,
498
484
  shouldSave,
499
485
  );
486
+ throw error;
500
487
  }
501
488
  }
502
489
 
@@ -892,33 +879,30 @@ commands:
892
879
  // based on the current state of the system (primarily the current screenshot)
893
880
  // it will generate files that contain only "prompts"
894
881
  // @todo revit the generate command
895
- async generate(count = 1, prompt = null) {
896
- this.emitter.emit(
897
- events.log.debug,
898
- `generate called with count: ${count}, prompt: ${prompt}`,
899
- );
900
-
901
- await this.runLifecycle("prerun");
882
+ async generate(type, count, baseYaml, skipYaml = false) {
883
+ this.emitter.emit(events.log.debug, "generate called, %s", type);
902
884
 
903
885
  this.emitter.emit(events.log.narration, theme.dim("thinking..."), true);
904
886
 
887
+ if (baseYaml && !skipYaml) {
888
+ await this.runLifecycle("prerun");
889
+ await this.run(baseYaml, false, false);
890
+ await this.runLifecycle("postrun");
891
+ }
892
+
905
893
  let image = await this.system.captureScreenBase64();
906
894
 
907
895
  const streamId = `generate-${Date.now()}`;
908
896
  this.emitter.emit(events.log.markdown.start, streamId);
909
897
 
910
- let mouse = await this.system.getMousePosition();
911
- let activeWindow = await this.system.activeWin();
912
-
913
898
  let message = await this.sdk.req(
914
899
  "generate",
915
900
  {
916
- prompt: prompt || "make sure to do a spellcheck",
901
+ type,
917
902
  image,
918
- mousePosition: mouse,
919
- activeWindow: activeWindow,
903
+ mousePosition: await this.system.getMousePosition(),
904
+ activeWindow: await this.system.activeWin(),
920
905
  count,
921
- stream: false,
922
906
  },
923
907
  (chunk) => {
924
908
  if (chunk.type === "data") {
@@ -941,36 +925,35 @@ commands:
941
925
  .replace(/['"`]/g, "")
942
926
  .replace(/[^a-zA-Z0-9-]/g, "") // remove any non-alphanumeric chars except hyphens
943
927
  .toLowerCase() + ".yaml";
944
-
945
928
  let path1 = path.join(
946
929
  this.workingDir,
947
930
  "testdriver",
948
931
  "generate",
949
932
  fileName,
950
933
  );
934
+
951
935
  // create generate directory if it doesn't exist
952
- const generateDir = path.join(this.workingDir, "testdriver", "generate");
953
- if (!fs.existsSync(generateDir)) {
954
- fs.mkdirSync(generateDir);
955
- console.log("Created generate directory:", generateDir);
956
- } else {
957
- console.log("Generate directory already exists:", generateDir);
936
+ if (!fs.existsSync(path.join(this.workingDir, "generate"))) {
937
+ fs.mkdirSync(path.join(this.workingDir, "generate"));
958
938
  }
959
939
 
960
940
  let list = testPrompt.steps;
961
941
 
942
+ if (baseYaml && fs.existsSync(baseYaml)) {
943
+ list.unshift({
944
+ step: {
945
+ command: "run",
946
+ file: baseYaml,
947
+ },
948
+ });
949
+ }
962
950
  let contents = yaml.dump({
963
951
  version: packageJson.version,
964
952
  steps: list,
965
953
  });
966
-
967
- this.emitter.emit(events.log.debug, `writing file ${path1} ${contents}`);
968
-
969
954
  fs.writeFileSync(path1, contents);
970
955
  }
971
956
 
972
- await this.runLifecycle("postrun");
973
-
974
957
  this.exit(false);
975
958
  }
976
959
 
@@ -1156,8 +1139,21 @@ ${yml}
1156
1139
 
1157
1140
  // Create diff if file exists and content has changed
1158
1141
  let diffResult = null;
1142
+ console.log("Checking for diff. File exists:", fileExists);
1143
+ console.log(
1144
+ "Content changed:",
1145
+ fileExists && existingContent !== regression,
1146
+ );
1147
+ if (fileExists) {
1148
+ console.log(
1149
+ "Existing content preview:",
1150
+ existingContent.substring(0, 100),
1151
+ );
1152
+ console.log("New content preview:", regression.substring(0, 100));
1153
+ }
1159
1154
 
1160
1155
  if (fileExists && existingContent !== regression) {
1156
+ console.log("Creating diff - content has changed");
1161
1157
  const patches = diff.structuredPatch(
1162
1158
  filepath,
1163
1159
  filepath,
@@ -1245,6 +1241,8 @@ ${yml}
1245
1241
  diff: diffResult,
1246
1242
  timestamp: endTime,
1247
1243
  });
1244
+ } else {
1245
+ console.log("No diff result to emit");
1248
1246
  }
1249
1247
 
1250
1248
  // Emit file save completion event
@@ -1521,8 +1519,6 @@ ${regression}
1521
1519
  }
1522
1520
 
1523
1521
  async embed(file, depth, pushToHistory) {
1524
- let inputFile = JSON.parse(JSON.stringify(file));
1525
-
1526
1522
  this.analytics.track("embed", { file });
1527
1523
 
1528
1524
  this.emitter.emit(
@@ -1532,7 +1528,7 @@ ${regression}
1532
1528
 
1533
1529
  depth = depth + 1;
1534
1530
 
1535
- this.emitter.emit(events.log.log, `${inputFile} (start)`);
1531
+ this.emitter.emit(events.log.log, `${file} (start)`);
1536
1532
 
1537
1533
  // Use the new helper method to resolve file paths relative to testdriver directory
1538
1534
  const currentFilePath = this.sourceMapper.currentFilePath || this.thisFile;
@@ -1585,7 +1581,7 @@ ${regression}
1585
1581
  this.sourceMapper.restoreContext(previousContext);
1586
1582
  }
1587
1583
 
1588
- this.emitter.emit(events.log.log, `${inputFile} (end)`);
1584
+ this.emitter.emit(events.log.log, `${file} (end)`);
1589
1585
  }
1590
1586
 
1591
1587
  // Returns sandboxId to use (either from file if recent, or null)
@@ -1710,20 +1706,7 @@ ${regression}
1710
1706
  const recentId = createNew ? null : this.getRecentSandboxId();
1711
1707
 
1712
1708
  // Set sandbox ID for reconnection (only if not creating new and recent ID exists)
1713
- if (this.ip) {
1714
- let instance = await this.sandbox.send({
1715
- type: "direct",
1716
- resolution: this.config.TD_RESOLUTION,
1717
- ci: this.config.CI,
1718
- ip: this.ip,
1719
- });
1720
-
1721
- await this.renderSandbox(instance.instance, headless);
1722
- await this.newSession();
1723
- await this.runLifecycle("provision");
1724
-
1725
- return;
1726
- } else if (!createNew && recentId) {
1709
+ if (!createNew && recentId) {
1727
1710
  this.emitter.emit(
1728
1711
  events.log.narration,
1729
1712
  theme.dim(`using recent sandbox: ${recentId}`),
@@ -1734,8 +1717,10 @@ ${regression}
1734
1717
  events.log.narration,
1735
1718
  theme.dim(`no recent sandbox found, creating a new one.`),
1736
1719
  );
1737
- } else if (this.sandboxId && !this.config.CI) {
1738
- // Only attempt to connect to existing sandbox if not in CI mode and not creating new
1720
+ }
1721
+
1722
+ // Only attempt to connect to existing sandbox if not in CI mode and not creating new
1723
+ if (this.sandboxId && !this.config.CI && !createNew) {
1739
1724
  // Attempt to connect to known instance
1740
1725
  this.emitter.emit(
1741
1726
  events.log.narration,
@@ -1776,15 +1761,24 @@ ${regression}
1776
1761
  return this.createNewSandbox();
1777
1762
  });
1778
1763
 
1779
- this.saveLastSandboxId(newSandbox.sandbox.instanceId);
1780
- let instance = await this.connectToSandboxDirect(
1781
- newSandbox.sandbox.instanceId,
1782
- true, // always persist by default
1783
- );
1784
- this.instance = instance;
1785
- await this.renderSandbox(instance, headless);
1764
+ console.log("New sandbox created:", newSandbox);
1765
+
1766
+ let data = {
1767
+ resolution: this.config.TD_RESOLUTION,
1768
+ url: newSandbox.url,
1769
+ };
1770
+
1771
+ const encodedData = encodeURIComponent(JSON.stringify(data));
1772
+
1773
+ // Use the debugger URL instead of the VNC URL
1774
+ const urlToOpen = `${this.debuggerUrl}?data=${encodedData}`;
1775
+
1776
+ this.emitter.emit(events.showWindow, urlToOpen);
1777
+
1786
1778
  await this.newSession();
1787
1779
  await this.runLifecycle("provision");
1780
+
1781
+ console.log("provision run");
1788
1782
  }
1789
1783
 
1790
1784
  async start() {
@@ -1793,16 +1787,13 @@ ${regression}
1793
1787
  events.log.log,
1794
1788
  theme.green(`Howdy! I'm TestDriver v${packageJson.version}`),
1795
1789
  );
1796
-
1797
- // Emit test start event for the entire test execution
1798
- this.emitter.emit(events.test.start, {
1799
- filePath: this.thisFile,
1800
- timestamp: Date.now(),
1801
- });
1802
-
1803
1790
  // Start the debugger server as early as possible to ensure event listeners are attached
1804
1791
  if (!debuggerStarted) {
1805
1792
  debuggerStarted = true; // Prevent multiple starts, especially when running test in parallel
1793
+ this.emitter.emit(
1794
+ events.log.narration,
1795
+ theme.green(`Starting debugger server...`),
1796
+ );
1806
1797
  debuggerProcess = await createDebuggerProcess(
1807
1798
  this.config,
1808
1799
  this.emitter,
@@ -1810,7 +1801,6 @@ ${regression}
1810
1801
  }
1811
1802
  this.debuggerUrl = debuggerProcess.url || null; // Store the debugger URL
1812
1803
  this.emitter.emit(events.log.log, `This is beta software!`);
1813
- this.emitter.emit(events.log.log, ``);
1814
1804
  this.emitter.emit(
1815
1805
  events.log.log,
1816
1806
  theme.yellow(`Join our Discord for help`),
@@ -1819,7 +1809,6 @@ ${regression}
1819
1809
  events.log.log,
1820
1810
  `https://discord.com/invite/cWDFW8DzPm`,
1821
1811
  );
1822
- this.emitter.emit(events.log.log, ``);
1823
1812
 
1824
1813
  // make testdriver directory if it doesn't exist
1825
1814
  let testdriverFolder = path.join(this.workingDir);
@@ -1833,10 +1822,7 @@ ${regression}
1833
1822
  }
1834
1823
 
1835
1824
  // if the directory for thisFile doesn't exist, create it
1836
- if (
1837
- this.cliArgs.command !== "sandbox" &&
1838
- this.cliArgs.command !== "generate"
1839
- ) {
1825
+ if (this.cliArgs.command !== "sandbox") {
1840
1826
  const dir = path.dirname(this.thisFile);
1841
1827
  if (!fs.existsSync(dir)) {
1842
1828
  fs.mkdirSync(dir, { recursive: true });
@@ -1861,10 +1847,7 @@ ${regression}
1861
1847
  await this.sdk.auth();
1862
1848
  }
1863
1849
 
1864
- if (
1865
- this.cliArgs.command !== "sandbox" &&
1866
- this.cliArgs.command !== "generate"
1867
- ) {
1850
+ if (this.cliArgs.command !== "sandbox") {
1868
1851
  this.emitter.emit(
1869
1852
  events.log.log,
1870
1853
  theme.dim(`Working on ${this.thisFile}`),
@@ -1963,6 +1946,7 @@ Please check your network connection, TD_API_KEY, or the service status.`,
1963
1946
  async createNewSandbox() {
1964
1947
  const sandboxConfig = {
1965
1948
  type: "create",
1949
+ os: "linux",
1966
1950
  resolution: this.config.TD_RESOLUTION,
1967
1951
  ci: this.config.CI,
1968
1952
  };
@@ -1975,7 +1959,12 @@ Please check your network connection, TD_API_KEY, or the service status.`,
1975
1959
  sandboxConfig.instanceType = this.sandboxInstance;
1976
1960
  }
1977
1961
 
1962
+ console.log("sending create");
1963
+
1978
1964
  let instance = await this.sandbox.send(sandboxConfig);
1965
+
1966
+ console.log("instance created", instance);
1967
+
1979
1968
  return instance;
1980
1969
  }
1981
1970
 
@@ -2061,20 +2050,6 @@ Please check your network connection, TD_API_KEY, or the service status.`,
2061
2050
  // Use the current file path from sourceMapper to find the lifecycle directory
2062
2051
  // If sourceMapper doesn't have a current file, use thisFile which should be the file being run
2063
2052
  let currentFilePath = this.sourceMapper.currentFilePath || this.thisFile;
2064
-
2065
- this.emitter.emit(events.log.log, ``);
2066
- this.emitter.emit(events.log.log, "Running lifecycle: " + lifecycleName);
2067
-
2068
- // If we still don't have a currentFilePath, fall back to the default testdriver directory
2069
- if (!currentFilePath) {
2070
- currentFilePath = path.join(
2071
- this.workingDir,
2072
- "testdriver",
2073
- "testdriver.yaml",
2074
- );
2075
- console.log("No currentFilePath found, using fallback:", currentFilePath);
2076
- }
2077
-
2078
2053
  // Ensure we have an absolute path
2079
2054
  if (currentFilePath && !path.isAbsolute(currentFilePath)) {
2080
2055
  currentFilePath = path.resolve(this.workingDir, currentFilePath);
@@ -2111,9 +2086,6 @@ Please check your network connection, TD_API_KEY, or the service status.`,
2111
2086
  }
2112
2087
  }
2113
2088
  }
2114
-
2115
- this.emitter.emit(events.log.log, lifecycleFile);
2116
-
2117
2089
  if (lifecycleFile) {
2118
2090
  // Store current source mapping state before running lifecycle file
2119
2091
  const previousContext = this.sourceMapper.saveContext();
@@ -2183,7 +2155,7 @@ Please check your network connection, TD_API_KEY, or the service status.`,
2183
2155
  }
2184
2156
 
2185
2157
  // Move environment setup and special handling here
2186
- if (["edit", "run", "generate"].includes(commandName)) {
2158
+ if (["edit", "run"].includes(commandName)) {
2187
2159
  await this.buildEnv(options);
2188
2160
  }
2189
2161
 
@@ -55,10 +55,6 @@ function createCommandDefinitions(agent) {
55
55
  "sandbox-instance": Flags.string({
56
56
  description: "Specify EC2 instance type for sandbox (e.g., i3.metal)",
57
57
  }),
58
- ip: Flags.string({
59
- description:
60
- "Connect directly to a sandbox at the specified IP address",
61
- }),
62
58
  summary: Flags.string({
63
59
  description: "Specify output file for summarize results",
64
60
  }),
@@ -72,6 +68,12 @@ function createCommandDefinitions(agent) {
72
68
  const file = normalizeFilePath(args.file);
73
69
  const testStartTime = Date.now();
74
70
 
71
+ // Emit test start event for the entire test execution
72
+ agent.emitter.emit(events.test.start, {
73
+ filePath: file,
74
+ timestamp: testStartTime,
75
+ });
76
+
75
77
  try {
76
78
  await agent.runLifecycle("prerun");
77
79
  // When run() is called through run.js CLI command, shouldExit should be true
@@ -133,10 +135,6 @@ function createCommandDefinitions(agent) {
133
135
  "sandbox-instance": Flags.string({
134
136
  description: "Specify EC2 instance type for sandbox (e.g., i3.metal)",
135
137
  }),
136
- ip: Flags.string({
137
- description:
138
- "Connect directly to a sandbox at the specified IP address",
139
- }),
140
138
  summary: Flags.string({
141
139
  description: "Specify output file for summarize results",
142
140
  }),
@@ -204,41 +202,6 @@ function createCommandDefinitions(agent) {
204
202
  console.log(`TestDriver.ai v${packageJson.version}`);
205
203
  },
206
204
  },
207
-
208
- generate: {
209
- description: "Generate test files based on current screen state",
210
- args: {
211
- prompt: Args.string({
212
- description: "Multi-line text prompt describing what to generate",
213
- required: false,
214
- }),
215
- },
216
- flags: {
217
- count: Flags.integer({
218
- description: "Number of test files to generate",
219
- default: 3,
220
- }),
221
- headless: Flags.boolean({
222
- description: "Run in headless mode (no GUI)",
223
- default: false,
224
- }),
225
- new: Flags.boolean({
226
- description:
227
- "Create a new sandbox instead of reconnecting to an existing one",
228
- default: false,
229
- }),
230
- "sandbox-ami": Flags.string({
231
- description: "Specify AMI ID for sandbox instance (e.g., ami-1234)",
232
- }),
233
- "sandbox-instance": Flags.string({
234
- description: "Specify EC2 instance type for sandbox (e.g., i3.metal)",
235
- }),
236
- },
237
- handler: async (args, flags) => {
238
- // Call generate with the count and prompt
239
- await agent.generate(flags.count || 3, args.prompt);
240
- },
241
- },
242
205
  };
243
206
  }
244
207
 
@@ -38,23 +38,18 @@ const censorSensitiveData = (message) => {
38
38
 
39
39
  // Function to censor sensitive data in any value (recursive for objects/arrays)
40
40
  const censorSensitiveDataDeep = (value) => {
41
- try {
42
- if (typeof value === "string") {
43
- return censorSensitiveData(value);
44
- } else if (Array.isArray(value)) {
45
- return value.map(censorSensitiveDataDeep);
46
- } else if (value && typeof value === "object") {
47
- const result = {};
48
- for (const [key, val] of Object.entries(value)) {
49
- result[key] = censorSensitiveDataDeep(val);
50
- }
51
- return result;
41
+ if (typeof value === "string") {
42
+ return censorSensitiveData(value);
43
+ } else if (Array.isArray(value)) {
44
+ return value.map(censorSensitiveDataDeep);
45
+ } else if (value && typeof value === "object") {
46
+ const result = {};
47
+ for (const [key, val] of Object.entries(value)) {
48
+ result[key] = censorSensitiveDataDeep(val);
52
49
  }
53
- return value;
54
- } catch {
55
- // If we hit any error (like circular reference), just return a safe placeholder
56
- return "[Object]";
50
+ return result;
57
51
  }
52
+ return value;
58
53
  };
59
54
 
60
55
  // Function to update interpolation variables (for runtime updates)