testdriverai 7.2.9 → 7.2.10

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 (124) hide show
  1. package/.github/workflows/testdriver.yml +127 -0
  2. package/.testdriver/last-sandbox +7 -0
  3. package/agent/events.js +1 -0
  4. package/agent/index.js +71 -54
  5. package/agent/lib/sandbox.js +11 -1
  6. package/agents.md +393 -0
  7. package/debug/01-table-initial.png +0 -0
  8. package/debug/02-after-ai-explore.png +0 -0
  9. package/debug/02-after-scroll.png +0 -0
  10. package/docs/docs.json +93 -125
  11. package/docs/v7/_drafts/caching.mdx +2 -2
  12. package/docs/v7/{getting-started → _drafts}/installation.mdx +0 -66
  13. package/docs/v7/{features/coverage.mdx → _drafts/powerful.mdx} +1 -90
  14. package/docs/v7/{features → _drafts}/scalable.mdx +126 -4
  15. package/docs/v7/_drafts/screenshot.mdx +155 -0
  16. package/docs/v7/_drafts/writing-tests.mdx +25 -0
  17. package/docs/v7/{api/act.mdx → ai.mdx} +27 -27
  18. package/docs/v7/{api/assert.mdx → assert.mdx} +3 -3
  19. package/docs/v7/aws-setup.mdx +338 -0
  20. package/docs/v7/caching.mdx +128 -0
  21. package/docs/v7/ci-cd.mdx +605 -0
  22. package/docs/v7/{api/click.mdx → click.mdx} +4 -4
  23. package/docs/v7/cloud.mdx +120 -0
  24. package/docs/v7/customizing-devices.mdx +129 -0
  25. package/docs/v7/{api/doubleClick.mdx → double-click.mdx} +5 -5
  26. package/docs/v7/enterprise.mdx +135 -0
  27. package/docs/v7/examples.mdx +5 -0
  28. package/docs/v7/{api/exec.mdx → exec.mdx} +3 -3
  29. package/docs/v7/{api/find.mdx → find.mdx} +17 -21
  30. package/docs/v7/{api/focusApplication.mdx → focus-application.mdx} +3 -3
  31. package/docs/v7/generating-tests.mdx +32 -0
  32. package/docs/v7/{api/hover.mdx → hover.mdx} +3 -3
  33. package/docs/v7/locating-elements.mdx +71 -0
  34. package/docs/v7/making-assertions.mdx +32 -0
  35. package/docs/v7/{api/mouseDown.mdx → mouse-down.mdx} +7 -7
  36. package/docs/v7/{api/mouseUp.mdx → mouse-up.mdx} +8 -8
  37. package/docs/v7/performing-actions.mdx +51 -0
  38. package/docs/v7/{api/pressKeys.mdx → press-keys.mdx} +3 -3
  39. package/docs/v7/quickstart.mdx +162 -0
  40. package/docs/v7/reusable-code.mdx +240 -0
  41. package/docs/v7/{api/rightClick.mdx → right-click.mdx} +5 -5
  42. package/docs/v7/running-tests.mdx +181 -0
  43. package/docs/v7/{api/scroll.mdx → scroll.mdx} +3 -3
  44. package/docs/v7/secrets.mdx +115 -0
  45. package/docs/v7/self-hosted.mdx +66 -0
  46. package/docs/v7/{api/type.mdx → type.mdx} +3 -3
  47. package/docs/v7/variables.mdx +111 -0
  48. package/docs/v7/waiting-for-elements.mdx +66 -0
  49. package/docs/v7/what-is-testdriver.mdx +54 -0
  50. package/lib/vitest/hooks.mjs +80 -68
  51. package/package.json +1 -1
  52. package/sdk.d.ts +22 -9
  53. package/sdk.js +177 -44
  54. package/test/manual/reconnect-provision.test.mjs +49 -0
  55. package/test/manual/reconnect-signin.test.mjs +41 -0
  56. package/test/testdriver/ai.test.mjs +30 -0
  57. package/test/testdriver/setup/testHelpers.mjs +0 -1
  58. package/test/testdriver/windows-installer.test.mjs +61 -0
  59. package/tests/table-sort-enrollments.test.mjs +72 -0
  60. package/tests/table-sort-experiment.test.mjs +42 -0
  61. package/tests/table-sort-setup.test.mjs +59 -0
  62. package/vitest.config.mjs +1 -0
  63. package/docs/v7/api/assertions.mdx +0 -403
  64. package/docs/v7/features/ai-native.mdx +0 -413
  65. package/docs/v7/features/application-logs.mdx +0 -353
  66. package/docs/v7/features/browser-logs.mdx +0 -414
  67. package/docs/v7/features/cache-management.mdx +0 -402
  68. package/docs/v7/features/continuous-testing.mdx +0 -346
  69. package/docs/v7/features/data-driven-testing.mdx +0 -441
  70. package/docs/v7/features/easy-to-write.mdx +0 -280
  71. package/docs/v7/features/enterprise.mdx +0 -656
  72. package/docs/v7/features/fast.mdx +0 -406
  73. package/docs/v7/features/managed-sandboxes.mdx +0 -384
  74. package/docs/v7/features/network-monitoring.mdx +0 -568
  75. package/docs/v7/features/parallel-execution.mdx +0 -381
  76. package/docs/v7/features/powerful.mdx +0 -531
  77. package/docs/v7/features/sandbox-customization.mdx +0 -229
  78. package/docs/v7/features/stable.mdx +0 -473
  79. package/docs/v7/features/system-performance.mdx +0 -616
  80. package/docs/v7/features/test-analytics.mdx +0 -373
  81. package/docs/v7/features/test-cases.mdx +0 -393
  82. package/docs/v7/features/test-replays.mdx +0 -408
  83. package/docs/v7/features/test-reports.mdx +0 -308
  84. package/docs/v7/getting-started/debugging-tests.mdx +0 -382
  85. package/docs/v7/getting-started/quickstart.mdx +0 -90
  86. package/docs/v7/getting-started/running-tests.mdx +0 -173
  87. package/docs/v7/getting-started/setting-up-in-ci.mdx +0 -612
  88. package/docs/v7/getting-started/writing-tests.mdx +0 -534
  89. package/docs/v7/overview/what-is-testdriver.mdx +0 -386
  90. package/docs/v7/presets/chrome-extension.mdx +0 -248
  91. package/docs/v7/presets/chrome.mdx +0 -300
  92. package/docs/v7/presets/electron.mdx +0 -460
  93. package/docs/v7/presets/vscode.mdx +0 -417
  94. package/docs/v7/presets/webapp.mdx +0 -393
  95. package/vitest.config.js +0 -18
  96. /package/docs/v7/{commands → _drafts/commands}/assert.mdx +0 -0
  97. /package/docs/v7/{commands → _drafts/commands}/exec.mdx +0 -0
  98. /package/docs/v7/{commands → _drafts/commands}/focus-application.mdx +0 -0
  99. /package/docs/v7/{commands → _drafts/commands}/hover-image.mdx +0 -0
  100. /package/docs/v7/{commands → _drafts/commands}/hover-text.mdx +0 -0
  101. /package/docs/v7/{commands → _drafts/commands}/if.mdx +0 -0
  102. /package/docs/v7/{commands → _drafts/commands}/match-image.mdx +0 -0
  103. /package/docs/v7/{commands → _drafts/commands}/press-keys.mdx +0 -0
  104. /package/docs/v7/{commands → _drafts/commands}/remember.mdx +0 -0
  105. /package/docs/v7/{commands → _drafts/commands}/run.mdx +0 -0
  106. /package/docs/v7/{commands → _drafts/commands}/scroll-until-image.mdx +0 -0
  107. /package/docs/v7/{commands → _drafts/commands}/scroll-until-text.mdx +0 -0
  108. /package/docs/v7/{commands → _drafts/commands}/scroll.mdx +0 -0
  109. /package/docs/v7/{commands → _drafts/commands}/type.mdx +0 -0
  110. /package/docs/v7/{commands → _drafts/commands}/wait-for-image.mdx +0 -0
  111. /package/docs/v7/{commands → _drafts/commands}/wait-for-text.mdx +0 -0
  112. /package/docs/v7/{commands → _drafts/commands}/wait.mdx +0 -0
  113. /package/docs/v7/{getting-started → _drafts}/configuration.mdx +0 -0
  114. /package/docs/v7/{features → _drafts}/observable.mdx +0 -0
  115. /package/docs/v7/{platforms → _drafts/platforms}/linux.mdx +0 -0
  116. /package/docs/v7/{platforms → _drafts/platforms}/macos.mdx +0 -0
  117. /package/docs/v7/{platforms → _drafts/platforms}/windows.mdx +0 -0
  118. /package/docs/v7/{playwright.mdx → _drafts/playwright.mdx} +0 -0
  119. /package/docs/v7/{overview → _drafts}/readme.mdx +0 -0
  120. /package/docs/v7/{features → _drafts}/reports.mdx +0 -0
  121. /package/docs/v7/{api/client.mdx → client.mdx} +0 -0
  122. /package/docs/v7/{api/dashcam.mdx → dashcam.mdx} +0 -0
  123. /package/docs/v7/{api/elements.mdx → elements.mdx} +0 -0
  124. /package/docs/v7/{api/sandbox.mdx → sandbox.mdx} +0 -0
