@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,243 @@
|
|
|
1
|
+
const { Desktop } = require("../index.js");
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cross-Application Verification Test Suite
|
|
5
|
+
* Tests verification methods across multiple Windows built-in applications
|
|
6
|
+
* These apps are available on all Windows 10/11 computers
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const desktop = new Desktop();
|
|
10
|
+
|
|
11
|
+
// Test configuration for each app
|
|
12
|
+
const APP_TESTS = [
|
|
13
|
+
{
|
|
14
|
+
name: "Notepad",
|
|
15
|
+
launch: "notepad.exe",
|
|
16
|
+
existsSelector: "role:Document || role:Edit",
|
|
17
|
+
existsDescription: "text editor area",
|
|
18
|
+
menuBarSelector: "role:MenuBar",
|
|
19
|
+
notExistsSelector: "role:Window && name:Save As",
|
|
20
|
+
notExistsDescription: "Save As dialog",
|
|
21
|
+
required: true,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: "Calculator",
|
|
25
|
+
launch: "calc",
|
|
26
|
+
existsSelector: "role:Button && name:Zero",
|
|
27
|
+
existsDescription: "Zero button",
|
|
28
|
+
menuBarSelector: null, // Calculator has no traditional menu bar
|
|
29
|
+
notExistsSelector: "role:Button && name:NonExistentCalcButton",
|
|
30
|
+
notExistsDescription: "non-existent button",
|
|
31
|
+
required: true,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "Google Chrome",
|
|
35
|
+
launch: "chrome",
|
|
36
|
+
existsSelector: "role:Document || role:Pane",
|
|
37
|
+
existsDescription: "browser content area",
|
|
38
|
+
menuBarSelector: null, // Chrome uses toolbar, not menu bar
|
|
39
|
+
notExistsSelector: "role:Window && name:Downloads Complete",
|
|
40
|
+
notExistsDescription: "Downloads Complete dialog",
|
|
41
|
+
required: false, // Chrome may not be installed
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "Microsoft Edge",
|
|
45
|
+
launch: "msedge",
|
|
46
|
+
existsSelector: "role:Document || role:Pane",
|
|
47
|
+
existsDescription: "browser content area",
|
|
48
|
+
menuBarSelector: null, // Edge uses toolbar, not menu bar
|
|
49
|
+
notExistsSelector: "role:Window && name:Downloads Complete",
|
|
50
|
+
notExistsDescription: "Downloads Complete dialog",
|
|
51
|
+
required: false, // Edge can fail if already open or slow to launch
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "File Explorer",
|
|
55
|
+
launch: "explorer",
|
|
56
|
+
existsSelector: "role:Pane",
|
|
57
|
+
existsDescription: "content pane",
|
|
58
|
+
menuBarSelector: null, // Modern Explorer uses ribbon
|
|
59
|
+
notExistsSelector: "role:Window && name:Confirm Delete",
|
|
60
|
+
notExistsDescription: "Confirm Delete dialog",
|
|
61
|
+
required: false, // Explorer has complex window structure, can be flaky
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
async function closeApp(appElement, appName) {
|
|
66
|
+
try {
|
|
67
|
+
await appElement.pressKey("Alt+F4");
|
|
68
|
+
await new Promise(r => setTimeout(r, 500));
|
|
69
|
+
|
|
70
|
+
// Handle save dialogs if they appear
|
|
71
|
+
try {
|
|
72
|
+
const dontSave = await appElement.locator("role:Button && name:Don\\'t Save").first(1000);
|
|
73
|
+
await dontSave.click();
|
|
74
|
+
} catch {
|
|
75
|
+
// Try "No" button for some dialogs
|
|
76
|
+
try {
|
|
77
|
+
const noBtn = await appElement.locator("role:Button && name:No").first(500);
|
|
78
|
+
await noBtn.click();
|
|
79
|
+
} catch {
|
|
80
|
+
// No dialog, that's fine
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.log(` Note: Could not close ${appName} cleanly: ${e.message.substring(0, 50)}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function testApp(config) {
|
|
89
|
+
console.log(`\n ═══ ${config.name} ═══`);
|
|
90
|
+
|
|
91
|
+
let appElement = null;
|
|
92
|
+
let passed = 0;
|
|
93
|
+
let failed = 0;
|
|
94
|
+
let skipped = false;
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
// Launch app
|
|
98
|
+
console.log(` Launching ${config.name}...`);
|
|
99
|
+
try {
|
|
100
|
+
appElement = await desktop.openApplication(config.launch);
|
|
101
|
+
await new Promise(r => setTimeout(r, 1500)); // Wait for app to stabilize
|
|
102
|
+
} catch (launchError) {
|
|
103
|
+
if (!config.required) {
|
|
104
|
+
console.log(` ⏭️ Skipping ${config.name} (not installed or failed to launch)`);
|
|
105
|
+
return { passed: 0, failed: 0, skipped: true, name: config.name };
|
|
106
|
+
}
|
|
107
|
+
throw launchError;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Test 1: verifyElementExists for known element
|
|
111
|
+
console.log(` Test 1: verifyElementExists (${config.existsDescription})`);
|
|
112
|
+
try {
|
|
113
|
+
const found = await desktop.verifyElementExists(
|
|
114
|
+
appElement,
|
|
115
|
+
config.existsSelector,
|
|
116
|
+
5000
|
|
117
|
+
);
|
|
118
|
+
if (found) {
|
|
119
|
+
console.log(` ✅ Found ${config.existsDescription}: role=${found.role()}`);
|
|
120
|
+
passed++;
|
|
121
|
+
} else {
|
|
122
|
+
console.log(` ❌ Expected to find ${config.existsDescription}`);
|
|
123
|
+
failed++;
|
|
124
|
+
}
|
|
125
|
+
} catch (e) {
|
|
126
|
+
console.log(` ❌ Error: ${e.message.substring(0, 80)}`);
|
|
127
|
+
failed++;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Test 2: verifyElementExists for MenuBar (if applicable)
|
|
131
|
+
if (config.menuBarSelector) {
|
|
132
|
+
console.log(` Test 2: verifyElementExists (MenuBar)`);
|
|
133
|
+
try {
|
|
134
|
+
const menuBar = await desktop.verifyElementExists(
|
|
135
|
+
appElement,
|
|
136
|
+
config.menuBarSelector,
|
|
137
|
+
3000
|
|
138
|
+
);
|
|
139
|
+
if (menuBar) {
|
|
140
|
+
console.log(` ✅ Found MenuBar: role=${menuBar.role()}`);
|
|
141
|
+
passed++;
|
|
142
|
+
}
|
|
143
|
+
} catch (e) {
|
|
144
|
+
console.log(` ❌ MenuBar not found: ${e.message.substring(0, 60)}`);
|
|
145
|
+
failed++;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Test 3: verifyElementNotExists for non-existent element
|
|
150
|
+
console.log(` Test 3: verifyElementNotExists (${config.notExistsDescription})`);
|
|
151
|
+
try {
|
|
152
|
+
await desktop.verifyElementNotExists(
|
|
153
|
+
appElement,
|
|
154
|
+
config.notExistsSelector,
|
|
155
|
+
2000
|
|
156
|
+
);
|
|
157
|
+
console.log(` ✅ Correctly confirmed ${config.notExistsDescription} does not exist`);
|
|
158
|
+
passed++;
|
|
159
|
+
} catch (e) {
|
|
160
|
+
if (e.message.includes("VERIFICATION_FAILED")) {
|
|
161
|
+
console.log(` ❌ Element unexpectedly exists`);
|
|
162
|
+
} else {
|
|
163
|
+
console.log(` ❌ Error: ${e.message.substring(0, 60)}`);
|
|
164
|
+
}
|
|
165
|
+
failed++;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Test 4: Scoped locator search
|
|
169
|
+
console.log(` Test 4: Scoped locator within app window`);
|
|
170
|
+
try {
|
|
171
|
+
const element = await appElement.locator(config.existsSelector).first(3000);
|
|
172
|
+
if (element) {
|
|
173
|
+
console.log(` ✅ Scoped locator found element: role=${element.role()}`);
|
|
174
|
+
passed++;
|
|
175
|
+
}
|
|
176
|
+
} catch (e) {
|
|
177
|
+
console.log(` ❌ Scoped locator failed: ${e.message.substring(0, 60)}`);
|
|
178
|
+
failed++;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
} finally {
|
|
182
|
+
// Cleanup
|
|
183
|
+
if (appElement) {
|
|
184
|
+
console.log(` Closing ${config.name}...`);
|
|
185
|
+
await closeApp(appElement, config.name);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return { passed, failed, name: config.name };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function runTests() {
|
|
193
|
+
console.log("═══════════════════════════════════════════════════════════════════");
|
|
194
|
+
console.log("Cross-Application Verification Test Suite");
|
|
195
|
+
console.log("Tests SDK verification methods across Windows built-in applications");
|
|
196
|
+
console.log("═══════════════════════════════════════════════════════════════════");
|
|
197
|
+
|
|
198
|
+
let totalPassed = 0;
|
|
199
|
+
let totalFailed = 0;
|
|
200
|
+
const results = [];
|
|
201
|
+
|
|
202
|
+
for (const config of APP_TESTS) {
|
|
203
|
+
try {
|
|
204
|
+
const result = await testApp(config);
|
|
205
|
+
totalPassed += result.passed;
|
|
206
|
+
totalFailed += result.failed;
|
|
207
|
+
results.push(result);
|
|
208
|
+
} catch (error) {
|
|
209
|
+
console.log(`\n ❌ ${config.name} test suite crashed: ${error.message}`);
|
|
210
|
+
results.push({ name: config.name, passed: 0, failed: 1, error: error.message });
|
|
211
|
+
totalFailed++;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Brief pause between apps
|
|
215
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
console.log("\n═══════════════════════════════════════════════════════════════════");
|
|
219
|
+
console.log("Results Summary:");
|
|
220
|
+
console.log("═══════════════════════════════════════════════════════════════════");
|
|
221
|
+
|
|
222
|
+
let totalSkipped = 0;
|
|
223
|
+
for (const result of results) {
|
|
224
|
+
if (result.skipped) {
|
|
225
|
+
console.log(` ⏭️ ${result.name}: SKIPPED (not installed)`);
|
|
226
|
+
totalSkipped++;
|
|
227
|
+
} else {
|
|
228
|
+
const status = result.failed === 0 ? "✅" : "❌";
|
|
229
|
+
console.log(` ${status} ${result.name}: ${result.passed} passed, ${result.failed} failed`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
console.log("───────────────────────────────────────────────────────────────────");
|
|
234
|
+
console.log(` TOTAL: ${totalPassed} passed, ${totalFailed} failed, ${totalSkipped} skipped`);
|
|
235
|
+
console.log("═══════════════════════════════════════════════════════════════════");
|
|
236
|
+
|
|
237
|
+
process.exit(totalFailed > 0 ? 1 : 0);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
runTests().catch(error => {
|
|
241
|
+
console.error("Test suite crashed:", error);
|
|
242
|
+
process.exit(1);
|
|
243
|
+
});
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
const { Desktop } = require("../index.js");
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test for Desktop.verifyElementExists() and Desktop.verifyElementNotExists() methods
|
|
5
|
+
* These tests verify the verification methods work correctly with window-scoped searches
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
async function testVerifyElementExists() {
|
|
9
|
+
console.log("🔍 Testing Desktop.verifyElementExists()...");
|
|
10
|
+
const desktop = new Desktop();
|
|
11
|
+
|
|
12
|
+
// Open Notepad for testing
|
|
13
|
+
console.log(" Opening Notepad...");
|
|
14
|
+
const notepad = await desktop.openApplication("notepad.exe");
|
|
15
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
// Test 1: Find an element that exists (Document/text editor)
|
|
19
|
+
console.log(" Test 1: Verify existing element (Document)");
|
|
20
|
+
const textEditor = await desktop.verifyElementExists(
|
|
21
|
+
notepad,
|
|
22
|
+
"role:Document || role:Edit",
|
|
23
|
+
5000
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
if (!textEditor) {
|
|
27
|
+
throw new Error("Expected to find text editor element");
|
|
28
|
+
}
|
|
29
|
+
console.log(` ✅ Found: role=${textEditor.role()}, name=${textEditor.name()}`);
|
|
30
|
+
|
|
31
|
+
// Test 2: Find MenuBar (tests menubar role mapping)
|
|
32
|
+
console.log(" Test 2: Verify MenuBar exists");
|
|
33
|
+
const menuBar = await desktop.verifyElementExists(
|
|
34
|
+
notepad,
|
|
35
|
+
"role:MenuBar",
|
|
36
|
+
5000
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
if (!menuBar) {
|
|
40
|
+
throw new Error("Expected to find MenuBar element");
|
|
41
|
+
}
|
|
42
|
+
console.log(` ✅ Found: role=${menuBar.role()}`);
|
|
43
|
+
|
|
44
|
+
// Test 3: Non-existent element should throw
|
|
45
|
+
console.log(" Test 3: Verify non-existent element throws error");
|
|
46
|
+
let threwError = false;
|
|
47
|
+
try {
|
|
48
|
+
await desktop.verifyElementExists(
|
|
49
|
+
notepad,
|
|
50
|
+
"role:Button && name:NonExistentButton12345",
|
|
51
|
+
2000
|
|
52
|
+
);
|
|
53
|
+
} catch (e) {
|
|
54
|
+
threwError = true;
|
|
55
|
+
console.log(` ✅ Correctly threw error for non-existent element`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!threwError) {
|
|
59
|
+
throw new Error("Expected verifyElementExists to throw for non-existent element");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return true;
|
|
63
|
+
} finally {
|
|
64
|
+
// Cleanup
|
|
65
|
+
console.log(" Closing Notepad...");
|
|
66
|
+
try {
|
|
67
|
+
await notepad.pressKey("Alt+F4");
|
|
68
|
+
await new Promise(r => setTimeout(r, 500));
|
|
69
|
+
const dontSave = await notepad.locator("role:Button && name:Don\\'t Save").first();
|
|
70
|
+
await dontSave.click();
|
|
71
|
+
} catch {
|
|
72
|
+
// No save dialog
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function testVerifyElementNotExists() {
|
|
78
|
+
console.log("\n🔍 Testing Desktop.verifyElementNotExists()...");
|
|
79
|
+
const desktop = new Desktop();
|
|
80
|
+
|
|
81
|
+
// Open Notepad for testing
|
|
82
|
+
console.log(" Opening Notepad...");
|
|
83
|
+
const notepad = await desktop.openApplication("notepad.exe");
|
|
84
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
// Test 1: Verify non-existent element passes
|
|
88
|
+
console.log(" Test 1: Verify non-existent element (Save As dialog)");
|
|
89
|
+
await desktop.verifyElementNotExists(
|
|
90
|
+
notepad,
|
|
91
|
+
"role:Window && name:Save As",
|
|
92
|
+
2000
|
|
93
|
+
);
|
|
94
|
+
console.log(" ✅ Correctly confirmed 'Save As' dialog does not exist");
|
|
95
|
+
|
|
96
|
+
// Test 2: Verify existing element throws
|
|
97
|
+
console.log(" Test 2: Verify existing element (MenuBar) throws error");
|
|
98
|
+
let threwError = false;
|
|
99
|
+
try {
|
|
100
|
+
await desktop.verifyElementNotExists(
|
|
101
|
+
notepad,
|
|
102
|
+
"role:MenuBar",
|
|
103
|
+
2000
|
|
104
|
+
);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
threwError = true;
|
|
107
|
+
if (e.message.includes("VERIFICATION_FAILED")) {
|
|
108
|
+
console.log(" ✅ Correctly threw VERIFICATION_FAILED for existing element");
|
|
109
|
+
} else {
|
|
110
|
+
throw new Error(`Expected VERIFICATION_FAILED error, got: ${e.message}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!threwError) {
|
|
115
|
+
throw new Error("Expected verifyElementNotExists to throw for existing element");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return true;
|
|
119
|
+
} finally {
|
|
120
|
+
// Cleanup
|
|
121
|
+
console.log(" Closing Notepad...");
|
|
122
|
+
try {
|
|
123
|
+
await notepad.pressKey("Alt+F4");
|
|
124
|
+
await new Promise(r => setTimeout(r, 500));
|
|
125
|
+
const dontSave = await notepad.locator("role:Button && name:Don\\'t Save").first();
|
|
126
|
+
await dontSave.click();
|
|
127
|
+
} catch {
|
|
128
|
+
// No save dialog
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function runTests() {
|
|
134
|
+
console.log("═══════════════════════════════════════════════════════════════");
|
|
135
|
+
console.log("Desktop Verification Methods Test Suite");
|
|
136
|
+
console.log("═══════════════════════════════════════════════════════════════\n");
|
|
137
|
+
|
|
138
|
+
let passed = 0;
|
|
139
|
+
let failed = 0;
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
await testVerifyElementExists();
|
|
143
|
+
passed++;
|
|
144
|
+
console.log("\n✅ testVerifyElementExists PASSED\n");
|
|
145
|
+
} catch (error) {
|
|
146
|
+
failed++;
|
|
147
|
+
console.error("\n❌ testVerifyElementExists FAILED:", error.message, "\n");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
await testVerifyElementNotExists();
|
|
152
|
+
passed++;
|
|
153
|
+
console.log("\n✅ testVerifyElementNotExists PASSED\n");
|
|
154
|
+
} catch (error) {
|
|
155
|
+
failed++;
|
|
156
|
+
console.error("\n❌ testVerifyElementNotExists FAILED:", error.message, "\n");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log("═══════════════════════════════════════════════════════════════");
|
|
160
|
+
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
161
|
+
console.log("═══════════════════════════════════════════════════════════════");
|
|
162
|
+
|
|
163
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
runTests().catch(error => {
|
|
167
|
+
console.error("Test suite crashed:", error);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
});
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
const { Desktop } = require("../index.js");
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test for Element.locator() chaining functionality
|
|
5
|
+
* This test verifies the fix for issue #258 where Element.locator() chaining would fail
|
|
6
|
+
* due to expensive WindowsEngine creation on every call.
|
|
7
|
+
*/
|
|
8
|
+
async function testElementChaining() {
|
|
9
|
+
console.log("🔗 Testing Element.locator() chaining...");
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const desktop = new Desktop();
|
|
13
|
+
|
|
14
|
+
// Get any available application for testing
|
|
15
|
+
const apps = desktop.applications();
|
|
16
|
+
if (apps.length === 0) {
|
|
17
|
+
throw new Error("No applications found for testing");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const testApp = apps[0];
|
|
21
|
+
console.log(`📱 Testing with app: ${testApp.name()}`);
|
|
22
|
+
|
|
23
|
+
// Test 1: Basic chaining - this was the main issue
|
|
24
|
+
console.log("Test 1: Basic element chaining");
|
|
25
|
+
const windowLocator = testApp.locator("role:window");
|
|
26
|
+
const buttonLocator = windowLocator.locator("role:button");
|
|
27
|
+
console.log("✅ Basic chaining works");
|
|
28
|
+
|
|
29
|
+
// Test 2: Multiple chaining levels
|
|
30
|
+
console.log("Test 2: Multiple chaining levels");
|
|
31
|
+
const deepChain = testApp
|
|
32
|
+
.locator("role:window")
|
|
33
|
+
.locator("role:pane")
|
|
34
|
+
.locator("role:button");
|
|
35
|
+
console.log("✅ Deep chaining works");
|
|
36
|
+
|
|
37
|
+
// Test 3: Stress test - create many locators quickly
|
|
38
|
+
console.log("Test 3: Stress test (50 rapid chains)");
|
|
39
|
+
const startTime = Date.now();
|
|
40
|
+
for (let i = 0; i < 50; i++) {
|
|
41
|
+
const locator = testApp.locator("role:window").locator("role:button");
|
|
42
|
+
// Just create the locator, don't need to use it
|
|
43
|
+
}
|
|
44
|
+
const endTime = Date.now();
|
|
45
|
+
console.log(`✅ Created 50 chained locators in ${endTime - startTime}ms`);
|
|
46
|
+
|
|
47
|
+
// Test 4: Concurrent chaining
|
|
48
|
+
console.log("Test 4: Concurrent chaining");
|
|
49
|
+
const promises = [];
|
|
50
|
+
for (let i = 0; i < 10; i++) {
|
|
51
|
+
promises.push(
|
|
52
|
+
Promise.resolve().then(() => {
|
|
53
|
+
return testApp.locator("role:window").locator("role:text");
|
|
54
|
+
})
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
await Promise.all(promises);
|
|
58
|
+
console.log("✅ Concurrent chaining works");
|
|
59
|
+
|
|
60
|
+
return true;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error("❌ Element chaining test failed:", error.message);
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Test for Wikipedia-specific chaining (if Wikipedia is available)
|
|
69
|
+
*/
|
|
70
|
+
async function testWikipediaChaining() {
|
|
71
|
+
console.log("🌐 Testing Wikipedia-specific chaining...");
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const desktop = new Desktop();
|
|
75
|
+
|
|
76
|
+
// Try to find Wikipedia page
|
|
77
|
+
try {
|
|
78
|
+
const wikipediaLocator = desktop.locator("role:document|wikipedia");
|
|
79
|
+
const w = await wikipediaLocator.first(0); // timeout in ms (0 = immediate)
|
|
80
|
+
console.log(`📖 Found Wikipedia page: ${w.name()}`);
|
|
81
|
+
|
|
82
|
+
// Test the original failing case
|
|
83
|
+
const textLocator = w.locator("role:text|wikipedia");
|
|
84
|
+
console.log("✅ Wikipedia element chaining works");
|
|
85
|
+
|
|
86
|
+
// Try to find some elements
|
|
87
|
+
try {
|
|
88
|
+
const elements = await textLocator.all(2000);
|
|
89
|
+
console.log(`✅ Found ${elements.length} Wikipedia text elements`);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
console.log("ℹ️ No specific Wikipedia text elements found (normal)");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return true;
|
|
95
|
+
} catch (err) {
|
|
96
|
+
if (err.message.includes("Timed out")) {
|
|
97
|
+
console.log(
|
|
98
|
+
"ℹ️ No Wikipedia page found - skipping Wikipedia-specific test"
|
|
99
|
+
);
|
|
100
|
+
return true; // Not a failure, just no Wikipedia available
|
|
101
|
+
}
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error("❌ Wikipedia chaining test failed:", error.message);
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Main test runner
|
|
112
|
+
*/
|
|
113
|
+
async function runChainingTests() {
|
|
114
|
+
console.log("🚀 Starting Element.locator() chaining tests...\n");
|
|
115
|
+
|
|
116
|
+
let passed = 0;
|
|
117
|
+
let total = 0;
|
|
118
|
+
|
|
119
|
+
// Test 1: Basic element chaining
|
|
120
|
+
total++;
|
|
121
|
+
if (await testElementChaining()) {
|
|
122
|
+
passed++;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log(); // Empty line
|
|
126
|
+
|
|
127
|
+
// Test 2: Wikipedia-specific chaining
|
|
128
|
+
total++;
|
|
129
|
+
if (await testWikipediaChaining()) {
|
|
130
|
+
passed++;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
console.log(); // Empty line
|
|
134
|
+
|
|
135
|
+
// Results
|
|
136
|
+
if (passed === total) {
|
|
137
|
+
console.log(`🎉 All chaining tests passed! (${passed}/${total})`);
|
|
138
|
+
process.exit(0);
|
|
139
|
+
} else {
|
|
140
|
+
console.log(`❌ Some tests failed: ${passed}/${total} passed`);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Export for use in other test files
|
|
146
|
+
module.exports = {
|
|
147
|
+
testElementChaining,
|
|
148
|
+
testWikipediaChaining,
|
|
149
|
+
runChainingTests,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Run tests if this file is executed directly
|
|
153
|
+
if (require.main === module) {
|
|
154
|
+
runChainingTests().catch((error) => {
|
|
155
|
+
console.error("💥 Test runner crashed:", error);
|
|
156
|
+
process.exit(1);
|
|
157
|
+
});
|
|
158
|
+
}
|