testdriverai 7.2.37 → 7.2.39

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.
@@ -9,6 +9,10 @@ on:
9
9
  jobs:
10
10
  test:
11
11
  runs-on: ubuntu-latest
12
+
13
+ permissions:
14
+ contents: read
15
+ pull-requests: write # Required to post comments on PRs
12
16
 
13
17
  steps:
14
18
  - uses: actions/checkout@v4
@@ -25,6 +29,8 @@ jobs:
25
29
  - name: Run TestDriver.ai tests
26
30
  env:
27
31
  TD_API_KEY: ${{ secrets.TD_API_KEY }}
32
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33
+ GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
28
34
  run: npx vitest run
29
35
 
30
36
  - name: Upload test results
@@ -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;
@@ -2,8 +2,8 @@ import { execSync } from "child_process";
2
2
  import crypto from "crypto";
3
3
  import { createRequire } from "module";
4
4
  import path from "path";
5
+ import { postOrUpdateTestResults } from "../lib/github-comment.mjs";
5
6
  import { setTestRunInfo } from "./shared-test-state.mjs";
6
- import { generateGitHubComment, postOrUpdateTestResults } from "../lib/github-comment.mjs";
7
7
 
8
8
  // Use createRequire to import CommonJS modules without esbuild processing
9
9
  const require = createRequire(import.meta.url);
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
+ }
@@ -47,10 +47,17 @@ function generateTestResultsTable(testCases, testRunUrl) {
47
47
  return '_No test cases recorded_';
48
48
  }
49
49
 
50
+ // Filter out skipped tests
51
+ const nonSkippedTests = testCases.filter(test => test.status !== 'skipped');
52
+
53
+ if (nonSkippedTests.length === 0) {
54
+ return '_No test cases to display (all tests were skipped)_';
55
+ }
56
+
50
57
  let table = '| Status | Test | File | Duration | Replay |\n';
51
58
  table += '|--------|------|------|----------|--------|\n';
52
59
 
53
- for (const test of testCases) {
60
+ for (const test of nonSkippedTests) {
54
61
  const status = getStatusEmoji(test.status);
55
62
  const name = test.testName || 'Unknown';
56
63
  const file = test.testFile || 'unknown';
@@ -65,8 +72,8 @@ function generateTestResultsTable(testCases, testRunUrl) {
65
72
  const replayId = extractReplayId(test.replayUrl);
66
73
  if (replayId) {
67
74
  const gifUrl = getReplayGifUrl(test.replayUrl, replayId);
68
- // Embed GIF with link using HTML for width control
69
- replay = `<a href="${linkUrl}"><img src="${gifUrl}" width="250" alt="${name}" /></a>`;
75
+ // Embed GIF with link using HTML for height control
76
+ replay = `<a href="${linkUrl}"><img src="${gifUrl}" height="100" alt="Test replay" /></a>`;
70
77
  } else {
71
78
  // Fallback to text link if no GIF available
72
79
  replay = `[🎥 View](${linkUrl})`;
@@ -258,9 +265,15 @@ export function generateGitHubComment(testRunData, testCases = []) {
258
265
  comment += ` • **Duration:** ${formatDuration(duration)}`;
259
266
  comment += ` • ${passedTests} passed`;
260
267
  if (failedTests > 0) comment += `, ${failedTests} failed`;
261
- if (skippedTests > 0) comment += `, ${skippedTests} skipped`;
268
+ // Only show skipped count if there are no passed or failed tests
269
+ if (skippedTests > 0 && passedTests === 0 && failedTests === 0) {
270
+ comment += `, ${skippedTests} skipped`;
271
+ }
262
272
  comment += `\n\n`;
263
273
 
274
+ // Exceptions section (only if there are failures) - show first
275
+ comment += generateExceptionsSection(testCases, testRunUrl);
276
+
264
277
  // Test results table (now includes embedded GIFs)
265
278
  comment += '## 📝 Test Results\n\n';
266
279
  comment += generateTestResultsTable(testCases, testRunUrl);
@@ -270,9 +283,6 @@ export function generateGitHubComment(testRunData, testCases = []) {
270
283
  comment += `\n[📋 View Full Test Run](${testRunUrl})\n`;
271
284
  }
272
285
 
273
- // Exceptions section (only if there are failures)
274
- comment += generateExceptionsSection(testCases, testRunUrl);
275
-
276
286
  // Footer
277
287
  comment += '\n---\n';
278
288
  comment += `<sub>Generated by [TestDriver](https://testdriver.ai) • Run ID: \`${runId}\`</sub>\n`;
@@ -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.37",
3
+ "version": "7.2.39",
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
@@ -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
  */
@@ -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
@@ -1,73 +0,0 @@
1
- name: TestDriver Tests with GitHub Comments
2
-
3
- on:
4
- pull_request:
5
- types: [opened, synchronize, reopened]
6
- push:
7
- branches: [main]
8
- workflow_dispatch: # Allows manual trigger from Actions tab
9
-
10
- jobs:
11
- test:
12
- runs-on: ubuntu-latest
13
-
14
- permissions:
15
- contents: read
16
- pull-requests: write # Required to post comments on PRs
17
-
18
- steps:
19
- - name: Checkout code
20
- uses: actions/checkout@v4
21
-
22
- - name: Setup Node.js
23
- uses: actions/setup-node@v4
24
- with:
25
- node-version: '20'
26
- cache: 'npm'
27
-
28
- - name: Install dependencies
29
- run: npm ci
30
-
31
- - name: Run assert test with GitHub comments
32
- env:
33
- # TestDriver API key (from repository secrets)
34
- TD_API_KEY: ${{ secrets.TD_API_KEY }}
35
-
36
- # GitHub token for posting comments (auto-provided)
37
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
38
-
39
- # PR number for PR comments (auto-extracted for pull_request events)
40
- GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
41
-
42
- # Git information (automatically provided by GitHub Actions)
43
- # GITHUB_SHA, GITHUB_REF, GITHUB_REPOSITORY are already set
44
-
45
- run: |
46
- echo "🚀 Running TestDriver assert test..."
47
- echo "📍 Repository: $GITHUB_REPOSITORY"
48
- echo "🔢 PR Number: ${{ github.event.pull_request.number || 'N/A (not a PR)' }}"
49
- echo "📦 Running test..."
50
- npm run test:sdk -- test/testdriver/assert.test.mjs
51
-
52
- - name: Upload test results (on failure)
53
- if: failure()
54
- uses: actions/upload-artifact@v4
55
- with:
56
- name: test-results
57
- path: |
58
- test-report.junit.xml
59
- .testdriver/
60
- retention-days: 7
61
-
62
- - name: Comment on PR (manual fallback if auto-comment fails)
63
- if: failure() && github.event_name == 'pull_request'
64
- uses: actions/github-script@v7
65
- with:
66
- script: |
67
- const testRunUrl = process.env.TESTDRIVER_RUN_URL || 'Check workflow logs';
68
- github.rest.issues.createComment({
69
- issue_number: context.issue.number,
70
- owner: context.repo.owner,
71
- repo: context.repo.repo,
72
- body: `⚠️ TestDriver tests encountered an error. ${testRunUrl}`
73
- })