testdriverai 6.2.0 → 6.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-tests.yml +2 -0
- package/.github/workflows/acceptance-v6.yml +2 -0
- package/.github/workflows/lint.yml +4 -1
- package/.github/workflows/publish-canary.yml +2 -0
- package/.github/workflows/publish-latest.yml +1 -0
- package/.github/workflows/self-hosted.yml +102 -0
- package/.prettierignore +1 -0
- package/.vscode/settings.json +4 -1
- package/agent/events.js +1 -10
- package/agent/index.js +98 -55
- package/agent/interface.js +43 -6
- package/agent/lib/censorship.js +15 -10
- package/agent/lib/commander.js +31 -18
- package/agent/lib/commands.js +62 -17
- package/agent/lib/debugger-server.js +0 -5
- package/agent/lib/generator.js +2 -2
- package/agent/lib/sdk.js +2 -1
- package/agent/lib/source-mapper.js +1 -1
- package/debugger/index.html +2 -2
- package/docs/account/enterprise.mdx +8 -12
- package/docs/account/pricing.mdx +2 -2
- package/docs/account/projects.mdx +5 -0
- package/docs/apps/tauri-apps.mdx +361 -0
- package/docs/cli/overview.mdx +6 -6
- package/docs/commands/assert.mdx +1 -0
- package/docs/commands/hover-text.mdx +3 -1
- package/docs/commands/match-image.mdx +5 -4
- package/docs/commands/press-keys.mdx +6 -8
- package/docs/commands/scroll-until-image.mdx +8 -7
- package/docs/commands/scroll-until-text.mdx +7 -6
- package/docs/commands/wait-for-image.mdx +5 -4
- package/docs/commands/wait-for-text.mdx +6 -5
- package/docs/docs.json +42 -40
- package/docs/getting-started/playwright.mdx +342 -0
- package/docs/getting-started/self-hosting.mdx +370 -0
- package/docs/getting-started/vscode.mdx +67 -56
- package/docs/guide/dashcam.mdx +118 -0
- package/docs/guide/environment-variables.mdx +5 -5
- package/docs/images/content/self-hosted/launchtemplateid.png +0 -0
- package/docs/images/content/vscode/ide-full.png +0 -0
- package/docs/images/content/vscode/running.png +0 -0
- package/docs/overview/comparison.mdx +22 -39
- package/docs/overview/quickstart.mdx +84 -32
- package/docs/styles.css +10 -1
- package/interfaces/cli/commands/generate.js +3 -0
- package/interfaces/cli/lib/base.js +27 -5
- package/interfaces/cli/utils/factory.js +17 -4
- package/interfaces/logger.js +4 -4
- package/interfaces/readline.js +1 -1
- package/package.json +3 -3
- package/schema.json +21 -0
- package/setup/aws/cloudformation.yaml +463 -0
- package/setup/aws/spawn-runner.sh +190 -0
- package/testdriver/acceptance/hover-text.yaml +2 -1
- package/testdriver/acceptance/prompt.yaml +4 -1
- package/testdriver/acceptance/scroll-until-image.yaml +5 -0
- package/testdriver/edge-cases/js-exception.yaml +8 -0
- package/testdriver/edge-cases/js-promise.yaml +19 -0
- package/testdriver/edge-cases/lifecycle/postrun.yaml +10 -0
- package/testdriver/edge-cases/success-test.yaml +9 -0
- package/testdriver/examples/web/lifecycle/postrun.yaml +7 -0
- package/testdriver/examples/web/lifecycle/{provision.yaml → prerun.yaml} +6 -0
- package/testdriver/lifecycle/postrun.yaml +7 -0
- package/testdriver/lifecycle/prerun.yaml +17 -0
package/agent/lib/commander.js
CHANGED
|
@@ -80,21 +80,21 @@ commands:
|
|
|
80
80
|
// this will actually interpret the command and execute it
|
|
81
81
|
switch (object.command) {
|
|
82
82
|
case "type":
|
|
83
|
-
emitter.emit(events.log.narration, `typing ${object.text}`);
|
|
84
83
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
84
|
+
emitter.emit(events.log.narration, `typing ${object.text}`);
|
|
85
85
|
response = await commands.type(object.text, object.delay);
|
|
86
86
|
break;
|
|
87
87
|
case "press-keys":
|
|
88
|
+
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
88
89
|
emitter.emit(
|
|
89
90
|
events.log.narration,
|
|
90
|
-
`pressing keys ${object.keys.join(",")}`,
|
|
91
|
+
`pressing keys: ${Array.isArray(object.keys) ? object.keys.join(", ") : object.keys}`,
|
|
91
92
|
);
|
|
92
|
-
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
93
93
|
response = await commands["press-keys"](object.keys);
|
|
94
94
|
break;
|
|
95
95
|
case "scroll":
|
|
96
|
-
emitter.emit(events.log.narration, `scrolling ${object.direction}`);
|
|
97
96
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
97
|
+
emitter.emit(events.log.narration, `scrolling ${object.direction}`);
|
|
98
98
|
response = await commands.scroll(
|
|
99
99
|
object.direction,
|
|
100
100
|
object.amount,
|
|
@@ -102,21 +102,21 @@ commands:
|
|
|
102
102
|
);
|
|
103
103
|
break;
|
|
104
104
|
case "wait":
|
|
105
|
+
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
105
106
|
emitter.emit(
|
|
106
107
|
events.log.narration,
|
|
107
108
|
`waiting ${object.timeout} seconds`,
|
|
108
109
|
);
|
|
109
|
-
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
110
110
|
response = await commands.wait(object.timeout);
|
|
111
111
|
break;
|
|
112
112
|
case "click":
|
|
113
|
-
emitter.emit(events.log.narration, `${object.action}`);
|
|
114
113
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
114
|
+
emitter.emit(events.log.narration, `${object.action}`);
|
|
115
115
|
response = await commands["click"](object.x, object.y, object.action);
|
|
116
116
|
break;
|
|
117
117
|
case "hover":
|
|
118
|
-
emitter.emit(events.log.narration, `moving mouse`);
|
|
119
118
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
119
|
+
emitter.emit(events.log.narration, `moving mouse`);
|
|
120
120
|
response = await commands["hover"](object.x, object.y);
|
|
121
121
|
break;
|
|
122
122
|
case "drag":
|
|
@@ -124,24 +124,25 @@ commands:
|
|
|
124
124
|
response = await commands["drag"](object.x, object.y);
|
|
125
125
|
break;
|
|
126
126
|
case "hover-text":
|
|
127
|
+
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
127
128
|
emitter.emit(
|
|
128
129
|
events.log.narration,
|
|
129
130
|
`searching for ${object.description}`,
|
|
130
131
|
);
|
|
131
|
-
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
132
132
|
response = await commands["hover-text"](
|
|
133
133
|
object.text,
|
|
134
134
|
object.description,
|
|
135
135
|
object.action,
|
|
136
136
|
object.method,
|
|
137
|
+
object.timeout,
|
|
137
138
|
);
|
|
138
139
|
break;
|
|
139
140
|
case "hover-image":
|
|
141
|
+
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
140
142
|
emitter.emit(
|
|
141
143
|
events.log.narration,
|
|
142
144
|
`searching for image of ${object.description}`,
|
|
143
145
|
);
|
|
144
|
-
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
145
146
|
response = await commands["hover-image"](
|
|
146
147
|
object.description,
|
|
147
148
|
object.action,
|
|
@@ -153,32 +154,38 @@ commands:
|
|
|
153
154
|
events.log.narration,
|
|
154
155
|
`${object.action} image ${object.path}`,
|
|
155
156
|
);
|
|
156
|
-
response = await commands["match-image"](
|
|
157
|
+
response = await commands["match-image"](
|
|
158
|
+
object.path,
|
|
159
|
+
object.action,
|
|
160
|
+
object.invert,
|
|
161
|
+
);
|
|
157
162
|
break;
|
|
158
163
|
case "wait-for-image":
|
|
164
|
+
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
159
165
|
emitter.emit(
|
|
160
166
|
events.log.narration,
|
|
161
167
|
`waiting for ${object.description}`,
|
|
162
168
|
);
|
|
163
|
-
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
164
169
|
response = await commands["wait-for-image"](
|
|
165
170
|
object.description,
|
|
166
171
|
object.timeout,
|
|
172
|
+
object.invert,
|
|
167
173
|
);
|
|
168
174
|
break;
|
|
169
175
|
case "wait-for-text":
|
|
170
|
-
emitter.emit(events.log.narration, `waiting for ${object.text}`);
|
|
171
176
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
177
|
+
emitter.emit(events.log.narration, `waiting for ${object.text}`);
|
|
172
178
|
copy.text = "*****";
|
|
173
179
|
response = await commands["wait-for-text"](
|
|
174
180
|
object.text,
|
|
175
181
|
object.timeout,
|
|
176
182
|
object.method,
|
|
183
|
+
object.invert,
|
|
177
184
|
);
|
|
178
185
|
break;
|
|
179
186
|
case "scroll-until-text":
|
|
180
|
-
emitter.emit(events.log.narration, `scrolling until ${object.text}`);
|
|
181
187
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
188
|
+
emitter.emit(events.log.narration, `scrolling until ${object.text}`);
|
|
182
189
|
copy.text = "*****";
|
|
183
190
|
response = await commands["scroll-until-text"](
|
|
184
191
|
object.text,
|
|
@@ -186,24 +193,26 @@ commands:
|
|
|
186
193
|
object.distance,
|
|
187
194
|
object.textMatchMethod,
|
|
188
195
|
object.method,
|
|
196
|
+
object.invert,
|
|
189
197
|
);
|
|
190
198
|
break;
|
|
191
199
|
case "scroll-until-image": {
|
|
192
200
|
const needle = object.description || object.path;
|
|
193
|
-
emitter.emit(events.log.narration, `scrolling until ${needle}`);
|
|
194
201
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
202
|
+
emitter.emit(events.log.narration, `scrolling until ${needle}`);
|
|
195
203
|
response = await commands["scroll-until-image"](
|
|
196
204
|
object.description,
|
|
197
205
|
object.direction,
|
|
198
206
|
object.distance,
|
|
199
207
|
object.method,
|
|
200
208
|
object.path,
|
|
209
|
+
object.invert,
|
|
201
210
|
);
|
|
202
211
|
break;
|
|
203
212
|
}
|
|
204
213
|
case "focus-application":
|
|
205
|
-
emitter.emit(events.log.narration, `focusing ${object.name}`);
|
|
206
214
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
215
|
+
emitter.emit(events.log.narration, `focusing ${object.name}`);
|
|
207
216
|
response = await commands["focus-application"](object.name);
|
|
208
217
|
break;
|
|
209
218
|
case "remember": {
|
|
@@ -214,12 +223,16 @@ commands:
|
|
|
214
223
|
break;
|
|
215
224
|
}
|
|
216
225
|
case "assert":
|
|
217
|
-
emitter.emit(events.log.narration, `asserting ${object.expect}`);
|
|
218
226
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
219
|
-
|
|
227
|
+
emitter.emit(events.log.narration, `asserting ${object.expect}`);
|
|
228
|
+
response = await commands.assert(
|
|
229
|
+
object.expect,
|
|
230
|
+
object.async,
|
|
231
|
+
object.invert,
|
|
232
|
+
);
|
|
233
|
+
|
|
220
234
|
break;
|
|
221
235
|
case "exec":
|
|
222
|
-
emitter.emit(events.log.narration, `exec`);
|
|
223
236
|
emitter.emit(
|
|
224
237
|
events.log.log,
|
|
225
238
|
generator.jsonToManual({
|
package/agent/lib/commands.js
CHANGED
|
@@ -170,20 +170,34 @@ const createCommands = (
|
|
|
170
170
|
return result;
|
|
171
171
|
};
|
|
172
172
|
|
|
173
|
-
const assert = async (
|
|
173
|
+
const assert = async (
|
|
174
|
+
assertion,
|
|
175
|
+
shouldThrow = false,
|
|
176
|
+
async = false,
|
|
177
|
+
invert = false,
|
|
178
|
+
) => {
|
|
174
179
|
if (async) {
|
|
175
180
|
shouldThrow = true;
|
|
176
181
|
}
|
|
177
182
|
|
|
178
183
|
const handleAssertResponse = (response) => {
|
|
179
|
-
emitter.emit(events.log.
|
|
184
|
+
emitter.emit(events.log.log, response);
|
|
185
|
+
|
|
186
|
+
let valid = response.indexOf("The task passed") > -1;
|
|
187
|
+
|
|
188
|
+
if (invert) {
|
|
189
|
+
valid = !valid;
|
|
190
|
+
}
|
|
180
191
|
|
|
181
|
-
if (
|
|
192
|
+
if (valid) {
|
|
182
193
|
return true;
|
|
183
194
|
} else {
|
|
184
195
|
if (shouldThrow) {
|
|
185
196
|
// Is fatal, othewise it just changes the assertion to be true
|
|
186
|
-
throw new MatchError(
|
|
197
|
+
throw new MatchError(
|
|
198
|
+
`AI Assertion failed ${invert && "(Inverted)"}`,
|
|
199
|
+
true,
|
|
200
|
+
);
|
|
187
201
|
} else {
|
|
188
202
|
return false;
|
|
189
203
|
}
|
|
@@ -357,11 +371,12 @@ const createCommands = (
|
|
|
357
371
|
description = null,
|
|
358
372
|
action = "click",
|
|
359
373
|
method = "turbo",
|
|
374
|
+
timeout = 5000, // we pass this to the subsequent wait-for-text block
|
|
360
375
|
) => {
|
|
361
376
|
text = text ? text.toString() : null;
|
|
362
377
|
|
|
363
378
|
// wait for the text to appear on screen
|
|
364
|
-
await commands["wait-for-text"](text,
|
|
379
|
+
await commands["wait-for-text"](text, timeout);
|
|
365
380
|
|
|
366
381
|
description = description ? description.toString() : null;
|
|
367
382
|
|
|
@@ -416,7 +431,7 @@ const createCommands = (
|
|
|
416
431
|
return response.data;
|
|
417
432
|
}
|
|
418
433
|
},
|
|
419
|
-
"match-image": async (relativePath, action = "click") => {
|
|
434
|
+
"match-image": async (relativePath, action = "click", invert = false) => {
|
|
420
435
|
// Resolve the image path relative to the current file
|
|
421
436
|
const resolvedPath = resolveRelativePath(relativePath);
|
|
422
437
|
|
|
@@ -424,6 +439,10 @@ const createCommands = (
|
|
|
424
439
|
|
|
425
440
|
let result = await findImageOnScreen(resolvedPath, image);
|
|
426
441
|
|
|
442
|
+
if (invert) {
|
|
443
|
+
result = !result;
|
|
444
|
+
}
|
|
445
|
+
|
|
427
446
|
if (!result) {
|
|
428
447
|
throw new CommandError(`Image not found: ${resolvedPath}`);
|
|
429
448
|
} else {
|
|
@@ -463,7 +482,7 @@ const createCommands = (
|
|
|
463
482
|
wait: async (timeout = 3000) => {
|
|
464
483
|
return await delay(timeout);
|
|
465
484
|
},
|
|
466
|
-
"wait-for-image": async (description, timeout = 10000) => {
|
|
485
|
+
"wait-for-image": async (description, timeout = 10000, invert = false) => {
|
|
467
486
|
emitter.emit(
|
|
468
487
|
events.log.narration,
|
|
469
488
|
theme.dim(
|
|
@@ -481,6 +500,7 @@ const createCommands = (
|
|
|
481
500
|
`An image matching the description "${description}" appears on screen.`,
|
|
482
501
|
false,
|
|
483
502
|
false,
|
|
503
|
+
invert,
|
|
484
504
|
);
|
|
485
505
|
|
|
486
506
|
durationPassed = new Date().getTime() - startTime;
|
|
@@ -511,7 +531,12 @@ const createCommands = (
|
|
|
511
531
|
);
|
|
512
532
|
}
|
|
513
533
|
},
|
|
514
|
-
"wait-for-text": async (
|
|
534
|
+
"wait-for-text": async (
|
|
535
|
+
text,
|
|
536
|
+
timeout = 5000,
|
|
537
|
+
method = "turbo",
|
|
538
|
+
invert = false,
|
|
539
|
+
) => {
|
|
515
540
|
await redraw.start();
|
|
516
541
|
|
|
517
542
|
emitter.emit(
|
|
@@ -541,7 +566,12 @@ const createCommands = (
|
|
|
541
566
|
);
|
|
542
567
|
|
|
543
568
|
passed = response.data;
|
|
569
|
+
|
|
570
|
+
if (invert) {
|
|
571
|
+
passed = !passed;
|
|
572
|
+
}
|
|
544
573
|
durationPassed = new Date().getTime() - startTime;
|
|
574
|
+
|
|
545
575
|
if (!passed) {
|
|
546
576
|
emitter.emit(
|
|
547
577
|
events.log.narration,
|
|
@@ -569,6 +599,7 @@ const createCommands = (
|
|
|
569
599
|
maxDistance = 10000,
|
|
570
600
|
textMatchMethod = "turbo",
|
|
571
601
|
method = "keyboard",
|
|
602
|
+
invert = false,
|
|
572
603
|
) => {
|
|
573
604
|
await redraw.start();
|
|
574
605
|
|
|
@@ -616,6 +647,11 @@ const createCommands = (
|
|
|
616
647
|
);
|
|
617
648
|
|
|
618
649
|
passed = response.data;
|
|
650
|
+
|
|
651
|
+
if (invert) {
|
|
652
|
+
passed = !passed;
|
|
653
|
+
}
|
|
654
|
+
|
|
619
655
|
if (!passed) {
|
|
620
656
|
emitter.emit(
|
|
621
657
|
events.log.narration,
|
|
@@ -644,6 +680,7 @@ const createCommands = (
|
|
|
644
680
|
maxDistance = 10000,
|
|
645
681
|
method = "keyboard",
|
|
646
682
|
path,
|
|
683
|
+
invert = false,
|
|
647
684
|
) => {
|
|
648
685
|
const needle = description || path;
|
|
649
686
|
|
|
@@ -673,6 +710,7 @@ const createCommands = (
|
|
|
673
710
|
`An image matching the description "${description}" appears on screen.`,
|
|
674
711
|
false,
|
|
675
712
|
false,
|
|
713
|
+
invert,
|
|
676
714
|
);
|
|
677
715
|
}
|
|
678
716
|
|
|
@@ -726,8 +764,10 @@ const createCommands = (
|
|
|
726
764
|
});
|
|
727
765
|
return result.data;
|
|
728
766
|
},
|
|
729
|
-
assert: async (assertion, async = false) => {
|
|
730
|
-
|
|
767
|
+
assert: async (assertion, async = false, invert = false) => {
|
|
768
|
+
let response = await assert(assertion, true, async, invert);
|
|
769
|
+
|
|
770
|
+
return response;
|
|
731
771
|
},
|
|
732
772
|
exec: async (language = "pwsh", code, timeout, silent = false) => {
|
|
733
773
|
emitter.emit(events.log.narration, theme.dim(`calling exec...`), true);
|
|
@@ -753,14 +793,14 @@ const createCommands = (
|
|
|
753
793
|
`Command failed with exit code ${result.out.returncode}: ${result.out.stderr}`,
|
|
754
794
|
);
|
|
755
795
|
} else {
|
|
756
|
-
if (!silent) {
|
|
757
|
-
emitter.emit(events.log.log, theme.dim(`
|
|
796
|
+
if (!silent && result.out?.stdout) {
|
|
797
|
+
emitter.emit(events.log.log, theme.dim(`stdout:`), true);
|
|
758
798
|
emitter.emit(events.log.log, `${result.out.stdout}`, true);
|
|
799
|
+
}
|
|
759
800
|
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
}
|
|
801
|
+
if (!silent && result.out.stderr) {
|
|
802
|
+
emitter.emit(events.log.log, theme.dim(`stderr:`), true);
|
|
803
|
+
emitter.emit(events.log.log, `${result.out.stderr}`, true);
|
|
764
804
|
}
|
|
765
805
|
|
|
766
806
|
return result.out?.stdout?.trim();
|
|
@@ -792,7 +832,12 @@ const createCommands = (
|
|
|
792
832
|
try {
|
|
793
833
|
await script.runInNewContext(context);
|
|
794
834
|
} catch (e) {
|
|
795
|
-
console.error
|
|
835
|
+
// Log the error to the emitter instead of console.error to maintain consistency
|
|
836
|
+
emitter.emit(
|
|
837
|
+
events.log.debug,
|
|
838
|
+
`JavaScript execution error: ${e.message}`,
|
|
839
|
+
);
|
|
840
|
+
// Wait a tick to allow any promise rejections to be handled
|
|
796
841
|
throw new CommandError(`Error running script: ${e.message}`);
|
|
797
842
|
}
|
|
798
843
|
|
|
@@ -65,11 +65,6 @@ function createDebuggerServer(config = {}) {
|
|
|
65
65
|
|
|
66
66
|
ws.on("close", () => {
|
|
67
67
|
clients.delete(ws);
|
|
68
|
-
|
|
69
|
-
// If no clients connected, we can optionally shut down
|
|
70
|
-
if (clients.size === 0) {
|
|
71
|
-
console.log("No clients connected, keeping server alive");
|
|
72
|
-
}
|
|
73
68
|
});
|
|
74
69
|
|
|
75
70
|
ws.on("error", (error) => {
|
package/agent/lib/generator.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// parses markdown content to find code blocks, and then extracts yaml from those code blocks
|
|
2
2
|
const yaml = require("js-yaml");
|
|
3
|
-
const
|
|
3
|
+
const pkg = require("../../package.json");
|
|
4
4
|
const session = require("./session");
|
|
5
5
|
const theme = require("./theme");
|
|
6
6
|
// do the actual parsing
|
|
@@ -56,7 +56,7 @@ const jsonToManual = function (json, colors = true) {
|
|
|
56
56
|
const dumpToYML = async function (inputArray, sessionInstance = null) {
|
|
57
57
|
// use yml dump to convert json to yml
|
|
58
58
|
let yml = await yaml.dump({
|
|
59
|
-
version:
|
|
59
|
+
version: pkg.version,
|
|
60
60
|
session: sessionInstance ? sessionInstance.get() : session.get(),
|
|
61
61
|
steps: inputArray,
|
|
62
62
|
});
|
package/agent/lib/sdk.js
CHANGED
|
@@ -95,7 +95,7 @@ const createSDK = (emitter, config, sessionInstance) => {
|
|
|
95
95
|
return token;
|
|
96
96
|
} catch (error) {
|
|
97
97
|
outputError(error);
|
|
98
|
-
|
|
98
|
+
throw error; // Re-throw the error so calling code can handle it properly
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
};
|
|
@@ -194,6 +194,7 @@ const createSDK = (emitter, config, sessionInstance) => {
|
|
|
194
194
|
return value;
|
|
195
195
|
} catch (error) {
|
|
196
196
|
outputError(error);
|
|
197
|
+
throw error; // Re-throw the error so calling code can handle it properly
|
|
197
198
|
}
|
|
198
199
|
};
|
|
199
200
|
|
|
@@ -263,7 +263,7 @@ class SourceMapper {
|
|
|
263
263
|
let description = `${fileName}:${(sourcePosition.step.startLine || 0) + 1}`;
|
|
264
264
|
|
|
265
265
|
if (sourcePosition.command) {
|
|
266
|
-
description += `:${(sourcePosition.command.startLine || 0) + 1}
|
|
266
|
+
description += `:${(sourcePosition.command.startLine || 0) + 1}`;
|
|
267
267
|
} else {
|
|
268
268
|
description += ` (step ${sourcePosition.step.stepIndex + 1})`;
|
|
269
269
|
}
|
package/debugger/index.html
CHANGED
|
@@ -330,7 +330,7 @@
|
|
|
330
330
|
<div class="message">Click to interact with VM</div>
|
|
331
331
|
</div>
|
|
332
332
|
</div>
|
|
333
|
-
<iframe id="vm-iframe" src=""></iframe>
|
|
333
|
+
<iframe id="vm-iframe" src="" credentialless></iframe>
|
|
334
334
|
</div>
|
|
335
335
|
|
|
336
336
|
<script>
|
|
@@ -340,7 +340,7 @@
|
|
|
340
340
|
let parsedData;
|
|
341
341
|
if (data) {
|
|
342
342
|
try {
|
|
343
|
-
parsedData = JSON.parse(
|
|
343
|
+
parsedData = JSON.parse(atob(data));
|
|
344
344
|
console.log("Data from URL:", parsedData);
|
|
345
345
|
// You can use parsedData here if needed
|
|
346
346
|
} catch (error) {
|
|
@@ -24,15 +24,15 @@ We specialize in testing scenarios that other tools can't handle - desktop appli
|
|
|
24
24
|
|
|
25
25
|
## Enterprise Plans
|
|
26
26
|
|
|
27
|
-
TestDriver Enterprise plans start at **$
|
|
27
|
+
TestDriver Enterprise plans start at **$2,000/month** and include:
|
|
28
28
|
|
|
29
|
+
- A pilot program with our expert team to create 4 custom tests within your first 4 weeks (4x4 Guarantee).
|
|
30
|
+
- 4 parallel tests
|
|
29
31
|
- **12,500 runner minutes per month**: Sufficient capacity for continuous testing of your custom test suite.
|
|
30
|
-
- Enterprise-grade test dashboards with advanced analytics.
|
|
31
32
|
- Full CI/CD pipeline integration with custom configurations.
|
|
32
33
|
- Dedicated infrastructure and ongoing support for complex testing scenarios.
|
|
33
|
-
- Expert test creation and maintenance services.
|
|
34
34
|
|
|
35
|
-
For detailed pricing and contract information
|
|
35
|
+
For detailed pricing and contract information our [Pricing](/account/pricing) page. Want unlimited minutes or enhanced security? We also support self-hosted options with 16 parallel tests starting at $2,000/month. See our [Self Hosting](/getting-started/self-hosting) docs for more info.
|
|
36
36
|
|
|
37
37
|
<CardGroup cols={3}>
|
|
38
38
|
<Card title="Custom Desktop & Extension Testing">
|
|
@@ -66,17 +66,14 @@ TestDriver Enterprise provides comprehensive support for fast-moving teams with
|
|
|
66
66
|
|
|
67
67
|
Testing complex applications requires more than standard automation tools. Desktop applications, browser extensions, and multi-platform workflows demand specialized infrastructure, custom integrations, and deep technical expertise. TestDriver Enterprise provides the complete solution - from initial setup through ongoing maintenance and support.
|
|
68
68
|
|
|
69
|
-
For more details, see [Contract Details](#contract-details).
|
|
70
|
-
|
|
71
69
|
---
|
|
72
70
|
|
|
73
71
|
## Implementation Process
|
|
74
72
|
|
|
75
73
|
1. **Initial Consultation**: Discuss your specific testing challenges, application architecture, and infrastructure requirements.
|
|
76
|
-
2. **
|
|
77
|
-
3. **
|
|
78
|
-
4. **
|
|
79
|
-
5. **Team Training & Ongoing Support**: Comprehensive training for your team plus ongoing technical support and consultation.
|
|
74
|
+
2. **Expert Test Development**: Our team develops 4 custom tests designed specifically for your application's critical user flows and business logic.
|
|
75
|
+
3. **Integration & Deployment**: Implement tests within your CI/CD pipeline with custom monitoring and reporting configurations.
|
|
76
|
+
4. **Team Training & Ongoing Support**: Comprehensive training for your team plus ongoing technical support and consultation.
|
|
80
77
|
|
|
81
78
|
Complex applications - particularly desktop software, browser extensions, and multi-platform workflows - present unique testing challenges that require specialized infrastructure and deep technical expertise. TestDriver Enterprise addresses these challenges with custom solutions designed specifically for your application and development process.
|
|
82
79
|
|
|
@@ -86,7 +83,6 @@ Complex applications - particularly desktop software, browser extensions, and mu
|
|
|
86
83
|
|
|
87
84
|
| Service | Timeline | Description |
|
|
88
85
|
| --------------------------------- | ------------- | ------------------------------------------------------------------------------------------------ |
|
|
89
|
-
| **Infrastructure Design** | First 7 Days | Analysis and configuration of specialized testing environments for your application stack. |
|
|
90
86
|
| **Requirements Analysis** | First 7 Days | Comprehensive review of testing requirements and technical specifications. |
|
|
91
87
|
| **Custom Test Development** | First 4 Weeks | Expert creation of 4 fully customized tests (4x4 Guarantee) tailored to your critical workflows. |
|
|
92
88
|
| **Training & Knowledge Transfer** | First 30 Days | Technical training for your team and establishment of ongoing support processes. |
|
|
@@ -103,7 +99,7 @@ Complex applications - particularly desktop software, browser extensions, and mu
|
|
|
103
99
|
- **Service Level**: Dedicated support team and technical consultation included.
|
|
104
100
|
- **Usage Tracking**: Monthly runner minute allocation with standard overage rates.
|
|
105
101
|
- **Custom Infrastructure**: Specialized testing environments included for complex applications.
|
|
106
|
-
- **Enterprise Options**:
|
|
102
|
+
- **Enterprise Options**: [Self hosting](/getting-started/self-hosting) configurations available.
|
|
107
103
|
|
|
108
104
|
---
|
|
109
105
|
|
package/docs/account/pricing.mdx
CHANGED
|
@@ -19,12 +19,12 @@ TestDriver offers a range of pricing plans to suit different needs, from individ
|
|
|
19
19
|
</Card>
|
|
20
20
|
<Card title="Enterprise" icon="shield">
|
|
21
21
|
Need advanced features? Contact us for tailored solutions. Starting at
|
|
22
|
-
$
|
|
22
|
+
$2,000/month.
|
|
23
23
|
</Card>
|
|
24
24
|
</CardGroup>
|
|
25
25
|
|
|
26
26
|
<Tip>
|
|
27
|
-
Every plan
|
|
27
|
+
Every plan comes with access to the Playwright SDK to get you off the starting
|
|
28
28
|
line!
|
|
29
29
|
</Tip>
|
|
30
30
|
|
|
@@ -23,6 +23,11 @@ From the Project view, you can see all the replays (Dashes) stored for that proj
|
|
|
23
23
|
|
|
24
24
|
When you create a new Project, you can also enable the <Icon icon="jira" /> Jira integration to create issues automatically each time a replay (Dash) is created.
|
|
25
25
|
|
|
26
|
+
<Info>
|
|
27
|
+
The project ID can be used in conjunction with your `lifecycle/postrun.yaml`
|
|
28
|
+
script to automatically assign a replay to a project. For more info see the
|
|
29
|
+
(Dashcam section)[/guide/dashcam].
|
|
30
|
+
</Info>
|
|
26
31
|
<Frame caption="Click a Project to view its replays">
|
|
27
32
|
<img src="/images/content/account/newprojectsettings.png" />
|
|
28
33
|
</Frame>
|