testdriverai 7.1.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/agent/index.js +18 -45
- package/agent/interface.js +13 -2
- package/agent/lib/commands.js +1 -1
- package/agent/lib/redraw.js +1 -1
- package/agent/lib/sandbox.js +30 -2
- package/agent/lib/valid-version.js +2 -2
- package/debugger/index.html +1 -1
- package/docs/docs.json +86 -125
- 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/{guides → _drafts}/caching-selectors.mdx +125 -17
- package/docs/v7/_drafts/dashcam-title-feature.mdx +89 -0
- package/docs/v7/_drafts/error-handling.mdx +501 -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/plugin-migration.mdx +222 -0
- package/docs/v7/_drafts/prompt-cache.mdx +200 -0
- package/docs/{QUICK_START_TEST_RECORDING.md → v7/_drafts/quick-start-test-recording.mdx} +3 -3
- package/docs/v7/_drafts/sdk-logging.mdx +222 -0
- package/docs/v7/_drafts/sdk-migration.mdx +474 -0
- package/docs/v7/_drafts/sdk-v7-complete.mdx +345 -0
- package/docs/v7/{guides → _drafts}/self-hosting.mdx +1 -1
- package/docs/v7/{guides → _drafts}/troubleshooting.mdx +2 -2
- package/docs/v7/{guides → _drafts}/vitest-plugin.mdx +4 -4
- package/docs/v7/api/{ai.mdx → act.mdx} +24 -24
- package/docs/v7/api/client.mdx +1 -1
- package/docs/v7/api/dashcam.mdx +2 -2
- package/docs/v7/api/elements.mdx +143 -41
- package/docs/v7/api/find.mdx +258 -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 +1 -1
- 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 +51 -5
- 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/playwright.mdx +3 -3
- package/docs/v7/presets/chrome.mdx +16 -0
- package/docs/v7/presets/electron.mdx +18 -0
- package/docs/v7/presets/vscode.mdx +19 -0
- 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/commands/init.js +358 -0
- package/interfaces/vitest-plugin.mjs +214 -10
- package/{src → lib}/core/Dashcam.js +41 -4
- package/{src → lib}/vitest/hooks.mjs +118 -100
- package/lib/vitest/setup.mjs +44 -0
- package/package.json +9 -10
- package/sdk.d.ts +15 -2
- package/sdk.js +70 -18
- package/{self-hosted.yml → setup/aws/self-hosted.yml} +1 -1
- package/{testdriver/acceptance-sdk → test/manual}/test-console-logs.test.mjs +1 -1
- package/test/manual/test-find-api.js +73 -0
- package/test/manual/test-init.sh +54 -0
- package/test/manual/test-prompt-cache.js +96 -0
- package/test/manual/test-provision-auth.mjs +22 -0
- package/test/manual/test-sandbox-render.js +28 -0
- package/test/manual/test-sdk-methods.js +15 -0
- package/test/manual/test-sdk-refactor.js +53 -0
- package/test/manual/test-stack-trace.mjs +57 -0
- package/test/testdriver/assert.test.mjs +41 -0
- package/{testdriver/acceptance-sdk → test/testdriver}/auto-cache-key-demo.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/drag-and-drop.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/element-not-found.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/exec-js.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/exec-output.test.mjs +3 -3
- package/{testdriver/acceptance-sdk → test/testdriver}/exec-pwsh.test.mjs +3 -3
- package/{testdriver/acceptance-sdk → test/testdriver}/focus-window.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/formatted-logging.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/hover-image.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/hover-text-with-description.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/hover-text.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/match-image.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/press-keys.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/prompt.test.mjs +2 -2
- package/{testdriver/acceptance-sdk → test/testdriver}/scroll-keyboard.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/scroll-until-image.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/scroll-until-text.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/scroll.test.mjs +1 -1
- package/{src/vitest/lifecycle.mjs → test/testdriver/setup/lifecycleHelpers.mjs} +84 -99
- package/test/testdriver/setup/testHelpers.mjs +653 -0
- package/{testdriver/acceptance-sdk → test/testdriver}/type.test.mjs +1 -1
- package/vitest.config.mjs +11 -57
- 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/settings.json +0 -14
- package/AGENTS.md +0 -550
- package/CODEOWNERS +0 -2
- 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/docs/v7/guides/ci-cd/azure.mdx +0 -587
- package/docs/v7/guides/ci-cd/circleci.mdx +0 -523
- package/docs/v7/guides/ci-cd/github-actions.mdx +0 -457
- package/docs/v7/guides/ci-cd/gitlab.mdx +0 -498
- package/docs/v7/guides/ci-cd/jenkins.mdx +0 -664
- package/docs/v7/guides/ci-cd/travis.mdx +0 -438
- package/scripts/view-test-results.mjs +0 -96
- package/src/vitest/extended.mjs +0 -108
- package/src/vitest/index.mjs +0 -64
- package/src/vitest/utils.mjs +0 -150
- 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/dashcam.test.js +0 -137
- 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 -26
- package/testdriver/acceptance-sdk/hooks-example.test.mjs +0 -38
- package/testdriver/acceptance-sdk/presets-example.test.mjs +0 -87
- package/testdriver/acceptance-sdk/setup/testHelpers.mjs +0 -420
- package/testdriver/acceptance-sdk/sully-ai.test.mjs +0 -234
- package/testdriver/acceptance-sdk/type-checking-demo.js +0 -49
- 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/docs/v7/{guides → _drafts}/best-practices.mdx +0 -0
- /package/docs/v7/{guides → _drafts}/caching-ai.mdx +0 -0
- /package/docs/v7/{guides → _drafts}/caching.mdx +0 -0
- /package/docs/{MIGRATION.md → v7/_drafts/cli-to-sdk-migration.mdx} +0 -0
- /package/{CONTRIBUTING.md → docs/v7/_drafts/contributing.mdx} +0 -0
- /package/docs/v7/{progressive-apis/CORE.md → _drafts/core.mdx} +0 -0
- /package/docs/v7/{guides → _drafts}/debugging.mdx +0 -0
- /package/docs/v7/{guides → _drafts}/faq.mdx +0 -0
- /package/docs/v7/{progressive-apis/HOOKS.md → _drafts/hooks.mdx} +0 -0
- /package/docs/v7/{guides → _drafts}/migration.mdx +0 -0
- /package/docs/v7/{guides → _drafts}/performance.mdx +0 -0
- /package/docs/{PRESETS.md → v7/_drafts/presets.mdx} +0 -0
- /package/docs/v7/{progressive-apis/PROGRESSIVE_DISCLOSURE.md → _drafts/progressive-disclosure.mdx} +0 -0
- /package/docs/v7/{progressive-apis/PROVISION.md → _drafts/provision.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/docs/{TEST_RECORDING.md → v7/_drafts/test-recording.mdx} +0 -0
- /package/docs/v7/{guides → _drafts}/vitest.mdx +0 -0
- /package/docs/v7/{README.md → overview/readme.mdx} +0 -0
- /package/{src → lib}/core/index.d.ts +0 -0
- /package/{src → lib}/core/index.js +0 -0
- /package/{src → lib}/presets/index.mjs +0 -0
- /package/{src → lib}/vitest/hooks.d.ts +0 -0
- /package/{debug-locate-response.js → test/manual/debug-locate-response.js} +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}/chrome-extension.test.mjs +0 -0
- /package/{testdriver/acceptance-sdk → test/testdriver}/setup/globalTeardown.mjs +0 -0
- /package/{testdriver/acceptance-sdk → test/testdriver}/setup/vitestSetup.mjs +0 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
const BaseCommand = require("../lib/base.js");
|
|
2
|
+
const { createCommandDefinitions } = require("../../../agent/interface.js");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const chalk = require("chalk");
|
|
6
|
+
const { execSync } = require("child_process");
|
|
7
|
+
const readline = require("readline");
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Init command - scaffolds Vitest SDK example tests for TestDriver
|
|
11
|
+
*/
|
|
12
|
+
class InitCommand extends BaseCommand {
|
|
13
|
+
async run() {
|
|
14
|
+
await this.parse(InitCommand);
|
|
15
|
+
|
|
16
|
+
console.log(chalk.cyan("\n🚀 Initializing TestDriver project...\n"));
|
|
17
|
+
|
|
18
|
+
await this.setupPackageJson();
|
|
19
|
+
await this.createVitestExample();
|
|
20
|
+
await this.createGitHubWorkflow();
|
|
21
|
+
await this.createGitignore();
|
|
22
|
+
await this.installDependencies();
|
|
23
|
+
await this.promptForApiKey();
|
|
24
|
+
|
|
25
|
+
console.log(chalk.green("\n✅ Project initialized successfully!\n"));
|
|
26
|
+
this.printNextSteps();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Prompt user for API key and save to .env
|
|
31
|
+
*/
|
|
32
|
+
async promptForApiKey() {
|
|
33
|
+
const envPath = path.join(process.cwd(), ".env");
|
|
34
|
+
|
|
35
|
+
// Check if .env already exists with TD_API_KEY
|
|
36
|
+
if (fs.existsSync(envPath)) {
|
|
37
|
+
const envContent = fs.readFileSync(envPath, "utf8");
|
|
38
|
+
if (envContent.includes("TD_API_KEY=")) {
|
|
39
|
+
console.log(chalk.gray("\n API key already configured in .env, skipping...\n"));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log(chalk.cyan(" Setting up your TestDriver API key...\n"));
|
|
45
|
+
console.log(chalk.gray(" Get your API key from: https://console.testdriver.ai/team"));
|
|
46
|
+
|
|
47
|
+
// Ask if user wants to open the browser
|
|
48
|
+
const shouldOpen = await this.askYesNo(" Open API keys page in browser? (Y/n): ");
|
|
49
|
+
if (shouldOpen) {
|
|
50
|
+
try {
|
|
51
|
+
// Dynamic import for ES module
|
|
52
|
+
const open = (await import("open")).default;
|
|
53
|
+
await open("https://console.testdriver.ai/team");
|
|
54
|
+
console.log(chalk.gray(" Opening browser...\n"));
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.log(chalk.yellow(" ⚠️ Could not open browser automatically\n"));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Prompt for API key with hidden input
|
|
61
|
+
const apiKey = await this.promptHidden(" Enter your API key (input will be hidden): ");
|
|
62
|
+
|
|
63
|
+
if (apiKey && apiKey.trim()) {
|
|
64
|
+
// Save to .env
|
|
65
|
+
const envContent = fs.existsSync(envPath)
|
|
66
|
+
? fs.readFileSync(envPath, "utf8") + "\n"
|
|
67
|
+
: "";
|
|
68
|
+
|
|
69
|
+
fs.writeFileSync(envPath, envContent + `TD_API_KEY=${apiKey.trim()}\n`);
|
|
70
|
+
console.log(chalk.green("\n ✓ API key saved to .env\n"));
|
|
71
|
+
} else {
|
|
72
|
+
console.log(chalk.yellow("\n ⚠️ No API key entered. You can add it later to .env:\n"));
|
|
73
|
+
console.log(chalk.gray(" TD_API_KEY=your_api_key\n"));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Prompt for hidden input (like password)
|
|
79
|
+
*/
|
|
80
|
+
async promptHidden(question) {
|
|
81
|
+
return new Promise((resolve) => {
|
|
82
|
+
const rl = readline.createInterface({
|
|
83
|
+
input: process.stdin,
|
|
84
|
+
output: process.stdout,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Mute output to hide the input
|
|
88
|
+
const stdin = process.stdin;
|
|
89
|
+
const muted = {
|
|
90
|
+
write: () => {},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
rl.question(question, (answer) => {
|
|
94
|
+
rl.close();
|
|
95
|
+
stdin.removeListener("data", muted.write);
|
|
96
|
+
console.log(""); // New line after hidden input
|
|
97
|
+
resolve(answer);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Mute stdin to hide input
|
|
101
|
+
stdin.on("data", (char) => {
|
|
102
|
+
// Don't write to output (hides the input)
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Ask a yes/no question
|
|
109
|
+
*/
|
|
110
|
+
async askYesNo(question) {
|
|
111
|
+
return new Promise((resolve) => {
|
|
112
|
+
const rl = readline.createInterface({
|
|
113
|
+
input: process.stdin,
|
|
114
|
+
output: process.stdout,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
rl.question(question, (answer) => {
|
|
118
|
+
rl.close();
|
|
119
|
+
const normalized = answer.toLowerCase().trim();
|
|
120
|
+
resolve(normalized === "" || normalized === "y" || normalized === "yes");
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Setup package.json if it doesn't exist
|
|
127
|
+
*/
|
|
128
|
+
async setupPackageJson() {
|
|
129
|
+
const packageJsonPath = path.join(process.cwd(), "package.json");
|
|
130
|
+
|
|
131
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
132
|
+
console.log(chalk.gray(" Creating package.json..."));
|
|
133
|
+
|
|
134
|
+
const packageJson = {
|
|
135
|
+
name: path.basename(process.cwd()),
|
|
136
|
+
version: "1.0.0",
|
|
137
|
+
description: "TestDriver.ai test suite",
|
|
138
|
+
type: "module",
|
|
139
|
+
scripts: {
|
|
140
|
+
test: "vitest run",
|
|
141
|
+
"test:watch": "vitest",
|
|
142
|
+
"test:ui": "vitest --ui"
|
|
143
|
+
},
|
|
144
|
+
keywords: ["testdriver", "testing", "e2e"],
|
|
145
|
+
author: "",
|
|
146
|
+
license: "ISC"
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n");
|
|
150
|
+
console.log(chalk.green(` Created package.json`));
|
|
151
|
+
} else {
|
|
152
|
+
console.log(chalk.gray(" package.json already exists, skipping..."));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Create a Vitest SDK example
|
|
158
|
+
*/
|
|
159
|
+
async createVitestExample() {
|
|
160
|
+
const testDir = path.join(process.cwd(), "tests");
|
|
161
|
+
const testFile = path.join(testDir, "example.test.js");
|
|
162
|
+
const configFile = path.join(process.cwd(), "vitest.config.js");
|
|
163
|
+
|
|
164
|
+
// Create test directory if it doesn't exist
|
|
165
|
+
if (!fs.existsSync(testDir)) {
|
|
166
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
167
|
+
console.log(chalk.gray(` Created directory: ${testDir}`));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Create example Vitest test
|
|
171
|
+
const vitestContent = `import { test, expect } from 'vitest';
|
|
172
|
+
import { chrome } from 'testdriverai/presets';
|
|
173
|
+
|
|
174
|
+
test('should navigate to example.com and find elements', async (context) => {
|
|
175
|
+
// The chrome preset handles connection, browser launch, and cleanup automatically
|
|
176
|
+
const { testdriver } = await chrome(context, {
|
|
177
|
+
url: 'https://example.com'
|
|
178
|
+
// apiKey automatically read from process.env.TD_API_KEY via .env file
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Find and verify elements
|
|
182
|
+
const heading = await testdriver.find('heading that says Example Domain');
|
|
183
|
+
expect(heading.found()).toBe(true);
|
|
184
|
+
|
|
185
|
+
const link = await testdriver.find('More information link');
|
|
186
|
+
expect(link.found()).toBe(true);
|
|
187
|
+
});
|
|
188
|
+
`;
|
|
189
|
+
|
|
190
|
+
fs.writeFileSync(testFile, vitestContent);
|
|
191
|
+
console.log(chalk.green(` Created test file: ${testFile}`));
|
|
192
|
+
|
|
193
|
+
// Create vitest config if it doesn't exist
|
|
194
|
+
if (!fs.existsSync(configFile)) {
|
|
195
|
+
const configContent = `import { defineConfig } from 'vitest/config';
|
|
196
|
+
import TestDriver from 'testdriverai/vitest';
|
|
197
|
+
import dotenv from 'dotenv';
|
|
198
|
+
|
|
199
|
+
// Load environment variables from .env file
|
|
200
|
+
dotenv.config();
|
|
201
|
+
|
|
202
|
+
export default defineConfig({
|
|
203
|
+
plugins: [TestDriver()],
|
|
204
|
+
test: {
|
|
205
|
+
testTimeout: 120000,
|
|
206
|
+
hookTimeout: 120000,
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
`;
|
|
210
|
+
|
|
211
|
+
fs.writeFileSync(configFile, configContent);
|
|
212
|
+
console.log(chalk.green(` Created config file: ${configFile}`));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Create or update .gitignore to include .env
|
|
219
|
+
*/
|
|
220
|
+
async createGitignore() {
|
|
221
|
+
const gitignorePath = path.join(process.cwd(), ".gitignore");
|
|
222
|
+
|
|
223
|
+
let gitignoreContent = "";
|
|
224
|
+
if (fs.existsSync(gitignorePath)) {
|
|
225
|
+
gitignoreContent = fs.readFileSync(gitignorePath, "utf8");
|
|
226
|
+
|
|
227
|
+
// Check if .env is already in .gitignore
|
|
228
|
+
if (gitignoreContent.includes(".env")) {
|
|
229
|
+
console.log(chalk.gray(" .env already in .gitignore, skipping..."));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Add common ignores including .env
|
|
235
|
+
const ignoresToAdd = [
|
|
236
|
+
"",
|
|
237
|
+
"# TestDriver.ai",
|
|
238
|
+
".env",
|
|
239
|
+
"node_modules/",
|
|
240
|
+
"test-results/",
|
|
241
|
+
"*.log",
|
|
242
|
+
];
|
|
243
|
+
|
|
244
|
+
const newContent = gitignoreContent.trim()
|
|
245
|
+
? gitignoreContent + "\n" + ignoresToAdd.join("\n") + "\n"
|
|
246
|
+
: ignoresToAdd.join("\n") + "\n";
|
|
247
|
+
|
|
248
|
+
fs.writeFileSync(gitignorePath, newContent);
|
|
249
|
+
console.log(chalk.green(" Updated .gitignore"));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Create GitHub Actions workflow
|
|
254
|
+
*/
|
|
255
|
+
async createGitHubWorkflow() {
|
|
256
|
+
const workflowDir = path.join(process.cwd(), ".github", "workflows");
|
|
257
|
+
const workflowFile = path.join(workflowDir, "testdriver.yml");
|
|
258
|
+
|
|
259
|
+
// Create .github/workflows directory if it doesn't exist
|
|
260
|
+
if (!fs.existsSync(workflowDir)) {
|
|
261
|
+
fs.mkdirSync(workflowDir, { recursive: true });
|
|
262
|
+
console.log(chalk.gray(` Created directory: ${workflowDir}`));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (!fs.existsSync(workflowFile)) {
|
|
266
|
+
const workflowContent = `name: TestDriver.ai Tests
|
|
267
|
+
|
|
268
|
+
on:
|
|
269
|
+
push:
|
|
270
|
+
branches: [ main, master ]
|
|
271
|
+
pull_request:
|
|
272
|
+
branches: [ main, master ]
|
|
273
|
+
|
|
274
|
+
jobs:
|
|
275
|
+
test:
|
|
276
|
+
runs-on: ubuntu-latest
|
|
277
|
+
|
|
278
|
+
steps:
|
|
279
|
+
- uses: actions/checkout@v4
|
|
280
|
+
|
|
281
|
+
- name: Setup Node.js
|
|
282
|
+
uses: actions/setup-node@v4
|
|
283
|
+
with:
|
|
284
|
+
node-version: '20'
|
|
285
|
+
cache: 'npm'
|
|
286
|
+
|
|
287
|
+
- name: Install dependencies
|
|
288
|
+
run: npm ci
|
|
289
|
+
|
|
290
|
+
- name: Run TestDriver.ai tests
|
|
291
|
+
env:
|
|
292
|
+
TD_API_KEY: \${{ secrets.TD_API_KEY }}
|
|
293
|
+
run: npm test
|
|
294
|
+
|
|
295
|
+
- name: Upload test results
|
|
296
|
+
if: always()
|
|
297
|
+
uses: actions/upload-artifact@v4
|
|
298
|
+
with:
|
|
299
|
+
name: test-results
|
|
300
|
+
path: test-results/
|
|
301
|
+
retention-days: 30
|
|
302
|
+
`;
|
|
303
|
+
|
|
304
|
+
fs.writeFileSync(workflowFile, workflowContent);
|
|
305
|
+
console.log(chalk.green(` Created GitHub workflow: ${workflowFile}`));
|
|
306
|
+
} else {
|
|
307
|
+
console.log(chalk.gray(" GitHub workflow already exists, skipping..."));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Install dependencies
|
|
313
|
+
*/
|
|
314
|
+
async installDependencies() {
|
|
315
|
+
console.log(chalk.cyan("\n Installing dependencies...\n"));
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
execSync("npm install -D vitest testdriverai && npm install dotenv", {
|
|
319
|
+
cwd: process.cwd(),
|
|
320
|
+
stdio: "inherit"
|
|
321
|
+
});
|
|
322
|
+
console.log(chalk.green("\n Dependencies installed successfully!"));
|
|
323
|
+
} catch (error) {
|
|
324
|
+
console.log(
|
|
325
|
+
chalk.yellow(
|
|
326
|
+
"\n⚠️ Failed to install dependencies automatically. Please run:",
|
|
327
|
+
),
|
|
328
|
+
);
|
|
329
|
+
console.log(chalk.gray(" npm install -D vitest testdriverai"));
|
|
330
|
+
console.log(chalk.gray(" npm install dotenv\n"));
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Print next steps
|
|
336
|
+
*/
|
|
337
|
+
printNextSteps() {
|
|
338
|
+
console.log(chalk.cyan("Next steps:\n"));
|
|
339
|
+
console.log(" 1. Run your tests:");
|
|
340
|
+
console.log(chalk.gray(" npm test\n"));
|
|
341
|
+
console.log(" 2. For CI/CD, add TD_API_KEY to your GitHub repository secrets");
|
|
342
|
+
console.log(chalk.gray(" Settings → Secrets → Actions → New repository secret\n"));
|
|
343
|
+
console.log(
|
|
344
|
+
chalk.cyan("Learn more at https://docs.testdriver.ai/getting-started\n"),
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Get command definition from interface.js
|
|
350
|
+
const tempAgent = { workingDir: process.cwd() };
|
|
351
|
+
const definitions = createCommandDefinitions(tempAgent);
|
|
352
|
+
const commandDef = definitions["init"];
|
|
353
|
+
|
|
354
|
+
InitCommand.description = commandDef?.description || "";
|
|
355
|
+
InitCommand.args = commandDef?.args || {};
|
|
356
|
+
InitCommand.flags = commandDef?.flags || {};
|
|
357
|
+
|
|
358
|
+
module.exports = InitCommand;
|
|
@@ -1,9 +1,14 @@
|
|
|
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
|
+
|
|
7
12
|
/**
|
|
8
13
|
* Simple logger for the vitest plugin
|
|
9
14
|
* Supports log levels: debug, info, warn, error
|
|
@@ -218,6 +223,126 @@ export async function recordTestCaseDirect(token, apiRoot, testCaseData) {
|
|
|
218
223
|
return await response.json();
|
|
219
224
|
}
|
|
220
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
|
+
|
|
221
346
|
/**
|
|
222
347
|
* Handle process termination and mark test run as cancelled
|
|
223
348
|
*/
|
|
@@ -292,10 +417,10 @@ function registerExitHandlers() {
|
|
|
292
417
|
* This sets up global state and provides the registration API
|
|
293
418
|
*/
|
|
294
419
|
export default function testDriverPlugin(options = {}) {
|
|
295
|
-
//
|
|
296
|
-
|
|
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
|
|
297
422
|
pluginState.apiRoot =
|
|
298
|
-
options.apiRoot || process.env.TD_API_ROOT || "
|
|
423
|
+
options.apiRoot || process.env.TD_API_ROOT || "https://testdriver-api.onrender.com";
|
|
299
424
|
pluginState.ciProvider = detectCI();
|
|
300
425
|
pluginState.gitInfo = getGitInfo();
|
|
301
426
|
|
|
@@ -307,7 +432,11 @@ export default function testDriverPlugin(options = {}) {
|
|
|
307
432
|
registerExitHandlers();
|
|
308
433
|
|
|
309
434
|
// Note: globalThis setup happens in vitestSetup.mjs for worker processes
|
|
310
|
-
logger.debug("
|
|
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");
|
|
311
440
|
if (Object.keys(testDriverOptions).length > 0) {
|
|
312
441
|
logger.debug("Global TestDriver options:", testDriverOptions);
|
|
313
442
|
}
|
|
@@ -322,12 +451,22 @@ export default function testDriverPlugin(options = {}) {
|
|
|
322
451
|
class TestDriverReporter {
|
|
323
452
|
constructor(options = {}) {
|
|
324
453
|
this.options = options;
|
|
325
|
-
logger.debug("Reporter created");
|
|
454
|
+
logger.debug("Reporter created with options:", { hasApiKey: !!options.apiKey, hasApiRoot: !!options.apiRoot });
|
|
326
455
|
}
|
|
327
456
|
|
|
328
457
|
async onInit(ctx) {
|
|
329
458
|
this.ctx = ctx;
|
|
330
|
-
logger.debug("onInit called");
|
|
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);
|
|
331
470
|
|
|
332
471
|
// Initialize test run
|
|
333
472
|
await this.initializeTestRun();
|
|
@@ -335,16 +474,23 @@ class TestDriverReporter {
|
|
|
335
474
|
|
|
336
475
|
async initializeTestRun() {
|
|
337
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);
|
|
338
479
|
|
|
339
480
|
// Check if we should enable the reporter
|
|
340
481
|
if (!pluginState.apiKey) {
|
|
341
|
-
logger.
|
|
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);
|
|
342
484
|
return;
|
|
343
485
|
}
|
|
344
486
|
|
|
487
|
+
logger.info("Starting test run initialization with API key...");
|
|
488
|
+
|
|
345
489
|
try {
|
|
346
490
|
// Exchange API key for JWT token
|
|
491
|
+
logger.debug("Authenticating with API...");
|
|
347
492
|
await authenticate();
|
|
493
|
+
logger.debug("Authentication successful, token received");
|
|
348
494
|
|
|
349
495
|
// Generate unique run ID
|
|
350
496
|
pluginState.testRunId = generateRunId();
|
|
@@ -369,7 +515,9 @@ class TestDriverReporter {
|
|
|
369
515
|
// Default to linux if no tests write platform info
|
|
370
516
|
testRunData.platform = "linux";
|
|
371
517
|
|
|
518
|
+
logger.debug("Creating test run with data:", testRunData);
|
|
372
519
|
pluginState.testRun = await createTestRun(testRunData);
|
|
520
|
+
logger.debug("Test run created successfully:", pluginState.testRun);
|
|
373
521
|
|
|
374
522
|
// Store in environment variables for worker processes to access
|
|
375
523
|
process.env.TD_TEST_RUN_ID = pluginState.testRunId;
|
|
@@ -396,17 +544,20 @@ class TestDriverReporter {
|
|
|
396
544
|
|
|
397
545
|
async onTestRunEnd(testModules, unhandledErrors, reason) {
|
|
398
546
|
logger.debug("Test run ending with reason:", reason);
|
|
547
|
+
logger.debug("Plugin state - API key present:", !!pluginState.apiKey, "Test run present:", !!pluginState.testRun);
|
|
399
548
|
|
|
400
549
|
if (!pluginState.apiKey) {
|
|
401
|
-
logger.
|
|
550
|
+
logger.warn("Skipping completion - no API key (was it cleared after init failure?)");
|
|
402
551
|
return;
|
|
403
552
|
}
|
|
404
553
|
|
|
405
554
|
if (!pluginState.testRun) {
|
|
406
|
-
logger.
|
|
555
|
+
logger.warn("Skipping completion - no test run created (check initialization logs)");
|
|
407
556
|
return;
|
|
408
557
|
}
|
|
409
558
|
|
|
559
|
+
logger.info("Completing test run...");
|
|
560
|
+
|
|
410
561
|
try {
|
|
411
562
|
// Calculate statistics from testModules
|
|
412
563
|
const stats = calculateStatsFromModules(testModules);
|
|
@@ -639,7 +790,7 @@ class TestDriverReporter {
|
|
|
639
790
|
const testRunDbId = process.env.TD_TEST_RUN_DB_ID;
|
|
640
791
|
|
|
641
792
|
logger.debug(`Reported test case to API${dashcamUrl ? " with dashcam URL" : ""}`);
|
|
642
|
-
logger.info(`🔗 View test: ${pluginState.apiRoot.replace("testdriver-api.onrender.com", "
|
|
793
|
+
logger.info(`🔗 View test: ${pluginState.apiRoot.replace("testdriver-api.onrender.com", "console.testdriver.ai")}/runs/${testRunDbId}/${testCaseDbId}`);
|
|
643
794
|
} catch (error) {
|
|
644
795
|
logger.error("Failed to report test case:", error.message);
|
|
645
796
|
}
|
|
@@ -743,6 +894,59 @@ function getGitInfo() {
|
|
|
743
894
|
if (process.env.CIRCLE_USERNAME) info.author = process.env.CIRCLE_USERNAME;
|
|
744
895
|
}
|
|
745
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
|
+
|
|
746
950
|
return info;
|
|
747
951
|
}
|
|
748
952
|
|