testdriverai 7.9.0-test.9 → 7.9.1-test

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.
Files changed (38) hide show
  1. package/agent/lib/sandbox.js +55 -6
  2. package/agent/lib/sdk.js +4 -4
  3. package/ai/skills/testdriver-enterprise/SKILL.md +2 -109
  4. package/ai/skills/testdriver-hosted/SKILL.md +156 -0
  5. package/ai/skills/testdriver-mcp/SKILL.md +2 -2
  6. package/ai/skills/testdriver-quickstart/SKILL.md +30 -2
  7. package/ai/skills/testdriver-self-hosted/SKILL.md +125 -43
  8. package/ai/skills/testdriver-test-results-json/SKILL.md +257 -0
  9. package/docs/_scripts/generate-examples.js +127 -60
  10. package/docs/docs.json +27 -28
  11. package/docs/v7/examples/ai.mdx +4 -3
  12. package/docs/v7/examples/assert.mdx +19 -4
  13. package/docs/v7/examples/chrome-extension.mdx +36 -29
  14. package/docs/v7/examples/element-not-found.mdx +2 -1
  15. package/docs/v7/examples/exec-output.mdx +3 -4
  16. package/docs/v7/examples/exec-pwsh.mdx +3 -4
  17. package/docs/v7/examples/findall-coffee-icons.mdx +88 -0
  18. package/docs/v7/examples/focus-window.mdx +3 -4
  19. package/docs/v7/examples/hover-image.mdx +4 -3
  20. package/docs/v7/examples/hover-text-with-description.mdx +104 -0
  21. package/docs/v7/examples/hover-text.mdx +4 -3
  22. package/docs/v7/examples/installer.mdx +5 -4
  23. package/docs/v7/examples/launch-vscode-linux.mdx +3 -7
  24. package/docs/v7/examples/match-image.mdx +3 -2
  25. package/docs/v7/examples/parse.mdx +66 -0
  26. package/docs/v7/examples/press-keys.mdx +8 -14
  27. package/docs/v7/examples/scroll-keyboard.mdx +4 -3
  28. package/docs/v7/examples/scroll-until-image.mdx +3 -2
  29. package/docs/v7/examples/scroll.mdx +6 -14
  30. package/docs/v7/examples/type.mdx +1 -5
  31. package/docs/v7/examples/windows-installer.mdx +10 -4
  32. package/interfaces/vitest-plugin.mjs +2 -2
  33. package/lib/core/Dashcam.js +4 -1
  34. package/lib/sentry.js +5 -1
  35. package/package.json +1 -1
  36. package/setup/aws/install-dev-runner.sh +7 -2
  37. package/setup/aws/spawn-runner.sh +12 -0
  38. package/vitest.config.mjs +1 -1
@@ -12,6 +12,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
12
12
  this._ably = null;
13
13
  this._sessionChannel = null;
14
14
  this._channelName = null;
15
+ this._membersChannelName = null;
15
16
  this.ps = {};
16
17
  this._execBuffers = {}; // accumulate streamed exec.output chunks per requestId
17
18
  this.heartbeat = null;
@@ -53,7 +54,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
53
54
  return this._publishCount;
54
55
  }
55
56
 
