@redhat-developer/page-objects 1.20.0 → 1.22.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.
Files changed (60) hide show
  1. package/out/components/AbstractElement.d.ts +1 -1
  2. package/out/components/AbstractElement.js +3 -2
  3. package/out/components/ElementWithContextMenu.d.ts +11 -0
  4. package/out/components/ElementWithContextMenu.js +63 -15
  5. package/out/components/WebviewMixin.js +73 -7
  6. package/out/components/activityBar/ActionsControl.js +1 -1
  7. package/out/components/activityBar/ViewControl.d.ts +16 -0
  8. package/out/components/activityBar/ViewControl.js +45 -8
  9. package/out/components/bottomBar/AbstractViews.js +2 -2
  10. package/out/components/bottomBar/BottomBarPanel.js +26 -11
  11. package/out/components/bottomBar/ProblemsView.js +2 -2
  12. package/out/components/bottomBar/Views.js +1 -1
  13. package/out/components/editor/ContentAssist.d.ts +9 -1
  14. package/out/components/editor/ContentAssist.js +98 -20
  15. package/out/components/editor/CustomEditor.js +2 -2
  16. package/out/components/editor/EditorAction.js +1 -1
  17. package/out/components/editor/EditorView.d.ts +6 -0
  18. package/out/components/editor/EditorView.js +73 -3
  19. package/out/components/editor/SettingsEditor.d.ts +1 -0
  20. package/out/components/editor/SettingsEditor.js +20 -4
  21. package/out/components/editor/TextEditor.d.ts +4 -1
  22. package/out/components/editor/TextEditor.js +99 -62
  23. package/out/components/editor/WebView.js +6 -1
  24. package/out/components/menu/ContextMenu.d.ts +1 -1
  25. package/out/components/menu/ContextMenu.js +9 -6
  26. package/out/components/menu/Menu.js +3 -1
  27. package/out/components/menu/TitleBar.js +1 -1
  28. package/out/components/sidebar/ViewItem.js +1 -0
  29. package/out/components/sidebar/ViewSection.js +2 -2
  30. package/out/components/sidebar/ViewTitlePart.js +2 -2
  31. package/out/components/sidebar/debug/DebugView.d.ts +10 -0
  32. package/out/components/sidebar/debug/DebugView.js +45 -13
  33. package/out/components/sidebar/extensions/ExtensionsViewItem.js +1 -1
  34. package/out/components/sidebar/scm/NewScmView.js +1 -1
  35. package/out/components/sidebar/scm/ScmView.js +2 -2
  36. package/out/components/sidebar/tree/custom/CustomTreeItem.js +2 -1
  37. package/out/components/sidebar/tree/debug/CallStackItem.js +1 -1
  38. package/out/components/sidebar/tree/debug/WatchSectionItem.js +1 -1
  39. package/out/components/sidebar/tree/default/DefaultTreeItem.js +3 -3
  40. package/out/components/sidebar/tree/default/DefaultTreeSection.js +6 -0
  41. package/out/components/statusBar/StatusBar.js +2 -2
  42. package/out/components/workbench/DebugToolbar.js +1 -1
  43. package/out/components/workbench/Notification.js +3 -3
  44. package/out/components/workbench/NotificationsCenter.js +6 -1
  45. package/out/components/workbench/Workbench.js +1 -1
  46. package/out/components/workbench/input/Input.d.ts +18 -0
  47. package/out/components/workbench/input/Input.js +70 -2
  48. package/out/components/workbench/input/InputBox.js +2 -2
  49. package/out/components/workbench/input/QuickOpenBox.js +2 -2
  50. package/out/errors/ExTesterError.js +1 -1
  51. package/out/index.d.ts +3 -1
  52. package/out/index.js +33 -3
  53. package/out/locators/loader.d.ts +2 -1
  54. package/out/locators/loader.js +2 -2
  55. package/out/locators/locators.d.ts +5 -2
  56. package/out/locators/locators.js +28 -5
  57. package/out/utils/TimeoutConstants.d.ts +59 -0
  58. package/out/utils/TimeoutConstants.js +63 -0
  59. package/out/utils/WaitHelper.js +8 -2
  60. package/package.json +8 -6
