testdriverai 5.2.2 → 5.3.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/.github/workflows/test-install.yml +1 -1
- package/README.md +5 -11
- package/agent.js +135 -99
- package/docs/30x30.mdx +84 -0
- package/docs/action/browser.mdx +129 -0
- package/docs/action/os.mdx +157 -0
- package/docs/action/output.mdx +98 -0
- package/docs/action/performance.mdx +71 -0
- package/docs/action/prerun.mdx +80 -0
- package/docs/action/secrets.mdx +103 -0
- package/docs/action/setup.mdx +115 -0
- package/docs/bugs/jira.mdx +208 -0
- package/docs/cli/overview.mdx +65 -0
- package/docs/commands/assert.mdx +31 -0
- package/docs/commands/exec.mdx +42 -0
- package/docs/commands/focus-application.mdx +29 -0
- package/docs/commands/hover-image.mdx +32 -0
- package/docs/commands/hover-text.mdx +37 -0
- package/docs/commands/if.mdx +43 -0
- package/docs/commands/match-image.mdx +41 -0
- package/docs/commands/press-keys.mdx +30 -0
- package/docs/commands/run.mdx +30 -0
- package/docs/commands/scroll-until-image.mdx +33 -0
- package/docs/commands/scroll-until-text.mdx +37 -0
- package/docs/commands/scroll.mdx +33 -0
- package/docs/commands/type.mdx +29 -0
- package/docs/commands/wait-for-image.mdx +31 -0
- package/docs/commands/wait-for-text.mdx +35 -0
- package/docs/commands/wait.mdx +30 -0
- package/docs/docs.json +226 -0
- package/docs/exporting/playwright.mdx +159 -0
- package/docs/features/auto-healing.mdx +124 -0
- package/docs/features/cross-platform.mdx +106 -0
- package/docs/features/generation.mdx +180 -0
- package/docs/features/github.mdx +161 -0
- package/docs/features/parallel-testing.mdx +130 -0
- package/docs/features/reusable-snippets.mdx +124 -0
- package/docs/features/selectorless.mdx +62 -0
- package/docs/features/visual-assertions.mdx +123 -0
- package/docs/getting-started/ci.mdx +196 -0
- package/docs/getting-started/generating.mdx +210 -0
- package/docs/getting-started/running.mdx +67 -0
- package/docs/getting-started/setup.mdx +133 -0
- package/docs/getting-started/writing.mdx +99 -0
- package/docs/guide/assertions.mdx +195 -0
- package/docs/guide/authentication.mdx +150 -0
- package/docs/guide/code.mdx +169 -0
- package/docs/guide/locating.mdx +136 -0
- package/docs/guide/setup-teardown.mdx +161 -0
- package/docs/guide/variables.mdx +218 -0
- package/docs/guide/waiting.mdx +199 -0
- package/docs/importing/csv.mdx +196 -0
- package/docs/importing/gherkin.mdx +142 -0
- package/docs/importing/jira.mdx +172 -0
- package/docs/importing/testrail.mdx +161 -0
- package/docs/integrations/electron.mdx +152 -0
- package/docs/integrations/netlify.mdx +98 -0
- package/docs/integrations/vercel.mdx +177 -0
- package/docs/interactive/assert.mdx +51 -0
- package/docs/interactive/generate.mdx +41 -0
- package/docs/interactive/run.mdx +36 -0
- package/docs/interactive/save.mdx +53 -0
- package/docs/interactive/undo.mdx +47 -0
- package/docs/issues.mdx +9 -0
- package/docs/overview/comparison.mdx +82 -0
- package/docs/overview/faq.mdx +122 -0
- package/docs/overview/quickstart.mdx +66 -0
- package/docs/overview/what-is-testdriver.mdx +73 -0
- package/docs/quickstart.mdx +66 -0
- package/docs/reference/commands/scroll.mdx +0 -0
- package/docs/reference/interactive/assert.mdx +0 -0
- package/docs/security/action.mdx +62 -0
- package/docs/security/agent.mdx +62 -0
- package/docs/security/dashboard.mdx +0 -0
- package/docs/security/platform.mdx +54 -0
- package/docs/tutorials/advanced-test.mdx +79 -0
- package/docs/tutorials/basic-test.mdx +41 -0
- package/electron/icon.png +0 -0
- package/electron/overlay.html +7 -3
- package/electron/overlay.js +75 -15
- package/electron/tray-buffered.png +0 -0
- package/electron/tray.png +0 -0
- package/index.js +75 -34
- package/lib/commander.js +22 -1
- package/lib/commands.js +87 -19
- package/lib/config.js +10 -1
- package/lib/focus-application.js +30 -23
- package/lib/generator.js +58 -7
- package/lib/init.js +48 -19
- package/lib/ipc.js +50 -0
- package/lib/logger.js +19 -6
- package/lib/overlay.js +82 -36
- package/lib/parser.js +9 -7
- package/lib/resources/prerun.yaml +17 -0
- package/lib/sandbox.js +2 -3
- package/lib/sdk.js +0 -2
- package/lib/session.js +3 -1
- package/lib/speak.js +0 -2
- package/lib/subimage/opencv.js +0 -4
- package/lib/system.js +56 -39
- package/lib/upload-secrets.js +65 -0
- package/lib/validation.js +175 -0
- package/package.json +2 -1
- package/postinstall.js +0 -24
- package/lib/websockets.js +0 -85
- package/test.md +0 -8
- package/test.yml +0 -18
package/lib/system.js
CHANGED
|
@@ -19,15 +19,14 @@ if (!config.TD_VM) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const screenshot = async (options) => {
|
|
22
|
-
|
|
23
22
|
if (config.TD_VM) {
|
|
24
|
-
let {base64} = await sandbox.send({type:
|
|
25
|
-
let image = Buffer.from(base64,
|
|
23
|
+
let { base64 } = await sandbox.send({ type: "screenshot" });
|
|
24
|
+
let image = Buffer.from(base64, "base64");
|
|
26
25
|
fs.writeFileSync(options.filename, image);
|
|
27
26
|
} else {
|
|
28
27
|
return await scshotdesk({ filename: options.filename, format: "png" });
|
|
29
28
|
}
|
|
30
|
-
}
|
|
29
|
+
};
|
|
31
30
|
|
|
32
31
|
let primaryDisplay = null;
|
|
33
32
|
|
|
@@ -35,26 +34,20 @@ let primaryDisplay = null;
|
|
|
35
34
|
// this is the only display we ever target, because fuck it
|
|
36
35
|
// the vm only has one and most people only have one
|
|
37
36
|
const getPrimaryDisplay = async () => {
|
|
38
|
-
|
|
39
37
|
if (!primaryDisplay) {
|
|
40
|
-
|
|
41
38
|
// calculate scaling resolution
|
|
42
39
|
let graphics = await si.graphics();
|
|
43
|
-
primaryDisplay = graphics.displays.find(
|
|
44
|
-
(display) => display.main == true,
|
|
45
|
-
);
|
|
46
|
-
|
|
40
|
+
primaryDisplay = graphics.displays.find((display) => display.main == true);
|
|
47
41
|
}
|
|
48
|
-
|
|
42
|
+
|
|
49
43
|
return primaryDisplay;
|
|
50
|
-
|
|
51
44
|
};
|
|
52
45
|
|
|
53
46
|
const getSystemInformationOsInfo = async () => {
|
|
54
47
|
if (config.TD_VM) {
|
|
55
48
|
return {
|
|
56
|
-
os:
|
|
57
|
-
}
|
|
49
|
+
os: "linux",
|
|
50
|
+
};
|
|
58
51
|
} else {
|
|
59
52
|
return await si.osInfo();
|
|
60
53
|
}
|
|
@@ -68,7 +61,6 @@ const tmpFilename = () => {
|
|
|
68
61
|
|
|
69
62
|
const captureAndResize = async (scale = 1, silent = false, mouse = false) => {
|
|
70
63
|
try {
|
|
71
|
-
|
|
72
64
|
if (!silent) {
|
|
73
65
|
emitter.emit(events.screenCapture.start, {
|
|
74
66
|
scale,
|
|
@@ -82,35 +74,32 @@ const captureAndResize = async (scale = 1, silent = false, mouse = false) => {
|
|
|
82
74
|
|
|
83
75
|
await screenshot({ filename: step1, format: "png" });
|
|
84
76
|
|
|
77
|
+
// Location of cursor image
|
|
78
|
+
const cursorPath = path.join(__dirname, "resources", "cursor.png");
|
|
79
|
+
|
|
85
80
|
let sharpInstance;
|
|
86
|
-
|
|
81
|
+
const mousePos = await getMousePosition();
|
|
87
82
|
|
|
83
|
+
if (config.TD_VM) {
|
|
88
84
|
// resize to 1:1 px ratio
|
|
89
85
|
sharpInstance = sharp(step1).resize(
|
|
90
|
-
Math.floor(
|
|
91
|
-
Math.floor(
|
|
86
|
+
Math.floor(config.TD_VM_RESOLUTION[0] * scale),
|
|
87
|
+
Math.floor(config.TD_VM_RESOLUTION[1] * scale),
|
|
92
88
|
);
|
|
93
|
-
|
|
94
89
|
} else {
|
|
95
|
-
|
|
96
90
|
const primaryDisplay = await getPrimaryDisplay();
|
|
97
|
-
|
|
91
|
+
|
|
98
92
|
sharpInstance = sharp(step1).resize(
|
|
99
93
|
Math.floor(primaryDisplay.currentResX * scale),
|
|
100
94
|
Math.floor(primaryDisplay.currentResY * scale),
|
|
101
95
|
);
|
|
96
|
+
}
|
|
102
97
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (mouse) {
|
|
110
|
-
// composite the mouse image ontop
|
|
111
|
-
sharpInstance.composite([{ input: cursorPath, left: mousePos.x, top: mousePos.y }]);
|
|
112
|
-
}
|
|
113
|
-
|
|
98
|
+
if (mouse) {
|
|
99
|
+
// composite the mouse image ontop
|
|
100
|
+
sharpInstance.composite([
|
|
101
|
+
{ input: cursorPath, left: mousePos.x, top: mousePos.y },
|
|
102
|
+
]);
|
|
114
103
|
}
|
|
115
104
|
|
|
116
105
|
await sharpInstance.toFile(step2);
|
|
@@ -134,7 +123,11 @@ const captureAndResize = async (scale = 1, silent = false, mouse = false) => {
|
|
|
134
123
|
};
|
|
135
124
|
|
|
136
125
|
// our handy screenshot function
|
|
137
|
-
const captureScreenBase64 = async (
|
|
126
|
+
const captureScreenBase64 = async (
|
|
127
|
+
scale = 1,
|
|
128
|
+
silent = false,
|
|
129
|
+
mouse = false,
|
|
130
|
+
) => {
|
|
138
131
|
let step2 = await captureAndResize(scale, silent, mouse);
|
|
139
132
|
return fs.readFileSync(step2, "base64");
|
|
140
133
|
};
|
|
@@ -145,6 +138,11 @@ const captureScreenPNG = async (scale = 1, silent = false, mouse = false) => {
|
|
|
145
138
|
|
|
146
139
|
const platform = () => {
|
|
147
140
|
let platform = process.platform;
|
|
141
|
+
|
|
142
|
+
if (config.TD_VM) {
|
|
143
|
+
platform = "linux";
|
|
144
|
+
}
|
|
145
|
+
|
|
148
146
|
if (platform === "darwin") {
|
|
149
147
|
platform = "mac";
|
|
150
148
|
} else if (platform === "win32") {
|
|
@@ -161,7 +159,10 @@ const platform = () => {
|
|
|
161
159
|
let activeWindowFn = null;
|
|
162
160
|
const initializeActiveWindow = async () => {
|
|
163
161
|
if (!activeWindowFn && !config.TD_VM) {
|
|
164
|
-
|
|
162
|
+
// For some reason, the import fails in certain situations
|
|
163
|
+
const activeWindow = await import("get-windows")
|
|
164
|
+
.then(({ activeWindow }) => activeWindow)
|
|
165
|
+
.catch(() => null);
|
|
165
166
|
activeWindowFn = activeWindow;
|
|
166
167
|
}
|
|
167
168
|
return activeWindowFn;
|
|
@@ -169,18 +170,34 @@ const initializeActiveWindow = async () => {
|
|
|
169
170
|
|
|
170
171
|
// this is the focused window
|
|
171
172
|
const activeWin = async () => {
|
|
172
|
-
|
|
173
|
-
if (config.TD_VM) {
|
|
173
|
+
if (config.TD_VM) {
|
|
174
174
|
return "error getting active window, proceed normally";
|
|
175
175
|
} else {
|
|
176
176
|
const activeWindow = await initializeActiveWindow();
|
|
177
|
-
return await activeWindow();
|
|
177
|
+
return await activeWindow?.();
|
|
178
178
|
}
|
|
179
179
|
};
|
|
180
180
|
|
|
181
181
|
const getMousePosition = async () => {
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
if (config.TD_VM) {
|
|
183
|
+
// Get Mouse Position from command line
|
|
184
|
+
let response = await sandbox.send({
|
|
185
|
+
type: "commands.run",
|
|
186
|
+
command:
|
|
187
|
+
'eval $(xdotool getmouselocation --shell) && echo "{\\"x\\":$X,\\"y\\":$Y}"',
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
if (response.out.stderr) {
|
|
191
|
+
return {
|
|
192
|
+
x: 0,
|
|
193
|
+
y: 0,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return JSON.parse(response.out.stdout);
|
|
198
|
+
} else {
|
|
199
|
+
return await robot.getMousePos();
|
|
200
|
+
}
|
|
184
201
|
};
|
|
185
202
|
|
|
186
203
|
module.exports = {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const { execSync } = require('child_process');
|
|
3
|
+
const dotenv = require('dotenv');
|
|
4
|
+
const prompts = require('prompts');
|
|
5
|
+
|
|
6
|
+
module.exports = async () => {
|
|
7
|
+
|
|
8
|
+
// 1. Check for GitHub CLI
|
|
9
|
+
try {
|
|
10
|
+
execSync('gh --version', { stdio: 'ignore' });
|
|
11
|
+
} catch (err) {
|
|
12
|
+
console.log(err)
|
|
13
|
+
console.error('❌ GitHub CLI (gh) is not installed.\n');
|
|
14
|
+
console.log('👉 Install it from https://cli.github.com/');
|
|
15
|
+
console.log(' macOS: brew install gh');
|
|
16
|
+
console.log(' Windows: winget install GitHub.cli');
|
|
17
|
+
console.log(' Ubuntu: sudo apt install gh\n');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// 2. Load and parse .env file
|
|
22
|
+
const envPath = '.env';
|
|
23
|
+
if (!fs.existsSync(envPath)) {
|
|
24
|
+
console.error('❌ .env file not found.');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const env = dotenv.parse(fs.readFileSync(envPath));
|
|
29
|
+
const tdSecrets = Object.entries(env).filter(([key]) => key.startsWith('TD_'));
|
|
30
|
+
|
|
31
|
+
if (tdSecrets.length === 0) {
|
|
32
|
+
console.log('ℹ️ No secrets found in .env that start with TD_');
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 3. Prompt user for confirmation
|
|
37
|
+
(async () => {
|
|
38
|
+
console.log('\n🔐 The following TD_ secrets will be uploaded:');
|
|
39
|
+
tdSecrets.forEach(([key]) => console.log(`- ${key}`));
|
|
40
|
+
|
|
41
|
+
const response = await prompts({
|
|
42
|
+
type: 'confirm',
|
|
43
|
+
name: 'confirm',
|
|
44
|
+
message: '\nAre you sure you want to upload these secrets to the current GitHub repo?',
|
|
45
|
+
initial: false,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (!response.confirm) {
|
|
49
|
+
console.log('❌ Upload cancelled.');
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 4. Upload secrets using GitHub CLI
|
|
54
|
+
for (const [key, value] of tdSecrets) {
|
|
55
|
+
try {
|
|
56
|
+
const cmd = `gh secret set ${key} --body "${value}"`;
|
|
57
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.error(`❌ Failed to upload ${key}: ${err.message}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
})();
|
|
64
|
+
|
|
65
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
const types = () =>
|
|
2
|
+
import("arktype").then(({ scope }) =>
|
|
3
|
+
scope({
|
|
4
|
+
// - command: press-keys # Types a keyboard combination. Repeat the command to repeat the keypress.
|
|
5
|
+
// keys: [command, space]
|
|
6
|
+
PressKeysCommand: {
|
|
7
|
+
command: '"press-keys"',
|
|
8
|
+
keys: "string[]",
|
|
9
|
+
},
|
|
10
|
+
// - command: hover-text # Hovers text matching the \`description\`. The text must be visible. This will also handle clicking or right clicking on the text if required.
|
|
11
|
+
// text: Sign Up # The text to find on screen. The longer and more unique the better.
|
|
12
|
+
// description: registration in the top right of the header # Describe the element so it can be identified in the future. Do not include the text itself here. Make sure to include the unique traits of this element.
|
|
13
|
+
// action: click # What to do when text is found. Available actions are: click, right-click, double-click, hover
|
|
14
|
+
// method: ai # Optional. Only try this if text match is not working.
|
|
15
|
+
HoverTextCommand: {
|
|
16
|
+
command: '"hover-text"',
|
|
17
|
+
text: "string",
|
|
18
|
+
description: "string",
|
|
19
|
+
action: '"click" | "right-click" | "double-click" | "hover"',
|
|
20
|
+
"method?": '"ai"',
|
|
21
|
+
},
|
|
22
|
+
// - command: type # Types the string into the active application. You must focus the correct field before typing.
|
|
23
|
+
// text: Hello World
|
|
24
|
+
TypeCommand: {
|
|
25
|
+
command: '"type"',
|
|
26
|
+
text: "string",
|
|
27
|
+
},
|
|
28
|
+
// - command: wait # Waits a number of miliseconds before continuing.
|
|
29
|
+
// timeout: 5000
|
|
30
|
+
WaitCommand: {
|
|
31
|
+
command: '"wait"',
|
|
32
|
+
timeout: "number",
|
|
33
|
+
},
|
|
34
|
+
// - command: hover-image # Hovers an icon, button, or image matching \`description\`. This will also handle handle clicking or right clicking on the icon or image if required.
|
|
35
|
+
// description: search icon in the webpage content # Describe the icon or image and what it represents. Describe the element so it can be identified in the future. Do not include the image or icon itself here. Make sure to include the unique traits of this element.
|
|
36
|
+
// action: click # What to do when text is found. Available actions are: click, right-click, double-click, hover
|
|
37
|
+
HoverImageCommand: {
|
|
38
|
+
command: '"hover-image"',
|
|
39
|
+
description: "string",
|
|
40
|
+
action: '"click" | "right-click" | "double-click" | "hover"',
|
|
41
|
+
},
|
|
42
|
+
// - command: focus-application # Focus an application by name.
|
|
43
|
+
// name: Google Chrome # The name of the application to focus.
|
|
44
|
+
FocusApplicationCommand: {
|
|
45
|
+
command: '"focus-application"',
|
|
46
|
+
name: "string",
|
|
47
|
+
},
|
|
48
|
+
// - command: remember # Remember a string value without needing to interact with the desktop.
|
|
49
|
+
// description: My dog's name # The key of the memory value to store.
|
|
50
|
+
// value: Roofus # The value of the memory to store
|
|
51
|
+
RememberCommand: {
|
|
52
|
+
command: '"remember"',
|
|
53
|
+
description: "string",
|
|
54
|
+
value: "string",
|
|
55
|
+
},
|
|
56
|
+
// - command: get-email-url # Retrieves the URL from a sign-up confirmation email in the background.
|
|
57
|
+
// # This retrieves an email confirmation URL without opening an email client. Do not view the screen, just run this command when dealing with emails
|
|
58
|
+
// username: testdriver # The username of the email address to check.
|
|
59
|
+
GetEmailUrlCommand: {
|
|
60
|
+
command: '"get-email-url"',
|
|
61
|
+
username: "string",
|
|
62
|
+
},
|
|
63
|
+
// - command: scroll # Scroll up or down. Make sure the correct portion of the page is focused before scrolling.
|
|
64
|
+
// direction: down # Available directions are: up, down, left, right
|
|
65
|
+
// method: keyboard # Optional. Available methods are: keyboard (default), mouse. Use mouse only if the prompt explicitly asks for it.
|
|
66
|
+
// amount: 300 # Optional. The amount of pixels to scroll. Defaults to 300 for keyboard and 200 for mouse.
|
|
67
|
+
ScrollCommand: {
|
|
68
|
+
command: '"scroll"',
|
|
69
|
+
direction: '"up" | "down" | "left" | "right"',
|
|
70
|
+
"method?": '"keyboard" | "mouse"',
|
|
71
|
+
"amount?": "number",
|
|
72
|
+
},
|
|
73
|
+
// - command: scroll-until-text # Scroll until text is found
|
|
74
|
+
// text: Sign Up # The text to find on screen. The longer and more unique the better.
|
|
75
|
+
// direction: down # Available directions are: up, down, left, right
|
|
76
|
+
// method: keyboard # Optional. Available methods are: keyboard (default), mouse. Use mouse only if the prompt explicitly asks for it.
|
|
77
|
+
ScrollUntilTextCommand: {
|
|
78
|
+
command: '"scroll-until-text"',
|
|
79
|
+
text: "string",
|
|
80
|
+
direction: '"up" | "down" | "left" | "right"',
|
|
81
|
+
"method?": '"keyboard" | "mouse"',
|
|
82
|
+
},
|
|
83
|
+
// - command: scroll-until-image # Scroll until icon or image is found
|
|
84
|
+
// description: Submit at the bottom of the form
|
|
85
|
+
// direction: down # Available directions are: up, down, left, rights
|
|
86
|
+
// method: keyboard # Optional. Available methods are: keyboard (default), mouse. Use mouse only if the prompt explicitly asks for it.
|
|
87
|
+
ScrollUntilImageCommand: {
|
|
88
|
+
command: '"scroll-until-image"',
|
|
89
|
+
description: "string",
|
|
90
|
+
direction: '"up" | "down" | "left" | "right"',
|
|
91
|
+
"method?": '"keyboard" | "mouse"',
|
|
92
|
+
},
|
|
93
|
+
// - command: wait-for-text # Wait until text is seen on screen. Not recommended unless explicitly requested by user.
|
|
94
|
+
// text: Copyright 2024 # The text to find on screen.
|
|
95
|
+
WaitForTextCommand: {
|
|
96
|
+
command: '"wait-for-text"',
|
|
97
|
+
text: "string",
|
|
98
|
+
},
|
|
99
|
+
// - command: wait-for-image # Wait until icon or image is seen on screen. Not recommended unless explicitly requested by user.
|
|
100
|
+
// description: trash icon
|
|
101
|
+
WaitForImageCommand: {
|
|
102
|
+
command: '"wait-for-image"',
|
|
103
|
+
description: "string",
|
|
104
|
+
},
|
|
105
|
+
// - command: assert # Assert that a condition is true. This is used to validate that a task was successful. Only use this when the user asks to "assert", "check," or "make sure" of something.
|
|
106
|
+
// expect: the video is playing # The condition to check. This should be a string that describes what you see on screen.
|
|
107
|
+
AssertCommand: {
|
|
108
|
+
command: '"assert"',
|
|
109
|
+
expect: "string",
|
|
110
|
+
},
|
|
111
|
+
// - command: if # Conditional block. If the condition is true, run the commands in the block. Otherwise, run the commands in the else block. Only use this if the user explicitly asks for a condition.
|
|
112
|
+
// condition: the active window is "Google Chrome"
|
|
113
|
+
// then:
|
|
114
|
+
// - command: hover-text
|
|
115
|
+
// text: Search Google or type a URL
|
|
116
|
+
// description: main google search
|
|
117
|
+
// action: click
|
|
118
|
+
// - command: type
|
|
119
|
+
// text: monster trucks
|
|
120
|
+
// description: search for monster trucks
|
|
121
|
+
// else:
|
|
122
|
+
// - command: focus-application
|
|
123
|
+
// name: Google Chrome
|
|
124
|
+
IfCommand: {
|
|
125
|
+
command: '"if"',
|
|
126
|
+
condition: "string",
|
|
127
|
+
// The unknown[] instead of Command[] is because of a weird error, although it does work
|
|
128
|
+
// on the other typescript repo
|
|
129
|
+
then: "unknown[]",
|
|
130
|
+
"else?": "unknown[]",
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
Command:
|
|
134
|
+
"PressKeysCommand | HoverTextCommand | TypeCommand | WaitCommand | HoverImageCommand | FocusApplicationCommand | RememberCommand | GetEmailUrlCommand | ScrollCommand | ScrollUntilTextCommand | ScrollUntilImageCommand | WaitForTextCommand | WaitForImageCommand | AssertCommand | IfCommand",
|
|
135
|
+
|
|
136
|
+
CommandList: "Command[]",
|
|
137
|
+
|
|
138
|
+
Step: {
|
|
139
|
+
prompt: "string",
|
|
140
|
+
commands: "Command[]",
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
StepList: "Step[]",
|
|
144
|
+
|
|
145
|
+
File: {
|
|
146
|
+
version: "string",
|
|
147
|
+
session: "string | undefined",
|
|
148
|
+
steps: "Step[]",
|
|
149
|
+
},
|
|
150
|
+
}).export(),
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
*
|
|
155
|
+
* @param {unknown} data
|
|
156
|
+
*/
|
|
157
|
+
const getType = async (data) => {
|
|
158
|
+
const { type } = await import("arktype");
|
|
159
|
+
const t = await types();
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* @type {["File", "Step", "StepList", "Command", "CommandList"]}
|
|
163
|
+
*/
|
|
164
|
+
const options = ["File", "Step", "StepList", "Command", "CommandList"];
|
|
165
|
+
|
|
166
|
+
for (const option of options) {
|
|
167
|
+
const result = t[option](data);
|
|
168
|
+
if (!(result instanceof type.errors)) {
|
|
169
|
+
return option;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
module.exports = { getType };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testdriverai",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.0",
|
|
4
4
|
"description": "Next generation autonomous AI agent for end-to-end testing of web & desktop",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@e2b/desktop": "^1.6.0",
|
|
20
20
|
"@electerm/strip-ansi": "^1.0.0",
|
|
21
|
+
"arktype": "^2.1.19",
|
|
21
22
|
"axios": "^1.7.7",
|
|
22
23
|
"chalk": "^4.1.2",
|
|
23
24
|
"cli-progress": "^3.12.0",
|
package/postinstall.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
let platform = require("os").platform();
|
|
2
2
|
let exec = require("child_process").exec;
|
|
3
|
-
const { execSync } = require("child_process");
|
|
4
|
-
const readline = require("readline");
|
|
5
3
|
|
|
6
4
|
if (platform !== "darwin") {
|
|
7
5
|
console.log("TestDriver Setup: Skipping codesign becasue not on Mac");
|
|
@@ -20,25 +18,3 @@ exec(signScript, (error, stdout, stderr) => {
|
|
|
20
18
|
console.log(`stdout: ${stdout}`);
|
|
21
19
|
console.error(`stderr: ${stderr}`);
|
|
22
20
|
});
|
|
23
|
-
|
|
24
|
-
const rl = readline.createInterface({
|
|
25
|
-
input: process.stdin,
|
|
26
|
-
output: process.stdout
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
rl.question("Do you want to install Vue support? (y/n) ", (answer) => {
|
|
30
|
-
if (answer.toLowerCase() === "y") {
|
|
31
|
-
console.log("Installing Vue dependencies...");
|
|
32
|
-
execSync("npm install -g vue vue-router", { stdio: "inherit" });
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
rl.question("Do you want to install React support? (y/n) ", (answer2) => {
|
|
36
|
-
if (answer2.toLowerCase() === "y") {
|
|
37
|
-
console.log("Installing React dependencies...");
|
|
38
|
-
execSync("npm install -g react react-dom", { stdio: "inherit" });
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
console.log("Setup complete!");
|
|
42
|
-
rl.close();
|
|
43
|
-
});
|
|
44
|
-
});
|
package/lib/websockets.js
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
const WebSocket = require('ws');
|
|
2
|
-
const config = require('./config');
|
|
3
|
-
|
|
4
|
-
let instance;
|
|
5
|
-
class WebSocketServerSingleton {
|
|
6
|
-
constructor() {
|
|
7
|
-
|
|
8
|
-
if (!WebSocketServerSingleton.instance) {
|
|
9
|
-
this.wss = new WebSocket.Server({ port: 8080 }, () => {
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
this.clients = new Set();
|
|
13
|
-
this.eventListeners = new Map();
|
|
14
|
-
|
|
15
|
-
this.wss.on('error', (error) => {
|
|
16
|
-
console.error('WebSocket server error:', error);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
this.wss.on('connection', (ws) => {
|
|
20
|
-
|
|
21
|
-
console.log('Client connected');
|
|
22
|
-
this.clients.add(ws);
|
|
23
|
-
|
|
24
|
-
ws.on('message', (message) => {
|
|
25
|
-
try {
|
|
26
|
-
const parsedMessage = JSON.parse(message);
|
|
27
|
-
if (parsedMessage.event) {
|
|
28
|
-
this.triggerEvent(parsedMessage.event, parsedMessage);
|
|
29
|
-
} else {
|
|
30
|
-
console.error('Parsed message does not contain an event property');
|
|
31
|
-
}
|
|
32
|
-
} catch (error) {
|
|
33
|
-
console.error('Error parsing message:', error);
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
ws.on('close', () => {
|
|
38
|
-
this.clients.delete(ws);
|
|
39
|
-
console.log('Client disconnected');
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
ws.on('error', (error) => {
|
|
43
|
-
console.error('WebSocket error:', error);
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
WebSocketServerSingleton.instance = this;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return WebSocketServerSingleton.instance;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
addEventListener(event, listener) {
|
|
54
|
-
if (!this.eventListeners.has(event)) {
|
|
55
|
-
this.eventListeners.set(event, []);
|
|
56
|
-
}
|
|
57
|
-
this.eventListeners.get(event).push(listener);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
triggerEvent(event, data) {
|
|
61
|
-
if (this.eventListeners.has(event)) {
|
|
62
|
-
for (const listener of this.eventListeners.get(event)) {
|
|
63
|
-
listener(data);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
sendToClients(event, message) {
|
|
69
|
-
const data = JSON.stringify({ event, message });
|
|
70
|
-
for (const client of this.clients) {
|
|
71
|
-
if (client.readyState === WebSocket.OPEN) {
|
|
72
|
-
client.send(data);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
module.exports = {
|
|
80
|
-
create: () => {
|
|
81
|
-
instance = new WebSocketServerSingleton();
|
|
82
|
-
Object.freeze(instance);
|
|
83
|
-
return instance;
|
|
84
|
-
}
|
|
85
|
-
};
|
package/test.md
DELETED
package/test.yml
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
version: 4.1.40
|
|
2
|
-
steps:
|
|
3
|
-
- prompt: Enter Password n stuff
|
|
4
|
-
commands:
|
|
5
|
-
- command: focus-application
|
|
6
|
-
name: Google Chrome
|
|
7
|
-
- command: hover-text
|
|
8
|
-
text: Username
|
|
9
|
-
description: username field
|
|
10
|
-
action: click
|
|
11
|
-
- command: type
|
|
12
|
-
text: ${TD_PASSWORD}
|
|
13
|
-
- command: hover-text
|
|
14
|
-
text: Password
|
|
15
|
-
description: Password field
|
|
16
|
-
action: click
|
|
17
|
-
- command: type
|
|
18
|
-
text: ${TD_PASSWORD}
|