testdriverai 6.2.1 → 7.0.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/MIGRATION.md +389 -0
- package/PLUGIN_MIGRATION.md +222 -0
- package/PROMPT_CACHE.md +200 -0
- package/SDK_LOGGING.md +222 -0
- package/SDK_MIGRATION.md +474 -0
- package/SDK_README.md +1122 -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 +258 -68
- 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 +143 -188
- package/agent/lib/redraw.js +6 -3
- package/agent/lib/sandbox.js +19 -5
- package/agent/lib/sdk.js +1 -0
- package/agent/lib/system.js +0 -3
- package/agent/lib/validation.js +1 -7
- package/debug-locate-response.js +82 -0
- package/debug-screenshot-1763401388589.png +0 -0
- package/debugger/index.html +16 -5
- package/docs/ARCHITECTURE.md +424 -0
- package/docs/AWESOME_LOGS_QUICK_REF.md +100 -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 +232 -152
- 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/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/pressKeys.mdx +349 -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/quickstart.mdx +199 -0
- package/docs/v7/guides/migration.mdx +562 -0
- package/docs/{getting-started → v7/guides}/self-hosting.mdx +11 -12
- package/docs/v7/playwright.mdx +342 -0
- package/eslint.config.js +19 -1
- package/examples/run-tests-with-recording.sh +70 -0
- package/examples/screenshot-example.js +63 -0
- package/examples/sdk-awesome-logs-demo.js +177 -0
- package/examples/sdk-cache-thresholds.js +96 -0
- package/examples/sdk-element-properties.js +155 -0
- package/examples/sdk-simple-example.js +65 -0
- package/examples/test-recording-example.test.js +166 -0
- 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 +744 -0
- package/mcp-server/AI_GUIDELINES.md +57 -0
- package/package.json +18 -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 +735 -0
- package/sdk.js +1906 -0
- package/{.github/workflows/self-hosted.yml → self-hosted.yml} +13 -4
- package/setup/aws/cloudformation.yaml +9 -2
- package/test/mcp-example-test.yaml +27 -0
- package/test-find-api.js +73 -0
- package/test-prompt-cache.js +96 -0
- package/test-sandbox-render.js +28 -0
- package/test-sdk-methods.js +15 -0
- package/test-sdk-refactor.js +53 -0
- package/test-stack-trace.mjs +57 -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 +44 -0
- package/testdriver/acceptance-sdk/drag-and-drop.test.mjs +70 -0
- package/testdriver/acceptance-sdk/element-not-found.test.mjs +38 -0
- package/testdriver/acceptance-sdk/exec-js.test.mjs +55 -0
- package/testdriver/acceptance-sdk/exec-output.test.mjs +71 -0
- package/testdriver/acceptance-sdk/exec-pwsh.test.mjs +69 -0
- package/testdriver/acceptance-sdk/focus-window.test.mjs +48 -0
- package/testdriver/acceptance-sdk/formatted-logging.test.mjs +41 -0
- package/testdriver/acceptance-sdk/hover-image.test.mjs +43 -0
- package/testdriver/acceptance-sdk/hover-text-with-description.test.mjs +50 -0
- package/testdriver/acceptance-sdk/hover-text.test.mjs +41 -0
- package/testdriver/acceptance-sdk/match-image.test.mjs +48 -0
- package/testdriver/acceptance-sdk/press-keys.test.mjs +64 -0
- package/testdriver/acceptance-sdk/prompt.test.mjs +45 -0
- package/testdriver/acceptance-sdk/scroll-keyboard.test.mjs +52 -0
- package/testdriver/acceptance-sdk/scroll-until-image.test.mjs +51 -0
- package/testdriver/acceptance-sdk/scroll-until-text.test.mjs +42 -0
- package/testdriver/acceptance-sdk/scroll.test.mjs +50 -0
- package/testdriver/acceptance-sdk/setup/globalTeardown.mjs +11 -0
- package/testdriver/acceptance-sdk/setup/lifecycleHelpers.mjs +239 -0
- package/testdriver/acceptance-sdk/setup/testHelpers.mjs +648 -0
- package/testdriver/acceptance-sdk/setup/vitestSetup.mjs +40 -0
- package/testdriver/acceptance-sdk/type-checking-demo.js +49 -0
- package/testdriver/acceptance-sdk/type.test.mjs +84 -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 +65 -0
- package/vitest.config.mjs.bak +44 -0
- package/.github/workflows/acceptance-v6.yml +0 -169
- 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,744 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { setTestRunInfo } from "./shared-test-state.mjs";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Timeout wrapper for promises
|
|
9
|
+
* @param {Promise} promise - Promise to wrap
|
|
10
|
+
* @param {number} timeoutMs - Timeout in milliseconds
|
|
11
|
+
* @param {string} operationName - Name of operation for error message
|
|
12
|
+
* @returns {Promise} Promise that rejects if timeout is reached
|
|
13
|
+
*/
|
|
14
|
+
function withTimeout(promise, timeoutMs, operationName) {
|
|
15
|
+
return Promise.race([
|
|
16
|
+
promise,
|
|
17
|
+
new Promise((_, reject) =>
|
|
18
|
+
setTimeout(
|
|
19
|
+
() =>
|
|
20
|
+
reject(new Error(`${operationName} timed out after ${timeoutMs}ms`)),
|
|
21
|
+
timeoutMs,
|
|
22
|
+
),
|
|
23
|
+
),
|
|
24
|
+
]);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Vitest Plugin for TestDriver
|
|
29
|
+
*
|
|
30
|
+
* Records test runs, test cases, and associates them with dashcam recordings.
|
|
31
|
+
* Uses plugin architecture for better global state management.
|
|
32
|
+
*
|
|
33
|
+
* ## How it works:
|
|
34
|
+
*
|
|
35
|
+
* 1. **Plugin State**: All state is managed in a single `pluginState` object
|
|
36
|
+
* - No class instances or complex scoping
|
|
37
|
+
* - Easy to access from anywhere in the plugin
|
|
38
|
+
* - Dashcam URLs tracked in memory (no temp files!)
|
|
39
|
+
*
|
|
40
|
+
* 2. **Dashcam URL Registration**: Tests register dashcam URLs via simple API
|
|
41
|
+
* - `globalThis.__testdriverPlugin.registerDashcamUrl(testId, url, platform)`
|
|
42
|
+
* - No file system operations
|
|
43
|
+
* - No complex matching logic
|
|
44
|
+
* - Direct association via test ID
|
|
45
|
+
*
|
|
46
|
+
* 3. **Test Recording Flow**:
|
|
47
|
+
* - `onTestRunStart`: Create test run record
|
|
48
|
+
* - `onTestCaseReady`: Track test start time
|
|
49
|
+
* - `onTestCaseResult`: Record individual test result (immediate)
|
|
50
|
+
* - `onTestRunEnd`: Complete test run with final stats
|
|
51
|
+
*
|
|
52
|
+
* 4. **Platform Detection**: Automatically detects platform from SDK client
|
|
53
|
+
* - No manual configuration needed
|
|
54
|
+
* - Stored when dashcam URL is registered
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
// Shared state that can be imported by both the reporter and setup files
|
|
58
|
+
export const pluginState = {
|
|
59
|
+
testRun: null,
|
|
60
|
+
testRunId: null,
|
|
61
|
+
client: null,
|
|
62
|
+
startTime: null,
|
|
63
|
+
testCases: new Map(),
|
|
64
|
+
token: null,
|
|
65
|
+
detectedPlatform: null,
|
|
66
|
+
pendingTestCaseRecords: new Set(),
|
|
67
|
+
ciProvider: null,
|
|
68
|
+
gitInfo: {},
|
|
69
|
+
apiKey: null,
|
|
70
|
+
apiRoot: null,
|
|
71
|
+
// Dashcam URL tracking (in-memory, no files needed!)
|
|
72
|
+
dashcamUrls: new Map(), // testId -> dashcamUrl
|
|
73
|
+
lastDashcamUrl: null, // Fallback for when test ID isn't available
|
|
74
|
+
// Suite-level test run tracking
|
|
75
|
+
suiteTestRuns: new Map(), // suiteId -> { runId, testRunDbId, token }
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Export functions that can be used by the reporter or tests
|
|
79
|
+
export function registerDashcamUrl(testId, url, platform) {
|
|
80
|
+
console.log(`[Plugin] Registering dashcam URL for test ${testId}:`, url);
|
|
81
|
+
pluginState.dashcamUrls.set(testId, { url, platform });
|
|
82
|
+
pluginState.lastDashcamUrl = url;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function getDashcamUrl(testId) {
|
|
86
|
+
return pluginState.dashcamUrls.get(testId);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function clearDashcamUrls() {
|
|
90
|
+
pluginState.dashcamUrls.clear();
|
|
91
|
+
pluginState.lastDashcamUrl = null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function getSuiteTestRun(suiteId) {
|
|
95
|
+
return pluginState.suiteTestRuns.get(suiteId);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function setSuiteTestRun(suiteId, runData) {
|
|
99
|
+
console.log(`[Plugin] Setting test run for suite ${suiteId}:`, runData);
|
|
100
|
+
pluginState.suiteTestRuns.set(suiteId, runData);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function clearSuiteTestRun(suiteId) {
|
|
104
|
+
pluginState.suiteTestRuns.delete(suiteId);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function getPluginState() {
|
|
108
|
+
return pluginState;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Export API helper functions for direct use from tests
|
|
112
|
+
export async function authenticateWithApiKey(apiKey, apiRoot) {
|
|
113
|
+
const url = `${apiRoot}/auth/exchange-api-key`;
|
|
114
|
+
const response = await withTimeout(
|
|
115
|
+
fetch(url, {
|
|
116
|
+
method: "POST",
|
|
117
|
+
headers: {
|
|
118
|
+
"Content-Type": "application/json",
|
|
119
|
+
},
|
|
120
|
+
body: JSON.stringify({ apiKey }),
|
|
121
|
+
}),
|
|
122
|
+
10000,
|
|
123
|
+
"Authentication",
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
if (!response.ok) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`Authentication failed: ${response.status} ${response.statusText}`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const data = await response.json();
|
|
133
|
+
return data.token;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function createTestRunDirect(token, apiRoot, testRunData) {
|
|
137
|
+
const url = `${apiRoot}/api/v1/testdriver/test-run-create`;
|
|
138
|
+
const response = await withTimeout(
|
|
139
|
+
fetch(url, {
|
|
140
|
+
method: "POST",
|
|
141
|
+
headers: {
|
|
142
|
+
"Content-Type": "application/json",
|
|
143
|
+
Authorization: `Bearer ${token}`,
|
|
144
|
+
},
|
|
145
|
+
body: JSON.stringify(testRunData),
|
|
146
|
+
}),
|
|
147
|
+
10000,
|
|
148
|
+
"Create Test Run",
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
if (!response.ok) {
|
|
152
|
+
const errorText = await response.text();
|
|
153
|
+
throw new Error(
|
|
154
|
+
`API error: ${response.status} ${response.statusText} - ${errorText}`,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return await response.json();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export async function recordTestCaseDirect(token, apiRoot, testCaseData) {
|
|
162
|
+
const url = `${apiRoot}/api/v1/testdriver/test-case-create`;
|
|
163
|
+
const response = await withTimeout(
|
|
164
|
+
fetch(url, {
|
|
165
|
+
method: "POST",
|
|
166
|
+
headers: {
|
|
167
|
+
"Content-Type": "application/json",
|
|
168
|
+
Authorization: `Bearer ${token}`,
|
|
169
|
+
},
|
|
170
|
+
body: JSON.stringify(testCaseData),
|
|
171
|
+
}),
|
|
172
|
+
10000,
|
|
173
|
+
"Record Test Case",
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
if (!response.ok) {
|
|
177
|
+
const errorText = await response.text();
|
|
178
|
+
throw new Error(
|
|
179
|
+
`API error: ${response.status} ${response.statusText} - ${errorText}`,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return await response.json();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Create the TestDriver Vitest plugin
|
|
188
|
+
* This sets up global state and provides the registration API
|
|
189
|
+
*/
|
|
190
|
+
export default function testDriverPlugin(options = {}) {
|
|
191
|
+
// Initialize plugin state with options
|
|
192
|
+
pluginState.apiKey = options.apiKey;
|
|
193
|
+
pluginState.apiRoot =
|
|
194
|
+
options.apiRoot || process.env.TD_API_ROOT || "http://localhost:1337";
|
|
195
|
+
pluginState.ciProvider = detectCI();
|
|
196
|
+
pluginState.gitInfo = getGitInfo();
|
|
197
|
+
|
|
198
|
+
// Note: globalThis setup happens in vitestSetup.mjs for worker processes
|
|
199
|
+
console.log(
|
|
200
|
+
"[TestDriver Plugin] Initialized with API root:",
|
|
201
|
+
pluginState.apiRoot,
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
return new TestDriverReporter(options);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* TestDriver Reporter Class
|
|
209
|
+
* Handles Vitest test lifecycle events
|
|
210
|
+
*/
|
|
211
|
+
class TestDriverReporter {
|
|
212
|
+
constructor(options = {}) {
|
|
213
|
+
this.options = options;
|
|
214
|
+
console.log("[TestDriver Reporter] Created");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async onInit(ctx) {
|
|
218
|
+
this.ctx = ctx;
|
|
219
|
+
console.log("[TestDriver Reporter] onInit called");
|
|
220
|
+
|
|
221
|
+
// Initialize test run
|
|
222
|
+
await this.initializeTestRun();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async initializeTestRun() {
|
|
226
|
+
console.log("[TestDriver Reporter] Initializing test run...");
|
|
227
|
+
|
|
228
|
+
// Check if we should enable the reporter
|
|
229
|
+
if (!pluginState.apiKey) {
|
|
230
|
+
console.log(
|
|
231
|
+
"[TestDriver Reporter] No API key provided, skipping test recording",
|
|
232
|
+
);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
// Exchange API key for JWT token
|
|
238
|
+
await authenticate();
|
|
239
|
+
|
|
240
|
+
// Generate unique run ID
|
|
241
|
+
pluginState.testRunId = generateRunId();
|
|
242
|
+
pluginState.startTime = Date.now();
|
|
243
|
+
|
|
244
|
+
// Create test run via direct API call
|
|
245
|
+
const testRunData = {
|
|
246
|
+
runId: pluginState.testRunId,
|
|
247
|
+
suiteName: getSuiteName(),
|
|
248
|
+
...pluginState.gitInfo,
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
// Only add ciProvider if it's not null
|
|
252
|
+
if (pluginState.ciProvider) {
|
|
253
|
+
testRunData.ciProvider = pluginState.ciProvider;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Platform will be set from the first test result file
|
|
257
|
+
// Default to linux if no tests write platform info
|
|
258
|
+
testRunData.platform = "linux";
|
|
259
|
+
|
|
260
|
+
pluginState.testRun = await createTestRun(testRunData);
|
|
261
|
+
|
|
262
|
+
// Store in environment variables for worker processes to access
|
|
263
|
+
process.env.TD_TEST_RUN_ID = pluginState.testRunId;
|
|
264
|
+
process.env.TD_TEST_RUN_DB_ID = pluginState.testRun.data?.id || "";
|
|
265
|
+
process.env.TD_TEST_RUN_TOKEN = pluginState.token;
|
|
266
|
+
|
|
267
|
+
// Also store in shared state module (won't work across processes but good for main)
|
|
268
|
+
setTestRunInfo({
|
|
269
|
+
testRun: pluginState.testRun,
|
|
270
|
+
testRunId: pluginState.testRunId,
|
|
271
|
+
token: pluginState.token,
|
|
272
|
+
apiKey: pluginState.apiKey,
|
|
273
|
+
apiRoot: pluginState.apiRoot,
|
|
274
|
+
startTime: pluginState.startTime,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
console.log(
|
|
278
|
+
`[TestDriver Reporter] Test run created: ${pluginState.testRunId}`,
|
|
279
|
+
);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
console.error(
|
|
282
|
+
"[TestDriver Reporter] Failed to initialize:",
|
|
283
|
+
error.message,
|
|
284
|
+
);
|
|
285
|
+
pluginState.apiKey = null;
|
|
286
|
+
pluginState.token = null;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async onTestRunEnd(testModules, unhandledErrors, reason) {
|
|
291
|
+
console.log("[TestDriver Reporter] Test run ending with reason:", reason);
|
|
292
|
+
|
|
293
|
+
if (!pluginState.apiKey) {
|
|
294
|
+
console.log("[TestDriver Reporter] Skipping completion - no API key");
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (!pluginState.testRun) {
|
|
299
|
+
console.log(
|
|
300
|
+
"[TestDriver Reporter] Skipping completion - no test run created",
|
|
301
|
+
);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
// Calculate statistics from testModules
|
|
307
|
+
const stats = calculateStatsFromModules(testModules);
|
|
308
|
+
|
|
309
|
+
console.log(`[TestDriver Reporter] Stats:`, stats);
|
|
310
|
+
|
|
311
|
+
// Determine overall status based on reason and stats
|
|
312
|
+
let status = "passed";
|
|
313
|
+
if (reason === "failed" || stats.failedTests > 0) {
|
|
314
|
+
status = "failed";
|
|
315
|
+
} else if (reason === "interrupted") {
|
|
316
|
+
status = "cancelled";
|
|
317
|
+
} else if (stats.totalTests === 0) {
|
|
318
|
+
status = "cancelled";
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Complete test run via API
|
|
322
|
+
console.log(
|
|
323
|
+
`[TestDriver Reporter] Completing test run ${pluginState.testRunId} with status: ${status}`,
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
const completeData = {
|
|
327
|
+
runId: pluginState.testRunId,
|
|
328
|
+
status,
|
|
329
|
+
totalTests: stats.totalTests,
|
|
330
|
+
passedTests: stats.passedTests,
|
|
331
|
+
failedTests: stats.failedTests,
|
|
332
|
+
skippedTests: stats.skippedTests,
|
|
333
|
+
duration: Date.now() - pluginState.startTime,
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// Update platform if detected from test results
|
|
337
|
+
const platform = getPlatform();
|
|
338
|
+
if (platform) {
|
|
339
|
+
completeData.platform = platform;
|
|
340
|
+
console.log(
|
|
341
|
+
`[TestDriver Reporter] Updating test run with platform: ${platform}`,
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Wait for any pending operations (shouldn't be any, but just in case)
|
|
346
|
+
if (pluginState.pendingTestCaseRecords.size > 0) {
|
|
347
|
+
console.log(
|
|
348
|
+
`[TestDriver Reporter] Waiting for ${pluginState.pendingTestCaseRecords.size} pending operations...`,
|
|
349
|
+
);
|
|
350
|
+
await Promise.all(Array.from(pluginState.pendingTestCaseRecords));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Test cases are reported directly from teardownTest
|
|
354
|
+
console.log(
|
|
355
|
+
`[TestDriver Reporter] All test cases reported from teardown`,
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
const completeResponse = await completeTestRun(completeData);
|
|
359
|
+
console.log(
|
|
360
|
+
`[TestDriver Reporter] ✅ Test run completion API response:`,
|
|
361
|
+
completeResponse,
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
console.log(
|
|
365
|
+
`[TestDriver Reporter] Test run completed: ${stats.passedTests}/${stats.totalTests} passed`,
|
|
366
|
+
);
|
|
367
|
+
} catch (error) {
|
|
368
|
+
console.error(
|
|
369
|
+
"[TestDriver Reporter] Failed to complete test run:",
|
|
370
|
+
error.message,
|
|
371
|
+
);
|
|
372
|
+
console.error("[TestDriver Reporter] Error stack:", error.stack);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
onTestCaseReady(test) {
|
|
377
|
+
if (!pluginState.apiKey || !pluginState.testRun) return;
|
|
378
|
+
|
|
379
|
+
pluginState.testCases.set(test.id, {
|
|
380
|
+
test,
|
|
381
|
+
startTime: Date.now(),
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// Try to detect platform from test context
|
|
385
|
+
detectPlatformFromTest(test);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async onTestCaseResult(test) {
|
|
389
|
+
if (!pluginState.apiKey || !pluginState.testRun) return;
|
|
390
|
+
|
|
391
|
+
const result = test.result();
|
|
392
|
+
const status =
|
|
393
|
+
result.state === "passed"
|
|
394
|
+
? "passed"
|
|
395
|
+
: result.state === "skipped"
|
|
396
|
+
? "skipped"
|
|
397
|
+
: "failed";
|
|
398
|
+
|
|
399
|
+
console.log(
|
|
400
|
+
`[TestDriver Reporter] Test case completed: ${test.name} (${status})`,
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
// Read test metadata from file (cross-process communication)
|
|
404
|
+
let dashcamUrl = null;
|
|
405
|
+
let testFile = "unknown";
|
|
406
|
+
let testOrder = 0;
|
|
407
|
+
let duration = result.duration || 0;
|
|
408
|
+
|
|
409
|
+
const testResultFile = path.join(
|
|
410
|
+
os.tmpdir(),
|
|
411
|
+
"testdriver-results",
|
|
412
|
+
`${test.id}.json`,
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
try {
|
|
416
|
+
if (fs.existsSync(testResultFile)) {
|
|
417
|
+
const testResult = JSON.parse(fs.readFileSync(testResultFile, "utf-8"));
|
|
418
|
+
dashcamUrl = testResult.dashcamUrl || null;
|
|
419
|
+
const platform = testResult.platform || null;
|
|
420
|
+
testFile =
|
|
421
|
+
testResult.testFile ||
|
|
422
|
+
test.file?.filepath ||
|
|
423
|
+
test.file?.name ||
|
|
424
|
+
"unknown";
|
|
425
|
+
testOrder =
|
|
426
|
+
testResult.testOrder !== undefined ? testResult.testOrder : 0;
|
|
427
|
+
duration = testResult.duration || result.duration || 0;
|
|
428
|
+
|
|
429
|
+
console.log(
|
|
430
|
+
`[TestDriver Reporter] ✅ Read from file - dashcam: ${dashcamUrl}, platform: ${platform}, testFile: ${testFile}, testOrder: ${testOrder}, duration: ${duration}ms`,
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
// Update test run platform from first test that reports it
|
|
434
|
+
if (platform && !pluginState.detectedPlatform) {
|
|
435
|
+
pluginState.detectedPlatform = platform;
|
|
436
|
+
console.log(
|
|
437
|
+
`[TestDriver Reporter] 🖥️ Detected platform from test: ${platform}`,
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Clean up the file after reading
|
|
442
|
+
try {
|
|
443
|
+
fs.unlinkSync(testResultFile);
|
|
444
|
+
} catch {
|
|
445
|
+
// Ignore cleanup errors
|
|
446
|
+
}
|
|
447
|
+
} else {
|
|
448
|
+
console.log(
|
|
449
|
+
`[TestDriver Reporter] ⚠️ No result file found for test: ${test.id}`,
|
|
450
|
+
);
|
|
451
|
+
// Fallback to test object properties - try multiple sources
|
|
452
|
+
// In Vitest, the file path is typically on the module, not the test itself
|
|
453
|
+
const module = test.module || test.suite;
|
|
454
|
+
testFile =
|
|
455
|
+
test.file?.filepath ||
|
|
456
|
+
test.file?.name ||
|
|
457
|
+
module?.file?.filepath ||
|
|
458
|
+
module?.file?.name ||
|
|
459
|
+
test.location?.file ||
|
|
460
|
+
"unknown";
|
|
461
|
+
console.log(
|
|
462
|
+
`[TestDriver Reporter] 📂 Resolved testFile for skipped test: ${testFile}`,
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
} catch (error) {
|
|
466
|
+
console.error(
|
|
467
|
+
`[TestDriver Reporter] ❌ Failed to read test result file:`,
|
|
468
|
+
error.message,
|
|
469
|
+
);
|
|
470
|
+
// Fallback to test object properties - try multiple sources
|
|
471
|
+
const module = test.module || test.suite;
|
|
472
|
+
testFile =
|
|
473
|
+
test.file?.filepath ||
|
|
474
|
+
test.file?.name ||
|
|
475
|
+
module?.file?.filepath ||
|
|
476
|
+
module?.file?.name ||
|
|
477
|
+
test.location?.file ||
|
|
478
|
+
"unknown";
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Get test run info from environment variables
|
|
482
|
+
const testRunId = process.env.TD_TEST_RUN_ID;
|
|
483
|
+
const token = process.env.TD_TEST_RUN_TOKEN;
|
|
484
|
+
|
|
485
|
+
if (!testRunId || !token) {
|
|
486
|
+
console.warn(
|
|
487
|
+
`[TestDriver Reporter] ⚠️ Test run not initialized, skipping test case recording for: ${test.name}`,
|
|
488
|
+
);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
try {
|
|
493
|
+
let errorMessage = null;
|
|
494
|
+
let errorStack = null;
|
|
495
|
+
|
|
496
|
+
if (
|
|
497
|
+
result.state === "failed" &&
|
|
498
|
+
result.errors &&
|
|
499
|
+
result.errors.length > 0
|
|
500
|
+
) {
|
|
501
|
+
const error = result.errors[0];
|
|
502
|
+
errorMessage = error.message;
|
|
503
|
+
errorStack = error.stack;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const suiteName = test.suite?.name;
|
|
507
|
+
const startTime = Date.now() - duration; // Calculate start time from duration
|
|
508
|
+
|
|
509
|
+
// Record test case with all metadata
|
|
510
|
+
const testCaseData = {
|
|
511
|
+
runId: testRunId,
|
|
512
|
+
testName: test.name,
|
|
513
|
+
testFile: testFile,
|
|
514
|
+
testOrder: testOrder,
|
|
515
|
+
status,
|
|
516
|
+
startTime: startTime,
|
|
517
|
+
endTime: Date.now(),
|
|
518
|
+
duration: duration,
|
|
519
|
+
retries: result.retryCount || 0,
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
// Only include replayUrl if we have a valid dashcam URL
|
|
523
|
+
if (dashcamUrl) {
|
|
524
|
+
testCaseData.replayUrl = dashcamUrl;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (suiteName) testCaseData.suiteName = suiteName;
|
|
528
|
+
if (errorMessage) testCaseData.errorMessage = errorMessage;
|
|
529
|
+
if (errorStack) testCaseData.errorStack = errorStack;
|
|
530
|
+
|
|
531
|
+
console.log(
|
|
532
|
+
`[TestDriver Reporter] Recording test case: ${test.name} (${status}) with testFile: ${testFile}, testOrder: ${testOrder}, duration: ${duration}ms, replay: ${dashcamUrl ? "yes" : "no"}`,
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
const testCaseResponse = await recordTestCaseDirect(
|
|
536
|
+
token,
|
|
537
|
+
pluginState.apiRoot,
|
|
538
|
+
testCaseData,
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
const testCaseDbId = testCaseResponse.data?.id;
|
|
542
|
+
const testRunDbId = process.env.TD_TEST_RUN_DB_ID;
|
|
543
|
+
|
|
544
|
+
console.log(
|
|
545
|
+
`[TestDriver Reporter] ✅ Reported test case to API${dashcamUrl ? " with dashcam URL" : ""}`,
|
|
546
|
+
);
|
|
547
|
+
console.log(
|
|
548
|
+
`[TestDriver Reporter] 🔗 View test: ${pluginState.apiRoot.replace("testdriver-api.onrender.com", "app.testdriver.ai")}/test-runs/${testRunDbId}/${testCaseDbId}`,
|
|
549
|
+
);
|
|
550
|
+
} catch (error) {
|
|
551
|
+
console.error(
|
|
552
|
+
`[TestDriver Reporter] ❌ Failed to report test case:`,
|
|
553
|
+
error.message,
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// ============================================================================
|
|
560
|
+
// Helper Functions
|
|
561
|
+
// ============================================================================
|
|
562
|
+
|
|
563
|
+
function generateRunId() {
|
|
564
|
+
return `${Date.now()}-${crypto.randomBytes(4).toString("hex")}`;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function getSuiteName() {
|
|
568
|
+
return process.env.npm_package_name || path.basename(process.cwd());
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function getPlatform() {
|
|
572
|
+
// First try to get platform from SDK client detected during test execution
|
|
573
|
+
if (pluginState.detectedPlatform) {
|
|
574
|
+
console.log(
|
|
575
|
+
`[TestDriver Plugin] Using platform from SDK client: ${pluginState.detectedPlatform}`,
|
|
576
|
+
);
|
|
577
|
+
return pluginState.detectedPlatform;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
console.log(`[TestDriver Plugin] Platform not yet detected from client`);
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function detectPlatformFromTest(test) {
|
|
585
|
+
// Check if testdriver client is accessible via test context
|
|
586
|
+
const client = test.context?.testdriver || test.meta?.testdriver;
|
|
587
|
+
|
|
588
|
+
if (client && client.os) {
|
|
589
|
+
// Normalize platform value
|
|
590
|
+
let platform = client.os.toLowerCase();
|
|
591
|
+
if (platform === "darwin" || platform === "mac") platform = "mac";
|
|
592
|
+
else if (platform === "win32" || platform === "windows")
|
|
593
|
+
platform = "windows";
|
|
594
|
+
else if (platform === "linux") platform = "linux";
|
|
595
|
+
|
|
596
|
+
pluginState.detectedPlatform = platform;
|
|
597
|
+
console.log(
|
|
598
|
+
`[TestDriver Plugin] Detected platform from test context: ${platform}`,
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function calculateStatsFromModules(testModules) {
|
|
604
|
+
let totalTests = 0;
|
|
605
|
+
let passedTests = 0;
|
|
606
|
+
let failedTests = 0;
|
|
607
|
+
let skippedTests = 0;
|
|
608
|
+
|
|
609
|
+
for (const testModule of testModules) {
|
|
610
|
+
for (const testCase of testModule.children.allTests()) {
|
|
611
|
+
totalTests++;
|
|
612
|
+
const result = testCase.result();
|
|
613
|
+
if (result.state === "passed") passedTests++;
|
|
614
|
+
else if (result.state === "failed") failedTests++;
|
|
615
|
+
else if (result.state === "skipped") skippedTests++;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
return { totalTests, passedTests, failedTests, skippedTests };
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function detectCI() {
|
|
623
|
+
if (process.env.GITHUB_ACTIONS) return "github";
|
|
624
|
+
if (process.env.GITLAB_CI) return "gitlab";
|
|
625
|
+
if (process.env.CIRCLECI) return "circle";
|
|
626
|
+
if (process.env.TRAVIS) return "travis";
|
|
627
|
+
if (process.env.JENKINS_URL) return "jenkins";
|
|
628
|
+
if (process.env.BUILDKITE) return "buildkite";
|
|
629
|
+
return null;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function getGitInfo() {
|
|
633
|
+
const info = {};
|
|
634
|
+
|
|
635
|
+
if (process.env.GITHUB_ACTIONS) {
|
|
636
|
+
if (process.env.GITHUB_REPOSITORY)
|
|
637
|
+
info.repo = process.env.GITHUB_REPOSITORY;
|
|
638
|
+
if (process.env.GITHUB_REF_NAME) info.branch = process.env.GITHUB_REF_NAME;
|
|
639
|
+
if (process.env.GITHUB_SHA) info.commit = process.env.GITHUB_SHA;
|
|
640
|
+
if (process.env.GITHUB_ACTOR) info.author = process.env.GITHUB_ACTOR;
|
|
641
|
+
} else if (process.env.GITLAB_CI) {
|
|
642
|
+
if (process.env.CI_PROJECT_PATH) info.repo = process.env.CI_PROJECT_PATH;
|
|
643
|
+
if (process.env.CI_COMMIT_BRANCH)
|
|
644
|
+
info.branch = process.env.CI_COMMIT_BRANCH;
|
|
645
|
+
if (process.env.CI_COMMIT_SHA) info.commit = process.env.CI_COMMIT_SHA;
|
|
646
|
+
if (process.env.GITLAB_USER_LOGIN)
|
|
647
|
+
info.author = process.env.GITLAB_USER_LOGIN;
|
|
648
|
+
} else if (process.env.CIRCLECI) {
|
|
649
|
+
if (
|
|
650
|
+
process.env.CIRCLE_PROJECT_USERNAME &&
|
|
651
|
+
process.env.CIRCLE_PROJECT_REPONAME
|
|
652
|
+
) {
|
|
653
|
+
info.repo = `${process.env.CIRCLE_PROJECT_USERNAME}/${process.env.CIRCLE_PROJECT_REPONAME}`;
|
|
654
|
+
}
|
|
655
|
+
if (process.env.CIRCLE_BRANCH) info.branch = process.env.CIRCLE_BRANCH;
|
|
656
|
+
if (process.env.CIRCLE_SHA1) info.commit = process.env.CIRCLE_SHA1;
|
|
657
|
+
if (process.env.CIRCLE_USERNAME) info.author = process.env.CIRCLE_USERNAME;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
return info;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// ============================================================================
|
|
664
|
+
// API Methods
|
|
665
|
+
// ============================================================================
|
|
666
|
+
|
|
667
|
+
async function authenticate() {
|
|
668
|
+
const url = `${pluginState.apiRoot}/auth/exchange-api-key`;
|
|
669
|
+
const response = await withTimeout(
|
|
670
|
+
fetch(url, {
|
|
671
|
+
method: "POST",
|
|
672
|
+
headers: {
|
|
673
|
+
"Content-Type": "application/json",
|
|
674
|
+
},
|
|
675
|
+
body: JSON.stringify({
|
|
676
|
+
apiKey: pluginState.apiKey,
|
|
677
|
+
}),
|
|
678
|
+
}),
|
|
679
|
+
10000,
|
|
680
|
+
"Internal Authentication",
|
|
681
|
+
);
|
|
682
|
+
|
|
683
|
+
if (!response.ok) {
|
|
684
|
+
throw new Error(
|
|
685
|
+
`Authentication failed: ${response.status} ${response.statusText}`,
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const data = await response.json();
|
|
690
|
+
pluginState.token = data.token;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
async function createTestRun(data) {
|
|
694
|
+
const url = `${pluginState.apiRoot}/api/v1/testdriver/test-run-create`;
|
|
695
|
+
const response = await withTimeout(
|
|
696
|
+
fetch(url, {
|
|
697
|
+
method: "POST",
|
|
698
|
+
headers: {
|
|
699
|
+
"Content-Type": "application/json",
|
|
700
|
+
Authorization: `Bearer ${pluginState.token}`,
|
|
701
|
+
},
|
|
702
|
+
body: JSON.stringify(data),
|
|
703
|
+
}),
|
|
704
|
+
10000,
|
|
705
|
+
"Internal Create Test Run",
|
|
706
|
+
);
|
|
707
|
+
|
|
708
|
+
if (!response.ok) {
|
|
709
|
+
const errorText = await response.text();
|
|
710
|
+
throw new Error(
|
|
711
|
+
`API error: ${response.status} ${response.statusText} - ${errorText}`,
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
return await response.json();
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
async function completeTestRun(data) {
|
|
719
|
+
const url = `${pluginState.apiRoot}/api/v1/testdriver/test-run-complete`;
|
|
720
|
+
const response = await withTimeout(
|
|
721
|
+
fetch(url, {
|
|
722
|
+
method: "POST",
|
|
723
|
+
headers: {
|
|
724
|
+
"Content-Type": "application/json",
|
|
725
|
+
Authorization: `Bearer ${pluginState.token}`,
|
|
726
|
+
},
|
|
727
|
+
body: JSON.stringify(data),
|
|
728
|
+
}),
|
|
729
|
+
10000,
|
|
730
|
+
"Internal Complete Test Run",
|
|
731
|
+
);
|
|
732
|
+
|
|
733
|
+
if (!response.ok) {
|
|
734
|
+
const errorText = await response.text();
|
|
735
|
+
throw new Error(
|
|
736
|
+
`API error: ${response.status} ${response.statusText} - ${errorText}`,
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
return await response.json();
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Global state setup moved to setup file (vitestSetup.mjs)
|
|
744
|
+
// The setup file imports the exported functions and makes them available globally in worker processes
|