testdriverai 7.2.43 → 7.2.45

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 (36) hide show
  1. package/.github/workflows/acceptance-windows-scheduled.yaml +24 -15
  2. package/.github/workflows/acceptance.yaml +18 -16
  3. package/.github/workflows/windows-self-hosted.yaml +68 -0
  4. package/docs/v7/aws-setup.mdx +142 -144
  5. package/interfaces/cli/commands/init.js +1 -1
  6. package/lib/core/Dashcam.js +17 -16
  7. package/lib/vitest/hooks.mjs +13 -0
  8. package/lib/vitest/setup-aws.mjs +225 -0
  9. package/lib/vitest/setup-self-hosted.mjs +136 -0
  10. package/package.json +2 -1
  11. package/sdk.js +40 -9
  12. package/test/testdriver/ai.test.mjs +1 -1
  13. package/test/testdriver/assert.test.mjs +3 -1
  14. package/test/testdriver/chrome-extension.test.mjs +2 -2
  15. package/test/testdriver/drag-and-drop.test.mjs +1 -1
  16. package/test/testdriver/element-not-found.test.mjs +1 -1
  17. package/test/testdriver/exec-output.test.mjs +1 -1
  18. package/test/testdriver/exec-pwsh.test.mjs +1 -1
  19. package/test/testdriver/focus-window.test.mjs +1 -1
  20. package/test/testdriver/formatted-logging.test.mjs +1 -1
  21. package/test/testdriver/hover-image.test.mjs +2 -2
  22. package/test/testdriver/hover-text-with-description.test.mjs +2 -2
  23. package/test/testdriver/hover-text.test.mjs +1 -1
  24. package/test/testdriver/installer.test.mjs +2 -2
  25. package/test/testdriver/launch-vscode-linux.test.mjs +2 -2
  26. package/test/testdriver/match-image.test.mjs +2 -2
  27. package/test/testdriver/press-keys.test.mjs +1 -1
  28. package/test/testdriver/prompt.test.mjs +1 -1
  29. package/test/testdriver/scroll-keyboard.test.mjs +1 -1
  30. package/test/testdriver/scroll-until-image.test.mjs +1 -1
  31. package/test/testdriver/scroll-until-text.test.mjs +2 -2
  32. package/test/testdriver/scroll.test.mjs +1 -1
  33. package/test/testdriver/type.test.mjs +2 -2
  34. package/test/testdriver/windows-installer.test.mjs +1 -1
  35. package/vitest.config.mjs +4 -1
  36. package/setup/aws/self-hosted.yml +0 -111
@@ -15,7 +15,7 @@ describe("Chrome Extension Test", () => {
15
15
 
16
16
  console.log('connecting to', process.env.TD_IP)
17
17
 
18
- const testdriver = TestDriver(context, { ip: process.env.TD_IP });
18
+ const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP });
19
19
 
20
20
  // Wait for connection to be ready before running exec
21
21
  await testdriver.ready();
@@ -68,7 +68,7 @@ describe("Chrome Extension Test", () => {
68
68
  });
69
69
 
70
70
  it("should load Loom from Chrome Web Store by extensionId", async (context) => {
71
- const testdriver = TestDriver(context);
71
+ const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP });
72
72
 
73
73
  // Launch Chrome with Loom loaded by its Chrome Web Store ID
74
74
  // Loom ID: liecbddmkiiihnedobmlmillhodjkdmb
