testdriverai 7.1.4 โ 7.2.2
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/.github/workflows/acceptance.yaml +81 -0
- package/.github/workflows/publish.yaml +44 -0
- package/agent/index.js +18 -19
- package/agent/interface.js +4 -0
- package/agent/lib/commands.js +321 -121
- package/agent/lib/redraw.js +99 -39
- package/agent/lib/sandbox.js +98 -6
- package/agent/lib/sdk.js +25 -0
- package/agent/lib/system.js +2 -1
- package/agent/lib/validation.js +6 -6
- package/docs/docs.json +211 -101
- package/docs/snippets/tests/type-repeated-replay.mdx +1 -1
- package/docs/v7/_drafts/caching-selectors.mdx +24 -0
- package/docs/v7/api/act.mdx +1 -1
- package/docs/v7/api/assert.mdx +1 -1
- package/docs/v7/api/assertions.mdx +7 -7
- package/docs/v7/api/elements.mdx +78 -0
- package/docs/v7/api/find.mdx +38 -0
- package/docs/v7/api/focusApplication.mdx +2 -2
- package/docs/v7/api/hover.mdx +2 -2
- package/docs/v7/features/ai-native.mdx +57 -71
- package/docs/v7/features/application-logs.mdx +353 -0
- package/docs/v7/features/browser-logs.mdx +414 -0
- package/docs/v7/features/cache-management.mdx +402 -0
- package/docs/v7/features/continuous-testing.mdx +346 -0
- package/docs/v7/features/coverage.mdx +508 -0
- package/docs/v7/features/data-driven-testing.mdx +441 -0
- package/docs/v7/features/easy-to-write.mdx +2 -73
- package/docs/v7/features/enterprise.mdx +155 -39
- package/docs/v7/features/fast.mdx +63 -81
- package/docs/v7/features/managed-sandboxes.mdx +384 -0
- package/docs/v7/features/network-monitoring.mdx +568 -0
- package/docs/v7/features/observable.mdx +3 -22
- package/docs/v7/features/parallel-execution.mdx +381 -0
- package/docs/v7/features/powerful.mdx +1 -1
- package/docs/v7/features/reports.mdx +414 -0
- package/docs/v7/features/sandbox-customization.mdx +229 -0
- package/docs/v7/features/scalable.mdx +217 -2
- package/docs/v7/features/stable.mdx +106 -147
- package/docs/v7/features/system-performance.mdx +616 -0
- package/docs/v7/features/test-analytics.mdx +373 -0
- package/docs/v7/features/test-cases.mdx +393 -0
- package/docs/v7/features/test-replays.mdx +408 -0
- package/docs/v7/features/test-reports.mdx +308 -0
- package/docs/v7/getting-started/{running-and-debugging.mdx โ debugging-tests.mdx} +12 -142
- package/docs/v7/getting-started/quickstart.mdx +22 -305
- package/docs/v7/getting-started/running-tests.mdx +173 -0
- package/docs/v7/overview/what-is-testdriver.mdx +2 -14
- package/docs/v7/presets/chrome-extension.mdx +147 -122
- package/interfaces/cli/commands/init.js +3 -3
- package/interfaces/cli/lib/base.js +3 -2
- package/interfaces/logger.js +0 -2
- package/interfaces/shared-test-state.mjs +0 -5
- package/interfaces/vitest-plugin.mjs +70 -50
- package/lib/core/Dashcam.js +60 -85
- package/lib/vitest/hooks.mjs +42 -50
- package/package.json +1 -1
- package/sdk-log-formatter.js +350 -175
- package/sdk.d.ts +36 -3
- package/sdk.js +431 -116
- package/setup/aws/cloudformation.yaml +2 -2
- package/setup/aws/self-hosted.yml +1 -1
- package/test/testdriver/chrome-extension.test.mjs +55 -72
- package/test/testdriver/element-not-found.test.mjs +2 -1
- package/test/testdriver/hover-image.test.mjs +1 -1
- package/test/testdriver/scroll-until-text.test.mjs +10 -6
- package/test/testdriver/setup/lifecycleHelpers.mjs +19 -24
- package/test/testdriver/setup/testHelpers.mjs +18 -23
- package/vitest.config.mjs +3 -3
- package/.github/workflows/linux-tests.yml +0 -28
- package/docs/v7/getting-started/generating-tests.mdx +0 -525
- package/test/testdriver/auto-cache-key-demo.test.mjs +0 -56
package/sdk-log-formatter.js
CHANGED
|
@@ -8,6 +8,15 @@ const chalk = require("chalk");
|
|
|
8
8
|
* Now with full UTF-8 and emoji support! ๐
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
// Duration threshold configurations for different contexts
|
|
12
|
+
const DURATION_THRESHOLDS = {
|
|
13
|
+
default: { fast: 3000, medium: 10000 },
|
|
14
|
+
action: { fast: 100, medium: 500 },
|
|
15
|
+
redraw: { fast: 5000, medium: 10000 },
|
|
16
|
+
quickAction: { fast: 50, medium: 200 },
|
|
17
|
+
test: { fast: 1000, medium: 5000 },
|
|
18
|
+
};
|
|
19
|
+
|
|
11
20
|
class SDKLogFormatter {
|
|
12
21
|
constructor(options = {}) {
|
|
13
22
|
this.testContext = {
|
|
@@ -41,6 +50,156 @@ class SDKLogFormatter {
|
|
|
41
50
|
return `[${seconds}s]`;
|
|
42
51
|
}
|
|
43
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Add elapsed time to parts array if available
|
|
55
|
+
* @param {Array} parts - Array to push time string to
|
|
56
|
+
* @param {boolean} dim - Whether to dim the time string
|
|
57
|
+
*/
|
|
58
|
+
addTimestamp(parts, dim = true) {
|
|
59
|
+
const timeStr = this.getElapsedTime();
|
|
60
|
+
if (timeStr) {
|
|
61
|
+
parts.push(dim ? chalk.dim(timeStr) : timeStr);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get color function based on duration and thresholds
|
|
67
|
+
* @param {number} durationMs - Duration in milliseconds
|
|
68
|
+
* @param {string} thresholdKey - Key from DURATION_THRESHOLDS
|
|
69
|
+
* @returns {Function} Chalk color function
|
|
70
|
+
*/
|
|
71
|
+
getDurationColor(durationMs, thresholdKey = "default") {
|
|
72
|
+
const thresholds = DURATION_THRESHOLDS[thresholdKey] || DURATION_THRESHOLDS.default;
|
|
73
|
+
if (durationMs < thresholds.fast) return chalk.green;
|
|
74
|
+
if (durationMs < thresholds.medium) return chalk.yellow;
|
|
75
|
+
return chalk.red;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Format duration with appropriate color
|
|
80
|
+
* @param {number|string} duration - Duration in ms
|
|
81
|
+
* @param {string} thresholdKey - Key from DURATION_THRESHOLDS
|
|
82
|
+
* @param {boolean} showSeconds - Show as seconds (true) or raw (false)
|
|
83
|
+
* @returns {string} Formatted duration string
|
|
84
|
+
*/
|
|
85
|
+
formatDurationColored(duration, thresholdKey = "default", showSeconds = true) {
|
|
86
|
+
const durationMs = parseInt(duration);
|
|
87
|
+
const color = this.getDurationColor(durationMs, thresholdKey);
|
|
88
|
+
const display = showSeconds ? `(${(durationMs / 1000).toFixed(1)}s)` : `(${duration})`;
|
|
89
|
+
return color(display);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Join metadata parts with separator
|
|
94
|
+
* @param {Array} metaParts - Array of metadata strings
|
|
95
|
+
* @returns {string} Joined metadata string with separators
|
|
96
|
+
*/
|
|
97
|
+
joinMetaParts(metaParts) {
|
|
98
|
+
if (metaParts.length === 0) return "";
|
|
99
|
+
return chalk.dim("ยท") + " " + metaParts.join(chalk.dim(" ยท "));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Create an indented result line prefix (for child results)
|
|
104
|
+
* @returns {string} Indented arrow prefix
|
|
105
|
+
*/
|
|
106
|
+
getResultPrefix() {
|
|
107
|
+
return " " + chalk.dim("โ");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Format a nested action result line (scrolled, clicked, typed, pressed keys, etc.)
|
|
112
|
+
* @param {string} message - The action message (e.g., "scrolled down 300px", "pressed keys: tab")
|
|
113
|
+
* @param {number} durationMs - Duration in milliseconds
|
|
114
|
+
* @returns {string} Formatted nested result line
|
|
115
|
+
*/
|
|
116
|
+
formatNestedAction(message, durationMs) {
|
|
117
|
+
return this.getResultPrefix() + " " + chalk.dim(message) + " " + this.formatDurationColored(durationMs);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Format a redraw/idle wait completion line
|
|
122
|
+
* @param {number} durationMs - Duration in milliseconds
|
|
123
|
+
* @returns {string} Formatted redraw complete line
|
|
124
|
+
*/
|
|
125
|
+
formatRedrawComplete(durationMs) {
|
|
126
|
+
return this.formatNestedAction("flake protection", durationMs);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Format a scroll action result
|
|
131
|
+
* @param {string} direction - Scroll direction (up, down, left, right)
|
|
132
|
+
* @param {number} amount - Scroll amount in pixels
|
|
133
|
+
* @param {number} durationMs - Duration in milliseconds
|
|
134
|
+
* @returns {string} Formatted scroll result line
|
|
135
|
+
*/
|
|
136
|
+
formatScrollResult(direction, amount, durationMs) {
|
|
137
|
+
return this.formatNestedAction(`scrolled ${direction} ${amount}px`, durationMs);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Format a click action result
|
|
142
|
+
* @param {string} button - Button type (left, right, middle)
|
|
143
|
+
* @param {number} x - X coordinate
|
|
144
|
+
* @param {number} y - Y coordinate
|
|
145
|
+
* @param {number} durationMs - Duration in milliseconds
|
|
146
|
+
* @returns {string} Formatted click result line
|
|
147
|
+
*/
|
|
148
|
+
formatClickResult(button, x, y, durationMs) {
|
|
149
|
+
return this.formatNestedAction(`click ${button} clicking at ${x}, ${y}`, durationMs);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Format a type action result
|
|
154
|
+
* @param {string} text - Text that was typed (or "****" for secrets)
|
|
155
|
+
* @param {boolean} isSecret - Whether the text is a secret
|
|
156
|
+
* @param {number} durationMs - Duration in milliseconds
|
|
157
|
+
* @returns {string} Formatted type result line
|
|
158
|
+
*/
|
|
159
|
+
formatTypeResult(text, isSecret, durationMs) {
|
|
160
|
+
const displayText = isSecret ? "secret ****" : `"${text}"`;
|
|
161
|
+
return this.formatNestedAction(`typed ${displayText}`, durationMs);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Format a press keys action result
|
|
166
|
+
* @param {string} keysDisplay - Keys that were pressed (comma-separated)
|
|
167
|
+
* @param {number} durationMs - Duration in milliseconds
|
|
168
|
+
* @returns {string} Formatted press keys result line
|
|
169
|
+
*/
|
|
170
|
+
formatPressKeysResult(keysDisplay, durationMs) {
|
|
171
|
+
return this.formatNestedAction(`pressed keys: ${keysDisplay}`, durationMs);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Format a nested code display line (for exec commands)
|
|
176
|
+
* @param {string} codeDisplay - The code to display
|
|
177
|
+
* @returns {string} Formatted code line
|
|
178
|
+
*/
|
|
179
|
+
formatCodeLine(codeDisplay) {
|
|
180
|
+
return this.getResultPrefix() + " " + chalk.dim(codeDisplay);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Format an exec complete result
|
|
185
|
+
* @param {number} exitCode - The exit code
|
|
186
|
+
* @param {number} durationMs - Duration in milliseconds
|
|
187
|
+
* @returns {string} Formatted exec result line
|
|
188
|
+
*/
|
|
189
|
+
formatExecComplete(exitCode, durationMs) {
|
|
190
|
+
const statusText = exitCode !== 0
|
|
191
|
+
? `failed (exit code ${exitCode})`
|
|
192
|
+
: `complete (exit code 0)`;
|
|
193
|
+
const statusColor = exitCode !== 0 ? chalk.red : chalk.green;
|
|
194
|
+
|
|
195
|
+
return this.formatResultLine(
|
|
196
|
+
statusText,
|
|
197
|
+
statusColor,
|
|
198
|
+
{ duration: durationMs },
|
|
199
|
+
"action"
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
44
203
|
/**
|
|
45
204
|
* Format a log message in Vitest style
|
|
46
205
|
* @param {string} type - Log type (info, success, error, action, debug)
|
|
@@ -54,8 +213,7 @@ class SDKLogFormatter {
|
|
|
54
213
|
const parts = [];
|
|
55
214
|
|
|
56
215
|
// Add timestamp/elapsed time
|
|
57
|
-
|
|
58
|
-
if (timeStr) parts.push(timeStr);
|
|
216
|
+
this.addTimestamp(parts, false);
|
|
59
217
|
|
|
60
218
|
// Add type prefix with color
|
|
61
219
|
const prefix = this.getPrefix(type);
|
|
@@ -118,7 +276,7 @@ class SDKLogFormatter {
|
|
|
118
276
|
drag: chalk.cyan("โ"),
|
|
119
277
|
|
|
120
278
|
// Keyboard actions
|
|
121
|
-
type: chalk.yellow("โจ๏ธ"),
|
|
279
|
+
type: chalk.yellow("โจ๏ธ "),
|
|
122
280
|
pressKeys: chalk.yellow("๐น"),
|
|
123
281
|
|
|
124
282
|
// Navigation
|
|
@@ -149,7 +307,7 @@ class SDKLogFormatter {
|
|
|
149
307
|
debug: chalk.gray("๐ง"),
|
|
150
308
|
|
|
151
309
|
// Default
|
|
152
|
-
action: chalk.cyan("โถ๏ธ"),
|
|
310
|
+
action: chalk.cyan("โถ๏ธ "),
|
|
153
311
|
};
|
|
154
312
|
return prefixes[type] || chalk.gray("โข");
|
|
155
313
|
}
|
|
@@ -173,94 +331,109 @@ class SDKLogFormatter {
|
|
|
173
331
|
}
|
|
174
332
|
|
|
175
333
|
/**
|
|
176
|
-
* Format
|
|
177
|
-
* @param {string}
|
|
334
|
+
* Format a "finding" style message (when search starts) ๐
|
|
335
|
+
* @param {string} prefixType - Prefix type for getPrefix
|
|
336
|
+
* @param {string} label - Action label (e.g., "Finding", "Finding All", "Asserting")
|
|
337
|
+
* @param {string} description - Element/assertion description
|
|
178
338
|
* @returns {string} Formatted message
|
|
179
339
|
*/
|
|
180
|
-
|
|
340
|
+
formatFindingStyle(prefixType, label, description) {
|
|
181
341
|
const parts = [];
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (timeStr) {
|
|
186
|
-
parts.push(chalk.dim(timeStr));
|
|
187
|
-
}
|
|
188
|
-
parts.push(this.getPrefix("find"));
|
|
189
|
-
|
|
190
|
-
// Main message with emphasis
|
|
191
|
-
parts.push(chalk.bold.cyan("Finding"));
|
|
342
|
+
this.addTimestamp(parts);
|
|
343
|
+
parts.push(this.getPrefix(prefixType));
|
|
344
|
+
parts.push(chalk.bold.cyan(label));
|
|
192
345
|
parts.push(chalk.cyan(`"${description}"`));
|
|
193
|
-
|
|
194
346
|
return parts.join(" ");
|
|
195
347
|
}
|
|
196
348
|
|
|
197
349
|
/**
|
|
198
|
-
* Format an element
|
|
350
|
+
* Format an element finding message (when search starts) ๐
|
|
199
351
|
* @param {string} description - Element description
|
|
200
|
-
* @param {Object} meta - Element metadata (coordinates, duration, cache hit)
|
|
201
352
|
* @returns {string} Formatted message
|
|
202
353
|
*/
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
// Time and icon on same line
|
|
207
|
-
const timeStr = this.getElapsedTime();
|
|
208
|
-
if (timeStr) {
|
|
209
|
-
parts.push(chalk.dim(timeStr));
|
|
210
|
-
}
|
|
211
|
-
parts.push(this.getPrefix("find"));
|
|
212
|
-
|
|
213
|
-
// Main message with emphasis
|
|
214
|
-
parts.push(chalk.bold.green("Found"));
|
|
215
|
-
parts.push(chalk.cyan(`"${description}"`));
|
|
354
|
+
formatElementFinding(description) {
|
|
355
|
+
return this.formatFindingStyle("find", "Finding", description);
|
|
356
|
+
}
|
|
216
357
|
|
|
217
|
-
|
|
358
|
+
/**
|
|
359
|
+
* Build common metadata parts for result messages
|
|
360
|
+
* @param {Object} meta - Metadata object
|
|
361
|
+
* @param {string} thresholdKey - Duration threshold key
|
|
362
|
+
* @returns {Array} Array of formatted metadata strings
|
|
363
|
+
*/
|
|
364
|
+
buildResultMetaParts(meta, thresholdKey = "default") {
|
|
218
365
|
const metaParts = [];
|
|
366
|
+
|
|
219
367
|
if (meta.x !== undefined && meta.y !== undefined) {
|
|
220
368
|
metaParts.push(chalk.dim.gray(`๐ (${meta.x}, ${meta.y})`));
|
|
221
369
|
}
|
|
222
|
-
if (meta.
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
? chalk.yellow
|
|
229
|
-
: chalk.red;
|
|
230
|
-
metaParts.push(chalk.dim(`โฑ๏ธ ${durationColor(meta.duration)}`));
|
|
370
|
+
if (meta.selectorId && meta.consoleUrl) {
|
|
371
|
+
const cacheUrl = `${meta.consoleUrl}/cache/${meta.selectorId}`;
|
|
372
|
+
metaParts.push(chalk.blue.underline(cacheUrl));
|
|
373
|
+
}
|
|
374
|
+
if (meta.error) {
|
|
375
|
+
metaParts.push(chalk.dim.red(meta.error));
|
|
231
376
|
}
|
|
232
377
|
if (meta.cacheHit) {
|
|
233
378
|
metaParts.push(chalk.bold.yellow("โก cached"));
|
|
234
379
|
}
|
|
380
|
+
// Duration always last
|
|
381
|
+
if (meta.duration) {
|
|
382
|
+
metaParts.push(this.formatDurationColored(meta.duration, thresholdKey));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return metaParts;
|
|
386
|
+
}
|
|
235
387
|
|
|
388
|
+
/**
|
|
389
|
+
* Format a result line (indented child result)
|
|
390
|
+
* @param {string} statusText - Status text (e.g., "found", "not found")
|
|
391
|
+
* @param {Function} statusColor - Chalk color function for status
|
|
392
|
+
* @param {Object} meta - Metadata object
|
|
393
|
+
* @param {string} thresholdKey - Duration threshold key
|
|
394
|
+
* @returns {string} Formatted result line
|
|
395
|
+
*/
|
|
396
|
+
formatResultLine(statusText, statusColor, meta = {}, thresholdKey = "default") {
|
|
397
|
+
const parts = [];
|
|
398
|
+
this.addTimestamp(parts);
|
|
399
|
+
parts.push(this.getResultPrefix());
|
|
400
|
+
parts.push(statusColor(statusText));
|
|
401
|
+
|
|
402
|
+
const metaParts = this.buildResultMetaParts(meta, thresholdKey);
|
|
236
403
|
if (metaParts.length > 0) {
|
|
237
|
-
parts.push(
|
|
238
|
-
parts.push(metaParts.join(chalk.dim(" ยท ")));
|
|
404
|
+
parts.push(this.joinMetaParts(metaParts));
|
|
239
405
|
}
|
|
240
|
-
|
|
406
|
+
|
|
241
407
|
return parts.join(" ");
|
|
242
408
|
}
|
|
243
409
|
|
|
244
410
|
/**
|
|
245
|
-
* Format
|
|
411
|
+
* Format an element found message with AWESOME styling ๐ฏ
|
|
246
412
|
* @param {string} description - Element description
|
|
413
|
+
* @param {Object} meta - Element metadata (coordinates, duration, cache hit)
|
|
247
414
|
* @returns {string} Formatted message
|
|
248
415
|
*/
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
// Time and icon on same line
|
|
253
|
-
const timeStr = this.getElapsedTime();
|
|
254
|
-
if (timeStr) {
|
|
255
|
-
parts.push(chalk.dim(timeStr));
|
|
256
|
-
}
|
|
257
|
-
parts.push(this.getPrefix("findAll"));
|
|
416
|
+
formatElementFound(description, meta = {}) {
|
|
417
|
+
return this.formatResultLine("found", chalk.green, meta);
|
|
418
|
+
}
|
|
258
419
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
420
|
+
/**
|
|
421
|
+
* Format an element not found message with styling โ
|
|
422
|
+
* @param {string} description - Element description
|
|
423
|
+
* @param {Object} meta - Metadata (duration, error)
|
|
424
|
+
* @returns {string} Formatted message
|
|
425
|
+
*/
|
|
426
|
+
formatElementNotFound(description, meta = {}) {
|
|
427
|
+
return this.formatResultLine("not found", chalk.red, meta);
|
|
428
|
+
}
|
|
262
429
|
|
|
263
|
-
|
|
430
|
+
/**
|
|
431
|
+
* Format a finding all message (when search starts) ๐
|
|
432
|
+
* @param {string} description - Element description
|
|
433
|
+
* @returns {string} Formatted message
|
|
434
|
+
*/
|
|
435
|
+
formatElementsFinding(description) {
|
|
436
|
+
return this.formatFindingStyle("findAll", "Finding All", description);
|
|
264
437
|
}
|
|
265
438
|
|
|
266
439
|
/**
|
|
@@ -272,38 +445,20 @@ class SDKLogFormatter {
|
|
|
272
445
|
*/
|
|
273
446
|
formatElementsFound(description, count, meta = {}) {
|
|
274
447
|
const parts = [];
|
|
448
|
+
this.addTimestamp(parts);
|
|
449
|
+
parts.push(this.getResultPrefix());
|
|
450
|
+
parts.push(chalk.green(`found ${count} elements`));
|
|
275
451
|
|
|
276
|
-
// Time and icon on same line
|
|
277
|
-
const timeStr = this.getElapsedTime();
|
|
278
|
-
if (timeStr) {
|
|
279
|
-
parts.push(chalk.dim(timeStr));
|
|
280
|
-
}
|
|
281
|
-
parts.push(this.getPrefix("findAll"));
|
|
282
|
-
|
|
283
|
-
// Main message with emphasis
|
|
284
|
-
parts.push(chalk.bold.green("Found"));
|
|
285
|
-
parts.push(chalk.cyan(`${count}`));
|
|
286
|
-
parts.push(chalk.cyan(`"${description}"`));
|
|
287
|
-
|
|
288
|
-
// Metadata on same line with subtle styling
|
|
289
452
|
const metaParts = [];
|
|
290
|
-
if (meta.duration) {
|
|
291
|
-
const durationMs = parseInt(meta.duration);
|
|
292
|
-
const durationColor =
|
|
293
|
-
durationMs < 100
|
|
294
|
-
? chalk.green
|
|
295
|
-
: durationMs < 500
|
|
296
|
-
? chalk.yellow
|
|
297
|
-
: chalk.red;
|
|
298
|
-
metaParts.push(chalk.dim(`โฑ๏ธ ${durationColor(meta.duration)}`));
|
|
299
|
-
}
|
|
300
453
|
if (meta.cacheHit) {
|
|
301
454
|
metaParts.push(chalk.bold.yellow("โก cached"));
|
|
302
455
|
}
|
|
456
|
+
if (meta.duration) {
|
|
457
|
+
metaParts.push(this.formatDurationColored(meta.duration));
|
|
458
|
+
}
|
|
303
459
|
|
|
304
460
|
if (metaParts.length > 0) {
|
|
305
|
-
parts.push(
|
|
306
|
-
parts.push(metaParts.join(chalk.dim(" ยท ")));
|
|
461
|
+
parts.push(this.joinMetaParts(metaParts));
|
|
307
462
|
}
|
|
308
463
|
|
|
309
464
|
return parts.join(" ");
|
|
@@ -315,79 +470,134 @@ class SDKLogFormatter {
|
|
|
315
470
|
* @returns {string} Formatted message
|
|
316
471
|
*/
|
|
317
472
|
formatAsserting(assertion) {
|
|
473
|
+
return this.formatFindingStyle("assert", "Asserting", assertion);
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Format the assertion result as a subtask line
|
|
477
|
+
* @param {boolean} passed - Whether assertion passed
|
|
478
|
+
* @param {string} response - The AI response message
|
|
479
|
+
* @param {number} durationMs - Duration in milliseconds
|
|
480
|
+
* @returns {string} Formatted result line
|
|
481
|
+
*/
|
|
482
|
+
formatAssertResult(passed, response, durationMs) {
|
|
318
483
|
const parts = [];
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
if (
|
|
323
|
-
parts.push(chalk.
|
|
484
|
+
this.addTimestamp(parts);
|
|
485
|
+
parts.push(this.getResultPrefix());
|
|
486
|
+
|
|
487
|
+
if (passed) {
|
|
488
|
+
parts.push(chalk.green("passed"));
|
|
489
|
+
} else {
|
|
490
|
+
parts.push(chalk.red("failed"));
|
|
324
491
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
492
|
+
|
|
493
|
+
// Add the response message (trimmed)
|
|
494
|
+
if (response) {
|
|
495
|
+
const trimmedResponse = response.trim().split('\n')[0]; // First line only
|
|
496
|
+
parts.push(chalk.dim(trimmedResponse));
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Add duration
|
|
500
|
+
if (durationMs) {
|
|
501
|
+
parts.push(this.formatDurationColored(durationMs, "action"));
|
|
502
|
+
}
|
|
503
|
+
|
|
330
504
|
return parts.join(" ");
|
|
331
505
|
}
|
|
332
506
|
|
|
507
|
+
// Action color mapping (shared between formatAction and formatActionComplete)
|
|
508
|
+
static ACTION_COLORS = {
|
|
509
|
+
click: chalk.bold.cyan,
|
|
510
|
+
hover: chalk.bold.blue,
|
|
511
|
+
type: chalk.bold.yellow,
|
|
512
|
+
scroll: chalk.bold.magenta,
|
|
513
|
+
assert: chalk.bold.green,
|
|
514
|
+
wait: chalk.bold.yellow,
|
|
515
|
+
};
|
|
516
|
+
|
|
333
517
|
/**
|
|
334
|
-
*
|
|
518
|
+
* Build action message parts (shared logic for formatAction and formatActionComplete)
|
|
335
519
|
* @param {string} action - Action type
|
|
336
520
|
* @param {string} description - Description or target
|
|
337
|
-
* @
|
|
338
|
-
* @returns {string} Formatted message
|
|
521
|
+
* @returns {Array} Array of formatted parts
|
|
339
522
|
*/
|
|
340
|
-
|
|
523
|
+
buildActionParts(action, description) {
|
|
341
524
|
const parts = [];
|
|
525
|
+
this.addTimestamp(parts);
|
|
342
526
|
|
|
343
|
-
// Time and icon
|
|
344
|
-
const timeStr = this.getElapsedTime();
|
|
345
|
-
if (timeStr) {
|
|
346
|
-
parts.push(chalk.dim(timeStr));
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// Use action-specific prefix
|
|
350
527
|
const actionKey = action.toLowerCase().replace(/\s+/g, "");
|
|
351
528
|
parts.push(this.getPrefix(actionKey));
|
|
352
529
|
|
|
353
|
-
|
|
354
|
-
const
|
|
355
|
-
action.charAt(0).toUpperCase() + action.slice(1).toLowerCase();
|
|
356
|
-
const actionColors = {
|
|
357
|
-
click: chalk.bold.cyan,
|
|
358
|
-
hover: chalk.bold.blue,
|
|
359
|
-
type: chalk.bold.yellow,
|
|
360
|
-
scroll: chalk.bold.magenta,
|
|
361
|
-
assert: chalk.bold.green,
|
|
362
|
-
wait: chalk.bold.yellow,
|
|
363
|
-
};
|
|
364
|
-
const colorFn = actionColors[actionKey] || chalk.bold.white;
|
|
530
|
+
const actionText = action.charAt(0).toUpperCase() + action.slice(1).toLowerCase();
|
|
531
|
+
const colorFn = SDKLogFormatter.ACTION_COLORS[actionKey] || chalk.bold.white;
|
|
365
532
|
parts.push(colorFn(actionText));
|
|
366
533
|
|
|
367
|
-
// Target with color
|
|
368
534
|
if (description) {
|
|
369
535
|
parts.push(chalk.cyan(`"${description}"`));
|
|
370
536
|
}
|
|
371
537
|
|
|
372
|
-
|
|
538
|
+
return { parts, actionKey };
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Format an action message with AWESOME emojis! ๐ฌ
|
|
543
|
+
* @param {string} action - Action type
|
|
544
|
+
* @param {string} description - Description or target
|
|
545
|
+
* @param {Object} meta - Action metadata
|
|
546
|
+
* @returns {string} Formatted message
|
|
547
|
+
*/
|
|
548
|
+
formatAction(action, description, meta = {}) {
|
|
549
|
+
const { parts } = this.buildActionParts(action, description);
|
|
550
|
+
|
|
373
551
|
const metaParts = [];
|
|
374
552
|
if (meta.text) {
|
|
375
553
|
metaParts.push(chalk.gray(`โ ${chalk.white(meta.text)}`));
|
|
376
554
|
}
|
|
377
555
|
if (meta.duration) {
|
|
378
|
-
|
|
379
|
-
const durationColor =
|
|
380
|
-
durationMs < 50
|
|
381
|
-
? chalk.green
|
|
382
|
-
: durationMs < 200
|
|
383
|
-
? chalk.yellow
|
|
384
|
-
: chalk.red;
|
|
385
|
-
metaParts.push(chalk.dim(`โฑ๏ธ ${durationColor(meta.duration)}`));
|
|
556
|
+
metaParts.push(chalk.dim(`โฑ๏ธ ${this.formatDurationColored(meta.duration, "quickAction", false)}`));
|
|
386
557
|
}
|
|
387
558
|
|
|
388
559
|
if (metaParts.length > 0) {
|
|
389
|
-
parts.push(
|
|
390
|
-
|
|
560
|
+
parts.push(this.joinMetaParts(metaParts));
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return parts.join(" ");
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Format an action complete message with separate action and redraw durations ๐ฌ
|
|
568
|
+
* @param {string} action - Action type
|
|
569
|
+
* @param {string} description - Description or target
|
|
570
|
+
* @param {Object} meta - Action metadata
|
|
571
|
+
* @param {number} meta.actionDuration - Duration of the action itself in ms
|
|
572
|
+
* @param {number} meta.redrawDuration - Duration of the redraw wait in ms
|
|
573
|
+
* @param {boolean} meta.cacheHit - Whether cache was hit
|
|
574
|
+
* @returns {string} Formatted message
|
|
575
|
+
*/
|
|
576
|
+
formatActionComplete(action, description, meta = {}) {
|
|
577
|
+
const { parts } = this.buildActionParts(action, description);
|
|
578
|
+
|
|
579
|
+
const metaParts = [];
|
|
580
|
+
|
|
581
|
+
if (meta.actionDuration !== undefined) {
|
|
582
|
+
const durationMs = parseInt(meta.actionDuration);
|
|
583
|
+
const durationSec = (durationMs / 1000).toFixed(1) + 's';
|
|
584
|
+
const color = this.getDurationColor(durationMs, "action");
|
|
585
|
+
metaParts.push(chalk.dim(`โก ${color(durationSec)}`));
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (meta.redrawDuration !== undefined) {
|
|
589
|
+
const durationMs = parseInt(meta.redrawDuration);
|
|
590
|
+
const durationSec = (durationMs / 1000).toFixed(1) + 's';
|
|
591
|
+
const color = this.getDurationColor(durationMs, "redraw");
|
|
592
|
+
metaParts.push(chalk.dim(`๐ ${color(durationSec)}`));
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (meta.cacheHit) {
|
|
596
|
+
metaParts.push(chalk.bold.yellow("โก cached"));
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if (metaParts.length > 0) {
|
|
600
|
+
parts.push(this.joinMetaParts(metaParts));
|
|
391
601
|
}
|
|
392
602
|
|
|
393
603
|
return parts.join(" ");
|
|
@@ -402,12 +612,7 @@ class SDKLogFormatter {
|
|
|
402
612
|
*/
|
|
403
613
|
formatAssertion(assertion, passed, meta = {}) {
|
|
404
614
|
const parts = [];
|
|
405
|
-
|
|
406
|
-
// Time and icon
|
|
407
|
-
const timeStr = this.getElapsedTime();
|
|
408
|
-
if (timeStr) {
|
|
409
|
-
parts.push(chalk.dim(timeStr));
|
|
410
|
-
}
|
|
615
|
+
this.addTimestamp(parts);
|
|
411
616
|
|
|
412
617
|
if (passed) {
|
|
413
618
|
parts.push(this.getPrefix("success"));
|
|
@@ -424,15 +629,8 @@ class SDKLogFormatter {
|
|
|
424
629
|
}
|
|
425
630
|
|
|
426
631
|
if (meta.duration) {
|
|
427
|
-
const durationMs = parseInt(meta.duration);
|
|
428
|
-
const durationColor =
|
|
429
|
-
durationMs < 100
|
|
430
|
-
? chalk.green
|
|
431
|
-
: durationMs < 500
|
|
432
|
-
? chalk.yellow
|
|
433
|
-
: chalk.red;
|
|
434
632
|
parts.push(chalk.dim("ยท"));
|
|
435
|
-
parts.push(chalk.dim(`โฑ๏ธ ${
|
|
633
|
+
parts.push(chalk.dim(`โฑ๏ธ ${this.formatDurationColored(meta.duration, "action", false)}`));
|
|
436
634
|
}
|
|
437
635
|
|
|
438
636
|
return parts.join(" ");
|
|
@@ -446,12 +644,7 @@ class SDKLogFormatter {
|
|
|
446
644
|
*/
|
|
447
645
|
formatError(message, error) {
|
|
448
646
|
const parts = [];
|
|
449
|
-
|
|
450
|
-
const timeStr = this.getElapsedTime();
|
|
451
|
-
if (timeStr) {
|
|
452
|
-
parts.push(chalk.dim(timeStr));
|
|
453
|
-
}
|
|
454
|
-
|
|
647
|
+
this.addTimestamp(parts);
|
|
455
648
|
parts.push(this.getPrefix("error"));
|
|
456
649
|
parts.push(chalk.red.bold(message));
|
|
457
650
|
|
|
@@ -471,12 +664,7 @@ class SDKLogFormatter {
|
|
|
471
664
|
*/
|
|
472
665
|
formatConnection(type, meta = {}) {
|
|
473
666
|
const parts = [];
|
|
474
|
-
|
|
475
|
-
const timeStr = this.getElapsedTime();
|
|
476
|
-
if (timeStr) {
|
|
477
|
-
parts.push(chalk.dim(timeStr));
|
|
478
|
-
}
|
|
479
|
-
|
|
667
|
+
this.addTimestamp(parts);
|
|
480
668
|
parts.push(this.getPrefix(type));
|
|
481
669
|
|
|
482
670
|
if (type === "connect") {
|
|
@@ -503,12 +691,7 @@ class SDKLogFormatter {
|
|
|
503
691
|
*/
|
|
504
692
|
formatScreenshot(meta = {}) {
|
|
505
693
|
const parts = [];
|
|
506
|
-
|
|
507
|
-
const timeStr = this.getElapsedTime();
|
|
508
|
-
if (timeStr) {
|
|
509
|
-
parts.push(chalk.dim(timeStr));
|
|
510
|
-
}
|
|
511
|
-
|
|
694
|
+
this.addTimestamp(parts);
|
|
512
695
|
parts.push(this.getPrefix("screenshot"));
|
|
513
696
|
parts.push(chalk.bold.blue("Screenshot"));
|
|
514
697
|
|
|
@@ -533,7 +716,6 @@ class SDKLogFormatter {
|
|
|
533
716
|
*/
|
|
534
717
|
formatCacheStatus(hit, meta = {}) {
|
|
535
718
|
const parts = [];
|
|
536
|
-
|
|
537
719
|
parts.push(this.getPrefix(hit ? "cacheHit" : "cacheMiss"));
|
|
538
720
|
|
|
539
721
|
if (hit) {
|
|
@@ -565,9 +747,7 @@ class SDKLogFormatter {
|
|
|
565
747
|
const width = Math.min(60, Math.max(title.length + 4, 40));
|
|
566
748
|
const topLine = chalk.dim("โญ" + "โ".repeat(width - 2) + "โฎ");
|
|
567
749
|
const titleLine =
|
|
568
|
-
`${chalk.dim("โ")} ${emoji} ${chalk.bold.white(title)}`.padEnd(
|
|
569
|
-
width + 20,
|
|
570
|
-
) + chalk.dim("โ");
|
|
750
|
+
`${chalk.dim("โ")} ${emoji} ${chalk.bold.white(title)}`.padEnd(width + 20) + chalk.dim("โ");
|
|
571
751
|
const bottomLine = chalk.dim("โฐ" + "โ".repeat(width - 2) + "โฏ");
|
|
572
752
|
return `\n${topLine}\n${titleLine}\n${bottomLine}\n`;
|
|
573
753
|
}
|
|
@@ -605,8 +785,9 @@ class SDKLogFormatter {
|
|
|
605
785
|
parts.push(chalk.dim(`โฑ๏ธ ${stats.duration}`));
|
|
606
786
|
}
|
|
607
787
|
|
|
788
|
+
const divider = this.formatDivider();
|
|
608
789
|
const separator = chalk.dim(" โ ");
|
|
609
|
-
return `\n${
|
|
790
|
+
return `\n${divider}\n${parts.join(separator)}\n${divider}\n`;
|
|
610
791
|
}
|
|
611
792
|
|
|
612
793
|
/**
|
|
@@ -648,7 +829,6 @@ class SDKLogFormatter {
|
|
|
648
829
|
*/
|
|
649
830
|
formatWaiting(message, elapsed) {
|
|
650
831
|
const parts = [];
|
|
651
|
-
|
|
652
832
|
parts.push(this.getPrefix("wait"));
|
|
653
833
|
parts.push(chalk.bold.yellow("Waiting"));
|
|
654
834
|
parts.push(chalk.cyan(message));
|
|
@@ -691,14 +871,9 @@ class SDKLogFormatter {
|
|
|
691
871
|
|
|
692
872
|
if (duration) {
|
|
693
873
|
const seconds = (duration / 1000).toFixed(2);
|
|
694
|
-
const
|
|
695
|
-
duration < 1000
|
|
696
|
-
? chalk.green
|
|
697
|
-
: duration < 5000
|
|
698
|
-
? chalk.yellow
|
|
699
|
-
: chalk.red;
|
|
874
|
+
const color = this.getDurationColor(duration, "test");
|
|
700
875
|
parts.push(chalk.dim("ยท"));
|
|
701
|
-
parts.push(
|
|
876
|
+
parts.push(color(`${seconds}s`));
|
|
702
877
|
}
|
|
703
878
|
|
|
704
879
|
return `\n${parts.join(" ")}\n`;
|