testdriverai 7.8.0-test.32 → 7.8.0-test.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.
- package/agent/lib/sandbox.js +134 -55
- package/docs/_data/examples-manifest.json +46 -46
- package/docs/v7/examples/ai.mdx +1 -1
- package/docs/v7/examples/assert.mdx +1 -1
- package/docs/v7/examples/chrome-extension.mdx +1 -1
- package/docs/v7/examples/element-not-found.mdx +1 -1
- package/docs/v7/examples/exec-output.mdx +1 -1
- package/docs/v7/examples/exec-pwsh.mdx +1 -1
- package/docs/v7/examples/focus-window.mdx +1 -1
- package/docs/v7/examples/hover-image.mdx +1 -1
- package/docs/v7/examples/hover-text.mdx +1 -1
- package/docs/v7/examples/installer.mdx +1 -1
- package/docs/v7/examples/launch-vscode-linux.mdx +1 -1
- package/docs/v7/examples/match-image.mdx +1 -1
- package/docs/v7/examples/press-keys.mdx +1 -1
- package/docs/v7/examples/scroll-keyboard.mdx +1 -1
- package/docs/v7/examples/scroll-until-image.mdx +1 -1
- package/docs/v7/examples/scroll.mdx +1 -1
- package/docs/v7/examples/type.mdx +1 -1
- package/docs/v7/examples/windows-installer.mdx +1 -1
- package/interfaces/vitest-plugin.mjs +45 -43
- package/lib/core/Dashcam.js +17 -7
- package/lib/github-comment.mjs +58 -40
- package/package.json +1 -1
- package/setup/aws/install-dev-runner.sh +79 -0
- package/setup/aws/spawn-runner.sh +134 -0
- package/vitest.runner.config.mjs +33 -0
package/lib/github-comment.mjs
CHANGED
|
@@ -49,7 +49,7 @@ function generateTestResultsTable(testCases, testRunUrl) {
|
|
|
49
49
|
|
|
50
50
|
// Filter out skipped tests
|
|
51
51
|
const nonSkippedTests = testCases.filter(test => test.status !== 'skipped');
|
|
52
|
-
|
|
52
|
+
|
|
53
53
|
if (nonSkippedTests.length === 0) {
|
|
54
54
|
return '_No test cases to display (all tests were skipped)_';
|
|
55
55
|
}
|
|
@@ -62,12 +62,12 @@ function generateTestResultsTable(testCases, testRunUrl) {
|
|
|
62
62
|
const name = test.testName || 'Unknown';
|
|
63
63
|
const file = test.testFile || 'unknown';
|
|
64
64
|
const duration = formatDuration(test.duration || 0);
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
// Use test run context URL instead of direct replay URL
|
|
67
67
|
let replay = '-';
|
|
68
68
|
if (test.replayUrl) {
|
|
69
69
|
const linkUrl = (test.id && testRunUrl) ? `${testRunUrl}/${test.id}` : test.replayUrl;
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
// Extract replay ID and generate GIF URL
|
|
72
72
|
const replayId = extractReplayId(test.replayUrl);
|
|
73
73
|
if (replayId) {
|
|
@@ -97,28 +97,28 @@ function generateTestResultsTable(testCases, testRunUrl) {
|
|
|
97
97
|
*/
|
|
98
98
|
function generateExceptionsSection(testCases, testRunUrl) {
|
|
99
99
|
const failedTests = testCases.filter(t => t.status === 'failed' && t.errorMessage);
|
|
100
|
-
|
|
100
|
+
|
|
101
101
|
if (failedTests.length === 0) {
|
|
102
102
|
return '';
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
let section = '\n## 🔴 Failures\n\n';
|
|
106
|
-
|
|
106
|
+
|
|
107
107
|
for (const test of failedTests) {
|
|
108
108
|
section += `### ${test.testName}\n\n`;
|
|
109
109
|
section += `**File:** \`${test.testFile}\`\n\n`;
|
|
110
|
-
|
|
110
|
+
|
|
111
111
|
// Use test run context URL instead of direct replay URL
|
|
112
112
|
if (test.id && testRunUrl) {
|
|
113
113
|
section += `**📹 [Watch Replay](${testRunUrl}/${test.id})**\n\n`;
|
|
114
114
|
} else if (test.replayUrl) {
|
|
115
115
|
section += `**📹 [Watch Replay](${test.replayUrl})**\n\n`;
|
|
116
116
|
}
|
|
117
|
-
|
|
117
|
+
|
|
118
118
|
section += '```\n';
|
|
119
119
|
section += test.errorMessage || 'Unknown error';
|
|
120
120
|
section += '\n```\n\n';
|
|
121
|
-
|
|
121
|
+
|
|
122
122
|
if (test.errorStack) {
|
|
123
123
|
section += '<details>\n';
|
|
124
124
|
section += '<summary>Stack Trace</summary>\n\n';
|
|
@@ -140,22 +140,22 @@ function generateExceptionsSection(testCases, testRunUrl) {
|
|
|
140
140
|
*/
|
|
141
141
|
function generateReplaySection(testCases, testRunUrl) {
|
|
142
142
|
const testsWithReplays = testCases.filter(t => t.replayUrl);
|
|
143
|
-
|
|
143
|
+
|
|
144
144
|
if (testsWithReplays.length === 0) {
|
|
145
145
|
return '';
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
let section = '\n## 🎥 Dashcam Replays\n\n';
|
|
149
|
-
|
|
149
|
+
|
|
150
150
|
for (const test of testsWithReplays) {
|
|
151
151
|
section += `### ${test.testName}\n\n`;
|
|
152
|
-
|
|
152
|
+
|
|
153
153
|
// Determine the link URL - prefer test run context
|
|
154
154
|
let linkUrl = test.replayUrl;
|
|
155
155
|
if (test.id && testRunUrl) {
|
|
156
156
|
linkUrl = `${testRunUrl}/${test.id}`;
|
|
157
157
|
}
|
|
158
|
-
|
|
158
|
+
|
|
159
159
|
// Extract replay ID from URL for GIF embed
|
|
160
160
|
const replayId = extractReplayId(test.replayUrl);
|
|
161
161
|
if (replayId) {
|
|
@@ -177,7 +177,7 @@ function generateReplaySection(testCases, testRunUrl) {
|
|
|
177
177
|
*/
|
|
178
178
|
function extractReplayId(url) {
|
|
179
179
|
if (!url) return null;
|
|
180
|
-
|
|
180
|
+
|
|
181
181
|
// Match pattern: /replay/{id} or /replay/{id}?params
|
|
182
182
|
const match = url.match(/\/replay\/([^?/#]+)/);
|
|
183
183
|
return match ? match[1] : null;
|
|
@@ -190,7 +190,7 @@ function extractReplayId(url) {
|
|
|
190
190
|
*/
|
|
191
191
|
function extractShareKey(url) {
|
|
192
192
|
if (!url) return null;
|
|
193
|
-
|
|
193
|
+
|
|
194
194
|
// Match pattern: ?share=KEY or &share=KEY
|
|
195
195
|
const match = url.match(/[?&]share=([^&#]+)/);
|
|
196
196
|
return match ? match[1] : null;
|
|
@@ -204,32 +204,50 @@ function extractShareKey(url) {
|
|
|
204
204
|
*/
|
|
205
205
|
function getReplayGifUrl(replayUrl, replayId) {
|
|
206
206
|
// Determine the API base URL based on the replay URL
|
|
207
|
+
// Replay URLs use console domains; GIF endpoints live on the corresponding API domain
|
|
207
208
|
let apiBaseUrl;
|
|
208
|
-
|
|
209
|
+
|
|
209
210
|
if (replayUrl.includes('app.dashcam.io')) {
|
|
210
211
|
// Production dashcam uses Heroku API
|
|
211
212
|
apiBaseUrl = 'https://testdriverai-v6-c96fc597be11.herokuapp.com';
|
|
212
|
-
} else if (replayUrl.includes('console.testdriver.ai')) {
|
|
213
|
-
// TestDriver console
|
|
214
|
-
apiBaseUrl = 'https://api.testdriver.ai';
|
|
215
213
|
} else if (replayUrl.includes('localhost')) {
|
|
216
214
|
// Local development
|
|
217
215
|
apiBaseUrl = 'http://localhost:1337';
|
|
218
216
|
} else {
|
|
219
|
-
//
|
|
220
|
-
|
|
221
|
-
|
|
217
|
+
// Map console URLs → API URLs for all environments
|
|
218
|
+
// console-test.testdriver.ai → api-test.testdriver.ai
|
|
219
|
+
// console-canary.testdriver.ai → api-canary.testdriver.ai
|
|
220
|
+
// console.testdriver.ai → api.testdriver.ai
|
|
221
|
+
const consoleEnvMatch = replayUrl.match(/https:\/\/console-(test|canary)\.testdriver\.ai/);
|
|
222
|
+
if (consoleEnvMatch) {
|
|
223
|
+
apiBaseUrl = `https://api-${consoleEnvMatch[1]}.testdriver.ai`;
|
|
224
|
+
} else if (replayUrl.includes('console.testdriver.ai')) {
|
|
225
|
+
apiBaseUrl = 'https://api.testdriver.ai';
|
|
226
|
+
}
|
|
227
|
+
// Fly.io: map web app → API app
|
|
228
|
+
// pr-123-web.fly.dev → pr-123-api.fly.dev
|
|
229
|
+
// td-test-web.fly.dev → td-test-api.fly.dev
|
|
230
|
+
else {
|
|
231
|
+
const flyWebMatch = replayUrl.match(/https:\/\/([\w-]+)-web\.fly\.dev/);
|
|
232
|
+
if (flyWebMatch) {
|
|
233
|
+
apiBaseUrl = `https://${flyWebMatch[1]}-api.fly.dev`;
|
|
234
|
+
} else {
|
|
235
|
+
// Fallback: extract base URL from replay URL as-is
|
|
236
|
+
const urlObj = new URL(replayUrl);
|
|
237
|
+
apiBaseUrl = `${urlObj.protocol}//${urlObj.host}`;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
222
240
|
}
|
|
223
|
-
|
|
241
|
+
|
|
224
242
|
// Extract share key if present
|
|
225
243
|
const shareKey = extractShareKey(replayUrl);
|
|
226
|
-
|
|
244
|
+
|
|
227
245
|
// Build GIF URL with shareKey parameter
|
|
228
246
|
let gifUrl = `${apiBaseUrl}/replay/${replayId}/gif`;
|
|
229
247
|
if (shareKey) {
|
|
230
248
|
gifUrl += `?shareKey=${shareKey}`;
|
|
231
249
|
}
|
|
232
|
-
|
|
250
|
+
|
|
233
251
|
return gifUrl;
|
|
234
252
|
}
|
|
235
253
|
|
|
@@ -257,9 +275,9 @@ export function generateGitHubComment(testRunData, testCases = []) {
|
|
|
257
275
|
// Header with overall status
|
|
258
276
|
const statusEmoji = getStatusEmoji(status);
|
|
259
277
|
const statusColor = status === 'passed' ? '🟢' : status === 'failed' ? '🔴' : '🟡';
|
|
260
|
-
|
|
278
|
+
|
|
261
279
|
let comment = `# ${statusColor} TestDriver Test Results\n\n`;
|
|
262
|
-
|
|
280
|
+
|
|
263
281
|
// Compact summary line
|
|
264
282
|
comment += `**Status:** ${statusEmoji} ${status.toUpperCase()}`;
|
|
265
283
|
comment += ` • **Duration:** ${formatDuration(duration)}`;
|
|
@@ -270,23 +288,23 @@ export function generateGitHubComment(testRunData, testCases = []) {
|
|
|
270
288
|
comment += `, ${skippedTests} skipped`;
|
|
271
289
|
}
|
|
272
290
|
comment += `\n\n`;
|
|
273
|
-
|
|
291
|
+
|
|
274
292
|
// Exceptions section (only if there are failures) - show first
|
|
275
293
|
comment += generateExceptionsSection(testCases, testRunUrl);
|
|
276
|
-
|
|
294
|
+
|
|
277
295
|
// Test results table (now includes embedded GIFs)
|
|
278
296
|
comment += '## 📝 Test Results\n\n';
|
|
279
297
|
comment += generateTestResultsTable(testCases, testRunUrl);
|
|
280
|
-
|
|
298
|
+
|
|
281
299
|
// Link to full test run (below table)
|
|
282
300
|
if (testRunUrl) {
|
|
283
301
|
comment += `\n[📋 View Full Test Run](${testRunUrl})\n`;
|
|
284
302
|
}
|
|
285
|
-
|
|
303
|
+
|
|
286
304
|
// Footer
|
|
287
305
|
comment += '\n---\n';
|
|
288
306
|
comment += `<sub>Generated by [TestDriver](https://testdriver.ai) • Run ID: \`${runId}\`</sub>\n`;
|
|
289
|
-
|
|
307
|
+
|
|
290
308
|
return comment;
|
|
291
309
|
}
|
|
292
310
|
|
|
@@ -303,15 +321,15 @@ export function generateGitHubComment(testRunData, testCases = []) {
|
|
|
303
321
|
*/
|
|
304
322
|
export async function postGitHubComment(options) {
|
|
305
323
|
const { token, owner, repo, prNumber, commitSha, body } = options;
|
|
306
|
-
|
|
324
|
+
|
|
307
325
|
if (!token) {
|
|
308
326
|
throw new Error('GitHub token is required');
|
|
309
327
|
}
|
|
310
|
-
|
|
328
|
+
|
|
311
329
|
if (!owner || !repo) {
|
|
312
330
|
throw new Error('Repository owner and name are required');
|
|
313
331
|
}
|
|
314
|
-
|
|
332
|
+
|
|
315
333
|
if (!prNumber && !commitSha) {
|
|
316
334
|
throw new Error('Either prNumber or commitSha must be provided');
|
|
317
335
|
}
|
|
@@ -351,7 +369,7 @@ export async function postGitHubComment(options) {
|
|
|
351
369
|
*/
|
|
352
370
|
export async function updateGitHubComment(options) {
|
|
353
371
|
const { token, owner, repo, commentId, body } = options;
|
|
354
|
-
|
|
372
|
+
|
|
355
373
|
if (!token || !owner || !repo || !commentId) {
|
|
356
374
|
throw new Error('Token, owner, repo, and commentId are required');
|
|
357
375
|
}
|
|
@@ -364,7 +382,7 @@ export async function updateGitHubComment(options) {
|
|
|
364
382
|
comment_id: commentId,
|
|
365
383
|
body,
|
|
366
384
|
});
|
|
367
|
-
|
|
385
|
+
|
|
368
386
|
return response.data;
|
|
369
387
|
}
|
|
370
388
|
|
|
@@ -379,7 +397,7 @@ export async function updateGitHubComment(options) {
|
|
|
379
397
|
*/
|
|
380
398
|
export async function findExistingComment(options) {
|
|
381
399
|
const { token, owner, repo, prNumber } = options;
|
|
382
|
-
|
|
400
|
+
|
|
383
401
|
if (!token || !owner || !repo || !prNumber) {
|
|
384
402
|
return null;
|
|
385
403
|
}
|
|
@@ -410,11 +428,11 @@ export async function findExistingComment(options) {
|
|
|
410
428
|
*/
|
|
411
429
|
export async function postOrUpdateTestResults(testRunData, testCases, githubOptions) {
|
|
412
430
|
const commentBody = generateGitHubComment(testRunData, testCases);
|
|
413
|
-
|
|
431
|
+
|
|
414
432
|
// Try to find and delete existing comment to keep it at the end
|
|
415
433
|
if (githubOptions.prNumber) {
|
|
416
434
|
const existingComment = await findExistingComment(githubOptions);
|
|
417
|
-
|
|
435
|
+
|
|
418
436
|
if (existingComment) {
|
|
419
437
|
// Delete the old comment
|
|
420
438
|
const octokit = new Octokit({ auth: githubOptions.token });
|
|
@@ -425,7 +443,7 @@ export async function postOrUpdateTestResults(testRunData, testCases, githubOpti
|
|
|
425
443
|
});
|
|
426
444
|
}
|
|
427
445
|
}
|
|
428
|
-
|
|
446
|
+
|
|
429
447
|
// Always create a new comment (will be at the end of the thread)
|
|
430
448
|
return await postGitHubComment({
|
|
431
449
|
...githubOptions,
|
package/package.json
CHANGED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Usage: ./install-dev-runner.sh <instance-id>
|
|
5
|
+
INSTANCE_ID="${1:?Usage: $0 <instance-id>}"
|
|
6
|
+
AWS_REGION="${AWS_REGION:-us-east-2}"
|
|
7
|
+
|
|
8
|
+
RUNNER_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../runner" && pwd)"
|
|
9
|
+
|
|
10
|
+
echo "Packing local runner..."
|
|
11
|
+
TMPDIR=$(mktemp -d)
|
|
12
|
+
pushd "$RUNNER_DIR" > /dev/null
|
|
13
|
+
npm pack --pack-destination "$TMPDIR" > /dev/null 2>&1
|
|
14
|
+
TARBALL=$(ls "$TMPDIR"/*.tgz)
|
|
15
|
+
popd > /dev/null
|
|
16
|
+
echo "Tarball: $TARBALL"
|
|
17
|
+
|
|
18
|
+
echo "Uploading to S3..."
|
|
19
|
+
S3_KEY="runner-dev/$(date +%s)-$(openssl rand -hex 4)/runner.tgz"
|
|
20
|
+
aws s3 cp "$TARBALL" "s3://v7-transfer/${S3_KEY}" --region "$AWS_REGION" > /dev/null
|
|
21
|
+
DOWNLOAD_URL=$(aws s3 presign "s3://v7-transfer/${S3_KEY}" --expires-in 900 --region "$AWS_REGION")
|
|
22
|
+
rm -rf "$TMPDIR"
|
|
23
|
+
|
|
24
|
+
echo "Creating SSM params file..."
|
|
25
|
+
|
|
26
|
+
# Write Python script to temp file to generate valid JSON
|
|
27
|
+
PYTHON_SCRIPT=$(mktemp --suffix=.py)
|
|
28
|
+
cat > "$PYTHON_SCRIPT" << 'PYEOF'
|
|
29
|
+
import json
|
|
30
|
+
import sys
|
|
31
|
+
|
|
32
|
+
url = sys.argv[1]
|
|
33
|
+
|
|
34
|
+
commands = [
|
|
35
|
+
"Write-Host '=== Stopping runner ==='",
|
|
36
|
+
"Stop-ScheduledTask -TaskName RunTestDriverAgent -ErrorAction SilentlyContinue",
|
|
37
|
+
"Stop-Process -Name node -Force -ErrorAction SilentlyContinue",
|
|
38
|
+
"Start-Sleep -Seconds 2",
|
|
39
|
+
"Set-Location 'C:\\testdriver\\sandbox-agent'",
|
|
40
|
+
"$tarball = 'C:\\Windows\\Temp\\runner-dev.tgz'",
|
|
41
|
+
f"Invoke-WebRequest -Uri '{url}' -OutFile $tarball",
|
|
42
|
+
"Write-Host 'Tarball size:'; (Get-Item $tarball).Length",
|
|
43
|
+
"Remove-Item -Path lib -Recurse -Force -ErrorAction SilentlyContinue",
|
|
44
|
+
"tar -xzf $tarball --strip-components=1 -C .",
|
|
45
|
+
"Get-Content 'package.json' | ConvertFrom-Json | Select-Object -ExpandProperty version",
|
|
46
|
+
"Write-Host '=== Starting runner ==='",
|
|
47
|
+
"Start-ScheduledTask -TaskName RunTestDriverAgent",
|
|
48
|
+
"Start-Sleep -Seconds 3",
|
|
49
|
+
"Get-Content 'C:\\testdriver\\log.txt' -Tail 20"
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
params = {"commands": commands}
|
|
53
|
+
print(json.dumps(params))
|
|
54
|
+
PYEOF
|
|
55
|
+
|
|
56
|
+
python3 "$PYTHON_SCRIPT" "$DOWNLOAD_URL" > /tmp/ssm-install-params.json
|
|
57
|
+
rm "$PYTHON_SCRIPT"
|
|
58
|
+
|
|
59
|
+
echo "Sending SSM command..."
|
|
60
|
+
CMD_JSON=$(aws ssm send-command \
|
|
61
|
+
--region "$AWS_REGION" \
|
|
62
|
+
--instance-ids "$INSTANCE_ID" \
|
|
63
|
+
--document-name "AWS-RunPowerShellScript" \
|
|
64
|
+
--parameters "file:///tmp/ssm-install-params.json" \
|
|
65
|
+
--output json)
|
|
66
|
+
|
|
67
|
+
COMMAND_ID=$(echo "$CMD_JSON" | jq -r '.Command.CommandId')
|
|
68
|
+
echo "Command ID: $COMMAND_ID"
|
|
69
|
+
|
|
70
|
+
echo "Waiting for completion..."
|
|
71
|
+
aws ssm wait command-executed --region "$AWS_REGION" --command-id "$COMMAND_ID" --instance-id "$INSTANCE_ID" || true
|
|
72
|
+
|
|
73
|
+
echo "Getting output..."
|
|
74
|
+
aws ssm get-command-invocation \
|
|
75
|
+
--region "$AWS_REGION" \
|
|
76
|
+
--command-id "$COMMAND_ID" \
|
|
77
|
+
--instance-id "$INSTANCE_ID" \
|
|
78
|
+
--query 'StandardOutputContent' \
|
|
79
|
+
--output text
|
|
@@ -141,6 +141,140 @@ while :; do
|
|
|
141
141
|
sleep 20
|
|
142
142
|
done
|
|
143
143
|
|
|
144
|
+
# --- 4) Install/update runner ---
|
|
145
|
+
echo "Installing runner..."
|
|
146
|
+
|
|
147
|
+
# Determine environment and version
|
|
148
|
+
TD_ENV="${TD_ENV:-stable}"
|
|
149
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
150
|
+
SDK_PKG_JSON="${SCRIPT_DIR}/../../../sdk/package.json"
|
|
151
|
+
RUNNER_DIR="${SCRIPT_DIR}/../../../runner"
|
|
152
|
+
|
|
153
|
+
if [ -f "$SDK_PKG_JSON" ]; then
|
|
154
|
+
RUNNER_VERSION=$(jq -r '.version' "$SDK_PKG_JSON")
|
|
155
|
+
echo "Runner version from SDK: $RUNNER_VERSION"
|
|
156
|
+
else
|
|
157
|
+
RUNNER_VERSION="$TD_ENV"
|
|
158
|
+
echo "SDK package.json not found, using env tag: $RUNNER_VERSION"
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
if [ "$TD_ENV" = "dev" ]; then
|
|
162
|
+
echo "Dev mode: packing and uploading local runner to S3..."
|
|
163
|
+
|
|
164
|
+
# Pack local runner
|
|
165
|
+
TMPDIR=$(mktemp -d)
|
|
166
|
+
pushd "$RUNNER_DIR" > /dev/null
|
|
167
|
+
npm pack --pack-destination "$TMPDIR" > /dev/null 2>&1
|
|
168
|
+
TARBALL=$(ls "$TMPDIR"/*.tgz | head -1)
|
|
169
|
+
popd > /dev/null
|
|
170
|
+
|
|
171
|
+
# Upload to S3
|
|
172
|
+
S3_BUCKET="${AWS_BUCKET_IMAGE_TRANSFER:-v7-transfer}"
|
|
173
|
+
S3_KEY="runner-dev/$(date +%s)-$(openssl rand -hex 4)/runner.tgz"
|
|
174
|
+
aws s3 cp "$TARBALL" "s3://${S3_BUCKET}/${S3_KEY}" --region "$AWS_REGION"
|
|
175
|
+
|
|
176
|
+
# Generate presigned URL (15 min)
|
|
177
|
+
DOWNLOAD_URL=$(aws s3 presign "s3://${S3_BUCKET}/${S3_KEY}" --expires-in 900 --region "$AWS_REGION")
|
|
178
|
+
rm -rf "$TMPDIR"
|
|
179
|
+
|
|
180
|
+
# Build SSM parameters JSON in a temp file to avoid shell escaping issues with URL
|
|
181
|
+
PARAMS_FILE=$(mktemp)
|
|
182
|
+
cat > "$PARAMS_FILE" << 'PARAMS_EOF'
|
|
183
|
+
{
|
|
184
|
+
"commands": [
|
|
185
|
+
"Write-Host '=== Starting runner dev install ==='",
|
|
186
|
+
"Write-Host 'Stopping existing runner processes...'",
|
|
187
|
+
"Stop-ScheduledTask -TaskName RunTestDriverAgent -ErrorAction SilentlyContinue",
|
|
188
|
+
"Stop-Process -Name node -Force -ErrorAction SilentlyContinue",
|
|
189
|
+
"Start-Sleep -Seconds 2",
|
|
190
|
+
"Write-Host 'Current runner version:'",
|
|
191
|
+
"Get-Content 'C:\\testdriver\\sandbox-agent\\package.json' | ConvertFrom-Json | Select-Object -ExpandProperty version",
|
|
192
|
+
"Set-Location 'C:\\testdriver\\sandbox-agent'",
|
|
193
|
+
"Write-Host 'Dev mode: downloading runner from S3...'",
|
|
194
|
+
"$tarball = 'C:\\Windows\\Temp\\runner-dev.tgz'",
|
|
195
|
+
PARAMS_EOF
|
|
196
|
+
|
|
197
|
+
# Add the URL line with proper JSON escaping
|
|
198
|
+
echo " \"Invoke-WebRequest -Uri '$(echo "$DOWNLOAD_URL" | sed 's/"/\\"/g')' -OutFile \$tarball\"," >> "$PARAMS_FILE"
|
|
199
|
+
|
|
200
|
+
cat >> "$PARAMS_FILE" << 'PARAMS_EOF'
|
|
201
|
+
"Write-Host 'Downloaded tarball size:'",
|
|
202
|
+
"(Get-Item $tarball).Length",
|
|
203
|
+
"Write-Host 'Extracting runner...'",
|
|
204
|
+
"tar -xzf $tarball -C 'C:\\Windows\\Temp'",
|
|
205
|
+
"Write-Host 'Extracted package contents:'",
|
|
206
|
+
"Get-ChildItem 'C:\\Windows\\Temp\\package' -Recurse | Select-Object FullName",
|
|
207
|
+
"Write-Host 'New runner version in package:'",
|
|
208
|
+
"Get-Content 'C:\\Windows\\Temp\\package\\package.json' | ConvertFrom-Json | Select-Object -ExpandProperty version",
|
|
209
|
+
"Write-Host 'Clearing old lib folder...'",
|
|
210
|
+
"Remove-Item 'C:\\testdriver\\sandbox-agent\\lib' -Recurse -Force -ErrorAction SilentlyContinue",
|
|
211
|
+
"Write-Host 'Copying files to sandbox-agent...'",
|
|
212
|
+
"xcopy 'C:\\Windows\\Temp\\package\\*' 'C:\\testdriver\\sandbox-agent\\' /E /Y /I",
|
|
213
|
+
"Write-Host 'Files after copy:'",
|
|
214
|
+
"Get-ChildItem 'C:\\testdriver\\sandbox-agent' | Select-Object Name",
|
|
215
|
+
"Remove-Item 'C:\\Windows\\Temp\\package' -Recurse -Force -ErrorAction SilentlyContinue",
|
|
216
|
+
"Remove-Item $tarball -Force -ErrorAction SilentlyContinue",
|
|
217
|
+
"Write-Host 'Runner version after copy:'",
|
|
218
|
+
"Get-Content 'C:\\testdriver\\sandbox-agent\\package.json' | ConvertFrom-Json | Select-Object -ExpandProperty version",
|
|
219
|
+
"Write-Host 'Installing npm dependencies...'",
|
|
220
|
+
"npm install --omit=dev 2>&1 | Write-Host",
|
|
221
|
+
"Write-Host 'Final verification - ably-service.js exists:'",
|
|
222
|
+
"Test-Path 'C:\\testdriver\\sandbox-agent\\lib\\ably-service.js'",
|
|
223
|
+
"Write-Host 'Restarting RunTestDriverAgent scheduled task...'",
|
|
224
|
+
"Start-ScheduledTask -TaskName RunTestDriverAgent -ErrorAction SilentlyContinue",
|
|
225
|
+
"Write-Host '=== Runner install complete (dev) ==='"
|
|
226
|
+
]
|
|
227
|
+
}
|
|
228
|
+
PARAMS_EOF
|
|
229
|
+
|
|
230
|
+
echo "Sending SSM command to download and install runner from S3..."
|
|
231
|
+
INSTALL_CMD=$(aws ssm send-command \
|
|
232
|
+
--region "$AWS_REGION" \
|
|
233
|
+
--instance-ids "$INSTANCE_ID" \
|
|
234
|
+
--document-name "AWS-RunPowerShellScript" \
|
|
235
|
+
--parameters "file://$PARAMS_FILE" \
|
|
236
|
+
--timeout-seconds 180 \
|
|
237
|
+
--output json)
|
|
238
|
+
rm -f "$PARAMS_FILE"
|
|
239
|
+
else
|
|
240
|
+
echo "Installing @testdriverai/runner@${RUNNER_VERSION} via npm..."
|
|
241
|
+
INSTALL_CMD=$(aws ssm send-command \
|
|
242
|
+
--region "$AWS_REGION" \
|
|
243
|
+
--instance-ids "$INSTANCE_ID" \
|
|
244
|
+
--document-name "AWS-RunPowerShellScript" \
|
|
245
|
+
--parameters "commands=[
|
|
246
|
+
\"Set-Location 'C:\\\\testdriver\\\\sandbox-agent'\",
|
|
247
|
+
\"Write-Host 'Installing @testdriverai/runner@${RUNNER_VERSION}...'\",
|
|
248
|
+
\"npm install @testdriverai/runner@${RUNNER_VERSION} --omit=dev 2>&1 | Write-Host\",
|
|
249
|
+
\"Write-Host 'Stopping runner (config not yet provisioned)...'\",
|
|
250
|
+
\"Stop-Process -Name node -Force -ErrorAction SilentlyContinue\",
|
|
251
|
+
\"Stop-ScheduledTask -TaskName RunTestDriverAgent -ErrorAction SilentlyContinue\",
|
|
252
|
+
\"Start-Sleep -Seconds 2\",
|
|
253
|
+
\"Start-ScheduledTask -TaskName RunTestDriverAgent -ErrorAction SilentlyContinue\",
|
|
254
|
+
\"Write-Host 'Runner install complete'\"
|
|
255
|
+
]" \
|
|
256
|
+
--timeout-seconds 120 \
|
|
257
|
+
--output json)
|
|
258
|
+
fi
|
|
259
|
+
|
|
260
|
+
INSTALL_CMD_ID=$(jq -r '.Command.CommandId' <<<"$INSTALL_CMD")
|
|
261
|
+
echo "Runner install command sent (Command ID: $INSTALL_CMD_ID)"
|
|
262
|
+
|
|
263
|
+
# Wait for install to complete
|
|
264
|
+
echo "Waiting for runner install to complete..."
|
|
265
|
+
if aws ssm wait command-executed --region "$AWS_REGION" --command-id "$INSTALL_CMD_ID" --instance-id "$INSTANCE_ID" 2>/dev/null; then
|
|
266
|
+
echo "✓ Runner install succeeded"
|
|
267
|
+
else
|
|
268
|
+
INSTALL_STATUS=$(aws ssm get-command-invocation \
|
|
269
|
+
--region "$AWS_REGION" \
|
|
270
|
+
--command-id "$INSTALL_CMD_ID" \
|
|
271
|
+
--instance-id "$INSTANCE_ID" \
|
|
272
|
+
--output json 2>/dev/null || echo '{}')
|
|
273
|
+
echo "⚠ Runner install status: $(jq -r '.Status // "Unknown"' <<<"$INSTALL_STATUS")"
|
|
274
|
+
echo "Output: $(jq -r '.StandardOutputContent // "No output"' <<<"$INSTALL_STATUS" | head -20)"
|
|
275
|
+
echo "Errors: $(jq -r '.StandardErrorContent // "No errors"' <<<"$INSTALL_STATUS" | head -10)"
|
|
276
|
+
fi
|
|
277
|
+
|
|
144
278
|
echo "Getting Public IP..."
|
|
145
279
|
|
|
146
280
|
# --- 5) Get instance Public IP ---
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vitest config for runner/packer tests.
|
|
3
|
+
* Lives under sdk/ so vitest resolves from sdk/node_modules,
|
|
4
|
+
* but uses shared/resolve-env.js for environment variable loading.
|
|
5
|
+
*/
|
|
6
|
+
import { defineConfig } from "vitest/config";
|
|
7
|
+
import { createRequire } from "module";
|
|
8
|
+
import { dirname, resolve } from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const monoRoot = resolve(__dirname, "..");
|
|
13
|
+
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
const { resolveEnv } = require("../shared/resolve-env");
|
|
16
|
+
|
|
17
|
+
const plan = process.env.TD_PLAN || "enterprise";
|
|
18
|
+
const env = process.env.TD_ENV || "dev";
|
|
19
|
+
const resolved = resolveEnv(env, plan);
|
|
20
|
+
|
|
21
|
+
// Apply to the main process so test code sees the vars immediately
|
|
22
|
+
Object.assign(process.env, resolved);
|
|
23
|
+
|
|
24
|
+
export default defineConfig({
|
|
25
|
+
test: {
|
|
26
|
+
root: monoRoot,
|
|
27
|
+
testTimeout: 900_000, // 15 min per test
|
|
28
|
+
hookTimeout: 2_400_000, // 40 min for beforeAll (AMI build + spawn)
|
|
29
|
+
reporters: ["default"],
|
|
30
|
+
include: ["runner/packer/test/**/*.test.mjs"],
|
|
31
|
+
env: resolved,
|
|
32
|
+
},
|
|
33
|
+
});
|