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
|
@@ -1,9 +1,46 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
1
2
|
import crypto from "crypto";
|
|
2
3
|
import fs from "fs";
|
|
4
|
+
import { createRequire } from "module";
|
|
3
5
|
import os from "os";
|
|
4
6
|
import path from "path";
|
|
5
7
|
import { setTestRunInfo } from "./shared-test-state.mjs";
|
|
6
8
|
|
|
9
|
+
// Use createRequire to import CommonJS modules without esbuild processing
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Simple logger for the vitest plugin
|
|
14
|
+
* Supports log levels: debug, info, warn, error
|
|
15
|
+
* Control via TD_LOG_LEVEL environment variable (default: "info")
|
|
16
|
+
* Set TD_LOG_LEVEL=debug for verbose output
|
|
17
|
+
*/
|
|
18
|
+
const LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
19
|
+
const currentLogLevel = LOG_LEVELS[process.env.TD_LOG_LEVEL?.toLowerCase()] ?? LOG_LEVELS.info;
|
|
20
|
+
|
|
21
|
+
const logger = {
|
|
22
|
+
debug: (...args) => {
|
|
23
|
+
if (currentLogLevel <= LOG_LEVELS.debug) {
|
|
24
|
+
console.log("[TestDriver]", ...args);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
info: (...args) => {
|
|
28
|
+
if (currentLogLevel <= LOG_LEVELS.info) {
|
|
29
|
+
console.log("[TestDriver]", ...args);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
warn: (...args) => {
|
|
33
|
+
if (currentLogLevel <= LOG_LEVELS.warn) {
|
|
34
|
+
console.warn("[TestDriver]", ...args);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
error: (...args) => {
|
|
38
|
+
if (currentLogLevel <= LOG_LEVELS.error) {
|
|
39
|
+
console.error("[TestDriver]", ...args);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
7
44
|
/**
|
|
8
45
|
* Timeout wrapper for promises
|
|
9
46
|
* @param {Promise} promise - Promise to wrap
|
|
@@ -58,6 +95,7 @@ function withTimeout(promise, timeoutMs, operationName) {
|
|
|
58
95
|
export const pluginState = {
|
|
59
96
|
testRun: null,
|
|
60
97
|
testRunId: null,
|
|
98
|
+
testRunCompleted: false,
|
|
61
99
|
client: null,
|
|
62
100
|
startTime: null,
|
|
63
101
|
testCases: new Map(),
|
|
@@ -68,6 +106,8 @@ export const pluginState = {
|
|
|
68
106
|
gitInfo: {},
|
|
69
107
|
apiKey: null,
|
|
70
108
|
apiRoot: null,
|
|
109
|
+
// TestDriver options to pass to all instances
|
|
110
|
+
testDriverOptions: {},
|
|
71
111
|
// Dashcam URL tracking (in-memory, no files needed!)
|
|
72
112
|
dashcamUrls: new Map(), // testId -> dashcamUrl
|
|
73
113
|
lastDashcamUrl: null, // Fallback for when test ID isn't available
|
|
@@ -77,7 +117,7 @@ export const pluginState = {
|
|
|
77
117
|
|
|
78
118
|
// Export functions that can be used by the reporter or tests
|
|
79
119
|
export function registerDashcamUrl(testId, url, platform) {
|
|
80
|
-
|
|
120
|
+
logger.debug(`Registering dashcam URL for test ${testId}:`, url);
|
|
81
121
|
pluginState.dashcamUrls.set(testId, { url, platform });
|
|
82
122
|
pluginState.lastDashcamUrl = url;
|
|
83
123
|
}
|
|
@@ -96,7 +136,7 @@ export function getSuiteTestRun(suiteId) {
|
|
|
96
136
|
}
|
|
97
137
|
|
|
98
138
|
export function setSuiteTestRun(suiteId, runData) {
|
|
99
|
-
|
|
139
|
+
logger.debug(`Setting test run for suite ${suiteId}:`, runData);
|
|
100
140
|
pluginState.suiteTestRuns.set(suiteId, runData);
|
|
101
141
|
}
|
|
102
142
|
|
|
@@ -183,23 +223,223 @@ export async function recordTestCaseDirect(token, apiRoot, testCaseData) {
|
|
|
183
223
|
return await response.json();
|
|
184
224
|
}
|
|
185
225
|
|
|
226
|
+
// Import TestDriverSDK using require to avoid esbuild transformation issues
|
|
227
|
+
const TestDriverSDK = require('../sdk.js');
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Create a TestDriver client for use in beforeAll/beforeEach hooks
|
|
231
|
+
* This is for the shared instance pattern where one driver is used across multiple tests
|
|
232
|
+
*
|
|
233
|
+
* @param {object} options - TestDriver options
|
|
234
|
+
* @param {string} [options.apiKey] - TestDriver API key (defaults to process.env.TD_API_KEY)
|
|
235
|
+
* @param {boolean} [options.headless] - Run sandbox in headless mode
|
|
236
|
+
* @returns {Promise<TestDriver>} Connected TestDriver client instance
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* let testdriver;
|
|
240
|
+
* beforeAll(async () => {
|
|
241
|
+
* testdriver = await createTestDriver({ headless: true });
|
|
242
|
+
* await testdriver.provision.chrome({ url: 'https://example.com' });
|
|
243
|
+
* });
|
|
244
|
+
*/
|
|
245
|
+
export async function createTestDriver(options = {}) {
|
|
246
|
+
// Get global plugin options if available
|
|
247
|
+
const pluginOptions = globalThis.__testdriverPlugin?.state?.testDriverOptions || {};
|
|
248
|
+
|
|
249
|
+
// Merge options: plugin global options < test-specific options
|
|
250
|
+
const mergedOptions = { ...pluginOptions, ...options };
|
|
251
|
+
|
|
252
|
+
// Extract TestDriver-specific options
|
|
253
|
+
const apiKey = mergedOptions.apiKey || process.env.TD_API_KEY;
|
|
254
|
+
|
|
255
|
+
// Build config for TestDriverSDK constructor
|
|
256
|
+
const config = { ...mergedOptions };
|
|
257
|
+
delete config.apiKey;
|
|
258
|
+
|
|
259
|
+
// Use TD_API_ROOT from environment if not provided in config
|
|
260
|
+
if (!config.apiRoot && process.env.TD_API_ROOT) {
|
|
261
|
+
config.apiRoot = process.env.TD_API_ROOT;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const testdriver = new TestDriverSDK(apiKey, config);
|
|
265
|
+
|
|
266
|
+
// Connect to sandbox
|
|
267
|
+
console.log('[testdriver] Connecting to sandbox...');
|
|
268
|
+
await testdriver.auth();
|
|
269
|
+
await testdriver.connect();
|
|
270
|
+
console.log('[testdriver] ✅ Connected to sandbox');
|
|
271
|
+
|
|
272
|
+
return testdriver;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Register a test with a shared TestDriver instance
|
|
277
|
+
* Call this at the start of each test to associate the test context with the driver
|
|
278
|
+
*
|
|
279
|
+
* @param {TestDriver} testdriver - TestDriver client instance from createTestDriver
|
|
280
|
+
* @param {object} context - Vitest test context (from async (context) => {})
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* it("step01: verify login", async (context) => {
|
|
284
|
+
* registerTest(testdriver, context);
|
|
285
|
+
* const result = await testdriver.assert("login form visible");
|
|
286
|
+
* });
|
|
287
|
+
*/
|
|
288
|
+
export function registerTest(testdriver, context) {
|
|
289
|
+
if (!testdriver) {
|
|
290
|
+
throw new Error('registerTest() requires a TestDriver instance');
|
|
291
|
+
}
|
|
292
|
+
if (!context || !context.task) {
|
|
293
|
+
throw new Error('registerTest() requires Vitest context. Pass the context parameter from your test function.');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
testdriver.__vitestContext = context.task;
|
|
297
|
+
logger.debug(`Registered test: ${context.task.name}`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Clean up a TestDriver client created with createTestDriver
|
|
302
|
+
* Call this in afterAll to properly disconnect and stop recordings
|
|
303
|
+
*
|
|
304
|
+
* @param {TestDriver} testdriver - TestDriver client instance
|
|
305
|
+
*
|
|
306
|
+
* @example
|
|
307
|
+
* afterAll(async () => {
|
|
308
|
+
* await cleanupTestDriver(testdriver);
|
|
309
|
+
* });
|
|
310
|
+
*/
|
|
311
|
+
export async function cleanupTestDriver(testdriver) {
|
|
312
|
+
if (!testdriver) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
console.log('[testdriver] Cleaning up TestDriver client...');
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
// Stop dashcam if it was started
|
|
320
|
+
if (testdriver._dashcam && testdriver._dashcam.recording) {
|
|
321
|
+
try {
|
|
322
|
+
const dashcamUrl = await testdriver.dashcam.stop();
|
|
323
|
+
console.log('🎥 Dashcam URL:', dashcamUrl);
|
|
324
|
+
|
|
325
|
+
// Register dashcam URL in memory for the reporter
|
|
326
|
+
if (dashcamUrl && globalThis.__testdriverPlugin?.registerDashcamUrl) {
|
|
327
|
+
const testId = testdriver.__vitestContext?.id || 'unknown';
|
|
328
|
+
const platform = testdriver.os || 'linux';
|
|
329
|
+
globalThis.__testdriverPlugin.registerDashcamUrl(testId, dashcamUrl, platform);
|
|
330
|
+
}
|
|
331
|
+
} catch (error) {
|
|
332
|
+
console.error('❌ Failed to stop dashcam:', error.message);
|
|
333
|
+
if (error.name === 'NotFoundError' || error.responseData?.error === 'NotFoundError') {
|
|
334
|
+
console.log(' ℹ️ Sandbox session already terminated - dashcam stop skipped');
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
await testdriver.disconnect();
|
|
340
|
+
console.log('✅ Client disconnected');
|
|
341
|
+
} catch (error) {
|
|
342
|
+
console.error('Error disconnecting client:', error);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Handle process termination and mark test run as cancelled
|
|
348
|
+
*/
|
|
349
|
+
async function handleProcessExit() {
|
|
350
|
+
if (!pluginState.testRun || !pluginState.testRunId) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
logger.info("Process interrupted, marking test run as cancelled...");
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
const stats = {
|
|
358
|
+
totalTests: pluginState.testCases.size,
|
|
359
|
+
passedTests: 0,
|
|
360
|
+
failedTests: 0,
|
|
361
|
+
skippedTests: 0,
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const completeData = {
|
|
365
|
+
runId: pluginState.testRunId,
|
|
366
|
+
status: "cancelled",
|
|
367
|
+
totalTests: stats.totalTests,
|
|
368
|
+
passedTests: stats.passedTests,
|
|
369
|
+
failedTests: stats.failedTests,
|
|
370
|
+
skippedTests: stats.skippedTests,
|
|
371
|
+
duration: Date.now() - pluginState.startTime,
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// Update platform if detected
|
|
375
|
+
const platform = getPlatform();
|
|
376
|
+
if (platform) {
|
|
377
|
+
completeData.platform = platform;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
await completeTestRun(completeData);
|
|
381
|
+
logger.info("✅ Test run marked as cancelled");
|
|
382
|
+
} catch (error) {
|
|
383
|
+
logger.error("Failed to mark test run as cancelled:", error.message);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Set up process exit handlers
|
|
388
|
+
let exitHandlersRegistered = false;
|
|
389
|
+
|
|
390
|
+
function registerExitHandlers() {
|
|
391
|
+
if (exitHandlersRegistered) return;
|
|
392
|
+
exitHandlersRegistered = true;
|
|
393
|
+
|
|
394
|
+
// Handle Ctrl+C
|
|
395
|
+
process.on("SIGINT", async () => {
|
|
396
|
+
await handleProcessExit();
|
|
397
|
+
process.exit(130); // Standard exit code for SIGINT
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// Handle kill command
|
|
401
|
+
process.on("SIGTERM", async () => {
|
|
402
|
+
await handleProcessExit();
|
|
403
|
+
process.exit(143); // Standard exit code for SIGTERM
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// Handle unexpected exits
|
|
407
|
+
process.on("beforeExit", async () => {
|
|
408
|
+
// Only handle if test run is still running (hasn't been completed normally)
|
|
409
|
+
if (pluginState.testRun && !pluginState.testRunCompleted) {
|
|
410
|
+
await handleProcessExit();
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
186
415
|
/**
|
|
187
416
|
* Create the TestDriver Vitest plugin
|
|
188
417
|
* This sets up global state and provides the registration API
|
|
189
418
|
*/
|
|
190
419
|
export default function testDriverPlugin(options = {}) {
|
|
191
|
-
//
|
|
192
|
-
|
|
420
|
+
// Store options but don't read env vars yet - they may not be loaded
|
|
421
|
+
// Environment variables will be read in onInit after setupFiles run
|
|
193
422
|
pluginState.apiRoot =
|
|
194
|
-
options.apiRoot || process.env.TD_API_ROOT || "
|
|
423
|
+
options.apiRoot || process.env.TD_API_ROOT || "https://testdriver-api.onrender.com";
|
|
195
424
|
pluginState.ciProvider = detectCI();
|
|
196
425
|
pluginState.gitInfo = getGitInfo();
|
|
426
|
+
|
|
427
|
+
// Store TestDriver-specific options (excluding plugin-specific ones)
|
|
428
|
+
const { apiKey, apiRoot, ...testDriverOptions } = options;
|
|
429
|
+
pluginState.testDriverOptions = testDriverOptions;
|
|
430
|
+
|
|
431
|
+
// Register process exit handlers to handle cancellation
|
|
432
|
+
registerExitHandlers();
|
|
197
433
|
|
|
198
434
|
// Note: globalThis setup happens in vitestSetup.mjs for worker processes
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
);
|
|
435
|
+
logger.debug("TestDriver plugin initializing...");
|
|
436
|
+
logger.debug("API root:", pluginState.apiRoot);
|
|
437
|
+
logger.debug("API key from options:", !!options.apiKey);
|
|
438
|
+
logger.debug("API key from env (at config time):", !!process.env.TD_API_KEY);
|
|
439
|
+
logger.debug("CI Provider:", pluginState.ciProvider || "none");
|
|
440
|
+
if (Object.keys(testDriverOptions).length > 0) {
|
|
441
|
+
logger.debug("Global TestDriver options:", testDriverOptions);
|
|
442
|
+
}
|
|
203
443
|
|
|
204
444
|
return new TestDriverReporter(options);
|
|
205
445
|
}
|
|
@@ -211,35 +451,51 @@ export default function testDriverPlugin(options = {}) {
|
|
|
211
451
|
class TestDriverReporter {
|
|
212
452
|
constructor(options = {}) {
|
|
213
453
|
this.options = options;
|
|
214
|
-
|
|
454
|
+
logger.debug("Reporter created with options:", { hasApiKey: !!options.apiKey, hasApiRoot: !!options.apiRoot });
|
|
215
455
|
}
|
|
216
456
|
|
|
217
457
|
async onInit(ctx) {
|
|
218
458
|
this.ctx = ctx;
|
|
219
|
-
|
|
459
|
+
logger.debug("onInit called - UPDATED VERSION");
|
|
460
|
+
|
|
461
|
+
// NOW read the API key and API root (after setupFiles have run, including dotenv/config)
|
|
462
|
+
pluginState.apiKey = this.options.apiKey || process.env.TD_API_KEY;
|
|
463
|
+
pluginState.apiRoot = this.options.apiRoot || process.env.TD_API_ROOT || "https://testdriver-api.onrender.com";
|
|
464
|
+
logger.debug("API key from options:", !!this.options.apiKey);
|
|
465
|
+
logger.debug("API key from env (at onInit):", !!process.env.TD_API_KEY);
|
|
466
|
+
logger.debug("API root from options:", this.options.apiRoot);
|
|
467
|
+
logger.debug("API root from env (at onInit):", process.env.TD_API_ROOT);
|
|
468
|
+
logger.debug("Final API key set:", !!pluginState.apiKey);
|
|
469
|
+
logger.debug("Final API root set:", pluginState.apiRoot);
|
|
220
470
|
|
|
221
471
|
// Initialize test run
|
|
222
472
|
await this.initializeTestRun();
|
|
223
473
|
}
|
|
224
474
|
|
|
225
475
|
async initializeTestRun() {
|
|
226
|
-
|
|
476
|
+
logger.debug("Initializing test run...");
|
|
477
|
+
logger.debug("Current API key in pluginState:", !!pluginState.apiKey);
|
|
478
|
+
logger.debug("Current API root in pluginState:", pluginState.apiRoot);
|
|
227
479
|
|
|
228
480
|
// Check if we should enable the reporter
|
|
229
481
|
if (!pluginState.apiKey) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
);
|
|
482
|
+
logger.warn("No API key provided, skipping test recording");
|
|
483
|
+
logger.debug("API key sources - options:", !!this.options.apiKey, "env:", !!process.env.TD_API_KEY);
|
|
233
484
|
return;
|
|
234
485
|
}
|
|
235
486
|
|
|
487
|
+
logger.info("Starting test run initialization with API key...");
|
|
488
|
+
|
|
236
489
|
try {
|
|
237
490
|
// Exchange API key for JWT token
|
|
491
|
+
logger.debug("Authenticating with API...");
|
|
238
492
|
await authenticate();
|
|
493
|
+
logger.debug("Authentication successful, token received");
|
|
239
494
|
|
|
240
495
|
// Generate unique run ID
|
|
241
496
|
pluginState.testRunId = generateRunId();
|
|
242
497
|
pluginState.startTime = Date.now();
|
|
498
|
+
pluginState.testRunCompleted = false; // Reset completion flag
|
|
243
499
|
|
|
244
500
|
// Create test run via direct API call
|
|
245
501
|
const testRunData = {
|
|
@@ -248,6 +504,8 @@ class TestDriverReporter {
|
|
|
248
504
|
...pluginState.gitInfo,
|
|
249
505
|
};
|
|
250
506
|
|
|
507
|
+
// Session ID will be added from the first test result file that includes it
|
|
508
|
+
|
|
251
509
|
// Only add ciProvider if it's not null
|
|
252
510
|
if (pluginState.ciProvider) {
|
|
253
511
|
testRunData.ciProvider = pluginState.ciProvider;
|
|
@@ -257,7 +515,9 @@ class TestDriverReporter {
|
|
|
257
515
|
// Default to linux if no tests write platform info
|
|
258
516
|
testRunData.platform = "linux";
|
|
259
517
|
|
|
518
|
+
logger.debug("Creating test run with data:", testRunData);
|
|
260
519
|
pluginState.testRun = await createTestRun(testRunData);
|
|
520
|
+
logger.debug("Test run created successfully:", pluginState.testRun);
|
|
261
521
|
|
|
262
522
|
// Store in environment variables for worker processes to access
|
|
263
523
|
process.env.TD_TEST_RUN_ID = pluginState.testRunId;
|
|
@@ -274,39 +534,35 @@ class TestDriverReporter {
|
|
|
274
534
|
startTime: pluginState.startTime,
|
|
275
535
|
});
|
|
276
536
|
|
|
277
|
-
|
|
278
|
-
`[TestDriver Reporter] Test run created: ${pluginState.testRunId}`,
|
|
279
|
-
);
|
|
537
|
+
logger.info(`Test run created: ${pluginState.testRunId}`);
|
|
280
538
|
} catch (error) {
|
|
281
|
-
|
|
282
|
-
"[TestDriver Reporter] Failed to initialize:",
|
|
283
|
-
error.message,
|
|
284
|
-
);
|
|
539
|
+
logger.error("Failed to initialize:", error.message);
|
|
285
540
|
pluginState.apiKey = null;
|
|
286
541
|
pluginState.token = null;
|
|
287
542
|
}
|
|
288
543
|
}
|
|
289
544
|
|
|
290
545
|
async onTestRunEnd(testModules, unhandledErrors, reason) {
|
|
291
|
-
|
|
546
|
+
logger.debug("Test run ending with reason:", reason);
|
|
547
|
+
logger.debug("Plugin state - API key present:", !!pluginState.apiKey, "Test run present:", !!pluginState.testRun);
|
|
292
548
|
|
|
293
549
|
if (!pluginState.apiKey) {
|
|
294
|
-
|
|
550
|
+
logger.warn("Skipping completion - no API key (was it cleared after init failure?)");
|
|
295
551
|
return;
|
|
296
552
|
}
|
|
297
553
|
|
|
298
554
|
if (!pluginState.testRun) {
|
|
299
|
-
|
|
300
|
-
"[TestDriver Reporter] Skipping completion - no test run created",
|
|
301
|
-
);
|
|
555
|
+
logger.warn("Skipping completion - no test run created (check initialization logs)");
|
|
302
556
|
return;
|
|
303
557
|
}
|
|
304
558
|
|
|
559
|
+
logger.info("Completing test run...");
|
|
560
|
+
|
|
305
561
|
try {
|
|
306
562
|
// Calculate statistics from testModules
|
|
307
563
|
const stats = calculateStatsFromModules(testModules);
|
|
308
564
|
|
|
309
|
-
|
|
565
|
+
logger.debug("Stats:", stats);
|
|
310
566
|
|
|
311
567
|
// Determine overall status based on reason and stats
|
|
312
568
|
let status = "passed";
|
|
@@ -319,9 +575,7 @@ class TestDriverReporter {
|
|
|
319
575
|
}
|
|
320
576
|
|
|
321
577
|
// Complete test run via API
|
|
322
|
-
|
|
323
|
-
`[TestDriver Reporter] Completing test run ${pluginState.testRunId} with status: ${status}`,
|
|
324
|
-
);
|
|
578
|
+
logger.debug(`Completing test run ${pluginState.testRunId} with status: ${status}`);
|
|
325
579
|
|
|
326
580
|
const completeData = {
|
|
327
581
|
runId: pluginState.testRunId,
|
|
@@ -337,39 +591,28 @@ class TestDriverReporter {
|
|
|
337
591
|
const platform = getPlatform();
|
|
338
592
|
if (platform) {
|
|
339
593
|
completeData.platform = platform;
|
|
340
|
-
|
|
341
|
-
`[TestDriver Reporter] Updating test run with platform: ${platform}`,
|
|
342
|
-
);
|
|
594
|
+
logger.debug(`Updating test run with platform: ${platform}`);
|
|
343
595
|
}
|
|
344
596
|
|
|
345
597
|
// Wait for any pending operations (shouldn't be any, but just in case)
|
|
346
598
|
if (pluginState.pendingTestCaseRecords.size > 0) {
|
|
347
|
-
|
|
348
|
-
`[TestDriver Reporter] Waiting for ${pluginState.pendingTestCaseRecords.size} pending operations...`,
|
|
349
|
-
);
|
|
599
|
+
logger.debug(`Waiting for ${pluginState.pendingTestCaseRecords.size} pending operations...`);
|
|
350
600
|
await Promise.all(Array.from(pluginState.pendingTestCaseRecords));
|
|
351
601
|
}
|
|
352
602
|
|
|
353
603
|
// Test cases are reported directly from teardownTest
|
|
354
|
-
|
|
355
|
-
`[TestDriver Reporter] All test cases reported from teardown`,
|
|
356
|
-
);
|
|
604
|
+
logger.debug("All test cases reported from teardown");
|
|
357
605
|
|
|
358
606
|
const completeResponse = await completeTestRun(completeData);
|
|
359
|
-
|
|
360
|
-
`[TestDriver Reporter] ✅ Test run completion API response:`,
|
|
361
|
-
completeResponse,
|
|
362
|
-
);
|
|
607
|
+
logger.debug("Test run completion API response:", completeResponse);
|
|
363
608
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
609
|
+
// Mark test run as completed to prevent duplicate completion
|
|
610
|
+
pluginState.testRunCompleted = true;
|
|
611
|
+
|
|
612
|
+
logger.info(`✅ Test run completed: ${stats.passedTests}/${stats.totalTests} passed`);
|
|
367
613
|
} catch (error) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
error.message,
|
|
371
|
-
);
|
|
372
|
-
console.error("[TestDriver Reporter] Error stack:", error.stack);
|
|
614
|
+
logger.error("Failed to complete test run:", error.message);
|
|
615
|
+
logger.debug("Error stack:", error.stack);
|
|
373
616
|
}
|
|
374
617
|
}
|
|
375
618
|
|
|
@@ -396,15 +639,19 @@ class TestDriverReporter {
|
|
|
396
639
|
? "skipped"
|
|
397
640
|
: "failed";
|
|
398
641
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
642
|
+
logger.info(`Test case completed: ${test.name} (${status})`);
|
|
643
|
+
|
|
644
|
+
// Calculate duration from tracked start time
|
|
645
|
+
const testCase = pluginState.testCases.get(test.id);
|
|
646
|
+
const duration = testCase ? Date.now() - testCase.startTime : 0;
|
|
647
|
+
|
|
648
|
+
logger.debug(`Calculated duration: ${duration}ms (startTime: ${testCase?.startTime}, now: ${Date.now()})`);
|
|
402
649
|
|
|
403
650
|
// Read test metadata from file (cross-process communication)
|
|
404
651
|
let dashcamUrl = null;
|
|
652
|
+
let sessionId = null;
|
|
405
653
|
let testFile = "unknown";
|
|
406
654
|
let testOrder = 0;
|
|
407
|
-
let duration = result.duration || 0;
|
|
408
655
|
|
|
409
656
|
const testResultFile = path.join(
|
|
410
657
|
os.tmpdir(),
|
|
@@ -412,11 +659,15 @@ class TestDriverReporter {
|
|
|
412
659
|
`${test.id}.json`,
|
|
413
660
|
);
|
|
414
661
|
|
|
662
|
+
logger.debug(`Looking for test result file with test.id: ${test.id}`);
|
|
663
|
+
logger.debug(`Test result file path: ${testResultFile}`);
|
|
664
|
+
|
|
415
665
|
try {
|
|
416
666
|
if (fs.existsSync(testResultFile)) {
|
|
417
667
|
const testResult = JSON.parse(fs.readFileSync(testResultFile, "utf-8"));
|
|
418
668
|
dashcamUrl = testResult.dashcamUrl || null;
|
|
419
669
|
const platform = testResult.platform || null;
|
|
670
|
+
sessionId = testResult.sessionId || null;
|
|
420
671
|
testFile =
|
|
421
672
|
testResult.testFile ||
|
|
422
673
|
test.file?.filepath ||
|
|
@@ -424,18 +675,15 @@ class TestDriverReporter {
|
|
|
424
675
|
"unknown";
|
|
425
676
|
testOrder =
|
|
426
677
|
testResult.testOrder !== undefined ? testResult.testOrder : 0;
|
|
427
|
-
|
|
678
|
+
// Don't override duration from file - use Vitest's result.duration
|
|
679
|
+
// duration is already set above from result.duration
|
|
428
680
|
|
|
429
|
-
|
|
430
|
-
`[TestDriver Reporter] ✅ Read from file - dashcam: ${dashcamUrl}, platform: ${platform}, testFile: ${testFile}, testOrder: ${testOrder}, duration: ${duration}ms`,
|
|
431
|
-
);
|
|
681
|
+
logger.debug(`Read from file - dashcam: ${dashcamUrl}, platform: ${platform}, sessionId: ${sessionId}, testFile: ${testFile}, testOrder: ${testOrder}, duration: ${duration}ms`);
|
|
432
682
|
|
|
433
683
|
// Update test run platform from first test that reports it
|
|
434
684
|
if (platform && !pluginState.detectedPlatform) {
|
|
435
685
|
pluginState.detectedPlatform = platform;
|
|
436
|
-
|
|
437
|
-
`[TestDriver Reporter] 🖥️ Detected platform from test: ${platform}`,
|
|
438
|
-
);
|
|
686
|
+
logger.debug(`Detected platform from test: ${platform}`);
|
|
439
687
|
}
|
|
440
688
|
|
|
441
689
|
// Clean up the file after reading
|
|
@@ -445,37 +693,36 @@ class TestDriverReporter {
|
|
|
445
693
|
// Ignore cleanup errors
|
|
446
694
|
}
|
|
447
695
|
} else {
|
|
448
|
-
|
|
449
|
-
`[TestDriver Reporter] ⚠️ No result file found for test: ${test.id}`,
|
|
450
|
-
);
|
|
696
|
+
logger.debug(`No result file found for test: ${test.id}`);
|
|
451
697
|
// Fallback to test object properties - try multiple sources
|
|
452
|
-
// In Vitest, the file path is
|
|
453
|
-
const module = test.module || test.suite;
|
|
698
|
+
// In Vitest, the file path is on test.module.task.filepath
|
|
454
699
|
testFile =
|
|
700
|
+
test.module?.task?.filepath ||
|
|
701
|
+
test.module?.file?.filepath ||
|
|
702
|
+
test.module?.file?.name ||
|
|
455
703
|
test.file?.filepath ||
|
|
456
704
|
test.file?.name ||
|
|
457
|
-
|
|
458
|
-
|
|
705
|
+
test.suite?.file?.filepath ||
|
|
706
|
+
test.suite?.file?.name ||
|
|
459
707
|
test.location?.file ||
|
|
460
708
|
"unknown";
|
|
461
|
-
|
|
462
|
-
`[TestDriver Reporter] 📂 Resolved testFile for skipped test: ${testFile}`,
|
|
463
|
-
);
|
|
709
|
+
logger.debug(`Resolved testFile: ${testFile}`);
|
|
464
710
|
}
|
|
465
711
|
} catch (error) {
|
|
466
|
-
|
|
467
|
-
`[TestDriver Reporter] ❌ Failed to read test result file:`,
|
|
468
|
-
error.message,
|
|
469
|
-
);
|
|
712
|
+
logger.error("Failed to read test result file:", error.message);
|
|
470
713
|
// Fallback to test object properties - try multiple sources
|
|
471
|
-
|
|
714
|
+
// In Vitest, the file path is on test.module.task.filepath
|
|
472
715
|
testFile =
|
|
716
|
+
test.module?.task?.filepath ||
|
|
717
|
+
test.module?.file?.filepath ||
|
|
718
|
+
test.module?.file?.name ||
|
|
473
719
|
test.file?.filepath ||
|
|
474
720
|
test.file?.name ||
|
|
475
|
-
|
|
476
|
-
|
|
721
|
+
test.suite?.file?.filepath ||
|
|
722
|
+
test.suite?.file?.name ||
|
|
477
723
|
test.location?.file ||
|
|
478
724
|
"unknown";
|
|
725
|
+
logger.debug(`Resolved testFile from fallback: ${testFile}`);
|
|
479
726
|
}
|
|
480
727
|
|
|
481
728
|
// Get test run info from environment variables
|
|
@@ -483,9 +730,7 @@ class TestDriverReporter {
|
|
|
483
730
|
const token = process.env.TD_TEST_RUN_TOKEN;
|
|
484
731
|
|
|
485
732
|
if (!testRunId || !token) {
|
|
486
|
-
|
|
487
|
-
`[TestDriver Reporter] ⚠️ Test run not initialized, skipping test case recording for: ${test.name}`,
|
|
488
|
-
);
|
|
733
|
+
logger.warn(`Test run not initialized, skipping test case recording for: ${test.name}`);
|
|
489
734
|
return;
|
|
490
735
|
}
|
|
491
736
|
|
|
@@ -519,6 +764,11 @@ class TestDriverReporter {
|
|
|
519
764
|
retries: result.retryCount || 0,
|
|
520
765
|
};
|
|
521
766
|
|
|
767
|
+
// Add sessionId if available
|
|
768
|
+
if (sessionId) {
|
|
769
|
+
testCaseData.sessionId = sessionId;
|
|
770
|
+
}
|
|
771
|
+
|
|
522
772
|
// Only include replayUrl if we have a valid dashcam URL
|
|
523
773
|
if (dashcamUrl) {
|
|
524
774
|
testCaseData.replayUrl = dashcamUrl;
|
|
@@ -528,9 +778,7 @@ class TestDriverReporter {
|
|
|
528
778
|
if (errorMessage) testCaseData.errorMessage = errorMessage;
|
|
529
779
|
if (errorStack) testCaseData.errorStack = errorStack;
|
|
530
780
|
|
|
531
|
-
|
|
532
|
-
`[TestDriver Reporter] Recording test case: ${test.name} (${status}) with testFile: ${testFile}, testOrder: ${testOrder}, duration: ${duration}ms, replay: ${dashcamUrl ? "yes" : "no"}`,
|
|
533
|
-
);
|
|
781
|
+
logger.debug(`Recording test case: ${test.name} (${status}) with testFile: ${testFile}, testOrder: ${testOrder}, duration: ${duration}ms, replay: ${dashcamUrl ? "yes" : "no"}`);
|
|
534
782
|
|
|
535
783
|
const testCaseResponse = await recordTestCaseDirect(
|
|
536
784
|
token,
|
|
@@ -541,17 +789,10 @@ class TestDriverReporter {
|
|
|
541
789
|
const testCaseDbId = testCaseResponse.data?.id;
|
|
542
790
|
const testRunDbId = process.env.TD_TEST_RUN_DB_ID;
|
|
543
791
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
);
|
|
547
|
-
console.log(
|
|
548
|
-
`[TestDriver Reporter] 🔗 View test: ${pluginState.apiRoot.replace("testdriver-api.onrender.com", "app.testdriver.ai")}/test-runs/${testRunDbId}/${testCaseDbId}`,
|
|
549
|
-
);
|
|
792
|
+
logger.debug(`Reported test case to API${dashcamUrl ? " with dashcam URL" : ""}`);
|
|
793
|
+
logger.info(`🔗 View test: ${pluginState.apiRoot.replace("testdriver-api.onrender.com", "console.testdriver.ai")}/runs/${testRunDbId}/${testCaseDbId}`);
|
|
550
794
|
} catch (error) {
|
|
551
|
-
|
|
552
|
-
`[TestDriver Reporter] ❌ Failed to report test case:`,
|
|
553
|
-
error.message,
|
|
554
|
-
);
|
|
795
|
+
logger.error("Failed to report test case:", error.message);
|
|
555
796
|
}
|
|
556
797
|
}
|
|
557
798
|
}
|
|
@@ -571,13 +812,11 @@ function getSuiteName() {
|
|
|
571
812
|
function getPlatform() {
|
|
572
813
|
// First try to get platform from SDK client detected during test execution
|
|
573
814
|
if (pluginState.detectedPlatform) {
|
|
574
|
-
|
|
575
|
-
`[TestDriver Plugin] Using platform from SDK client: ${pluginState.detectedPlatform}`,
|
|
576
|
-
);
|
|
815
|
+
logger.debug(`Using platform from SDK client: ${pluginState.detectedPlatform}`);
|
|
577
816
|
return pluginState.detectedPlatform;
|
|
578
817
|
}
|
|
579
818
|
|
|
580
|
-
|
|
819
|
+
logger.debug("Platform not yet detected from client");
|
|
581
820
|
return null;
|
|
582
821
|
}
|
|
583
822
|
|
|
@@ -594,9 +833,7 @@ function detectPlatformFromTest(test) {
|
|
|
594
833
|
else if (platform === "linux") platform = "linux";
|
|
595
834
|
|
|
596
835
|
pluginState.detectedPlatform = platform;
|
|
597
|
-
|
|
598
|
-
`[TestDriver Plugin] Detected platform from test context: ${platform}`,
|
|
599
|
-
);
|
|
836
|
+
logger.debug(`Detected platform from test context: ${platform}`);
|
|
600
837
|
}
|
|
601
838
|
}
|
|
602
839
|
|
|
@@ -657,6 +894,59 @@ function getGitInfo() {
|
|
|
657
894
|
if (process.env.CIRCLE_USERNAME) info.author = process.env.CIRCLE_USERNAME;
|
|
658
895
|
}
|
|
659
896
|
|
|
897
|
+
// If not in CI or if commit info is missing, try to get it from local git
|
|
898
|
+
if (!info.commit) {
|
|
899
|
+
try {
|
|
900
|
+
info.commit = execSync("git rev-parse HEAD", {
|
|
901
|
+
encoding: "utf8",
|
|
902
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
903
|
+
}).trim();
|
|
904
|
+
} catch (e) {
|
|
905
|
+
// Git command failed, ignore
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
if (!info.branch) {
|
|
910
|
+
try {
|
|
911
|
+
info.branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
912
|
+
encoding: "utf8",
|
|
913
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
914
|
+
}).trim();
|
|
915
|
+
} catch (e) {
|
|
916
|
+
// Git command failed, ignore
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if (!info.author) {
|
|
921
|
+
try {
|
|
922
|
+
info.author = execSync("git config user.name", {
|
|
923
|
+
encoding: "utf8",
|
|
924
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
925
|
+
}).trim();
|
|
926
|
+
} catch (e) {
|
|
927
|
+
// Git command failed, ignore
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
if (!info.repo) {
|
|
932
|
+
try {
|
|
933
|
+
const remoteUrl = execSync("git config --get remote.origin.url", {
|
|
934
|
+
encoding: "utf8",
|
|
935
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
936
|
+
}).trim();
|
|
937
|
+
|
|
938
|
+
// Extract repo from git URL (supports both SSH and HTTPS)
|
|
939
|
+
// SSH: git@github.com:user/repo.git
|
|
940
|
+
// HTTPS: https://github.com/user/repo.git
|
|
941
|
+
const match = remoteUrl.match(/[:/]([^/:]+\/[^/:]+?)(\.git)?$/);
|
|
942
|
+
if (match) {
|
|
943
|
+
info.repo = match[1];
|
|
944
|
+
}
|
|
945
|
+
} catch (e) {
|
|
946
|
+
// Git command failed, ignore
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
660
950
|
return info;
|
|
661
951
|
}
|
|
662
952
|
|