testdriverai 7.0.0 → 7.1.1
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/.env.example +2 -0
- package/.github/workflows/linux-tests.yml +28 -0
- package/README.md +126 -0
- package/agent/index.js +7 -9
- package/agent/interface.js +13 -2
- package/agent/lib/commands.js +795 -136
- package/agent/lib/redraw.js +124 -39
- package/agent/lib/sandbox.js +40 -3
- package/agent/lib/sdk.js +21 -0
- package/agent/lib/valid-version.js +2 -2
- package/debugger/index.html +1 -1
- package/docs/docs.json +86 -71
- package/docs/guide/best-practices-polling.mdx +154 -0
- package/docs/v6/getting-started/self-hosting.mdx +3 -2
- package/docs/v7/_drafts/agents.mdx +852 -0
- package/docs/v7/_drafts/auto-cache-key.mdx +167 -0
- package/docs/v7/_drafts/best-practices.mdx +486 -0
- package/docs/v7/_drafts/caching-ai.mdx +215 -0
- package/docs/v7/_drafts/caching-selectors.mdx +400 -0
- package/docs/v7/_drafts/caching.mdx +366 -0
- package/docs/v7/_drafts/cli-to-sdk-migration.mdx +425 -0
- package/docs/v7/_drafts/core.mdx +459 -0
- package/docs/v7/_drafts/dashcam-title-feature.mdx +89 -0
- package/docs/v7/_drafts/debugging.mdx +349 -0
- package/docs/v7/_drafts/error-handling.mdx +501 -0
- package/docs/v7/_drafts/faq.mdx +393 -0
- package/docs/v7/_drafts/hooks.mdx +360 -0
- package/docs/v7/_drafts/implementation-plan.mdx +994 -0
- package/docs/v7/_drafts/init-command.mdx +95 -0
- package/docs/v7/_drafts/optimal-sdk-design.mdx +1348 -0
- package/docs/v7/_drafts/performance.mdx +517 -0
- package/docs/v7/_drafts/presets.mdx +210 -0
- package/docs/v7/_drafts/progressive-disclosure.mdx +230 -0
- package/docs/v7/_drafts/provision.mdx +266 -0
- package/docs/{QUICK_START_TEST_RECORDING.md → v7/_drafts/quick-start-test-recording.mdx} +3 -3
- package/docs/v7/_drafts/sdk-v7-complete.mdx +345 -0
- package/docs/v7/{guides → _drafts}/self-hosting.mdx +1 -1
- package/docs/v7/_drafts/troubleshooting.mdx +526 -0
- package/docs/v7/_drafts/vitest-plugin.mdx +477 -0
- package/docs/v7/_drafts/vitest.mdx +535 -0
- package/docs/v7/api/{ai.mdx → act.mdx} +24 -24
- package/docs/v7/api/client.mdx +1 -1
- package/docs/v7/api/dashcam.mdx +497 -0
- package/docs/v7/api/doubleClick.mdx +102 -0
- package/docs/v7/api/elements.mdx +143 -41
- package/docs/v7/api/find.mdx +258 -0
- package/docs/v7/api/mouseDown.mdx +161 -0
- package/docs/v7/api/mouseUp.mdx +164 -0
- package/docs/v7/api/rightClick.mdx +123 -0
- package/docs/v7/api/type.mdx +51 -7
- package/docs/v7/features/ai-native.mdx +427 -0
- package/docs/v7/features/easy-to-write.mdx +351 -0
- package/docs/v7/features/enterprise.mdx +540 -0
- package/docs/v7/features/fast.mdx +424 -0
- package/docs/v7/features/observable.mdx +623 -0
- package/docs/v7/features/powerful.mdx +531 -0
- package/docs/v7/features/scalable.mdx +417 -0
- package/docs/v7/features/stable.mdx +514 -0
- package/docs/v7/getting-started/configuration.mdx +380 -0
- package/docs/v7/getting-started/generating-tests.mdx +525 -0
- package/docs/v7/getting-started/installation.mdx +486 -0
- package/docs/v7/getting-started/quickstart.mdx +320 -141
- package/docs/v7/getting-started/running-and-debugging.mdx +511 -0
- package/docs/v7/getting-started/setting-up-in-ci.mdx +612 -0
- package/docs/v7/getting-started/writing-tests.mdx +535 -0
- package/docs/v7/overview/what-is-testdriver.mdx +398 -0
- package/docs/v7/platforms/linux.mdx +308 -0
- package/docs/v7/platforms/macos.mdx +433 -0
- package/docs/v7/platforms/windows.mdx +430 -0
- package/docs/v7/playwright.mdx +3 -3
- package/docs/v7/presets/chrome-extension.mdx +223 -0
- package/docs/v7/presets/chrome.mdx +303 -0
- package/docs/v7/presets/electron.mdx +453 -0
- package/docs/v7/presets/vscode.mdx +417 -0
- package/docs/v7/presets/webapp.mdx +396 -0
- package/examples/run-tests-with-recording.sh +2 -2
- package/interfaces/cli/commands/init.js +358 -0
- package/interfaces/vitest-plugin.mjs +393 -103
- package/lib/core/Dashcam.js +506 -0
- package/lib/core/index.d.ts +150 -0
- package/lib/core/index.js +12 -0
- package/lib/presets/index.mjs +331 -0
- package/lib/vitest/hooks.d.ts +119 -0
- package/lib/vitest/hooks.mjs +316 -0
- package/lib/vitest/setup.mjs +44 -0
- package/package.json +13 -3
- package/sdk.d.ts +350 -44
- package/sdk.js +818 -105
- package/{self-hosted.yml → setup/aws/self-hosted.yml} +1 -1
- package/test/manual/test-console-logs.test.mjs +42 -0
- package/test/manual/test-init.sh +54 -0
- package/test/manual/test-provision-auth.mjs +22 -0
- package/test/testdriver/assert.test.mjs +41 -0
- package/test/testdriver/auto-cache-key-demo.test.mjs +56 -0
- package/test/testdriver/chrome-extension.test.mjs +89 -0
- package/{testdriver/acceptance-sdk → test/testdriver}/drag-and-drop.test.mjs +7 -19
- package/{testdriver/acceptance-sdk → test/testdriver}/element-not-found.test.mjs +6 -19
- package/{testdriver/acceptance-sdk → test/testdriver}/exec-js.test.mjs +6 -18
- package/{testdriver/acceptance-sdk → test/testdriver}/exec-output.test.mjs +9 -21
- package/{testdriver/acceptance-sdk → test/testdriver}/exec-pwsh.test.mjs +14 -26
- package/{testdriver/acceptance-sdk → test/testdriver}/focus-window.test.mjs +8 -20
- package/{testdriver/acceptance-sdk → test/testdriver}/formatted-logging.test.mjs +5 -20
- package/{testdriver/acceptance-sdk → test/testdriver}/hover-image.test.mjs +10 -19
- package/{testdriver/acceptance-sdk → test/testdriver}/hover-text-with-description.test.mjs +7 -19
- package/{testdriver/acceptance-sdk → test/testdriver}/hover-text.test.mjs +5 -19
- package/{testdriver/acceptance-sdk → test/testdriver}/match-image.test.mjs +7 -19
- package/{testdriver/acceptance-sdk → test/testdriver}/press-keys.test.mjs +5 -19
- package/{testdriver/acceptance-sdk → test/testdriver}/prompt.test.mjs +7 -19
- package/{testdriver/acceptance-sdk → test/testdriver}/scroll-keyboard.test.mjs +6 -20
- package/{testdriver/acceptance-sdk → test/testdriver}/scroll-until-image.test.mjs +6 -18
- package/test/testdriver/scroll-until-text.test.mjs +28 -0
- package/{testdriver/acceptance-sdk → test/testdriver}/scroll.test.mjs +12 -21
- package/test/testdriver/setup/lifecycleHelpers.mjs +262 -0
- package/{testdriver/acceptance-sdk → test/testdriver}/setup/testHelpers.mjs +25 -20
- package/test/testdriver/type.test.mjs +45 -0
- package/vitest.config.mjs +11 -56
- package/.github/dependabot.yml +0 -11
- package/.github/workflows/acceptance-linux.yml +0 -75
- package/.github/workflows/acceptance-sdk-tests.yml +0 -133
- package/.github/workflows/acceptance-tests.yml +0 -130
- package/.github/workflows/lint.yml +0 -27
- package/.github/workflows/publish-canary.yml +0 -40
- package/.github/workflows/publish-latest.yml +0 -61
- package/.github/workflows/test-install.yml +0 -29
- package/.vscode/extensions.json +0 -3
- package/.vscode/launch.json +0 -22
- package/.vscode/mcp.json +0 -9
- package/.vscode/settings.json +0 -14
- package/CODEOWNERS +0 -3
- package/MIGRATION.md +0 -389
- package/SDK_README.md +0 -1122
- package/_testdriver/acceptance/assert.yaml +0 -7
- package/_testdriver/acceptance/dashcam.yaml +0 -9
- package/_testdriver/acceptance/drag-and-drop.yaml +0 -49
- package/_testdriver/acceptance/embed.yaml +0 -9
- package/_testdriver/acceptance/exec-js.yaml +0 -29
- package/_testdriver/acceptance/exec-output.yaml +0 -43
- package/_testdriver/acceptance/exec-shell.yaml +0 -40
- package/_testdriver/acceptance/focus-window.yaml +0 -16
- package/_testdriver/acceptance/hover-image.yaml +0 -18
- package/_testdriver/acceptance/hover-text-with-description.yaml +0 -29
- package/_testdriver/acceptance/hover-text.yaml +0 -14
- package/_testdriver/acceptance/if-else.yaml +0 -31
- package/_testdriver/acceptance/match-image.yaml +0 -15
- package/_testdriver/acceptance/press-keys.yaml +0 -35
- package/_testdriver/acceptance/prompt.yaml +0 -11
- package/_testdriver/acceptance/remember.yaml +0 -27
- package/_testdriver/acceptance/screenshots/cart.png +0 -0
- package/_testdriver/acceptance/scroll-keyboard.yaml +0 -34
- package/_testdriver/acceptance/scroll-until-image.yaml +0 -26
- package/_testdriver/acceptance/scroll-until-text.yaml +0 -20
- package/_testdriver/acceptance/scroll.yaml +0 -33
- package/_testdriver/acceptance/snippets/login.yaml +0 -29
- package/_testdriver/acceptance/snippets/match-cart.yaml +0 -8
- package/_testdriver/acceptance/type.yaml +0 -29
- package/_testdriver/behavior/failure.yaml +0 -7
- package/_testdriver/behavior/hover-text.yaml +0 -13
- package/_testdriver/behavior/lifecycle/postrun.yaml +0 -10
- package/_testdriver/behavior/lifecycle/prerun.yaml +0 -8
- package/_testdriver/behavior/lifecycle/provision.yaml +0 -8
- package/_testdriver/behavior/secrets.yaml +0 -7
- package/_testdriver/edge-cases/dashcam-chrome.yaml +0 -8
- package/_testdriver/edge-cases/exec-pwsh-multiline.yaml +0 -10
- package/_testdriver/edge-cases/js-exception.yaml +0 -8
- package/_testdriver/edge-cases/js-promise.yaml +0 -19
- package/_testdriver/edge-cases/lifecycle/postrun.yaml +0 -10
- package/_testdriver/edge-cases/prompt-in-middle.yaml +0 -23
- package/_testdriver/edge-cases/prompt-nested.yaml +0 -7
- package/_testdriver/edge-cases/success-test.yaml +0 -9
- package/_testdriver/examples/android/example.yaml +0 -12
- package/_testdriver/examples/android/lifecycle/postrun.yaml +0 -11
- package/_testdriver/examples/android/lifecycle/provision.yaml +0 -47
- package/_testdriver/examples/android/readme.md +0 -7
- package/_testdriver/examples/chrome-extension/lifecycle/provision.yaml +0 -74
- package/_testdriver/examples/desktop/lifecycle/prerun.yaml +0 -0
- package/_testdriver/examples/desktop/lifecycle/provision.yaml +0 -64
- package/_testdriver/examples/vscode-extension/lifecycle/provision.yaml +0 -73
- package/_testdriver/examples/web/lifecycle/postrun.yaml +0 -7
- package/_testdriver/examples/web/lifecycle/prerun.yaml +0 -22
- package/_testdriver/lifecycle/postrun.yaml +0 -8
- package/_testdriver/lifecycle/prerun.yaml +0 -15
- package/_testdriver/lifecycle/provision.yaml +0 -25
- package/debug-screenshot-1763401388589.png +0 -0
- package/mcp-server/AI_GUIDELINES.md +0 -57
- package/scripts/view-test-results.mjs +0 -96
- package/styles/.vale-config/2-MDX.ini +0 -5
- package/styles/Microsoft/AMPM.yml +0 -9
- package/styles/Microsoft/Accessibility.yml +0 -30
- package/styles/Microsoft/Acronyms.yml +0 -64
- package/styles/Microsoft/Adverbs.yml +0 -272
- package/styles/Microsoft/Auto.yml +0 -11
- package/styles/Microsoft/Avoid.yml +0 -14
- package/styles/Microsoft/Contractions.yml +0 -50
- package/styles/Microsoft/Dashes.yml +0 -13
- package/styles/Microsoft/DateFormat.yml +0 -8
- package/styles/Microsoft/DateNumbers.yml +0 -40
- package/styles/Microsoft/DateOrder.yml +0 -8
- package/styles/Microsoft/Ellipses.yml +0 -9
- package/styles/Microsoft/FirstPerson.yml +0 -16
- package/styles/Microsoft/Foreign.yml +0 -13
- package/styles/Microsoft/Gender.yml +0 -8
- package/styles/Microsoft/GenderBias.yml +0 -42
- package/styles/Microsoft/GeneralURL.yml +0 -11
- package/styles/Microsoft/HeadingAcronyms.yml +0 -7
- package/styles/Microsoft/HeadingColons.yml +0 -8
- package/styles/Microsoft/HeadingPunctuation.yml +0 -13
- package/styles/Microsoft/Headings.yml +0 -28
- package/styles/Microsoft/Hyphens.yml +0 -14
- package/styles/Microsoft/Negative.yml +0 -13
- package/styles/Microsoft/Ordinal.yml +0 -13
- package/styles/Microsoft/OxfordComma.yml +0 -8
- package/styles/Microsoft/Passive.yml +0 -183
- package/styles/Microsoft/Percentages.yml +0 -7
- package/styles/Microsoft/Plurals.yml +0 -7
- package/styles/Microsoft/Quotes.yml +0 -7
- package/styles/Microsoft/RangeTime.yml +0 -13
- package/styles/Microsoft/Semicolon.yml +0 -8
- package/styles/Microsoft/SentenceLength.yml +0 -6
- package/styles/Microsoft/Spacing.yml +0 -8
- package/styles/Microsoft/Suspended.yml +0 -7
- package/styles/Microsoft/Terms.yml +0 -42
- package/styles/Microsoft/URLFormat.yml +0 -9
- package/styles/Microsoft/Units.yml +0 -16
- package/styles/Microsoft/Vocab.yml +0 -25
- package/styles/Microsoft/We.yml +0 -11
- package/styles/Microsoft/Wordiness.yml +0 -127
- package/styles/Microsoft/meta.json +0 -4
- package/styles/alex/Ablist.yml +0 -274
- package/styles/alex/Condescending.yml +0 -16
- package/styles/alex/Gendered.yml +0 -110
- package/styles/alex/LGBTQ.yml +0 -55
- package/styles/alex/OCD.yml +0 -10
- package/styles/alex/Press.yml +0 -12
- package/styles/alex/ProfanityLikely.yml +0 -1289
- package/styles/alex/ProfanityMaybe.yml +0 -282
- package/styles/alex/ProfanityUnlikely.yml +0 -251
- package/styles/alex/README.md +0 -27
- package/styles/alex/Race.yml +0 -85
- package/styles/alex/Suicide.yml +0 -26
- package/styles/alex/meta.json +0 -4
- package/styles/config/vocabularies/Docs/accept.txt +0 -47
- package/styles/config/vocabularies/Docs/reject.txt +0 -4
- package/styles/proselint/Airlinese.yml +0 -8
- package/styles/proselint/AnimalLabels.yml +0 -48
- package/styles/proselint/Annotations.yml +0 -9
- package/styles/proselint/Apologizing.yml +0 -8
- package/styles/proselint/Archaisms.yml +0 -52
- package/styles/proselint/But.yml +0 -8
- package/styles/proselint/Cliches.yml +0 -782
- package/styles/proselint/CorporateSpeak.yml +0 -30
- package/styles/proselint/Currency.yml +0 -5
- package/styles/proselint/Cursing.yml +0 -15
- package/styles/proselint/DateCase.yml +0 -7
- package/styles/proselint/DateMidnight.yml +0 -7
- package/styles/proselint/DateRedundancy.yml +0 -10
- package/styles/proselint/DateSpacing.yml +0 -7
- package/styles/proselint/DenizenLabels.yml +0 -52
- package/styles/proselint/Diacritical.yml +0 -95
- package/styles/proselint/GenderBias.yml +0 -45
- package/styles/proselint/GroupTerms.yml +0 -39
- package/styles/proselint/Hedging.yml +0 -8
- package/styles/proselint/Hyperbole.yml +0 -6
- package/styles/proselint/Jargon.yml +0 -11
- package/styles/proselint/LGBTOffensive.yml +0 -13
- package/styles/proselint/LGBTTerms.yml +0 -15
- package/styles/proselint/Malapropisms.yml +0 -8
- package/styles/proselint/Needless.yml +0 -358
- package/styles/proselint/Nonwords.yml +0 -38
- package/styles/proselint/Oxymorons.yml +0 -22
- package/styles/proselint/P-Value.yml +0 -6
- package/styles/proselint/RASSyndrome.yml +0 -30
- package/styles/proselint/README.md +0 -12
- package/styles/proselint/Skunked.yml +0 -13
- package/styles/proselint/Spelling.yml +0 -17
- package/styles/proselint/Typography.yml +0 -11
- package/styles/proselint/Uncomparables.yml +0 -50
- package/styles/proselint/Very.yml +0 -6
- package/styles/proselint/meta.json +0 -15
- package/styles/write-good/Cliches.yml +0 -702
- package/styles/write-good/E-Prime.yml +0 -32
- package/styles/write-good/Illusions.yml +0 -11
- package/styles/write-good/Passive.yml +0 -183
- package/styles/write-good/README.md +0 -27
- package/styles/write-good/So.yml +0 -5
- package/styles/write-good/ThereIs.yml +0 -6
- package/styles/write-good/TooWordy.yml +0 -221
- package/styles/write-good/Weasel.yml +0 -29
- package/styles/write-good/meta.json +0 -4
- package/test/mcp-example-test.yaml +0 -27
- package/test/test_parser.js +0 -47
- package/testdriver/acceptance-sdk/QUICK_REFERENCE.md +0 -61
- package/testdriver/acceptance-sdk/README.md +0 -128
- package/testdriver/acceptance-sdk/TEST_REPORTING.md +0 -245
- package/testdriver/acceptance-sdk/assert.test.mjs +0 -44
- package/testdriver/acceptance-sdk/scroll-until-text.test.mjs +0 -42
- package/testdriver/acceptance-sdk/setup/lifecycleHelpers.mjs +0 -239
- package/testdriver/acceptance-sdk/type-checking-demo.js +0 -49
- package/testdriver/acceptance-sdk/type.test.mjs +0 -84
- package/vale.ini +0 -18
- package/vitest.config.example.js +0 -19
- package/vitest.config.mjs.bak +0 -44
- /package/docs/{ARCHITECTURE.md → v7/_drafts/architecture.mdx} +0 -0
- /package/docs/{AWESOME_LOGS_QUICK_REF.md → v7/_drafts/awesome-logs-quick-ref.mdx} +0 -0
- /package/{CONTRIBUTING.md → docs/v7/_drafts/contributing.mdx} +0 -0
- /package/docs/v7/{guides → _drafts}/migration.mdx +0 -0
- /package/{PLUGIN_MIGRATION.md → docs/v7/_drafts/plugin-migration.mdx} +0 -0
- /package/{PROMPT_CACHE.md → docs/v7/_drafts/prompt-cache.mdx} +0 -0
- /package/docs/{SDK_AWESOME_LOGS.md → v7/_drafts/sdk-awesome-logs.mdx} +0 -0
- /package/docs/{sdk-browser-rendering.md → v7/_drafts/sdk-browser-rendering.mdx} +0 -0
- /package/{SDK_LOGGING.md → docs/v7/_drafts/sdk-logging.mdx} +0 -0
- /package/{SDK_MIGRATION.md → docs/v7/_drafts/sdk-migration.mdx} +0 -0
- /package/docs/{TEST_RECORDING.md → v7/_drafts/test-recording.mdx} +0 -0
- /package/docs/v7/{README.md → overview/readme.mdx} +0 -0
- /package/{debug-locate-response.js → test/manual/debug-locate-response.js} +0 -0
- /package/{test-find-api.js → test/manual/test-find-api.js} +0 -0
- /package/{test-prompt-cache.js → test/manual/test-prompt-cache.js} +0 -0
- /package/{test-sandbox-render.js → test/manual/test-sandbox-render.js} +0 -0
- /package/{test-sdk-methods.js → test/manual/test-sdk-methods.js} +0 -0
- /package/{test-sdk-refactor.js → test/manual/test-sdk-refactor.js} +0 -0
- /package/{test-stack-trace.mjs → test/manual/test-stack-trace.mjs} +0 -0
- /package/{verify-element-api.js → test/manual/verify-element-api.js} +0 -0
- /package/{verify-types.js → test/manual/verify-types.js} +0 -0
- /package/{testdriver/acceptance-sdk → test/testdriver}/setup/globalTeardown.mjs +0 -0
- /package/{testdriver/acceptance-sdk → test/testdriver}/setup/vitestSetup.mjs +0 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vitest Hooks for TestDriver
|
|
3
|
+
*
|
|
4
|
+
* Provides lifecycle management for TestDriver in Vitest tests.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* import { TestDriver } from 'testdriverai/vitest/hooks';
|
|
8
|
+
*
|
|
9
|
+
* test('my test', async (context) => {
|
|
10
|
+
* const testdriver = TestDriver(context, { headless: true });
|
|
11
|
+
*
|
|
12
|
+
* await testdriver.ready();
|
|
13
|
+
* await testdriver.provision.chrome({ url: 'https://example.com' });
|
|
14
|
+
* await testdriver.find('button').click();
|
|
15
|
+
* });
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import fs from 'fs';
|
|
19
|
+
import os from 'os';
|
|
20
|
+
import path from 'path';
|
|
21
|
+
import { vi } from 'vitest';
|
|
22
|
+
import TestDriverSDK from '../../sdk.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Set up console spies using Vitest's vi.spyOn to intercept console logs
|
|
26
|
+
* and forward them to the sandbox for Dashcam visibility.
|
|
27
|
+
* This is test-isolated and doesn't cause conflicts with concurrent tests.
|
|
28
|
+
* @param {TestDriver} client - TestDriver client instance
|
|
29
|
+
* @param {string} taskId - Unique task identifier for this test
|
|
30
|
+
*/
|
|
31
|
+
function setupConsoleSpy(client, taskId) {
|
|
32
|
+
|
|
33
|
+
// Debug logging for console spy setup
|
|
34
|
+
const debugConsoleSpy = process.env.TD_DEBUG_CONSOLE_SPY === 'true';
|
|
35
|
+
if (debugConsoleSpy) {
|
|
36
|
+
process.stdout.write(`[DEBUG setupConsoleSpy] taskId: ${taskId}\n`);
|
|
37
|
+
process.stdout.write(`[DEBUG setupConsoleSpy] client.sandbox exists: ${!!client.sandbox}\n`);
|
|
38
|
+
process.stdout.write(`[DEBUG setupConsoleSpy] client.sandbox?.instanceSocketConnected: ${client.sandbox?.instanceSocketConnected}\n`);
|
|
39
|
+
process.stdout.write(`[DEBUG setupConsoleSpy] client.sandbox?.send: ${typeof client.sandbox?.send}\n`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Track forwarding stats
|
|
43
|
+
let forwardedCount = 0;
|
|
44
|
+
let skippedCount = 0;
|
|
45
|
+
|
|
46
|
+
// Helper to forward logs to sandbox
|
|
47
|
+
const forwardToSandbox = (args) => {
|
|
48
|
+
const message = args
|
|
49
|
+
.map((arg) =>
|
|
50
|
+
typeof arg === "object"
|
|
51
|
+
? JSON.stringify(arg, null, 2)
|
|
52
|
+
: String(arg),
|
|
53
|
+
)
|
|
54
|
+
.join(" ");
|
|
55
|
+
|
|
56
|
+
// Send to sandbox for immediate visibility in dashcam
|
|
57
|
+
if (client.sandbox && client.sandbox.instanceSocketConnected) {
|
|
58
|
+
try {
|
|
59
|
+
client.sandbox.send({
|
|
60
|
+
type: "output",
|
|
61
|
+
output: Buffer.from(message, "utf8").toString("base64"),
|
|
62
|
+
});
|
|
63
|
+
forwardedCount++;
|
|
64
|
+
if (debugConsoleSpy && forwardedCount <= 3) {
|
|
65
|
+
process.stdout.write(`[DEBUG forwardToSandbox] Forwarded message #${forwardedCount}: "${message.substring(0, 50)}..."\n`);
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
if (debugConsoleSpy) {
|
|
69
|
+
process.stdout.write(`[DEBUG forwardToSandbox] Error sending: ${err.message}\n`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
skippedCount++;
|
|
74
|
+
if (debugConsoleSpy && skippedCount <= 3) {
|
|
75
|
+
process.stdout.write(`[DEBUG forwardToSandbox] SKIPPED (sandbox not connected): "${message.substring(0, 50)}..."\n`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Create spies for each console method
|
|
81
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation((...args) => {
|
|
82
|
+
// Call through to original
|
|
83
|
+
logSpy.mock.calls; // Track calls
|
|
84
|
+
process.stdout.write(args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ') + '\n');
|
|
85
|
+
forwardToSandbox(args);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const errorSpy = vi.spyOn(console, 'error').mockImplementation((...args) => {
|
|
89
|
+
process.stderr.write(args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ') + '\n');
|
|
90
|
+
forwardToSandbox(args);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation((...args) => {
|
|
94
|
+
process.stderr.write(args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ') + '\n');
|
|
95
|
+
forwardToSandbox(args);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const infoSpy = vi.spyOn(console, 'info').mockImplementation((...args) => {
|
|
99
|
+
process.stdout.write(args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ') + '\n');
|
|
100
|
+
forwardToSandbox(args);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Store spies on client for cleanup
|
|
104
|
+
client._consoleSpies = { logSpy, errorSpy, warnSpy, infoSpy };
|
|
105
|
+
|
|
106
|
+
console.log(`[testdriver] Console spy set up for task: ${taskId}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Clean up console spies and restore original console methods
|
|
111
|
+
* @param {TestDriver} client - TestDriver client instance
|
|
112
|
+
*/
|
|
113
|
+
function cleanupConsoleSpy(client) {
|
|
114
|
+
if (client._consoleSpies) {
|
|
115
|
+
const { logSpy, errorSpy, warnSpy, infoSpy } = client._consoleSpies;
|
|
116
|
+
logSpy.mockRestore();
|
|
117
|
+
errorSpy.mockRestore();
|
|
118
|
+
warnSpy.mockRestore();
|
|
119
|
+
infoSpy.mockRestore();
|
|
120
|
+
delete client._consoleSpies;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Weak maps to store instances per test context
|
|
125
|
+
const testDriverInstances = new WeakMap();
|
|
126
|
+
const lifecycleHandlers = new WeakMap();
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Create a TestDriver client in a Vitest test with automatic lifecycle management
|
|
130
|
+
*
|
|
131
|
+
* @param {object} context - Vitest test context (from async (context) => {})
|
|
132
|
+
* @param {object} options - TestDriver options (passed directly to TestDriver constructor)
|
|
133
|
+
* @param {string} [options.apiKey] - TestDriver API key (defaults to process.env.TD_API_KEY)
|
|
134
|
+
* @param {boolean} [options.headless] - Run sandbox in headless mode
|
|
135
|
+
* @param {boolean} [options.newSandbox] - Create new sandbox
|
|
136
|
+
* @param {boolean} [options.autoConnect=true] - Automatically connect to sandbox
|
|
137
|
+
* @returns {TestDriver} TestDriver client instance
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* test('my test', async (context) => {
|
|
141
|
+
* const testdriver = TestDriver(context, { headless: true });
|
|
142
|
+
*
|
|
143
|
+
* // provision.chrome() automatically calls ready() and starts dashcam
|
|
144
|
+
* await testdriver.provision.chrome({ url: 'https://example.com' });
|
|
145
|
+
*
|
|
146
|
+
* await testdriver.find('Login button').click();
|
|
147
|
+
* });
|
|
148
|
+
*/
|
|
149
|
+
export function TestDriver(context, options = {}) {
|
|
150
|
+
if (!context || !context.task) {
|
|
151
|
+
throw new Error('TestDriver() requires Vitest context. Pass the context parameter from your test function: test("name", async (context) => { ... })');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Return existing instance if already created for this test
|
|
155
|
+
if (testDriverInstances.has(context.task)) {
|
|
156
|
+
return testDriverInstances.get(context.task);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Get global plugin options if available
|
|
160
|
+
const pluginOptions = globalThis.__testdriverPlugin?.state?.testDriverOptions || {};
|
|
161
|
+
|
|
162
|
+
// Merge options: plugin global options < test-specific options
|
|
163
|
+
const mergedOptions = { ...pluginOptions, ...options };
|
|
164
|
+
|
|
165
|
+
// Extract TestDriver-specific options
|
|
166
|
+
const apiKey = mergedOptions.apiKey || process.env.TD_API_KEY;
|
|
167
|
+
|
|
168
|
+
// Build config for TestDriverSDK constructor
|
|
169
|
+
const config = { ...mergedOptions };
|
|
170
|
+
delete config.apiKey;
|
|
171
|
+
|
|
172
|
+
// Use TD_API_ROOT from environment if not provided in config
|
|
173
|
+
if (!config.apiRoot && process.env.TD_API_ROOT) {
|
|
174
|
+
config.apiRoot = process.env.TD_API_ROOT;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const testdriver = new TestDriverSDK(apiKey, config);
|
|
178
|
+
testdriver.__vitestContext = context.task;
|
|
179
|
+
testDriverInstances.set(context.task, testdriver);
|
|
180
|
+
|
|
181
|
+
// Auto-connect if enabled (default: true)
|
|
182
|
+
const autoConnect = config.autoConnect !== undefined ? config.autoConnect : true;
|
|
183
|
+
const debugConsoleSpy = process.env.TD_DEBUG_CONSOLE_SPY === 'true';
|
|
184
|
+
|
|
185
|
+
if (autoConnect) {
|
|
186
|
+
testdriver.__connectionPromise = (async () => {
|
|
187
|
+
console.log('[testdriver] Connecting to sandbox...');
|
|
188
|
+
if (debugConsoleSpy) {
|
|
189
|
+
console.log('[DEBUG] Before auth - sandbox.instanceSocketConnected:', testdriver.sandbox?.instanceSocketConnected);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
await testdriver.auth();
|
|
193
|
+
await testdriver.connect();
|
|
194
|
+
|
|
195
|
+
console.log('[testdriver] ✅ Connected to sandbox');
|
|
196
|
+
|
|
197
|
+
if (debugConsoleSpy) {
|
|
198
|
+
console.log('[DEBUG] After connect - sandbox.instanceSocketConnected:', testdriver.sandbox?.instanceSocketConnected);
|
|
199
|
+
console.log('[DEBUG] After connect - sandbox.send:', typeof testdriver.sandbox?.send);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Set up console spy using vi.spyOn (test-isolated)
|
|
203
|
+
setupConsoleSpy(testdriver, context.task.id);
|
|
204
|
+
|
|
205
|
+
// Create the log file on the remote machine
|
|
206
|
+
const shell = testdriver.os === "windows" ? "pwsh" : "sh";
|
|
207
|
+
const logPath = testdriver.os === "windows"
|
|
208
|
+
? "C:\\Users\\testdriver\\Documents\\testdriver.log"
|
|
209
|
+
: "/tmp/testdriver.log";
|
|
210
|
+
|
|
211
|
+
const createLogCmd = testdriver.os === "windows"
|
|
212
|
+
? `New-Item -ItemType File -Path "${logPath}" -Force | Out-Null`
|
|
213
|
+
: `touch ${logPath}`;
|
|
214
|
+
|
|
215
|
+
await testdriver.exec(shell, createLogCmd, 10000, true);
|
|
216
|
+
console.log('[testdriver] ✅ Created log file:', logPath);
|
|
217
|
+
|
|
218
|
+
// Add automatic log tracking when dashcam starts
|
|
219
|
+
// Store original start method
|
|
220
|
+
|
|
221
|
+
await testdriver.dashcam.addFileLog(logPath, "TestDriver Log");
|
|
222
|
+
|
|
223
|
+
})();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Register cleanup handler with dashcam.stop()
|
|
227
|
+
if (!lifecycleHandlers.has(context.task)) {
|
|
228
|
+
const cleanup = async () => {
|
|
229
|
+
console.log('[testdriver] Cleaning up TestDriver client...');
|
|
230
|
+
try {
|
|
231
|
+
// Stop dashcam if it was started - with timeout to prevent hanging
|
|
232
|
+
if (testdriver._dashcam && testdriver._dashcam.recording) {
|
|
233
|
+
try {
|
|
234
|
+
// Add a timeout wrapper to prevent dashcam.stop from hanging indefinitely
|
|
235
|
+
const stopWithTimeout = Promise.race([
|
|
236
|
+
testdriver.dashcam.stop(),
|
|
237
|
+
new Promise((_, reject) =>
|
|
238
|
+
setTimeout(() => reject(new Error('Dashcam stop timed out after 30s')), 30000)
|
|
239
|
+
)
|
|
240
|
+
]);
|
|
241
|
+
|
|
242
|
+
const dashcamUrl = await stopWithTimeout;
|
|
243
|
+
console.log('🎥 Dashcam URL:', dashcamUrl);
|
|
244
|
+
|
|
245
|
+
// Write dashcam URL to file for the reporter (cross-process communication)
|
|
246
|
+
if (dashcamUrl) {
|
|
247
|
+
const testId = context.task.id;
|
|
248
|
+
const platform = testdriver.os || 'linux';
|
|
249
|
+
const testFile = context.task.file?.filepath || context.task.file?.name || 'unknown';
|
|
250
|
+
|
|
251
|
+
// Create results directory if it doesn't exist
|
|
252
|
+
const resultsDir = path.join(os.tmpdir(), 'testdriver-results');
|
|
253
|
+
if (!fs.existsSync(resultsDir)) {
|
|
254
|
+
fs.mkdirSync(resultsDir, { recursive: true });
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Write test result file
|
|
258
|
+
const testResultFile = path.join(resultsDir, `${testId}.json`);
|
|
259
|
+
const testResult = {
|
|
260
|
+
dashcamUrl,
|
|
261
|
+
platform,
|
|
262
|
+
testFile,
|
|
263
|
+
testOrder: 0,
|
|
264
|
+
sessionId: testdriver.getSessionId(),
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
fs.writeFileSync(testResultFile, JSON.stringify(testResult, null, 2));
|
|
268
|
+
console.log(`[testdriver] ✅ Wrote dashcam URL to ${testResultFile}`);
|
|
269
|
+
|
|
270
|
+
// Also register in memory if plugin is available
|
|
271
|
+
if (globalThis.__testdriverPlugin?.registerDashcamUrl) {
|
|
272
|
+
globalThis.__testdriverPlugin.registerDashcamUrl(testId, dashcamUrl, platform);
|
|
273
|
+
console.log(`[testdriver] ✅ Registered dashcam URL in memory for test ${testId}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
} catch (error) {
|
|
277
|
+
// Log more detailed error information for debugging
|
|
278
|
+
console.error('❌ Failed to stop dashcam:', error.name || error.constructor?.name || 'Error');
|
|
279
|
+
if (error.message) console.error(' Message:', error.message);
|
|
280
|
+
// NotFoundError during cleanup is expected if sandbox already terminated
|
|
281
|
+
if (error.name === 'NotFoundError' || error.responseData?.error === 'NotFoundError') {
|
|
282
|
+
console.log(' ℹ️ Sandbox session already terminated - dashcam stop skipped');
|
|
283
|
+
}
|
|
284
|
+
// Mark as not recording to prevent retries
|
|
285
|
+
if (testdriver._dashcam) {
|
|
286
|
+
testdriver._dashcam.recording = false;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Clean up console spies
|
|
292
|
+
cleanupConsoleSpy(testdriver);
|
|
293
|
+
|
|
294
|
+
// Wait for connection to finish if it was initiated
|
|
295
|
+
if (testdriver.__connectionPromise) {
|
|
296
|
+
await testdriver.__connectionPromise.catch(() => {}); // Ignore connection errors during cleanup
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Disconnect with timeout
|
|
300
|
+
await Promise.race([
|
|
301
|
+
testdriver.disconnect(),
|
|
302
|
+
new Promise((resolve) => setTimeout(resolve, 5000)) // 5s timeout for disconnect
|
|
303
|
+
]);
|
|
304
|
+
console.log('✅ Client disconnected');
|
|
305
|
+
} catch (error) {
|
|
306
|
+
console.error('Error disconnecting client:', error);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
lifecycleHandlers.set(context.task, cleanup);
|
|
310
|
+
|
|
311
|
+
// Vitest will call this automatically after the test
|
|
312
|
+
context.onTestFinished?.(cleanup);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return testdriver;
|
|
316
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vitest Setup File for TestDriver
|
|
3
|
+
*
|
|
4
|
+
* This file is run by Vitest before each test file to set up
|
|
5
|
+
* the TestDriver plugin state and global helpers.
|
|
6
|
+
*
|
|
7
|
+
* Usage in vitest.config.mjs:
|
|
8
|
+
* ```js
|
|
9
|
+
* export default defineConfig({
|
|
10
|
+
* test: {
|
|
11
|
+
* setupFiles: ['testdriverai/vitest/setup'],
|
|
12
|
+
* },
|
|
13
|
+
* });
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
clearDashcamUrls,
|
|
19
|
+
clearSuiteTestRun,
|
|
20
|
+
getDashcamUrl,
|
|
21
|
+
getPluginState,
|
|
22
|
+
getSuiteTestRun,
|
|
23
|
+
pluginState,
|
|
24
|
+
registerDashcamUrl,
|
|
25
|
+
setSuiteTestRun,
|
|
26
|
+
} from '../../interfaces/vitest-plugin.mjs';
|
|
27
|
+
|
|
28
|
+
// Set up global TestDriver plugin interface
|
|
29
|
+
// This allows tests and the SDK to communicate with the reporter
|
|
30
|
+
globalThis.__testdriverPlugin = {
|
|
31
|
+
state: pluginState,
|
|
32
|
+
registerDashcamUrl,
|
|
33
|
+
getDashcamUrl,
|
|
34
|
+
clearDashcamUrls,
|
|
35
|
+
getPluginState,
|
|
36
|
+
getSuiteTestRun,
|
|
37
|
+
setSuiteTestRun,
|
|
38
|
+
clearSuiteTestRun,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Log that setup is complete (only in debug mode)
|
|
42
|
+
if (process.env.TD_LOG_LEVEL?.toLowerCase() === 'debug') {
|
|
43
|
+
console.log('[TestDriver] Setup file initialized, global plugin interface available');
|
|
44
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testdriverai",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.1.1",
|
|
4
4
|
"description": "Next generation autonomous AI agent for end-to-end testing of web & desktop",
|
|
5
5
|
"main": "sdk.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./sdk.js",
|
|
8
|
+
"./core": "./lib/core/index.js",
|
|
9
|
+
"./vitest": "./interfaces/vitest-plugin.mjs",
|
|
10
|
+
"./vitest/plugin": "./interfaces/vitest-plugin.mjs",
|
|
11
|
+
"./vitest/setup": "./lib/vitest/setup.mjs",
|
|
12
|
+
"./vitest/hooks": "./lib/vitest/hooks.mjs",
|
|
13
|
+
"./presets": "./lib/presets/index.mjs"
|
|
14
|
+
},
|
|
6
15
|
"bin": {
|
|
7
16
|
"testdriverai": "bin/testdriverai.js"
|
|
8
17
|
},
|
|
@@ -50,7 +59,7 @@
|
|
|
50
59
|
"chalk": "^4.1.2",
|
|
51
60
|
"cli-progress": "^3.12.0",
|
|
52
61
|
"diff": "^8.0.2",
|
|
53
|
-
"dotenv": "^16.
|
|
62
|
+
"dotenv": "^16.6.1",
|
|
54
63
|
"eventemitter2": "^6.4.9",
|
|
55
64
|
"jimp": "^0.22.12",
|
|
56
65
|
"js-yaml": "^4.1.0",
|
|
@@ -88,7 +97,8 @@
|
|
|
88
97
|
"mocha": "^10.8.2",
|
|
89
98
|
"node-addon-api": "^8.0.0",
|
|
90
99
|
"prettier": "3.3.3",
|
|
91
|
-
"
|
|
100
|
+
"testdriverai": "^6.1.11",
|
|
101
|
+
"vitest": "^4.0.15"
|
|
92
102
|
},
|
|
93
103
|
"optionalDependencies": {
|
|
94
104
|
"@esbuild/linux-x64": "^0.21.5"
|