testdriverai 6.2.2 → 7.1.0
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/.github/workflows/acceptance-linux.yml +75 -0
- package/.github/workflows/acceptance-sdk-tests.yml +133 -0
- package/.vscode/settings.json +5 -1
- package/AGENTS.md +550 -0
- package/CODEOWNERS +0 -1
- package/README.md +126 -0
- package/{testdriver → _testdriver}/acceptance/drag-and-drop.yaml +2 -2
- package/{testdriver → _testdriver}/acceptance/snippets/login.yaml +1 -1
- package/_testdriver/examples/desktop/lifecycle/prerun.yaml +0 -0
- package/{testdriver → _testdriver}/examples/web/lifecycle/prerun.yaml +6 -1
- package/{testdriver → _testdriver}/lifecycle/postrun.yaml +3 -2
- package/_testdriver/lifecycle/prerun.yaml +15 -0
- package/{testdriver → _testdriver}/lifecycle/provision.yaml +7 -2
- package/agent/index.js +300 -85
- package/agent/interface.js +15 -0
- package/agent/lib/cache.js +142 -0
- package/agent/lib/commander.js +1 -39
- package/agent/lib/commands.js +910 -296
- package/agent/lib/redraw.js +129 -41
- package/agent/lib/sandbox.js +29 -6
- package/agent/lib/sdk.js +22 -0
- package/agent/lib/system.js +0 -3
- package/agent/lib/validation.js +1 -7
- package/debug-locate-response.js +82 -0
- package/debugger/index.html +15 -4
- package/docs/ARCHITECTURE.md +424 -0
- package/docs/AWESOME_LOGS_QUICK_REF.md +100 -0
- package/docs/MIGRATION.md +425 -0
- package/docs/PRESETS.md +210 -0
- package/docs/QUICK_START_TEST_RECORDING.md +215 -0
- package/docs/SDK_AWESOME_LOGS.md +468 -0
- package/docs/TEST_RECORDING.md +388 -0
- package/docs/docs.json +286 -152
- package/docs/guide/best-practices-polling.mdx +154 -0
- package/docs/sdk-browser-rendering.md +167 -0
- package/docs/v6/getting-started/self-hosting.mdx +407 -0
- package/docs/{guide → v6/guide}/dashcam.mdx +1 -1
- package/docs/{guide → v6/guide}/environment-variables.mdx +4 -5
- package/docs/{guide → v6/guide}/lifecycle.mdx +1 -1
- package/docs/v6/overview/comparison.mdx +101 -0
- package/docs/v7/README.md +135 -0
- package/docs/v7/api/ai.mdx +205 -0
- package/docs/v7/api/assert.mdx +285 -0
- package/docs/v7/api/assertions.mdx +403 -0
- package/docs/v7/api/click.mdx +287 -0
- package/docs/v7/api/client.mdx +322 -0
- package/docs/v7/api/dashcam.mdx +497 -0
- package/docs/v7/api/doubleClick.mdx +102 -0
- package/docs/v7/api/elements.mdx +479 -0
- package/docs/v7/api/exec.mdx +346 -0
- package/docs/v7/api/find.mdx +316 -0
- package/docs/v7/api/focusApplication.mdx +294 -0
- package/docs/v7/api/hover.mdx +279 -0
- package/docs/v7/api/mouseDown.mdx +161 -0
- package/docs/v7/api/mouseUp.mdx +164 -0
- package/docs/v7/api/pressKeys.mdx +349 -0
- package/docs/v7/api/rightClick.mdx +123 -0
- package/docs/v7/api/sandbox.mdx +404 -0
- package/docs/v7/api/scroll.mdx +300 -0
- package/docs/v7/api/type.mdx +314 -0
- package/docs/v7/commands/assert.mdx +45 -0
- package/docs/v7/commands/exec.mdx +282 -0
- package/docs/v7/commands/focus-application.mdx +44 -0
- package/docs/v7/commands/hover-image.mdx +69 -0
- package/docs/v7/commands/hover-text.mdx +47 -0
- package/docs/v7/commands/if.mdx +53 -0
- package/docs/v7/commands/match-image.mdx +67 -0
- package/docs/v7/commands/press-keys.mdx +87 -0
- package/docs/v7/commands/remember.mdx +49 -0
- package/docs/v7/commands/run.mdx +44 -0
- package/docs/v7/commands/scroll-until-image.mdx +66 -0
- package/docs/v7/commands/scroll-until-text.mdx +60 -0
- package/docs/v7/commands/scroll.mdx +69 -0
- package/docs/v7/commands/type.mdx +45 -0
- package/docs/v7/commands/wait-for-image.mdx +54 -0
- package/docs/v7/commands/wait-for-text.mdx +48 -0
- package/docs/v7/commands/wait.mdx +45 -0
- package/docs/v7/getting-started/configuration.mdx +380 -0
- package/docs/v7/getting-started/quickstart.mdx +332 -0
- package/docs/v7/guides/best-practices.mdx +486 -0
- package/docs/v7/guides/caching-ai.mdx +215 -0
- package/docs/v7/guides/caching-selectors.mdx +292 -0
- package/docs/v7/guides/caching.mdx +366 -0
- package/docs/v7/guides/ci-cd/azure.mdx +587 -0
- package/docs/v7/guides/ci-cd/circleci.mdx +523 -0
- package/docs/v7/guides/ci-cd/github-actions.mdx +457 -0
- package/docs/v7/guides/ci-cd/gitlab.mdx +498 -0
- package/docs/v7/guides/ci-cd/jenkins.mdx +664 -0
- package/docs/v7/guides/ci-cd/travis.mdx +438 -0
- package/docs/v7/guides/debugging.mdx +349 -0
- package/docs/v7/guides/faq.mdx +393 -0
- package/docs/v7/guides/migration.mdx +562 -0
- package/docs/v7/guides/performance.mdx +517 -0
- package/docs/{getting-started → v7/guides}/self-hosting.mdx +11 -12
- package/docs/v7/guides/troubleshooting.mdx +526 -0
- package/docs/v7/guides/vitest-plugin.mdx +477 -0
- package/docs/v7/guides/vitest.mdx +535 -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 +342 -0
- package/docs/v7/presets/chrome-extension.mdx +223 -0
- package/docs/v7/presets/chrome.mdx +287 -0
- package/docs/v7/presets/electron.mdx +435 -0
- package/docs/v7/presets/vscode.mdx +398 -0
- package/docs/v7/presets/webapp.mdx +396 -0
- package/docs/v7/progressive-apis/CORE.md +459 -0
- package/docs/v7/progressive-apis/HOOKS.md +360 -0
- package/docs/v7/progressive-apis/PROGRESSIVE_DISCLOSURE.md +230 -0
- package/docs/v7/progressive-apis/PROVISION.md +266 -0
- package/eslint.config.js +19 -1
- package/interfaces/cli/lib/base.js +10 -4
- package/interfaces/logger.js +2 -1
- package/interfaces/shared-test-state.mjs +69 -0
- package/interfaces/vitest-plugin.mjs +830 -0
- package/package.json +29 -5
- package/schema.json +8 -29
- package/scripts/view-test-results.mjs +96 -0
- package/sdk-log-formatter.js +714 -0
- package/sdk.d.ts +1028 -0
- package/sdk.js +2567 -0
- package/{.github/workflows/self-hosted.yml → self-hosted.yml} +13 -4
- package/setup/aws/cloudformation.yaml +9 -2
- package/src/core/Dashcam.js +469 -0
- package/src/core/index.d.ts +150 -0
- package/src/core/index.js +12 -0
- package/src/presets/index.mjs +331 -0
- package/src/vitest/extended.mjs +108 -0
- package/src/vitest/hooks.d.ts +119 -0
- package/src/vitest/hooks.mjs +298 -0
- package/src/vitest/index.mjs +64 -0
- package/src/vitest/lifecycle.mjs +277 -0
- package/src/vitest/utils.mjs +150 -0
- package/test/dashcam.test.js +137 -0
- package/test/mcp-example-test.yaml +27 -0
- package/testdriver/acceptance-sdk/QUICK_REFERENCE.md +61 -0
- package/testdriver/acceptance-sdk/README.md +128 -0
- package/testdriver/acceptance-sdk/TEST_REPORTING.md +245 -0
- package/testdriver/acceptance-sdk/assert.test.mjs +26 -0
- package/testdriver/acceptance-sdk/auto-cache-key-demo.test.mjs +56 -0
- package/testdriver/acceptance-sdk/chrome-extension.test.mjs +89 -0
- package/testdriver/acceptance-sdk/drag-and-drop.test.mjs +58 -0
- package/testdriver/acceptance-sdk/element-not-found.test.mjs +25 -0
- package/testdriver/acceptance-sdk/exec-js.test.mjs +43 -0
- package/testdriver/acceptance-sdk/exec-output.test.mjs +59 -0
- package/testdriver/acceptance-sdk/exec-pwsh.test.mjs +57 -0
- package/testdriver/acceptance-sdk/focus-window.test.mjs +36 -0
- package/testdriver/acceptance-sdk/formatted-logging.test.mjs +26 -0
- package/testdriver/acceptance-sdk/hooks-example.test.mjs +38 -0
- package/testdriver/acceptance-sdk/hover-image.test.mjs +34 -0
- package/testdriver/acceptance-sdk/hover-text-with-description.test.mjs +38 -0
- package/testdriver/acceptance-sdk/hover-text.test.mjs +27 -0
- package/testdriver/acceptance-sdk/match-image.test.mjs +36 -0
- package/testdriver/acceptance-sdk/presets-example.test.mjs +87 -0
- package/testdriver/acceptance-sdk/press-keys.test.mjs +50 -0
- package/testdriver/acceptance-sdk/prompt.test.mjs +33 -0
- package/testdriver/acceptance-sdk/scroll-keyboard.test.mjs +38 -0
- package/testdriver/acceptance-sdk/scroll-until-image.test.mjs +39 -0
- package/testdriver/acceptance-sdk/scroll-until-text.test.mjs +28 -0
- package/testdriver/acceptance-sdk/scroll.test.mjs +41 -0
- package/testdriver/acceptance-sdk/setup/globalTeardown.mjs +11 -0
- package/testdriver/acceptance-sdk/setup/testHelpers.mjs +420 -0
- package/testdriver/acceptance-sdk/setup/vitestSetup.mjs +40 -0
- package/testdriver/acceptance-sdk/sully-ai.test.mjs +234 -0
- package/testdriver/acceptance-sdk/test-console-logs.test.mjs +42 -0
- package/testdriver/acceptance-sdk/type-checking-demo.js +49 -0
- package/testdriver/acceptance-sdk/type.test.mjs +45 -0
- package/verify-element-api.js +89 -0
- package/verify-types.js +0 -0
- package/vitest.config.example.js +19 -0
- package/vitest.config.mjs +66 -0
- package/vitest.config.mjs.bak +44 -0
- package/.github/workflows/acceptance-v6.yml +0 -169
- package/.vscode/mcp.json +0 -9
- package/docs/overview/comparison.mdx +0 -82
- package/testdriver/lifecycle/prerun.yaml +0 -17
- /package/{testdriver/examples/desktop/lifecycle/prerun.yaml → .env.example} +0 -0
- /package/{testdriver → _testdriver}/acceptance/assert.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/dashcam.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/embed.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/exec-js.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/exec-output.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/exec-shell.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/focus-window.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/hover-image.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/hover-text-with-description.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/hover-text.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/if-else.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/match-image.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/press-keys.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/prompt.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/remember.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/screenshots/cart.png +0 -0
- /package/{testdriver → _testdriver}/acceptance/scroll-keyboard.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/scroll-until-image.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/scroll-until-text.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/scroll.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/snippets/match-cart.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/type.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/failure.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/hover-text.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/lifecycle/postrun.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/lifecycle/prerun.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/secrets.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/dashcam-chrome.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/exec-pwsh-multiline.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/js-exception.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/js-promise.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/lifecycle/postrun.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/prompt-in-middle.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/prompt-nested.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/success-test.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/android/example.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/android/lifecycle/postrun.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/android/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/android/readme.md +0 -0
- /package/{testdriver → _testdriver}/examples/chrome-extension/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/desktop/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/vscode-extension/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/web/lifecycle/postrun.yaml +0 -0
- /package/docs/{account → v6/account}/dashboard.mdx +0 -0
- /package/docs/{account → v6/account}/enterprise.mdx +0 -0
- /package/docs/{account → v6/account}/pricing.mdx +0 -0
- /package/docs/{account → v6/account}/projects.mdx +0 -0
- /package/docs/{account → v6/account}/team.mdx +0 -0
- /package/docs/{action → v6/action}/ami.mdx +0 -0
- /package/docs/{action → v6/action}/performance.mdx +0 -0
- /package/docs/{action → v6/action}/secrets.mdx +0 -0
- /package/docs/{apps → v6/apps}/chrome-extensions.mdx +0 -0
- /package/docs/{apps → v6/apps}/desktop-apps.mdx +0 -0
- /package/docs/{apps → v6/apps}/mobile-apps.mdx +0 -0
- /package/docs/{apps → v6/apps}/static-websites.mdx +0 -0
- /package/docs/{apps → v6/apps}/tauri-apps.mdx +0 -0
- /package/docs/{bugs → v6/bugs}/jira.mdx +0 -0
- /package/docs/{cli → v6/cli}/overview.mdx +0 -0
- /package/docs/{commands → v6/commands}/assert.mdx +0 -0
- /package/docs/{commands → v6/commands}/exec.mdx +0 -0
- /package/docs/{commands → v6/commands}/focus-application.mdx +0 -0
- /package/docs/{commands → v6/commands}/hover-image.mdx +0 -0
- /package/docs/{commands → v6/commands}/hover-text.mdx +0 -0
- /package/docs/{commands → v6/commands}/if.mdx +0 -0
- /package/docs/{commands → v6/commands}/match-image.mdx +0 -0
- /package/docs/{commands → v6/commands}/press-keys.mdx +0 -0
- /package/docs/{commands → v6/commands}/remember.mdx +0 -0
- /package/docs/{commands → v6/commands}/run.mdx +0 -0
- /package/docs/{commands → v6/commands}/scroll-until-image.mdx +0 -0
- /package/docs/{commands → v6/commands}/scroll-until-text.mdx +0 -0
- /package/docs/{commands → v6/commands}/scroll.mdx +0 -0
- /package/docs/{commands → v6/commands}/type.mdx +0 -0
- /package/docs/{commands → v6/commands}/wait-for-image.mdx +0 -0
- /package/docs/{commands → v6/commands}/wait-for-text.mdx +0 -0
- /package/docs/{commands → v6/commands}/wait.mdx +0 -0
- /package/docs/{exporting → v6/exporting}/junit.mdx +0 -0
- /package/docs/{exporting → v6/exporting}/playwright.mdx +0 -0
- /package/docs/{features → v6/features}/auto-healing.mdx +0 -0
- /package/docs/{features → v6/features}/generation.mdx +0 -0
- /package/docs/{features → v6/features}/parallel-testing.mdx +0 -0
- /package/docs/{features → v6/features}/reusable-snippets.mdx +0 -0
- /package/docs/{features → v6/features}/selectorless.mdx +0 -0
- /package/docs/{features → v6/features}/visual-assertions.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/ci.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/cli.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/editing.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/playwright.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/running.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/vscode.mdx +0 -0
- /package/docs/{guide → v6/guide}/assertions.mdx +0 -0
- /package/docs/{guide → v6/guide}/authentication.mdx +0 -0
- /package/docs/{guide → v6/guide}/code.mdx +0 -0
- /package/docs/{guide → v6/guide}/locating.mdx +0 -0
- /package/docs/{guide → v6/guide}/protips.mdx +0 -0
- /package/docs/{guide → v6/guide}/variables.mdx +0 -0
- /package/docs/{guide → v6/guide}/waiting.mdx +0 -0
- /package/docs/{importing → v6/importing}/csv.mdx +0 -0
- /package/docs/{importing → v6/importing}/gherkin.mdx +0 -0
- /package/docs/{importing → v6/importing}/jira.mdx +0 -0
- /package/docs/{importing → v6/importing}/testrail.mdx +0 -0
- /package/docs/{integrations → v6/integrations}/electron.mdx +0 -0
- /package/docs/{integrations → v6/integrations}/netlify.mdx +0 -0
- /package/docs/{integrations → v6/integrations}/vercel.mdx +0 -0
- /package/docs/{interactive → v6/interactive}/explore.mdx +0 -0
- /package/docs/{interactive → v6/interactive}/run.mdx +0 -0
- /package/docs/{interactive → v6/interactive}/save.mdx +0 -0
- /package/docs/{overview → v6/overview}/faq.mdx +0 -0
- /package/docs/{overview → v6/overview}/performance.mdx +0 -0
- /package/docs/{overview → v6/overview}/quickstart.mdx +0 -0
- /package/docs/{overview → v6/overview}/what-is-testdriver.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/ai-chatbot.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/cookie-banner.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/file-upload.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/form-filling.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/log-in.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/pdf-generation.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/spell-check.mdx +0 -0
- /package/docs/{security → v6/security}/action.mdx +0 -0
- /package/docs/{security → v6/security}/agent.mdx +0 -0
- /package/docs/{security → v6/security}/platform.mdx +0 -0
- /package/docs/{tutorials → v6/tutorials}/advanced-test.mdx +0 -0
- /package/docs/{tutorials → v6/tutorials}/basic-test.mdx +0 -0
|
@@ -0,0 +1,298 @@
|
|
|
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 TestDriverSDK from '../../sdk.js';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Intercept console logs and write to a log file on the remote machine
|
|
25
|
+
* This allows test logs to appear in Dashcam recordings
|
|
26
|
+
* @param {TestDriver} client - TestDriver client instance
|
|
27
|
+
* @param {string} taskId - Unique task identifier for this test
|
|
28
|
+
*/
|
|
29
|
+
function setupConsoleInterceptor(client, taskId) {
|
|
30
|
+
// Store original console methods
|
|
31
|
+
const originalConsole = {
|
|
32
|
+
log: console.log,
|
|
33
|
+
error: console.error,
|
|
34
|
+
warn: console.warn,
|
|
35
|
+
info: console.info,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Determine log file path based on OS
|
|
39
|
+
const logPath = client.os === "windows"
|
|
40
|
+
? "C:\\Users\\testdriver\\Documents\\testdriver.log"
|
|
41
|
+
: "/tmp/testdriver.log";
|
|
42
|
+
|
|
43
|
+
// Store log path on client for later use
|
|
44
|
+
client._testLogPath = logPath;
|
|
45
|
+
|
|
46
|
+
// Track if we're currently writing to avoid infinite loops
|
|
47
|
+
let isWriting = false;
|
|
48
|
+
|
|
49
|
+
// Create wrapper that writes to log file
|
|
50
|
+
const createInterceptor = (level, originalMethod) => {
|
|
51
|
+
return function (...args) {
|
|
52
|
+
// Call original console method first
|
|
53
|
+
originalMethod.apply(console, args);
|
|
54
|
+
|
|
55
|
+
// Skip if already writing to avoid infinite loops
|
|
56
|
+
if (isWriting) return;
|
|
57
|
+
|
|
58
|
+
// Format the log message
|
|
59
|
+
const message = args
|
|
60
|
+
.map((arg) =>
|
|
61
|
+
typeof arg === "object"
|
|
62
|
+
? JSON.stringify(arg, null, 2)
|
|
63
|
+
: String(arg),
|
|
64
|
+
)
|
|
65
|
+
.join(" ");
|
|
66
|
+
|
|
67
|
+
// Also send to sandbox for immediate visibility
|
|
68
|
+
if (client.sandbox && client.sandbox.instanceSocketConnected) {
|
|
69
|
+
|
|
70
|
+
client.sandbox.send({
|
|
71
|
+
type: "output",
|
|
72
|
+
output: Buffer.from(message, "utf8").toString("base64"),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Replace console methods with interceptors
|
|
79
|
+
console.log = createInterceptor("log", originalConsole.log);
|
|
80
|
+
console.error = createInterceptor("error", originalConsole.error);
|
|
81
|
+
console.warn = createInterceptor("warn", originalConsole.warn);
|
|
82
|
+
console.info = createInterceptor("info", originalConsole.info);
|
|
83
|
+
|
|
84
|
+
// Store original methods and taskId on client for cleanup
|
|
85
|
+
client._consoleInterceptor = {
|
|
86
|
+
taskId,
|
|
87
|
+
original: originalConsole,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Use original console for this message
|
|
91
|
+
originalConsole.log(
|
|
92
|
+
`[useTestDriver] Console interceptor enabled for task: ${taskId}`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Remove console interceptor and restore original console methods
|
|
98
|
+
* @param {TestDriver} client - TestDriver client instance
|
|
99
|
+
*/
|
|
100
|
+
function removeConsoleInterceptor(client) {
|
|
101
|
+
if (client._consoleInterceptor) {
|
|
102
|
+
const { original, taskId } = client._consoleInterceptor;
|
|
103
|
+
|
|
104
|
+
// Restore original console methods
|
|
105
|
+
console.log = original.log;
|
|
106
|
+
console.error = original.error;
|
|
107
|
+
console.warn = original.warn;
|
|
108
|
+
console.info = original.info;
|
|
109
|
+
|
|
110
|
+
// Use original console for cleanup message
|
|
111
|
+
original.log(
|
|
112
|
+
`[useTestDriver] Console interceptor removed for task: ${taskId}`,
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// Clean up reference
|
|
116
|
+
delete client._consoleInterceptor;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Weak maps to store instances per test context
|
|
121
|
+
const testDriverInstances = new WeakMap();
|
|
122
|
+
const lifecycleHandlers = new WeakMap();
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Create a TestDriver client in a Vitest test with automatic lifecycle management
|
|
126
|
+
*
|
|
127
|
+
* @param {object} context - Vitest test context (from async (context) => {})
|
|
128
|
+
* @param {object} options - TestDriver options (passed directly to TestDriver constructor)
|
|
129
|
+
* @param {string} [options.apiKey] - TestDriver API key (defaults to process.env.TD_API_KEY)
|
|
130
|
+
* @param {boolean} [options.headless] - Run sandbox in headless mode
|
|
131
|
+
* @param {boolean} [options.newSandbox] - Create new sandbox
|
|
132
|
+
* @param {boolean} [options.autoConnect=true] - Automatically connect to sandbox
|
|
133
|
+
* @returns {TestDriver} TestDriver client instance
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* test('my test', async (context) => {
|
|
137
|
+
* const testdriver = TestDriver(context, { headless: true });
|
|
138
|
+
*
|
|
139
|
+
* // provision.chrome() automatically calls ready() and starts dashcam
|
|
140
|
+
* await testdriver.provision.chrome({ url: 'https://example.com' });
|
|
141
|
+
*
|
|
142
|
+
* await testdriver.find('Login button').click();
|
|
143
|
+
* });
|
|
144
|
+
*/
|
|
145
|
+
export function TestDriver(context, options = {}) {
|
|
146
|
+
if (!context || !context.task) {
|
|
147
|
+
throw new Error('TestDriver() requires Vitest context. Pass the context parameter from your test function: test("name", async (context) => { ... })');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Return existing instance if already created for this test
|
|
151
|
+
if (testDriverInstances.has(context.task)) {
|
|
152
|
+
return testDriverInstances.get(context.task);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Get global plugin options if available
|
|
156
|
+
const pluginOptions = globalThis.__testdriverPlugin?.state?.testDriverOptions || {};
|
|
157
|
+
|
|
158
|
+
// Merge options: plugin global options < test-specific options
|
|
159
|
+
const mergedOptions = { ...pluginOptions, ...options };
|
|
160
|
+
|
|
161
|
+
// Extract TestDriver-specific options
|
|
162
|
+
const apiKey = mergedOptions.apiKey || process.env.TD_API_KEY;
|
|
163
|
+
|
|
164
|
+
// Build config for TestDriverSDK constructor
|
|
165
|
+
const config = { ...mergedOptions };
|
|
166
|
+
delete config.apiKey;
|
|
167
|
+
|
|
168
|
+
// Use TD_API_ROOT from environment if not provided in config
|
|
169
|
+
if (!config.apiRoot && process.env.TD_API_ROOT) {
|
|
170
|
+
config.apiRoot = process.env.TD_API_ROOT;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const testdriver = new TestDriverSDK(apiKey, config);
|
|
174
|
+
testdriver.__vitestContext = context.task;
|
|
175
|
+
testDriverInstances.set(context.task, testdriver);
|
|
176
|
+
|
|
177
|
+
// Auto-connect if enabled (default: true)
|
|
178
|
+
const autoConnect = config.autoConnect !== undefined ? config.autoConnect : true;
|
|
179
|
+
if (autoConnect) {
|
|
180
|
+
testdriver.__connectionPromise = (async () => {
|
|
181
|
+
try {
|
|
182
|
+
console.log('[testdriver] Connecting to sandbox...');
|
|
183
|
+
await testdriver.auth();
|
|
184
|
+
await testdriver.connect();
|
|
185
|
+
console.log('[testdriver] ✅ Connected to sandbox');
|
|
186
|
+
|
|
187
|
+
// Set up console interceptor after connection
|
|
188
|
+
setupConsoleInterceptor(testdriver, context.task.id);
|
|
189
|
+
|
|
190
|
+
// Create the log file on the remote machine
|
|
191
|
+
const shell = testdriver.os === "windows" ? "pwsh" : "sh";
|
|
192
|
+
const logPath = testdriver.os === "windows"
|
|
193
|
+
? "C:\\Users\\testdriver\\Documents\\testdriver.log"
|
|
194
|
+
: "/tmp/testdriver.log";
|
|
195
|
+
|
|
196
|
+
const createLogCmd = testdriver.os === "windows"
|
|
197
|
+
? `New-Item -ItemType File -Path "${logPath}" -Force | Out-Null`
|
|
198
|
+
: `touch ${logPath}`;
|
|
199
|
+
|
|
200
|
+
await testdriver.exec(shell, createLogCmd, 10000, true);
|
|
201
|
+
console.log('[testdriver] ✅ Created log file:', logPath);
|
|
202
|
+
|
|
203
|
+
// Add automatic log tracking when dashcam starts
|
|
204
|
+
// Store original start method
|
|
205
|
+
const originalDashcamStart = testdriver.dashcam.start.bind(testdriver.dashcam);
|
|
206
|
+
testdriver.dashcam.start = async function() {
|
|
207
|
+
// Call original start (which handles auth)
|
|
208
|
+
await originalDashcamStart();
|
|
209
|
+
|
|
210
|
+
// Add log file tracking after dashcam starts
|
|
211
|
+
try {
|
|
212
|
+
await testdriver.dashcam.addFileLog(logPath, "TestDriver Log");
|
|
213
|
+
console.log('[testdriver] ✅ Added log file to dashcam tracking');
|
|
214
|
+
} catch (error) {
|
|
215
|
+
console.warn('[testdriver] ⚠️ Failed to add log tracking:', error.message);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error('[testdriver] Error during setup:', error);
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
})();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Register cleanup handler with dashcam.stop()
|
|
226
|
+
if (!lifecycleHandlers.has(context.task)) {
|
|
227
|
+
const cleanup = async () => {
|
|
228
|
+
console.log('[testdriver] Cleaning up TestDriver client...');
|
|
229
|
+
try {
|
|
230
|
+
// Stop dashcam if it was started
|
|
231
|
+
if (testdriver._dashcam && testdriver._dashcam.recording) {
|
|
232
|
+
try {
|
|
233
|
+
const dashcamUrl = await testdriver.dashcam.stop();
|
|
234
|
+
console.log('🎥 Dashcam URL:', dashcamUrl);
|
|
235
|
+
|
|
236
|
+
// Write dashcam URL to file for the reporter (cross-process communication)
|
|
237
|
+
if (dashcamUrl) {
|
|
238
|
+
const testId = context.task.id;
|
|
239
|
+
const platform = testdriver.os || 'linux';
|
|
240
|
+
const testFile = context.task.file?.filepath || context.task.file?.name || 'unknown';
|
|
241
|
+
|
|
242
|
+
// Create results directory if it doesn't exist
|
|
243
|
+
const resultsDir = path.join(os.tmpdir(), 'testdriver-results');
|
|
244
|
+
if (!fs.existsSync(resultsDir)) {
|
|
245
|
+
fs.mkdirSync(resultsDir, { recursive: true });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Write test result file
|
|
249
|
+
const testResultFile = path.join(resultsDir, `${testId}.json`);
|
|
250
|
+
const testResult = {
|
|
251
|
+
dashcamUrl,
|
|
252
|
+
platform,
|
|
253
|
+
testFile,
|
|
254
|
+
testOrder: 0,
|
|
255
|
+
sessionId: testdriver.getSessionId(),
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
fs.writeFileSync(testResultFile, JSON.stringify(testResult, null, 2));
|
|
259
|
+
console.log(`[testdriver] ✅ Wrote dashcam URL to ${testResultFile}`);
|
|
260
|
+
|
|
261
|
+
// Also register in memory if plugin is available
|
|
262
|
+
if (globalThis.__testdriverPlugin?.registerDashcamUrl) {
|
|
263
|
+
globalThis.__testdriverPlugin.registerDashcamUrl(testId, dashcamUrl, platform);
|
|
264
|
+
console.log(`[testdriver] ✅ Registered dashcam URL in memory for test ${testId}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
} catch (error) {
|
|
268
|
+
// Log more detailed error information for debugging
|
|
269
|
+
console.error('❌ Failed to stop dashcam:', error.name || error.constructor?.name || 'Error');
|
|
270
|
+
if (error.message) console.error(' Message:', error.message);
|
|
271
|
+
// NotFoundError during cleanup is expected if sandbox already terminated
|
|
272
|
+
if (error.name === 'NotFoundError' || error.responseData?.error === 'NotFoundError') {
|
|
273
|
+
console.log(' ℹ️ Sandbox session already terminated - dashcam stop skipped');
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Remove console interceptor before disconnecting
|
|
279
|
+
removeConsoleInterceptor(testdriver);
|
|
280
|
+
|
|
281
|
+
// Wait for connection to finish if it was initiated
|
|
282
|
+
if (testdriver.__connectionPromise) {
|
|
283
|
+
await testdriver.__connectionPromise.catch(() => {}); // Ignore connection errors during cleanup
|
|
284
|
+
}
|
|
285
|
+
await testdriver.disconnect();
|
|
286
|
+
console.log('✅ Client disconnected');
|
|
287
|
+
} catch (error) {
|
|
288
|
+
console.error('Error disconnecting client:', error);
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
lifecycleHandlers.set(context.task, cleanup);
|
|
292
|
+
|
|
293
|
+
// Vitest will call this automatically after the test
|
|
294
|
+
context.onTestFinished?.(cleanup);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return testdriver;
|
|
298
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TestDriver Vitest Integration
|
|
3
|
+
*
|
|
4
|
+
* Main entry point for the TestDriver Vitest plugin.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* // Basic usage - auto-managed lifecycle
|
|
8
|
+
* import { TestDriver } from 'testdriverai/vitest';
|
|
9
|
+
*
|
|
10
|
+
* test('my test', async (context) => {
|
|
11
|
+
* const testdriver = TestDriver(context, { headless: true });
|
|
12
|
+
* await testdriver.provision.chrome({ url: 'https://example.com' });
|
|
13
|
+
* await testdriver.find('Login button').click();
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // With extended test functions (it.once for setup steps)
|
|
18
|
+
* import { describe, it, expect, TestDriver } from 'testdriverai/vitest';
|
|
19
|
+
*
|
|
20
|
+
* describe('My Suite', () => {
|
|
21
|
+
* it.once('launch app', async (context) => {
|
|
22
|
+
* const testdriver = TestDriver(context);
|
|
23
|
+
* await testdriver.provision.chrome({ url: 'https://example.com' });
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* it('click button', async (context) => {
|
|
27
|
+
* const testdriver = TestDriver(context);
|
|
28
|
+
* await testdriver.find('Button').click();
|
|
29
|
+
* });
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* // Using lifecycle helpers directly
|
|
34
|
+
* import { TestDriver, launchChrome, waitForPage } from 'testdriverai/vitest';
|
|
35
|
+
*
|
|
36
|
+
* test('custom setup', async (context) => {
|
|
37
|
+
* const testdriver = TestDriver(context);
|
|
38
|
+
* await testdriver.ready();
|
|
39
|
+
* await launchChrome(testdriver, 'https://example.com', { guest: true });
|
|
40
|
+
* await waitForPage(testdriver, 'Welcome');
|
|
41
|
+
* });
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
// Core TestDriver hook
|
|
45
|
+
export { TestDriver } from './hooks.mjs';
|
|
46
|
+
|
|
47
|
+
// Extended Vitest functions
|
|
48
|
+
export {
|
|
49
|
+
afterAll, beforeAll, describe, expect, getTestDriver,
|
|
50
|
+
isReconnected, it,
|
|
51
|
+
test
|
|
52
|
+
} from './extended.mjs';
|
|
53
|
+
|
|
54
|
+
// Lifecycle helpers
|
|
55
|
+
export {
|
|
56
|
+
addDashcamLog, authDashcam, launchChrome, launchChromeExtension, launchChromeForTesting, runPostrun, runPrerun, runPrerunChromeExtension, runPrerunChromeForTesting, startDashcam,
|
|
57
|
+
stopDashcam, waitForPage
|
|
58
|
+
} from './lifecycle.mjs';
|
|
59
|
+
|
|
60
|
+
// Utility functions
|
|
61
|
+
export {
|
|
62
|
+
generateTestId, retryAsync, setupEventLogging, sleep, waitFor
|
|
63
|
+
} from './utils.mjs';
|
|
64
|
+
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lifecycle Helpers for TestDriver Vitest Plugin
|
|
3
|
+
*
|
|
4
|
+
* Provides reusable lifecycle hook functions for common test patterns.
|
|
5
|
+
* These are thin wrappers around the Dashcam class.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* import { launchChrome, waitForPage } from 'testdriverai/vitest';
|
|
9
|
+
*
|
|
10
|
+
* test('my test', async (context) => {
|
|
11
|
+
* const testdriver = TestDriver(context);
|
|
12
|
+
* await testdriver.provision.chrome({ url: 'https://example.com' });
|
|
13
|
+
*
|
|
14
|
+
* // Or use manual lifecycle helpers
|
|
15
|
+
* await launchChrome(testdriver, 'https://other-site.com');
|
|
16
|
+
* await waitForPage(testdriver, 'Welcome');
|
|
17
|
+
* });
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import Dashcam from '../core/Dashcam.js';
|
|
21
|
+
|
|
22
|
+
// Module-level cache to maintain Dashcam instance state across helper calls
|
|
23
|
+
const dashcamInstances = new WeakMap();
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get or create Dashcam instance for a client
|
|
27
|
+
* @private
|
|
28
|
+
* @param {TestDriver} client
|
|
29
|
+
* @param {object} options
|
|
30
|
+
* @returns {Dashcam}
|
|
31
|
+
*/
|
|
32
|
+
function getDashcam(client, options = {}) {
|
|
33
|
+
if (!dashcamInstances.has(client)) {
|
|
34
|
+
dashcamInstances.set(client, new Dashcam(client, options));
|
|
35
|
+
}
|
|
36
|
+
return dashcamInstances.get(client);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Authenticate dashcam with API key
|
|
41
|
+
* @param {TestDriver} client - TestDriver client
|
|
42
|
+
* @param {string} apiKey - Dashcam API key (default from environment)
|
|
43
|
+
*/
|
|
44
|
+
export async function authDashcam(client, apiKey) {
|
|
45
|
+
const dashcam = getDashcam(client, { apiKey });
|
|
46
|
+
await dashcam.auth();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Add log file tracking to dashcam
|
|
51
|
+
* @param {TestDriver} client - TestDriver client
|
|
52
|
+
* @param {string} logName - Name for the log in dashcam (default: "TestDriver Log")
|
|
53
|
+
*/
|
|
54
|
+
export async function addDashcamLog(client, logName = "TestDriver Log") {
|
|
55
|
+
const dashcam = getDashcam(client);
|
|
56
|
+
const logPath = client.os === "windows"
|
|
57
|
+
? "C:\\Users\\testdriver\\Documents\\testdriver.log"
|
|
58
|
+
: "/tmp/testdriver.log";
|
|
59
|
+
|
|
60
|
+
// Create log file first
|
|
61
|
+
const shell = client.os === "windows" ? "pwsh" : "sh";
|
|
62
|
+
if (client.os === "windows") {
|
|
63
|
+
await client.exec(shell, `New-Item -ItemType File -Path "${logPath}" -Force`, 10000, true);
|
|
64
|
+
} else {
|
|
65
|
+
await client.exec(shell, `touch ${logPath}`, 10000, true);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
await dashcam.addFileLog(logPath, logName);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Start dashcam recording
|
|
73
|
+
* @param {TestDriver} client - TestDriver client
|
|
74
|
+
*/
|
|
75
|
+
export async function startDashcam(client) {
|
|
76
|
+
const dashcam = getDashcam(client);
|
|
77
|
+
await dashcam.start();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Stop dashcam recording and retrieve URL
|
|
82
|
+
* @param {TestDriver} client - TestDriver client
|
|
83
|
+
* @returns {Promise<string|null>} Dashcam URL if available
|
|
84
|
+
*/
|
|
85
|
+
export async function stopDashcam(client) {
|
|
86
|
+
console.log("🎬 Stopping dashcam and retrieving URL...");
|
|
87
|
+
const dashcam = getDashcam(client);
|
|
88
|
+
const url = await dashcam.stop();
|
|
89
|
+
|
|
90
|
+
if (url) {
|
|
91
|
+
console.log("✅ Found dashcam URL:", url);
|
|
92
|
+
console.log("🎥 Dashcam URL:", url);
|
|
93
|
+
} else {
|
|
94
|
+
console.warn("⚠️ No replay URL found in dashcam output");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return url;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Launch Chrome browser
|
|
102
|
+
* @param {TestDriver} client - TestDriver client
|
|
103
|
+
* @param {string} url - URL to open
|
|
104
|
+
* @param {object} options - Browser options
|
|
105
|
+
* @param {boolean} options.guest - Launch in guest mode (default: true)
|
|
106
|
+
* @param {boolean} options.maximized - Start maximized (default: true)
|
|
107
|
+
*/
|
|
108
|
+
export async function launchChrome(client, url = "about:blank", options = {}) {
|
|
109
|
+
const { guest = true, maximized = true } = options;
|
|
110
|
+
const shell = client.os === "windows" ? "pwsh" : "sh";
|
|
111
|
+
|
|
112
|
+
const guestFlag = guest ? "--guest" : "";
|
|
113
|
+
const maxFlag = maximized ? "--start-maximized" : "";
|
|
114
|
+
|
|
115
|
+
if (client.os === "windows") {
|
|
116
|
+
const args = [maxFlag, guestFlag, `"${url}"`].filter(Boolean).join('", "');
|
|
117
|
+
await client.exec(
|
|
118
|
+
"pwsh",
|
|
119
|
+
`Start-Process "C:/Program Files/Google/Chrome/Application/chrome.exe" -ArgumentList "${args}"`,
|
|
120
|
+
30000,
|
|
121
|
+
);
|
|
122
|
+
} else {
|
|
123
|
+
const flags = [maxFlag, "--disable-fre", "--no-default-browser-check", "--no-first-run", guestFlag].filter(Boolean).join(" ");
|
|
124
|
+
await client.exec(
|
|
125
|
+
shell,
|
|
126
|
+
`google-chrome ${flags} "${url}" >/dev/null 2>&1 &`,
|
|
127
|
+
30000,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Launch Chrome for Testing browser
|
|
134
|
+
* @param {TestDriver} client - TestDriver client
|
|
135
|
+
* @param {string} url - URL to open
|
|
136
|
+
* @param {object} options - Browser options
|
|
137
|
+
*/
|
|
138
|
+
export async function launchChromeForTesting(client, url = "about:blank", options = {}) {
|
|
139
|
+
const { guest = true, maximized = true } = options;
|
|
140
|
+
const shell = client.os === "windows" ? "pwsh" : "sh";
|
|
141
|
+
|
|
142
|
+
const guestFlag = guest ? "--guest" : "";
|
|
143
|
+
const maxFlag = maximized ? "--start-maximized" : "";
|
|
144
|
+
|
|
145
|
+
if (client.os === "windows") {
|
|
146
|
+
// Fallback to regular Chrome on Windows
|
|
147
|
+
await launchChrome(client, url, options);
|
|
148
|
+
} else {
|
|
149
|
+
const flags = [maxFlag, "--disable-fre", "--no-default-browser-check", "--no-first-run", guestFlag].filter(Boolean).join(" ");
|
|
150
|
+
await client.exec(
|
|
151
|
+
shell,
|
|
152
|
+
`chrome-for-testing ${flags} "${url}" >/dev/null 2>&1 &`,
|
|
153
|
+
30000,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Launch Chrome with a Chrome extension loaded
|
|
160
|
+
* @param {TestDriver} client - TestDriver client
|
|
161
|
+
* @param {string} extensionId - Chrome Web Store extension ID
|
|
162
|
+
* @param {string} url - URL to open
|
|
163
|
+
* @example
|
|
164
|
+
* // Launch with uBlock Origin extension
|
|
165
|
+
* await launchChromeExtension(client, "cjpalhdlnbpafiamejdnhcphjbkeiagm");
|
|
166
|
+
*/
|
|
167
|
+
export async function launchChromeExtension(client, extensionId, url = "about:blank") {
|
|
168
|
+
const shell = client.os === "windows" ? "pwsh" : "sh";
|
|
169
|
+
|
|
170
|
+
if (client.os === "windows") {
|
|
171
|
+
await client.exec(
|
|
172
|
+
"pwsh",
|
|
173
|
+
`Start-Process "C:/Program Files/Google/Chrome/Application/chrome.exe" -ArgumentList "--start-maximized", "--load-extension=${extensionId}", "${url}"`,
|
|
174
|
+
30000,
|
|
175
|
+
);
|
|
176
|
+
} else {
|
|
177
|
+
await client.exec(
|
|
178
|
+
shell,
|
|
179
|
+
`chrome-for-testing --start-maximized --disable-fre --no-default-browser-check --no-first-run --load-extension=${extensionId} "${url}" >/dev/null 2>&1 &`,
|
|
180
|
+
30000,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Wait for page to load by polling for text
|
|
187
|
+
* @param {TestDriver} client - TestDriver client
|
|
188
|
+
* @param {string} text - Text to wait for
|
|
189
|
+
* @param {number} maxAttempts - Maximum number of attempts (default: 60)
|
|
190
|
+
* @param {number} pollInterval - Interval between polls in ms (default: 5000)
|
|
191
|
+
* @returns {Promise<boolean>} True if text was found
|
|
192
|
+
*/
|
|
193
|
+
export async function waitForPage(client, text, maxAttempts = 60, pollInterval = 5000) {
|
|
194
|
+
console.log("Waiting for page to load, looking for text:", text);
|
|
195
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
196
|
+
const element = await client.find(text);
|
|
197
|
+
if (element.found()) {
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
201
|
+
}
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Run standard prerun lifecycle hooks
|
|
207
|
+
* Authenticates, starts dashcam, launches Chrome
|
|
208
|
+
* @param {TestDriver} client - TestDriver client
|
|
209
|
+
* @param {object} options - Options
|
|
210
|
+
* @param {string} options.url - URL to open (default: sandbox)
|
|
211
|
+
* @param {string} options.waitForText - Text to wait for after page load
|
|
212
|
+
*/
|
|
213
|
+
export async function runPrerun(client, options = {}) {
|
|
214
|
+
const {
|
|
215
|
+
url = "http://testdriver-sandbox.vercel.app/",
|
|
216
|
+
waitForText = "TestDriver.ai Sandbox"
|
|
217
|
+
} = options;
|
|
218
|
+
|
|
219
|
+
await authDashcam(client);
|
|
220
|
+
await addDashcamLog(client);
|
|
221
|
+
await startDashcam(client);
|
|
222
|
+
await launchChrome(client, url);
|
|
223
|
+
if (waitForText) {
|
|
224
|
+
await waitForPage(client, waitForText);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Run prerun with Chrome for Testing
|
|
230
|
+
* @param {TestDriver} client - TestDriver client
|
|
231
|
+
* @param {object} options - Options
|
|
232
|
+
*/
|
|
233
|
+
export async function runPrerunChromeForTesting(client, options = {}) {
|
|
234
|
+
const {
|
|
235
|
+
url = "http://testdriver-sandbox.vercel.app/",
|
|
236
|
+
waitForText = "TestDriver.ai Sandbox"
|
|
237
|
+
} = options;
|
|
238
|
+
|
|
239
|
+
await authDashcam(client);
|
|
240
|
+
await addDashcamLog(client);
|
|
241
|
+
await startDashcam(client);
|
|
242
|
+
await launchChromeForTesting(client, url);
|
|
243
|
+
if (waitForText) {
|
|
244
|
+
await waitForPage(client, waitForText);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Run prerun with Chrome extension
|
|
250
|
+
* @param {TestDriver} client - TestDriver client
|
|
251
|
+
* @param {string} extensionId - Chrome extension ID
|
|
252
|
+
* @param {object} options - Options
|
|
253
|
+
*/
|
|
254
|
+
export async function runPrerunChromeExtension(client, extensionId, options = {}) {
|
|
255
|
+
const {
|
|
256
|
+
url = "http://testdriver-sandbox.vercel.app/",
|
|
257
|
+
waitForText = "TestDriver.ai Sandbox"
|
|
258
|
+
} = options;
|
|
259
|
+
|
|
260
|
+
await authDashcam(client);
|
|
261
|
+
await addDashcamLog(client);
|
|
262
|
+
await startDashcam(client);
|
|
263
|
+
await launchChromeExtension(client, extensionId, url);
|
|
264
|
+
if (waitForText) {
|
|
265
|
+
await waitForPage(client, waitForText);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Run standard postrun lifecycle hooks
|
|
271
|
+
* Stops dashcam and returns URL
|
|
272
|
+
* @param {TestDriver} client - TestDriver client
|
|
273
|
+
* @returns {Promise<string|null>} Dashcam URL if available
|
|
274
|
+
*/
|
|
275
|
+
export async function runPostrun(client) {
|
|
276
|
+
return await stopDashcam(client);
|
|
277
|
+
}
|