@@ -34,3 +34,130 @@ jobs:
34
34
  name: test-results
35
35
  path: test-results/
36
36
  retention-days: 30
37
+
38
+ # Init command test - only runs on PRs, not on main/master
39
+ - name: Create test directory for init
40
+ if: github.event_name == 'pull_request'
41
+ run: |
42
+ mkdir -p /tmp/test-init-project
43
+ cd /tmp/test-init-project
44
+
45
+ - name: Run init command (skip prompts)
46
+ if: github.event_name == 'pull_request'
47
+ working-directory: /tmp/test-init-project
48
+ run: |
49
+ # Create .env with API key first to skip the prompt
50
+ echo "TD_API_KEY=${{ secrets.TD_API_KEY }}" > .env
51
+
52
+ # Run init command using the CLI from the repo
53
+ node ${{ github.workspace }}/bin/testdriverai.js init
54
+ env:
55
+ TD_API_KEY: ${{ secrets.TD_API_KEY }}
56
+
57
+ - name: Verify project structure
58
+ if: github.event_name == 'pull_request'
59
+ working-directory: /tmp/test-init-project
60
+ run: |
61
+ echo "Checking generated files..."
62
+
63
+ # Check for package.json
64
+ if [ ! -f "package.json" ]; then
65
+ echo "❌ package.json not found"
66
+ exit 1
67
+ fi
68
+ echo "✓ package.json exists"
69
+
70
+ # Check for vitest config
71
+ if [ ! -f "vitest.config.js" ]; then
72
+ echo "❌ vitest.config.js not found"
73
+ exit 1
74
+ fi
75
+ echo "✓ vitest.config.js exists"
76
+
77
+ # Check for test file
78
+ if [ ! -f "tests/example.test.js" ]; then
79
+ echo "❌ tests/example.test.js not found"
80
+ exit 1
81
+ fi
82
+ echo "✓ tests/example.test.js exists"
83
+
84
+ # Check for .env file
85
+ if [ ! -f ".env" ]; then
86
+ echo "❌ .env not found"
87
+ exit 1
88
+ fi
89
+ echo "✓ .env exists"
90
+
91
+ # Check for .gitignore
92
+ if [ ! -f ".gitignore" ]; then
93
+ echo "❌ .gitignore not found"
94
+ exit 1
95
+ fi
96
+ echo "✓ .gitignore exists"
97
+
98
+ # Check for GitHub workflow
99
+ if [ ! -f ".github/workflows/testdriver.yml" ]; then
100
+ echo "❌ .github/workflows/testdriver.yml not found"
101
+ exit 1
102
+ fi
103
+ echo "✓ .github/workflows/testdriver.yml exists"
104
+
105
+ - name: Verify vitest config contents
106
+ if: github.event_name == 'pull_request'
107
+ working-directory: /tmp/test-init-project
108
+ run: |
109
+ echo "Checking vitest.config.js contents..."
110
+
111
+ # Check for TestDriver reporter
112
+ if ! grep -q "TestDriver()" vitest.config.js; then
113
+ echo "❌ TestDriver reporter not found in vitest.config.js"
114
+ cat vitest.config.js
115
+ exit 1
116
+ fi
117
+ echo "✓ TestDriver reporter is configured"
118
+
119
+ # Check for setupFiles
120
+ if ! grep -q "setupFiles.*testdriverai/vitest/setup" vitest.config.js; then
121
+ echo "❌ setupFiles not configured correctly"
122
+ cat vitest.config.js
123
+ exit 1
124
+ fi
125
+ echo "✓ setupFiles is configured"
126
+
127
+ - name: Verify test file contents
128
+ if: github.event_name == 'pull_request'
129
+ working-directory: /tmp/test-init-project
130
+ run: |
131
+ echo "Checking test file contents..."
132
+
133
+ # Check for .provision usage
134
+ if ! grep -q "\.provision\.chrome" tests/example.test.js; then
135
+ echo "❌ Test does not use .provision.chrome"
136
+ cat tests/example.test.js
137
+ exit 1
138
+ fi
139
+ echo "✓ Test uses .provision.chrome"
140
+
141
+ # Check for TestDriver import
142
+ if ! grep -q "from 'testdriverai/vitest/hooks'" tests/example.test.js; then
143
+ echo "❌ Test does not import from testdriverai/vitest/hooks"
144
+ cat tests/example.test.js
145
+ exit 1
146
+ fi
147
+ echo "✓ Test imports TestDriver from vitest/hooks"
148
+
149
+ - name: Run the generated test
150
+ if: github.event_name == 'pull_request'
151
+ working-directory: /tmp/test-init-project
152
+ run: npm test
153
+ env:
154
+ TD_API_KEY: ${{ secrets.TD_API_KEY }}
155
+
156
+ - name: Upload init test results
157
+ if: always() && github.event_name == 'pull_request'
158
+ uses: actions/upload-artifact@v4
159
+ with:
160
+ name: test-init-results
161
+ path: /tmp/test-init-project/test-results/
162
+ retention-days: 7
163
+ if-no-files-found: warn
@@ -0,0 +1,7 @@
1
+ {
2
+ "sandboxId": "i-034317335b2eb9678",
3
+ "os": "windows",
4
+ "ami": null,
5
+ "instanceType": null,
6
+ "timestamp": "2025-12-22T23:09:20.319Z"
7
+ }
package/agent/events.js CHANGED
@@ -99,6 +99,7 @@ const events = {
99
99
  disconnect: "sandbox:disconnected",
100
100
  sent: "sandbox:sent",
101
101
  received: "sandbox:received",
102
+ progress: "sandbox:progress",
102
103
  },