@@ -12,7 +12,7 @@ describe("Drag and Drop Test", () => {
12
12
  it.skipIf(isLinux)(
13
13
  'should drag "New Text Document" to "Recycle Bin"',
14
14
  async (context) => {
15
- const testdriver = TestDriver(context, { headless: true });
15
+ const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP, headless: true });
16
16
  await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
17
17
 
18
18
  //
@@ -8,7 +8,7 @@ import { TestDriver } from "../../lib/vitest/hooks.mjs";
8
8
 
9
9
  describe("Element Not Found Test", () => {
10
10
  it("should handle non-existent element gracefully without timing out", async (context) => {
11
- const testdriver = TestDriver(context, { headless: true });
11
+ const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP, headless: true });
12
12
  await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
13
13
 
14
14
  //
@@ -10,7 +10,7 @@ describe.skip("Exec Output Test", () => {
10
10
  it(
11
11
  "should set date using PowerShell and navigate to calendar",
12
12
  async (context) => {
13
- const testdriver = TestDriver(context, { headless: true });
13
+ const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP, headless: true });
14
14
  await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
15
15
 
16
16
  //
@@ -10,7 +10,7 @@ describe.skip("Exec PowerShell Test", () => {
10
10
  it(
11
11
  "should generate random email using PowerShell and enter it",
12
12
  async (context) => {
13
- const testdriver = TestDriver(context, { headless: true });
13
+ const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP, headless: true });
14
14
  await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
15
15
 
16
16
  //
@@ -10,7 +10,7 @@ describe("Focus Window Test", () => {
10
10
  it.skip(
11
11
  "should click Microsoft Edge icon and focus Google Chrome",
12
12
  async (context) => {
13
- const testdriver = TestDriver(context, { headless: true });
13
+ const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP, headless: true });
14
14
  await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
15
15
 
16
16
  //
@@ -8,7 +8,7 @@ import { TestDriver } from "../../lib/vitest/hooks.mjs";
8
8
 
9
9
  describe("Formatted Logging Test", () => {
10
10
  it("should demonstrate formatted logs in dashcam replay", async (context) => {
11
- const testdriver = TestDriver(context, { headless: true });
11
+ const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP, headless: true });
12
12
  await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
13
13
 
14
14
  // Find and click - logs will be nicely formatted
@@ -15,7 +15,7 @@ async function performLogin(client, username = "standard_user") {
15
15
  await client.focusApplication("Google Chrome");
16
16
  const password = await client.extract("the password");
17
17
  const usernameField = await client.find(
18
- "Username, label above the username input field on the login form",
18
+ "username input",
19
19
  );
20
20
  await usernameField.click();
21
21
  await client.type(username);
@@ -27,7 +27,7 @@ async function performLogin(client, username = "standard_user") {
27
27
 
28
28
  describe("Hover Image Test", () => {
29
29
  it("should click on shopping cart icon and verify empty cart", async (context) => {
30
- const testdriver = TestDriver(context, { ip: process.env.TD_IP });
30
+ const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP });
31
31
 
32
32
  // provision.chrome() automatically calls ready() and starts dashcam
33
33
  await testdriver.provision.chrome({
@@ -15,7 +15,7 @@ async function performLogin(client, username = "standard_user") {
15
15
  await client.focusApplication("Google Chrome");
16
16
  const password = await client.extract("the password");
17
17
  const usernameField = await client.find(
18
- "Username, label above the username input field on the login form",
18
+ "username input",
19
19
  );
20
20
  await usernameField.click();
21
21
  await client.type(username);
@@ -27,7 +27,7 @@ async function performLogin(client, username = "standard_user") {
27
27
 
28
28
  describe("Hover Text With Description Test", () => {
29
29
  it("should add TestDriver Hat to cart and verify", async (context) => {
30
- const testdriver = TestDriver(context, { headless: true });
30
+ const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP, headless: true });
31
31
  await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
32
32
 
33
33
  //
@@ -8,7 +8,7 @@ import { TestDriver } from "../../lib/vitest/hooks.mjs";
8
8
 
9
9
  describe("Hover Text Test", () => {
10
10
  it("should click Sign In and verify error message", async (context) => {
11
- const testdriver = TestDriver(context, { ip: '3.138.116.105'});
11
+ const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP });
12
12
  await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
13
13
 
14
14
  // Click on Sign In button using new find() API
@@ -12,7 +12,7 @@ describe("Provision Installer", () => {
12
12
  it.skipIf(!isLinux)(
13
13
  "should download and install a .deb package on Linux",
14
14
  async (context) => {
15
- const testdriver = TestDriver(context);
15
+ const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP });
16
16
 
17
17
  // Install bat (a cat clone with syntax highlighting) using provision.installer
18
18
  const filePath = await testdriver.provision.installer({
@@ -30,7 +30,7 @@ describe("Provision Installer", () => {
30
30
  it.skipIf(!isLinux)(
31
31
  "should download a shell script and verify it exists",
32
32
  async (context) => {
33
- const testdriver = TestDriver(context);
33
+ const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP });
34
34
 
35
35
  // Download a shell script (nvm installer)
36
36
  const filePath = await testdriver.provision.installer({
@@ -7,7 +7,7 @@ describe("Launch VS Code on Linux", () => {
7
7
  it.skipIf(!isLinux)(
8
8
  "should launch VS Code on Debian/Ubuntu",
9
9
  async (context) => {
10
- const testdriver = TestDriver(context);
10
+ const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP });
11
11
 
12
12
  // provision.vscode() automatically calls ready() and starts dashcam
13
13
  await testdriver.provision.vscode();
@@ -24,7 +24,7 @@ describe("Launch VS Code on Linux", () => {
24
24
  it.skipIf(!isLinux)(
25
25
  "should install and use a VS Code extension",
26
26
  async (context) => {
27
- const testdriver = TestDriver(context);
27
+ const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP });
28
28
 
29
29
  // Launch VS Code with the Prettier extension installed
30
30
  await testdriver.provision.vscode({
@@ -17,7 +17,7 @@ async function performLogin(client, username = "standard_user") {
17
17
  await client.focusApplication("Google Chrome");
18
18
  const password = await client.extract("the password");
19
19
  const usernameField = await client.find(
20
- "Username, label above the username input field on the login form",
20
+ "username input",
21
21
  );
22
22
  await usernameField.click();
23
23
  await client.type(username);
@@ -33,7 +33,7 @@ const __dirname = dirname(__filename);
33
33
 
34
34
  describe("Match Image Test", () => {
35
35
  it.skip("should match shopping cart image and verify empty cart", async (context) => {
36
- const testdriver = TestDriver(context, { headless: true });
36
+ const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP, headless: true });
37
37
  await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
38
38
 
39
39
  //
@@ -8,7 +8,7 @@ import { TestDriver } from "../../lib/vitest/hooks.mjs";
8
8
 
9
9
  describe("Press Keys Test", () => {
10
10
  it("should create tabs and navigate using keyboard shortcuts", async (context) => {
11
- const testdriver = TestDriver(context, { headless: true });
11
+ const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP, headless: true });
12
12
  await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
13
13
 
14
14
  const signInButton = await testdriver.find(
@@ -8,7 +8,7 @@ import { TestDriver } from "../../lib/vitest/hooks.mjs";
8
8
 
9
9
  describe.skip("Prompt Test", () => {
10
10
  it("should execute AI-driven prompts", async (context) => {
11
- const testdriver = TestDriver(context, { headless: true });
11
+ const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP, headless: true });
12
12
  await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
13
13
 
14
14
  //
@@ -8,7 +8,7 @@ import { TestDriver } from "../../lib/vitest/hooks.mjs";
8
8
 
9
9
  describe("Scroll Keyboard Test", () => {
10
10
  it("should navigate to webhamster.com and scroll with keyboard", async (context) => {
11
- const testdriver = TestDriver(context, { headless: true });
11
+ const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP, headless: true });
12
12
  await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
13
13
 
14
14
  //
@@ -8,7 +8,7 @@ import { TestDriver } from "../../lib/vitest/hooks.mjs";
8
8
 
9
9
  describe("Scroll Until Image Test", () => {
10
10
  it.skip("should scroll until brown colored house image appears", async (context) => {
11
- const testdriver = TestDriver(context, { headless: true });
11
+ const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP, headless: true });
12
12
  await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
13
13
 
14
14
  //
@@ -15,7 +15,7 @@ async function performLogin(client, username = "standard_user") {
15
15
  await client.focusApplication("Google Chrome");
16
16
  const password = await client.extract("the password");
17
17
  const usernameField = await client.find(
18
- "Username, label above the username input field on the login form",
18
+ "username input",
19
19
  );
20
20
  await usernameField.click();
21
21
  await client.type(username);
@@ -27,7 +27,7 @@ async function performLogin(client, username = "standard_user") {
27
27
 
28
28
  describe("Scroll Until Text Test", () => {
29
29
  it('should scroll until "testdriver socks" appears', async (context) => {
30
- const testdriver = TestDriver(context, { headless: true, reconnect: false });
30
+ const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP, headless: true });
31
31
  await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
32
32
 
33
33
  //
@@ -10,7 +10,7 @@ import { TestDriver } from "../../lib/vitest/hooks.mjs";
10
10
 
11
11
  describe("Scroll Test", () => {
12
12
  it("should navigate and scroll down the page", async (context) => {
13
- const testdriver = TestDriver(context, { headless: true });
13
+ const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP, headless: true });
14
14
  await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
15
15
 
16
16
  // Give Chrome a moment to fully render the UI
@@ -3,7 +3,7 @@ import { TestDriver } from "../../lib/vitest/hooks.mjs";
3
3
 
4
4
  describe("Type Test", () => {
5
5
  it("should enter standard_user in username field", async (context) => {
6
- const testdriver = TestDriver(context, { headless: true });
6
+ const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP, headless: true });
7
7
  await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
8
8
 
9
9
  //
@@ -20,7 +20,7 @@ describe("Type Test", () => {
20
20
  });
21
21
 
22
22
  it("should show validation message when clicking Sign In without password", async (context) => {
23
- const testdriver = TestDriver(context, { headless: true });
23
+ const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP, headless: true });
24
24
  await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
25
25
 
26
26
  // First fill in username
@@ -19,7 +19,7 @@ describe("Windows App Installation", () => {
19
19
  it.skipIf(isLinux)("should download, install, and launch GitButler on Windows", async (context) => {
20
20
  // Alternative approach using provision.installer helper
21
21
  const testdriver = TestDriver(context, {
22
- reconnect: true,
22
+ ip: context.ip || process.env.TD_IP,
23
23
  os: 'windows'
24
24
  });
25
25
 
package/vitest.config.mjs CHANGED
@@ -6,6 +6,9 @@ import { defineConfig } from 'vitest/config';
6
6
  // and to worker processes
7
7
  config();
8
8
 
9
+ // Always include AWS setup - it will be a no-op unless TD_OS=windows
10
+ const setupFiles = ['testdriverai/vitest/setup', 'testdriverai/vitest/setup-aws'];
11
+
9
12
  export default defineConfig({
10
13
  test: {
11
14
  testTimeout: 900000,
@@ -17,6 +20,6 @@ export default defineConfig({
17
20
  TestDriver(),
18
21
  ['junit', { outputFile: 'test-report.junit.xml' }]
19
22
  ],
20
- setupFiles: ['testdriverai/vitest/setup'],
23
+ setupFiles,
21
24
  },
22
25
  });
@@ -1,111 +0,0 @@
1
- name: AWS
2
- on:
3
- workflow_dispatch:
4
- push:
5
- branches:
6
- - main
7
- paths-ignore:
8
- - "docs/**"
9
- pull_request:
10
- branches:
11
- - main
12
- types:
13
- - ready_for_review
14
- pull_request_review:
15
- types:
16
- - submitted
17
-
18
- jobs:
19
- gather:
20
- name: Gather Test Files
21
- runs-on: ubuntu-latest
22
- outputs:
23
- test_files: ${{ steps.test_list.outputs.files }}
24
- steps:
25
- - name: Check out repository
26
- uses: actions/checkout@v4
27
-
28
- - name: Find all test files
29
- id: test_list
30
- run: |
31
- FILES=$(ls ./testdriver/acceptance/*.yaml)
32
- FILENAMES=$(basename -a $FILES)
33
- FILES_JSON=$(echo "$FILENAMES" | jq -R -s -c 'split("\n")[:-1]')
34
- echo "files=$FILES_JSON" >> $GITHUB_OUTPUT
35
-
36
- test:
37
- needs: gather
38
- runs-on: ubuntu-latest
39
- strategy:
40
- matrix:
41
- test: ${{ fromJson(needs.gather.outputs.test_files) }}
42
- fail-fast: false
43
- steps:
44
- - name: Checkout repository
45
- uses: actions/checkout@v4
46
- with:
47
- fetch-depth: 0
48
- # only needed for `act`
49
- # - name: Install AWS CLI
50
- # run: |
51
- # apt-get update
52
- # apt-get install curl unzip -y
53
- # curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
54
- # unzip awscliv2.zip
55
- # ./aws/install
56
- - name: Set up Node.js
57
- uses: actions/setup-node@v4
58
- with:
59
- node-version: "20"
60
- cache: "npm"
61
- - name: Install dependencies
62
- run: NODE_ENV=production npm ci
63
- - name: Setup AWS Instance
64
- id: aws-setup
65
- run: |
66
- chmod +x ./setup/aws/spawn-runner.sh
67
- OUTPUT=$(./setup/aws/spawn-runner.sh | tee /dev/stderr) # Capture and display output
68
- echo "$OUTPUT"
69
- PUBLIC_IP=$(echo "$OUTPUT" | grep "PUBLIC_IP=" | cut -d'=' -f2)
70
- INSTANCE_ID=$(echo "$OUTPUT" | grep "INSTANCE_ID=" | cut -d'=' -f2)
71
- AWS_REGION=$(echo "$OUTPUT" | grep "AWS_REGION=" | cut -d'=' -f2)
72
- echo "public-ip=$PUBLIC_IP" >> $GITHUB_OUTPUT
73
- echo "instance-id=$INSTANCE_ID" >> $GITHUB_OUTPUT
74
- echo "aws-region=$AWS_REGION" >> $GITHUB_OUTPUT
75
- env:
76
- FORCE_COLOR: 3
77
- AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
78
- AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
79
- AWS_REGION: us-east-2
80
- AWS_LAUNCH_TEMPLATE_ID: lt-00d02f31cfc602f27
81
- AMI_ID: ami-0c2858680200ac890
82
- RESOLUTION: 1920x1080
83
- - name: Run TestDriver
84
- run: node bin/testdriverai.js run testdriver/acceptance/${{ matrix.test }} --ip="${{ steps.aws-setup.outputs.public-ip }}" --junit=out.xml
85
- env:
86
- TD_API_KEY: ${{ secrets.TD_API_KEY }}
87
- TD_WEBSITE: https://testdriver-sandbox.vercel.app
88
- TD_THIS_FILE: ${{ matrix.test }}
89
- - name: Upload TestDriver AI CLI logs
90
- if: always()
91
- uses: actions/upload-artifact@v4
92
- with:
93
- name: testdriverai-cli-logs-${{ matrix.test }}
94
- path: /tmp/testdriverai-cli-*.log
95
- if-no-files-found: warn
96
- retention-days: 30
97
- - name: Upload test results as artifact
98
- if: always()
99
- uses: actions/upload-artifact@v4
100
- with:
101
- name: test-results-${{ matrix.test }}
102
- path: out.xml
103
- retention-days: 30
104
- - name: Shutdown AWS Instance
105
- if: always()
106
- run: aws ec2 terminate-instances --region "$AWS_REGION" --instance-ids "$INSTANCE_ID"
107
- env:
108
- AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
109
- AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
110
- AWS_REGION: ${{ steps.aws-setup.outputs.aws-region }}
111
- INSTANCE_ID: ${{ steps.aws-setup.outputs.instance-id }}