@redhat-developer/page-objects 1.21.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 +12 -1
- package/out/components/ElementWithContextMenu.d.ts +11 -0
- package/out/components/ElementWithContextMenu.js +63 -15
- package/out/components/WebviewMixin.js +73 -7
- package/out/components/activityBar/ViewControl.d.ts +16 -0
- package/out/components/activityBar/ViewControl.js +45 -8
- package/out/components/bottomBar/BottomBarPanel.js +26 -11
- package/out/components/editor/ContentAssist.d.ts +9 -1
- package/out/components/editor/ContentAssist.js +88 -13
- package/out/components/editor/CustomEditor.js +1 -1
- package/out/components/editor/EditorView.d.ts +6 -0
- package/out/components/editor/EditorView.js +53 -3
- package/out/components/editor/SettingsEditor.d.ts +1 -0
- package/out/components/editor/SettingsEditor.js +17 -1
- package/out/components/editor/TextEditor.d.ts +3 -1
- package/out/components/editor/TextEditor.js +95 -56
- package/out/components/editor/WebView.js +6 -1
- package/out/components/menu/ContextMenu.d.ts +1 -1
- package/out/components/menu/ContextMenu.js +7 -4
- package/out/components/menu/Menu.js +3 -1
- package/out/components/sidebar/ViewItem.js +1 -0
- package/out/components/sidebar/tree/default/DefaultTreeSection.js +6 -0
- package/out/components/workbench/DebugToolbar.js +1 -1
- package/out/components/workbench/NotificationsCenter.js +6 -1
- package/out/components/workbench/Workbench.js +1 -1
- package/out/components/workbench/input/Input.d.ts +18 -0
- package/out/components/workbench/input/Input.js +68 -0
- package/out/components/workbench/input/QuickOpenBox.js +1 -1
- package/out/errors/ExTesterError.js +1 -1
- package/out/index.d.ts +1 -0
- package/out/index.js +1 -0
- package/out/locators/locators.js +26 -3
- package/out/utils/TimeoutConstants.d.ts +59 -0
- package/out/utils/TimeoutConstants.js +63 -0
- package/out/utils/WaitHelper.d.ts +31 -0
- package/out/utils/WaitHelper.js +78 -2
- package/package.json +6 -3
|
@@ -120,6 +120,12 @@ export declare class EditorGroup extends AbstractElement {
|
|
|
120
120
|
* @returns Promise resolving once all tabs have had their close button pressed
|
|
121
121
|
*/
|
|
122
122
|
closeAllEditors(): Promise<void>;
|
|
123
|
+
/**
|
|
124
|
+
* If a "save changes?" modal dialog is currently blocking the workbench,
|
|
125
|
+
* dismiss it by discarding the changes ("Don't Save") so the underlying
|
|
126
|
+
* dirty editor closes. Returns true when a dialog was found and handled.
|
|
127
|
+
*/
|
|
128
|
+
private discardDirtyEditorDialog;
|
|
123
129
|
/**
|
|
124
130
|
* Retrieve all open editor tab titles in an array
|
|
125
131
|
* @returns Promise resolving to array of editor titles
|
|
@@ -25,6 +25,7 @@ const DiffEditor_1 = require("./DiffEditor");
|
|
|
25
25
|
const EditorAction_1 = require("./EditorAction");
|
|
26
26
|
const SettingsEditor_1 = require("./SettingsEditor");
|
|
27
27
|
const WebView_1 = require("./WebView");
|
|
28
|
+
const ModalDialog_1 = require("../dialog/ModalDialog");
|
|
28
29
|
class EditorTabNotFound extends Error {
|
|
29
30
|
constructor(title, group) {
|
|
30
31
|
super(`No editor with title '${title}' in group '${group}' available`);
|
|
@@ -68,7 +69,8 @@ class EditorView extends AbstractElement_1.AbstractElement {
|
|
|
68
69
|
if (groupIndex !== undefined) {
|
|
69
70
|
return groups[groupIndex].closeAllEditors();
|
|
70
71
|
}
|
|
71
|
-
|
|
72
|
+
const deadline = Date.now() + 60000;
|
|
73
|
+
while (groups.length > 0 && (await groups[0].getOpenEditorTitles()).length > 0 && Date.now() < deadline) {
|
|
72
74
|
await groups[0].closeAllEditors();
|
|
73
75
|
// Brief wait for DOM to settle after closing editors
|
|
74
76
|
await this.getWaitHelper().sleep(500);
|
|
@@ -250,15 +252,19 @@ class EditorGroup extends AbstractElement_1.AbstractElement {
|
|
|
250
252
|
async closeEditor(title) {
|
|
251
253
|
const tab = await this.getTabByTitle(title);
|
|
252
254
|
const closeButton = await tab.findElement(EditorView.locators.EditorView.closeTab);
|
|
253
|
-
|
|
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);
|
|
254
258
|
}
|
|
255
259
|
/**
|
|
256
260
|
* Close all open editor tabs
|
|
257
261
|
* @returns Promise resolving once all tabs have had their close button pressed
|
|
258
262
|
*/
|
|
259
263
|
async closeAllEditors() {
|
|
264
|
+
const deadline = Date.now() + 30000;
|
|
260
265
|
let titles = await this.getOpenEditorTitles();
|
|
261
|
-
while (titles.length > 0) {
|
|
266
|
+
while (titles.length > 0 && Date.now() < deadline) {
|
|
267
|
+
const previousCount = titles.length;
|
|
262
268
|
await this.closeEditor(titles[0]);
|
|
263
269
|
try {
|
|
264
270
|
// check if the group still exists
|
|
@@ -268,6 +274,50 @@ class EditorGroup extends AbstractElement_1.AbstractElement {
|
|
|
268
274
|
break;
|
|
269
275
|
}
|
|
270
276
|
titles = await this.getOpenEditorTitles();
|
|
277
|
+
if (titles.length >= previousCount) {
|
|
278
|
+
// The tab did not close: a dirty editor raises a "save changes?" modal
|
|
279
|
+
// that blocks the close. Discard the changes so the editor actually
|
|
280
|
+
// closes — otherwise the modal is left intercepting every subsequent
|
|
281
|
+
// click and cascades the rest of the suite into failure. Fall back to
|
|
282
|
+
// Escape (which cancels the modal) if the dialog cannot be handled.
|
|
283
|
+
if (!(await this.discardDirtyEditorDialog())) {
|
|
284
|
+
try {
|
|
285
|
+
await this.getDriver().actions().sendKeys(selenium_webdriver_1.Key.ESCAPE).perform();
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
/* ignore */
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
await this.getWaitHelper().sleep(500);
|
|
292
|
+
titles = await this.getOpenEditorTitles();
|
|
293
|
+
if (titles.length >= previousCount) {
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* If a "save changes?" modal dialog is currently blocking the workbench,
|
|
301
|
+
* dismiss it by discarding the changes ("Don't Save") so the underlying
|
|
302
|
+
* dirty editor closes. Returns true when a dialog was found and handled.
|
|
303
|
+
*/
|
|
304
|
+
async discardDirtyEditorDialog() {
|
|
305
|
+
const dialogs = await this.getDriver().findElements(EditorView.locators.Dialog.constructor);
|
|
306
|
+
if (dialogs.length === 0) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
try {
|
|
310
|
+
const dialog = new ModalDialog_1.ModalDialog();
|
|
311
|
+
await dialog.pushButton(`Don't Save`);
|
|
312
|
+
await this.getDriver()
|
|
313
|
+
.wait(selenium_webdriver_1.until.stalenessOf(dialogs[0]), 5000)
|
|
314
|
+
.catch(() => {
|
|
315
|
+
/* dialog may already be gone */
|
|
316
|
+
});
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
return false;
|
|
271
321
|
}
|
|
272
322
|
}
|
|
273
323
|
/**
|
|
@@ -49,6 +49,7 @@ export declare class SettingsEditor extends Editor {
|
|
|
49
49
|
*/
|
|
50
50
|
findSettingByID(id: string): Promise<Setting>;
|
|
51
51
|
private _getSettingItem;
|
|
52
|
+
private scanForSettingItem;
|
|
52
53
|
/**
|
|
53
54
|
* Switch between settings perspectives
|
|
54
55
|
* Works only if your vscode instance has both user and workspace settings available
|
|
@@ -82,7 +82,23 @@ class SettingsEditor extends Editor_1.Editor {
|
|
|
82
82
|
const match = new RegExp(/\d+/).exec(text);
|
|
83
83
|
return match ? Number.parseInt(match[0], 10) : 0;
|
|
84
84
|
}, { timeout: 5000, stabilityInterval: 300, stableChecks: 2 });
|
|
85
|
+
// The count can stabilize before the search filter has been applied on slow
|
|
86
|
+
// machines — poll the rendered rows until the requested setting shows up
|
|
87
|
+
// instead of sampling them a single time.
|
|
88
|
+
const deadline = Date.now() + 10_000;
|
|
85
89
|
let setting;
|
|
90
|
+
for (;;) {
|
|
91
|
+
const found = await this.scanForSettingItem(title, category);
|
|
92
|
+
if (found) {
|
|
93
|
+
return found;
|
|
94
|
+
}
|
|
95
|
+
if (Date.now() >= deadline) {
|
|
96
|
+
return setting;
|
|
97
|
+
}
|
|
98
|
+
await this.getWaitHelper().sleep(500);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async scanForSettingItem(title, category) {
|
|
86
102
|
const items = await this.findElements(SettingsEditor.locators.SettingsEditor.itemRow);
|
|
87
103
|
for (const item of items) {
|
|
88
104
|
try {
|
|
@@ -105,7 +121,7 @@ class SettingsEditor extends Editor_1.Editor {
|
|
|
105
121
|
// do nothing
|
|
106
122
|
}
|
|
107
123
|
}
|
|
108
|
-
return
|
|
124
|
+
return undefined;
|
|
109
125
|
}
|
|
110
126
|
/**
|
|
111
127
|
* Switch between settings perspectives
|
|
@@ -221,8 +221,10 @@ export declare class TextEditor extends Editor {
|
|
|
221
221
|
* Get the cursor's coordinates as an array of two numbers: `[line, column]`
|
|
222
222
|
*
|
|
223
223
|
* **Caution** line & column coordinates do not start at `0` but at `1`!
|
|
224
|
+
*
|
|
225
|
+
* @param timeout ms to wait for the status bar to show a valid position (default 5000)
|
|
224
226
|
*/
|
|
225
|
-
getCoordinates(): Promise<[number, number]>;
|
|
227
|
+
getCoordinates(timeout?: number): Promise<[number, number]>;
|
|
226
228
|
private getTabSize;
|
|
227
229
|
/**
|
|
228
230
|
* Toggle breakpoint on a given line
|
|
@@ -19,7 +19,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
19
19
|
exports.FindWidget = exports.CodeLens = exports.TextEditor = exports.BreakpointError = void 0;
|
|
20
20
|
const __1 = require("../..");
|
|
21
21
|
const selenium_webdriver_1 = require("selenium-webdriver");
|
|
22
|
-
const
|
|
22
|
+
const node_url_1 = require("node:url");
|
|
23
23
|
const StatusBar_1 = require("../statusBar/StatusBar");
|
|
24
24
|
const Editor_1 = require("./Editor");
|
|
25
25
|
const ElementWithContextMenu_1 = require("../ElementWithContextMenu");
|
|
@@ -41,7 +41,7 @@ class TextEditor extends Editor_1.Editor {
|
|
|
41
41
|
async isDirty() {
|
|
42
42
|
const tab = await this.enclosingItem.findElement(TextEditor.locators.TextEditor.activeTab);
|
|
43
43
|
const klass = (await tab.getAttribute('class'));
|
|
44
|
-
return klass.
|
|
44
|
+
return klass.includes('dirty');
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
47
|
* Saves the active editor
|
|
@@ -74,7 +74,7 @@ class TextEditor extends Editor_1.Editor {
|
|
|
74
74
|
* @returns Promise resolving to editor's underlying file path
|
|
75
75
|
*/
|
|
76
76
|
async getFilePath() {
|
|
77
|
-
return (0,
|
|
77
|
+
return (0, node_url_1.fileURLToPath)(await this.getFileUri());
|
|
78
78
|
}
|
|
79
79
|
/**
|
|
80
80
|
* Open/Close the content assistant at the current position in the editor by sending the default
|
|
@@ -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
|
}
|
|
@@ -179,6 +191,13 @@ class TextEditor extends Editor_1.Editor {
|
|
|
179
191
|
* @returns Promise resolving once the text is deleted
|
|
180
192
|
*/
|
|
181
193
|
async clearText() {
|
|
194
|
+
// Dismiss any open menu or overlay first (e.g. context menu left open after
|
|
195
|
+
// formatDocument), then focus the editor container so the inputArea becomes
|
|
196
|
+
// interactable. We press Escape via the driver (safe regardless of focus) and
|
|
197
|
+
// click the outer editor element — NOT the native-edit-context, which cannot
|
|
198
|
+
// receive a synthetic click when it is not the active focus target.
|
|
199
|
+
await this.getDriver().actions().sendKeys(selenium_webdriver_1.Key.ESCAPE).perform();
|
|
200
|
+
await this.enclosingItem.click();
|
|
182
201
|
return this.withRecovery(async (self) => {
|
|
183
202
|
const input = await self.findElement(TextEditor.locators.Editor.inputArea);
|
|
184
203
|
await input.sendKeys(selenium_webdriver_1.Key.chord(TextEditor.ctlKey, 'a'));
|
|
@@ -341,12 +360,16 @@ class TextEditor extends Editor_1.Editor {
|
|
|
341
360
|
async setCursor(line, column, timeout = 2_500) {
|
|
342
361
|
const input = await new __1.Workbench().openCommandPrompt();
|
|
343
362
|
await input.setText(`:${line},${column}`);
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
363
|
+
try {
|
|
364
|
+
await this.getWaitHelper().forCondition(async () => {
|
|
365
|
+
const picks = await input.getQuickPicks().catch(() => []);
|
|
366
|
+
return picks.length > 0;
|
|
367
|
+
}, { timeout: 2000, pollInterval: 50 });
|
|
368
|
+
await input.selectQuickPick(0);
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
await input.confirm();
|
|
372
|
+
}
|
|
350
373
|
await this.waitForCursorPositionAt(line, column, timeout);
|
|
351
374
|
}
|
|
352
375
|
/**
|
|
@@ -533,19 +556,9 @@ class TextEditor extends Editor_1.Editor {
|
|
|
533
556
|
}
|
|
534
557
|
async openContextMenu() {
|
|
535
558
|
await this.getDriver().actions().contextClick(this).perform();
|
|
536
|
-
const
|
|
537
|
-
if (
|
|
538
|
-
|
|
539
|
-
const webdriverCapabilities = await this.getDriver().getCapabilities();
|
|
540
|
-
const chromiumVersion = webdriverCapabilities.getBrowserVersion();
|
|
541
|
-
if (chromiumVersion && parseInt(chromiumVersion.split('.')[0]) >= 96) {
|
|
542
|
-
shadowRoot = await shadowRootHost[0].getShadowRoot();
|
|
543
|
-
return new __1.ContextMenu(await shadowRoot.findElement(TextEditor.locators.TextEditor.monacoMenuContainer)).wait();
|
|
544
|
-
}
|
|
545
|
-
else {
|
|
546
|
-
shadowRoot = (await this.getDriver().executeScript('return arguments[0].shadowRoot', shadowRootHost[0]));
|
|
547
|
-
return new __1.ContextMenu(shadowRoot).wait();
|
|
548
|
-
}
|
|
559
|
+
const menu = await this.waitForShadowRootMenu();
|
|
560
|
+
if (menu) {
|
|
561
|
+
return menu;
|
|
549
562
|
}
|
|
550
563
|
return await super.openContextMenu();
|
|
551
564
|
}
|
|
@@ -553,15 +566,30 @@ class TextEditor extends Editor_1.Editor {
|
|
|
553
566
|
* Get the cursor's coordinates as an array of two numbers: `[line, column]`
|
|
554
567
|
*
|
|
555
568
|
* **Caution** line & column coordinates do not start at `0` but at `1`!
|
|
569
|
+
*
|
|
570
|
+
* @param timeout ms to wait for the status bar to show a valid position (default 5000)
|
|
556
571
|
*/
|
|
557
|
-
async getCoordinates() {
|
|
558
|
-
const coords = [];
|
|
572
|
+
async getCoordinates(timeout = 5_000) {
|
|
559
573
|
const statusBar = new StatusBar_1.StatusBar();
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
574
|
+
// The status bar position item may not update immediately after a cursor move,
|
|
575
|
+
// especially on slow CI runners. Poll until it returns a valid "Ln X, Col Y" string.
|
|
576
|
+
let positionText = '';
|
|
577
|
+
await this.getDriver().wait(async () => {
|
|
578
|
+
try {
|
|
579
|
+
const text = await statusBar.getCurrentPosition();
|
|
580
|
+
const match = text.match(/\d+/g);
|
|
581
|
+
if (match && match.length >= 2) {
|
|
582
|
+
positionText = text;
|
|
583
|
+
return true;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
catch {
|
|
587
|
+
// Status bar item not yet available — keep polling
|
|
588
|
+
}
|
|
589
|
+
return false;
|
|
590
|
+
}, timeout, `Status bar did not show a valid cursor position within ${timeout}ms`);
|
|
591
|
+
const coordinates = positionText.match(/\d+/g);
|
|
592
|
+
return [+coordinates[0], +coordinates[1]];
|
|
565
593
|
}
|
|
566
594
|
async getTabSize() {
|
|
567
595
|
const statusBar = new StatusBar_1.StatusBar();
|
|
@@ -585,7 +613,7 @@ class TextEditor extends Editor_1.Editor {
|
|
|
585
613
|
: lineOverlay;
|
|
586
614
|
const breakPoint = await breakpointContainer.findElements(TextEditor.locators.TextEditor.breakpoint.generalSelector);
|
|
587
615
|
if (breakPoint.length > 0) {
|
|
588
|
-
if (this.breakPoints.
|
|
616
|
+
if (this.breakPoints.includes(line)) {
|
|
589
617
|
await breakPoint[this.breakPoints.indexOf(line)].click();
|
|
590
618
|
// Wait for breakpoint to be removed
|
|
591
619
|
await this.getWaitHelper().forCondition(async () => {
|
|
@@ -665,15 +693,23 @@ class TextEditor extends Editor_1.Editor {
|
|
|
665
693
|
async getBreakpoints() {
|
|
666
694
|
const breakpoints = [];
|
|
667
695
|
const breakpointLocators = Breakpoint_1.Breakpoint.locators.TextEditor.breakpoint;
|
|
668
|
-
const
|
|
669
|
-
|
|
670
|
-
: this;
|
|
696
|
+
const isNewVersion = (0, compare_versions_1.satisfies)(TextEditor.versionInfo.version, '>=1.80.0');
|
|
697
|
+
const breakpointContainer = isNewVersion ? await this.findElement(TextEditor.locators.TextEditor.glyphMarginWidget) : this;
|
|
671
698
|
const breakpointsSelectors = await breakpointContainer.findElements(breakpointLocators.generalSelector);
|
|
699
|
+
// When a debug session is paused at a breakpoint, VS Code renders two overlapping
|
|
700
|
+
// elements with the `codicon-debug-breakpoint` class at the same vertical position
|
|
701
|
+
// (the regular breakpoint dot and the stackframe arrow). Deduplicate by `top` so
|
|
702
|
+
// that each line contributes at most one Breakpoint.
|
|
703
|
+
const seenTops = new Set();
|
|
672
704
|
for (const breakpointSelector of breakpointsSelectors) {
|
|
673
705
|
try {
|
|
674
706
|
let lineElement;
|
|
675
|
-
if (
|
|
707
|
+
if (isNewVersion) {
|
|
676
708
|
const styleTopAttr = await breakpointSelector.getCssValue('top');
|
|
709
|
+
if (seenTops.has(styleTopAttr)) {
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
seenTops.add(styleTopAttr);
|
|
677
713
|
lineElement = await this.findElement(TextEditor.locators.TextEditor.marginArea).findElement(TextEditor.locators.TextEditor.lineElement(styleTopAttr));
|
|
678
714
|
}
|
|
679
715
|
else {
|
|
@@ -696,14 +732,23 @@ class TextEditor extends Editor_1.Editor {
|
|
|
696
732
|
*/
|
|
697
733
|
async getCodeLenses() {
|
|
698
734
|
const lenses = [];
|
|
699
|
-
|
|
700
|
-
|
|
735
|
+
// Use findElements so that a missing/not-yet-visible contentWidgets container
|
|
736
|
+
// returns an empty array rather than throwing a TimeoutError. The caller
|
|
737
|
+
// (or the before-hook poll loop) is responsible for retrying until lenses appear.
|
|
738
|
+
const widgetContainers = await this.findElements(TextEditor.locators.TextEditor.contentWidgets);
|
|
739
|
+
if (widgetContainers.length === 0) {
|
|
740
|
+
return lenses;
|
|
741
|
+
}
|
|
742
|
+
const items = await widgetContainers[0].findElements(TextEditor.locators.TextEditor.contentWidgetsElements);
|
|
701
743
|
for (const item of items) {
|
|
702
744
|
try {
|
|
703
745
|
lenses.push(await new CodeLens(item, this).wait());
|
|
704
746
|
}
|
|
705
747
|
catch (e) {
|
|
706
|
-
|
|
748
|
+
// Skip stale or not-yet-visible lens elements. TimeoutError means the
|
|
749
|
+
// lens widget exists in the DOM but is not visible yet (e.g. the editor
|
|
750
|
+
// is still rendering); the caller's poll loop will retry the whole call.
|
|
751
|
+
if (e.name === 'StaleElementReferenceError' || e.name === 'TimeoutError') {
|
|
707
752
|
continue;
|
|
708
753
|
}
|
|
709
754
|
throw e;
|
|
@@ -779,19 +824,9 @@ class Selection extends ElementWithContextMenu_1.ElementWithContextMenu {
|
|
|
779
824
|
}
|
|
780
825
|
}
|
|
781
826
|
}
|
|
782
|
-
const
|
|
783
|
-
if (
|
|
784
|
-
|
|
785
|
-
const webdriverCapabilities = await this.getDriver().getCapabilities();
|
|
786
|
-
const chromiumVersion = webdriverCapabilities.getBrowserVersion();
|
|
787
|
-
if (chromiumVersion && parseInt(chromiumVersion.split('.')[0]) >= 96) {
|
|
788
|
-
shadowRoot = await shadowRootHost[0].getShadowRoot();
|
|
789
|
-
return new __1.ContextMenu(await shadowRoot.findElement(TextEditor.locators.TextEditor.monacoMenuContainer)).wait();
|
|
790
|
-
}
|
|
791
|
-
else {
|
|
792
|
-
shadowRoot = (await this.getDriver().executeScript('return arguments[0].shadowRoot', shadowRootHost[0]));
|
|
793
|
-
return new __1.ContextMenu(shadowRoot).wait();
|
|
794
|
-
}
|
|
827
|
+
const menu = await this.waitForShadowRootMenu();
|
|
828
|
+
if (menu) {
|
|
829
|
+
return menu;
|
|
795
830
|
}
|
|
796
831
|
return await super.openContextMenu();
|
|
797
832
|
}
|
|
@@ -973,8 +1008,12 @@ class FindWidget extends AbstractElement_1.AbstractElement {
|
|
|
973
1008
|
}
|
|
974
1009
|
const btn = await element.findElement(FindWidget.locators.FindWidget.button(title));
|
|
975
1010
|
await btn.click();
|
|
976
|
-
//
|
|
977
|
-
|
|
1011
|
+
// Give the widget a moment to settle after the action (animations, relayout).
|
|
1012
|
+
// Best-effort: a slow animation must not fail the interaction that already
|
|
1013
|
+
// happened, so a stability timeout is not an error here.
|
|
1014
|
+
await this.getWaitHelper()
|
|
1015
|
+
.forStable(element, { timeout: 2000 })
|
|
1016
|
+
.catch(() => undefined);
|
|
978
1017
|
}
|
|
979
1018
|
async setText(text, composite) {
|
|
980
1019
|
const input = await composite.findElement(FindWidget.locators.FindWidget.input);
|
|
@@ -29,7 +29,12 @@ const locators_1 = require("../../locators/locators");
|
|
|
29
29
|
class WebViewBase extends Editor_1.Editor {
|
|
30
30
|
async getViewToSwitchTo() {
|
|
31
31
|
const frames = await this.getDriver().findElements(WebViewBase.locators.WebView.iframe);
|
|
32
|
-
|
|
32
|
+
if (frames.length === 0) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
// Try geometry-based selection first; fall back to the first available frame
|
|
36
|
+
// when the editor container has not been laid out yet (common on slow CI runners).
|
|
37
|
+
return (await (0, locators_1.findBestContainingElement)(await this.getRect(), frames)) ?? frames[0];
|
|
33
38
|
}
|
|
34
39
|
}
|
|
35
40
|
exports.WebView = (0, WebviewMixin_1.default)(WebViewBase);
|
|
@@ -20,7 +20,7 @@ import { WebElement } from 'selenium-webdriver';
|
|
|
20
20
|
* Object representing a context menu
|
|
21
21
|
*/
|
|
22
22
|
export declare class ContextMenu extends Menu {
|
|
23
|
-
constructor(containingElement: WebElement);
|
|
23
|
+
constructor(containingElement: WebElement, menuElement?: WebElement);
|
|
24
24
|
/**
|
|
25
25
|
* Get context menu item by name
|
|
26
26
|
* @param name name of the item to search by
|
|
@@ -23,8 +23,11 @@ const selenium_webdriver_1 = require("selenium-webdriver");
|
|
|
23
23
|
* Object representing a context menu
|
|
24
24
|
*/
|
|
25
25
|
class ContextMenu extends __1.Menu {
|
|
26
|
-
constructor(containingElement) {
|
|
27
|
-
|
|
26
|
+
constructor(containingElement, menuElement) {
|
|
27
|
+
// menuElement covers menus whose container cannot be found by locator
|
|
28
|
+
// from the containing element, e.g. the menu inside a shadow root where
|
|
29
|
+
// the root's only child IS the menu container.
|
|
30
|
+
super(menuElement ?? ContextMenu.locators.ContextMenu.constructor, containingElement);
|
|
28
31
|
}
|
|
29
32
|
/**
|
|
30
33
|
* Get context menu item by name
|
|
@@ -84,7 +87,7 @@ class ContextMenu extends __1.Menu {
|
|
|
84
87
|
await actions.clear();
|
|
85
88
|
await this.getDriver().actions().sendKeys(selenium_webdriver_1.Key.ESCAPE).perform();
|
|
86
89
|
try {
|
|
87
|
-
await this.getDriver().wait(selenium_webdriver_1.until.elementIsNotVisible(this));
|
|
90
|
+
await this.getDriver().wait(selenium_webdriver_1.until.elementIsNotVisible(this), 5_000);
|
|
88
91
|
}
|
|
89
92
|
catch (err) {
|
|
90
93
|
if (!(err instanceof selenium_webdriver_1.error.StaleElementReferenceError)) {
|
|
@@ -146,7 +149,7 @@ class ContextMenuItem extends __1.MenuItem {
|
|
|
146
149
|
const actions = this.getDriver().actions();
|
|
147
150
|
await actions.clear();
|
|
148
151
|
await this.getDriver().actions().sendKeys(selenium_webdriver_1.Key.ESCAPE).perform();
|
|
149
|
-
await this.getDriver().wait(selenium_webdriver_1.until.elementIsNotVisible(this));
|
|
152
|
+
await this.getDriver().wait(selenium_webdriver_1.until.elementIsNotVisible(this), 5_000);
|
|
150
153
|
}
|
|
151
154
|
}
|
|
152
155
|
catch (err) {
|
|
@@ -52,9 +52,11 @@ class Menu extends AbstractElement_1.AbstractElement {
|
|
|
52
52
|
if (!item) {
|
|
53
53
|
return parent;
|
|
54
54
|
}
|
|
55
|
+
// Guard with an explicit timeout so the wait never hangs indefinitely
|
|
56
|
+
// on CI where the implicit/default Selenium timeout may be 0 or very low.
|
|
55
57
|
await Menu.driver.wait(async function () {
|
|
56
58
|
return (await item.isDisplayed()) && (await item.isEnabled());
|
|
57
|
-
});
|
|
59
|
+
}, 5000);
|
|
58
60
|
const submenu = await item.select();
|
|
59
61
|
if (submenu) {
|
|
60
62
|
parent = submenu;
|
|
@@ -68,6 +68,7 @@ class TreeItem extends ViewItem {
|
|
|
68
68
|
async expand() {
|
|
69
69
|
if ((await this.isExpandable()) && !(await this.isExpanded())) {
|
|
70
70
|
await (await this.findTwistie()).click();
|
|
71
|
+
await this.getDriver().wait(() => this.isExpanded(), 5000, 'Tree item did not expand within 5s');
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
/**
|
|
@@ -45,7 +45,13 @@ class DefaultTreeSection extends TreeSection_1.TreeSection {
|
|
|
45
45
|
const container = await this.findElement(DefaultTreeSection.locators.DefaultTreeSection.rowContainer);
|
|
46
46
|
await container.sendKeys(selenium_webdriver_1.Key.HOME);
|
|
47
47
|
let item = undefined;
|
|
48
|
+
const maxIterations = 500;
|
|
49
|
+
let iteration = 0;
|
|
48
50
|
do {
|
|
51
|
+
if (iteration++ >= maxIterations) {
|
|
52
|
+
console.warn(`findItem('${label}'): exceeded ${maxIterations} scroll iterations, giving up`);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
49
55
|
const temp = await container.findElements(DefaultTreeSection.locators.DefaultTreeItem.ctor(label));
|
|
50
56
|
if (temp.length > 0) {
|
|
51
57
|
const level = +(await temp[0].getAttribute(DefaultTreeSection.locators.ViewSection.level));
|
|
@@ -42,7 +42,7 @@ class DebugToolbar extends AbstractElement_1.AbstractElement {
|
|
|
42
42
|
* Wait for the execution to pause at the next breakpoint
|
|
43
43
|
*/
|
|
44
44
|
async waitForBreakPoint(timeout = 10_000) {
|
|
45
|
-
let btn = await this.getDriver().wait(selenium_webdriver_1.until.elementLocated(DebugToolbar.locators.DebugToolbar.button('continue')));
|
|
45
|
+
let btn = await this.getDriver().wait(selenium_webdriver_1.until.elementLocated(DebugToolbar.locators.DebugToolbar.button('continue')), timeout);
|
|
46
46
|
await this.getDriver().wait(async () => {
|
|
47
47
|
try {
|
|
48
48
|
const enabled = await btn.isEnabled();
|
|
@@ -48,7 +48,12 @@ class NotificationsCenter extends AbstractElement_1.AbstractElement {
|
|
|
48
48
|
* @returns Promise resolving when the clear all button is pressed
|
|
49
49
|
*/
|
|
50
50
|
async clearAllNotifications() {
|
|
51
|
-
await this.findElement(NotificationsCenter.locators.NotificationsCenter.clear)
|
|
51
|
+
const btn = await this.findElement(NotificationsCenter.locators.NotificationsCenter.clear);
|
|
52
|
+
const classes = (await btn.getAttribute('class')) ?? '';
|
|
53
|
+
if (classes.includes('disabled')) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
await this.getDriver().executeScript('arguments[0].click()', btn);
|
|
52
57
|
}
|
|
53
58
|
/**
|
|
54
59
|
* Get all notifications of a given type
|
|
@@ -115,7 +115,7 @@ class Workbench extends AbstractElement_1.AbstractElement {
|
|
|
115
115
|
async openSettings() {
|
|
116
116
|
await this.executeCommand('Preferences: Open User Settings');
|
|
117
117
|
await new EditorView_1.EditorView().openEditor('Settings');
|
|
118
|
-
await Workbench.driver.wait(selenium_webdriver_1.until.elementLocated(Workbench.locators.Editor.constructor));
|
|
118
|
+
await Workbench.driver.wait(selenium_webdriver_1.until.elementLocated(Workbench.locators.Editor.constructor), 10_000);
|
|
119
119
|
// Wait for settings editor to fully load
|
|
120
120
|
const editor = new SettingsEditor_1.SettingsEditor();
|
|
121
121
|
await this.getWaitHelper().forStable(editor, { timeout: 2000 });
|
|
@@ -41,6 +41,24 @@ export declare abstract class Input extends AbstractElement {
|
|
|
41
41
|
* @returns Promise resolving when the input is confirmed
|
|
42
42
|
*/
|
|
43
43
|
confirm(): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Confirm path selection for file/folder dialog.
|
|
46
|
+
*
|
|
47
|
+
* VS Code's file picker auto-navigates as text is typed: when a full path such as
|
|
48
|
+
* `/dir/file.txt` is in the input, the picker shows the parent directory contents
|
|
49
|
+
* with the filename filtered in the list. A single Enter press navigates into the
|
|
50
|
+
* matched list entry. A subsequent click on the OK button (or a second Enter) then
|
|
51
|
+
* confirms the selection and closes the dialog.
|
|
52
|
+
*
|
|
53
|
+
* When the path is not formatted to OS specifics there may first be a 'Select' step.
|
|
54
|
+
* See also https://github.com/redhat-developer/vscode-extension-tester/issues/1778
|
|
55
|
+
*/
|
|
56
|
+
confirmPath(): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Best-effort check whether this input/picker widget is still displayed.
|
|
59
|
+
* A hidden, detached or stale widget counts as closed.
|
|
60
|
+
*/
|
|
61
|
+
private isPickerOpen;
|
|
44
62
|
/**
|
|
45
63
|
* Cancel the input field by pressing Escape
|
|
46
64
|
* @returns Promise resolving when the input is cancelled
|