copilot-liku-cli 0.0.1
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/ARCHITECTURE.md +411 -0
- package/CONFIGURATION.md +302 -0
- package/CONTRIBUTING.md +225 -0
- package/ELECTRON_README.md +121 -0
- package/INSTALLATION.md +350 -0
- package/LICENSE.md +1 -0
- package/PROJECT_STATUS.md +229 -0
- package/QUICKSTART.md +255 -0
- package/README.md +167 -0
- package/TESTING.md +274 -0
- package/package.json +61 -0
- package/scripts/start.js +30 -0
- package/src/assets/tray-icon.png +0 -0
- package/src/cli/commands/agent.js +327 -0
- package/src/cli/commands/click.js +108 -0
- package/src/cli/commands/drag.js +85 -0
- package/src/cli/commands/find.js +109 -0
- package/src/cli/commands/keys.js +132 -0
- package/src/cli/commands/mouse.js +79 -0
- package/src/cli/commands/repl.js +290 -0
- package/src/cli/commands/screenshot.js +72 -0
- package/src/cli/commands/scroll.js +74 -0
- package/src/cli/commands/start.js +67 -0
- package/src/cli/commands/type.js +57 -0
- package/src/cli/commands/wait.js +84 -0
- package/src/cli/commands/window.js +104 -0
- package/src/cli/liku.js +249 -0
- package/src/cli/util/output.js +174 -0
- package/src/main/agents/base-agent.js +410 -0
- package/src/main/agents/builder.js +484 -0
- package/src/main/agents/index.js +62 -0
- package/src/main/agents/orchestrator.js +362 -0
- package/src/main/agents/researcher.js +511 -0
- package/src/main/agents/state-manager.js +344 -0
- package/src/main/agents/supervisor.js +365 -0
- package/src/main/agents/verifier.js +452 -0
- package/src/main/ai-service.js +1633 -0
- package/src/main/index.js +2208 -0
- package/src/main/inspect-service.js +467 -0
- package/src/main/system-automation.js +1186 -0
- package/src/main/ui-automation/config.js +76 -0
- package/src/main/ui-automation/core/helpers.js +41 -0
- package/src/main/ui-automation/core/index.js +15 -0
- package/src/main/ui-automation/core/powershell.js +82 -0
- package/src/main/ui-automation/elements/finder.js +274 -0
- package/src/main/ui-automation/elements/index.js +14 -0
- package/src/main/ui-automation/elements/wait.js +66 -0
- package/src/main/ui-automation/index.js +164 -0
- package/src/main/ui-automation/interactions/element-click.js +211 -0
- package/src/main/ui-automation/interactions/high-level.js +230 -0
- package/src/main/ui-automation/interactions/index.js +47 -0
- package/src/main/ui-automation/keyboard/index.js +15 -0
- package/src/main/ui-automation/keyboard/input.js +179 -0
- package/src/main/ui-automation/mouse/click.js +186 -0
- package/src/main/ui-automation/mouse/drag.js +88 -0
- package/src/main/ui-automation/mouse/index.js +30 -0
- package/src/main/ui-automation/mouse/movement.js +51 -0
- package/src/main/ui-automation/mouse/scroll.js +116 -0
- package/src/main/ui-automation/screenshot.js +183 -0
- package/src/main/ui-automation/window/index.js +23 -0
- package/src/main/ui-automation/window/manager.js +305 -0
- package/src/main/utils/time.js +62 -0
- package/src/main/visual-awareness.js +597 -0
- package/src/renderer/chat/chat.js +671 -0
- package/src/renderer/chat/index.html +725 -0
- package/src/renderer/chat/preload.js +112 -0
- package/src/renderer/overlay/index.html +648 -0
- package/src/renderer/overlay/overlay.js +782 -0
- package/src/renderer/overlay/preload.js +90 -0
- package/src/shared/grid-math.js +82 -0
- package/src/shared/inspect-types.js +230 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI Automation Module
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive Windows UI automation using semantic element discovery,
|
|
5
|
+
* SendInput API for reliable input, and PowerShell for system integration.
|
|
6
|
+
*
|
|
7
|
+
* @module ui-automation
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const ui = require('./ui-automation');
|
|
11
|
+
*
|
|
12
|
+
* // Find and click a button by text
|
|
13
|
+
* await ui.click({ text: 'Submit' });
|
|
14
|
+
*
|
|
15
|
+
* // Type in a text field
|
|
16
|
+
* await ui.click({ automationId: 'searchBox' });
|
|
17
|
+
* await ui.typeText('Hello world');
|
|
18
|
+
*
|
|
19
|
+
* // Wait for element and click
|
|
20
|
+
* await ui.waitAndClick({ text: 'OK' }, { timeout: 5000 });
|
|
21
|
+
*
|
|
22
|
+
* // Take screenshot
|
|
23
|
+
* await ui.screenshot({ path: 'capture.png' });
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
// Configuration
|
|
27
|
+
const { CONFIG, CONTROL_TYPES } = require('./config');
|
|
28
|
+
|
|
29
|
+
// Core utilities
|
|
30
|
+
const { sleep, debug, log, executePowerShellScript } = require('./core');
|
|
31
|
+
|
|
32
|
+
// Element operations
|
|
33
|
+
const {
|
|
34
|
+
findElements,
|
|
35
|
+
findElement,
|
|
36
|
+
waitForElement,
|
|
37
|
+
waitForElementGone
|
|
38
|
+
} = require('./elements');
|
|
39
|
+
|
|
40
|
+
// Mouse operations
|
|
41
|
+
const {
|
|
42
|
+
moveMouse,
|
|
43
|
+
getMousePosition,
|
|
44
|
+
clickAt,
|
|
45
|
+
doubleClickAt,
|
|
46
|
+
drag,
|
|
47
|
+
scroll,
|
|
48
|
+
scrollUp,
|
|
49
|
+
scrollDown,
|
|
50
|
+
scrollLeft,
|
|
51
|
+
scrollRight,
|
|
52
|
+
} = require('./mouse');
|
|
53
|
+
|
|
54
|
+
// Keyboard operations
|
|
55
|
+
const {
|
|
56
|
+
typeText,
|
|
57
|
+
sendKeys,
|
|
58
|
+
keyDown,
|
|
59
|
+
keyUp,
|
|
60
|
+
VK,
|
|
61
|
+
} = require('./keyboard');
|
|
62
|
+
|
|
63
|
+
// Window operations
|
|
64
|
+
const {
|
|
65
|
+
getActiveWindow,
|
|
66
|
+
findWindows,
|
|
67
|
+
focusWindow,
|
|
68
|
+
minimizeWindow,
|
|
69
|
+
maximizeWindow,
|
|
70
|
+
restoreWindow,
|
|
71
|
+
} = require('./window');
|
|
72
|
+
|
|
73
|
+
// High-level interactions
|
|
74
|
+
const {
|
|
75
|
+
click,
|
|
76
|
+
clickByText,
|
|
77
|
+
clickByAutomationId,
|
|
78
|
+
rightClick,
|
|
79
|
+
doubleClick,
|
|
80
|
+
clickElement,
|
|
81
|
+
invokeElement,
|
|
82
|
+
fillField,
|
|
83
|
+
selectDropdownItem,
|
|
84
|
+
waitForWindow,
|
|
85
|
+
clickSequence,
|
|
86
|
+
hover,
|
|
87
|
+
waitAndClick,
|
|
88
|
+
clickAndWaitFor,
|
|
89
|
+
selectFromDropdown,
|
|
90
|
+
} = require('./interactions');
|
|
91
|
+
|
|
92
|
+
// Screenshot
|
|
93
|
+
const {
|
|
94
|
+
screenshot,
|
|
95
|
+
screenshotActiveWindow,
|
|
96
|
+
screenshotElement,
|
|
97
|
+
} = require('./screenshot');
|
|
98
|
+
|
|
99
|
+
module.exports = {
|
|
100
|
+
// Configuration
|
|
101
|
+
CONFIG,
|
|
102
|
+
CONTROL_TYPES,
|
|
103
|
+
|
|
104
|
+
// Core utilities
|
|
105
|
+
sleep,
|
|
106
|
+
debug,
|
|
107
|
+
log,
|
|
108
|
+
executePowerShellScript,
|
|
109
|
+
|
|
110
|
+
// Element operations
|
|
111
|
+
findElements,
|
|
112
|
+
findElement,
|
|
113
|
+
waitForElement,
|
|
114
|
+
waitForElementGone,
|
|
115
|
+
|
|
116
|
+
// Mouse operations - low level
|
|
117
|
+
moveMouse,
|
|
118
|
+
getMousePosition,
|
|
119
|
+
clickAt,
|
|
120
|
+
doubleClickAt,
|
|
121
|
+
drag,
|
|
122
|
+
scroll,
|
|
123
|
+
scrollUp,
|
|
124
|
+
scrollDown,
|
|
125
|
+
scrollLeft,
|
|
126
|
+
scrollRight,
|
|
127
|
+
|
|
128
|
+
// Keyboard operations
|
|
129
|
+
typeText,
|
|
130
|
+
sendKeys,
|
|
131
|
+
keyDown,
|
|
132
|
+
keyUp,
|
|
133
|
+
VK,
|
|
134
|
+
|
|
135
|
+
// Window operations
|
|
136
|
+
getActiveWindow,
|
|
137
|
+
findWindows,
|
|
138
|
+
focusWindow,
|
|
139
|
+
minimizeWindow,
|
|
140
|
+
maximizeWindow,
|
|
141
|
+
restoreWindow,
|
|
142
|
+
|
|
143
|
+
// High-level interactions (element-based clicks)
|
|
144
|
+
click,
|
|
145
|
+
clickByText,
|
|
146
|
+
clickByAutomationId,
|
|
147
|
+
rightClick,
|
|
148
|
+
doubleClick,
|
|
149
|
+
clickElement,
|
|
150
|
+
invokeElement,
|
|
151
|
+
fillField,
|
|
152
|
+
selectDropdownItem,
|
|
153
|
+
waitForWindow,
|
|
154
|
+
clickSequence,
|
|
155
|
+
hover,
|
|
156
|
+
waitAndClick,
|
|
157
|
+
clickAndWaitFor,
|
|
158
|
+
selectFromDropdown,
|
|
159
|
+
|
|
160
|
+
// Screenshot
|
|
161
|
+
screenshot,
|
|
162
|
+
screenshotActiveWindow,
|
|
163
|
+
screenshotElement,
|
|
164
|
+
};
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Element Click Interactions
|
|
3
|
+
*
|
|
4
|
+
* Click on UI elements by criteria (text, automationId, etc.)
|
|
5
|
+
* @module ui-automation/interactions/element-click
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { findElement, waitForElement } = require('../elements');
|
|
9
|
+
const { clickAt, doubleClickAt } = require('../mouse');
|
|
10
|
+
const { executePowerShellScript } = require('../core/powershell');
|
|
11
|
+
const { log, sleep } = require('../core/helpers');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Click on an element found by criteria
|
|
15
|
+
*
|
|
16
|
+
* @param {Object} criteria - Element search criteria
|
|
17
|
+
* @param {string} [criteria.text] - Element text/name
|
|
18
|
+
* @param {string} [criteria.automationId] - Automation ID
|
|
19
|
+
* @param {string} [criteria.controlType] - Control type
|
|
20
|
+
* @param {string} [criteria.className] - Class name
|
|
21
|
+
* @param {string} [criteria.windowTitle] - Window title to search in
|
|
22
|
+
* @param {Object} [options] - Click options
|
|
23
|
+
* @param {boolean} [options.doubleClick=false] - Double click instead
|
|
24
|
+
* @param {string} [options.button='left'] - Mouse button
|
|
25
|
+
* @param {boolean} [options.focusWindow=true] - Focus window first
|
|
26
|
+
* @param {number} [options.waitTimeout=0] - Wait for element (ms, 0 = no wait)
|
|
27
|
+
* @returns {Promise<{success: boolean, element: Object|null}>}
|
|
28
|
+
*/
|
|
29
|
+
async function click(criteria, options = {}) {
|
|
30
|
+
const {
|
|
31
|
+
doubleClick = false,
|
|
32
|
+
button = 'left',
|
|
33
|
+
focusWindow = true,
|
|
34
|
+
waitTimeout = 0,
|
|
35
|
+
} = options;
|
|
36
|
+
|
|
37
|
+
// Find or wait for element
|
|
38
|
+
let findResult;
|
|
39
|
+
if (waitTimeout > 0) {
|
|
40
|
+
findResult = await waitForElement(criteria, { timeout: waitTimeout });
|
|
41
|
+
} else {
|
|
42
|
+
findResult = await findElement(criteria);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Extract element from result
|
|
46
|
+
const element = findResult?.element;
|
|
47
|
+
|
|
48
|
+
if (!element || !element.bounds) {
|
|
49
|
+
log(`click: Element not found for criteria: ${JSON.stringify(criteria)}`, 'warn');
|
|
50
|
+
return { success: false, element: null, error: findResult?.error || 'Element not found' };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Calculate center point
|
|
54
|
+
const bounds = element.bounds;
|
|
55
|
+
const x = bounds.x + bounds.width / 2;
|
|
56
|
+
const y = bounds.y + bounds.height / 2;
|
|
57
|
+
|
|
58
|
+
// Focus window if needed
|
|
59
|
+
if (focusWindow && element.windowHwnd) {
|
|
60
|
+
const { focusWindow: doFocus } = require('../window');
|
|
61
|
+
await doFocus(element.windowHwnd);
|
|
62
|
+
await sleep(50);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Click
|
|
66
|
+
const clickFn = doubleClick ? doubleClickAt : clickAt;
|
|
67
|
+
const clickResult = await clickFn(x, y, { button, focusWindow: false });
|
|
68
|
+
|
|
69
|
+
log(`click on "${element.name || element.automationId}" at (${Math.round(x)}, ${Math.round(y)}) - ${clickResult.success ? 'success' : 'failed'}`);
|
|
70
|
+
|
|
71
|
+
return { success: clickResult.success, element };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Click element by text
|
|
76
|
+
*
|
|
77
|
+
* @param {string} text - Element text to find
|
|
78
|
+
* @param {Object} [options] - Click options
|
|
79
|
+
* @returns {Promise<{success: boolean, element: Object|null}>}
|
|
80
|
+
*/
|
|
81
|
+
async function clickByText(text, options = {}) {
|
|
82
|
+
return click({ text }, options);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Click element by automation ID
|
|
87
|
+
*
|
|
88
|
+
* @param {string} automationId - Automation ID
|
|
89
|
+
* @param {Object} [options] - Click options
|
|
90
|
+
* @returns {Promise<{success: boolean, element: Object|null}>}
|
|
91
|
+
*/
|
|
92
|
+
async function clickByAutomationId(automationId, options = {}) {
|
|
93
|
+
return click({ automationId }, options);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Right-click on an element
|
|
98
|
+
*
|
|
99
|
+
* @param {Object} criteria - Element search criteria
|
|
100
|
+
* @param {Object} [options] - Additional options
|
|
101
|
+
* @returns {Promise<{success: boolean, element: Object|null}>}
|
|
102
|
+
*/
|
|
103
|
+
async function rightClick(criteria, options = {}) {
|
|
104
|
+
return click(criteria, { ...options, button: 'right' });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Double-click on an element
|
|
109
|
+
*
|
|
110
|
+
* @param {Object} criteria - Element search criteria
|
|
111
|
+
* @param {Object} [options] - Additional options
|
|
112
|
+
* @returns {Promise<{success: boolean, element: Object|null}>}
|
|
113
|
+
*/
|
|
114
|
+
async function doubleClick(criteria, options = {}) {
|
|
115
|
+
return click(criteria, { ...options, doubleClick: true });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Click on an element object directly (low-level)
|
|
120
|
+
*
|
|
121
|
+
* @param {Object} element - Element with bounds property
|
|
122
|
+
* @param {Object} [options] - Click options
|
|
123
|
+
* @param {string} [options.button='left'] - Mouse button
|
|
124
|
+
* @param {boolean} [options.useInvoke=true] - Try Invoke pattern first
|
|
125
|
+
* @returns {Promise<{success: boolean, method: string, error?: string}>}
|
|
126
|
+
*/
|
|
127
|
+
async function clickElement(element, options = {}) {
|
|
128
|
+
const { button = 'left', useInvoke = true } = options;
|
|
129
|
+
|
|
130
|
+
if (!element || !element.bounds) {
|
|
131
|
+
return { success: false, error: 'Invalid element' };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const bounds = element.bounds;
|
|
135
|
+
const centerX = bounds.x + bounds.width / 2;
|
|
136
|
+
const centerY = bounds.y + bounds.height / 2;
|
|
137
|
+
|
|
138
|
+
// Strategy 1: Try Invoke pattern for buttons
|
|
139
|
+
if (useInvoke && element.patterns?.includes('InvokePatternIdentifiers.Pattern')) {
|
|
140
|
+
log(`Attempting Invoke pattern for "${element.name}"`);
|
|
141
|
+
const invokeResult = await invokeElement(element);
|
|
142
|
+
if (invokeResult.success) {
|
|
143
|
+
return { success: true, method: 'invoke', element };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Strategy 2: Click
|
|
148
|
+
log(`Clicking "${element.name}" at (${centerX}, ${centerY})`);
|
|
149
|
+
const clickResult = await clickAt(centerX, centerY, { button, focusWindow: true });
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
success: clickResult.success,
|
|
153
|
+
method: 'sendInput',
|
|
154
|
+
element,
|
|
155
|
+
coordinates: clickResult.coordinates,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Invoke an element using UI Automation Invoke pattern
|
|
161
|
+
* Works directly with buttons without simulating mouse clicks
|
|
162
|
+
*
|
|
163
|
+
* @param {Object} element - Element to invoke
|
|
164
|
+
* @returns {Promise<{success: boolean, error?: string}>}
|
|
165
|
+
*/
|
|
166
|
+
async function invokeElement(element) {
|
|
167
|
+
const searchName = (element.name || '').replace(/"/g, '`"');
|
|
168
|
+
|
|
169
|
+
const psScript = `
|
|
170
|
+
Add-Type -AssemblyName UIAutomationClient
|
|
171
|
+
Add-Type -AssemblyName UIAutomationTypes
|
|
172
|
+
|
|
173
|
+
$root = [System.Windows.Automation.AutomationElement]::RootElement
|
|
174
|
+
$condition = [System.Windows.Automation.PropertyCondition]::new(
|
|
175
|
+
[System.Windows.Automation.AutomationElement]::NameProperty,
|
|
176
|
+
"${searchName}"
|
|
177
|
+
)
|
|
178
|
+
$element = $root.FindFirst([System.Windows.Automation.TreeScope]::Descendants, $condition)
|
|
179
|
+
|
|
180
|
+
if ($element -eq $null) {
|
|
181
|
+
Write-Output '{"success": false, "error": "Element not found"}'
|
|
182
|
+
exit
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
$invokePattern = $element.GetCurrentPattern([System.Windows.Automation.InvokePattern]::Pattern)
|
|
187
|
+
$invokePattern.Invoke()
|
|
188
|
+
Write-Output '{"success": true, "method": "invoke"}'
|
|
189
|
+
} catch {
|
|
190
|
+
Write-Output "{\\"success\\": false, \\"error\\": \\"$($_.Exception.Message)\\"}"
|
|
191
|
+
}
|
|
192
|
+
`;
|
|
193
|
+
|
|
194
|
+
const result = await executePowerShellScript(psScript);
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
return JSON.parse(result.stdout.trim());
|
|
198
|
+
} catch {
|
|
199
|
+
return { success: false, error: result.error || 'Parse error' };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = {
|
|
204
|
+
click,
|
|
205
|
+
clickByText,
|
|
206
|
+
clickByAutomationId,
|
|
207
|
+
rightClick,
|
|
208
|
+
doubleClick,
|
|
209
|
+
clickElement,
|
|
210
|
+
invokeElement,
|
|
211
|
+
};
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* High-Level UI Interactions
|
|
3
|
+
*
|
|
4
|
+
* Complex automation workflows and convenience functions.
|
|
5
|
+
* @module ui-automation/interactions/high-level
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { findElement, findElements, waitForElement } = require('../elements');
|
|
9
|
+
const { click, clickByText } = require('./element-click');
|
|
10
|
+
const { typeText, sendKeys } = require('../keyboard');
|
|
11
|
+
const { focusWindow, findWindows } = require('../window');
|
|
12
|
+
const { log, sleep } = require('../core/helpers');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Fill a text field by clicking it then typing
|
|
16
|
+
*
|
|
17
|
+
* @param {Object} criteria - Element search criteria
|
|
18
|
+
* @param {string} text - Text to type
|
|
19
|
+
* @param {Object} [options] - Options
|
|
20
|
+
* @param {boolean} [options.clear=true] - Clear field first (Ctrl+A)
|
|
21
|
+
* @returns {Promise<{success: boolean}>}
|
|
22
|
+
*/
|
|
23
|
+
async function fillField(criteria, text, options = {}) {
|
|
24
|
+
const { clear = true } = options;
|
|
25
|
+
|
|
26
|
+
// Click the field
|
|
27
|
+
const clickResult = await click(criteria);
|
|
28
|
+
if (!clickResult.success) {
|
|
29
|
+
return { success: false };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
await sleep(50);
|
|
33
|
+
|
|
34
|
+
// Clear if requested
|
|
35
|
+
if (clear) {
|
|
36
|
+
await sendKeys('^a');
|
|
37
|
+
await sleep(20);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Type text
|
|
41
|
+
const typeResult = await typeText(text);
|
|
42
|
+
return { success: typeResult.success };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Select an item from a dropdown/combobox
|
|
47
|
+
*
|
|
48
|
+
* @param {Object} dropdownCriteria - Criteria to find the dropdown
|
|
49
|
+
* @param {string|Object} itemCriteria - Item text or criteria
|
|
50
|
+
* @param {Object} [options] - Options
|
|
51
|
+
* @param {number} [options.itemWait=1000] - Time to wait for dropdown items to appear
|
|
52
|
+
* @returns {Promise<{success: boolean}>}
|
|
53
|
+
*/
|
|
54
|
+
async function selectDropdownItem(dropdownCriteria, itemCriteria, options = {}) {
|
|
55
|
+
const { itemWait = 1000 } = options;
|
|
56
|
+
|
|
57
|
+
// Click dropdown to open
|
|
58
|
+
const openResult = await click(dropdownCriteria);
|
|
59
|
+
if (!openResult.success) {
|
|
60
|
+
log('selectDropdownItem: Failed to open dropdown', 'warn');
|
|
61
|
+
return { success: false };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
await sleep(itemWait);
|
|
65
|
+
|
|
66
|
+
// Click item
|
|
67
|
+
const itemQuery = typeof itemCriteria === 'string'
|
|
68
|
+
? { text: itemCriteria }
|
|
69
|
+
: itemCriteria;
|
|
70
|
+
|
|
71
|
+
const itemResult = await click(itemQuery);
|
|
72
|
+
return { success: itemResult.success };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Wait for a window and focus it
|
|
77
|
+
*
|
|
78
|
+
* @param {string|Object} criteria - Window title or search criteria
|
|
79
|
+
* @param {Object} [options] - Options
|
|
80
|
+
* @param {number} [options.timeout=10000] - Timeout in ms
|
|
81
|
+
* @param {number} [options.pollInterval=500] - Poll interval in ms
|
|
82
|
+
* @returns {Promise<{success: boolean, window: Object|null}>}
|
|
83
|
+
*/
|
|
84
|
+
async function waitForWindow(criteria, options = {}) {
|
|
85
|
+
const { timeout = 10000, pollInterval = 500 } = options;
|
|
86
|
+
const searchCriteria = typeof criteria === 'string' ? { title: criteria } : criteria;
|
|
87
|
+
|
|
88
|
+
const startTime = Date.now();
|
|
89
|
+
|
|
90
|
+
while (Date.now() - startTime < timeout) {
|
|
91
|
+
const windows = await findWindows(searchCriteria);
|
|
92
|
+
if (windows.length > 0) {
|
|
93
|
+
const result = await focusWindow(windows[0].hwnd);
|
|
94
|
+
return { success: result.success, window: windows[0] };
|
|
95
|
+
}
|
|
96
|
+
await sleep(pollInterval);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
log(`waitForWindow: Timeout waiting for window`, 'warn');
|
|
100
|
+
return { success: false, window: null };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Click a sequence of elements in order
|
|
105
|
+
*
|
|
106
|
+
* @param {Array<Object>} steps - Array of {criteria, options?, delay?}
|
|
107
|
+
* @returns {Promise<{success: boolean, completedSteps: number}>}
|
|
108
|
+
*/
|
|
109
|
+
async function clickSequence(steps) {
|
|
110
|
+
let completedSteps = 0;
|
|
111
|
+
|
|
112
|
+
for (const step of steps) {
|
|
113
|
+
const { criteria, options = {}, delay = 200 } = step;
|
|
114
|
+
|
|
115
|
+
const result = await click(criteria, options);
|
|
116
|
+
if (!result.success) {
|
|
117
|
+
log(`clickSequence: Failed at step ${completedSteps + 1}`, 'warn');
|
|
118
|
+
return { success: false, completedSteps };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
completedSteps++;
|
|
122
|
+
await sleep(delay);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return { success: true, completedSteps };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Perform hover over an element
|
|
130
|
+
*
|
|
131
|
+
* @param {Object} criteria - Element search criteria
|
|
132
|
+
* @param {Object} [options] - Options
|
|
133
|
+
* @param {number} [options.duration=500] - How long to hover in ms
|
|
134
|
+
* @returns {Promise<{success: boolean, element: Object|null}>}
|
|
135
|
+
*/
|
|
136
|
+
async function hover(criteria, options = {}) {
|
|
137
|
+
const { duration = 500 } = options;
|
|
138
|
+
const { moveMouse } = require('../mouse');
|
|
139
|
+
|
|
140
|
+
const element = await findElement(criteria);
|
|
141
|
+
if (!element) {
|
|
142
|
+
return { success: false, element: null };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const bounds = element.bounds;
|
|
146
|
+
const x = bounds.x + bounds.width / 2;
|
|
147
|
+
const y = bounds.y + bounds.height / 2;
|
|
148
|
+
|
|
149
|
+
await moveMouse(x, y);
|
|
150
|
+
await sleep(duration);
|
|
151
|
+
|
|
152
|
+
return { success: true, element };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Wait for element and click
|
|
157
|
+
* Convenience wrapper combining wait + click
|
|
158
|
+
*
|
|
159
|
+
* @param {Object} criteria - Element search criteria
|
|
160
|
+
* @param {Object} [options] - Options
|
|
161
|
+
* @param {number} [options.timeout=5000] - Wait timeout
|
|
162
|
+
* @returns {Promise<{success: boolean, element: Object|null}>}
|
|
163
|
+
*/
|
|
164
|
+
async function waitAndClick(criteria, options = {}) {
|
|
165
|
+
const { timeout = 5000, ...clickOptions } = options;
|
|
166
|
+
return click(criteria, { ...clickOptions, waitTimeout: timeout });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Click an element then wait for another element to appear
|
|
171
|
+
*
|
|
172
|
+
* @param {Object} clickCriteria - Element to click
|
|
173
|
+
* @param {Object} waitCriteria - Element to wait for
|
|
174
|
+
* @param {number} [timeout=10000] - Wait timeout
|
|
175
|
+
* @returns {Promise<{success: boolean, clickedElement?: Object, resultElement?: Object, error?: string}>}
|
|
176
|
+
*/
|
|
177
|
+
async function clickAndWaitFor(clickCriteria, waitCriteria, timeout = 10000) {
|
|
178
|
+
const clickResult = await click(clickCriteria);
|
|
179
|
+
if (!clickResult.success) {
|
|
180
|
+
return { success: false, error: `Click failed: ${clickResult.error || 'Element not found'}` };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const waitResult = await waitForElement(waitCriteria, { timeout });
|
|
184
|
+
return {
|
|
185
|
+
success: !!waitResult,
|
|
186
|
+
clickedElement: clickResult.element,
|
|
187
|
+
resultElement: waitResult,
|
|
188
|
+
error: waitResult ? undefined : 'Wait timeout',
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Select from a dropdown/combobox (alias for selectDropdownItem)
|
|
194
|
+
*
|
|
195
|
+
* @param {Object} dropdownCriteria - Dropdown element criteria
|
|
196
|
+
* @param {string} optionText - Text of option to select
|
|
197
|
+
* @param {number} [timeout=5000] - Wait timeout for options
|
|
198
|
+
* @returns {Promise<{success: boolean, error?: string}>}
|
|
199
|
+
*/
|
|
200
|
+
async function selectFromDropdown(dropdownCriteria, optionText, timeout = 5000) {
|
|
201
|
+
// Click the dropdown to open it
|
|
202
|
+
const openResult = await click(dropdownCriteria);
|
|
203
|
+
if (!openResult.success) {
|
|
204
|
+
return { success: false, error: `Failed to open dropdown` };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
await sleep(200);
|
|
208
|
+
|
|
209
|
+
// Find and click the option
|
|
210
|
+
const optionResult = await waitAndClick({ text: optionText }, { timeout });
|
|
211
|
+
if (!optionResult.success) {
|
|
212
|
+
// Try to close dropdown if option not found
|
|
213
|
+
const { sendKeys } = require('../keyboard');
|
|
214
|
+
await sendKeys('{ESC}');
|
|
215
|
+
return { success: false, error: `Option "${optionText}" not found` };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return { success: true, selectedOption: optionText };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
module.exports = {
|
|
222
|
+
fillField,
|
|
223
|
+
selectDropdownItem,
|
|
224
|
+
waitForWindow,
|
|
225
|
+
clickSequence,
|
|
226
|
+
hover,
|
|
227
|
+
waitAndClick,
|
|
228
|
+
clickAndWaitFor,
|
|
229
|
+
selectFromDropdown,
|
|
230
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactions Module
|
|
3
|
+
*
|
|
4
|
+
* @module ui-automation/interactions
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
click,
|
|
9
|
+
clickByText,
|
|
10
|
+
clickByAutomationId,
|
|
11
|
+
rightClick,
|
|
12
|
+
doubleClick,
|
|
13
|
+
clickElement,
|
|
14
|
+
invokeElement,
|
|
15
|
+
} = require('./element-click');
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
fillField,
|
|
19
|
+
selectDropdownItem,
|
|
20
|
+
waitForWindow,
|
|
21
|
+
clickSequence,
|
|
22
|
+
hover,
|
|
23
|
+
waitAndClick,
|
|
24
|
+
clickAndWaitFor,
|
|
25
|
+
selectFromDropdown,
|
|
26
|
+
} = require('./high-level');
|
|
27
|
+
|
|
28
|
+
module.exports = {
|
|
29
|
+
// Element clicks
|
|
30
|
+
click,
|
|
31
|
+
clickByText,
|
|
32
|
+
clickByAutomationId,
|
|
33
|
+
rightClick,
|
|
34
|
+
doubleClick,
|
|
35
|
+
clickElement,
|
|
36
|
+
invokeElement,
|
|
37
|
+
|
|
38
|
+
// High-level interactions
|
|
39
|
+
fillField,
|
|
40
|
+
selectDropdownItem,
|
|
41
|
+
waitForWindow,
|
|
42
|
+
clickSequence,
|
|
43
|
+
hover,
|
|
44
|
+
waitAndClick,
|
|
45
|
+
clickAndWaitFor,
|
|
46
|
+
selectFromDropdown,
|
|
47
|
+
};
|