testdriverai 7.2.28 → 7.2.30

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/README.md CHANGED
@@ -49,7 +49,7 @@ expect(result).toBeTruthy();
49
49
 
50
50
  ### Step 1: Create a TestDriver Account
51
51
 
52
- <a href="https://app.testdriver.ai/team"><img src="https://img.shields.io/badge/Sign_Up-Free_Account-blue?style=for-the-badge" alt="Sign Up"/></a>
52
+ <a href="https://console.testdriver.ai/team"><img src="https://img.shields.io/badge/Sign_Up-Free_Account-blue?style=for-the-badge" alt="Sign Up"/></a>
53
53
 
54
54
  *No credit card required!*
55
55
 
package/docs/docs.json CHANGED
@@ -294,7 +294,7 @@
294
294
  {
295
295
  "icon": "gauge-high",
296
296
  "label": "Dashboard",
297
- "href": "https://app.testdriver.ai"
297
+ "href": "https://console.testdriver.ai"
298
298
  }
299
299
  ]
300
300
  },
@@ -22,7 +22,7 @@ TestDriver makes it easy to write automated computer-use tests for web browsers,
22
22
  <Card
23
23
  title="Sign Up for TestDriver"
24
24
  icon="user-plus"
25
- href="https://app.testdriver.ai/team"
25
+ href="https://console.testdriver.ai/team"
26
26
  arrow
27
27
  horizontal
28
28
  >
@@ -67,7 +67,7 @@ TestDriver makes it easy to write automated computer-use tests for web browsers,
67
67
  <Card
68
68
  title="Sign Up for TestDriver"
69
69
  icon="user-plus"
70
- href="https://app.testdriver.ai/team"
70
+ href="https://console.testdriver.ai/team"
71
71
  arrow
72
72
  horizontal
73
73
  >
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testdriverai",
3
- "version": "7.2.28",
3
+ "version": "7.2.30",
4
4
  "description": "Next generation autonomous AI agent for end-to-end testing of web & desktop",
5
5
  "main": "sdk.js",
6
6
  "exports": {
package/sdk.js CHANGED
@@ -1918,82 +1918,76 @@ with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
1918
1918
  await this.dashcam.start();
1919
1919
  }
1920
1920
 
1921
- // Determine filename from URL if not provided
1922
- const urlObj = new URL(url);
1923
- const detectedFilename = filename || urlObj.pathname.split('/').pop() || 'installer';
1924
-
1925
- // Determine download directory and full path
1921
+ // Determine download directory
1926
1922
  const downloadDir = this.os === 'windows'
1927
1923
  ? 'C:\\Users\\testdriver\\Downloads'
1928
1924
  : '/tmp';
1929
- const filePath = this.os === 'windows'
1930
- ? `${downloadDir}\\${detectedFilename}`
1931
- : `${downloadDir}/${detectedFilename}`;
1932
1925
 
1933
1926
  console.log(`[provision.installer] Downloading ${url}...`);
1934
1927
 
1935
- // Download the file
1936
- if (this.os === 'windows') {
1937
- await this.exec(
1938
- shell,
1939
- `Invoke-WebRequest -Uri "${url}" -OutFile "${filePath}"`,
1940
- 300000, // 5 min timeout for download
1941
- true
1942
- );
1943
- } else {
1944
- await this.exec(
1945
- shell,
1946
- `curl -L -o "${filePath}" "${url}"`,
1947
- 300000,
1948
- true
1949
- );
1950
- }
1928
+ let actualFilePath;
1951
1929
 