56
- async _initAbly(ablyToken, channelName) {
57
+ async _initAbly(ablyToken, channelName, membersChannelName) {
57
58
  if (this._ably) {
58
59
  try {
59
60
  this._ably.close();
@@ -62,6 +63,19 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
62
63
  }
63
64
  }
64
65
  this._channelName = channelName;
66
+
67
+ // Derive the members channel from the session channel if not supplied.
68
+ // Format: testdriver:{env}:{teamId}:{sandboxId} → testdriver:{env}:{teamId}:members
69
+ if (!membersChannelName) {
70
+ const parts = channelName.split(":");
71
+ if (parts.length >= 3) {
72
+ membersChannelName = parts.slice(0, 3).join(":") + ":members";
73
+ } else {
74
+ logger.warn("[ably] Channel name format unexpected (" + channelName + "), cannot derive members channel");
75
+ }
76
+ }
77
+ this._membersChannelName = membersChannelName;
78
+
65
79
  var self = this;
66
80
 
67
81
  this._ably = new Ably.Realtime({
@@ -103,9 +117,32 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
103
117
 
104
118
  this._sessionChannel = this._ably.channels.get(channelName);
105
119
 
106
- logger.debug(`[realtime] Channel initialized: ${channelName}`);
120
+ // Explicitly attach the session channel BEFORE entering presence on the
121
+ // members channel. Entering members-presence triggers the API's waitpoint
122
+ // completion → claim-slot task → publishes slot-approved on the session
123
+ // channel. If the session channel isn't attached yet, that message lands
124
+ // before our attachment point and historyBeforeSubscribe() won't see it.
125
+ await this._sessionChannel.attach();
126
+ logger.debug(`[realtime] Channel attached: ${channelName}`);
127
+
128
+ // Enter presence on the team members channel so the API can count
129
+ // connected SDK clients with a single direct lookup per team.
130
+ if (membersChannelName) {
131
+ try {
132
+ var membersChannel = this._ably.channels.get(membersChannelName);
133
+ await membersChannel.presence.enter({
134
+ sandboxId: this._sandboxId,
135
+ connectedAt: Date.now(),
136
+ });
137
+ logger.debug(`[realtime] Entered presence on members channel (sandbox=${this._sandboxId})`);
138
+ } catch (e) {
139
+ // Non-fatal — presence is used for concurrency counting, not critical path
140
+ logger.warn("Failed to enter presence on members channel: " + (e.message || e));
141
+ }
142
+ }
107
143
 
108
- // Enter presence on the session channel so the API can count connected SDK clients
144
+ // Enter presence on the session channel so the API's session monitor can
145
+ // detect SDK connect/disconnect events for this sandbox.
109
146
  try {
110
147
  await this._sessionChannel.presence.enter({
111
148
  sandboxId: this._sandboxId,
@@ -113,7 +150,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
113
150
  });
114
151
  logger.debug(`[realtime] Entered presence on session channel (sandbox=${this._sandboxId})`);
115
152
  } catch (e) {
116
- // Non-fatal — presence is used for concurrency counting, not critical path
153
+ // Non-fatal — presence is used for disconnect detection, not critical path
117
154
  logger.warn("Failed to enter presence on session channel: " + (e.message || e));
118
155
  }
119
156
 
@@ -510,6 +547,8 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
510
547
  timeout,
511
548
  );
512
549
 
550
+ logger.debug(`[sandbox] authenticate response: success=${reply.success} status=${reply.status || 'none'} sandboxId=${reply.sandboxId || 'none'}`);
551
+
513
552
  if (!reply.success) {
514
553
  var err = new Error(
515
554
  reply.errorMessage || "Failed to allocate sandbox",
@@ -522,7 +561,8 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
522
561
  this._teamId = reply.teamId;
523
562
 
524
563
  if (reply.ably && reply.ably.token) {
525
- await this._initAbly(reply.ably.token, reply.ably.channel);
564
+ logger.debug(`[sandbox] Initializing Ably with channel=${reply.ably.channel}, membersChannel=${reply.ably.membersChannel || '(derived)'}`);
565
+ await this._initAbly(reply.ably.token, reply.ably.channel, reply.ably.membersChannel);
526
566
  this.instanceSocketConnected = true;
527
567
 
528
568
  // Tell the runner to enable debug log forwarding if debug mode is on
@@ -547,6 +587,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
547
587
  var slotPollStart = Date.now();
548
588
  while (reply.status === 'pending') {
549
589
  logger.log('Slot claim pending — waiting for approval via Ably...');
590
+ logger.debug(`[slots] sandboxId=${this._sandboxId} channel=${this._channelName} membersChannel=${this._membersChannelName}`);
550
591
 
551
592
  var self = this;
552
593
  var slotResolved = false;
@@ -587,24 +628,32 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
587
628
  // The claim-slot task fires in response to presence enter, so the
588
629
  // decision may already be published by the time we get here.
589
630
  var slotControlSub = await self._sessionChannel.subscribe('control', onSlotControl);
631
+ logger.debug(`[slots] Subscribed to control channel for slot decision (sandboxId=${this._sandboxId})`);
590
632
 
591
633
  // Check for decisions published before this subscription was active
592
634
  if (!slotResolved && slotControlSub) {
593
635
  try {
636
+ logger.debug(`[slots] Checking history for pre-subscription slot decisions (sandboxId=${this._sandboxId})`);
637
+ logger.debug(`[slots] Checking history for pre-subscription slot decisions (sandboxId=${this._sandboxId})`);
594
638
  var histPage = await slotControlSub.historyBeforeSubscribe({ limit: 10 });
639
+ var histItemCount = 0;
595
640
  while (histPage && !slotResolved) {
596
641
  for (var hi = 0; hi < histPage.items.length; hi++) {
642
+ histItemCount++;
643
+ logger.debug(`[slots] History item: type=${histPage.items[hi].data && histPage.items[hi].data.type} (sandboxId=${this._sandboxId})`);
597
644
  onSlotControl(histPage.items[hi]);
598
645
  if (slotResolved) break;
599
646
  }
600
647
  histPage = (!slotResolved && histPage.hasNext()) ? await histPage.next() : null;
601
648
  }
649
+ logger.debug(`[slots] History check done: ${histItemCount} items checked, resolved=${slotResolved} (sandboxId=${this._sandboxId})`);
602
650
  } catch (histErr) {
603
651
  logger.warn('[slots] Failed to check history for slot decision: ' + (histErr.message || histErr));
604
652
  }
605
653
  }
606
654
 
607
655
  var slotDecision = await slotDecisionPromise;
656
+ logger.debug(`[slots] Slot decision received: approved=${slotDecision.approved} sandboxId=${this._sandboxId} elapsedMs=${Date.now() - slotPollStart}`);
608
657
 
609
658
  if (!slotDecision.approved) {
610
659
  // Slot denied — disconnect Ably and re-try the full authenticate
@@ -648,7 +697,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
648
697
  if (reply.status === 'pending' && reply.ably && reply.ably.token) {
649
698
  this._sandboxId = reply.sandboxId;
650
699
  this._teamId = reply.teamId;
651
- await this._initAbly(reply.ably.token, reply.ably.channel);
700
+ await this._initAbly(reply.ably.token, reply.ably.channel, reply.ably.membersChannel);
652
701
  this.instanceSocketConnected = true;
653
702
  }
654
703
 
package/agent/lib/sdk.js CHANGED
@@ -273,9 +273,9 @@ const createSDK = (emitter, config, sessionInstance) => {
273
273
  if (status >= 500) {
274
274
  const serverError = new Error(
275
275
  data?.message ||
276
- `TestDriver API is currently unavailable (HTTP ${status}). Please try again later.`
276
+ `An error occurred on the TestDriver server (HTTP ${status}). Please try again later.`
277
277
  );
278
- serverError.code = data?.error || "API_UNAVAILABLE";
278
+ serverError.code = data?.error || "SERVER_ERROR";
279
279
  serverError.isServerError = true;
280
280
  serverError.originalError = error;
281
281
  return serverError;
@@ -567,9 +567,9 @@ const createSDK = (emitter, config, sessionInstance) => {
567
567
  if (status >= 500) {
568
568
  const serverError = new Error(
569
569
  error.response?.data?.message ||
570
- `TestDriver API is currently unavailable (HTTP ${status}). Please try again later.`
570
+ `An error occurred on the TestDriver server (HTTP ${status}). Please try again later.`
571
571
  );
572
- serverError.code = error.response?.data?.error || "API_UNAVAILABLE";
572
+ serverError.code = error.response?.data?.error || "SERVER_ERROR";
573
573
  serverError.isServerError = true;
574
574
  serverError.originalError = error;
575
575
  serverError.path = path;
@@ -1,114 +1,7 @@
1
1
  ---
2
2
  name: testdriver:enterprise
3
- description: Air-gapped security and full customization for demanding environments
3
+ description: Self-hosted enterprise deployments with assisted setup and dedicated support
4
4
  ---
5
5
  <!-- Generated from enterprise.mdx. DO NOT EDIT. -->
6
6
 
7
- ## Why Enterprise?
8
-
9
- <CardGroup cols={2}>
10
- <Card title="Air-Gapped Security" icon="shield-check">
11
- Deploy everything in your environment. No data leaves your network. Complete isolation from external services.
12
- </Card>
13
- <Card title="Full Customization" icon="gear">
14
- Custom integrations, dedicated infrastructure, and tailored solutions for your unique requirements.
15
- </Card>
16
- <Card title="Self-Hosted Dashboard & API" icon="server">
17
- Run the entire TestDriver stack — dashboard, API, and test infrastructure — within your own environment.
18
- </Card>
19
- <Card title="Dedicated Support" icon="headset">
20
- Direct access to our engineering team for implementation, customization, and ongoing support.
21
- </Card>
22
- </CardGroup>
23
-
24
- ## Who Needs Enterprise?
25
-
26
- Enterprise is designed for organizations that:
27
-
28
- - **Require air-gapped deployments** — Regulated industries, government, defense, or strict compliance requirements
29
- - **Cannot use external APIs** — Data must never leave your network perimeter
30
- - **Need custom integrations** — Unique CI/CD systems, internal tools, or specialized workflows
31
- - **Want dedicated support** — Direct engineering support for complex implementations
32
-
33
- ## What's Included
34
-
35
- ### Fully Self-Hosted Stack
36
-
37
- Unlike [Self-Hosted](/v7/self-hosted) (which uses TestDriver's hosted dashboard and API), Enterprise deploys everything in your environment:
38
-
39
- | Component | Self-Hosted | Enterprise |
40
- |-----------|-------------|------------|
41
- | Test Sandboxes | Your infrastructure | Your infrastructure |
42
- | Dashboard | TestDriver hosted | Your infrastructure |
43
- | API | TestDriver hosted | Your infrastructure |
44
- | AI Processing | Your API keys | Your infrastructure |
45
- | Data Storage | Your AWS account | Your infrastructure |
46
-
47
- ### Custom Contract Terms
48
-
49
- - Volume-based pricing
50
- - Custom SLAs
51
- - Dedicated support channels
52
- - Professional services for implementation
53
- - Training for your team
54
-
55
- ## Implementation Process
56
-
57
- <Steps>
58
- <Step title="Discovery Call">
59
- Discuss your requirements, security constraints, and integration needs with our team.
60
- </Step>
61
-
62
- <Step title="Architecture Review">
63
- Our engineers design a deployment architecture that meets your security and compliance requirements.
64
- </Step>
65
-
66
- <Step title="Deployment">
67
- We work with your team to deploy TestDriver within your environment, including dashboard, API, and test infrastructure.
68
- </Step>
69
-
70
- <Step title="Integration">
71
- Connect TestDriver to your CI/CD pipelines, internal tools, and workflows.
72
- </Step>
73
-
74
- <Step title="Training & Handoff">
75
- Comprehensive training for your team on operating and maintaining the deployment.
76
- </Step>
77
- </Steps>
78
-
79
- ## Security & Compliance
80
-
81
- Enterprise deployments support:
82
-
83
- - **SOC 2** compliance requirements
84
- - **HIPAA** for healthcare applications
85
- - **FedRAMP** for government deployments
86
- - **PCI DSS** for payment processing
87
- - **Custom compliance frameworks** as needed
88
-
89
- <Note>
90
- All data remains within your network perimeter. TestDriver has no access to your test results, application data, or infrastructure.
91
- </Note>
92
-
93
- ## Comparison: Self-Hosted vs Enterprise
94
-
95
- | Feature | Self-Hosted | Enterprise |
96
- |---------|-------------|------------|
97
- | **Test Infrastructure** | Your AWS | Your infrastructure (any) |
98
- | **Dashboard** | TestDriver cloud | Your infrastructure |
99
- | **API** | TestDriver cloud | Your infrastructure |
100
- | **Data Location** | Your AWS + TestDriver | 100% your infrastructure |
101
- | **Network Requirements** | Internet access | Can be fully air-gapped |
102
- | **Cloud Providers** | AWS only | Any (AWS, Azure, GCP, on-prem) |
103
- | **Support** | Standard | Dedicated engineering |
104
- | **Contract** | Standard licensing | Custom terms |
105
-
106
- ## Get Started
107
-
108
- <Card
109
- title="Schedule a Consultation"
110
- icon="calendar"
111
- href="https://calendly.com/d/cq23-qyn-3v6/testdriver-ai-demo"
112
- >
113
- Discuss your requirements with our team and get a custom proposal for your Enterprise deployment.
114
- </Card>
7
+ This page has moved to [Self-Hosted](/v7/self-hosted).
@@ -0,0 +1,156 @@
1
+ ---
2
+ name: testdriver:hosted
3
+ description: The fastest way to get started with TestDriver. Just set your API key and start testing.
4
+ ---
5
+ <!-- Generated from hosted.mdx. DO NOT EDIT. -->
6
+
7
+ Hosted pricing is based on **device-seconds**: the amount of time your tests run on **our infrastructure**.
8
+
9
+ - **Zero Setup** — Start testing immediately. No DevOps required.
10
+ - **Free Tier** — Get started with a limited preview at no cost.
11
+ - **Pay As You Go** — Only pay for the device-seconds you use.
12
+
13
+ ## Hosted Plans
14
+
15
+ <CardGroup cols={3}>
16
+ <Card title="Free Trial" icon="gift" href="https://docs.testdriver.ai">
17
+ **$0/month**
18
+
19
+ - 1 Concurrent Sandbox
20
+ - 60 Minutes Included
21
+ - 1 Team User
22
+ - Community Support
23
+ </Card>
24
+
25
+ <Card title="Pro" icon="rocket" href="https://console.testdriver.ai/checkout/pro">
26
+ **$20/month**
27
+
28
+ - 2 Concurrent Sandboxes
29
+ - 600 Minutes Included
30
+ - Overage: $0.002/second
31
+ - 1 Team User
32
+ - Test Recordings
33
+ - Community Support
34
+ </Card>
35
+
36
+ <Card title="Team" icon="users" href="https://console.testdriver.ai/checkout/team">
37
+ **$600/month**
38
+
39
+ - 8 Concurrent Sandboxes
40
+ - 10,000 Minutes Included
41
+ - Overage: $0.001/second
42
+ - 5 Team Users
43
+ - Test Recordings
44
+ - Private Support
45
+ - Test Analytics
46
+ - CPU, RAM, & Network Profiles
47
+ </Card>
48
+ </CardGroup>
49
+
50
+ ## Get Started
51
+ Hosted is the default when you follow the Quickstart guide.
52
+ <Card
53
+ title="Try the Quickstart"
54
+ icon="play"
55
+ href="/v7/quickstart"
56
+ >
57
+ Set your API key and start testing in minutes.
58
+ </Card>
59
+
60
+ ## Parallel Testing Limits
61
+
62
+ Your account has a set number of **license slots** that determine how many devices can run simultaneously. You can view your available slots in the [TestDriver Dashboard](https://console.testdriver.ai).
63
+
64
+ <Info>
65
+ **When is a slot in use?** A license slot is occupied when a test client is connected. As soon as your device is destroyed the slot becomes available immediately.
66
+ </Info>
67
+
68
+ ## Avoiding Slot Conflicts
69
+
70
+ To prevent tests from failing due to exceeding your license slot limit, we recommend two key configurations:
71
+
72
+ <AccordionGroup>
73
+ <Accordion title="Set Maximum Concurrency in Vitest">
74
+ Limit concurrent tests to match your available license slots:
75
+
76
+ ```javascript vitest.config.mjs
77
+ import { defineConfig } from 'vitest/config';
78
+ import { TestDriver } from 'testdriverai/vitest';
79
+
80
+ export default defineConfig({
81
+ test: {
82
+ testTimeout: 900000,
83
+ hookTimeout: 900000,
84
+ maxConcurrency: 5, // Set to your license slot limit
85
+ reporters: ['default', TestDriver()],
86
+ setupFiles: ['testdriverai/vitest/setup'],
87
+ },
88
+ });
89
+ ```
90
+
91
+ <Tip>
92
+ Check your slot count at [console.testdriver.ai](https://console.testdriver.ai) and set `maxConcurrency` to that number or lower.
93
+ </Tip>
94
+ </Accordion>
95
+
96
+ <Accordion title="Use GitHub Concurrency Keys">
97
+ Prevent multiple workflow runs from competing for the same slots by using [GitHub's concurrency controls](https://docs.github.com/actions/writing-workflows/choosing-what-your-workflow-does/control-the-concurrency-of-workflows-and-jobs):
98
+
99
+ ```yaml .github/workflows/test.yml
100
+ name: Tests
101
+
102
+ on:
103
+ push:
104
+ branches: [main]
105
+ pull_request:
106
+
107
+ # Prevent concurrent runs from competing for license slots
108
+ concurrency:
109
+ group: ${{ github.workflow }}-${{ github.ref }}
110
+ cancel-in-progress: true
111
+
112
+ jobs:
113
+ test:
114
+ runs-on: ubuntu-latest
115
+ steps:
116
+ - uses: actions/checkout@v4
117
+
118
+ - name: Setup Node.js
119
+ uses: actions/setup-node@v4
120
+ with:
121
+ node-version: '20'
122
+
123
+ - name: Install dependencies
124
+ run: npm install
125
+
126
+ - name: Run tests
127
+ run: vitest run
128
+ env:
129
+ TD_API_KEY: ${{ secrets.TD_API_KEY }}
130
+ ```
131
+
132
+ The `concurrency` block ensures:
133
+ - Only one workflow run per branch runs at a time
134
+ - New pushes cancel in-progress runs on the same branch
135
+ - Different branches/PRs can run in parallel (up to your slot limit)
136
+ </Accordion>
137
+ </AccordionGroup>
138
+
139
+ ## When to Consider Self-Hosted
140
+
141
+ Hosted is perfect for getting started and for teams that want zero infrastructure management. However, you might consider [Self-Hosted](/v7/self-hosted) if you:
142
+
143
+ - Want to escape per-second billing with a flat license fee
144
+ - Require greater concurrency than offered in Cloud plans
145
+ - Need full control over your infrastructure and privacy
146
+ - Want to use your own AI API keys
147
+ - Require custom hardware configurations
148
+ - Have high test volumes that make self-hosting more economical
149
+
150
+ <Card
151
+ title="Explore Self-Hosted"
152
+ icon="server"
153
+ href="/v7/self-hosted"
154
+ >
155
+ Learn about self-hosting for unlimited test execution at a flat rate.
156
+ </Card>
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: testdriver:mcp
3
- description: mcp
3
+ description: Execute natural language tasks using AI
4
4
  ---
5
5
  <!-- Generated from mcp.mdx. DO NOT EDIT. -->
6
6
 
7
-
7
+ ## Overview
@@ -9,7 +9,7 @@ TestDriver makes it easy to write automated computer-use tests for web browsers,
9
9
  <Tip><a href="https://discord.com/invite/cWDFW8DzPm" target="_blank" rel="noreferrer">Join our Discord</a> if you have any questions or need help getting started!</Tip>
10
10
 
11
11
  <Tabs>
12
- <Tab title="Automatic Setup">
12
+ <Tab title="CLI" icon="terminal">
13
13
 
14
14
  Get started quickly with the TestDriver CLI.
15
15
 
@@ -39,7 +39,35 @@ TestDriver makes it easy to write automated computer-use tests for web browsers,
39
39
  </Step>
40
40
  </Steps>
41
41
  </Tab>
42
- <Tab title="Manual Setup">
42
+ <Tab title="GitHub Copilot" icon="github">
43
+
44
+ Use the TestDriver VS Code extension with GitHub Copilot for an AI-powered testing workflow.
45
+
46
+ <Card
47
+ title="Install TestDriver for VS Code"
48
+ icon="/images/content/extension/vscode.svg"
49
+ href="vscode:extension/testdriver.testdriver"
50
+ arrow
51
+ horizontal
52
+ >
53
+ </Card>
54
+
55
+ The extension provides one-click sign-in, project initialization, a live preview panel for watching tests execute, and MCP server configuration for GitHub Copilot.
56
+
57
+ Once installed, follow the full setup guide to configure MCP and start building tests with AI assistance:
58
+
59
+ <Card
60
+ title="VS Code + Copilot Setup Guide"
61
+ icon="arrow-right"
62
+ href="/v7/copilot/setup"
63
+ arrow
64
+ horizontal
65
+ >
66
+ Sign in, initialize your project, and configure MCP for GitHub Copilot.
67
+ </Card>
68
+
69
+ </Tab>
70
+ <Tab title="Manual" icon="wrench">
43
71
 
44
72
  Install TestDriver and manually create the files yourself.
45
73