@elizaos/computeruse 0.24.20
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/Cargo.toml +34 -0
- package/build.rs +10 -0
- package/computeruse.darwin-arm64.node +0 -0
- package/index.d.ts +0 -0
- package/index.js +327 -0
- package/package.json +74 -0
- package/scripts/sync-version.js +60 -0
- package/src/desktop.rs +2763 -0
- package/src/element.rs +1341 -0
- package/src/exceptions.rs +65 -0
- package/src/lib.rs +26 -0
- package/src/locator.rs +172 -0
- package/src/selector.rs +158 -0
- package/src/types.rs +963 -0
- package/src/window_manager.rs +342 -0
- package/tests/comprehensive-ui-elements.test.js +524 -0
- package/tests/cross-app-verification.test.js +243 -0
- package/tests/desktop-verify.test.js +169 -0
- package/tests/element-chaining.test.js +158 -0
- package/tests/element-range.test.js +207 -0
- package/tests/element-scroll-into-view.test.js +256 -0
- package/tests/element-value.test.js +264 -0
- package/tests/execute-browser-script-wrapper.test.js +135 -0
- package/tests/fixtures/sample-browser-script.js +7 -0
- package/tests/fixtures/script-with-env.js +16 -0
- package/tests/locator-validate.test.js +260 -0
- package/tests/locator-waitfor.test.js +286 -0
- package/wrapper.d.ts +84 -0
- package/wrapper.js +344 -0
- package/wrapper.ts +394 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
const { Desktop } = require("../index.js");
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test for Element.getValue() and setValue() methods
|
|
5
|
+
*
|
|
6
|
+
* Note: This test requires an application with text input fields.
|
|
7
|
+
* Common apps with text inputs: Notepad, Browser address bar, Settings search
|
|
8
|
+
*/
|
|
9
|
+
async function testGetSetValue() {
|
|
10
|
+
console.log("đ Testing Element getValue/setValue methods...");
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const desktop = new Desktop();
|
|
14
|
+
|
|
15
|
+
// Try to find a text input field (edit control)
|
|
16
|
+
console.log("Searching for text input fields...");
|
|
17
|
+
|
|
18
|
+
// Common roles for value-based controls: edit, textfield, combobox
|
|
19
|
+
const editLocator = desktop.locator("role:edit");
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const editField = await editLocator.first(2000);
|
|
23
|
+
console.log(`â
Found edit field: ${editField.name() || '(unnamed)'}`);
|
|
24
|
+
|
|
25
|
+
// Test: Get current value
|
|
26
|
+
console.log("Test: Get current value");
|
|
27
|
+
const currentValue = editField.getValue();
|
|
28
|
+
console.log(` Current value: ${currentValue !== null ? `"${currentValue}"` : 'null'}`);
|
|
29
|
+
|
|
30
|
+
// Test: Set a new value
|
|
31
|
+
console.log("Test: Set value");
|
|
32
|
+
const testValue = "Test Input 123";
|
|
33
|
+
editField.setValue(testValue);
|
|
34
|
+
console.log(` Set value to: "${testValue}"`);
|
|
35
|
+
|
|
36
|
+
// Wait a bit for the UI to update
|
|
37
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
38
|
+
|
|
39
|
+
// Verify the value changed
|
|
40
|
+
const newValue = editField.getValue();
|
|
41
|
+
console.log(` New value: ${newValue !== null ? `"${newValue}"` : 'null'}`);
|
|
42
|
+
|
|
43
|
+
if (newValue === testValue) {
|
|
44
|
+
console.log(` â
Value set successfully`);
|
|
45
|
+
} else {
|
|
46
|
+
console.log(` â ī¸ Value changed but doesn't match exactly (may be platform behavior)`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Restore original value if it existed
|
|
50
|
+
if (currentValue !== null) {
|
|
51
|
+
editField.setValue(currentValue);
|
|
52
|
+
console.log(` Restored original value: "${currentValue}"`);
|
|
53
|
+
} else {
|
|
54
|
+
// Clear the field
|
|
55
|
+
editField.setValue("");
|
|
56
|
+
console.log(` Cleared field`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return true;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (error.message && error.message.includes("Timed out")) {
|
|
62
|
+
console.log("âšī¸ No edit field found - this is acceptable if no text input apps are running");
|
|
63
|
+
console.log(" Try running: Notepad, Browser, or Settings app with search field");
|
|
64
|
+
return true; // Not a test failure, just no suitable UI available
|
|
65
|
+
}
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error("â getValue/setValue test failed:", error.message);
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Test getValue with combo box
|
|
76
|
+
*/
|
|
77
|
+
async function testComboBoxValue() {
|
|
78
|
+
console.log("đ Testing combo box value...");
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const desktop = new Desktop();
|
|
82
|
+
|
|
83
|
+
// Try to find a combo box
|
|
84
|
+
console.log("Searching for combo box controls...");
|
|
85
|
+
const comboLocator = desktop.locator("role:combobox");
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const comboBox = await comboLocator.first(2000);
|
|
89
|
+
console.log(`â
Found combo box: ${comboBox.name() || '(unnamed)'}`);
|
|
90
|
+
|
|
91
|
+
// Just verify we can read the value
|
|
92
|
+
const comboValue = comboBox.getValue();
|
|
93
|
+
console.log(` Combo box value: ${comboValue !== null ? `"${comboValue}"` : 'null'}`);
|
|
94
|
+
|
|
95
|
+
console.log("â
Successfully read combo box value");
|
|
96
|
+
return true;
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (error.message && error.message.includes("Timed out")) {
|
|
99
|
+
console.log("âšī¸ No combo box found - this is acceptable");
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error("â Combo box value test failed:", error.message);
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Test getValue returns null for non-value elements
|
|
112
|
+
*/
|
|
113
|
+
async function testGetValueNull() {
|
|
114
|
+
console.log("đ Testing getValue with non-value element...");
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const desktop = new Desktop();
|
|
118
|
+
|
|
119
|
+
// Try to get value from a button (which shouldn't have a value attribute)
|
|
120
|
+
console.log("Test: Get value from button element");
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const button = await desktop.locator("role:button").first(2000);
|
|
124
|
+
|
|
125
|
+
const buttonValue = button.getValue();
|
|
126
|
+
if (buttonValue === null) {
|
|
127
|
+
console.log(` â
Correctly returned null for button element`);
|
|
128
|
+
} else {
|
|
129
|
+
console.log(` â ī¸ Got value from button: "${buttonValue}" (unexpected but not fatal)`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return true;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (error.message && error.message.includes("Timed out")) {
|
|
135
|
+
console.log("âšī¸ No button found for null value test");
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error("â getValue null test failed:", error.message);
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Test setValue with empty string
|
|
148
|
+
*/
|
|
149
|
+
async function testSetValueEmpty() {
|
|
150
|
+
console.log("đ Testing setValue with empty string...");
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const desktop = new Desktop();
|
|
154
|
+
|
|
155
|
+
console.log("Test: Set empty value");
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
const editField = await desktop.locator("role:edit").first(2000);
|
|
159
|
+
|
|
160
|
+
// Get current value
|
|
161
|
+
const currentValue = editField.getValue();
|
|
162
|
+
console.log(` Current value: ${currentValue !== null ? `"${currentValue}"` : 'null'}`);
|
|
163
|
+
|
|
164
|
+
// Set to empty
|
|
165
|
+
editField.setValue("");
|
|
166
|
+
console.log(` Set value to empty string`);
|
|
167
|
+
|
|
168
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
169
|
+
|
|
170
|
+
// Verify it's empty
|
|
171
|
+
const emptyValue = editField.getValue();
|
|
172
|
+
console.log(` New value: ${emptyValue !== null ? `"${emptyValue}"` : 'null'}`);
|
|
173
|
+
|
|
174
|
+
if (emptyValue === "" || emptyValue === null) {
|
|
175
|
+
console.log(` â
Field cleared successfully`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Restore original value
|
|
179
|
+
if (currentValue !== null && currentValue !== "") {
|
|
180
|
+
editField.setValue(currentValue);
|
|
181
|
+
console.log(` Restored original value`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return true;
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (error.message && error.message.includes("Timed out")) {
|
|
187
|
+
console.log("âšī¸ No edit field found for empty value test");
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
console.error("â setValue empty test failed:", error.message);
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Main test runner
|
|
200
|
+
*/
|
|
201
|
+
async function runValueTests() {
|
|
202
|
+
console.log("đ Starting Element value tests...\n");
|
|
203
|
+
|
|
204
|
+
let passed = 0;
|
|
205
|
+
let total = 0;
|
|
206
|
+
|
|
207
|
+
// Test 1: Basic get/set value
|
|
208
|
+
total++;
|
|
209
|
+
if (await testGetSetValue()) {
|
|
210
|
+
passed++;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
console.log(); // Empty line
|
|
214
|
+
|
|
215
|
+
// Test 2: Combo box value
|
|
216
|
+
total++;
|
|
217
|
+
if (await testComboBoxValue()) {
|
|
218
|
+
passed++;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
console.log(); // Empty line
|
|
222
|
+
|
|
223
|
+
// Test 3: getValue returns null
|
|
224
|
+
total++;
|
|
225
|
+
if (await testGetValueNull()) {
|
|
226
|
+
passed++;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
console.log(); // Empty line
|
|
230
|
+
|
|
231
|
+
// Test 4: setValue empty string
|
|
232
|
+
total++;
|
|
233
|
+
if (await testSetValueEmpty()) {
|
|
234
|
+
passed++;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
console.log(); // Empty line
|
|
238
|
+
|
|
239
|
+
// Results
|
|
240
|
+
if (passed === total) {
|
|
241
|
+
console.log(`đ All value tests passed! (${passed}/${total})`);
|
|
242
|
+
process.exit(0);
|
|
243
|
+
} else {
|
|
244
|
+
console.log(`â Some tests failed: ${passed}/${total} passed`);
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Export for use in other test files
|
|
250
|
+
module.exports = {
|
|
251
|
+
testGetSetValue,
|
|
252
|
+
testComboBoxValue,
|
|
253
|
+
testGetValueNull,
|
|
254
|
+
testSetValueEmpty,
|
|
255
|
+
runValueTests,
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// Run tests if this file is executed directly
|
|
259
|
+
if (require.main === module) {
|
|
260
|
+
runValueTests().catch((error) => {
|
|
261
|
+
console.error("đĨ Test runner crashed:", error);
|
|
262
|
+
process.exit(1);
|
|
263
|
+
});
|
|
264
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const assert = require("assert");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
|
|
5
|
+
const { Desktop } = require("../wrapper.js");
|
|
6
|
+
|
|
7
|
+
async function testFunctionInput() {
|
|
8
|
+
let capturedScript = null;
|
|
9
|
+
const expected = { greeting: "hello", answer: 42 };
|
|
10
|
+
const fake = {
|
|
11
|
+
_originalExecuteBrowserScript: async (script) => {
|
|
12
|
+
capturedScript = script;
|
|
13
|
+
return JSON.stringify(expected);
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const result = await Desktop.prototype.executeBrowserScript.call(
|
|
18
|
+
fake,
|
|
19
|
+
({ greeting, answer }) => ({ greeting, answer }),
|
|
20
|
+
{ greeting: "hello", answer: 42 },
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
assert.deepStrictEqual(result, expected);
|
|
24
|
+
assert.ok(
|
|
25
|
+
capturedScript.includes('"greeting":"hello"') &&
|
|
26
|
+
capturedScript.includes('"answer":42'),
|
|
27
|
+
"env payload should be embedded in generated script",
|
|
28
|
+
);
|
|
29
|
+
assert.ok(
|
|
30
|
+
capturedScript.trim().startsWith("(async function()"),
|
|
31
|
+
"generated script should be wrapped in async IIFE",
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function testStringInput() {
|
|
36
|
+
const fake = {
|
|
37
|
+
_originalExecuteBrowserScript: async () => "raw-result",
|
|
38
|
+
};
|
|
39
|
+
const script = '(() => "ignored")()';
|
|
40
|
+
const result = await Desktop.prototype.executeBrowserScript.call(
|
|
41
|
+
fake,
|
|
42
|
+
script,
|
|
43
|
+
);
|
|
44
|
+
assert.strictEqual(result, "raw-result");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function testFileInput() {
|
|
48
|
+
const fixturePath = path.join(
|
|
49
|
+
__dirname,
|
|
50
|
+
"fixtures",
|
|
51
|
+
"sample-browser-script.js",
|
|
52
|
+
);
|
|
53
|
+
const expectedScript = fs.readFileSync(fixturePath, "utf8");
|
|
54
|
+
let receivedScript = null;
|
|
55
|
+
const fake = {
|
|
56
|
+
_originalExecuteBrowserScript: async (script) => {
|
|
57
|
+
receivedScript = script;
|
|
58
|
+
return "file-result";
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Test without env - should not inject
|
|
63
|
+
const result = await Desktop.prototype.executeBrowserScript.call(fake, {
|
|
64
|
+
file: fixturePath,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
assert.strictEqual(result, "file-result");
|
|
68
|
+
assert.strictEqual(receivedScript, expectedScript);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function testFileInputWithEnvInjection() {
|
|
72
|
+
const fixturePath = path.join(__dirname, "fixtures", "script-with-env.js");
|
|
73
|
+
const columnPositions = [10, 20, 30];
|
|
74
|
+
let receivedScript = null;
|
|
75
|
+
const fake = {
|
|
76
|
+
_originalExecuteBrowserScript: async (script) => {
|
|
77
|
+
receivedScript = script;
|
|
78
|
+
// Simulate executing the script with injected env
|
|
79
|
+
const column_positions = columnPositions;
|
|
80
|
+
const result = eval(script);
|
|
81
|
+
return JSON.stringify(result);
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const result = await Desktop.prototype.executeBrowserScript.call(fake, {
|
|
86
|
+
file: fixturePath,
|
|
87
|
+
env: { column_positions: columnPositions },
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Result comes back as JSON string for file-based scripts
|
|
91
|
+
const parsed = JSON.parse(result);
|
|
92
|
+
assert.deepStrictEqual(parsed, {
|
|
93
|
+
positions: columnPositions,
|
|
94
|
+
count: 3,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Verify the env was injected into the script as an env object
|
|
98
|
+
assert.ok(
|
|
99
|
+
receivedScript.includes("const env = "),
|
|
100
|
+
"env object should be injected into script",
|
|
101
|
+
);
|
|
102
|
+
assert.ok(
|
|
103
|
+
receivedScript.includes('"column_positions"'),
|
|
104
|
+
"column_positions key should be in env object",
|
|
105
|
+
);
|
|
106
|
+
assert.ok(
|
|
107
|
+
receivedScript.includes(JSON.stringify(columnPositions)),
|
|
108
|
+
"column_positions value should be embedded",
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function run() {
|
|
113
|
+
try {
|
|
114
|
+
await testFunctionInput();
|
|
115
|
+
console.log("â
executeBrowserScript handles function input");
|
|
116
|
+
|
|
117
|
+
await testStringInput();
|
|
118
|
+
console.log("â
executeBrowserScript preserves string behavior");
|
|
119
|
+
|
|
120
|
+
await testFileInput();
|
|
121
|
+
console.log("â
executeBrowserScript loads scripts from files");
|
|
122
|
+
|
|
123
|
+
await testFileInputWithEnvInjection();
|
|
124
|
+
console.log(
|
|
125
|
+
"â
executeBrowserScript injects env variables into file scripts",
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
console.log("đ All executeBrowserScript wrapper tests passed");
|
|
129
|
+
} catch (err) {
|
|
130
|
+
console.error("â executeBrowserScript wrapper test failed:", err);
|
|
131
|
+
process.exitCode = 1;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
run();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Browser script that uses injected env object
|
|
2
|
+
(() => {
|
|
3
|
+
// env object should be auto-injected by wrapper
|
|
4
|
+
if (typeof env === "undefined") {
|
|
5
|
+
throw new Error("env not injected");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (!env.column_positions) {
|
|
9
|
+
throw new Error("column_positions not in env");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
positions: env.column_positions,
|
|
14
|
+
count: env.column_positions.length,
|
|
15
|
+
};
|
|
16
|
+
})();
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
const { Desktop } = require("../index.js");
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test for Locator.validate() method
|
|
5
|
+
* This test verifies that validate() returns ValidationResult without throwing errors
|
|
6
|
+
*/
|
|
7
|
+
async function testValidateExists() {
|
|
8
|
+
console.log("đ Testing Locator.validate() for existing elements...");
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const desktop = new Desktop();
|
|
12
|
+
|
|
13
|
+
// Get any available application for testing
|
|
14
|
+
const apps = desktop.applications();
|
|
15
|
+
if (apps.length === 0) {
|
|
16
|
+
throw new Error("No applications found for testing");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const testApp = apps[0];
|
|
20
|
+
console.log(`đą Testing with app: ${testApp.name()}`);
|
|
21
|
+
|
|
22
|
+
// Test 1: Validate an existing element (the app window itself)
|
|
23
|
+
console.log("Test 1: Validate existing element");
|
|
24
|
+
const result = await desktop.locator("role:window").validate(2000);
|
|
25
|
+
|
|
26
|
+
if (!result.exists) {
|
|
27
|
+
throw new Error("Expected element to exist");
|
|
28
|
+
}
|
|
29
|
+
if (!result.element) {
|
|
30
|
+
throw new Error("Expected element to be present in result");
|
|
31
|
+
}
|
|
32
|
+
if (result.error) {
|
|
33
|
+
throw new Error(`Expected no error, got: ${result.error}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log(`â
Window element found: ${result.element.name()}`);
|
|
37
|
+
|
|
38
|
+
return true;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error("â Validate exists test failed:", error.message);
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Test validate() with non-existent element
|
|
47
|
+
*/
|
|
48
|
+
async function testValidateNotExists() {
|
|
49
|
+
console.log("đ Testing Locator.validate() for non-existent elements...");
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const desktop = new Desktop();
|
|
53
|
+
|
|
54
|
+
// Test: Validate a non-existent element
|
|
55
|
+
console.log("Test: Validate non-existent element");
|
|
56
|
+
const result = await desktop
|
|
57
|
+
.locator("role:button|ThisButtonDoesNotExist12345XYZ")
|
|
58
|
+
.validate(1000);
|
|
59
|
+
|
|
60
|
+
if (result.exists) {
|
|
61
|
+
throw new Error("Expected element to not exist");
|
|
62
|
+
}
|
|
63
|
+
if (result.element) {
|
|
64
|
+
throw new Error("Expected element to be undefined");
|
|
65
|
+
}
|
|
66
|
+
if (result.error) {
|
|
67
|
+
// Errors should only happen for invalid selectors, not for "not found"
|
|
68
|
+
throw new Error(`Unexpected error in validation: ${result.error}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log("â
Correctly reported element as not existing");
|
|
72
|
+
|
|
73
|
+
return true;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error("â Validate not exists test failed:", error.message);
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Test validate() with chained locators
|
|
82
|
+
*/
|
|
83
|
+
async function testValidateChaining() {
|
|
84
|
+
console.log("đ Testing Locator.validate() with chaining...");
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const desktop = new Desktop();
|
|
88
|
+
const apps = desktop.applications();
|
|
89
|
+
|
|
90
|
+
if (apps.length === 0) {
|
|
91
|
+
throw new Error("No applications found for testing");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const testApp = apps[0];
|
|
95
|
+
|
|
96
|
+
// Test: Validate with chained locator
|
|
97
|
+
console.log("Test: Validate with chained locator");
|
|
98
|
+
const result = await testApp
|
|
99
|
+
.locator("role:window")
|
|
100
|
+
.locator("role:button")
|
|
101
|
+
.validate(2000);
|
|
102
|
+
|
|
103
|
+
// Either exists or doesn't exist - both are valid, we just check no crash
|
|
104
|
+
if (result.exists) {
|
|
105
|
+
console.log(`â
Found button in chain: ${result.element.name()}`);
|
|
106
|
+
} else {
|
|
107
|
+
console.log("â
No button found in chain (expected)");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (result.error) {
|
|
111
|
+
throw new Error(`Unexpected error: ${result.error}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return true;
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error("â Validate chaining test failed:", error.message);
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Test validate() with zero timeout
|
|
123
|
+
*/
|
|
124
|
+
async function testValidateZeroTimeout() {
|
|
125
|
+
console.log("đ Testing Locator.validate() with zero timeout...");
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const desktop = new Desktop();
|
|
129
|
+
|
|
130
|
+
// Test: Immediate validation (no retry)
|
|
131
|
+
console.log("Test: Validate with zero timeout (immediate)");
|
|
132
|
+
const result = await desktop.locator("role:window").validate(0);
|
|
133
|
+
|
|
134
|
+
// With zero timeout, should still find immediate elements
|
|
135
|
+
console.log(`â
Immediate validation returned: exists=${result.exists}`);
|
|
136
|
+
|
|
137
|
+
return true;
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error("â Validate zero timeout test failed:", error.message);
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Test that validate() doesn't throw like first() does
|
|
146
|
+
*/
|
|
147
|
+
async function testValidateVsFirst() {
|
|
148
|
+
console.log("đ Testing validate() vs first() behavior...");
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const desktop = new Desktop();
|
|
152
|
+
const selector = "role:button|ThisButtonDoesNotExist12345XYZ";
|
|
153
|
+
|
|
154
|
+
// Test: first() should throw
|
|
155
|
+
console.log("Test: Verifying first() throws on not found");
|
|
156
|
+
let firstThrew = false;
|
|
157
|
+
try {
|
|
158
|
+
await desktop.locator(selector).first(500);
|
|
159
|
+
} catch (err) {
|
|
160
|
+
firstThrew = true;
|
|
161
|
+
console.log("â
first() correctly threw error");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (!firstThrew) {
|
|
165
|
+
throw new Error("Expected first() to throw, but it didn't");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Test: validate() should NOT throw
|
|
169
|
+
console.log("Test: Verifying validate() doesn't throw on not found");
|
|
170
|
+
const result = await desktop.locator(selector).validate(500);
|
|
171
|
+
|
|
172
|
+
if (result.exists) {
|
|
173
|
+
throw new Error("Expected element to not exist");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
console.log("â
validate() correctly returned {exists: false} without throwing");
|
|
177
|
+
|
|
178
|
+
return true;
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error("â Validate vs first test failed:", error.message);
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Main test runner
|
|
187
|
+
*/
|
|
188
|
+
async function runValidateTests() {
|
|
189
|
+
console.log("đ Starting Locator.validate() tests...\n");
|
|
190
|
+
|
|
191
|
+
let passed = 0;
|
|
192
|
+
let total = 0;
|
|
193
|
+
|
|
194
|
+
// Test 1: Validate existing element
|
|
195
|
+
total++;
|
|
196
|
+
if (await testValidateExists()) {
|
|
197
|
+
passed++;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
console.log(); // Empty line
|
|
201
|
+
|
|
202
|
+
// Test 2: Validate non-existent element
|
|
203
|
+
total++;
|
|
204
|
+
if (await testValidateNotExists()) {
|
|
205
|
+
passed++;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
console.log(); // Empty line
|
|
209
|
+
|
|
210
|
+
// Test 3: Validate with chaining
|
|
211
|
+
total++;
|
|
212
|
+
if (await testValidateChaining()) {
|
|
213
|
+
passed++;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
console.log(); // Empty line
|
|
217
|
+
|
|
218
|
+
// Test 4: Validate with zero timeout
|
|
219
|
+
total++;
|
|
220
|
+
if (await testValidateZeroTimeout()) {
|
|
221
|
+
passed++;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
console.log(); // Empty line
|
|
225
|
+
|
|
226
|
+
// Test 5: Validate vs first behavior
|
|
227
|
+
total++;
|
|
228
|
+
if (await testValidateVsFirst()) {
|
|
229
|
+
passed++;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
console.log(); // Empty line
|
|
233
|
+
|
|
234
|
+
// Results
|
|
235
|
+
if (passed === total) {
|
|
236
|
+
console.log(`đ All validate tests passed! (${passed}/${total})`);
|
|
237
|
+
process.exit(0);
|
|
238
|
+
} else {
|
|
239
|
+
console.log(`â Some tests failed: ${passed}/${total} passed`);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Export for use in other test files
|
|
245
|
+
module.exports = {
|
|
246
|
+
testValidateExists,
|
|
247
|
+
testValidateNotExists,
|
|
248
|
+
testValidateChaining,
|
|
249
|
+
testValidateZeroTimeout,
|
|
250
|
+
testValidateVsFirst,
|
|
251
|
+
runValidateTests,
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// Run tests if this file is executed directly
|
|
255
|
+
if (require.main === module) {
|
|
256
|
+
runValidateTests().catch((error) => {
|
|
257
|
+
console.error("đĨ Test runner crashed:", error);
|
|
258
|
+
process.exit(1);
|
|
259
|
+
});
|
|
260
|
+
}
|