testdriverai 7.0.0 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +550 -0
- package/CODEOWNERS +0 -1
- package/README.md +126 -0
- package/agent/index.js +43 -18
- package/agent/lib/commands.js +794 -135
- package/agent/lib/redraw.js +124 -39
- package/agent/lib/sandbox.js +10 -1
- package/agent/lib/sdk.js +21 -0
- package/docs/MIGRATION.md +425 -0
- package/docs/PRESETS.md +210 -0
- package/docs/docs.json +91 -37
- package/docs/guide/best-practices-polling.mdx +154 -0
- package/docs/v7/api/dashcam.mdx +497 -0
- package/docs/v7/api/doubleClick.mdx +102 -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/getting-started/configuration.mdx +380 -0
- package/docs/v7/getting-started/quickstart.mdx +273 -140
- package/docs/v7/guides/best-practices.mdx +486 -0
- package/docs/v7/guides/caching-ai.mdx +215 -0
- package/docs/v7/guides/caching-selectors.mdx +292 -0
- package/docs/v7/guides/caching.mdx +366 -0
- package/docs/v7/guides/ci-cd/azure.mdx +587 -0
- package/docs/v7/guides/ci-cd/circleci.mdx +523 -0
- package/docs/v7/guides/ci-cd/github-actions.mdx +457 -0
- package/docs/v7/guides/ci-cd/gitlab.mdx +498 -0
- package/docs/v7/guides/ci-cd/jenkins.mdx +664 -0
- package/docs/v7/guides/ci-cd/travis.mdx +438 -0
- package/docs/v7/guides/debugging.mdx +349 -0
- package/docs/v7/guides/faq.mdx +393 -0
- package/docs/v7/guides/performance.mdx +517 -0
- package/docs/v7/guides/troubleshooting.mdx +526 -0
- package/docs/v7/guides/vitest-plugin.mdx +477 -0
- package/docs/v7/guides/vitest.mdx +535 -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/presets/chrome-extension.mdx +223 -0
- package/docs/v7/presets/chrome.mdx +287 -0
- package/docs/v7/presets/electron.mdx +435 -0
- package/docs/v7/presets/vscode.mdx +398 -0
- package/docs/v7/presets/webapp.mdx +396 -0
- package/docs/v7/progressive-apis/CORE.md +459 -0
- package/docs/v7/progressive-apis/HOOKS.md +360 -0
- package/docs/v7/progressive-apis/PROGRESSIVE_DISCLOSURE.md +230 -0
- package/docs/v7/progressive-apis/PROVISION.md +266 -0
- package/interfaces/vitest-plugin.mjs +186 -100
- package/package.json +12 -1
- package/sdk.d.ts +335 -42
- package/sdk.js +756 -95
- package/src/core/Dashcam.js +469 -0
- package/src/core/index.d.ts +150 -0
- package/src/core/index.js +12 -0
- package/src/presets/index.mjs +331 -0
- package/src/vitest/extended.mjs +108 -0
- package/src/vitest/hooks.d.ts +119 -0
- package/src/vitest/hooks.mjs +298 -0
- package/src/vitest/index.mjs +64 -0
- package/src/vitest/lifecycle.mjs +277 -0
- package/src/vitest/utils.mjs +150 -0
- package/test/dashcam.test.js +137 -0
- package/testdriver/acceptance-sdk/assert.test.mjs +13 -31
- package/testdriver/acceptance-sdk/auto-cache-key-demo.test.mjs +56 -0
- package/testdriver/acceptance-sdk/chrome-extension.test.mjs +89 -0
- package/testdriver/acceptance-sdk/drag-and-drop.test.mjs +7 -19
- package/testdriver/acceptance-sdk/element-not-found.test.mjs +6 -19
- package/testdriver/acceptance-sdk/exec-js.test.mjs +6 -18
- package/testdriver/acceptance-sdk/exec-output.test.mjs +8 -20
- package/testdriver/acceptance-sdk/exec-pwsh.test.mjs +13 -25
- package/testdriver/acceptance-sdk/focus-window.test.mjs +8 -20
- package/testdriver/acceptance-sdk/formatted-logging.test.mjs +5 -20
- package/testdriver/acceptance-sdk/hooks-example.test.mjs +38 -0
- package/testdriver/acceptance-sdk/hover-image.test.mjs +10 -19
- package/testdriver/acceptance-sdk/hover-text-with-description.test.mjs +7 -19
- package/testdriver/acceptance-sdk/hover-text.test.mjs +5 -19
- package/testdriver/acceptance-sdk/match-image.test.mjs +7 -19
- package/testdriver/acceptance-sdk/presets-example.test.mjs +87 -0
- package/testdriver/acceptance-sdk/press-keys.test.mjs +5 -19
- package/testdriver/acceptance-sdk/prompt.test.mjs +6 -18
- package/testdriver/acceptance-sdk/scroll-keyboard.test.mjs +6 -20
- package/testdriver/acceptance-sdk/scroll-until-image.test.mjs +6 -18
- package/testdriver/acceptance-sdk/scroll-until-text.test.mjs +9 -23
- package/testdriver/acceptance-sdk/scroll.test.mjs +12 -21
- package/testdriver/acceptance-sdk/setup/testHelpers.mjs +124 -352
- package/testdriver/acceptance-sdk/sully-ai.test.mjs +234 -0
- package/testdriver/acceptance-sdk/test-console-logs.test.mjs +42 -0
- package/testdriver/acceptance-sdk/type.test.mjs +19 -58
- package/vitest.config.mjs +1 -0
- package/.vscode/mcp.json +0 -9
- package/MIGRATION.md +0 -389
- package/PLUGIN_MIGRATION.md +0 -222
- package/PROMPT_CACHE.md +0 -200
- package/SDK_LOGGING.md +0 -222
- package/SDK_MIGRATION.md +0 -474
- package/SDK_README.md +0 -1122
- package/debug-screenshot-1763401388589.png +0 -0
- package/examples/run-tests-with-recording.sh +0 -70
- package/examples/screenshot-example.js +0 -63
- package/examples/sdk-awesome-logs-demo.js +0 -177
- package/examples/sdk-cache-thresholds.js +0 -96
- package/examples/sdk-element-properties.js +0 -155
- package/examples/sdk-simple-example.js +0 -65
- package/examples/test-recording-example.test.js +0 -166
- package/mcp-server/AI_GUIDELINES.md +0 -57
- package/test-find-api.js +0 -73
- package/test-prompt-cache.js +0 -96
- package/test-sandbox-render.js +0 -28
- package/test-sdk-methods.js +0 -15
- package/test-sdk-refactor.js +0 -53
- package/test-stack-trace.mjs +0 -57
- package/testdriver/acceptance-sdk/setup/lifecycleHelpers.mjs +0 -239
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility Functions for TestDriver Vitest Plugin
|
|
3
|
+
*
|
|
4
|
+
* General-purpose utilities for testing with TestDriver.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* import { retryAsync, setupEventLogging } from 'testdriverai/vitest';
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Set up detailed event logging for debugging
|
|
12
|
+
* @param {TestDriver} client - TestDriver client
|
|
13
|
+
*/
|
|
14
|
+
export function setupEventLogging(client) {
|
|
15
|
+
const emitter = client.getEmitter();
|
|
16
|
+
|
|
17
|
+
// Log all events
|
|
18
|
+
emitter.on("**", function (data) {
|
|
19
|
+
const event = this.event;
|
|
20
|
+
if (event.startsWith("log:debug")) return; // Skip debug logs
|
|
21
|
+
console.log(`[EVENT] ${event}`, data || "");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Log command lifecycle
|
|
25
|
+
emitter.on("command:start", (data) => {
|
|
26
|
+
console.log("🚀 Command started:", data);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
emitter.on("command:success", (data) => {
|
|
30
|
+
console.log("✅ Command succeeded:", data);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
emitter.on("command:error", (data) => {
|
|
34
|
+
console.error("❌ Command error:", data);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Log sandbox events
|
|
38
|
+
emitter.on("sandbox:connected", () => {
|
|
39
|
+
console.log("🔌 Sandbox connected");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
emitter.on("sandbox:authenticated", () => {
|
|
43
|
+
console.log("🔐 Sandbox authenticated");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
emitter.on("sandbox:error", (error) => {
|
|
47
|
+
console.error("⚠️ Sandbox error:", error);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Log SDK API calls
|
|
51
|
+
emitter.on("sdk:request", (data) => {
|
|
52
|
+
console.log("📤 SDK Request:", data);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
emitter.on("sdk:response", (data) => {
|
|
56
|
+
console.log("📥 SDK Response:", data);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Retry an async function with configurable attempts and delay
|
|
62
|
+
* @param {Function} fn - Async function to retry
|
|
63
|
+
* @param {number} retries - Number of retries (default: 3)
|
|
64
|
+
* @param {number} delay - Delay between retries in ms (default: 1000)
|
|
65
|
+
* @returns {Promise} Result of successful execution
|
|
66
|
+
* @throws {Error} Last error if all retries fail
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* const result = await retryAsync(
|
|
70
|
+
* () => testdriver.find("Flaky Element"),
|
|
71
|
+
* 5,
|
|
72
|
+
* 2000
|
|
73
|
+
* );
|
|
74
|
+
*/
|
|
75
|
+
export async function retryAsync(fn, retries = 3, delay = 1000) {
|
|
76
|
+
let lastError;
|
|
77
|
+
|
|
78
|
+
for (let i = 0; i < retries; i++) {
|
|
79
|
+
try {
|
|
80
|
+
return await fn();
|
|
81
|
+
} catch (error) {
|
|
82
|
+
lastError = error;
|
|
83
|
+
if (i < retries - 1) {
|
|
84
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
throw lastError;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Wait for a condition to be true
|
|
94
|
+
* @param {Function} condition - Function that returns boolean or Promise<boolean>
|
|
95
|
+
* @param {object} options - Wait options
|
|
96
|
+
* @param {number} options.timeout - Maximum time to wait in ms (default: 30000)
|
|
97
|
+
* @param {number} options.interval - Poll interval in ms (default: 500)
|
|
98
|
+
* @param {string} options.message - Error message if timeout (default: "Condition not met")
|
|
99
|
+
* @returns {Promise<boolean>} True if condition met
|
|
100
|
+
* @throws {Error} If timeout reached
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* await waitFor(() => element.exists(), { timeout: 10000 });
|
|
104
|
+
*/
|
|
105
|
+
export async function waitFor(condition, options = {}) {
|
|
106
|
+
const {
|
|
107
|
+
timeout = 30000,
|
|
108
|
+
interval = 500,
|
|
109
|
+
message = "Condition not met within timeout"
|
|
110
|
+
} = options;
|
|
111
|
+
|
|
112
|
+
const startTime = Date.now();
|
|
113
|
+
|
|
114
|
+
while (Date.now() - startTime < timeout) {
|
|
115
|
+
try {
|
|
116
|
+
const result = await condition();
|
|
117
|
+
if (result) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
} catch (error) {
|
|
121
|
+
// Condition threw, keep polling
|
|
122
|
+
}
|
|
123
|
+
await new Promise(resolve => setTimeout(resolve, interval));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
throw new Error(`${message} (timeout: ${timeout}ms)`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Sleep for a specified duration
|
|
131
|
+
* @param {number} ms - Milliseconds to sleep
|
|
132
|
+
* @returns {Promise<void>}
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* await sleep(1000); // Wait 1 second
|
|
136
|
+
*/
|
|
137
|
+
export function sleep(ms) {
|
|
138
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Generate a unique test ID
|
|
143
|
+
* @param {string} prefix - Optional prefix for the ID
|
|
144
|
+
* @returns {string} Unique ID
|
|
145
|
+
*/
|
|
146
|
+
export function generateTestId(prefix = "test") {
|
|
147
|
+
const timestamp = Date.now().toString(36);
|
|
148
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
149
|
+
return `${prefix}-${timestamp}-${random}`;
|
|
150
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashcam Class - Unit Tests
|
|
3
|
+
* Tests for the new Dashcam class
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { Dashcam } = require('../src/core');
|
|
7
|
+
|
|
8
|
+
// Mock TestDriver client
|
|
9
|
+
class MockTestDriver {
|
|
10
|
+
constructor(os = 'linux') {
|
|
11
|
+
this.os = os;
|
|
12
|
+
this.commands = [];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async exec(shell, command, timeout, silent) {
|
|
16
|
+
this.commands.push({ shell, command, timeout, silent });
|
|
17
|
+
|
|
18
|
+
// Mock responses
|
|
19
|
+
if (command.includes('npm prefix -g')) {
|
|
20
|
+
return this.os === 'windows'
|
|
21
|
+
? 'C:\\Users\\testdriver\\AppData\\Roaming\\npm'
|
|
22
|
+
: '/usr/local';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (command.includes('stop')) {
|
|
26
|
+
return 'Recording stopped\nReplay URL: https://app.testdriver.ai/replay/abc123?share=xyz';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return 'OK';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
wait(ms) {
|
|
33
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function testDashcamCreation() {
|
|
38
|
+
console.log('🧪 Test: Dashcam creation');
|
|
39
|
+
|
|
40
|
+
const client = new MockTestDriver();
|
|
41
|
+
const dashcam = new Dashcam(client);
|
|
42
|
+
|
|
43
|
+
console.assert(dashcam.client === client, 'Client should be stored');
|
|
44
|
+
console.assert(dashcam.recording === false, 'Should not be recording initially');
|
|
45
|
+
console.assert(dashcam._authenticated === false, 'Should not be authenticated initially');
|
|
46
|
+
|
|
47
|
+
console.log('✅ Dashcam creation test passed');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function testDashcamWithOptions() {
|
|
51
|
+
console.log('\n🧪 Test: Dashcam with options');
|
|
52
|
+
|
|
53
|
+
const client = new MockTestDriver();
|
|
54
|
+
const dashcam = new Dashcam(client, {
|
|
55
|
+
apiKey: 'custom-key',
|
|
56
|
+
autoStart: true,
|
|
57
|
+
logs: [
|
|
58
|
+
{ type: 'file', path: '/tmp/test.log', name: 'Test Log' }
|
|
59
|
+
]
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
console.assert(dashcam.apiKey === 'custom-key', 'Custom API key should be used');
|
|
63
|
+
console.assert(dashcam.autoStart === true, 'Auto start should be enabled');
|
|
64
|
+
console.assert(dashcam.logs.length === 1, 'Logs should be stored');
|
|
65
|
+
|
|
66
|
+
console.log('✅ Dashcam options test passed');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function testErrorOnMissingClient() {
|
|
70
|
+
console.log('\n🧪 Test: Error when client missing');
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
new Dashcam();
|
|
74
|
+
console.error('❌ Should have thrown error');
|
|
75
|
+
process.exit(1);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.assert(
|
|
78
|
+
error.message.includes('TestDriver client'),
|
|
79
|
+
'Error message should mention TestDriver client'
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
console.log('✅ Missing client error test passed');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function testStopReturnsUrl() {
|
|
87
|
+
console.log('\n🧪 Test: Stop returns URL');
|
|
88
|
+
|
|
89
|
+
const client = new MockTestDriver();
|
|
90
|
+
const dashcam = new Dashcam(client);
|
|
91
|
+
|
|
92
|
+
dashcam.recording = true; // Simulate recording state
|
|
93
|
+
const url = await dashcam.stop();
|
|
94
|
+
|
|
95
|
+
console.assert(url !== null, 'URL should be returned');
|
|
96
|
+
console.assert(url.includes('replay'), 'URL should contain replay');
|
|
97
|
+
console.assert(dashcam.recording === false, 'Should stop recording');
|
|
98
|
+
|
|
99
|
+
console.log('✅ Stop returns URL test passed');
|
|
100
|
+
console.log(' URL:', url);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function testIsRecording() {
|
|
104
|
+
console.log('\n🧪 Test: isRecording method');
|
|
105
|
+
|
|
106
|
+
const client = new MockTestDriver();
|
|
107
|
+
const dashcam = new Dashcam(client);
|
|
108
|
+
|
|
109
|
+
let recording = await dashcam.isRecording();
|
|
110
|
+
console.assert(recording === false, 'Should not be recording initially');
|
|
111
|
+
|
|
112
|
+
dashcam.recording = true;
|
|
113
|
+
recording = await dashcam.isRecording();
|
|
114
|
+
console.assert(recording === true, 'Should return recording state');
|
|
115
|
+
|
|
116
|
+
console.log('✅ isRecording test passed');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function runAllTests() {
|
|
120
|
+
console.log('🚀 Running Dashcam class tests...\n');
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
await testDashcamCreation();
|
|
124
|
+
await testDashcamWithOptions();
|
|
125
|
+
await testErrorOnMissingClient();
|
|
126
|
+
await testStopReturnsUrl();
|
|
127
|
+
await testIsRecording();
|
|
128
|
+
|
|
129
|
+
console.log('\n✅ All tests passed!');
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error('\n❌ Test failed:', error.message);
|
|
132
|
+
console.error(error.stack);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
runAllTests();
|
|
@@ -3,42 +3,24 @@
|
|
|
3
3
|
* Converted from: testdriver/acceptance/assert.yaml
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
createTestClient,
|
|
9
|
-
setupTest,
|
|
10
|
-
teardownTest,
|
|
11
|
-
} from "./setup/testHelpers.mjs";
|
|
6
|
+
import { describe, expect, it } from "vitest";
|
|
7
|
+
import { TestDriver } from "../../src/vitest/hooks.mjs";
|
|
12
8
|
|
|
13
9
|
describe("Assert Test", () => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
//
|
|
18
|
-
testdriver
|
|
19
|
-
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
await setupTest(testdriver);
|
|
23
|
-
}, 600000);
|
|
24
|
-
|
|
25
|
-
afterEach(async (context) => {
|
|
26
|
-
// Teardown after each test, passing the individual test context
|
|
27
|
-
const sessionInfo = await teardownTest(testdriver, {
|
|
28
|
-
task: context.task,
|
|
10
|
+
it("should assert the testdriver login page shows", async (context) => {
|
|
11
|
+
const testdriver = TestDriver(context, { headless: true, newSandbox: true });
|
|
12
|
+
|
|
13
|
+
// provision.chrome() automatically calls ready() and starts dashcam
|
|
14
|
+
await testdriver.provision.chrome({
|
|
15
|
+
url: 'http://testdriver-sandbox.vercel.app/login',
|
|
29
16
|
});
|
|
30
|
-
console.log(
|
|
31
|
-
`[Test] Teardown complete, dashcam URL: ${sessionInfo.dashcamUrl}`,
|
|
32
|
-
);
|
|
33
|
-
});
|
|
34
17
|
|
|
35
|
-
it("should assert the testdriver login page shows", async () => {
|
|
36
18
|
// Assert the TestDriver.ai Sandbox login page is displayed
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
19
|
+
const result = await testdriver.assert(
|
|
20
|
+
"the TestDriver.ai Sandbox login page is displayed",
|
|
21
|
+
);
|
|
40
22
|
|
|
41
|
-
|
|
42
|
-
return true;
|
|
23
|
+
expect(result).toBeTruthy();
|
|
43
24
|
});
|
|
44
25
|
});
|
|
26
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TestDriver SDK - Auto Cache Key Demo
|
|
3
|
+
*
|
|
4
|
+
* This test demonstrates the auto-generated cache key feature.
|
|
5
|
+
* When no cacheKey is provided, TestDriver will automatically generate
|
|
6
|
+
* one based on the hash of this test file.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, expect, it } from "vitest";
|
|
10
|
+
import { TestDriver } from "../../src/vitest/hooks.mjs";
|
|
11
|
+
|
|
12
|
+
describe("Auto Cache Key Demo", () => {
|
|
13
|
+
it("should use auto-generated cache key based on file hash", async (context) => {
|
|
14
|
+
// NOTE: No cacheKey is provided here!
|
|
15
|
+
// TestDriver will automatically generate one from the hash of this file
|
|
16
|
+
const testdriver = TestDriver(context, {
|
|
17
|
+
headless: true,
|
|
18
|
+
newSandbox: true
|
|
19
|
+
// cacheKey NOT specified - will be auto-generated
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// The cache key should be auto-generated
|
|
23
|
+
console.log('Auto-generated cache key:', testdriver.options.cacheKey);
|
|
24
|
+
expect(testdriver.options.cacheKey).toBeTruthy();
|
|
25
|
+
expect(testdriver.options.cacheKey).toMatch(/^[0-9a-f]{16}$/); // 16 hex chars
|
|
26
|
+
|
|
27
|
+
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
28
|
+
|
|
29
|
+
// First find - will be cached with auto-generated key
|
|
30
|
+
const signInButton1 = await testdriver.find(
|
|
31
|
+
"Sign In, black button below the password field"
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Second find - should hit cache because it's the same file (same cache key)
|
|
35
|
+
const signInButton2 = await testdriver.find(
|
|
36
|
+
"Sign In, black button below the password field"
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
expect(signInButton1.found()).toBe(true);
|
|
40
|
+
expect(signInButton2.found()).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should use same auto-generated cache key for multiple tests in the same file", async (context) => {
|
|
44
|
+
// This test is in the same file, so it should get the same auto-generated cache key
|
|
45
|
+
const testdriver = TestDriver(context, {
|
|
46
|
+
headless: true,
|
|
47
|
+
newSandbox: true
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
console.log('Auto-generated cache key (test 2):', testdriver.options.cacheKey);
|
|
51
|
+
expect(testdriver.options.cacheKey).toBeTruthy();
|
|
52
|
+
|
|
53
|
+
// If you modify this file, the hash (and therefore cache key) will change,
|
|
54
|
+
// invalidating the cache for this test file
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chrome Extension Loading Demo
|
|
3
|
+
* Demonstrates how to load Chrome extensions using Chrome for Testing
|
|
4
|
+
*
|
|
5
|
+
* This test shows how to launch Chrome with a specific extension loaded
|
|
6
|
+
* by using its Chrome Web Store extension ID.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
10
|
+
import TestDriver from "../../sdk.js";
|
|
11
|
+
import {
|
|
12
|
+
runPostrun,
|
|
13
|
+
runPrerunChromeExtension
|
|
14
|
+
} from "./setup/lifecycleHelpers.mjs";
|
|
15
|
+
|
|
16
|
+
describe("Chrome Extension Loading", () => {
|
|
17
|
+
let client;
|
|
18
|
+
let dashcamUrl;
|
|
19
|
+
|
|
20
|
+
beforeAll(async () => {
|
|
21
|
+
// Initialize TestDriver client
|
|
22
|
+
client = await TestDriver.create({
|
|
23
|
+
apiKey: process.env.TD_API_KEY,
|
|
24
|
+
apiRoot: process.env.TD_API_ROOT,
|
|
25
|
+
os: "linux",
|
|
26
|
+
verbosity: 1,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Run prerun with uBlock Origin extension loaded
|
|
30
|
+
// Extension ID: cjpalhdlnbpafiamejdnhcphjbkeiagm
|
|
31
|
+
await runPrerunChromeExtension(client, "cjpalhdlnbpafiamejdnhcphjbkeiagm");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterAll(async () => {
|
|
35
|
+
if (client) {
|
|
36
|
+
dashcamUrl = await runPostrun(client);
|
|
37
|
+
await client.cleanup();
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should load Chrome with extension and verify functionality", async () => {
|
|
42
|
+
// Focus Chrome browser
|
|
43
|
+
await client.focusApplication("Google Chrome");
|
|
44
|
+
|
|
45
|
+
// Verify the page loaded
|
|
46
|
+
const pageElement = await client.find("TestDriver.ai Sandbox");
|
|
47
|
+
expect(pageElement.found()).toBe(true);
|
|
48
|
+
|
|
49
|
+
// Test basic interaction to ensure Chrome is working with the extension
|
|
50
|
+
const signInButton = await client.find(
|
|
51
|
+
"Sign In, black button below the password field",
|
|
52
|
+
);
|
|
53
|
+
await signInButton.click();
|
|
54
|
+
|
|
55
|
+
// Verify error message appears
|
|
56
|
+
const result = await client.assert(
|
|
57
|
+
"an error shows that fields are required",
|
|
58
|
+
);
|
|
59
|
+
expect(result).toBeTruthy();
|
|
60
|
+
|
|
61
|
+
console.log("✅ Chrome extension loaded successfully!");
|
|
62
|
+
if (dashcamUrl) {
|
|
63
|
+
console.log("🎥 Dashcam URL:", dashcamUrl);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should demonstrate extension interaction", async () => {
|
|
68
|
+
// You can add specific tests here to interact with the extension
|
|
69
|
+
// For example, if testing uBlock Origin, you might:
|
|
70
|
+
// 1. Navigate to a page with ads
|
|
71
|
+
// 2. Verify ads are blocked
|
|
72
|
+
// 3. Access the extension's popup or settings
|
|
73
|
+
|
|
74
|
+
await client.focusApplication("Google Chrome");
|
|
75
|
+
|
|
76
|
+
// Example: Navigate to extension management page to verify it's loaded
|
|
77
|
+
await client.exec(
|
|
78
|
+
"sh",
|
|
79
|
+
`xdotool key --clearmodifiers ctrl+shift+e`,
|
|
80
|
+
5000,
|
|
81
|
+
true
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Wait a moment for the extensions page to potentially load
|
|
85
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
86
|
+
|
|
87
|
+
console.log("✅ Extension interaction test completed");
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -3,31 +3,19 @@
|
|
|
3
3
|
* Converted from: testdriver/acceptance/drag-and-drop.yaml
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
createTestClient,
|
|
9
|
-
setupTest,
|
|
10
|
-
teardownTest,
|
|
11
|
-
} from "./setup/testHelpers.mjs";
|
|
6
|
+
import { describe, expect, it } from "vitest";
|
|
7
|
+
import { TestDriver } from "../../src/vitest/hooks.mjs";
|
|
12
8
|
|
|
13
9
|
const isLinux = (process.env.TD_OS || "linux") === "linux";
|
|
14
10
|
|
|
15
11
|
describe("Drag and Drop Test", () => {
|
|
16
|
-
let testdriver;
|
|
17
|
-
|
|
18
|
-
beforeEach(async (context) => {
|
|
19
|
-
testdriver = createTestClient({ task: context.task });
|
|
20
|
-
|
|
21
|
-
await setupTest(testdriver);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
afterEach(async (context) => {
|
|
25
|
-
await teardownTest(testdriver, { task: context.task });
|
|
26
|
-
});
|
|
27
|
-
|
|
28
12
|
it.skipIf(isLinux)(
|
|
29
13
|
'should drag "New Text Document" to "Recycle Bin"',
|
|
30
|
-
async () => {
|
|
14
|
+
async (context) => {
|
|
15
|
+
const testdriver = TestDriver(context, { headless: true });
|
|
16
|
+
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
17
|
+
|
|
18
|
+
//
|
|
31
19
|
// Show the desktop
|
|
32
20
|
await testdriver.pressKeys(["win", "d"]);
|
|
33
21
|
|
|
@@ -3,28 +3,15 @@
|
|
|
3
3
|
* Tests that finding a non-existent element returns properly without timing out
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
createTestClient,
|
|
9
|
-
setupTest,
|
|
10
|
-
teardownTest,
|
|
11
|
-
} from "./setup/testHelpers.mjs";
|
|
6
|
+
import { describe, expect, it } from "vitest";
|
|
7
|
+
import { TestDriver } from "../../src/vitest/hooks.mjs";
|
|
12
8
|
|
|
13
9
|
describe("Element Not Found Test", () => {
|
|
14
|
-
|
|
10
|
+
it("should handle non-existent element gracefully without timing out", async (context) => {
|
|
11
|
+
const testdriver = TestDriver(context, { headless: true });
|
|
12
|
+
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
testdriver = createTestClient({ task: context.task });
|
|
18
|
-
|
|
19
|
-
await setupTest(testdriver);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
afterEach(async (context) => {
|
|
23
|
-
await teardownTest(testdriver, { task: context.task });
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("should handle non-existent element gracefully without timing out", async () => {
|
|
27
|
-
await testdriver.focusApplication("Google Chrome");
|
|
14
|
+
//
|
|
28
15
|
|
|
29
16
|
// Try to find an element that definitely doesn't exist
|
|
30
17
|
const element = await testdriver.find(
|
|
@@ -3,27 +3,15 @@
|
|
|
3
3
|
* Converted from: testdriver/acceptance/exec-js.yaml
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
createTestClient,
|
|
9
|
-
setupTest,
|
|
10
|
-
teardownTest,
|
|
11
|
-
} from "./setup/testHelpers.mjs";
|
|
6
|
+
import { describe, expect, it } from "vitest";
|
|
7
|
+
import { TestDriver } from "../../src/vitest/hooks.mjs";
|
|
12
8
|
|
|
13
9
|
describe("Exec JavaScript Test", () => {
|
|
14
|
-
|
|
10
|
+
it("should fetch user data from API and enter email", async (context) => {
|
|
11
|
+
const testdriver = TestDriver(context, { headless: true });
|
|
12
|
+
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
testdriver = createTestClient({ task: context.task });
|
|
18
|
-
|
|
19
|
-
await setupTest(testdriver);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
afterEach(async (context) => {
|
|
23
|
-
await teardownTest(testdriver, { task: context.task });
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("should fetch user data from API and enter email", async () => {
|
|
14
|
+
//
|
|
27
15
|
// Execute JavaScript to fetch user data
|
|
28
16
|
const userEmail = await testdriver.exec(
|
|
29
17
|
"js",
|
|
@@ -3,29 +3,17 @@
|
|
|
3
3
|
* Converted from: testdriver/acceptance/exec-output.yaml
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
createTestClient,
|
|
9
|
-
setupTest,
|
|
10
|
-
teardownTest,
|
|
11
|
-
} from "./setup/testHelpers.mjs";
|
|
6
|
+
import { describe, expect, it } from "vitest";
|
|
7
|
+
import { TestDriver } from "../../src/vitest/hooks.mjs";
|
|
12
8
|
|
|
13
9
|
describe("Exec Output Test", () => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
beforeEach(async (context) => {
|
|
17
|
-
testdriver = createTestClient({ task: context.task });
|
|
18
|
-
|
|
19
|
-
await setupTest(testdriver);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
afterEach(async (context) => {
|
|
23
|
-
await teardownTest(testdriver, { task: context.task });
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it.skipIf(() => testdriver.os === "linux")(
|
|
10
|
+
it.skipIf(process.env.TD_OS === "linux")(
|
|
27
11
|
"should set date using PowerShell and navigate to calendar",
|
|
28
|
-
async () => {
|
|
12
|
+
async (context) => {
|
|
13
|
+
const testdriver = TestDriver(context, { headless: true });
|
|
14
|
+
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
15
|
+
|
|
16
|
+
//
|
|
29
17
|
// Generate date in query string format
|
|
30
18
|
const queryString = await testdriver.exec(
|
|
31
19
|
"pwsh",
|
|
@@ -3,33 +3,21 @@
|
|
|
3
3
|
* Converted from: testdriver/acceptance/exec-shell.yaml
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
createTestClient,
|
|
9
|
-
setupTest,
|
|
10
|
-
teardownTest,
|
|
11
|
-
} from "./setup/testHelpers.mjs";
|
|
6
|
+
import { describe, expect, it } from "vitest";
|
|
7
|
+
import { TestDriver } from "../../src/vitest/hooks.mjs";
|
|
12
8
|
|
|
13
9
|
describe("Exec PowerShell Test", () => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
beforeEach(async (context) => {
|
|
17
|
-
testdriver = createTestClient({ task: context.task });
|
|
18
|
-
|
|
19
|
-
await setupTest(testdriver);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
afterEach(async (context) => {
|
|
23
|
-
await teardownTest(testdriver, { task: context.task });
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it.skipIf(() => testdriver.os === "linux")(
|
|
10
|
+
it.skipIf(process.env.TD_OS === "linux")(
|
|
27
11
|
"should generate random email using PowerShell and enter it",
|
|
28
|
-
async () => {
|
|
12
|
+
async (context) => {
|
|
13
|
+
const testdriver = TestDriver(context, { headless: true });
|
|
14
|
+
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
15
|
+
|
|
16
|
+
//
|
|
29
17
|
// Generate random email using PowerShell
|
|
30
|
-
const randomEmail = await testdriver.exec(
|
|
31
|
-
"pwsh",
|
|
32
|
-
`
|
|
18
|
+
const randomEmail = await testdriver.exec({
|
|
19
|
+
language: "pwsh",
|
|
20
|
+
code: `
|
|
33
21
|
# Random email generator in PowerShell
|
|
34
22
|
|
|
35
23
|
# Arrays of possible names and domains
|
|
@@ -49,8 +37,8 @@ $email = "$first.$last$number@$domain".ToLower()
|
|
|
49
37
|
# Output
|
|
50
38
|
Write-Output "$email"
|
|
51
39
|
`,
|
|
52
|
-
10000,
|
|
53
|
-
);
|
|
40
|
+
timeout: 10000,
|
|
41
|
+
});
|
|
54
42
|
|
|
55
43
|
// Enter the email in username field
|
|
56
44
|
const usernameField = await testdriver.find(
|