testdriverai 7.10.0-canary.0 → 7.10.1-test
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/.claude-plugin/marketplace.json +17 -0
- package/agent/index.js +116 -71
- package/agent/interface.js +13 -1
- package/agent/lib/commands.js +35 -15
- package/agent/lib/provision-commands.js +92 -11
- package/agent/lib/sandbox.js +263 -30
- package/agent/lib/sdk.js +16 -12
- package/agent/lib/system.js +107 -31
- package/ai/.claude-plugin/plugin.json +89 -4
- package/ai/agents/testdriver.md +4 -8
- package/ai/skills/testdriver-assert/SKILL.md +0 -1
- package/ai/skills/testdriver-aws-setup/SKILL.md +1 -1
- package/ai/skills/testdriver-caching/SKILL.md +13 -6
- package/ai/skills/testdriver-ci-cd/SKILL.md +57 -14
- package/ai/skills/testdriver-client/SKILL.md +10 -6
- package/ai/skills/testdriver-customizing-devices/SKILL.md +7 -5
- package/ai/skills/testdriver-dashcam/SKILL.md +36 -3
- package/ai/skills/testdriver-debugging-with-screenshots/SKILL.md +28 -4
- package/ai/skills/testdriver-device-config/SKILL.md +5 -5
- package/ai/skills/testdriver-find/SKILL.md +50 -11
- package/ai/skills/testdriver-generating-tests/SKILL.md +163 -8
- package/ai/skills/testdriver-hosted/SKILL.md +1 -1
- package/ai/skills/testdriver-machine-setup/SKILL.md +331 -0
- package/ai/skills/testdriver-making-assertions/SKILL.md +78 -2
- package/ai/skills/testdriver-mcp-workflow/SKILL.md +2 -2
- package/ai/skills/testdriver-parse/SKILL.md +1 -1
- package/ai/skills/testdriver-performing-actions/SKILL.md +97 -3
- package/ai/skills/testdriver-provision/SKILL.md +0 -20
- package/ai/skills/testdriver-quickstart/SKILL.md +299 -21
- package/ai/skills/testdriver-reusable-code/SKILL.md +3 -3
- package/ai/skills/testdriver-running-tests/SKILL.md +1 -1
- package/ai/skills/testdriver-screenshot/SKILL.md +5 -5
- package/ai/skills/testdriver-scroll/SKILL.md +30 -160
- package/ai/skills/testdriver-test-writer/SKILL.md +3 -5
- package/ai/skills/testdriver-testdriver/SKILL.md +3 -7
- package/ai/skills/testdriver-wait/SKILL.md +1 -1
- package/{docs/v7/ai.mdx → ai/skills/testdriver:ai/SKILL.md} +3 -4
- package/ai/skills/testdriver:assert/SKILL.md +315 -0
- package/ai/skills/testdriver:aws-setup/SKILL.md +448 -0
- package/ai/skills/testdriver:cache/SKILL.md +221 -0
- package/ai/skills/testdriver:caching/SKILL.md +124 -0
- package/ai/skills/testdriver:captcha/SKILL.md +158 -0
- package/ai/skills/testdriver:ci-cd/SKILL.md +659 -0
- package/ai/skills/testdriver:click/SKILL.md +286 -0
- package/ai/skills/testdriver:client/SKILL.md +477 -0
- package/ai/skills/testdriver:customizing-devices/SKILL.md +319 -0
- package/ai/skills/testdriver:dashcam/SKILL.md +451 -0
- package/ai/skills/testdriver:debugging-with-screenshots/SKILL.md +415 -0
- package/{docs/v7/device-config.mdx → ai/skills/testdriver:device-config/SKILL.md} +8 -8
- package/ai/skills/testdriver:double-click/SKILL.md +102 -0
- package/ai/skills/testdriver:elements/SKILL.md +605 -0
- package/ai/skills/testdriver:enterprise/SKILL.md +7 -0
- package/ai/skills/testdriver:errors/SKILL.md +246 -0
- package/ai/skills/testdriver:events/SKILL.md +356 -0
- package/ai/skills/testdriver:exec/SKILL.md +317 -0
- package/ai/skills/testdriver:find/SKILL.md +860 -0
- package/ai/skills/testdriver:focus-application/SKILL.md +293 -0
- package/ai/skills/testdriver:generating-tests/SKILL.md +25 -0
- package/ai/skills/testdriver:hosted/SKILL.md +156 -0
- package/ai/skills/testdriver:hover/SKILL.md +278 -0
- package/{docs/v7/locating-elements.mdx → ai/skills/testdriver:locating-elements/SKILL.md} +3 -3
- package/ai/skills/testdriver:making-assertions/SKILL.md +32 -0
- package/ai/skills/testdriver:mcp/SKILL.md +7 -0
- package/ai/skills/testdriver:mouse-down/SKILL.md +161 -0
- package/ai/skills/testdriver:mouse-up/SKILL.md +164 -0
- package/ai/skills/testdriver:parse/SKILL.md +236 -0
- package/ai/skills/testdriver:performing-actions/SKILL.md +53 -0
- package/ai/skills/testdriver:press-keys/SKILL.md +348 -0
- package/ai/skills/testdriver:provision/SKILL.md +331 -0
- package/ai/skills/testdriver:quickstart/SKILL.md +172 -0
- package/ai/skills/testdriver:redraw/SKILL.md +214 -0
- package/ai/skills/testdriver:reusable-code/SKILL.md +249 -0
- package/ai/skills/testdriver:right-click/SKILL.md +123 -0
- package/{docs/v7/running-tests.mdx → ai/skills/testdriver:running-tests/SKILL.md} +4 -4
- package/ai/skills/testdriver:screenshot/SKILL.md +248 -0
- package/ai/skills/testdriver:screenshots/SKILL.md +184 -0
- package/ai/skills/testdriver:scroll/SKILL.md +205 -0
- package/ai/skills/testdriver:secrets/SKILL.md +115 -0
- package/ai/skills/testdriver:self-hosted/SKILL.md +147 -0
- package/ai/skills/testdriver:test-results-json/SKILL.md +257 -0
- package/ai/skills/testdriver:testdriver/SKILL.md +624 -0
- package/ai/skills/testdriver:type/SKILL.md +357 -0
- package/ai/skills/testdriver:variables/SKILL.md +111 -0
- package/ai/skills/testdriver:wait/SKILL.md +50 -0
- package/{docs/v7/waiting-for-elements.mdx → ai/skills/testdriver:waiting-for-elements/SKILL.md} +3 -3
- package/ai/skills/testdriver:what-is-testdriver/SKILL.md +54 -0
- package/debugger/index.html +12 -0
- package/docs/_data/examples-manifest.json +54 -46
- package/docs/_data/examples-manifest.schema.json +12 -2
- package/docs/_scripts/extract-example-urls.js +21 -3
- package/docs/_scripts/generate-examples.js +64 -16
- package/docs/changelog.mdx +29 -2
- package/docs/claude-mcp-plugin.mdx +65 -18
- package/docs/docs.json +16 -33
- package/docs/snippets/tests/scroll-yaml.mdx +2 -2
- package/docs/v6/commands/scroll-until-image.mdx +1 -1
- package/docs/v6/commands/scroll-until-text.mdx +2 -2
- package/docs/v6/commands/scroll.mdx +2 -2
- package/docs/v7/_drafts/agents.mdx +0 -4
- package/docs/v7/_drafts/best-practices.mdx +0 -12
- package/docs/v7/_drafts/commands/scroll-until-image.mdx +1 -1
- package/docs/v7/_drafts/commands/scroll-until-text.mdx +2 -2
- package/docs/v7/_drafts/commands/scroll.mdx +1 -1
- package/docs/v7/_drafts/configuration.mdx +0 -12
- package/docs/v7/_drafts/faq.mdx +2 -10
- package/docs/v7/_drafts/readme.mdx +0 -1
- package/docs/v7/_drafts/troubleshooting.mdx +1 -4
- package/docs/v7/assert.mdx +0 -1
- package/docs/v7/aws-setup.mdx +1 -1
- package/docs/v7/caching.mdx +14 -10
- package/docs/v7/ci-cd.mdx +98 -14
- package/docs/v7/client.mdx +10 -6
- package/docs/v7/copilot/auto-healing.mdx +167 -18
- package/docs/v7/copilot/running-tests.mdx +915 -54
- package/docs/v7/customizing-devices.mdx +7 -5
- package/docs/v7/dashcam.mdx +36 -3
- package/docs/v7/debugging-with-screenshots.mdx +31 -7
- package/docs/v7/examples/ai.mdx +13 -8
- package/docs/v7/examples/assert.mdx +1 -1
- package/docs/v7/examples/chrome-extension.mdx +30 -13
- package/docs/v7/examples/element-not-found.mdx +1 -1
- package/docs/v7/examples/findall-coffee-icons.mdx +3 -2
- package/docs/v7/examples/formatted-logging.mdx +74 -0
- package/docs/v7/examples/hover-image.mdx +1 -1
- package/docs/v7/examples/hover-text-with-description.mdx +2 -2
- 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 +27 -27
- package/docs/v7/examples/match-image.mdx +1 -1
- package/docs/v7/examples/parse.mdx +3 -2
- package/docs/v7/examples/press-keys.mdx +2 -2
- package/docs/v7/examples/scroll-keyboard.mdx +2 -2
- package/docs/v7/examples/scroll-until-text.mdx +1 -1
- package/docs/v7/examples/scroll.mdx +1 -1
- package/docs/v7/examples/type.mdx +1 -1
- package/docs/v7/find.mdx +50 -11
- package/docs/v7/generating-tests.mdx +166 -10
- package/docs/v7/hosted.mdx +1 -1
- package/docs/v7/making-assertions.mdx +81 -4
- package/docs/v7/parse.mdx +1 -1
- package/docs/v7/performing-actions.mdx +100 -5
- package/docs/v7/provision.mdx +0 -20
- package/docs/v7/quickstart.mdx +299 -21
- package/docs/v7/reusable-code.mdx +3 -3
- package/docs/v7/screenshot.mdx +5 -5
- package/docs/v7/scroll.mdx +30 -160
- package/docs/v7/wait.mdx +1 -1
- package/examples/chrome-extension.test.mjs +29 -12
- package/examples/config.mjs +1 -1
- package/examples/launch-vscode-linux.test.mjs +25 -25
- package/interfaces/cli/commands/init.js +84 -2
- package/interfaces/vitest-plugin.mjs +51 -61
- package/lib/core/Dashcam.js +43 -2
- package/lib/core/index.d.ts +13 -1
- package/lib/github-comment.mjs +11 -5
- package/lib/init-project.js +98 -44
- package/lib/install-clients.js +470 -0
- package/lib/provision.js +749 -0
- package/lib/resolve-channel.js +2 -2
- package/lib/sentry.js +15 -2
- package/lib/vitest/hooks.mjs +44 -34
- package/mcp-server/README.md +32 -0
- package/mcp-server/dist/core/actions.d.ts +176 -0
- package/mcp-server/dist/core/actions.js +753 -0
- package/mcp-server/dist/env-utils.d.ts +45 -0
- package/mcp-server/dist/env-utils.js +63 -0
- package/mcp-server/dist/provision-types.d.ts +6 -2
- package/mcp-server/dist/provision-types.js +3 -1
- package/mcp-server/dist/server.mjs +449 -751
- package/mcp-server/package-lock.json +384 -1
- package/mcp-server/package.json +5 -2
- package/mcp-server/src/core/actions.ts +912 -0
- package/mcp-server/src/env-utils.test.ts +82 -0
- package/mcp-server/src/env-utils.ts +77 -0
- package/mcp-server/src/provision-types.ts +4 -1
- package/mcp-server/src/server.ts +533 -954
- package/mcp-server/tsconfig.json +1 -1
- package/mcp-server/vitest.config.ts +7 -0
- package/package.json +15 -3
- package/sdk.d.ts +11 -9
- package/sdk.js +116 -746
- package/setup/aws/spawn-runner.sh +24 -3
- package/docs/v7/_drafts/caching-ai.mdx +0 -215
- package/docs/v7/_drafts/caching.mdx +0 -366
- package/docs/v7/_drafts/prompt-cache.mdx +0 -200
- package/docs/v7/copilot/creating-tests.mdx +0 -156
- package/docs/v7/copilot/github.mdx +0 -143
- package/docs/v7/copilot/setup.mdx +0 -143
- package/docs/v7/examples/exec-output.mdx +0 -84
- package/docs/v7/examples/exec-pwsh.mdx +0 -82
- package/docs/v7/examples/focus-window.mdx +0 -61
- package/docs/v7/examples/scroll-until-image.mdx +0 -82
- package/docs/v7/examples/windows-installer.mdx +0 -95
- package/examples/formatted-logging.test.mjs +0 -27
- package/examples/match-image.test.mjs +0 -55
- package/manual/exec-stream-logs.test.mjs +0 -25
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "testdriver",
|
|
3
|
+
"version": "7.9.54-test",
|
|
4
|
+
"description": "TestDriver.ai plugins for Claude Code — MCP server, agent, and skills for building natural-language end-to-end tests.",
|
|
5
|
+
"repository": "https://github.com/testdriverai/testdriverai",
|
|
6
|
+
"owner": {
|
|
7
|
+
"name": "TestDriver.ai",
|
|
8
|
+
"url": "https://testdriver.ai"
|
|
9
|
+
},
|
|
10
|
+
"plugins": [
|
|
11
|
+
{
|
|
12
|
+
"name": "testdriver",
|
|
13
|
+
"source": "./ai",
|
|
14
|
+
"description": "TestDriver.ai MCP server, TestDriver expert agent, and skills for authoring computer-use end-to-end tests with Vitest."
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
package/agent/index.js
CHANGED
|
@@ -494,65 +494,89 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
494
494
|
);
|
|
495
495
|
}
|
|
496
496
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
497
|
+
const sentry = require("../lib/sentry");
|
|
498
|
+
return sentry.withSpan(
|
|
499
|
+
{
|
|
500
|
+
name: `command.${commandName}`,
|
|
501
|
+
op: "command",
|
|
502
|
+
attributes: { "command.name": commandName, "command.depth": depth },
|
|
503
|
+
},
|
|
504
|
+
async (span) => {
|
|
505
|
+
try {
|
|
506
|
+
let response;
|
|
507
|
+
|
|
508
|
+
// "run" and "if" commands are special meta commands
|
|
509
|
+
// that change the flow of execution
|
|
510
|
+
if (command.command == "run") {
|
|
511
|
+
response = await this.embed(command.file, depth, pushToHistory);
|
|
512
|
+
} else if (command.command == "if") {
|
|
513
|
+
response = await this.iffy(
|
|
514
|
+
command.condition,
|
|
515
|
+
command.then,
|
|
516
|
+
command.else,
|
|
517
|
+
depth,
|
|
518
|
+
);
|
|
519
|
+
} else {
|
|
520
|
+
response = await this.commander.run(command, depth);
|
|
521
|
+
}
|
|
514
522
|
|
|
515
|
-
|
|
516
|
-
|
|
523
|
+
const endTime = Date.now();
|
|
524
|
+
const duration = endTime - startTime;
|
|
517
525
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
depth,
|
|
522
|
-
data: command,
|
|
523
|
-
duration,
|
|
524
|
-
response,
|
|
525
|
-
timestamp: endTime,
|
|
526
|
-
sourcePosition: sourcePosition,
|
|
527
|
-
});
|
|
526
|
+
if (span) {
|
|
527
|
+
span.setStatus({ code: 1 }); // OK
|
|
528
|
+
}
|
|
528
529
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
530
|
+
// Emit command success event with source mapping
|
|
531
|
+
this.emitter.emit(events.command.success, {
|
|
532
|
+
command: commandName,
|
|
533
|
+
depth,
|
|
534
|
+
data: command,
|
|
535
|
+
duration,
|
|
536
|
+
response,
|
|
537
|
+
timestamp: endTime,
|
|
538
|
+
sourcePosition: sourcePosition,
|
|
539
|
+
});
|
|
536
540
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
541
|
+
// if the result of a command contains more commands, we perform the process again
|
|
542
|
+
if (response && typeof response === "string") {
|
|
543
|
+
return await this.actOnMarkdown(
|
|
544
|
+
response,
|
|
545
|
+
depth,
|
|
546
|
+
false,
|
|
547
|
+
false,
|
|
548
|
+
false,
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
} catch (error) {
|
|
552
|
+
const endTime = Date.now();
|
|
553
|
+
const duration = endTime - startTime;
|
|
547
554
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
555
|
+
if (span) {
|
|
556
|
+
span.setStatus({ code: 2, message: error.message });
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Emit command error event with source mapping
|
|
560
|
+
this.emitter.emit(events.command.error, {
|
|
561
|
+
command: commandName,
|
|
562
|
+
depth,
|
|
563
|
+
data: command,
|
|
564
|
+
error: error.message,
|
|
565
|
+
duration,
|
|
566
|
+
timestamp: endTime,
|
|
567
|
+
sourcePosition: sourcePosition,
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
return await this.haveAIResolveError(
|
|
571
|
+
error,
|
|
572
|
+
yaml.dump({ commands: [yml] }),
|
|
573
|
+
depth,
|
|
574
|
+
true,
|
|
575
|
+
shouldSave,
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
},
|
|
579
|
+
);
|
|
556
580
|
}
|
|
557
581
|
|
|
558
582
|
async executeCommands(
|
|
@@ -1968,29 +1992,50 @@ ${regression}
|
|
|
1968
1992
|
// Write session file for IDE preview (VSCode extension watches for these)
|
|
1969
1993
|
writeIdeSessionFile(debuggerUrl, data) {
|
|
1970
1994
|
const fs = require("fs");
|
|
1995
|
+
const os = require("os");
|
|
1971
1996
|
const path = require("path");
|
|
1972
1997
|
|
|
1973
|
-
|
|
1974
|
-
|
|
1998
|
+
// Base dir for the preview file. `process.cwd()` is read-only in some hosts
|
|
1999
|
+
// (e.g. Vercel/Lambda serverless, where cwd is /var/task), which makes the
|
|
2000
|
+
// mkdir below throw. Allow an explicit override and otherwise fall back to a
|
|
2001
|
+
// writable temp dir, so a preview write never blocks the session.
|
|
2002
|
+
const baseDir =
|
|
2003
|
+
process.env.TD_PREVIEWS_DIR ||
|
|
2004
|
+
(this.config && this.config.TD_PREVIEWS_DIR) ||
|
|
2005
|
+
process.cwd();
|
|
2006
|
+
const previewsDir = path.join(baseDir, ".testdriver", ".previews");
|
|
1975
2007
|
|
|
1976
|
-
|
|
1977
|
-
if (!fs.existsSync(previewsDir)) {
|
|
1978
|
-
fs.mkdirSync(previewsDir, { recursive: true });
|
|
1979
|
-
}
|
|
2008
|
+
const sessionId = `${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
1980
2009
|
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
2010
|
+
// The IDE preview file is a best-effort convenience (the VSCode extension
|
|
2011
|
+
// watches for it). It must never break session start — if the target dir
|
|
2012
|
+
// isn't writable, fall back to os.tmpdir(), and if that also fails, log and
|
|
2013
|
+
// move on rather than throwing out of the provisioning path.
|
|
2014
|
+
try {
|
|
2015
|
+
const sessionData = {
|
|
2016
|
+
sessionId,
|
|
2017
|
+
debuggerUrl,
|
|
2018
|
+
resolution: Array.isArray(data.resolution) ? data.resolution : (data.resolution ? data.resolution.split("x").map(Number) : [1920, 1080]),
|
|
2019
|
+
testFile: data.testFile || this.testFile || null,
|
|
2020
|
+
os: data.os || this.sandboxOs || "linux",
|
|
2021
|
+
timestamp: Date.now(),
|
|
2022
|
+
};
|
|
1989
2023
|
|
|
1990
|
-
|
|
1991
|
-
|
|
2024
|
+
let dir = previewsDir;
|
|
2025
|
+
try {
|
|
2026
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
2027
|
+
} catch {
|
|
2028
|
+
// cwd/base wasn't writable — retry under the OS temp dir.
|
|
2029
|
+
dir = path.join(os.tmpdir(), ".testdriver", ".previews");
|
|
2030
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
2031
|
+
}
|
|
1992
2032
|
|
|
1993
|
-
|
|
2033
|
+
const filePath = path.join(dir, `${sessionId}.json`);
|
|
2034
|
+
fs.writeFileSync(filePath, JSON.stringify(sessionData, null, 2));
|
|
2035
|
+
logger.log(`IDE preview session written to ${filePath}`);
|
|
2036
|
+
} catch (error) {
|
|
2037
|
+
logger.log(`Skipped IDE preview session file (non-fatal): ${error.message}`);
|
|
2038
|
+
}
|
|
1994
2039
|
}
|
|
1995
2040
|
|
|
1996
2041
|
// Find the VS Code instance that contains the test file
|
package/agent/interface.js
CHANGED
|
@@ -10,7 +10,19 @@ function createCommandDefinitions(agent) {
|
|
|
10
10
|
init: {
|
|
11
11
|
description: "Initialize a new TestDriver project with Vitest SDK examples",
|
|
12
12
|
args: {},
|
|
13
|
-
flags: {
|
|
13
|
+
flags: {
|
|
14
|
+
client: Flags.string({
|
|
15
|
+
description:
|
|
16
|
+
"AI client(s) to install into (comma-separated, or 'all'). e.g. --client claude-code,cursor. Omit for an interactive picker.",
|
|
17
|
+
multiple: false,
|
|
18
|
+
}),
|
|
19
|
+
"sample-test": Flags.boolean({
|
|
20
|
+
description:
|
|
21
|
+
"Scaffold the example test files (tests/example.test.js + tests/login.js). Use --no-sample-test to skip them (e.g. when an agent writes its own tests).",
|
|
22
|
+
default: true,
|
|
23
|
+
allowNo: true,
|
|
24
|
+
}),
|
|
25
|
+
},
|
|
14
26
|
handler: async () => {
|
|
15
27
|
// This handler is special - it doesn't need an agent instance
|
|
16
28
|
// It just scaffolds files, so it will be handled by the CLI command
|
package/agent/lib/commands.js
CHANGED
|
@@ -302,10 +302,12 @@ const createCommands = (
|
|
|
302
302
|
`🔍 assert() threshold: ${threshold} (cache ${threshold < 0 ? "DISABLED" : "ENABLED"}${cacheKey ? `, cacheKey: ${cacheKey.substring(0, 8)}...` : ""})`,
|
|
303
303
|
);
|
|
304
304
|
|
|
305
|
-
// Use v7 endpoint for assert with caching support
|
|
305
|
+
// Use v7 endpoint for assert with caching support.
|
|
306
|
+
// captureScreenImage returns { imageKey } (fast S3-key path) or { image }
|
|
307
|
+
// (base64 fallback) — see system.captureScreenImage.
|
|
306
308
|
let response = await sdk.req("assert", {
|
|
307
309
|
expect: assertion,
|
|
308
|
-
|
|
310
|
+
...(await system.captureScreenImage()),
|
|
309
311
|
threshold,
|
|
310
312
|
cacheKey,
|
|
311
313
|
os,
|
|
@@ -815,7 +817,7 @@ const createCommands = (
|
|
|
815
817
|
|
|
816
818
|
let response = await sdk.req("find", {
|
|
817
819
|
element: description,
|
|
818
|
-
|
|
820
|
+
...(await system.captureScreenImage()),
|
|
819
821
|
});
|
|
820
822
|
|
|
821
823
|
if (!response || !response.coordinates) {
|
|
@@ -855,7 +857,7 @@ const createCommands = (
|
|
|
855
857
|
|
|
856
858
|
let response = await sdk.req("find", {
|
|
857
859
|
element: description,
|
|
858
|
-
|
|
860
|
+
...(await system.captureScreenImage()),
|
|
859
861
|
});
|
|
860
862
|
|
|
861
863
|
if (!response || !response.coordinates) {
|
|
@@ -1211,7 +1213,7 @@ const createCommands = (
|
|
|
1211
1213
|
while (durationPassed < timeout && !passed) {
|
|
1212
1214
|
const response = await sdk.req("find", {
|
|
1213
1215
|
element: text,
|
|
1214
|
-
|
|
1216
|
+
...(await system.captureScreenImage()),
|
|
1215
1217
|
});
|
|
1216
1218
|
|
|
1217
1219
|
passed = !!(response && response.coordinates);
|
|
@@ -1304,7 +1306,7 @@ const createCommands = (
|
|
|
1304
1306
|
while (scrollDistance <= maxDistance && !passed) {
|
|
1305
1307
|
const response = await sdk.req("find", {
|
|
1306
1308
|
element: text,
|
|
1307
|
-
|
|
1309
|
+
...(await system.captureScreenImage()),
|
|
1308
1310
|
});
|
|
1309
1311
|
|
|
1310
1312
|
passed = !!(response && response.coordinates);
|
|
@@ -1531,9 +1533,11 @@ const createCommands = (
|
|
|
1531
1533
|
emitter.emit(events.log.narration, formatter.getPrefix("action") + " " + theme.cyan.bold("Exec") + " " + theme.magenta(`[${language}]`), true);
|
|
1532
1534
|
|
|
1533
1535
|
// Log nested command details (truncate to first line)
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1536
|
+
if (!silent) {
|
|
1537
|
+
const firstLine = code.split('\n')[0];
|
|
1538
|
+
const codeDisplay = code.includes('\n') ? firstLine + '...' : firstLine;
|
|
1539
|
+
emitter.emit(events.log.log, formatter.formatCodeLine(codeDisplay));
|
|
1540
|
+
}
|
|
1537
1541
|
|
|
1538
1542
|
let plat = system.platform();
|
|
1539
1543
|
|
|
@@ -1565,11 +1569,26 @@ const createCommands = (
|
|
|
1565
1569
|
let result = null;
|
|
1566
1570
|
|
|
1567
1571
|
const execTimeout = timeout || 300000;
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1572
|
+
|
|
1573
|
+
// Stream output chunks in real-time as they arrive from the runner
|
|
1574
|
+
let streamedOutput = false;
|
|
1575
|
+
const onExecOutput = ({ chunk }) => {
|
|
1576
|
+
if (!silent && chunk) {
|
|
1577
|
+
emitter.emit(events.log.log, theme.dim(chunk), true);
|
|
1578
|
+
streamedOutput = true;
|
|
1579
|
+
}
|
|
1580
|
+
};
|
|
1581
|
+
emitter.on(events.exec.output, onExecOutput);
|
|
1582
|
+
|
|
1583
|
+
try {
|
|
1584
|
+
result = await sandbox.send({
|
|
1585
|
+
type: "commands.run",
|
|
1586
|
+
command: code,
|
|
1587
|
+
timeout: execTimeout,
|
|
1588
|
+
}, execTimeout);
|
|
1589
|
+
} finally {
|
|
1590
|
+
emitter.off(events.exec.output, onExecOutput);
|
|
1591
|
+
}
|
|
1573
1592
|
|
|
1574
1593
|
const execActionEndTime = Date.now();
|
|
1575
1594
|
const execDuration = execActionEndTime - execActionLogStart;
|
|
@@ -1595,7 +1614,8 @@ const createCommands = (
|
|
|
1595
1614
|
true,
|
|
1596
1615
|
);
|
|
1597
1616
|
|
|
1598
|
-
if
|
|
1617
|
+
// Skip stdout log if already streamed in real-time to avoid duplication
|
|
1618
|
+
if (!silent && !streamedOutput && result.out?.stdout) {
|
|
1599
1619
|
emitter.emit(events.log.log, theme.dim(` stdout:`), true);
|
|
1600
1620
|
emitter.emit(events.log.log, theme.dim(` ${result.out.stdout}`), true);
|
|
1601
1621
|
}
|
|
@@ -13,6 +13,24 @@
|
|
|
13
13
|
|
|
14
14
|
'use strict';
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Extract the major.minor portion of a semver-ish version string.
|
|
18
|
+
*
|
|
19
|
+
* Examples:
|
|
20
|
+
* '7.9.59' -> '7.9'
|
|
21
|
+
* '7.9.59-test' -> '7.9'
|
|
22
|
+
* '7.10.0-canary.3'-> '7.10'
|
|
23
|
+
* '' -> ''
|
|
24
|
+
*
|
|
25
|
+
* @param {string} version
|
|
26
|
+
* @returns {string}
|
|
27
|
+
*/
|
|
28
|
+
function majorMinor(version) {
|
|
29
|
+
if (!version || typeof version !== 'string') return '';
|
|
30
|
+
var m = version.match(/^(\d+)\.(\d+)/);
|
|
31
|
+
return m ? m[1] + '.' + m[2] : '';
|
|
32
|
+
}
|
|
33
|
+
|
|
16
34
|
/**
|
|
17
35
|
* Build the agent config object written to the sandbox.
|
|
18
36
|
*
|
|
@@ -25,9 +43,10 @@
|
|
|
25
43
|
* @param {string} [opts.sentryChannel]
|
|
26
44
|
* @param {Object} opts.ablyToken - Ably token object
|
|
27
45
|
* @param {string} opts.channelName - Ably channel name
|
|
46
|
+
* @param {string} [opts.sessionId] - Session ID for distributed tracing (traceId = MD5(sessionId))
|
|
28
47
|
* @returns {Object} Agent config to serialize as JSON
|
|
29
48
|
*/
|
|
30
|
-
function buildAgentConfig({ sandboxId, apiRoot, apiKey, sentryDsn, sentryEnvironment, sentryChannel, ablyToken, channelName }) {
|
|
49
|
+
function buildAgentConfig({ sandboxId, apiRoot, apiKey, sentryDsn, sentryEnvironment, sentryChannel, ablyToken, channelName, sessionId }) {
|
|
31
50
|
return {
|
|
32
51
|
sandboxId,
|
|
33
52
|
apiRoot,
|
|
@@ -35,6 +54,7 @@ function buildAgentConfig({ sandboxId, apiRoot, apiKey, sentryDsn, sentryEnviron
|
|
|
35
54
|
sentryDsn: sentryDsn || undefined,
|
|
36
55
|
sentryEnvironment: sentryEnvironment || 'production',
|
|
37
56
|
sentryChannel: sentryChannel || undefined,
|
|
57
|
+
sessionId: sessionId || undefined,
|
|
38
58
|
ably: {
|
|
39
59
|
token: ablyToken,
|
|
40
60
|
channel: channelName,
|
|
@@ -54,9 +74,10 @@ function buildAgentConfig({ sandboxId, apiRoot, apiKey, sentryDsn, sentryEnviron
|
|
|
54
74
|
* @param {string} opts.configJson - JSON.stringify'd agent config
|
|
55
75
|
* @param {string} opts.sandboxId - For logging
|
|
56
76
|
* @param {string} [opts.s3DownloadUrl] - S3 pre-signed URL for dev/test (omit for npm install)
|
|
77
|
+
* @param {string} [opts.imageVersion] - Version baked into the AMI (skip npm install when it matches)
|
|
57
78
|
* @returns {string[]} Array of PowerShell command strings
|
|
58
79
|
*/
|
|
59
|
-
function windowsProvisionCommands({ channel, configJson, sandboxId, s3DownloadUrl }) {
|
|
80
|
+
function windowsProvisionCommands({ channel, configJson, sandboxId, s3DownloadUrl, imageVersion }) {
|
|
60
81
|
var useS3 = (channel === 'dev' || channel === 'test') && s3DownloadUrl;
|
|
61
82
|
var commands = [];
|
|
62
83
|
|
|
@@ -92,11 +113,40 @@ function windowsProvisionCommands({ channel, configJson, sandboxId, s3DownloadUr
|
|
|
92
113
|
// Canary/stable (or dev/test without S3 URL): npm install by dist-tag
|
|
93
114
|
agentScript = 'node_modules/@testdriverai/runner/sandbox-agent.js';
|
|
94
115
|
var runnerTag = channel === 'stable' ? 'latest' : channel;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
)
|
|
116
|
+
|
|
117
|
+
// If we know the image version, check whether the baked-in runner already
|
|
118
|
+
// matches — skip npm install entirely when it does (saves 5-15s per launch).
|
|
119
|
+
// Only the major.minor portion is compared (patch & prerelease suffix are
|
|
120
|
+
// ignored) so trivial patch bumps don't trigger an unnecessary npm install.
|
|
121
|
+
// Emits a structured RUNNER_VERSION_CHECK line so SSM output / runner logs
|
|
122
|
+
// capture the outcome for monitoring.
|
|
123
|
+
if (imageVersion) {
|
|
124
|
+
var expectedMinor = majorMinor(imageVersion);
|
|
125
|
+
commands.push(
|
|
126
|
+
"Write-Host 'Checking installed runner version...'",
|
|
127
|
+
"$installedVersion = ''",
|
|
128
|
+
"try { $pkg = Get-Content 'node_modules/@testdriverai/runner/package.json' -Raw | ConvertFrom-Json; $installedVersion = $pkg.version } catch {}",
|
|
129
|
+
"$installedMinor = if ($installedVersion) { ($installedVersion -split '[.-]')[0..1] -join '.' } else { '' }",
|
|
130
|
+
"if (('" + expectedMinor + "' -ne '') -and ($installedMinor -eq '" + expectedMinor + "')) {",
|
|
131
|
+
" Write-Host \"Runner already at v$installedVersion (minor " + expectedMinor + "), skipping update\"",
|
|
132
|
+
" Write-Host (\"RUNNER_VERSION_CHECK:\" + (ConvertTo-Json -Compress @{ action='skipped'; installedVersion=$installedVersion; expectedVersion='" + imageVersion + "'; expectedMinor='" + expectedMinor + "'; channel='" + channel + "'; sandboxId='" + sandboxId + "' }))",
|
|
133
|
+
"} else {",
|
|
134
|
+
" Write-Host \"Installed minor: $installedMinor, expected: " + expectedMinor + " — updating...\"",
|
|
135
|
+
" Write-Host 'Installing @testdriverai/runner@" + runnerTag + "...'",
|
|
136
|
+
' npm install @testdriverai/runner@' + runnerTag + ' --omit=dev 2>&1 | Write-Host',
|
|
137
|
+
" $newVersion = ''",
|
|
138
|
+
" try { $newPkg = Get-Content 'node_modules/@testdriverai/runner/package.json' -Raw | ConvertFrom-Json; $newVersion = $newPkg.version } catch {}",
|
|
139
|
+
" Write-Host (\"RUNNER_VERSION_CHECK:\" + (ConvertTo-Json -Compress @{ action='updated'; previousVersion=$installedVersion; expectedVersion='" + imageVersion + "'; expectedMinor='" + expectedMinor + "'; newVersion=$newVersion; channel='" + channel + "'; sandboxId='" + sandboxId + "' }))",
|
|
140
|
+
" Write-Host 'Runner install complete'",
|
|
141
|
+
"}"
|
|
142
|
+
);
|
|
143
|
+
} else {
|
|
144
|
+
commands.push(
|
|
145
|
+
"Write-Host 'Installing @testdriverai/runner@" + runnerTag + "...'",
|
|
146
|
+
'npm install @testdriverai/runner@' + runnerTag + ' --omit=dev 2>&1 | Write-Host',
|
|
147
|
+
"Write-Host 'Runner install complete'"
|
|
148
|
+
);
|
|
149
|
+
}
|
|
100
150
|
}
|
|
101
151
|
|
|
102
152
|
// ── 3. Regenerate run_testdriver.ps1 ──────────────────────────────
|
|
@@ -138,16 +188,23 @@ function windowsProvisionCommands({ channel, configJson, sandboxId, s3DownloadUr
|
|
|
138
188
|
/**
|
|
139
189
|
* Generate the bash command to install/update the runner on Linux (E2B).
|
|
140
190
|
*
|
|
191
|
+
* When `imageVersion` is provided with the non-S3 (npm) path, the generated
|
|
192
|
+
* command checks the installed version first and skips the install when it
|
|
193
|
+
* matches — mirroring the Windows version-check behaviour.
|
|
194
|
+
*
|
|
141
195
|
* @param {Object} opts
|
|
142
|
-
* @param {string} opts.channel
|
|
143
|
-
* @param {string} [opts.s3DownloadUrl]
|
|
144
|
-
* @param {string} [opts.runnerPath]
|
|
196
|
+
* @param {string} opts.channel - Release channel
|
|
197
|
+
* @param {string} [opts.s3DownloadUrl] - S3 pre-signed URL for dev/test
|
|
198
|
+
* @param {string} [opts.runnerPath] - Default '/opt/testdriver-runner'
|
|
199
|
+
* @param {string} [opts.imageVersion] - Version baked into the E2B template (skip install when it matches)
|
|
200
|
+
* @param {string} [opts.sandboxId] - For structured logging
|
|
145
201
|
* @returns {string} Single bash command (steps joined with &&)
|
|
146
202
|
*/
|
|
147
|
-
function linuxRunnerInstallCommand({ channel, s3DownloadUrl, runnerPath }) {
|
|
203
|
+
function linuxRunnerInstallCommand({ channel, s3DownloadUrl, runnerPath, imageVersion, sandboxId }) {
|
|
148
204
|
var rp = runnerPath || '/opt/testdriver-runner';
|
|
149
205
|
var useS3 = (channel === 'dev' || channel === 'test') && s3DownloadUrl;
|
|
150
206
|
var runnerTag = channel === 'stable' ? 'latest' : channel;
|
|
207
|
+
var sid = sandboxId || 'unknown';
|
|
151
208
|
|
|
152
209
|
if (useS3) {
|
|
153
210
|
return [
|
|
@@ -162,6 +219,29 @@ function linuxRunnerInstallCommand({ channel, s3DownloadUrl, runnerPath }) {
|
|
|
162
219
|
].join(' && ');
|
|
163
220
|
}
|
|
164
221
|
|
|
222
|
+
// If we know the image version, wrap the install in a version check.
|
|
223
|
+
// Only the major.minor portion is compared (patch & prerelease suffix are
|
|
224
|
+
// ignored) so trivial patch bumps don't trigger an unnecessary npm install.
|
|
225
|
+
if (imageVersion) {
|
|
226
|
+
var expectedMinor = majorMinor(imageVersion);
|
|
227
|
+
return [
|
|
228
|
+
'INSTALLED_VERSION=$(node -p "try{require(\'' + rp + '/package.json\').version}catch(e){\'\'}" 2>/dev/null || echo "")',
|
|
229
|
+
'INSTALLED_MINOR=$(echo "$INSTALLED_VERSION" | sed -E "s/^([0-9]+\\.[0-9]+).*/\\1/")',
|
|
230
|
+
'if [ -n "' + expectedMinor + '" ] && [ "$INSTALLED_MINOR" = "' + expectedMinor + '" ]; then',
|
|
231
|
+
' echo "RUNNER_VERSION_CHECK:{\\"action\\":\\"skipped\\",\\"installedVersion\\":\\"$INSTALLED_VERSION\\",\\"expectedVersion\\":\\"' + imageVersion + '\\",\\"expectedMinor\\":\\"' + expectedMinor + '\\",\\"channel\\":\\"' + channel + '\\",\\"sandboxId\\":\\"' + sid + '\\"}"',
|
|
232
|
+
' echo "Runner already at v$INSTALLED_VERSION (minor ' + expectedMinor + '), skipping update"',
|
|
233
|
+
'else',
|
|
234
|
+
' echo "Installed minor: $INSTALLED_MINOR, expected: ' + expectedMinor + ' — updating..."',
|
|
235
|
+
' sudo npm install -g @testdriverai/runner@' + runnerTag + ' --omit=dev --no-audit --no-fund --loglevel=error',
|
|
236
|
+
' sudo rm -rf ' + rp,
|
|
237
|
+
' sudo ln -sf $(npm root -g)/@testdriverai/runner ' + rp,
|
|
238
|
+
' NEW_VERSION=$(node -p "try{require(\'' + rp + '/package.json\').version}catch(e){\'\'}" 2>/dev/null || echo "")',
|
|
239
|
+
' echo "RUNNER_VERSION_CHECK:{\\"action\\":\\"updated\\",\\"previousVersion\\":\\"$INSTALLED_VERSION\\",\\"expectedVersion\\":\\"' + imageVersion + '\\",\\"expectedMinor\\":\\"' + expectedMinor + '\\",\\"newVersion\\":\\"$NEW_VERSION\\",\\"channel\\":\\"' + channel + '\\",\\"sandboxId\\":\\"' + sid + '\\"}"',
|
|
240
|
+
' echo "Runner install complete"',
|
|
241
|
+
'fi',
|
|
242
|
+
].join(' && ');
|
|
243
|
+
}
|
|
244
|
+
|
|
165
245
|
return [
|
|
166
246
|
'sudo npm install -g @testdriverai/runner@' + runnerTag + ' --omit=dev --no-audit --no-fund --loglevel=error',
|
|
167
247
|
'sudo rm -rf ' + rp,
|
|
@@ -173,4 +253,5 @@ module.exports = {
|
|
|
173
253
|
buildAgentConfig,
|
|
174
254
|
windowsProvisionCommands,
|
|
175
255
|
linuxRunnerInstallCommand,
|
|
256
|
+
majorMinor,
|
|
176
257
|
};
|