@redhat-developer/page-objects 1.17.0 → 1.18.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 (36) hide show
  1. package/out/components/AbstractElement.d.ts +77 -1
  2. package/out/components/AbstractElement.js +169 -12
  3. package/out/components/ActionButtonElementDropdown.js +14 -2
  4. package/out/components/activityBar/ViewControl.js +5 -1
  5. package/out/components/bottomBar/Views.js +25 -12
  6. package/out/components/editor/ContentAssist.js +2 -1
  7. package/out/components/editor/EditorView.js +50 -9
  8. package/out/components/editor/ExtensionEditorDetailsSection.js +31 -7
  9. package/out/components/editor/SettingsEditor.js +110 -28
  10. package/out/components/editor/TextEditor.js +120 -25
  11. package/out/components/menu/ContextMenu.js +27 -6
  12. package/out/components/menu/MenuItem.js +14 -1
  13. package/out/components/menu/TitleBar.js +15 -3
  14. package/out/components/sidebar/ViewSection.js +14 -4
  15. package/out/components/sidebar/WelcomeContent.js +10 -2
  16. package/out/components/sidebar/debug/DebugView.js +10 -2
  17. package/out/components/sidebar/extensions/ExtensionsViewSection.js +20 -5
  18. package/out/components/sidebar/scm/NewScmView.js +53 -13
  19. package/out/components/sidebar/scm/ScmView.js +12 -4
  20. package/out/components/sidebar/tree/TreeSection.js +11 -3
  21. package/out/components/sidebar/tree/custom/CustomTreeSection.js +22 -6
  22. package/out/components/sidebar/tree/debug/WatchSection.js +5 -1
  23. package/out/components/sidebar/tree/default/DefaultTreeSection.js +9 -1
  24. package/out/components/workbench/Notification.js +10 -2
  25. package/out/components/workbench/NotificationsCenter.js +11 -3
  26. package/out/components/workbench/Workbench.js +14 -4
  27. package/out/components/workbench/input/Input.js +45 -16
  28. package/out/components/workbench/input/InputBox.js +20 -4
  29. package/out/components/workbench/input/QuickOpenBox.js +11 -3
  30. package/out/errors/ExTesterError.d.ts +112 -0
  31. package/out/errors/ExTesterError.js +201 -0
  32. package/out/index.d.ts +2 -0
  33. package/out/index.js +2 -0
  34. package/out/utils/WaitHelper.d.ts +202 -0
  35. package/out/utils/WaitHelper.js +465 -0
  36. package/package.json +4 -4
