@redhat-developer/page-objects 1.22.0 → 1.23.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/README.md +59 -4
- package/out/components/AbstractElement.d.ts +8 -0
- package/out/components/AbstractElement.js +10 -0
- package/out/components/editor/EditorView.js +3 -23
- package/out/components/editor/TextEditor.d.ts +34 -0
- package/out/components/editor/TextEditor.js +89 -5
- package/out/utils/WaitHelper.d.ts +31 -0
- package/out/utils/WaitHelper.js +70 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,9 +1,64 @@
|
|
|
1
1
|
<h1 align="center">
|
|
2
|
-
<img alt="ExTester for Visual Studio Code" width="75%"
|
|
2
|
+
<img alt="ExTester for Visual Studio Code" width="75%" src="https://raw.githubusercontent.com/redhat-developer/vscode-extension-tester/main/icons/logo-text-side.png">
|
|
3
3
|
</h1>
|
|
4
4
|
|
|
5
5
|
<h2 align="center">Page Objects</h2>
|
|
6
6
|
|
|
7
|
-
<p align="center">
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
<p align="center">Page Object API for the Visual Studio Code UI, used by the <a href="https://github.com/redhat-developer/vscode-extension-tester">ExTester</a> framework.</p>
|
|
8
|
+
|
|
9
|
+
## What it is
|
|
10
|
+
|
|
11
|
+
Page objects wrap the parts of the VS Code workbench in classes with meaningful methods, so a test can say `new ActivityBar().getViewControl('Explorer')` instead of searching the DOM. This package contains:
|
|
12
|
+
|
|
13
|
+
- Page object classes for the activity bar, side bar views and tree sections, editors (text, diff, settings, custom and web view), the bottom panel (problems, output, terminal and debug console), the status bar, title bar and menus, dialogs, notifications and the debug views.
|
|
14
|
+
- `AbstractElement`, the base class every page object extends, including your own custom ones.
|
|
15
|
+
- `LocatorLoader` and `mergeLocators`, which assemble the locator set for the VS Code version under test from [`@redhat-developer/locators`](https://www.npmjs.com/package/@redhat-developer/locators) and an optional custom contribution.
|
|
16
|
+
- `WaitHelper`, timeout constants and a re-export of `selenium-webdriver`, so one import gives you `WebDriver`, `By`, `Key` and the rest.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
[`vscode-extension-tester`](https://www.npmjs.com/package/vscode-extension-tester) depends on this package and re-exports everything from it. In an ExTester project you import page objects from `vscode-extension-tester` and never install this package directly:
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { ActivityBar, EditorView, TextEditor } from "vscode-extension-tester";
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Install it on its own only if you drive a VS Code instance with your own WebDriver setup:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install --save-dev @redhat-developer/page-objects @redhat-developer/locators selenium-webdriver
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Peer dependencies: `selenium-webdriver ^4.48.0` and `typescript >= 4.6.2`.
|
|
33
|
+
|
|
34
|
+
## Standalone usage
|
|
35
|
+
|
|
36
|
+
The page objects must be initialised once before the first one is created. ExTester does this for you when `VSBrowser` starts; without ExTester, call `initPageObjects` yourself:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { getLocatorsPath } from "@redhat-developer/locators";
|
|
40
|
+
import { initPageObjects } from "@redhat-developer/page-objects";
|
|
41
|
+
|
|
42
|
+
initPageObjects(
|
|
43
|
+
"1.134.0", // VS Code version under test
|
|
44
|
+
"1.37.0", // version of the base locator set
|
|
45
|
+
getLocatorsPath(), // folder with the compiled locators
|
|
46
|
+
driver, // your selenium-webdriver WebDriver attached to VS Code
|
|
47
|
+
browserName, // name of the browser the page objects run in
|
|
48
|
+
"./out/test/pageObjects/locators.js", // optional custom locator contribution
|
|
49
|
+
);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Documentation
|
|
53
|
+
|
|
54
|
+
- [Page Object Reference](https://redhat-developer.github.io/vscode-extension-tester/objects/) — every page object with examples
|
|
55
|
+
- [Custom Page Objects](https://redhat-developer.github.io/vscode-extension-tester/guides/custom-page-objects/) — extend the API for your own extension's UI
|
|
56
|
+
|
|
57
|
+
## Feedback
|
|
58
|
+
|
|
59
|
+
- Bugs and feature requests: [open an issue](https://github.com/redhat-developer/vscode-extension-tester/issues/new/choose)
|
|
60
|
+
- Questions: [GitHub Discussions](https://github.com/redhat-developer/vscode-extension-tester/discussions)
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
[Apache License 2.0](https://github.com/redhat-developer/vscode-extension-tester/blob/main/LICENSE)
|
|
@@ -53,6 +53,14 @@ export declare abstract class AbstractElement extends WebElement {
|
|
|
53
53
|
*/
|
|
54
54
|
constructor(base: Locator | WebElement, enclosingItem?: WebElement | Locator);
|
|
55
55
|
isEnabled(): Promise<boolean>;
|
|
56
|
+
/**
|
|
57
|
+
* Click the element, recovering when a transient overlay intercepts the
|
|
58
|
+
* click. VS Code renders hovers/tooltips as DOM overlays that appear while
|
|
59
|
+
* the WebDriver pointer rests on the last click point and never auto-hide,
|
|
60
|
+
* so a plain W3C click deadlocks on its pre-dispatch hit-test. See
|
|
61
|
+
* {@link WaitHelper.clickThroughInterception} for the recovery strategy.
|
|
62
|
+
*/
|
|
63
|
+
click(): Promise<void>;
|
|
56
64
|
isSelected(): Promise<boolean>;
|
|
57
65
|
/**
|
|
58
66
|
* Wait for the element to become visible
|
|
@@ -76,6 +76,16 @@ class AbstractElement extends selenium_webdriver_1.WebElement {
|
|
|
76
76
|
async isEnabled() {
|
|
77
77
|
return (await super.isEnabled()) && (await AbstractElement.locators.AbstractElement.enabled(this));
|
|
78
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Click the element, recovering when a transient overlay intercepts the
|
|
81
|
+
* click. VS Code renders hovers/tooltips as DOM overlays that appear while
|
|
82
|
+
* the WebDriver pointer rests on the last click point and never auto-hide,
|
|
83
|
+
* so a plain W3C click deadlocks on its pre-dispatch hit-test. See
|
|
84
|
+
* {@link WaitHelper.clickThroughInterception} for the recovery strategy.
|
|
85
|
+
*/
|
|
86
|
+
async click() {
|
|
87
|
+
await AbstractElement.waitHelper.clickThroughInterception(this, () => super.click());
|
|
88
|
+
}
|
|
79
89
|
async isSelected() {
|
|
80
90
|
return (await super.isSelected()) && (await AbstractElement.locators.AbstractElement.selected(this));
|
|
81
91
|
}
|
|
@@ -252,29 +252,9 @@ class EditorGroup extends AbstractElement_1.AbstractElement {
|
|
|
252
252
|
async closeEditor(title) {
|
|
253
253
|
const tab = await this.getTabByTitle(title);
|
|
254
254
|
const closeButton = await tab.findElement(EditorView.locators.EditorView.closeTab);
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
// bypasses overlay elements entirely.
|
|
259
|
-
const maxAttempts = 3;
|
|
260
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
261
|
-
try {
|
|
262
|
-
await closeButton.click();
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
catch (err) {
|
|
266
|
-
if (err.name !== 'ElementClickInterceptedError' || attempt === maxAttempts - 1) {
|
|
267
|
-
// On the final attempt use executeScript to bypass the overlay, then return.
|
|
268
|
-
if (err.name === 'ElementClickInterceptedError') {
|
|
269
|
-
await this.getDriver().executeScript('arguments[0].click()', closeButton);
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
throw err;
|
|
273
|
-
}
|
|
274
|
-
// Brief back-off so the tooltip has time to dismiss before the next attempt.
|
|
275
|
-
await this.getWaitHelper().sleep(300 * (attempt + 1));
|
|
276
|
-
}
|
|
277
|
-
}
|
|
255
|
+
// hover tooltip overlays can intercept the click on CI - the centralized
|
|
256
|
+
// recovery parks the pointer to dismiss them and retries the native click
|
|
257
|
+
await this.getWaitHelper().clickThroughInterception(closeButton);
|
|
278
258
|
}
|
|
279
259
|
/**
|
|
280
260
|
* Close all open editor tabs
|
|
@@ -60,6 +60,40 @@ export declare class TextEditor extends Editor {
|
|
|
60
60
|
* @returns Promise resolving to ContentAssist object when opening, void otherwise
|
|
61
61
|
*/
|
|
62
62
|
toggleContentAssist(open: boolean): Promise<ContentAssist | void>;
|
|
63
|
+
/**
|
|
64
|
+
* Find out whether this editor currently has VS Code keyboard focus
|
|
65
|
+
* @returns Promise resolving to true when the editor's text input is focused
|
|
66
|
+
*/
|
|
67
|
+
hasFocus(): Promise<boolean>;
|
|
68
|
+
/**
|
|
69
|
+
* Bring VS Code keyboard focus into this editor without moving the cursor.
|
|
70
|
+
*
|
|
71
|
+
* Editor commands (select all, clipboard actions, the `:Ln,Col` go-to prompt)
|
|
72
|
+
* and the status bar cursor position all target whichever editor VS Code has
|
|
73
|
+
* focused, and the editor's zero-width `native-edit-context` input element only
|
|
74
|
+
* accepts keys while it is the focused element. With several editor groups open
|
|
75
|
+
* this makes the page object act on its own editor instance instead of on the
|
|
76
|
+
* group that happened to be focused before.
|
|
77
|
+
*
|
|
78
|
+
* @returns Promise resolving once the editor has focus
|
|
79
|
+
*/
|
|
80
|
+
focus(): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Open the command palette with this editor focused, so editor commands
|
|
83
|
+
* (select all, clipboard actions, the `:Ln,Col` go-to prompt) run against
|
|
84
|
+
* this editor instance.
|
|
85
|
+
*
|
|
86
|
+
* Focus is moved into this editor first, then the palette is opened with the
|
|
87
|
+
* global `Ctrl/Cmd+Shift+P` chord. `Workbench.openCommandPrompt()` cannot be
|
|
88
|
+
* reused here: while any webview editor is open it sends the key to the first
|
|
89
|
+
* active tab of whatever group, which need not be this editor's group.
|
|
90
|
+
*/
|
|
91
|
+
private openCommandPrompt;
|
|
92
|
+
/**
|
|
93
|
+
* Execute a command from the command palette opened on this editor
|
|
94
|
+
* @param command id or title of the command
|
|
95
|
+
*/
|
|
96
|
+
private executeCommand;
|
|
63
97
|
/**
|
|
64
98
|
* Get all text from the editor
|
|
65
99
|
* @returns Promise resolving to editor text
|
|
@@ -113,6 +113,76 @@ class TextEditor extends Editor_1.Editor {
|
|
|
113
113
|
}
|
|
114
114
|
});
|
|
115
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Find out whether this editor currently has VS Code keyboard focus
|
|
118
|
+
* @returns Promise resolving to true when the editor's text input is focused
|
|
119
|
+
*/
|
|
120
|
+
async hasFocus() {
|
|
121
|
+
const container = await this.findElement(TextEditor.locators.TextEditor.editorContainer);
|
|
122
|
+
const klass = (await container.getAttribute('class')) ?? '';
|
|
123
|
+
return klass.split(/\s+/).includes('focused');
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Bring VS Code keyboard focus into this editor without moving the cursor.
|
|
127
|
+
*
|
|
128
|
+
* Editor commands (select all, clipboard actions, the `:Ln,Col` go-to prompt)
|
|
129
|
+
* and the status bar cursor position all target whichever editor VS Code has
|
|
130
|
+
* focused, and the editor's zero-width `native-edit-context` input element only
|
|
131
|
+
* accepts keys while it is the focused element. With several editor groups open
|
|
132
|
+
* this makes the page object act on its own editor instance instead of on the
|
|
133
|
+
* group that happened to be focused before.
|
|
134
|
+
*
|
|
135
|
+
* @returns Promise resolving once the editor has focus
|
|
136
|
+
*/
|
|
137
|
+
async focus() {
|
|
138
|
+
if (await this.hasFocus()) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const inputarea = await this.findElement(TextEditor.locators.Editor.inputArea);
|
|
142
|
+
await this.getDriver().executeScript('arguments[0].focus()', inputarea);
|
|
143
|
+
await this.getWaitHelper().forCondition(() => this.hasFocus(), {
|
|
144
|
+
timeout: 2000,
|
|
145
|
+
pollInterval: 50,
|
|
146
|
+
message: 'Editor did not receive focus',
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Open the command palette with this editor focused, so editor commands
|
|
151
|
+
* (select all, clipboard actions, the `:Ln,Col` go-to prompt) run against
|
|
152
|
+
* this editor instance.
|
|
153
|
+
*
|
|
154
|
+
* Focus is moved into this editor first, then the palette is opened with the
|
|
155
|
+
* global `Ctrl/Cmd+Shift+P` chord. `Workbench.openCommandPrompt()` cannot be
|
|
156
|
+
* reused here: while any webview editor is open it sends the key to the first
|
|
157
|
+
* active tab of whatever group, which need not be this editor's group.
|
|
158
|
+
*/
|
|
159
|
+
async openCommandPrompt() {
|
|
160
|
+
await this.focus();
|
|
161
|
+
await this.getDriver()
|
|
162
|
+
.actions()
|
|
163
|
+
.keyDown(AbstractElement_1.AbstractElement.ctlKey)
|
|
164
|
+
.keyDown(selenium_webdriver_1.Key.SHIFT)
|
|
165
|
+
.sendKeys('p')
|
|
166
|
+
.keyUp(selenium_webdriver_1.Key.SHIFT)
|
|
167
|
+
.keyUp(AbstractElement_1.AbstractElement.ctlKey)
|
|
168
|
+
.perform();
|
|
169
|
+
return await __1.InputBox.create();
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Execute a command from the command palette opened on this editor
|
|
173
|
+
* @param command id or title of the command
|
|
174
|
+
*/
|
|
175
|
+
async executeCommand(command) {
|
|
176
|
+
const prompt = await this.openCommandPrompt();
|
|
177
|
+
await prompt.setText(`>${command}`);
|
|
178
|
+
const quickPicks = await Promise.all((await prompt.getQuickPicks()).map((item) => item.getLabel()));
|
|
179
|
+
if (quickPicks.includes(command)) {
|
|
180
|
+
await prompt.selectQuickPick(command);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
await prompt.confirm();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
116
186
|
/**
|
|
117
187
|
* Get all text from the editor
|
|
118
188
|
* @returns Promise resolving to editor text
|
|
@@ -128,8 +198,14 @@ class TextEditor extends Editor_1.Editor {
|
|
|
128
198
|
// workaround issue https://github.com/redhat-developer/vscode-extension-tester/issues/835
|
|
129
199
|
// do not fail if clipboard is empty
|
|
130
200
|
}
|
|
131
|
-
|
|
132
|
-
|
|
201
|
+
// The status bar position, the select-all/copy commands and the cursor
|
|
202
|
+
// restore below all act on the focused editor: make sure it is this one.
|
|
203
|
+
await self.focus();
|
|
204
|
+
// Store current position
|
|
205
|
+
const [line, col] = await self.getCoordinates();
|
|
206
|
+
// Select/copy contents
|
|
207
|
+
await self.executeCommand('editor.action.selectAll');
|
|
208
|
+
await self.executeCommand('editor.action.clipboardCopyAction');
|
|
133
209
|
// Wait for clipboard operation to complete
|
|
134
210
|
await self.getWaitHelper().forCondition(async () => {
|
|
135
211
|
try {
|
|
@@ -141,10 +217,16 @@ class TextEditor extends Editor_1.Editor {
|
|
|
141
217
|
}
|
|
142
218
|
}, { timeout: 2000, pollInterval: 50, message: 'Clipboard copy operation did not complete' });
|
|
143
219
|
const text = clipboard.readSync();
|
|
144
|
-
await inputarea.sendKeys(selenium_webdriver_1.Key.UP);
|
|
145
220
|
if (originalClipboard.length > 0) {
|
|
146
221
|
clipboard.writeSync(originalClipboard);
|
|
147
222
|
}
|
|
223
|
+
try {
|
|
224
|
+
// Restore original cursor position
|
|
225
|
+
await self.setCursor(line, col);
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
await self.sendKeys(selenium_webdriver_1.Key.UP);
|
|
229
|
+
}
|
|
148
230
|
return text;
|
|
149
231
|
});
|
|
150
232
|
}
|
|
@@ -164,9 +246,11 @@ class TextEditor extends Editor_1.Editor {
|
|
|
164
246
|
// workaround issue https://github.com/redhat-developer/vscode-extension-tester/issues/835
|
|
165
247
|
// do not fail if clipboard is empty
|
|
166
248
|
}
|
|
249
|
+
await this.focus();
|
|
167
250
|
const inputarea = await this.findElement(TextEditor.locators.Editor.inputArea);
|
|
168
251
|
clipboard.writeSync(text);
|
|
169
|
-
await
|
|
252
|
+
await this.executeCommand('editor.action.selectAll');
|
|
253
|
+
await inputarea.sendKeys(selenium_webdriver_1.Key.chord(TextEditor.ctlKey, 'v'));
|
|
170
254
|
if (originalClipboard.length > 0) {
|
|
171
255
|
clipboard.writeSync(originalClipboard);
|
|
172
256
|
}
|
|
@@ -346,7 +430,7 @@ class TextEditor extends Editor_1.Editor {
|
|
|
346
430
|
* @returns Promise resolving when the cursor has reached the given coordinates
|
|
347
431
|
*/
|
|
348
432
|
async setCursor(line, column, timeout = 2_500) {
|
|
349
|
-
const input = await
|
|
433
|
+
const input = await this.openCommandPrompt();
|
|
350
434
|
await input.setText(`:${line},${column}`);
|
|
351
435
|
try {
|
|
352
436
|
await this.getWaitHelper().forCondition(async () => {
|
|
@@ -189,6 +189,37 @@ export declare class WaitHelper {
|
|
|
189
189
|
* @param ms Milliseconds to sleep
|
|
190
190
|
*/
|
|
191
191
|
sleep(ms: number): Promise<void>;
|
|
192
|
+
/**
|
|
193
|
+
* Move the virtual pointer to a quiet spot: the title-bar drag region at the
|
|
194
|
+
* top-center of the window (the command center is disabled by the framework's
|
|
195
|
+
* default settings, and unlike the status bar the area has no hover targets).
|
|
196
|
+
*
|
|
197
|
+
* A WebDriver session has no OS mouse - the pointer rests wherever the last
|
|
198
|
+
* interaction left it. VS Code hovers appear while the pointer rests on an
|
|
199
|
+
* element and are torn down only when it moves away, so parking delivers the
|
|
200
|
+
* mouseout an open hover is waiting for and disarms pending hover timers.
|
|
201
|
+
*/
|
|
202
|
+
parkPointer(): Promise<void>;
|
|
203
|
+
/**
|
|
204
|
+
* Click an element, recovering when a transient overlay intercepts the click.
|
|
205
|
+
*
|
|
206
|
+
* VS Code renders rich hovers/tooltips as DOM overlays that appear while the
|
|
207
|
+
* pointer rests on the last click point and never auto-hide. A W3C click
|
|
208
|
+
* hit-tests the click point BEFORE dispatching any event, so a resting hover
|
|
209
|
+
* over the target fails every attempt until the pointer actually moves -
|
|
210
|
+
* waiting alone can never resolve it. On interception this actively parks
|
|
211
|
+
* the pointer (dismissing pointer-rest overlays), waits for hover teardown
|
|
212
|
+
* and retries the native click. As a last resort it falls back to a
|
|
213
|
+
* JS-executor click, which bypasses hit-testing but also mousedown semantics
|
|
214
|
+
* (Monaco lists select on mousedown) - the fallback is logged so the call
|
|
215
|
+
* site's true blocker can be root-caused instead of silently papered over.
|
|
216
|
+
*
|
|
217
|
+
* @param element the element to click
|
|
218
|
+
* @param nativeClick override for the native click action (used by
|
|
219
|
+
* AbstractElement.click() to reach WebElement.prototype.click through its
|
|
220
|
+
* own override)
|
|
221
|
+
*/
|
|
222
|
+
clickThroughInterception(element: WebElement, nativeClick?: () => Promise<void>): Promise<void>;
|
|
192
223
|
/**
|
|
193
224
|
* Get the WebDriver instance.
|
|
194
225
|
*/
|
package/out/utils/WaitHelper.js
CHANGED
|
@@ -450,6 +450,76 @@ class WaitHelper {
|
|
|
450
450
|
async sleep(ms) {
|
|
451
451
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
452
452
|
}
|
|
453
|
+
/**
|
|
454
|
+
* Move the virtual pointer to a quiet spot: the title-bar drag region at the
|
|
455
|
+
* top-center of the window (the command center is disabled by the framework's
|
|
456
|
+
* default settings, and unlike the status bar the area has no hover targets).
|
|
457
|
+
*
|
|
458
|
+
* A WebDriver session has no OS mouse - the pointer rests wherever the last
|
|
459
|
+
* interaction left it. VS Code hovers appear while the pointer rests on an
|
|
460
|
+
* element and are torn down only when it moves away, so parking delivers the
|
|
461
|
+
* mouseout an open hover is waiting for and disarms pending hover timers.
|
|
462
|
+
*/
|
|
463
|
+
async parkPointer() {
|
|
464
|
+
const width = Number(await this.driver.executeScript('return window.innerWidth')) || 200;
|
|
465
|
+
await this.driver
|
|
466
|
+
.actions()
|
|
467
|
+
.move({ x: Math.floor(width / 2), y: 5 })
|
|
468
|
+
.perform();
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Click an element, recovering when a transient overlay intercepts the click.
|
|
472
|
+
*
|
|
473
|
+
* VS Code renders rich hovers/tooltips as DOM overlays that appear while the
|
|
474
|
+
* pointer rests on the last click point and never auto-hide. A W3C click
|
|
475
|
+
* hit-tests the click point BEFORE dispatching any event, so a resting hover
|
|
476
|
+
* over the target fails every attempt until the pointer actually moves -
|
|
477
|
+
* waiting alone can never resolve it. On interception this actively parks
|
|
478
|
+
* the pointer (dismissing pointer-rest overlays), waits for hover teardown
|
|
479
|
+
* and retries the native click. As a last resort it falls back to a
|
|
480
|
+
* JS-executor click, which bypasses hit-testing but also mousedown semantics
|
|
481
|
+
* (Monaco lists select on mousedown) - the fallback is logged so the call
|
|
482
|
+
* site's true blocker can be root-caused instead of silently papered over.
|
|
483
|
+
*
|
|
484
|
+
* @param element the element to click
|
|
485
|
+
* @param nativeClick override for the native click action (used by
|
|
486
|
+
* AbstractElement.click() to reach WebElement.prototype.click through its
|
|
487
|
+
* own override)
|
|
488
|
+
*/
|
|
489
|
+
async clickThroughInterception(element, nativeClick = () => element.click()) {
|
|
490
|
+
const maxNativeAttempts = 3;
|
|
491
|
+
for (let attempt = 0;; attempt++) {
|
|
492
|
+
try {
|
|
493
|
+
return await nativeClick();
|
|
494
|
+
}
|
|
495
|
+
catch (e) {
|
|
496
|
+
if (e.name !== 'ElementClickInterceptedError') {
|
|
497
|
+
throw e;
|
|
498
|
+
}
|
|
499
|
+
// a modal dialog blocks the whole workbench by design - neither
|
|
500
|
+
// pointer parking nor a JS click may subvert it: JS-clicking
|
|
501
|
+
// through the blocker would trigger actions the real UI forbids.
|
|
502
|
+
// Fail fast with the original error so modal-aware callers (and
|
|
503
|
+
// test authors) see the true blocker.
|
|
504
|
+
const modalUp = await this.driver
|
|
505
|
+
.executeScript('return Array.from(document.querySelectorAll(".monaco-dialog-modal-block, .monaco-dialog-box")).some((el) => el.checkVisibility());')
|
|
506
|
+
.catch(() => false);
|
|
507
|
+
if (modalUp) {
|
|
508
|
+
throw e;
|
|
509
|
+
}
|
|
510
|
+
if (attempt >= maxNativeAttempts - 1) {
|
|
511
|
+
console.warn(`clickThroughInterception: falling back to a JS click after ${maxNativeAttempts} intercepted attempts: ${e.message}`);
|
|
512
|
+
await this.driver.executeScript('arguments[0].click();', element);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
await this.parkPointer();
|
|
516
|
+
// hover teardown is event-driven and near-instant once the pointer
|
|
517
|
+
// moved away; a non-hover interceptor (modal, toast) never passes
|
|
518
|
+
// this check, so time-box it and retry regardless
|
|
519
|
+
await this.forCondition(async () => await this.driver.executeScript('return !Array.from(document.querySelectorAll(".monaco-hover")).some((el) => el.checkVisibility());'), { timeout: 1500, pollInterval: 50, message: 'hover overlay did not dismiss after parking the pointer' }).catch(() => undefined);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
453
523
|
/**
|
|
454
524
|
* Get the WebDriver instance.
|
|
455
525
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redhat-developer/page-objects",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.23.1",
|
|
4
4
|
"description": "Page Object API implementation for a VS Code editor used by ExTester framework.",
|
|
5
5
|
"main": "out/index.js",
|
|
6
6
|
"types": "out/index.d.ts",
|
|
@@ -49,5 +49,5 @@
|
|
|
49
49
|
"selenium-webdriver": "^4.48.0",
|
|
50
50
|
"typescript": ">=4.6.2"
|
|
51
51
|
},
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "9c3230175f00a9210a7bc2238bc9d6e9101b86f1"
|
|
53
53
|
}
|