@testdriverai/agent 7.11.85-test → 7.11.87-canary
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/agent/index.js
CHANGED
|
@@ -1729,21 +1729,31 @@ ${regression}
|
|
|
1729
1729
|
|
|
1730
1730
|
// Set sandbox ID for reconnection (only if not creating new and recent ID exists)
|
|
1731
1731
|
if (this.ip) {
|
|
1732
|
+
// Reuse a known sandboxId for this machine so a re-auth re-attaches to the
|
|
1733
|
+
// same Ably channel instead of minting a new slot and orphaning the old
|
|
1734
|
+
// one (self-hosted: one machine → one sandbox).
|
|
1732
1735
|
let instance = await this.sandbox.send({
|
|
1733
1736
|
type: "direct",
|
|
1734
1737
|
resolution: this.config.TD_RESOLUTION,
|
|
1735
1738
|
ci: this.config.CI,
|
|
1736
1739
|
ip: this.ip,
|
|
1740
|
+
sandboxId: this.sandboxId || undefined,
|
|
1737
1741
|
instanceId: this.instanceId || undefined,
|
|
1738
1742
|
});
|
|
1739
1743
|
|
|
1744
|
+
// Remember the slot so a later re-auth reuses it instead of minting a new one.
|
|
1745
|
+
this.sandboxId =
|
|
1746
|
+
instance?.instance?.sandboxId ||
|
|
1747
|
+
instance?.instance?.instanceId ||
|
|
1748
|
+
this.sandboxId;
|
|
1749
|
+
|
|
1740
1750
|
// Store connection params for reconnection
|
|
1741
1751
|
// For direct IP connections, store as a direct type so reconnection
|
|
1742
1752
|
// sends a 'direct' message instead of 'connect' with an IP as sandboxId
|
|
1743
1753
|
this.sandbox.setConnectionParams({
|
|
1744
1754
|
type: 'direct',
|
|
1745
1755
|
ip: this.ip,
|
|
1746
|
-
sandboxId:
|
|
1756
|
+
sandboxId: this.sandboxId,
|
|
1747
1757
|
persist: true,
|
|
1748
1758
|
keepAlive: this.keepAlive,
|
|
1749
1759
|
});
|
|
@@ -125,7 +125,7 @@ function windowsProvisionCommands({ channel, configJson, sandboxId, s3DownloadUr
|
|
|
125
125
|
commands.push(
|
|
126
126
|
"Write-Host 'Checking installed runner version...'",
|
|
127
127
|
"$installedVersion = ''",
|
|
128
|
-
"try { $pkg = Get-Content 'node_modules/@testdriverai/runner/package.json' -Raw | ConvertFrom-Json; $installedVersion = $pkg.version } catch {}",
|
|
128
|
+
"try { $pkg = Get-Content 'node_modules/@testdriverai/runner/package.json' -Raw -ErrorAction Stop | ConvertFrom-Json; $installedVersion = $pkg.version } catch {}",
|
|
129
129
|
"$installedMinor = if ($installedVersion) { ($installedVersion -split '[.-]')[0..1] -join '.' } else { '' }",
|
|
130
130
|
"if (('" + expectedMinor + "' -ne '') -and ($installedMinor -eq '" + expectedMinor + "')) {",
|
|
131
131
|
" Write-Host \"Runner already at v$installedVersion (minor " + expectedMinor + "), skipping update\"",
|
|
@@ -135,7 +135,7 @@ function windowsProvisionCommands({ channel, configJson, sandboxId, s3DownloadUr
|
|
|
135
135
|
" Write-Host 'Installing @testdriverai/runner@" + runnerTag + "...'",
|
|
136
136
|
' npm install @testdriverai/runner@' + runnerTag + ' --omit=dev 2>&1 | Write-Host',
|
|
137
137
|
" $newVersion = ''",
|
|
138
|
-
" try { $newPkg = Get-Content 'node_modules/@testdriverai/runner/package.json' -Raw | ConvertFrom-Json; $newVersion = $newPkg.version } catch {}",
|
|
138
|
+
" try { $newPkg = Get-Content 'node_modules/@testdriverai/runner/package.json' -Raw -ErrorAction Stop | ConvertFrom-Json; $newVersion = $newPkg.version } catch {}",
|
|
139
139
|
" Write-Host (\"RUNNER_VERSION_CHECK:\" + (ConvertTo-Json -Compress @{ action='updated'; previousVersion=$installedVersion; expectedVersion='" + imageVersion + "'; expectedMinor='" + expectedMinor + "'; newVersion=$newVersion; channel='" + channel + "'; sandboxId='" + sandboxId + "' }))",
|
|
140
140
|
" Write-Host 'Runner install complete'",
|
|
141
141
|
"}"
|
package/agent/lib/sandbox.js
CHANGED
|
@@ -303,7 +303,16 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
|
|
|
303
303
|
};
|
|
304
304
|
this._fileSubscription = await this._sessionChannel.subscribe("file", this._onFileMsg);
|
|
305
305
|
|
|
306
|
-
|
|
306
|
+
// Idle keepalive: publish a control message every 30s so the API renews the
|
|
307
|
+
// Redis lease (LEASE_RENEWAL_WINDOW_MS = 2min). Without this, an idle session
|
|
308
|
+
// (no commands) lets the lease lapse and the sandbox is reaped mid-test on
|
|
309
|
+
// both Linux (E2B) and Windows (EC2).
|
|
310
|
+
this.heartbeat = setInterval(function () {
|
|
311
|
+
if (!self._sessionChannel || !self._ably || self._ably.connection.state !== "connected") return;
|
|
312
|
+
self._sessionChannel.publish("control", { type: "keepalive" }).catch(function (err) {
|
|
313
|
+
logger.debug("[realtime] keepalive publish failed (non-fatal): " + (err.message || err));
|
|
314
|
+
});
|
|
315
|
+
}, 30000);
|
|
307
316
|
if (this.heartbeat.unref) this.heartbeat.unref();
|
|
308
317
|
|
|
309
318
|
// ─── Periodic stats logging ────────────────────────────────────────
|
|
@@ -590,6 +599,8 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
|
|
|
590
599
|
body.resolution = message.resolution;
|
|
591
600
|
body.ci = message.ci;
|
|
592
601
|
if (message.instanceId) body.instanceId = message.instanceId;
|
|
602
|
+
// Reuse the same slot on re-auth so the old Ably channel isn't orphaned.
|
|
603
|
+
if (message.sandboxId) body.sandboxId = message.sandboxId;
|
|
593
604
|
}
|
|
594
605
|
|
|
595
606
|
var reply = await this._httpPostWithConcurrencyRetry(
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TestDriver SDK - Idle 10 Minute Test (Vitest)
|
|
3
|
+
*
|
|
4
|
+
* Spawns a VM, opens testdriver.ai, waits 10 minutes, then asserts that
|
|
5
|
+
* testdriver.ai is still visible on screen. Verifies the sandbox and session
|
|
6
|
+
* stay alive during a long idle period.
|
|
7
|
+
*
|
|
8
|
+
* Runs on both Linux and Windows acceptance suites (Windows via TD_OS=windows).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, expect, it } from "vitest";
|
|
12
|
+
import { TestDriver } from "../lib/vitest/hooks.mjs";
|
|
13
|
+
import { getDefaults } from "./config.mjs";
|
|
14
|
+
|
|
15
|
+
const DELAY = 10 * 60 * 1000;
|
|
16
|
+
|
|
17
|
+
describe("Idle 10 Minute Test", () => {
|
|
18
|
+
it(
|
|
19
|
+
"should keep testdriver.ai visible after idling for 10 minutes",
|
|
20
|
+
{ timeout: DELAY + 5 * 60 * 1000 },
|
|
21
|
+
async (context) => {
|
|
22
|
+
const testdriver = TestDriver(context, {
|
|
23
|
+
...getDefaults(context),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Spawn the VM and open testdriver.ai
|
|
27
|
+
await testdriver.provision.chrome({
|
|
28
|
+
url: "https://testdriver.ai",
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Idle for 10 minutes
|
|
32
|
+
await testdriver.wait(DELAY);
|
|
33
|
+
|
|
34
|
+
// Assert testdriver.ai is still visible on screen
|
|
35
|
+
const result = await testdriver.assert(
|
|
36
|
+
"the testdriver.ai website is visible on the screen",
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
expect(result).toBeTruthy();
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
});
|