@@ -28,7 +28,7 @@ class CustomEditor extends __1.Editor {
28
28
  * @returns WebView page object
29
29
  */
30
30
  getWebView() {
31
- return new __1.WebView();
31
+ return new __1.WebView(this.enclosingItem);
32
32
  }
33
33
  /**
34
34
  * Check if the editor has unsaved changes
@@ -36,7 +36,7 @@ class CustomEditor extends __1.Editor {
36
36
  */
37
37
  async isDirty() {
38
38
  const tab = await this.getTab();
39
- const klass = await tab.getAttribute('class');
39
+ const klass = (await tab.getAttribute('class'));
40
40
  return klass.includes('dirty');
41
41
  }
42
42
  /**
@@ -26,7 +26,7 @@ class BaseEditorAction extends ActionButtonElementDropdown_1.ActionButtonElement
26
26
  * Get text description of the action.
27
27
  */
28
28
  async getTitle() {
29
- return await this.getAttribute(BaseEditorAction.locators.EditorView.attribute);
29
+ return (await this.getAttribute(BaseEditorAction.locators.EditorView.attribute));
30
30
  }
31
31
  }
32
32
  class EditorAction extends BaseEditorAction {
@@ -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
- while (groups.length > 0 && (await groups[0].getOpenEditorTitles()).length > 0) {
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,39 @@ 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
- await closeButton.click();
255
+ // VS Code renders hover tooltip overlays (<div class="hover-contents">) that can
256
+ // intercept the click (ElementClickInterceptedError) on CI. Retry with an
257
+ // increasing delay; fall back to a JS-executor click on the last attempt which
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
+ }
254
278
  }
255
279
  /**
256
280
  * Close all open editor tabs
257
281
  * @returns Promise resolving once all tabs have had their close button pressed
258
282
  */
