@redhat-developer/page-objects 1.22.0 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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.js +16 -4
- 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
|
|
@@ -128,8 +128,12 @@ class TextEditor extends Editor_1.Editor {
|
|
|
128
128
|
// workaround issue https://github.com/redhat-developer/vscode-extension-tester/issues/835
|
|
129
129
|
// do not fail if clipboard is empty
|
|
130
130
|
}
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
// Store current position
|
|
132
|
+
const [line, col] = await this.getCoordinates();
|
|
133
|
+
// Select/copy contents
|
|
134
|
+
const bench = new __1.Workbench();
|
|
135
|
+
await bench.executeCommand('editor.action.selectAll');
|
|
136
|
+
await bench.executeCommand('editor.action.clipboardCopyAction');
|
|
133
137
|
// Wait for clipboard operation to complete
|
|
134
138
|
await self.getWaitHelper().forCondition(async () => {
|
|
135
139
|
try {
|
|
@@ -141,10 +145,16 @@ class TextEditor extends Editor_1.Editor {
|
|
|
141
145
|
}
|
|
142
146
|
}, { timeout: 2000, pollInterval: 50, message: 'Clipboard copy operation did not complete' });
|
|
143
147
|
const text = clipboard.readSync();
|
|
144
|
-
await inputarea.sendKeys(selenium_webdriver_1.Key.UP);
|
|
145
148
|
if (originalClipboard.length > 0) {
|
|
146
149
|
clipboard.writeSync(originalClipboard);
|
|
147
150
|
}
|
|
151
|
+
try {
|
|
152
|
+
// Restore original cursor position
|
|
153
|
+
await this.setCursor(line, col);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
await this.sendKeys(selenium_webdriver_1.Key.UP);
|
|
157
|
+
}
|
|
148
158
|
return text;
|
|
149
159
|
});
|
|
150
160
|
}
|
|
@@ -166,7 +176,9 @@ class TextEditor extends Editor_1.Editor {
|
|
|
166
176
|
}
|
|
167
177
|
const inputarea = await this.findElement(TextEditor.locators.Editor.inputArea);
|
|
168
178
|
clipboard.writeSync(text);
|
|
169
|
-
|
|
179
|
+
const bench = new __1.Workbench();
|
|
180
|
+
await bench.executeCommand('editor.action.selectAll');
|
|
181
|
+
await inputarea.sendKeys(selenium_webdriver_1.Key.chord(TextEditor.ctlKey, 'v'));
|
|
170
182
|
if (originalClipboard.length > 0) {
|
|
171
183
|
clipboard.writeSync(originalClipboard);
|
|
172
184
|
}
|
|
@@ -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.0",
|
|
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": "2333228475aff111f91b91d06e50a5b98e371886"
|
|
53
53
|
}
|