103
104
  redraw: {
104
105
  status: "redraw:status",
package/agent/index.js CHANGED
@@ -1619,49 +1619,35 @@ ${regression}
1619
1619
  this.emitter.emit(events.log.log, `${inputFile} (end)`);
1620
1620
  }
1621
1621
 
1622
- // Returns sandboxId to use (either from file if recent, or null)
1623
- getRecentSandboxId() {
1624
- const lastSandboxFile = path.join(
1625
- os.homedir(),
1626
- ".testdriverai-last-sandbox",
1627
- );
1622
+ // Returns the path to the last sandbox file
1623
+ getLastSandboxFilePath() {
1624
+ const testdriverDir = path.join(process.cwd(), '.testdriver');
1625
+ return path.join(testdriverDir, 'last-sandbox');
1626
+ }
1627
+
1628
+ // Returns full sandbox info from last-sandbox file (no timeout - let API validate)
1629
+ getLastSandboxId() {
1630
+ const lastSandboxFile = this.getLastSandboxFilePath();
1628
1631
 
1629
1632
  if (fs.existsSync(lastSandboxFile)) {
1630
1633
  try {
1631
- const stats = fs.statSync(lastSandboxFile);
1632
- const mtime = new Date(stats.mtime);
1633
- const now = new Date();
1634
- const diffMinutes = (now - mtime) / (1000 * 60);
1635
- if (diffMinutes < 10) {
1636
- const fileContent = fs.readFileSync(lastSandboxFile, "utf-8").trim();
1637
-
1638
- // Parse sandbox info (supports both old format and new format)
1639
- let sandboxInfo;
1640
- try {
1641
- sandboxInfo = JSON.parse(fileContent);
1642
- } catch {
1643
- return fileContent || null;
1644
- }
1634
+ const fileContent = fs.readFileSync(lastSandboxFile, "utf-8").trim();
1645
1635
 
1646
- // Check if AMI and instance type match current requirements
1647
- const currentAmi = this.sandboxAmi || null;
1648
- const currentInstance = this.sandboxInstance || null;
1649
- const storedAmi = sandboxInfo.ami || null;
1650
- const storedInstance = sandboxInfo.instanceType || null;
1651
-
1652
- if (currentAmi === storedAmi && currentInstance === storedInstance) {
1653
- // Return sandboxId (new format) or instanceId (old format for backwards compatibility)
1654
- return sandboxInfo.sandboxId || sandboxInfo.instanceId;
1655
- } else {
1656
- this.emitter.emit(
1657
- events.log.log,
1658
- theme.dim(
1659
- "Recent sandbox found but AMI/instance type doesn't match current requirements",
1660
- ),
1661
- );
1662
- return null;
1663
- }
1636
+ // Parse sandbox info (supports both old format and new format)
1637
+ let sandboxInfo;
1638
+ try {
1639
+ sandboxInfo = JSON.parse(fileContent);
1640
+ } catch {
1641
+ return { sandboxId: fileContent || null };
1664
1642
  }
1643
+
1644
+ return {
1645
+ sandboxId: sandboxInfo.sandboxId || sandboxInfo.instanceId || null,
1646
+ os: sandboxInfo.os || 'linux',
1647
+ ami: sandboxInfo.ami || null,
1648
+ instanceType: sandboxInfo.instanceType || null,
1649
+ timestamp: sandboxInfo.timestamp || null,
1650
+ };
1665
1651
  } catch {
1666
1652
  // ignore errors
1667
1653
  }
@@ -1669,12 +1655,43 @@ ${regression}
1669
1655
  return null;
1670
1656
  }
1671
1657
 
1658
+ // Returns sandboxId to use if AMI/instance type match current requirements
1659
+ getRecentSandboxId() {
1660
+ const sandboxInfo = this.getLastSandboxId();
1661
+
1662
+ if (!sandboxInfo || !sandboxInfo.sandboxId) {
1663
+ return null;
1664
+ }
1665
+
1666
+ // Check if AMI and instance type match current requirements
1667
+ const currentAmi = this.sandboxAmi || null;
1668
+ const currentInstance = this.sandboxInstance || null;
1669
+ const storedAmi = sandboxInfo.ami || null;
1670
+ const storedInstance = sandboxInfo.instanceType || null;
1671
+
1672
+ if (currentAmi === storedAmi && currentInstance === storedInstance) {
1673
+ return sandboxInfo.sandboxId;
1674
+ } else {
1675
+ this.emitter.emit(
1676
+ events.log.log,
1677
+ theme.dim(
1678
+ "Recent sandbox found but AMI/instance type doesn't match current requirements",
1679
+ ),
1680
+ );
1681
+ return null;
1682
+ }
1683
+ }
1684
+
1672
1685
  saveLastSandboxId(sandboxId, osType = "linux") {
1673
- const lastSandboxFile = path.join(
1674
- os.homedir(),
1675
- ".testdriverai-last-sandbox",
1676
- );
1686
+ const lastSandboxFile = this.getLastSandboxFilePath();
1687
+ const testdriverDir = path.dirname(lastSandboxFile);
1688
+
1677
1689
  try {
1690
+ // Ensure .testdriver directory exists
1691
+ if (!fs.existsSync(testdriverDir)) {
1692
+ fs.mkdirSync(testdriverDir, { recursive: true });
1693
+ }
1694
+
1678
1695
  const sandboxInfo = {
1679
1696
  sandboxId: sandboxId,
1680
1697
  os: osType,
@@ -1682,7 +1699,7 @@ ${regression}
1682
1699
  instanceType: this.sandboxInstance || null,
1683
1700
  timestamp: new Date().toISOString(),
1684
1701
  };
1685
- fs.writeFileSync(lastSandboxFile, JSON.stringify(sandboxInfo), {
1702
+ fs.writeFileSync(lastSandboxFile, JSON.stringify(sandboxInfo, null, 2), {
1686
1703
  encoding: "utf-8",
1687
1704
  });
1688
1705
  } catch {
@@ -1691,10 +1708,7 @@ ${regression}
1691
1708
  }
1692
1709
 
1693
1710
  clearRecentSandboxId() {
1694
- const lastSandboxFile = path.join(
1695
- os.homedir(),
1696
- ".testdriverai-last-sandbox",
1697
- );
1711
+ const lastSandboxFile = this.getLastSandboxFilePath();
1698
1712
  try {
1699
1713
  if (fs.existsSync(lastSandboxFile)) {
1700
1714
  fs.unlinkSync(lastSandboxFile);
@@ -1703,6 +1717,7 @@ ${regression}
1703
1717
  // ignore errors
1704
1718
  }
1705
1719
  }
1720
+
1706
1721
  async buildEnv(options = {}) {
1707
1722
  // If instance already exists, do not build environment again
1708
1723
  if (this.instance) {
@@ -1785,6 +1800,7 @@ ${regression}
1785
1800
  let instance = await this.connectToSandboxDirect(
1786
1801
  this.sandboxId,
1787
1802
  true, // always persist by default
1803
+ this.keepAlive, // pass keepAlive TTL
1788
1804
  );
1789
1805
 
1790
1806
  this.instance = instance;
@@ -1799,11 +1815,6 @@ ${regression}
1799
1815
  );
1800
1816
  console.error("Failed to reconnect to sandbox:", error);
1801
1817
  }
1802
- } else if (!createNew && !recentId) {
1803
- this.emitter.emit(
1804
- events.log.narration,
1805
- theme.dim(`no recent sandbox found, creating a new one.`),
1806
- );
1807
1818
  } else if (!createNew && this.sandboxId && !this.config.CI) {
1808
1819
  // Only attempt to connect to existing sandbox if not in CI mode and not creating new
1809
1820
  // Attempt to connect to known instance
@@ -1816,6 +1827,7 @@ ${regression}
1816
1827
  let instance = await this.connectToSandboxDirect(
1817
1828
  this.sandboxId,
1818
1829
  true, // always persist by default
1830
+ this.keepAlive, // pass keepAlive TTL
1819
1831
  );
1820
1832
 
1821
1833
  this.instance = instance;
@@ -1858,6 +1870,7 @@ ${regression}
1858
1870
  let instance = await this.connectToSandboxDirect(
1859
1871
  this.sandboxId,
1860
1872
  true, // always persist by default
1873
+ this.keepAlive, // pass keepAlive TTL
1861
1874
  );
1862
1875
  this.instance = instance;
1863
1876
  await this.renderSandbox(instance, headless);
@@ -2047,10 +2060,10 @@ Please check your network connection, TD_API_KEY, or the service status.`,
2047
2060
  }
2048
2061
  }
2049
2062
 
2050
- async connectToSandboxDirect(sandboxId, persist = false) {
2063
+ async connectToSandboxDirect(sandboxId, persist = false, keepAlive = null) {
2051
2064
  const { formatter } = require("../sdk-log-formatter.js");
2052
2065
  this.emitter.emit(events.log.narration, formatter.getPrefix("connect") + " " + theme.green.bold("Connecting") + " " + theme.cyan(`to sandbox...`));
2053
- let reply = await this.sandbox.connect(sandboxId, persist);
2066
+ let reply = await this.sandbox.connect(sandboxId, persist, keepAlive);
2054
2067
 
2055
2068
  // reply includes { success, url, sandbox: {...} }
2056
2069
  // For renderSandbox, we need the sandbox object with url merged in
@@ -2079,6 +2092,10 @@ Please check your network connection, TD_API_KEY, or the service status.`,
2079
2092
  if (this.sandboxInstance) {
2080
2093
  sandboxConfig.instanceType = this.sandboxInstance;
2081
2094
  }
2095
+ // Add keepAlive TTL if specified
2096
+ if (this.keepAlive !== undefined && this.keepAlive !== null) {
2097
+ sandboxConfig.keepAlive = this.keepAlive;
2098
+ }
2082
2099
 
2083
2100
  let instance = await this.sandbox.send(sandboxConfig, 60000 * 8);
2084
2101
 
@@ -155,11 +155,12 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
155
155
  }
156
156
  }
157
157
 
158
- async connect(sandboxId, persist = false) {
158
+ async connect(sandboxId, persist = false, keepAlive = null) {
159
159
  let reply = await this.send({
160
160
  type: "connect",
161
161
  persist,
162
162
  sandboxId,
163
+ keepAlive,
163
164
  });
164
165
 
165
166
  if (reply.success) {
@@ -227,6 +228,15 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
227
228
  this.socket.on("message", async (raw) => {
228
229
  let message = JSON.parse(raw);
229
230
 
231
+ // Handle progress messages (no requestId needed)
232
+ if (message.type === 'sandbox.progress') {
233
+ emitter.emit(events.sandbox.progress, {
234
+ step: message.step,
235
+ message: message.message,
236
+ });
237
+ return;
238
+ }
239
+
230
240
  if (!this.ps[message.requestId]) {
231
241
  console.warn(
232
242
  "No pending promise found for requestId:",