1952
- // Check if the downloaded file has a proper extension, if not scan the download directory
1953
- let actualFilePath = filePath;
1954
- const hasValidExtension = /\.(msi|exe|deb|rpm|appimage|sh|dmg|pkg)$/i.test(detectedFilename);
1955
-
1956
- if (!hasValidExtension && this.os === 'windows') {
1957
- // On Windows, scan the download directory for .msi or .exe files
1958
- console.log(`[provision.installer] Downloaded file has no extension, scanning for .msi or .exe files...`);
1959
- const scanResult = await this.exec(
1960
- shell,
1961
- `Get-ChildItem -Path "${downloadDir}" -File | Where-Object { $_.Extension -match '\\.(msi|exe)$' } | Sort-Object LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty FullName`,
1962
- 30000,
1963
- true
1964
- );
1930
+ // Download the file and get the actual filename (handles redirects)
1931
+ if (this.os === 'windows') {
1932
+ // Simple approach: download first, then get the actual filename from the response
1933
+ const tempFile = `${downloadDir}\\installer_temp_${Date.now()}`;
1965
1934
 
1966
- if (scanResult && scanResult.trim()) {
1967
- actualFilePath = scanResult.trim();
1968
- console.log(`[provision.installer] Found installer: ${actualFilePath}`);
1969
- }
1970
- } else if (!hasValidExtension && this.os === 'linux') {
1971
- // On Linux, scan for common installer extensions
1972
- console.log(`[provision.installer] Downloaded file has no extension, scanning for installer files...`);
1973
- const scanResult = await this.exec(
1974
- shell,
1975
- `find "${downloadDir}" -maxdepth 1 -type f \\( -name "*.deb" -o -name "*.rpm" -o -name "*.AppImage" -o -name "*.sh" \\) -printf '%T@ %p\\n' | sort -rn | head -1 | cut -d' ' -f2-`,
1976
- 30000,
1977
- true
1978
- );
1935
+ const downloadScript = `
1936
+ $ProgressPreference = 'SilentlyContinue'
1937
+ $response = Invoke-WebRequest -Uri "${url}" -OutFile "${tempFile}" -PassThru -UseBasicParsing
1938
+
1939
+ # Try to get filename from Content-Disposition header
1940
+ $filename = $null
1941
+ if ($response.Headers['Content-Disposition']) {
1942
+ if ($response.Headers['Content-Disposition'] -match 'filename=\\"?([^\\"]+)\\"?') {
1943
+ $filename = $matches[1]
1944
+ }
1945
+ }
1946
+
1947
+ # If no filename from header, try to get from URL or use default
1948
+ if (-not $filename) {
1949
+ $uri = [System.Uri]"${url}"
1950
+ $filename = [System.IO.Path]::GetFileName($uri.LocalPath)
1951
+ if (-not $filename -or $filename -eq '') {
1952
+ $filename = "installer"
1953
+ }
1954
+ }
1955
+
1956
+ # Move temp file to final location with proper filename
1957
+ $finalPath = Join-Path "${downloadDir}" $filename
1958
+ Move-Item -Path "${tempFile}" -Destination $finalPath -Force
1959
+ Write-Output $finalPath
1960
+ `;
1961
+
1962
+ const result = await this.exec(shell, downloadScript, 300000, true);
1963
+ actualFilePath = result ? result.trim() : null;
1979
1964
 
1980
- if (scanResult && scanResult.trim()) {
1981
- actualFilePath = scanResult.trim();
1982
- console.log(`[provision.installer] Found installer: ${actualFilePath}`);
1965
+ if (!actualFilePath) {
1966
+ throw new Error('[provision.installer] Failed to download file');
1983
1967
  }
1984
- } else if (!hasValidExtension && this.os === 'darwin') {
1985
- // On macOS, scan for common installer extensions
1986
- console.log(`[provision.installer] Downloaded file has no extension, scanning for installer files...`);
1987
- const scanResult = await this.exec(
1988
- shell,
1989
- `find "${downloadDir}" -maxdepth 1 -type f \\( -name "*.dmg" -o -name "*.pkg" \\) -print0 | xargs -0 ls -t | head -1`,
1990
- 30000,
1991
- true
1992
- );
1968
+ } else {
1969
+ // Use curl with options to get the final filename
1970
+ const tempMarker = `installer_${Date.now()}`;
1971
+ const downloadScript = `
1972
+ cd "${downloadDir}"
1973
+ curl -L -J -O -w "%{filename_effective}" "${url}" 2>/dev/null || echo "${tempMarker}"
1974
+ `;
1975
+
1976
+ const result = await this.exec(shell, downloadScript, 300000, true);
1977
+ const downloadedFile = result ? result.trim() : null;
1993
1978
 
1994
- if (scanResult && scanResult.trim()) {
1995
- actualFilePath = scanResult.trim();
1996
- console.log(`[provision.installer] Found installer: ${actualFilePath}`);
1979
+ if (downloadedFile && downloadedFile !== tempMarker) {
1980
+ actualFilePath = `${downloadDir}/${downloadedFile}`;
1981
+ } else {
1982
+ // Fallback: use curl without -J and specify output file
1983
+ const fallbackFilename = filename || 'installer';
1984
+ actualFilePath = `${downloadDir}/${fallbackFilename}`;
1985
+ await this.exec(
1986
+ shell,
1987
+ `curl -L -o "${actualFilePath}" "${url}"`,
1988
+ 300000,
1989
+ true
1990
+ );
1997
1991
  }
1998
1992
  }
1999
1993
 
@@ -0,0 +1,110 @@
1
+ /**
2
+ * TestDriver SDK - API Resilience Test
3
+ *
4
+ * This test verifies that TestDriver client can handle API restarts gracefully.
5
+ * It will:
6
+ * 1. Start a sandbox and browser
7
+ * 2. Make some API calls (TestDriver operations)
8
+ * 3. Kill the API (dev.sh)
9
+ * 4. Restart the API
10
+ * 5. Continue making API calls and verify they work
11
+ *
12
+ * Usage:
13
+ * npm test -- test/api-resilience.test.mjs
14
+ */
15
+
16
+ import { describe, expect, it } from "vitest";
17
+ import { TestDriver } from "../lib/vitest/hooks.mjs";
18
+ import { spawn, exec } from 'child_process';
19
+ import { promisify } from 'util';
20
+
21
+ const execAsync = promisify(exec);
22
+
23
+ describe("API Resilience Test", () => {
24
+ it("should continue working after API restart", async (context) => {
25
+ const testdriver = TestDriver(context, {
26
+ newSandbox: true,
27
+ headless: false
28
+ });
29
+
30
+ console.log("\nšŸ“‹ Step 1: Provision Chrome and navigate to test page");
31
+ await testdriver.provision.chrome({
32
+ url: 'http://testdriver-sandbox.vercel.app/login',
33
+ });
34
+
35
+ console.log("āœ… Provisioned successfully");
36
+
37
+ console.log("\nšŸ“‹ Step 2: Perform initial test operations");
38
+ const button1 = await testdriver.find("Sign In button");
39
+ console.log("āœ… Found Sign In button:", button1.found());
40
+ expect(button1.found()).toBe(true);
41
+
42
+ const result1 = await testdriver.assert("I can see a login page");
43
+ console.log("āœ… First assertion passed:", result1);
44
+ expect(result1).toBeTruthy();
45
+
46
+ console.log("\nšŸ“‹ Step 3: Simulate API going down");
47
+ console.log("āš ļø Killing dev.sh process...");
48
+
49
+ try {
50
+ // Kill all node processes running app.js (the API server)
51
+ await execAsync("pkill -f 'node.*app.js'");
52
+ console.log("āœ… API killed");
53
+ } catch (error) {
54
+ // pkill returns non-zero exit code if no processes found, which is okay
55
+ console.log("Note: No app.js processes found to kill (or already killed)");
56
+ }
57
+
58
+ // Wait a bit to ensure API is down
59
+ console.log("ā³ Waiting 3 seconds to ensure API is down...");
60
+ await new Promise(resolve => setTimeout(resolve, 3000));
61
+
62
+ console.log("\nšŸ“‹ Step 4: Restart API");
63
+ console.log("šŸ”„ Starting dev.sh...");
64
+
65
+ // Start dev.sh in background
66
+ const apiProcess = spawn('bash', ['dev.sh'], {
67
+ cwd: '/Users/ianjennings/Development/api',
68
+ detached: true,
69
+ stdio: 'ignore'
70
+ });
71
+
72
+ // Unref so the process doesn't keep this test running
73
+ apiProcess.unref();
74
+
75
+ console.log("āœ… API restarted (PID:", apiProcess.pid, ")");
76
+
77
+ // Wait for API to be ready
78
+ console.log("ā³ Waiting 10 seconds for API to initialize...");
79
+ await new Promise(resolve => setTimeout(resolve, 10000));
80
+
81
+ console.log("\nšŸ“‹ Step 5: Continue test operations after API restart");
82
+ console.log("šŸ”„ Attempting to find element again...");
83
+
84
+ const button2 = await testdriver.find("Sign In button");
85
+ console.log("āœ… Found Sign In button again:", button2.found());
86
+ expect(button2.found()).toBe(true);
87
+
88
+ console.log("šŸ”„ Performing another assertion...");
89
+ const result2 = await testdriver.assert("I can see a login page");
90
+ console.log("āœ… Second assertion passed:", result2);
91
+ expect(result2).toBeTruthy();
92
+
93
+ console.log("\nšŸ“‹ Step 6: Perform additional operations to verify full functionality");
94
+ const emailInput = await testdriver.find("email input field");
95
+ console.log("āœ… Found email input:", emailInput.found());
96
+ expect(emailInput.found()).toBe(true);
97
+
98
+ await emailInput.click();
99
+ await testdriver.type("test@example.com");
100
+ console.log("āœ… Typed into email field");
101
+
102
+ const result3 = await testdriver.assert("the email field contains text");
103
+ console.log("āœ… Final assertion passed:", result3);
104
+ expect(result3).toBeTruthy();
105
+
106
+ console.log("\nšŸŽ‰ Test completed successfully! API resilience verified.");
107
+ });
108
+ }, {
109
+ timeout: 120000 // 2 minute timeout for this test
110
+ });
@@ -9,7 +9,7 @@
9
9
  * Run: TD_OS=windows npx vitest run examples/windows-installer.test.mjs
10
10
  */
11
11
 
12
- import { describe, expect, it } from "vitest";
12
+ import { describe, it } from "vitest";
13
13
  import { TestDriver } from "../../lib/vitest/hooks.mjs";
14
14
 
15
15
  const isLinux = (process.env.TD_OS || "linux") === "linux";
@@ -19,7 +19,7 @@ describe("Windows App Installation", () => {
19
19
  it.skipIf(isLinux)("should download, install, and launch GitButler on Windows", async (context) => {
20
20
  // Alternative approach using provision.installer helper
21
21
  const testdriver = TestDriver(context, {
22
- newSandbox: true,
22
+ reconnect: true,
23
23
  os: 'windows'
24
24
  });
25
25
 
@@ -29,21 +29,13 @@ describe("Windows App Installation", () => {
29
29
  launch: false, // Don't auto-launch, we'll install manually
30
30
  });
31
31
 
32
- // The installer should be an .msi or .exe file
33
- expect(installerPath).toMatch(/\.(msi|exe)$/i);
34
-
35
- // Install the MSI silently (check which type it is)
36
- if (installerPath.toLowerCase().endsWith('.msi')) {
37
- await testdriver.exec('pwsh',
38
- `Start-Process msiexec.exe -ArgumentList "/i \`"${installerPath}\`" /qn /norestart" -Wait`,
39
- 120000
40
- );
41
- } else {
42
- await testdriver.exec('pwsh',
43
- `Start-Process "${installerPath}" -ArgumentList "/S" -Wait`,
44
- 120000
45
- );
46
- }
32
+ console.log('Installer downloaded to:', installerPath);
33
+
34
+ // Install the MSI silently (the file might not have an extension, so we try MSI first)
35
+ await testdriver.exec('pwsh',
36
+ `Start-Process msiexec.exe -ArgumentList "/i \`"${installerPath}\`" /qn /norestart" -Wait`,
37
+ 120000
38
+ );
47
39
 
48
40
  // Verify installation by checking if executable exists
49
41
  const verifyScript = `