testdriverai 7.2.21 → 7.2.23
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/docs/v7/_drafts/plugin-migration.mdx +3 -5
- package/lib/vitest/hooks.mjs +26 -14
- package/package.json +1 -1
- package/test/testdriver/hover-image.test.mjs +19 -1
- package/test/testdriver/hover-text-with-description.test.mjs +19 -1
- package/test/testdriver/match-image.test.mjs +19 -1
- package/test/testdriver/scroll-until-text.test.mjs +19 -1
- package/docs/v7/_drafts/implementation-plan.mdx +0 -994
- package/docs/v7/_drafts/optimal-sdk-design.mdx +0 -1348
- package/docs/v7/_drafts/performance.mdx +0 -517
- package/docs/v7/_drafts/platforms/linux.mdx +0 -308
- package/docs/v7/_drafts/platforms/macos.mdx +0 -433
- package/docs/v7/_drafts/platforms/windows.mdx +0 -430
- package/docs/v7/_drafts/sdk-logging.mdx +0 -222
- package/test/testdriver/setup/globalTeardown.mjs +0 -11
- package/test/testdriver/setup/lifecycleHelpers.mjs +0 -357
- package/test/testdriver/setup/testHelpers.mjs +0 -541
- package/test/testdriver/setup/vitestSetup.mjs +0 -40
|
@@ -1,541 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test Helpers and Utilities
|
|
3
|
-
* Shared functions for SDK tests
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import crypto from "crypto";
|
|
7
|
-
import { config } from "dotenv";
|
|
8
|
-
import path, { dirname } from "path";
|
|
9
|
-
import { fileURLToPath } from "url";
|
|
10
|
-
import TestDriver from "../../../sdk.js";
|
|
11
|
-
import {
|
|
12
|
-
addDashcamLog,
|
|
13
|
-
authDashcam,
|
|
14
|
-
launchChrome,
|
|
15
|
-
runPostrun,
|
|
16
|
-
runPrerun,
|
|
17
|
-
startDashcam,
|
|
18
|
-
stopDashcam,
|
|
19
|
-
waitForPage,
|
|
20
|
-
} from "./lifecycleHelpers.mjs";
|
|
21
|
-
|
|
22
|
-
// Re-export lifecycle helpers for backward compatibility
|
|
23
|
-
export {
|
|
24
|
-
addDashcamLog,
|
|
25
|
-
authDashcam,
|
|
26
|
-
launchChrome,
|
|
27
|
-
runPostrun,
|
|
28
|
-
runPrerun,
|
|
29
|
-
startDashcam,
|
|
30
|
-
stopDashcam,
|
|
31
|
-
waitForPage
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
// Get the directory of the current module
|
|
35
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
36
|
-
const __dirname = dirname(__filename);
|
|
37
|
-
|
|
38
|
-
// Load environment variables from .env file in the project root
|
|
39
|
-
// Go up 3 levels from setup/ to reach the project root
|
|
40
|
-
const envPath = path.resolve(__dirname, "../../../.env");
|
|
41
|
-
config({ path: envPath });
|
|
42
|
-
|
|
43
|
-
// Log loaded env vars for debugging
|
|
44
|
-
console.log("🔧 Environment variables loaded from:", envPath);
|
|
45
|
-
console.log(" TD_API_KEY:", process.env.TD_API_KEY ? "✓ Set" : "✗ Not set");
|
|
46
|
-
console.log(" TD_API_ROOT:", process.env.TD_API_ROOT || "Not set");
|
|
47
|
-
console.log(
|
|
48
|
-
" TD_OS:",
|
|
49
|
-
process.env.TD_OS || "Not set (will default to linux)",
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Intercept console logs and forward to TestDriver sandbox
|
|
54
|
-
* @param {TestDriver} client - TestDriver client instance
|
|
55
|
-
* @param {string} taskId - Unique task identifier for this test
|
|
56
|
-
*/
|
|
57
|
-
function setupConsoleInterceptor(client, taskId) {
|
|
58
|
-
// Store original console methods
|
|
59
|
-
const originalConsole = {
|
|
60
|
-
log: console.log,
|
|
61
|
-
error: console.error,
|
|
62
|
-
warn: console.warn,
|
|
63
|
-
info: console.info,
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
// Create wrapper that forwards to sandbox
|
|
67
|
-
const createInterceptor = (level, originalMethod) => {
|
|
68
|
-
return function (...args) {
|
|
69
|
-
// Call original console method first
|
|
70
|
-
originalMethod.apply(console, args);
|
|
71
|
-
|
|
72
|
-
// Forward to sandbox if connected
|
|
73
|
-
if (client.sandbox && client.sandbox.instanceSocketConnected) {
|
|
74
|
-
try {
|
|
75
|
-
// Format the log message
|
|
76
|
-
const message = args
|
|
77
|
-
.map((arg) =>
|
|
78
|
-
typeof arg === "object"
|
|
79
|
-
? JSON.stringify(arg, null, 2)
|
|
80
|
-
: String(arg),
|
|
81
|
-
)
|
|
82
|
-
.join(" ");
|
|
83
|
-
|
|
84
|
-
// Preserve ANSI color codes and emojis for rich sandbox output
|
|
85
|
-
// (don't add level prefix - sdk-log-formatter handles styling)
|
|
86
|
-
const logOutput = message;
|
|
87
|
-
|
|
88
|
-
client.sandbox.send({
|
|
89
|
-
type: "output",
|
|
90
|
-
output: Buffer.from(logOutput, "utf8").toString("base64"),
|
|
91
|
-
});
|
|
92
|
-
} catch (error) {
|
|
93
|
-
// Silently fail to avoid breaking the test
|
|
94
|
-
// Use original console to avoid infinite loop
|
|
95
|
-
originalConsole.error(
|
|
96
|
-
`[TestHelpers] Failed to forward log to sandbox:`,
|
|
97
|
-
error.message,
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
// Replace console methods with interceptors
|
|
105
|
-
console.log = createInterceptor("log", originalConsole.log);
|
|
106
|
-
console.error = createInterceptor("error", originalConsole.error);
|
|
107
|
-
console.warn = createInterceptor("warn", originalConsole.warn);
|
|
108
|
-
console.info = createInterceptor("info", originalConsole.info);
|
|
109
|
-
|
|
110
|
-
// Store original methods and taskId on client for cleanup
|
|
111
|
-
client._consoleInterceptor = {
|
|
112
|
-
taskId,
|
|
113
|
-
original: originalConsole,
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
// Use original console for this message
|
|
117
|
-
originalConsole.log(
|
|
118
|
-
`[TestHelpers] Console interceptor enabled for task: ${taskId}`,
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Remove console interceptor and restore original console methods
|
|
124
|
-
* @param {TestDriver} client - TestDriver client instance
|
|
125
|
-
*/
|
|
126
|
-
function removeConsoleInterceptor(client) {
|
|
127
|
-
if (client._consoleInterceptor) {
|
|
128
|
-
const { original, taskId } = client._consoleInterceptor;
|
|
129
|
-
|
|
130
|
-
// Restore original console methods
|
|
131
|
-
console.log = original.log;
|
|
132
|
-
console.error = original.error;
|
|
133
|
-
console.warn = original.warn;
|
|
134
|
-
console.info = original.info;
|
|
135
|
-
|
|
136
|
-
// Use original console for cleanup message
|
|
137
|
-
original.log(
|
|
138
|
-
`[TestHelpers] Console interceptor removed for task: ${taskId}`,
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
// Clean up reference
|
|
142
|
-
delete client._consoleInterceptor;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Create a configured TestDriver client
|
|
148
|
-
* @param {Object} options - Additional options
|
|
149
|
-
* @param {Object} options.task - Vitest task context (from beforeEach/it context)
|
|
150
|
-
* @returns {TestDriver} Configured client
|
|
151
|
-
*/
|
|
152
|
-
export function createTestClient(options = {}) {
|
|
153
|
-
// Check if API key is set
|
|
154
|
-
if (!process.env.TD_API_KEY) {
|
|
155
|
-
console.error("\n❌ Error: TD_API_KEY is not set!");
|
|
156
|
-
console.error("Please set it in one of the following ways:");
|
|
157
|
-
console.error(
|
|
158
|
-
" 1. Create a .env file in the project root with: TD_API_KEY=your_key",
|
|
159
|
-
);
|
|
160
|
-
console.error(
|
|
161
|
-
" 2. Pass it as an environment variable: TD_API_KEY=your_key npm run test:sdk",
|
|
162
|
-
);
|
|
163
|
-
console.error(" 3. Export it in your shell: export TD_API_KEY=your_key\n");
|
|
164
|
-
throw new Error("TD_API_KEY environment variable is required");
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Determine OS from TEST_PLATFORM or TD_OS
|
|
168
|
-
const os = process.env.TEST_PLATFORM || "linux";
|
|
169
|
-
|
|
170
|
-
// Extract task context if provided - we use taskId but remove task from clientOptions
|
|
171
|
-
let taskId = options.task?.id || options.task?.name || null;
|
|
172
|
-
|
|
173
|
-
// Remove task from options before passing to TestDriver (eslint wants us to use 'task')
|
|
174
|
-
// eslint-disable-next-line no-unused-vars
|
|
175
|
-
const { task, ...clientOptions } = options;
|
|
176
|
-
|
|
177
|
-
const client = new TestDriver(process.env.TD_API_KEY, {
|
|
178
|
-
resolution: "1366x768",
|
|
179
|
-
analytics: true,
|
|
180
|
-
os: os, // Use OS from environment variable (windows or linux)
|
|
181
|
-
apiKey: process.env.TD_API_KEY,
|
|
182
|
-
apiRoot: process.env.TD_API_ROOT || "https://testdriver-api.onrender.com",
|
|
183
|
-
// headless: false,
|
|
184
|
-
newSandbox: true,
|
|
185
|
-
// ip: '18.217.194.23'
|
|
186
|
-
// ...clientOptions,
|
|
187
|
-
// cache: false,
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
console.log(
|
|
191
|
-
"🔧 createTestClient: SDK created, cacheThresholds =",
|
|
192
|
-
client.cacheThresholds,
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
console.log(`[TestHelpers] Client OS configured as: ${client.os}`);
|
|
196
|
-
|
|
197
|
-
// Set Vitest task ID if available (for log filtering in parallel tests)
|
|
198
|
-
if (taskId) {
|
|
199
|
-
console.log(`[TestHelpers] Storing task ID on client: ${taskId}`);
|
|
200
|
-
// Store task ID directly on client for later use in teardown
|
|
201
|
-
client.vitestTaskId = taskId;
|
|
202
|
-
} else {
|
|
203
|
-
console.log(`[TestHelpers] No task ID available`);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Enable detailed event logging if requested
|
|
207
|
-
if (process.env.DEBUG_EVENTS === "true") {
|
|
208
|
-
setupEventLogging(client);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return client;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Set up detailed event logging for debugging
|
|
216
|
-
* @param {TestDriver} client - TestDriver client
|
|
217
|
-
*/
|
|
218
|
-
export function setupEventLogging(client) {
|
|
219
|
-
const emitter = client.getEmitter();
|
|
220
|
-
|
|
221
|
-
// Log all events
|
|
222
|
-
emitter.on("**", function (data) {
|
|
223
|
-
const event = this.event;
|
|
224
|
-
if (event.startsWith("log:debug")) return; // Skip debug logs
|
|
225
|
-
console.log(`[EVENT] ${event}`, data || "");
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
// Log command lifecycle
|
|
229
|
-
emitter.on("command:start", (data) => {
|
|
230
|
-
console.log("🚀 Command started:", data);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
emitter.on("command:success", (data) => {
|
|
234
|
-
console.log("✅ Command succeeded:", data);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
emitter.on("command:error", (data) => {
|
|
238
|
-
console.error("❌ Command error:", data);
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
// Log sandbox events
|
|
242
|
-
emitter.on("sandbox:connected", () => {
|
|
243
|
-
console.log("🔌 Sandbox connected");
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
emitter.on("sandbox:authenticated", () => {
|
|
247
|
-
console.log("🔐 Sandbox authenticated");
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
emitter.on("sandbox:error", (error) => {
|
|
251
|
-
console.error("⚠️ Sandbox error:", error);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
// Log SDK API calls
|
|
255
|
-
emitter.on("sdk:request", (data) => {
|
|
256
|
-
console.log("📤 SDK Request:", data);
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
emitter.on("sdk:response", (data) => {
|
|
260
|
-
console.log("📥 SDK Response:", data);
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Setup function to run before each test
|
|
266
|
-
* Authenticates and connects to sandbox
|
|
267
|
-
* @param {TestDriver} client - TestDriver client
|
|
268
|
-
* @param {Object} options - Connection options
|
|
269
|
-
* @returns {Promise<Object>} Sandbox instance
|
|
270
|
-
*/
|
|
271
|
-
export async function setupTest(client, options = {}) {
|
|
272
|
-
await client.auth();
|
|
273
|
-
const instance = await client.connect({
|
|
274
|
-
...options,
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
// Set up console interceptor after connection (needs sandbox to be connected)
|
|
278
|
-
if (client.vitestTaskId) {
|
|
279
|
-
setupConsoleInterceptor(client, client.vitestTaskId);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Run prerun lifecycle if enabled
|
|
283
|
-
if (options.prerun !== false) {
|
|
284
|
-
await runPrerun(client);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return instance;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Initialize a test run for the entire suite
|
|
292
|
-
* Should be called once in beforeEach
|
|
293
|
-
* @param {Object} suiteTask - Vitest suite task context
|
|
294
|
-
* @returns {Promise<Object>} Test run info { runId, testRunDbId, token }
|
|
295
|
-
*/
|
|
296
|
-
export async function initializeSuiteTestRun(suiteTask) {
|
|
297
|
-
const apiKey = process.env.TD_API_KEY;
|
|
298
|
-
const apiRoot =
|
|
299
|
-
process.env.TD_API_ROOT || "https://testdriver-api.onrender.com";
|
|
300
|
-
|
|
301
|
-
if (!apiKey || !globalThis.__testdriverPlugin) {
|
|
302
|
-
console.log(
|
|
303
|
-
`[TestHelpers] Skipping suite test run initialization - no API key or plugin`,
|
|
304
|
-
);
|
|
305
|
-
return null;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// Check if test run already exists for this suite
|
|
309
|
-
const existingRun = globalThis.__testdriverPlugin.getSuiteTestRun(
|
|
310
|
-
suiteTask.id,
|
|
311
|
-
);
|
|
312
|
-
if (existingRun) {
|
|
313
|
-
console.log(
|
|
314
|
-
`[TestHelpers] Test run already exists for suite: ${existingRun.runId}`,
|
|
315
|
-
);
|
|
316
|
-
return existingRun;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
try {
|
|
320
|
-
console.log(
|
|
321
|
-
`[TestHelpers] Initializing test run for suite: ${suiteTask.name}`,
|
|
322
|
-
);
|
|
323
|
-
|
|
324
|
-
// Authenticate
|
|
325
|
-
const token = await globalThis.__testdriverPlugin.authenticateWithApiKey(
|
|
326
|
-
apiKey,
|
|
327
|
-
apiRoot,
|
|
328
|
-
);
|
|
329
|
-
console.log(`[TestHelpers] ✅ Authenticated for suite`);
|
|
330
|
-
|
|
331
|
-
// Create test run for the suite
|
|
332
|
-
const runId = `${Date.now()}-${crypto.randomBytes(4).toString("hex")}`;
|
|
333
|
-
const testFile = suiteTask.file?.name || "unknown";
|
|
334
|
-
const testRunData = {
|
|
335
|
-
runId,
|
|
336
|
-
suiteName: suiteTask.name || testFile,
|
|
337
|
-
};
|
|
338
|
-
|
|
339
|
-
const testRunResponse =
|
|
340
|
-
await globalThis.__testdriverPlugin.createTestRunDirect(
|
|
341
|
-
token,
|
|
342
|
-
apiRoot,
|
|
343
|
-
testRunData,
|
|
344
|
-
);
|
|
345
|
-
const testRunDbId = testRunResponse.data?.id;
|
|
346
|
-
|
|
347
|
-
const runInfo = { runId, testRunDbId, token };
|
|
348
|
-
|
|
349
|
-
// Store in plugin state
|
|
350
|
-
globalThis.__testdriverPlugin.setSuiteTestRun(suiteTask.id, runInfo);
|
|
351
|
-
|
|
352
|
-
// Set environment variables for the reporter to use
|
|
353
|
-
process.env.TD_TEST_RUN_ID = runId;
|
|
354
|
-
process.env.TD_TEST_RUN_DB_ID = testRunDbId;
|
|
355
|
-
process.env.TD_TEST_RUN_TOKEN = token;
|
|
356
|
-
|
|
357
|
-
console.log(
|
|
358
|
-
`[TestHelpers] ✅ Created test run for suite: ${runId} (DB ID: ${testRunDbId})`,
|
|
359
|
-
);
|
|
360
|
-
|
|
361
|
-
return runInfo;
|
|
362
|
-
} catch (error) {
|
|
363
|
-
console.error(
|
|
364
|
-
`[TestHelpers] ❌ Failed to initialize suite test run:`,
|
|
365
|
-
error.message,
|
|
366
|
-
);
|
|
367
|
-
return null;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* Teardown function to run after each test
|
|
373
|
-
* @param {TestDriver} client - TestDriver client
|
|
374
|
-
* @param {Object} options - Teardown options
|
|
375
|
-
* @param {Object} options.task - Vitest task context (optional, for storing in task.meta)
|
|
376
|
-
* @param {string} options.dashcamUrl - Dashcam URL if already retrieved
|
|
377
|
-
* @param {boolean} options.postrun - Whether to run postrun lifecycle (default: true)
|
|
378
|
-
* @param {boolean} options.disconnect - Whether to disconnect client (default: true)
|
|
379
|
-
* @returns {Promise<Object>} Session info including dashcam URL
|
|
380
|
-
*/
|
|
381
|
-
export async function teardownTest(client, options = {}) {
|
|
382
|
-
let dashcamUrl = options.dashcamUrl || null;
|
|
383
|
-
|
|
384
|
-
console.log("🧹 Running teardown...");
|
|
385
|
-
|
|
386
|
-
try {
|
|
387
|
-
// Run postrun lifecycle if enabled and dashcamUrl not already provided
|
|
388
|
-
if (options.postrun !== false && !dashcamUrl) {
|
|
389
|
-
dashcamUrl = await runPostrun(client);
|
|
390
|
-
|
|
391
|
-
// Store dashcamUrl in client for reporter access
|
|
392
|
-
if (dashcamUrl) {
|
|
393
|
-
// Extract replay object ID from URL
|
|
394
|
-
// URL format: https://app.testdriver.ai/replay/{replayObjectId}?share={shareToken}
|
|
395
|
-
const replayIdMatch = dashcamUrl.match(/\/replay\/([^?]+)/);
|
|
396
|
-
const replayObjectId = replayIdMatch ? replayIdMatch[1] : null;
|
|
397
|
-
|
|
398
|
-
console.log(`🎥 Dashcam URL: ${dashcamUrl}`);
|
|
399
|
-
if (replayObjectId) {
|
|
400
|
-
console.log(`📝 Replay Object ID: ${replayObjectId}`);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// Store dashcam URL in task meta
|
|
404
|
-
if (options.task) {
|
|
405
|
-
options.task.meta.testdriverDashcamUrl = dashcamUrl;
|
|
406
|
-
options.task.meta.testdriverReplayObjectId = replayObjectId;
|
|
407
|
-
console.log(
|
|
408
|
-
`[TestHelpers] ✅ Stored dashcam URL in task.meta for test: ${options.task.name}`,
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
} else {
|
|
413
|
-
console.log("⏭️ Postrun skipped (disabled in options)");
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// Use Vitest's task.meta for cross-process communication with the reporter
|
|
417
|
-
if (options.task) {
|
|
418
|
-
try {
|
|
419
|
-
// Get test file path - make it relative to project root
|
|
420
|
-
const absolutePath =
|
|
421
|
-
options.task.file?.filepath || options.task.file?.name || "unknown";
|
|
422
|
-
const projectRoot = process.cwd();
|
|
423
|
-
const testFile = absolutePath !== "unknown"
|
|
424
|
-
? path.relative(projectRoot, absolutePath)
|
|
425
|
-
: absolutePath;
|
|
426
|
-
|
|
427
|
-
// Calculate test order (index within parent suite)
|
|
428
|
-
let testOrder = 0;
|
|
429
|
-
if (options.task.suite && options.task.suite.tasks) {
|
|
430
|
-
testOrder = options.task.suite.tasks.indexOf(options.task);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// Set metadata on task for the reporter to pick up
|
|
434
|
-
options.task.meta.dashcamUrl = dashcamUrl;
|
|
435
|
-
options.task.meta.platform = client.os; // Include platform from SDK client (source of truth)
|
|
436
|
-
options.task.meta.testFile = testFile;
|
|
437
|
-
options.task.meta.testOrder = testOrder;
|
|
438
|
-
options.task.meta.sessionId = client.getSessionId?.() || null;
|
|
439
|
-
} catch (error) {
|
|
440
|
-
console.error(
|
|
441
|
-
`[TestHelpers] ❌ Failed to set test metadata:`,
|
|
442
|
-
error.message,
|
|
443
|
-
);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
} catch (error) {
|
|
447
|
-
console.error("❌ Error in postrun:", error);
|
|
448
|
-
console.error("❌ Error stack:", error.stack);
|
|
449
|
-
} finally {
|
|
450
|
-
// Remove console interceptor before disconnecting
|
|
451
|
-
removeConsoleInterceptor(client);
|
|
452
|
-
|
|
453
|
-
// Only disconnect if not explicitly disabled
|
|
454
|
-
if (options.disconnect !== false) {
|
|
455
|
-
console.log("🔌 Disconnecting client...");
|
|
456
|
-
try {
|
|
457
|
-
await client.disconnect();
|
|
458
|
-
} catch (disconnectError) {
|
|
459
|
-
console.error("❌ Error disconnecting:", disconnectError.message);
|
|
460
|
-
// Don't throw - we're already in cleanup
|
|
461
|
-
}
|
|
462
|
-
} else {
|
|
463
|
-
console.log("⏭️ Disconnect skipped (disabled in options)");
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// Extract replay object ID from dashcam URL
|
|
468
|
-
let replayObjectId = null;
|
|
469
|
-
if (dashcamUrl) {
|
|
470
|
-
const replayIdMatch = dashcamUrl.match(/\/replay\/([^?]+)/);
|
|
471
|
-
replayObjectId = replayIdMatch ? replayIdMatch[1] : null;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
const sessionInfo = {
|
|
475
|
-
sessionId: client.getSessionId(),
|
|
476
|
-
dashcamUrl: dashcamUrl,
|
|
477
|
-
replayObjectId: replayObjectId,
|
|
478
|
-
instance: client.getInstance(),
|
|
479
|
-
};
|
|
480
|
-
|
|
481
|
-
console.log("📊 Session info:", JSON.stringify(sessionInfo, null, 2));
|
|
482
|
-
|
|
483
|
-
return sessionInfo;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
* Perform login flow (reusable snippet)
|
|
488
|
-
* @param {TestDriver} client - TestDriver client
|
|
489
|
-
* @param {string} username - Username (default: 'standard_user')
|
|
490
|
-
* @param {string} password - Password (default: retrieved from screen)
|
|
491
|
-
*/
|
|
492
|
-
export async function performLogin(
|
|
493
|
-
client,
|
|
494
|
-
username = "standard_user",
|
|
495
|
-
password = null,
|
|
496
|
-
) {
|
|
497
|
-
await client.focusApplication("Google Chrome");
|
|
498
|
-
|
|
499
|
-
// Get password from screen if not provided
|
|
500
|
-
if (!password) {
|
|
501
|
-
password = await client.extract("the password");
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
const usernameField = await client.find(
|
|
505
|
-
"Username, label above the username input field on the login form",
|
|
506
|
-
);
|
|
507
|
-
await usernameField.click();
|
|
508
|
-
await client.type(username);
|
|
509
|
-
|
|
510
|
-
// Enter password (marked as secret so it's not logged or stored)
|
|
511
|
-
await client.pressKeys(["tab"]);
|
|
512
|
-
await client.type(password, { secret: true });
|
|
513
|
-
|
|
514
|
-
// Submit form
|
|
515
|
-
await client.pressKeys(["tab"]);
|
|
516
|
-
await client.pressKeys(["enter"]);
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
/**
|
|
520
|
-
* Wait with retry logic
|
|
521
|
-
* @param {Function} fn - Async function to retry
|
|
522
|
-
* @param {number} retries - Number of retries (default: 3)
|
|
523
|
-
* @param {number} delay - Delay between retries in ms (default: 1000)
|
|
524
|
-
* @returns {Promise} Result of successful execution
|
|
525
|
-
*/
|
|
526
|
-
export async function retryAsync(fn, retries = 3, delay = 1000) {
|
|
527
|
-
let lastError;
|
|
528
|
-
|
|
529
|
-
for (let i = 0; i < retries; i++) {
|
|
530
|
-
try {
|
|
531
|
-
return await fn();
|
|
532
|
-
} catch (error) {
|
|
533
|
-
lastError = error;
|
|
534
|
-
if (i < retries - 1) {
|
|
535
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
throw lastError;
|
|
541
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vitest Setup File
|
|
3
|
-
* Runs once before all tests in each worker process
|
|
4
|
-
* This ensures the TestDriver plugin global state is available in test processes
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
// Import the plugin functions
|
|
8
|
-
import {
|
|
9
|
-
authenticateWithApiKey,
|
|
10
|
-
clearDashcamUrls,
|
|
11
|
-
clearSuiteTestRun,
|
|
12
|
-
createTestRunDirect,
|
|
13
|
-
getDashcamUrl,
|
|
14
|
-
getPluginState,
|
|
15
|
-
getSuiteTestRun,
|
|
16
|
-
pluginState,
|
|
17
|
-
recordTestCaseDirect,
|
|
18
|
-
registerDashcamUrl,
|
|
19
|
-
setSuiteTestRun,
|
|
20
|
-
} from "../../../interfaces/vitest-plugin.mjs";
|
|
21
|
-
|
|
22
|
-
// Make the plugin API available globally in the test worker process
|
|
23
|
-
if (typeof globalThis !== "undefined") {
|
|
24
|
-
globalThis.__testdriverPlugin = {
|
|
25
|
-
registerDashcamUrl,
|
|
26
|
-
getDashcamUrl,
|
|
27
|
-
clearDashcamUrls,
|
|
28
|
-
authenticateWithApiKey,
|
|
29
|
-
createTestRunDirect,
|
|
30
|
-
recordTestCaseDirect,
|
|
31
|
-
getSuiteTestRun,
|
|
32
|
-
setSuiteTestRun,
|
|
33
|
-
clearSuiteTestRun,
|
|
34
|
-
getPluginState,
|
|
35
|
-
state: pluginState,
|
|
36
|
-
};
|
|
37
|
-
console.log(
|
|
38
|
-
"[Vitest Setup] TestDriver plugin API initialized in worker process",
|
|
39
|
-
);
|
|
40
|
-
}
|