testdriverai 7.2.38 → 7.2.40

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.
@@ -1,5 +1,4 @@
1
1
  const WebSocket = require("ws");
2
- const marky = require("marky");
3
2
  const crypto = require("crypto");
4
3
  const { events } = require("../events");
5
4
 
@@ -83,10 +82,6 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
83
82
  }
84
83
  }
85
84
 
86
- // Start timing for this message
87
- const timingKey = `sandbox-${message.type}`;
88
- marky.mark(timingKey);
89
-
90
85
  let p = new Promise((resolve, reject) => {
91
86
  this.socket.send(JSON.stringify(message));
92
87
  emitter.emit(events.sandbox.sent, message);
@@ -99,13 +94,6 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
99
94
  // Set up timeout to prevent hanging requests
100
95
  const timeoutId = setTimeout(() => {
101
96
  if (this.ps[requestId]) {
102
- const pendingMessage = this.ps[requestId];
103
- // Stop the timing marker to prevent memory leak
104
- try {
105
- marky.stop(pendingMessage.timingKey);
106
- } catch (e) {
107
- // Ignore timing errors
108
- }
109
97
  delete this.ps[requestId];
110
98
  rejectPromise(new Error(`Sandbox message '${message.type}' timed out after ${timeout}ms`));
111
99
  }
@@ -122,7 +110,6 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
122
110
  rejectPromise(error);
123
111
  },
124
112
  message,
125
- timingKey,
126
113
  startTime: Date.now(),
127
114
  };
128
115
 
@@ -252,25 +239,6 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
252
239
  this.ps[message.requestId].reject(error);
253
240
  } else {
254
241
  emitter.emit(events.sandbox.received);
255
-
256
- // Get timing information for this message
257
- const pendingMessage = this.ps[message.requestId];
258
- if (pendingMessage) {
259
- const timing = marky.stop(pendingMessage.timingKey);
260
-
261
- // Track timing for each message type
262
- await analytics.track("sandbox", {
263
- operation: pendingMessage.message.type,
264
- timing,
265
- requestId: message.requestId,
266
- timestamp: Date.now(),
267
- data: {
268
- messageType: pendingMessage.message.type,
269
- ...pendingMessage.message,
270
- },
271
- });
272
- }
273
-
274
242
  this.ps[message.requestId]?.resolve(message);
275
243
  }
276
244
  delete this.ps[message.requestId];
package/agents.md CHANGED
@@ -241,6 +241,8 @@ describe("Experiment", () => {
241
241
  });
242
242
  ```
243
243
 
244
+ > ⚠️ **NEVER REMOVE `reconnect: true`**: If a test file already has `reconnect: true`, do NOT remove it. This option is intentional and removing it will break the two-file workflow. Only remove `reconnect: true` when explicitly combining files into a final standalone test.
245
+
244
246
  ### Step 4: Iterate in Experiment File
245
247
 
246
248
  - Run experiment, see output, fix issues
@@ -260,13 +262,15 @@ vitest run tests/experiment.test.mjs
260
262
 
261
263
  ### After Experimentation: Combine and Rename
262
264
 
263
- **IMPORTANT:** Once the test is working, combine both files into a single, properly-named test file:
265
+ **IMPORTANT:** Once the test is working AND the user explicitly asks to combine/finalize the test, combine both files into a single, properly-named test file:
264
266
 
265
267
  1. **Merge the code** - Copy the working steps from `experiment.test.mjs` into `setup.test.mjs`
266
- 2. **Remove reconnect options** - Delete `reconnect: true` since the final test runs standalone
268
+ 2. **Remove reconnect options** - Delete `reconnect: true` since the final test runs standalone (ONLY do this when creating the final combined test - never remove it from an existing experiment file!)
267
269
  3. **Rename the file** - Give it a meaningful name like `login-flow.test.mjs` or `checkout-process.test.mjs`
268
270
  4. **Delete the experiment file** - Clean up `experiment.test.mjs`
269
271
 
272
+ > ⚠️ **WARNING FOR AI AGENTS**: Do NOT remove `reconnect: true` from any existing test file unless you are explicitly combining files into a final test. If a test has `reconnect: true`, it is there intentionally.
273
+
270
274
  ```javascript
271
275
  // Final combined test: login-flow.test.mjs
272
276
  import { describe, expect, it } from "vitest";
@@ -15,27 +15,6 @@ const testdriver = TestDriver(context, {
15
15
  });
16
16
  ```
17
17
 
18
- ### Headless Mode
19
-
20
- Run tests without a visible browser window. Useful for CI/CD pipeline or spawning multiple tests locally:
21
-
22
- ```javascript
23
- const testdriver = TestDriver(context, {
24
- headless: true, // No visible browser window
25
- });
26
- ```
27
-
28
- ### IP Target
29
-
30
- If self-hosting TestDriver, use `ip` to specify the device IP. See [Self-Hosting TestDriver](../self-hosting.md) for details.
31
-
32
- ```javascript
33
- const testdriver = TestDriver(context, {
34
- newSandbox: true,
35
- ip: "203.0.113.42", // Your allowlisted IP
36
- });
37
- ```
38
-
39
18
  ### Operating System
40
19
 
41
20
  Set the `os` property to run tests on a specific operating system. Available options are `linux` (default) and `windows`.
@@ -77,6 +56,27 @@ steps:
77
56
  - run: TD_OS=${{ matrix.os }} npx vitest run
78
57
  ```
79
58
 
59
+ ### Headless Mode
60
+
61
+ Run tests without a visible browser window. Useful for CI/CD pipeline or spawning multiple tests locally:
62
+
63
+ ```javascript
64
+ const testdriver = TestDriver(context, {
65
+ headless: true, // No visible browser window
66
+ });
67
+ ```
68
+
69
+ ### IP Target
70
+
71
+ If self-hosting TestDriver, use `ip` to specify the device IP. See [Self-Hosting TestDriver](../self-hosting.md) for details.
72
+
73
+ ```javascript
74
+ const testdriver = TestDriver(context, {
75
+ newSandbox: true,
76
+ ip: "203.0.113.42", // Your allowlisted IP
77
+ });
78
+ ```
79
+
80
80
  ## Keepalive
81
81
 
82
82
  By default, sandboxes terminate immediately when the test finishes. Set this value to keep the sandbox alive for reconnection.
@@ -0,0 +1,101 @@
1
+ /**
2
+ * TypeScript definitions for TestDriver Vitest Plugin
3
+ * @module testdriverai/vitest/plugin
4
+ */
5
+
6
+ import TestDriverSDK, { TestDriverOptions } from '../sdk';
7
+
8
+ /**
9
+ * Plugin state object
10
+ */
11
+ export interface PluginState {
12
+ dashcamUrls: Map<string, { url: string; platform: string }>;
13
+ suiteTestRuns: Map<string, any>;
14
+ testDriverOptions: TestDriverOptions;
15
+ }
16
+
17
+ /**
18
+ * Current plugin state
19
+ */
20
+ export const pluginState: PluginState;
21
+
22
+ /**
23
+ * Register a Dashcam URL for a test
24
+ */
25
+ export function registerDashcamUrl(testId: string, url: string, platform: string): void;
26
+
27
+ /**
28
+ * Get Dashcam URL for a test
29
+ */
30
+ export function getDashcamUrl(testId: string): { url: string; platform: string } | undefined;
31
+
32
+ /**
33
+ * Clear all Dashcam URLs
34
+ */
35
+ export function clearDashcamUrls(): void;
36
+
37
+ /**
38
+ * Get suite test run data
39
+ */
40
+ export function getSuiteTestRun(suiteId: string): any;
41
+
42
+ /**
43
+ * Set suite test run data
44
+ */
45
+ export function setSuiteTestRun(suiteId: string, runData: any): void;
46
+
47
+ /**
48
+ * Clear suite test run data
49
+ */
50
+ export function clearSuiteTestRun(suiteId: string): void;
51
+
52
+ /**
53
+ * Get the current plugin state
54
+ */
55
+ export function getPluginState(): PluginState;
56
+
57
+ /**
58
+ * Authenticate with API key
59
+ */
60
+ export function authenticateWithApiKey(apiKey: string, apiRoot?: string): Promise<string>;
61
+
62
+ /**
63
+ * Create a test run directly via API
64
+ */
65
+ export function createTestRunDirect(token: string, apiRoot: string, testRunData: any): Promise<any>;
66
+
67
+ /**
68
+ * Record a test case directly via API
69
+ */
70
+ export function recordTestCaseDirect(token: string, apiRoot: string, testCaseData: any): Promise<any>;
71
+
72
+ /**
73
+ * Create a TestDriver instance
74
+ */
75
+ export function createTestDriver(options?: TestDriverOptions): Promise<TestDriverSDK>;
76
+
77
+ /**
78
+ * Register a test with TestDriver
79
+ */
80
+ export function registerTest(testdriver: TestDriverSDK, context: any): void;
81
+
82
+ /**
83
+ * Cleanup a TestDriver instance
84
+ */
85
+ export function cleanupTestDriver(testdriver: TestDriverSDK): Promise<void>;
86
+
87
+ /**
88
+ * Plugin options
89
+ */
90
+ export interface TestDriverPluginOptions extends TestDriverOptions {
91
+ /**
92
+ * API key (defaults to TD_API_KEY env var)
93
+ */
94
+ apiKey?: string;
95
+ }
96
+
97
+ /**
98
+ * TestDriver Vitest Plugin
99
+ * @param options - Plugin configuration options
100
+ */
101
+ export default function testDriverPlugin(options?: TestDriverPluginOptions): any;
package/jsconfig.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "ESNext",
4
+ "moduleResolution": "bundler",
5
+ "target": "ES2022",
6
+ "checkJs": true,
7
+ "strict": false,
8
+ "allowSyntheticDefaultImports": true,
9
+ "esModuleInterop": true,
10
+ "baseUrl": ".",
11
+ "typeRoots": ["."]
12
+ },
13
+ "include": [
14
+ "**/*.js",
15
+ "**/*.mjs",
16
+ "**/*.d.ts",
17
+ "sdk.d.ts",
18
+ "lib/**/*.d.ts",
19
+ "interfaces/**/*.d.ts"
20
+ ],
21
+ "exclude": [
22
+ "node_modules",
23
+ "dist",
24
+ "build"
25
+ ]
26
+ }
@@ -130,23 +130,6 @@ class Dashcam {
130
130
  const dashcamPath = await this._getDashcamPath();
131
131
  this._log('debug', 'Dashcam executable path:', dashcamPath);
132
132
 
133
- const installedVersion = await this.client.exec(
134
- shell,
135
- 'npm ls dashcam -g',
136
- 40000,
137
- true
138
- );
139
- this._log('debug', 'Installed dashcam version:', installedVersion);
140
-
141
- // Test version command
142
- const versionTest = await this.client.exec(
143
- shell,
144
- `& "${dashcamPath}" version`,
145
- 40000,
146
- true
147
- );
148
- this._log('debug', 'Dashcam version test:', versionTest);
149
-
150
133
  // Authenticate with TD_API_ROOT
151
134
  const authOutput = await this.client.exec(
152
135
  shell,
@@ -3,7 +3,7 @@
3
3
  * @module testdriverai/vitest/hooks
4
4
  */
5
5
 
6
- import { Dashcam, DashcamOptions, TestDriver, TestDriverOptions } from '../core/index';
6
+ import TestDriverSDK, { TestDriverOptions } from '../../sdk';
7
7
 
8
8
  /**
9
9
  * Vitest test context (from test function parameter)
@@ -21,99 +21,49 @@ export interface VitestContext {
21
21
  }
22
22
 
23
23
  /**
24
- * Options for useTestDriver hook
24
+ * Options for TestDriver hook
25
25
  */
26
- export interface UseTestDriverOptions extends TestDriverOptions {
26
+ export interface TestDriverHookOptions extends TestDriverOptions {
27
27
  /**
28
- * Automatically connect to sandbox (default: true)
28
+ * Force creation of a new sandbox (default: true)
29
29
  */
30
- autoConnect?: boolean;
30
+ newSandbox?: boolean;
31
31
 
32
32
  /**
33
- * Create new sandbox (default: true)
33
+ * Reconnect to the last used sandbox
34
34
  */
35
- new?: boolean;
36
- }
37
-
38
- /**
39
- * Options for useDashcam hook
40
- */
41
- export interface UseDashcamOptions extends DashcamOptions {
42
- /**
43
- * Automatically authenticate (default: true)
44
- */
45
- autoAuth?: boolean;
35
+ reconnect?: boolean;
46
36
 
47
37
  /**
48
- * Automatically start recording (default: false)
38
+ * Direct IP address to connect to a running sandbox instance
49
39
  */
50
- autoStart?: boolean;
51
-
52
- /**
53
- * Automatically stop recording at test end (default: false)
54
- */
55
- autoStop?: boolean;
40
+ ip?: string;
56
41
  }
57
42
 
58
43
  /**
59
- * Use TestDriver client in a test
60
- * Creates and manages TestDriver instance for the current test
44
+ * Create a TestDriver client for use in Vitest tests
45
+ * Manages lifecycle automatically (connects on first use, disconnects after test)
61
46
  *
62
47
  * @param context - Vitest test context (from async (context) => {})
63
48
  * @param options - TestDriver options
64
- * @returns TestDriver client instance
49
+ * @returns TestDriver SDK instance
65
50
  *
66
51
  * @example
67
- * test('my test', async (context) => {
68
- * const client = useTestDriver(context, { os: 'linux' });
69
- * await client.find('Login button').click();
70
- * });
71
- */
72
- export function useTestDriver(context: VitestContext, options?: UseTestDriverOptions): TestDriver;
73
-
74
- /**
75
- * Use Dashcam in a test
76
- * Creates and manages Dashcam instance for the current test
77
- *
78
- * @param context - Vitest test context
79
- * @param client - TestDriver client instance (from useTestDriver)
80
- * @param options - Dashcam options
81
- * @returns Dashcam instance
52
+ * import { describe, expect, it } from "vitest";
53
+ * import { TestDriver } from "testdriverai/lib/vitest/hooks.mjs";
82
54
  *
83
- * @example
84
- * test('my test', async (context) => {
85
- * const client = useTestDriver(context);
86
- * const dashcam = useDashcam(context, client, {
87
- * autoStart: true,
88
- * autoStop: true
89
- * });
90
- *
91
- * await client.find('button').click();
92
- * });
93
- */
94
- export function useDashcam(context: VitestContext, client: TestDriver, options?: UseDashcamOptions): Dashcam;
95
-
96
- /**
97
- * Use TestDriver with Dashcam in one call
98
- * Combined hook for the simplest usage pattern
99
- *
100
- * @param context - Vitest test context
101
- * @param options - Combined options for TestDriver and Dashcam
102
- * @returns Object with client and dashcam instances
103
- *
104
- * @example
105
- * test('my test', async (context) => {
106
- * const { client, dashcam } = useTestDriverWithDashcam(context, {
107
- * os: 'linux'
55
+ * describe("My Test Suite", () => {
56
+ * it("should do something", async (context) => {
57
+ * const testdriver = TestDriver(context, { newSandbox: true, headless: false });
58
+ *
59
+ * await testdriver.provision.chrome({ url: 'https://example.com' });
60
+ *
61
+ * const button = await testdriver.find("Sign In button");
62
+ * await button.click();
63
+ *
64
+ * const result = await testdriver.assert("the dashboard is visible");
65
+ * expect(result).toBeTruthy();
108
66
  * });
109
- *
110
- * await client.find('button').click();
111
67
  * });
112
68
  */
113
- export function useTestDriverWithDashcam(
114
- context: VitestContext,
115
- options?: UseTestDriverOptions & UseDashcamOptions
116
- ): {
117
- client: TestDriver;
118
- dashcam: Dashcam;
119
- };
69
+ export function TestDriver(context: VitestContext, options?: TestDriverHookOptions): TestDriverSDK;
package/package.json CHANGED
@@ -1,15 +1,31 @@
1
1
  {
2
2
  "name": "testdriverai",
3
- "version": "7.2.38",
3
+ "version": "7.2.40",
4
4
  "description": "Next generation autonomous AI agent for end-to-end testing of web & desktop",
5
5
  "main": "sdk.js",
6
+ "types": "sdk.d.ts",
6
7
  "exports": {
7
- ".": "./sdk.js",
8
- "./core": "./lib/core/index.js",
9
- "./vitest": "./interfaces/vitest-plugin.mjs",
10
- "./vitest/plugin": "./interfaces/vitest-plugin.mjs",
8
+ ".": {
9
+ "types": "./sdk.d.ts",
10
+ "default": "./sdk.js"
11
+ },
12
+ "./core": {
13
+ "types": "./lib/core/index.d.ts",
14
+ "default": "./lib/core/index.js"
15
+ },
16
+ "./vitest": {
17
+ "types": "./interfaces/vitest-plugin.d.ts",
18
+ "default": "./interfaces/vitest-plugin.mjs"
19
+ },
20
+ "./vitest/plugin": {
21
+ "types": "./interfaces/vitest-plugin.d.ts",
22
+ "default": "./interfaces/vitest-plugin.mjs"
23
+ },
11
24
  "./vitest/setup": "./lib/vitest/setup.mjs",
12
- "./vitest/hooks": "./lib/vitest/hooks.mjs",
25
+ "./vitest/hooks": {
26
+ "types": "./lib/vitest/hooks.d.ts",
27
+ "default": "./lib/vitest/hooks.mjs"
28
+ },
13
29
  "./presets": "./lib/presets/index.mjs"
14
30
  },
15
31
  "bin": {
@@ -47,12 +63,12 @@
47
63
  "license": "ISC",
48
64
  "dependencies": {
49
65
  "@npmcli/redact": "^3.2.2",
50
- "@octokit/rest": "^20.1.1",
51
66
  "@oclif/core": "^4.5.0",
52
67
  "@oclif/plugin-commands": "^4.1.28",
53
68
  "@oclif/plugin-help": "^6.2.30",
54
69
  "@oclif/plugin-not-found": "^3.2.59",
55
70
  "@oclif/plugin-warn-if-update-available": "^3.1.43",
71
+ "@octokit/rest": "^20.1.1",
56
72
  "@sentry/node": "^9.47.1",
57
73
  "@stoplight/yaml-ast-parser": "^0.0.50",
58
74
  "ajv": "^8.17.1",
@@ -107,7 +123,6 @@
107
123
  "mocha": "^10.8.2",
108
124
  "node-addon-api": "^8.0.0",
109
125
  "prettier": "3.3.3",
110
- "testdriverai": "^7.2.3",
111
126
  "vitest": "^4.0.16"
112
127
  },
113
128
  "optionalDependencies": {
package/sdk.d.ts CHANGED
@@ -242,7 +242,7 @@ export interface TestDriverOptions {
242
242
  sandboxInstance?: string;
243
243
  /** Cache key for element finding operations. If provided, enables caching tied to this key */
244
244
  cacheKey?: string;
245
- /** Reconnect to the last used sandbox (throws error if no last sandbox exists) */
245
+ /** Reconnect to the last used sandbox instead of creating a new one. When true, provision methods (chrome, vscode, installer, etc.) will be skipped since the application is already running. Throws error if no previous sandbox exists. */
246
246
  reconnect?: boolean;
247
247
  /** Redraw configuration for screen change detection */
248
248
  redraw?: boolean | {
@@ -266,7 +266,7 @@ export interface ConnectOptions {
266
266
  sandboxId?: string;
267
267
  /** Force creation of a new sandbox */
268
268
  newSandbox?: boolean;
269
- /** Reconnect to the last used sandbox (throws error if no last sandbox exists) */
269
+ /** Reconnect to the last used sandbox instead of creating a new one. When true, provision methods (chrome, vscode, installer, etc.) will be skipped since the application is already running. Throws error if no previous sandbox exists. */
270
270
  reconnect?: boolean;
271
271
  /** Direct IP address to connect to a running sandbox instance */
272
272
  ip?: string;
@@ -683,9 +683,139 @@ export class Element {
683
683
  readonly label: string | null;
684
684
  }
685
685
 
686
+ // ====================================
687
+ // Provision API Interfaces
688
+ // ====================================
689
+
690
+ /** Options for provision.chrome */
691
+ export interface ProvisionChromeOptions {
692
+ /** URL to navigate to (default: 'http://testdriver-sandbox.vercel.app/') */
693
+ url?: string;
694
+ /** Start maximized (default: true) */
695
+ maximized?: boolean;
696
+ /** Use guest mode (default: false) */
697
+ guest?: boolean;
698
+ }
699
+
700
+ /** Options for provision.chromeExtension */
701
+ export interface ProvisionChromeExtensionOptions {
702
+ /** Local filesystem path to the unpacked extension directory */
703
+ extensionPath?: string;
704
+ /** Chrome Web Store extension ID */
705
+ extensionId?: string;
706
+ /** Start maximized (default: true) */
707
+ maximized?: boolean;
708
+ }
709
+
710
+ /** Options for provision.vscode */
711
+ export interface ProvisionVSCodeOptions {
712
+ /** Path to workspace or folder to open */
713
+ workspace?: string;
714
+ /** Array of extension IDs to install */
715
+ extensions?: string[];
716
+ }
717
+
718
+ /** Options for provision.installer */
719
+ export interface ProvisionInstallerOptions {
720
+ /** URL to download the installer from */
721
+ url: string;
722
+ /** Filename to save as (auto-detected from URL if not provided) */
723
+ filename?: string;
724
+ /** Application name to focus after install */
725
+ appName?: string;
726
+ /** Whether to launch the app after installation (default: true) */
727
+ launch?: boolean;
728
+ }
729
+
730
+ /** Options for provision.electron */
731
+ export interface ProvisionElectronOptions {
732
+ /** Path to Electron app (required) */
733
+ appPath: string;
734
+ /** Additional electron args */
735
+ args?: string[];
736
+ }
737
+
738
+ /** Provision API for launching applications */
739
+ export interface ProvisionAPI {
740
+ /**
741
+ * Launch Chrome browser
742
+ * @param options - Chrome launch options
743
+ */
744
+ chrome(options?: ProvisionChromeOptions): Promise<void>;
745
+
746
+ /**
747
+ * Launch Chrome browser with a custom extension loaded
748
+ * @param options - Chrome extension launch options
749
+ */
750
+ chromeExtension(options?: ProvisionChromeExtensionOptions): Promise<void>;
751
+
752
+ /**
753
+ * Launch VS Code
754
+ * @param options - VS Code launch options
755
+ */
756
+ vscode(options?: ProvisionVSCodeOptions): Promise<void>;
757
+
758
+ /**
759
+ * Download and install an application
760
+ * @param options - Installer options
761
+ * @returns Path to the downloaded file
762
+ */
763
+ installer(options: ProvisionInstallerOptions): Promise<string>;
764
+
765
+ /**
766
+ * Launch Electron app
767
+ * @param options - Electron launch options
768
+ */
769
+ electron(options: ProvisionElectronOptions): Promise<void>;
770
+ }
771
+
772
+ /** Dashcam API for screen recording */
773
+ export interface DashcamAPI {
774
+ /**
775
+ * Start recording
776
+ */
777
+ start(): Promise<void>;
778
+
779
+ /**
780
+ * Stop recording and get replay URL
781
+ */
782
+ stop(): Promise<string | null>;
783
+
784
+ /**
785
+ * Check if currently recording
786
+ */
787
+ isRecording(): boolean;
788
+ }
789
+
686
790
  export default class TestDriverSDK {
687
791
  constructor(apiKey: string, options?: TestDriverOptions);
688
792
 
793
+ /**
794
+ * Whether the SDK is currently connected to a sandbox
795
+ */
796
+ readonly connected: boolean;
797
+
798
+ /**
799
+ * The operating system of the sandbox
800
+ */
801
+ readonly os: 'windows' | 'linux';
802
+
803
+ /**
804
+ * Provision API for launching applications
805
+ */
806
+ readonly provision: ProvisionAPI;
807
+
808
+ /**
809
+ * Dashcam API for screen recording
810
+ */
811
+ readonly dashcam: DashcamAPI;
812
+
813
+ /**
814
+ * Wait for the sandbox to be ready
815
+ * Called automatically by provision methods
816
+ */
817
+ ready(): Promise<void>;
818
+
689
819
  /**
690
820
  * Authenticate with TestDriver API
691
821
  */
package/sdk.js CHANGED
@@ -1362,10 +1362,13 @@ class TestDriverSDK {
1362
1362
 
1363
1363
  /**
1364
1364
  * Create the provision API with methods for launching applications
1365
+ * Automatically skips provisioning when reconnect mode is enabled
1365
1366
  * @private
1366
1367
  */
1367
1368
  _createProvisionAPI() {
1368
- return {
1369
+ const self = this;
1370
+
1371
+ const provisionMethods = {
1369
1372
  /**
1370
1373
  * Launch Chrome browser
1371
1374
  * @param {Object} options - Chrome launch options
@@ -2073,6 +2076,24 @@ with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
2073
2076
  await this.focusApplication('Electron');
2074
2077
  },
2075
2078
  };
2079
+
2080
+ // Wrap all provision methods with reconnect check using Proxy
2081
+ return new Proxy(provisionMethods, {
2082
+ get(target, prop) {
2083
+ const method = target[prop];
2084
+ if (typeof method === 'function') {
2085
+ return async (...args) => {
2086
+ // Skip provisioning if reconnecting to existing sandbox
2087
+ if (self.reconnect) {
2088
+ console.log(`[provision.${prop}] Skipping provisioning (reconnect mode)`);
2089
+ return;
2090
+ }
2091
+ return method(...args);
2092
+ };
2093
+ }
2094
+ return method;
2095
+ }
2096
+ });
2076
2097
  }
2077
2098
 
2078
2099
  /**
@@ -168,8 +168,8 @@ Resources:
168
168
  IpProtocol: tcp,
169
169
  FromPort: 8765,
170
170
  ToPort: 8765,
171
- CidrIp: 0.0.0.0/0,
172
- Description: "pyautogui-cli WebSockets - Open for testing",
171
+ CidrIp: !Ref AllowedIngressCidr,
172
+ Description: "pyautogui-cli WebSockets"
173
173
  }
174
174
  - {
175
175
  IpProtocol: tcp,
@@ -8,7 +8,7 @@ import { TestDriver } from "../../lib/vitest/hooks.mjs";
8
8
 
9
9
  describe("Hover Text Test", () => {
10
10
  it("should click Sign In and verify error message", async (context) => {
11
- const testdriver = TestDriver(context, { headless: false, newSandbox: true, cacheKey: 'hover-text-test' });
11
+ const testdriver = TestDriver(context, { headless: false, ip: '3.138.116.105'});
12
12
  await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
13
13
 
14
14
  // Click on Sign In button using new find() API
@@ -30,7 +30,7 @@ describe("Scroll Keyboard Test", () => {
30
30
  await testdriver.scroll("down", { amount: 1000 });
31
31
 
32
32
  // Assert the page is scrolled down
33
- const result = await testdriver.assert("the page is scrolled down, the hamster dance h1 text heading is not visible on the webpage");
33
+ const result = await testdriver.assert("The text 'The Hamster Dance' is not visible on the webpage content. It's ok if it's visible in the tab title.");
34
34
 
35
35
  expect(result).toBeTruthy();
36
36
  });
@@ -35,7 +35,7 @@ describe("Scroll Test", () => {
35
35
  await testdriver.scroll("down", { amount: 1000 });
36
36
 
37
37
  // Assert page is scrolled
38
- const result = await testdriver.assert("the page is scrolled down, the hamster dance h1 text heading is not visible on the webpage");
38
+ const result = await testdriver.assert("The text 'The Hamster Dance' is not visible on the webpage content. It's ok if it's visible in the tab title.");
39
39
  expect(result).toBeTruthy();
40
40
  });
41
41
  });