259
283
  async closeAllEditors() {
284
+ const deadline = Date.now() + 30000;
260
285
  let titles = await this.getOpenEditorTitles();
261
- while (titles.length > 0) {
286
+ while (titles.length > 0 && Date.now() < deadline) {
287
+ const previousCount = titles.length;
262
288
  await this.closeEditor(titles[0]);
263
289
  try {
264
290
  // check if the group still exists
@@ -268,6 +294,50 @@ class EditorGroup extends AbstractElement_1.AbstractElement {
268
294
  break;
269
295
  }
270
296
  titles = await this.getOpenEditorTitles();
297
+ if (titles.length >= previousCount) {
298
+ // The tab did not close: a dirty editor raises a "save changes?" modal
299
+ // that blocks the close. Discard the changes so the editor actually
300
+ // closes — otherwise the modal is left intercepting every subsequent
301
+ // click and cascades the rest of the suite into failure. Fall back to
302
+ // Escape (which cancels the modal) if the dialog cannot be handled.
303
+ if (!(await this.discardDirtyEditorDialog())) {
304
+ try {
305
+ await this.getDriver().actions().sendKeys(selenium_webdriver_1.Key.ESCAPE).perform();
306
+ }
307
+ catch {
308
+ /* ignore */
309
+ }
310
+ }
311
+ await this.getWaitHelper().sleep(500);
312
+ titles = await this.getOpenEditorTitles();
313
+ if (titles.length >= previousCount) {
314
+ break;
315
+ }
316
+ }
317
+ }
318
+ }
319
+ /**
320
+ * If a "save changes?" modal dialog is currently blocking the workbench,
321
+ * dismiss it by discarding the changes ("Don't Save") so the underlying
322
+ * dirty editor closes. Returns true when a dialog was found and handled.
323
+ */
324
+ async discardDirtyEditorDialog() {
325
+ const dialogs = await this.getDriver().findElements(EditorView.locators.Dialog.constructor);
326
+ if (dialogs.length === 0) {
327
+ return false;
328
+ }
329
+ try {
330
+ const dialog = new ModalDialog_1.ModalDialog();
331
+ await dialog.pushButton(`Don't Save`);
332
+ await this.getDriver()
333
+ .wait(selenium_webdriver_1.until.stalenessOf(dialogs[0]), 5000)
334
+ .catch(() => {
335
+ /* dialog may already be gone */
336
+ });
337
+ return true;
338
+ }
339
+ catch {
340
+ return false;
271
341
  }
272
342
  }
273
343
  /**
@@ -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 setting;
124
+ return undefined;
109
125
  }
110
126
  /**
111
127
  * Switch between settings perspectives
@@ -203,7 +219,7 @@ exports.Setting = Setting;
203
219
  class ComboSetting extends Setting {
204
220
  async getValue() {
205
221
  const combo = await this.findElement(SettingsEditor.locators.SettingsEditor.comboSetting);
206
- return await combo.getAttribute(SettingsEditor.locators.SettingsEditor.comboValue);
222
+ return (await combo.getAttribute(SettingsEditor.locators.SettingsEditor.comboValue));
207
223
  }
208
224
  async setValue(value) {
209
225
  const rows = await this.getOptions();
@@ -233,7 +249,7 @@ class ComboSetting extends Setting {
233
249
  const rows = await this.getOptions();
234
250
  for (const row of rows) {
235
251
  try {
236
- values.push(await row.getAttribute(SettingsEditor.locators.SettingsEditor.comboValue));
252
+ values.push((await row.getAttribute(SettingsEditor.locators.SettingsEditor.comboValue)));
237
253
  }
238
254
  catch (e) {
239
255
  if (e.name === 'StaleElementReferenceError') {
@@ -256,7 +272,7 @@ exports.ComboSetting = ComboSetting;
256
272
  class TextSetting extends Setting {
257
273
  async getValue() {
258
274
  const input = await this.findElement(SettingsEditor.locators.SettingsEditor.textSetting);
259
- return await input.getAttribute('value');
275
+ return (await input.getAttribute('value'));
260
276
  }
261
277
  async setValue(value) {
262
278
  const input = await this.findElement(SettingsEditor.locators.SettingsEditor.textSetting);
@@ -221,8 +221,11 @@ 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]>;
228
+ private getTabSize;
226
229
  /**
227
230
  * Toggle breakpoint on a given line
228
231
  *
@@ -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 url_1 = require("url");
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");
@@ -40,8 +40,8 @@ class TextEditor extends Editor_1.Editor {
40
40
  */
41
41
  async isDirty() {
42
42
  const tab = await this.enclosingItem.findElement(TextEditor.locators.TextEditor.activeTab);
43
- const klass = await tab.getAttribute('class');
44
- return klass.indexOf('dirty') >= 0;
43
+ const klass = (await tab.getAttribute('class'));
44
+ return klass.includes('dirty');
45
45
  }
46
46
  /**
47
47
  * Saves the active editor
@@ -67,14 +67,14 @@ class TextEditor extends Editor_1.Editor {
67
67
  */
68
68
  async getFileUri() {
69
69
  const ed = await this.findElement(TextEditor.locators.TextEditor.editorContainer);
70
- return await ed.getAttribute(TextEditor.locators.TextEditor.dataUri);
70
+ return (await ed.getAttribute(TextEditor.locators.TextEditor.dataUri));
71
71
  }
72
72
  /**
73
73
  * Retrieve the path to the file opened in the active editor
74
74
  * @returns Promise resolving to editor's underlying file path
75
75
  */
76
76
  async getFilePath() {
77
- return (0, url_1.fileURLToPath)(await this.getFileUri());
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
@@ -87,7 +87,7 @@ class TextEditor extends Editor_1.Editor {
87
87
  let isHidden = true;
88
88
  try {
89
89
  const assist = await self.findElement(TextEditor.locators.ContentAssist.constructor);
90
- const klass = await assist.getAttribute('class');
90
+ const klass = (await assist.getAttribute('class'));
91
91
  const visibility = await assist.getCssValue('visibility');
92
92
  isHidden = klass.indexOf('visible') < 0 || visibility === 'hidden';
93
93
  }
@@ -179,6 +179,13 @@ class TextEditor extends Editor_1.Editor {
179
179
  * @returns Promise resolving once the text is deleted
180
180
  */
181
181
  async clearText() {
182
+ // Dismiss any open menu or overlay first (e.g. context menu left open after
183
+ // formatDocument), then focus the editor container so the inputArea becomes
184
+ // interactable. We press Escape via the driver (safe regardless of focus) and
185
+ // click the outer editor element — NOT the native-edit-context, which cannot
186
+ // receive a synthetic click when it is not the active focus target.
187
+ await this.getDriver().actions().sendKeys(selenium_webdriver_1.Key.ESCAPE).perform();
188
+ await this.enclosingItem.click();
182
189
  return this.withRecovery(async (self) => {
183
190
  const input = await self.findElement(TextEditor.locators.Editor.inputArea);
184
191
  await input.sendKeys(selenium_webdriver_1.Key.chord(TextEditor.ctlKey, 'a'));
@@ -236,7 +243,7 @@ class TextEditor extends Editor_1.Editor {
236
243
  if (lineNum < 1) {
237
244
  throw new Error(`Text '${text}' not found`);
238
245
  }
239
- await this.moveCursor(lineNum, column);
246
+ await this.setCursor(lineNum, column);
240
247
  let actions = this.getDriver().actions();
241
248
  await actions.clear();
242
249
  actions.keyDown(selenium_webdriver_1.Key.SHIFT);
@@ -320,7 +327,7 @@ class TextEditor extends Editor_1.Editor {
320
327
  * @returns Promise resolving when the text is typed in
321
328
  */
322
329
  async typeTextAt(line, column, text) {
323
- await this.moveCursor(line, column);
330
+ await this.setCursor(line, column);
324
331
  await this.typeText(text);
325
332
  }
326
333
  /**
@@ -341,12 +348,16 @@ class TextEditor extends Editor_1.Editor {
341
348
  async setCursor(line, column, timeout = 2_500) {
342
349
  const input = await new __1.Workbench().openCommandPrompt();
343
350
  await input.setText(`:${line},${column}`);
344
- // Wait for input to be processed - check for quick picks appearing
345
- await this.getWaitHelper().forCondition(async () => {
346
- const picks = await input.getQuickPicks().catch(() => []);
347
- return picks.length > 0;
348
- }, { timeout: 1000, pollInterval: 50 });
349
- await input.selectQuickPick(0);
351
+ try {
352
+ await this.getWaitHelper().forCondition(async () => {
353
+ const picks = await input.getQuickPicks().catch(() => []);
354
+ return picks.length > 0;
355
+ }, { timeout: 2000, pollInterval: 50 });
356
+ await input.selectQuickPick(0);
357
+ }
358
+ catch {
359
+ await input.confirm();
360
+ }
350
361
  await this.waitForCursorPositionAt(line, column, timeout);
351
362
  }
352
363
  /**
@@ -489,11 +500,15 @@ class TextEditor extends Editor_1.Editor {
489
500
  let column = -1;
490
501
  let found = 0;
491
502
  const lines = (await this.getText()).split('\n');
503
+ const tabSize = await this.getTabSize();
492
504
  for (let i = 0; i < lines.length; i++) {
493
505
  let searchIndex = -1; // start at the beginning of the line
494
506
  while ((searchIndex = lines[i].indexOf(text, searchIndex)) > -1) {
495
507
  found++;
496
- column = searchIndex + 1;
508
+ // Compute offset for tabs
509
+ const nTabs = lines[i].substring(0, searchIndex + 1).split('\t').length - 1;
510
+ const tabOffset = nTabs * (tabSize - 1); // 1 character per tab has already been counted ('\t') - now offset for the tab size
511
+ column = searchIndex + 1 + tabOffset;
497
512
  line = i + 1;
498
513
  if (found >= occurrence) {
499
514
  return [line, column];
@@ -529,19 +544,9 @@ class TextEditor extends Editor_1.Editor {
529
544
  }
530
545
  async openContextMenu() {
531
546
  await this.getDriver().actions().contextClick(this).perform();
532
- const shadowRootHost = await this.enclosingItem.findElements(TextEditor.locators.TextEditor.shadowRootHost);
533
- if (shadowRootHost.length > 0) {
534
- let shadowRoot;
535
- const webdriverCapabilities = await this.getDriver().getCapabilities();
536
- const chromiumVersion = webdriverCapabilities.getBrowserVersion();
537
- if (chromiumVersion && parseInt(chromiumVersion.split('.')[0]) >= 96) {
538
- shadowRoot = await shadowRootHost[0].getShadowRoot();
539
- return new __1.ContextMenu(await shadowRoot.findElement(TextEditor.locators.TextEditor.monacoMenuContainer)).wait();
540
- }
541
- else {
542
- shadowRoot = (await this.getDriver().executeScript('return arguments[0].shadowRoot', shadowRootHost[0]));
543
- return new __1.ContextMenu(shadowRoot).wait();
544
- }
547
+ const menu = await this.waitForShadowRootMenu();
548
+ if (menu) {
549
+ return menu;
545
550
  }
546
551
  return await super.openContextMenu();
547
552
  }
@@ -549,15 +554,36 @@ class TextEditor extends Editor_1.Editor {
549
554
  * Get the cursor's coordinates as an array of two numbers: `[line, column]`
550
555
  *
551
556
  * **Caution** line & column coordinates do not start at `0` but at `1`!
557
+ *
558
+ * @param timeout ms to wait for the status bar to show a valid position (default 5000)
552
559
  */
553
- async getCoordinates() {
554
- const coords = [];
560
+ async getCoordinates(timeout = 5_000) {
555
561
  const statusBar = new StatusBar_1.StatusBar();
556
- const coordinates = (await statusBar.getCurrentPosition()).match(/\d+/g);
557
- for (const c of coordinates) {
558
- coords.push(+c);
559
- }
560
- return [coords[0], coords[1]];
562
+ // The status bar position item may not update immediately after a cursor move,
563
+ // especially on slow CI runners. Poll until it returns a valid "Ln X, Col Y" string.
564
+ let positionText = '';
565
+ await this.getDriver().wait(async () => {
566
+ try {
567
+ const text = await statusBar.getCurrentPosition();
568
+ const match = text.match(/\d+/g);
569
+ if (match && match.length >= 2) {
570
+ positionText = text;
571
+ return true;
572
+ }
573
+ }
574
+ catch {
575
+ // Status bar item not yet available — keep polling
576
+ }
577
+ return false;
578
+ }, timeout, `Status bar did not show a valid cursor position within ${timeout}ms`);
579
+ const coordinates = positionText.match(/\d+/g);
580
+ return [+coordinates[0], +coordinates[1]];
581
+ }
582
+ async getTabSize() {
583
+ const statusBar = new StatusBar_1.StatusBar();
584
+ const indent = await statusBar.getCurrentIndentation();
585
+ const matches = indent.match(/\d+/g);
586
+ return +matches[0];
561
587
  }
562
588
  /**
563
589
  * Toggle breakpoint on a given line
@@ -575,7 +601,7 @@ class TextEditor extends Editor_1.Editor {
575
601
  : lineOverlay;
576
602
  const breakPoint = await breakpointContainer.findElements(TextEditor.locators.TextEditor.breakpoint.generalSelector);
577
603
  if (breakPoint.length > 0) {
578
- if (this.breakPoints.indexOf(line) !== -1) {
604
+ if (this.breakPoints.includes(line)) {
579
605
  await breakPoint[this.breakPoints.indexOf(line)].click();
580
606
  // Wait for breakpoint to be removed
581
607
  await this.getWaitHelper().forCondition(async () => {
@@ -655,15 +681,23 @@ class TextEditor extends Editor_1.Editor {
655
681
  async getBreakpoints() {
656
682
  const breakpoints = [];
657
683
  const breakpointLocators = Breakpoint_1.Breakpoint.locators.TextEditor.breakpoint;
658
- const breakpointContainer = (0, compare_versions_1.satisfies)(TextEditor.versionInfo.version, '>=1.80.0')
659
- ? await this.findElement(TextEditor.locators.TextEditor.glyphMarginWidget)
660
- : this;
684
+ const isNewVersion = (0, compare_versions_1.satisfies)(TextEditor.versionInfo.version, '>=1.80.0');
685
+ const breakpointContainer = isNewVersion ? await this.findElement(TextEditor.locators.TextEditor.glyphMarginWidget) : this;
661
686
  const breakpointsSelectors = await breakpointContainer.findElements(breakpointLocators.generalSelector);
687
+ // When a debug session is paused at a breakpoint, VS Code renders two overlapping
688
+ // elements with the `codicon-debug-breakpoint` class at the same vertical position
689
+ // (the regular breakpoint dot and the stackframe arrow). Deduplicate by `top` so
690
+ // that each line contributes at most one Breakpoint.
691
+ const seenTops = new Set();
662
692
  for (const breakpointSelector of breakpointsSelectors) {
663
693
  try {
664
694
  let lineElement;
665
- if ((0, compare_versions_1.satisfies)(TextEditor.versionInfo.version, '>=1.80.0')) {
695
+ if (isNewVersion) {
666
696
  const styleTopAttr = await breakpointSelector.getCssValue('top');
697
+ if (seenTops.has(styleTopAttr)) {
698
+ continue;
699
+ }
700
+ seenTops.add(styleTopAttr);
667
701
  lineElement = await this.findElement(TextEditor.locators.TextEditor.marginArea).findElement(TextEditor.locators.TextEditor.lineElement(styleTopAttr));
668
702
  }
669
703
  else {
@@ -686,14 +720,23 @@ class TextEditor extends Editor_1.Editor {
686
720
  */
687
721
  async getCodeLenses() {
688
722
  const lenses = [];
689
- const widgets = await this.findElement(TextEditor.locators.TextEditor.contentWidgets);
690
- const items = await widgets.findElements(TextEditor.locators.TextEditor.contentWidgetsElements);
723
+ // Use findElements so that a missing/not-yet-visible contentWidgets container
724
+ // returns an empty array rather than throwing a TimeoutError. The caller
725
+ // (or the before-hook poll loop) is responsible for retrying until lenses appear.
726
+ const widgetContainers = await this.findElements(TextEditor.locators.TextEditor.contentWidgets);
727
+ if (widgetContainers.length === 0) {
728
+ return lenses;
729
+ }
730
+ const items = await widgetContainers[0].findElements(TextEditor.locators.TextEditor.contentWidgetsElements);
691
731
  for (const item of items) {
692
732
  try {
693
733
  lenses.push(await new CodeLens(item, this).wait());
694
734
  }
695
735
  catch (e) {
696
- if (e.name === 'StaleElementReferenceError') {
736
+ // Skip stale or not-yet-visible lens elements. TimeoutError means the
737
+ // lens widget exists in the DOM but is not visible yet (e.g. the editor
738
+ // is still rendering); the caller's poll loop will retry the whole call.
739
+ if (e.name === 'StaleElementReferenceError' || e.name === 'TimeoutError') {
697
740
  continue;
698
741
  }
699
742
  throw e;
@@ -769,19 +812,9 @@ class Selection extends ElementWithContextMenu_1.ElementWithContextMenu {
769
812
  }
770
813
  }
771
814
  }
772
- const shadowRootHost = await ed.getEnclosingElement().findElements(TextEditor.locators.TextEditor.shadowRootHost);
773
- if (shadowRootHost.length > 0) {
774
- let shadowRoot;
775
- const webdriverCapabilities = await this.getDriver().getCapabilities();
776
- const chromiumVersion = webdriverCapabilities.getBrowserVersion();
777
- if (chromiumVersion && parseInt(chromiumVersion.split('.')[0]) >= 96) {
778
- shadowRoot = await shadowRootHost[0].getShadowRoot();
779
- return new __1.ContextMenu(await shadowRoot.findElement(TextEditor.locators.TextEditor.monacoMenuContainer)).wait();
780
- }
781
- else {
782
- shadowRoot = (await this.getDriver().executeScript('return arguments[0].shadowRoot', shadowRootHost[0]));
783
- return new __1.ContextMenu(shadowRoot).wait();
784
- }
815
+ const menu = await this.waitForShadowRootMenu();
816
+ if (menu) {
817
+ return menu;
785
818
  }
786
819
  return await super.openContextMenu();
787
820
  }
@@ -795,7 +828,7 @@ class CodeLens extends AbstractElement_1.AbstractElement {
795
828
  * @returns tooltip as string
796
829
  */
797
830
  async getTooltip() {
798
- return await this.getAttribute('title');
831
+ return (await this.getAttribute('title'));
799
832
  }
800
833
  }
801
834
  exports.CodeLens = CodeLens;
@@ -817,7 +850,7 @@ class FindWidget extends AbstractElement_1.AbstractElement {
817
850
  */
818
851
  async toggleReplace(replace) {
819
852
  const btn = await this.findElement(FindWidget.locators.FindWidget.toggleReplace);
820
- const klass = await btn.getAttribute('class');
853
+ const klass = (await btn.getAttribute('class'));
821
854
  if ((replace && klass.includes('collapsed')) || (!replace && !klass.includes('collapsed'))) {
822
855
  await btn.sendKeys(selenium_webdriver_1.Key.SPACE);
823
856
  const repl = await this.getDriver().wait(selenium_webdriver_1.until.elementLocated(FindWidget.locators.FindWidget.replacePart), 2000);
@@ -843,7 +876,7 @@ class FindWidget extends AbstractElement_1.AbstractElement {
843
876
  */
844
877
  async getSearchText() {
845
878
  const findPart = await this.findElement(FindWidget.locators.FindWidget.findPart);
846
- return await this.getInputText(findPart);
879
+ return (await this.getInputText(findPart));
847
880
  }
848
881
  /**
849
882
  * Set text in the replace box. Will toggle replace mode on if called in find mode.
@@ -860,7 +893,7 @@ class FindWidget extends AbstractElement_1.AbstractElement {
860
893
  */
861
894
  async getReplaceText() {
862
895
  const replacePart = await this.findElement(FindWidget.locators.FindWidget.replacePart);
863
- return await this.getInputText(replacePart);
896
+ return (await this.getInputText(replacePart));
864
897
  }
865
898
  /**
866
899
  * Click 'Next match'
@@ -963,8 +996,12 @@ class FindWidget extends AbstractElement_1.AbstractElement {
963
996
  }
964
997
  const btn = await element.findElement(FindWidget.locators.FindWidget.button(title));
965
998
  await btn.click();
966
- // Wait for button action to complete
967
- await this.getWaitHelper().forStable(element, { timeout: 500 });
999
+ // Give the widget a moment to settle after the action (animations, relayout).
1000
+ // Best-effort: a slow animation must not fail the interaction that already
1001
+ // happened, so a stability timeout is not an error here.
1002
+ await this.getWaitHelper()
1003
+ .forStable(element, { timeout: 2000 })
1004
+ .catch(() => undefined);
968
1005
  }
969
1006
  async setText(text, composite) {
970
1007
  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
- return (0, locators_1.findBestContainingElement)(await this.getRect(), frames);
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
- super(ContextMenu.locators.ContextMenu.constructor, containingElement);
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
@@ -61,7 +64,7 @@ class ContextMenu extends __1.Menu {
61
64
  const elements = await this.findElements(ContextMenu.locators.ContextMenu.itemElement);
62
65
  for (const element of elements) {
63
66
  try {
64
- const klass = await element.getAttribute('class');
67
+ const klass = (await element.getAttribute('class'));
65
68
  if (klass.indexOf('disabled') < 0) {
66
69
  items.push(await new ContextMenuItem(element, this).wait());
67
70
  }
@@ -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) {
@@ -158,7 +161,7 @@ class ContextMenuItem extends __1.MenuItem {
158
161
  }
159
162
  async getLabel() {
160
163
  const labelItem = await this.findElement(ContextMenu.locators.ContextMenu.itemLabel);
161
- return await labelItem.getAttribute(ContextMenu.locators.ContextMenu.itemText);
164
+ return (await labelItem.getAttribute(ContextMenu.locators.ContextMenu.itemText));
162
165
  }
163
166
  async isNesting() {
164
167
  try {
@@ -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;
@@ -52,7 +52,7 @@ class TitleBar extends Menu_1.Menu {
52
52
  for (const element of elements) {
53
53
  try {
54
54
  if (await element.isDisplayed()) {
55
- items.push(await new TitleBarItem(await element.getAttribute(TitleBar.locators.TitleBar.itemLabel), this).wait());
55
+ items.push(await new TitleBarItem((await element.getAttribute(TitleBar.locators.TitleBar.itemLabel)), this).wait());
56
56
  }
57
57
  }
58
58
  catch (e) {