@@ -0,0 +1,465 @@
1
+ "use strict";
2
+ /**
3
+ * Licensed to the Apache Software Foundation (ASF) under one or more
4
+ * contributor license agreements. See the NOTICE file distributed with
5
+ * this work for additional information regarding copyright ownership.
6
+ * The ASF licenses this file to You under the Apache License, Version 2.0
7
+ * (the "License", destination); you may not use this file except in compliance with
8
+ * the License. You may obtain a copy of the License at
9
+ *
10
+ * https://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.WaitHelper = void 0;
20
+ exports.createWaitHelper = createWaitHelper;
21
+ const selenium_webdriver_1 = require("selenium-webdriver");
22
+ const DEFAULT_TIMEOUT = 5000;
23
+ const DEFAULT_POLL_INTERVAL = 100;
24
+ const DEFAULT_STABILITY_CHECKS = 3;
25
+ const DEFAULT_STABILITY_INTERVAL = 100;
26
+ /**
27
+ * Utility class for condition-based waiting operations.
28
+ * Replaces arbitrary sleep() calls with intelligent waiting strategies.
29
+ */
30
+ class WaitHelper {
31
+ driver;
32
+ defaultTimeout;
33
+ defaultPollInterval;
34
+ constructor(driver, defaultTimeout = DEFAULT_TIMEOUT, defaultPollInterval = DEFAULT_POLL_INTERVAL) {
35
+ this.driver = driver;
36
+ this.defaultTimeout = defaultTimeout;
37
+ this.defaultPollInterval = defaultPollInterval;
38
+ }
39
+ /**
40
+ * Wait for a condition to become truthy.
41
+ * @param condition Async function that returns a value - waiting stops when value is truthy
42
+ * @param options Wait configuration options
43
+ * @returns The truthy value returned by the condition
44
+ */
45
+ async forCondition(condition, options = {}) {
46
+ const { timeout = this.defaultTimeout, pollInterval = this.defaultPollInterval, message } = options;
47
+ const start = Date.now();
48
+ let lastError;
49
+ while (Date.now() - start < timeout) {
50
+ try {
51
+ const result = await condition();
52
+ if (result) {
53
+ return result;
54
+ }
55
+ }
56
+ catch (e) {
57
+ // Store error but continue retrying for transient errors
58
+ if (e instanceof selenium_webdriver_1.error.StaleElementReferenceError || e instanceof selenium_webdriver_1.error.NoSuchElementError) {
59
+ lastError = e;
60
+ }
61
+ else {
62
+ throw e;
63
+ }
64
+ }
65
+ await this.sleep(pollInterval);
66
+ }
67
+ const errorMessage = message || `Condition not met within ${timeout}ms`;
68
+ if (lastError) {
69
+ throw new Error(`${errorMessage}. Last error: ${lastError.message}`);
70
+ }
71
+ throw new Error(errorMessage);
72
+ }
73
+ /**
74
+ * Wait for an element's attribute to have a specific value.
75
+ * @param element The WebElement to check
76
+ * @param attribute Name of the attribute
77
+ * @param value Expected value
78
+ * @param options Wait configuration options
79
+ */
80
+ async forAttributeValue(element, attribute, value, options = {}) {
81
+ const { timeout = this.defaultTimeout, message } = options;
82
+ await this.forCondition(async () => (await element.getAttribute(attribute)) === value, {
83
+ ...options,
84
+ timeout,
85
+ message: message || `Attribute '${attribute}' did not become '${value}' within ${timeout}ms`,
86
+ });
87
+ }
88
+ /**
89
+ * Wait for an element's attribute to contain a specific value.
90
+ * @param element The WebElement to check
91
+ * @param attribute Name of the attribute
92
+ * @param value Value to search for
93
+ * @param options Wait configuration options
94
+ */
95
+ async forAttributeContains(element, attribute, value, options = {}) {
96
+ const { timeout = this.defaultTimeout, message } = options;
97
+ await this.forCondition(async () => {
98
+ const attrValue = await element.getAttribute(attribute);
99
+ return attrValue?.includes(value);
100
+ }, {
101
+ ...options,
102
+ timeout,
103
+ message: message || `Attribute '${attribute}' did not contain '${value}' within ${timeout}ms`,
104
+ });
105
+ }
106
+ /**
107
+ * Wait for an element to become stable (position/size stops changing).
108
+ * Useful for waiting after animations or dynamic content loading.
109
+ *
110
+ * Note: If the element becomes stale (removed from DOM), this method returns
111
+ * successfully since element removal is a form of "stability".
112
+ *
113
+ * @param element The WebElement to monitor
114
+ * @param options Stability configuration options
115
+ */
116
+ async forStable(element, options = {}) {
117
+ const { timeout = this.defaultTimeout, stableChecks = DEFAULT_STABILITY_CHECKS, stabilityInterval = DEFAULT_STABILITY_INTERVAL, message } = options;
118
+ const start = Date.now();
119
+ let lastRect;
120
+ let stableCount = 0;
121
+ try {
122
+ lastRect = await element.getRect();
123
+ }
124
+ catch (e) {
125
+ if (e instanceof selenium_webdriver_1.error.StaleElementReferenceError || e instanceof selenium_webdriver_1.error.NoSuchElementError) {
126
+ // Element already gone - consider it stable
127
+ return;
128
+ }
129
+ throw e;
130
+ }
131
+ while (Date.now() - start < timeout) {
132
+ await this.sleep(stabilityInterval);
133
+ try {
134
+ const currentRect = await element.getRect();
135
+ if (this.rectsEqual(lastRect, currentRect)) {
136
+ stableCount++;
137
+ if (stableCount >= stableChecks) {
138
+ return;
139
+ }
140
+ }
141
+ else {
142
+ stableCount = 0;
143
+ lastRect = currentRect;
144
+ }
145
+ }
146
+ catch (e) {
147
+ if (e instanceof selenium_webdriver_1.error.StaleElementReferenceError || e instanceof selenium_webdriver_1.error.NoSuchElementError) {
148
+ // Element was removed from DOM - consider it stable/done
149
+ return;
150
+ }
151
+ throw e;
152
+ }
153
+ }
154
+ throw new Error(message || `Element did not stabilize within ${timeout}ms`);
155
+ }
156
+ /**
157
+ * Wait for an element to become visible.
158
+ * @param element The WebElement to check
159
+ * @param options Wait configuration options
160
+ */
161
+ async forVisible(element, options = {}) {
162
+ const { timeout = this.defaultTimeout, message } = options;
163
+ await this.driver.wait(selenium_webdriver_1.until.elementIsVisible(element), timeout, message || `Element did not become visible within ${timeout}ms`);
164
+ }
165
+ /**
166
+ * Wait for an element to become invisible.
167
+ * @param element The WebElement to check
168
+ * @param options Wait configuration options
169
+ */
170
+ async forNotVisible(element, options = {}) {
171
+ const { timeout = this.defaultTimeout, message } = options;
172
+ await this.driver.wait(selenium_webdriver_1.until.elementIsNotVisible(element), timeout, message || `Element did not become invisible within ${timeout}ms`);
173
+ }
174
+ /**
175
+ * Wait for an element to become enabled.
176
+ * @param element The WebElement to check
177
+ * @param options Wait configuration options
178
+ */
179
+ async forEnabled(element, options = {}) {
180
+ const { timeout = this.defaultTimeout, message } = options;
181
+ await this.driver.wait(selenium_webdriver_1.until.elementIsEnabled(element), timeout, message || `Element did not become enabled within ${timeout}ms`);
182
+ }
183
+ /**
184
+ * Wait for the number of elements matching a condition to stabilize.
185
+ * Useful for lists that are loading items dynamically.
186
+ * @param getCount Function that returns the current count
187
+ * @param options Stability configuration options
188
+ */
189
+ async forCountStable(getCount, options = {}) {
190
+ const { timeout = this.defaultTimeout, stableChecks = DEFAULT_STABILITY_CHECKS, stabilityInterval = DEFAULT_STABILITY_INTERVAL, message } = options;
191
+ const start = Date.now();
192
+ let lastCount = await getCount();
193
+ let stableCount = 0;
194
+ while (Date.now() - start < timeout) {
195
+ await this.sleep(stabilityInterval);
196
+ const currentCount = await getCount();
197
+ if (currentCount === lastCount) {
198
+ stableCount++;
199
+ if (stableCount >= stableChecks) {
200
+ return currentCount;
201
+ }
202
+ }
203
+ else {
204
+ stableCount = 0;
205
+ lastCount = currentCount;
206
+ }
207
+ }
208
+ throw new Error(message || `Count did not stabilize within ${timeout}ms`);
209
+ }
210
+ /**
211
+ * Wait for text content to be present in an element.
212
+ * @param element The WebElement to check
213
+ * @param text Expected text (partial match)
214
+ * @param options Wait configuration options
215
+ */
216
+ async forTextPresent(element, text, options = {}) {
217
+ const { timeout = this.defaultTimeout, message } = options;
218
+ await this.forCondition(async () => {
219
+ const elementText = await element.getText();
220
+ return elementText.includes(text);
221
+ }, {
222
+ ...options,
223
+ timeout,
224
+ message: message || `Text '${text}' did not appear within ${timeout}ms`,
225
+ });
226
+ }
227
+ /**
228
+ * Wait for an element's class list to contain a specific class.
229
+ * @param element The WebElement to check
230
+ * @param className Class name to look for
231
+ * @param options Wait configuration options
232
+ */
233
+ async forClass(element, className, options = {}) {
234
+ await this.forAttributeContains(element, 'class', className, {
235
+ ...options,
236
+ message: options.message || `Element did not get class '${className}' within ${options.timeout || this.defaultTimeout}ms`,
237
+ });
238
+ }
239
+ /**
240
+ * Wait for an element's class list to NOT contain a specific class.
241
+ * @param element The WebElement to check
242
+ * @param className Class name that should not be present
243
+ * @param options Wait configuration options
244
+ */
245
+ async forNoClass(element, className, options = {}) {
246
+ const { timeout = this.defaultTimeout, message } = options;
247
+ await this.forCondition(async () => {
248
+ const classAttr = await element.getAttribute('class');
249
+ return !classAttr?.includes(className);
250
+ }, {
251
+ ...options,
252
+ timeout,
253
+ message: message || `Element still has class '${className}' after ${timeout}ms`,
254
+ });
255
+ }
256
+ /**
257
+ * Wait for an element to be clickable (visible AND enabled).
258
+ * @param element The WebElement to check
259
+ * @param options Wait configuration options
260
+ */
261
+ async forClickable(element, options = {}) {
262
+ const { timeout = this.defaultTimeout, message } = options;
263
+ await this.forCondition(async () => {
264
+ try {
265
+ const isDisplayed = await element.isDisplayed();
266
+ const isEnabled = await element.isEnabled();
267
+ return isDisplayed && isEnabled;
268
+ }
269
+ catch (e) {
270
+ if (e instanceof selenium_webdriver_1.error.StaleElementReferenceError) {
271
+ return false;
272
+ }
273
+ throw e;
274
+ }
275
+ }, {
276
+ ...options,
277
+ timeout,
278
+ message: message || `Element did not become clickable within ${timeout}ms`,
279
+ });
280
+ }
281
+ /**
282
+ * Wait for text content to match exactly or a regex pattern.
283
+ * @param element The WebElement to check
284
+ * @param expected Expected text string or RegExp pattern
285
+ * @param options Wait configuration options
286
+ */
287
+ async forTextContent(element, expected, options = {}) {
288
+ const { timeout = this.defaultTimeout, message } = options;
289
+ await this.forCondition(async () => {
290
+ const text = await element.getText();
291
+ if (expected instanceof RegExp) {
292
+ return expected.test(text);
293
+ }
294
+ return text === expected;
295
+ }, {
296
+ ...options,
297
+ timeout,
298
+ message: message || `Text did not match '${expected}' within ${timeout}ms`,
299
+ });
300
+ }
301
+ /**
302
+ * Wait for a specific number of elements to be present.
303
+ * @param parent Parent element or driver to search within
304
+ * @param locator Locator for the elements
305
+ * @param expectedCount Expected number of elements
306
+ * @param options Wait configuration options
307
+ */
308
+ async forElementCount(parent, locator, expectedCount, options = {}) {
309
+ const { timeout = this.defaultTimeout, message } = options;
310
+ const result = await this.forCondition(async () => {
311
+ const elements = await parent.findElements(locator);
312
+ return elements.length === expectedCount ? elements : null;
313
+ }, {
314
+ ...options,
315
+ timeout,
316
+ message: message || `Expected ${expectedCount} elements but condition not met within ${timeout}ms`,
317
+ });
318
+ return result;
319
+ }
320
+ /**
321
+ * Wait for at least one element matching the locator to be present.
322
+ * @param parent Parent element or driver to search within
323
+ * @param locator Locator for the element
324
+ * @param options Wait configuration options
325
+ * @returns The first matching element
326
+ */
327
+ async forElementLocated(parent, locator, options = {}) {
328
+ const { timeout = this.defaultTimeout, message } = options;
329
+ const result = await this.forCondition(async () => {
330
+ const elements = await parent.findElements(locator);
331
+ return elements.length > 0 ? elements[0] : null;
332
+ }, {
333
+ ...options,
334
+ timeout,
335
+ message: message || `Element not found within ${timeout}ms`,
336
+ });
337
+ return result;
338
+ }
339
+ /**
340
+ * Wait for no elements matching the locator to be present (element removed).
341
+ * @param parent Parent element or driver to search within
342
+ * @param locator Locator for the element
343
+ * @param options Wait configuration options
344
+ */
345
+ async forNoElement(parent, locator, options = {}) {
346
+ const { timeout = this.defaultTimeout, message } = options;
347
+ await this.forCondition(async () => {
348
+ try {
349
+ const elements = await parent.findElements(locator);
350
+ return elements.length === 0;
351
+ }
352
+ catch (e) {
353
+ if (e instanceof selenium_webdriver_1.error.StaleElementReferenceError) {
354
+ return true; // Parent is gone, so element is definitely gone
355
+ }
356
+ throw e;
357
+ }
358
+ }, {
359
+ ...options,
360
+ timeout,
361
+ message: message || `Element still present after ${timeout}ms`,
362
+ });
363
+ }
364
+ /**
365
+ * Wait for element to be removed from DOM or become stale.
366
+ * @param element The WebElement to monitor
367
+ * @param options Wait configuration options
368
+ */
369
+ async forElementRemoved(element, options = {}) {
370
+ const { timeout = this.defaultTimeout, message } = options;
371
+ await this.forCondition(async () => {
372
+ try {
373
+ await element.getTagName(); // Will throw if element is stale
374
+ return false;
375
+ }
376
+ catch (e) {
377
+ if (e instanceof selenium_webdriver_1.error.StaleElementReferenceError || e instanceof selenium_webdriver_1.error.NoSuchElementError) {
378
+ return true;
379
+ }
380
+ throw e;
381
+ }
382
+ }, {
383
+ ...options,
384
+ timeout,
385
+ message: message || `Element was not removed within ${timeout}ms`,
386
+ });
387
+ }
388
+ /**
389
+ * Wait for any of the conditions to be met (OR logic).
390
+ * @param conditions Array of condition functions
391
+ * @param options Wait configuration options
392
+ * @returns Index of the first condition that was met
393
+ */
394
+ async forAnyCondition(conditions, options = {}) {
395
+ const { timeout = this.defaultTimeout, message } = options;
396
+ const result = await this.forCondition(async () => {
397
+ for (let i = 0; i < conditions.length; i++) {
398
+ try {
399
+ if (await conditions[i]()) {
400
+ return i;
401
+ }
402
+ }
403
+ catch {
404
+ // Continue checking other conditions
405
+ }
406
+ }
407
+ return null;
408
+ }, {
409
+ ...options,
410
+ timeout,
411
+ message: message || `None of the conditions were met within ${timeout}ms`,
412
+ });
413
+ return result;
414
+ }
415
+ /**
416
+ * Execute a function with automatic retry on failure.
417
+ * @param fn The function to execute
418
+ * @param options Retry configuration options
419
+ * @returns The result of the function
420
+ */
421
+ async withRetry(fn, options = {}) {
422
+ const { maxRetries = 3, retryDelay = 500, backoffMultiplier = 1, isRetryable = (e) => e.name === 'StaleElementReferenceError' || e.name === 'ElementNotInteractableError', } = options;
423
+ let lastError;
424
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
425
+ try {
426
+ return await fn();
427
+ }
428
+ catch (e) {
429
+ lastError = e;
430
+ if (!isRetryable(e) || attempt === maxRetries) {
431
+ throw e;
432
+ }
433
+ const delay = retryDelay * Math.pow(backoffMultiplier, attempt);
434
+ await this.sleep(delay);
435
+ }
436
+ }
437
+ // This should never be reached as the loop always throws or returns
438
+ throw lastError ?? new Error('withRetry: unexpected state - no result and no error');
439
+ }
440
+ /**
441
+ * Simple sleep helper - use sparingly and prefer condition-based waits.
442
+ * @param ms Milliseconds to sleep
443
+ */
444
+ async sleep(ms) {
445
+ await new Promise((resolve) => setTimeout(resolve, ms));
446
+ }
447
+ /**
448
+ * Get the WebDriver instance.
449
+ */
450
+ getDriver() {
451
+ return this.driver;
452
+ }
453
+ rectsEqual(a, b) {
454
+ return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
455
+ }
456
+ }
457
+ exports.WaitHelper = WaitHelper;
458
+ /**
459
+ * Create a WaitHelper instance bound to a WebDriver.
460
+ * Convenience factory function for quick access.
461
+ */
462
+ function createWaitHelper(driver, defaultTimeout) {
463
+ return new WaitHelper(driver, defaultTimeout);
464
+ }
465
+ //# sourceMappingURL=WaitHelper.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redhat-developer/page-objects",
3
- "version": "1.17.0",
3
+ "version": "1.18.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",
@@ -37,15 +37,15 @@
37
37
  "ui-test"
38
38
  ],
39
39
  "dependencies": {
40
- "clipboardy": "^5.0.0",
40
+ "clipboardy": "^5.0.2",
41
41
  "clone-deep": "^4.0.1",
42
42
  "compare-versions": "^6.1.1",
43
- "fs-extra": "^11.3.2",
43
+ "fs-extra": "^11.3.3",
44
44
  "type-fest": "^4.41.0"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "selenium-webdriver": ">=4.6.1",
48
48
  "typescript": ">=4.6.2"
49
49
  },
50
- "gitHead": "8321c63da1348f27716c18db771e978fbbb46852"
50
+ "gitHead": "41c9e28f480e39d23317c0e358c021873af48acf"
51
51
  }