@redhat-developer/page-objects 1.17.0 → 1.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/components/AbstractElement.d.ts +77 -1
- package/out/components/AbstractElement.js +169 -12
- package/out/components/ActionButtonElementDropdown.js +14 -2
- package/out/components/activityBar/ViewControl.js +5 -1
- package/out/components/bottomBar/Views.js +25 -12
- package/out/components/editor/ContentAssist.js +2 -1
- package/out/components/editor/EditorView.js +50 -9
- package/out/components/editor/ExtensionEditorDetailsSection.js +31 -7
- package/out/components/editor/SettingsEditor.js +110 -28
- package/out/components/editor/TextEditor.js +120 -25
- package/out/components/menu/ContextMenu.js +27 -6
- package/out/components/menu/MenuItem.js +14 -1
- package/out/components/menu/TitleBar.js +15 -3
- package/out/components/sidebar/ViewSection.js +14 -4
- package/out/components/sidebar/WelcomeContent.js +10 -2
- package/out/components/sidebar/debug/DebugView.js +10 -2
- package/out/components/sidebar/extensions/ExtensionsViewSection.js +20 -5
- package/out/components/sidebar/scm/NewScmView.js +53 -13
- package/out/components/sidebar/scm/ScmView.js +12 -4
- package/out/components/sidebar/tree/TreeSection.js +11 -3
- package/out/components/sidebar/tree/custom/CustomTreeSection.js +22 -6
- package/out/components/sidebar/tree/debug/WatchSection.js +5 -1
- package/out/components/sidebar/tree/default/DefaultTreeSection.js +9 -1
- package/out/components/workbench/Notification.js +10 -2
- package/out/components/workbench/NotificationsCenter.js +11 -3
- package/out/components/workbench/Workbench.js +14 -4
- package/out/components/workbench/input/Input.js +45 -16
- package/out/components/workbench/input/InputBox.js +20 -4
- package/out/components/workbench/input/QuickOpenBox.js +11 -3
- package/out/errors/ExTesterError.d.ts +112 -0
- package/out/errors/ExTesterError.js +201 -0
- package/out/index.d.ts +2 -0
- package/out/index.js +2 -0
- package/out/utils/WaitHelper.d.ts +202 -0
- package/out/utils/WaitHelper.js +465 -0
- package/package.json +4 -4
|
@@ -16,6 +16,17 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { WebElement, WebDriver, Locator } from 'selenium-webdriver';
|
|
18
18
|
import { Locators } from '../locators/locators';
|
|
19
|
+
import { WaitHelper } from '../utils/WaitHelper';
|
|
20
|
+
import { type ErrorContext } from '../errors/ExTesterError';
|
|
21
|
+
/**
|
|
22
|
+
* Information needed to relocate an element after it becomes stale.
|
|
23
|
+
*/
|
|
24
|
+
export interface LocatorInfo {
|
|
25
|
+
/** The locator used to find this element */
|
|
26
|
+
locator: Locator;
|
|
27
|
+
/** The parent element or locator this element was found within */
|
|
28
|
+
enclosingLocator?: Locator | WebElement;
|
|
29
|
+
}
|
|
19
30
|
/**
|
|
20
31
|
* Default wrapper for webElement
|
|
21
32
|
*/
|
|
@@ -27,7 +38,13 @@ export declare abstract class AbstractElement extends WebElement {
|
|
|
27
38
|
version: string;
|
|
28
39
|
browser: string;
|
|
29
40
|
};
|
|
41
|
+
protected static waitHelper: WaitHelper;
|
|
30
42
|
protected enclosingItem: WebElement;
|
|
43
|
+
/**
|
|
44
|
+
* Locator information for auto-recovery when element becomes stale.
|
|
45
|
+
* Stored when element is created with a Locator (not a WebElement).
|
|
46
|
+
*/
|
|
47
|
+
protected locatorInfo?: LocatorInfo;
|
|
31
48
|
/**
|
|
32
49
|
* Constructs a new element from a Locator or an existing WebElement
|
|
33
50
|
* @param base WebDriver compatible Locator for the given element or a reference to an existing WebElement
|
|
@@ -43,11 +60,70 @@ export declare abstract class AbstractElement extends WebElement {
|
|
|
43
60
|
* @returns thenable self reference
|
|
44
61
|
*/
|
|
45
62
|
wait(timeout?: number): Promise<this>;
|
|
63
|
+
/**
|
|
64
|
+
* Wait for the element to become stable (position/size stops changing).
|
|
65
|
+
* Useful after animations or dynamic content loading.
|
|
66
|
+
* @param timeout Maximum time to wait in milliseconds
|
|
67
|
+
* @returns thenable self reference
|
|
68
|
+
*/
|
|
69
|
+
waitForStable(timeout?: number): Promise<this>;
|
|
46
70
|
/**
|
|
47
71
|
* Return a reference to the WebElement containing this element
|
|
48
72
|
*/
|
|
49
73
|
getEnclosingElement(): WebElement;
|
|
74
|
+
/**
|
|
75
|
+
* Get the stored locator information for this element, if available.
|
|
76
|
+
*/
|
|
77
|
+
getLocatorInfo(): LocatorInfo | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* Check if this element can be automatically recovered when stale.
|
|
80
|
+
*/
|
|
81
|
+
canAutoRecover(): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Get the WaitHelper instance for condition-based waiting.
|
|
84
|
+
*/
|
|
85
|
+
protected getWaitHelper(): WaitHelper;
|
|
86
|
+
/**
|
|
87
|
+
* Create error context for this element.
|
|
88
|
+
* Useful for creating detailed error messages.
|
|
89
|
+
*/
|
|
90
|
+
protected createErrorContext(action: string, details?: string): ErrorContext;
|
|
50
91
|
static init(locators: Locators, driver: WebDriver, browser: string, version: string): void;
|
|
51
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Execute a function with automatic recovery from stale element references.
|
|
94
|
+
* If the element becomes stale during execution, it will be relocated and
|
|
95
|
+
* the function will be retried once.
|
|
96
|
+
*
|
|
97
|
+
* @param fn The async function to execute
|
|
98
|
+
* @param maxRetries Maximum number of recovery attempts (default: 1)
|
|
99
|
+
* @returns The result of the function
|
|
100
|
+
*/
|
|
101
|
+
withRecovery<T>(fn: (self: this) => Promise<T>, maxRetries?: number): Promise<T>;
|
|
102
|
+
/**
|
|
103
|
+
* Re-locate this element using stored locator information.
|
|
104
|
+
* This is called automatically by withRecovery() when an element becomes stale.
|
|
105
|
+
*
|
|
106
|
+
* Subclasses can override this for custom recovery logic, but the default
|
|
107
|
+
* implementation works for most cases when the element was created with a locator.
|
|
108
|
+
*
|
|
109
|
+
* @returns A new instance of this element type pointing to the relocated DOM element
|
|
110
|
+
* @throws ElementRecoveryError if the element cannot be recovered
|
|
111
|
+
*/
|
|
52
112
|
protected reinitialize(): Promise<this>;
|
|
113
|
+
/**
|
|
114
|
+
* Safely click on the element with automatic retry on stale/interactable errors.
|
|
115
|
+
*/
|
|
116
|
+
safeClick(): Promise<void>;
|
|
117
|
+
/**
|
|
118
|
+
* Safely send keys to the element with automatic retry on stale/interactable errors.
|
|
119
|
+
*/
|
|
120
|
+
safeSendKeys(...keys: (string | Promise<string>)[]): Promise<void>;
|
|
121
|
+
/**
|
|
122
|
+
* Safely get text from the element with automatic retry on stale errors.
|
|
123
|
+
*/
|
|
124
|
+
safeGetText(): Promise<string>;
|
|
125
|
+
/**
|
|
126
|
+
* Safely get attribute from the element with automatic retry on stale errors.
|
|
127
|
+
*/
|
|
128
|
+
safeGetAttribute(name: string): Promise<string>;
|
|
53
129
|
}
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
19
|
exports.AbstractElement = void 0;
|
|
20
20
|
const selenium_webdriver_1 = require("selenium-webdriver");
|
|
21
|
+
const WaitHelper_1 = require("../utils/WaitHelper");
|
|
22
|
+
const ExTesterError_1 = require("../errors/ExTesterError");
|
|
21
23
|
/**
|
|
22
24
|
* Default wrapper for webElement
|
|
23
25
|
*/
|
|
@@ -26,13 +28,24 @@ class AbstractElement extends selenium_webdriver_1.WebElement {
|
|
|
26
28
|
static driver;
|
|
27
29
|
static locators;
|
|
28
30
|
static versionInfo;
|
|
31
|
+
static waitHelper;
|
|
29
32
|
enclosingItem;
|
|
33
|
+
/**
|
|
34
|
+
* Locator information for auto-recovery when element becomes stale.
|
|
35
|
+
* Stored when element is created with a Locator (not a WebElement).
|
|
36
|
+
*/
|
|
37
|
+
locatorInfo;
|
|
30
38
|
/**
|
|
31
39
|
* Constructs a new element from a Locator or an existing WebElement
|
|
32
40
|
* @param base WebDriver compatible Locator for the given element or a reference to an existing WebElement
|
|
33
41
|
* @param enclosingItem Locator or a WebElement reference to an element containing the element being constructed
|
|
34
42
|
* this will be used to narrow down the search for the underlying DOM element
|
|
35
43
|
*/
|
|
44
|
+
/*
|
|
45
|
+
* Note: The findElement calls below use selenium-webdriver's ThenableWebElement pattern
|
|
46
|
+
* for lazy element resolution. The WebElement base class is designed to work with these
|
|
47
|
+
* thenables, making this an intentional pattern.
|
|
48
|
+
*/
|
|
36
49
|
constructor(base, enclosingItem) {
|
|
37
50
|
let item = AbstractElement.driver.findElement(AbstractElement.locators.AbstractElement.tag);
|
|
38
51
|
if (!enclosingItem) {
|
|
@@ -46,11 +59,17 @@ class AbstractElement extends selenium_webdriver_1.WebElement {
|
|
|
46
59
|
}
|
|
47
60
|
if (base instanceof selenium_webdriver_1.WebElement) {
|
|
48
61
|
super(AbstractElement.driver, base.getId());
|
|
62
|
+
// Cannot store locator info for WebElement-based construction
|
|
49
63
|
}
|
|
50
64
|
else {
|
|
51
65
|
const toFind = item.findElement(base);
|
|
52
66
|
const id = toFind.getId();
|
|
53
67
|
super(AbstractElement.driver, id);
|
|
68
|
+
// Store locator info for potential recovery
|
|
69
|
+
this.locatorInfo = {
|
|
70
|
+
locator: base,
|
|
71
|
+
enclosingLocator: enclosingItem,
|
|
72
|
+
};
|
|
54
73
|
}
|
|
55
74
|
this.enclosingItem = item;
|
|
56
75
|
}
|
|
@@ -69,34 +88,172 @@ class AbstractElement extends selenium_webdriver_1.WebElement {
|
|
|
69
88
|
await this.getDriver().wait(selenium_webdriver_1.until.elementIsVisible(this), timeout);
|
|
70
89
|
return this;
|
|
71
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Wait for the element to become stable (position/size stops changing).
|
|
93
|
+
* Useful after animations or dynamic content loading.
|
|
94
|
+
* @param timeout Maximum time to wait in milliseconds
|
|
95
|
+
* @returns thenable self reference
|
|
96
|
+
*/
|
|
97
|
+
async waitForStable(timeout = 2000) {
|
|
98
|
+
await AbstractElement.waitHelper.forStable(this, { timeout });
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
72
101
|
/**
|
|
73
102
|
* Return a reference to the WebElement containing this element
|
|
74
103
|
*/
|
|
75
104
|
getEnclosingElement() {
|
|
76
105
|
return this.enclosingItem;
|
|
77
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Get the stored locator information for this element, if available.
|
|
109
|
+
*/
|
|
110
|
+
getLocatorInfo() {
|
|
111
|
+
return this.locatorInfo;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Check if this element can be automatically recovered when stale.
|
|
115
|
+
*/
|
|
116
|
+
canAutoRecover() {
|
|
117
|
+
return this.locatorInfo !== undefined;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get the WaitHelper instance for condition-based waiting.
|
|
121
|
+
*/
|
|
122
|
+
getWaitHelper() {
|
|
123
|
+
return AbstractElement.waitHelper;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Create error context for this element.
|
|
127
|
+
* Useful for creating detailed error messages.
|
|
128
|
+
*/
|
|
129
|
+
createErrorContext(action, details) {
|
|
130
|
+
return {
|
|
131
|
+
component: this.constructor.name,
|
|
132
|
+
action,
|
|
133
|
+
vscodeVersion: AbstractElement.versionInfo?.version,
|
|
134
|
+
locator: this.locatorInfo?.locator ? JSON.stringify(this.locatorInfo.locator) : undefined,
|
|
135
|
+
details,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
78
138
|
static init(locators, driver, browser, version) {
|
|
79
139
|
AbstractElement.locators = locators;
|
|
80
140
|
AbstractElement.driver = driver;
|
|
81
141
|
AbstractElement.versionInfo = { version: version, browser: browser };
|
|
142
|
+
AbstractElement.waitHelper = new WaitHelper_1.WaitHelper(driver);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Execute a function with automatic recovery from stale element references.
|
|
146
|
+
* If the element becomes stale during execution, it will be relocated and
|
|
147
|
+
* the function will be retried once.
|
|
148
|
+
*
|
|
149
|
+
* @param fn The async function to execute
|
|
150
|
+
* @param maxRetries Maximum number of recovery attempts (default: 1)
|
|
151
|
+
* @returns The result of the function
|
|
152
|
+
*/
|
|
153
|
+
async withRecovery(fn, maxRetries = 1) {
|
|
154
|
+
let lastError;
|
|
155
|
+
let recoveredElement;
|
|
156
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
157
|
+
try {
|
|
158
|
+
return await fn(recoveredElement ?? this);
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
const isRecoverable = err.name === 'StaleElementReferenceError' ||
|
|
162
|
+
err.name === 'ElementNotInteractableError' ||
|
|
163
|
+
/element.*(detached|not interactable)/i.test(err.message);
|
|
164
|
+
if (isRecoverable && attempt < maxRetries) {
|
|
165
|
+
try {
|
|
166
|
+
recoveredElement = await this.reinitialize();
|
|
167
|
+
}
|
|
168
|
+
catch (error_) {
|
|
169
|
+
// Recovery failed, throw original error with context
|
|
170
|
+
throw (0, ExTesterError_1.wrapError)(err, this.createErrorContext('withRecovery', `Recovery failed: ${error_.message}`));
|
|
171
|
+
}
|
|
172
|
+
lastError = err;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
throw (0, ExTesterError_1.wrapError)(err, this.createErrorContext('withRecovery'));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Should not reach here, but just in case
|
|
180
|
+
throw lastError || new Error('Unexpected error in withRecovery');
|
|
82
181
|
}
|
|
83
|
-
|
|
182
|
+
/**
|
|
183
|
+
* Re-locate this element using stored locator information.
|
|
184
|
+
* This is called automatically by withRecovery() when an element becomes stale.
|
|
185
|
+
*
|
|
186
|
+
* Subclasses can override this for custom recovery logic, but the default
|
|
187
|
+
* implementation works for most cases when the element was created with a locator.
|
|
188
|
+
*
|
|
189
|
+
* @returns A new instance of this element type pointing to the relocated DOM element
|
|
190
|
+
* @throws ElementRecoveryError if the element cannot be recovered
|
|
191
|
+
*/
|
|
192
|
+
async reinitialize() {
|
|
193
|
+
if (!this.locatorInfo) {
|
|
194
|
+
throw new ExTesterError_1.ElementRecoveryError(`Cannot auto-recover ${this.constructor.name}: no locator information stored. ` +
|
|
195
|
+
`Element was likely created from a WebElement reference. ` +
|
|
196
|
+
`Override reinitialize() in ${this.constructor.name} for custom recovery logic.`, this.createErrorContext('reinitialize'));
|
|
197
|
+
}
|
|
84
198
|
try {
|
|
85
|
-
|
|
199
|
+
// Resolve the parent element
|
|
200
|
+
let parentElement;
|
|
201
|
+
if (this.locatorInfo.enclosingLocator instanceof selenium_webdriver_1.WebElement) {
|
|
202
|
+
parentElement = this.locatorInfo.enclosingLocator;
|
|
203
|
+
}
|
|
204
|
+
else if (this.locatorInfo.enclosingLocator) {
|
|
205
|
+
parentElement = await AbstractElement.driver.findElement(this.locatorInfo.enclosingLocator);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
parentElement = await AbstractElement.driver.findElement(AbstractElement.locators.AbstractElement.tag);
|
|
209
|
+
}
|
|
210
|
+
// Find the element again
|
|
211
|
+
const newElement = await parentElement.findElement(this.locatorInfo.locator);
|
|
212
|
+
// Create a new instance of the same class
|
|
213
|
+
const Constructor = this.constructor;
|
|
214
|
+
const recovered = new Constructor(newElement, parentElement);
|
|
215
|
+
// Copy over the locator info so subsequent recoveries work
|
|
216
|
+
recovered.locatorInfo = this.locatorInfo;
|
|
217
|
+
return recovered;
|
|
86
218
|
}
|
|
87
219
|
catch (err) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return await fn(reinit);
|
|
93
|
-
}
|
|
94
|
-
throw err;
|
|
220
|
+
throw new ExTesterError_1.ElementRecoveryError(`Failed to relocate ${this.constructor.name}`, {
|
|
221
|
+
...this.createErrorContext('reinitialize'),
|
|
222
|
+
cause: err,
|
|
223
|
+
});
|
|
95
224
|
}
|
|
96
225
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
226
|
+
/**
|
|
227
|
+
* Safely click on the element with automatic retry on stale/interactable errors.
|
|
228
|
+
*/
|
|
229
|
+
async safeClick() {
|
|
230
|
+
await this.withRecovery(async (self) => {
|
|
231
|
+
await self.click();
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Safely send keys to the element with automatic retry on stale/interactable errors.
|
|
236
|
+
*/
|
|
237
|
+
async safeSendKeys(...keys) {
|
|
238
|
+
await this.withRecovery(async (self) => {
|
|
239
|
+
await self.sendKeys(...keys);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Safely get text from the element with automatic retry on stale errors.
|
|
244
|
+
*/
|
|
245
|
+
async safeGetText() {
|
|
246
|
+
return await this.withRecovery(async (self) => {
|
|
247
|
+
return await self.getText();
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Safely get attribute from the element with automatic retry on stale errors.
|
|
252
|
+
*/
|
|
253
|
+
async safeGetAttribute(name) {
|
|
254
|
+
return await this.withRecovery(async (self) => {
|
|
255
|
+
return await self.getAttribute(name);
|
|
256
|
+
});
|
|
100
257
|
}
|
|
101
258
|
}
|
|
102
259
|
exports.AbstractElement = AbstractElement;
|
|
@@ -23,14 +23,26 @@ const selenium_webdriver_1 = require("selenium-webdriver");
|
|
|
23
23
|
class ActionButtonElementDropdown extends AbstractElement_1.AbstractElement {
|
|
24
24
|
async open() {
|
|
25
25
|
await this.click();
|
|
26
|
-
|
|
26
|
+
// Wait for dropdown to open
|
|
27
|
+
await this.getWaitHelper()
|
|
28
|
+
.forCondition(async () => {
|
|
29
|
+
const expanded = await this.getAttribute('aria-expanded');
|
|
30
|
+
return expanded === 'true';
|
|
31
|
+
}, { timeout: 2000, pollInterval: 50 })
|
|
32
|
+
.catch(() => {
|
|
33
|
+
/* May not have aria-expanded attribute */
|
|
34
|
+
});
|
|
27
35
|
const shadowRootHost = await this.enclosingItem.findElements(ActionButtonElementDropdown.locators.ContextMenu.shadowRootHost);
|
|
28
36
|
const actions = this.getDriver().actions();
|
|
29
37
|
await actions.clear();
|
|
30
38
|
if (shadowRootHost.length > 0) {
|
|
31
39
|
if ((await this.getAttribute('aria-expanded')) !== 'true') {
|
|
32
40
|
await this.click();
|
|
33
|
-
|
|
41
|
+
// Wait for dropdown to expand after second click
|
|
42
|
+
await this.getWaitHelper().forCondition(async () => {
|
|
43
|
+
const expanded = await this.getAttribute('aria-expanded');
|
|
44
|
+
return expanded === 'true';
|
|
45
|
+
}, { timeout: 2000, pollInterval: 50 });
|
|
34
46
|
}
|
|
35
47
|
const shadowRoot = await shadowRootHost[0].getShadowRoot();
|
|
36
48
|
return new ContextMenu_1.ContextMenu(await shadowRoot.findElement(selenium_webdriver_1.By.className('monaco-menu-container'))).wait();
|
|
@@ -37,7 +37,11 @@ class ViewControl extends ElementWithContextMenu_1.ElementWithContextMenu {
|
|
|
37
37
|
const klass = await this.getAttribute(ViewControl.locators.ViewControl.attribute);
|
|
38
38
|
if (klass.indexOf(ViewControl.locators.ViewControl.klass) < 0) {
|
|
39
39
|
await this.click();
|
|
40
|
-
|
|
40
|
+
// Wait for view to be marked as active
|
|
41
|
+
await this.getWaitHelper().forCondition(async () => {
|
|
42
|
+
const newKlass = await this.getAttribute(ViewControl.locators.ViewControl.attribute);
|
|
43
|
+
return newKlass.includes(ViewControl.locators.ViewControl.klass);
|
|
44
|
+
}, { timeout: 2000, pollInterval: 100 });
|
|
41
45
|
}
|
|
42
46
|
const view = await new __1.SideBarView().wait();
|
|
43
47
|
if ((await view.findElements(ViewControl.locators.ViewControl.scmId)).length > 0) {
|
|
@@ -123,16 +123,16 @@ class TerminalView extends AbstractViews_1.ChannelView {
|
|
|
123
123
|
// try clearing, ignore if not available
|
|
124
124
|
}
|
|
125
125
|
await input.sendKeys(command, selenium_webdriver_1.Key.ENTER);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
126
|
+
// Wait for command to complete by monitoring cursor position
|
|
127
|
+
const effectiveTimeout = timeout > 0 ? timeout : 30000;
|
|
128
|
+
await this.getWaitHelper().forCondition(async () => {
|
|
129
|
+
const style = await input.getCssValue('left');
|
|
130
|
+
return style !== '0px';
|
|
131
|
+
}, {
|
|
132
|
+
timeout: effectiveTimeout,
|
|
133
|
+
pollInterval: 200,
|
|
134
|
+
message: `Terminal command did not complete within ${effectiveTimeout}ms`,
|
|
135
|
+
});
|
|
136
136
|
}
|
|
137
137
|
/**
|
|
138
138
|
* Get all text from the internal terminal
|
|
@@ -152,7 +152,16 @@ class TerminalView extends AbstractViews_1.ChannelView {
|
|
|
152
152
|
}
|
|
153
153
|
const workbench = new __1.Workbench();
|
|
154
154
|
await workbench.executeCommand('terminal select all');
|
|
155
|
-
|
|
155
|
+
// Wait for clipboard to be updated
|
|
156
|
+
await this.getWaitHelper().forCondition(async () => {
|
|
157
|
+
try {
|
|
158
|
+
const current = clipboard.readSync();
|
|
159
|
+
return current !== originalClipboard;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
}, { timeout: 2000, pollInterval: 100 });
|
|
156
165
|
const text = clipboard.readSync();
|
|
157
166
|
if (originalClipboard.length > 0) {
|
|
158
167
|
clipboard.writeSync(originalClipboard);
|
|
@@ -191,7 +200,11 @@ class TerminalView extends AbstractViews_1.ChannelView {
|
|
|
191
200
|
}
|
|
192
201
|
const list = await this.findElement(TerminalView.locators.TerminalView.tabList);
|
|
193
202
|
const row = await list.findElement(TerminalView.locators.TerminalView.selectedRow);
|
|
194
|
-
|
|
203
|
+
// Wait for row to have a valid aria-label
|
|
204
|
+
await this.getWaitHelper().forCondition(async () => {
|
|
205
|
+
const label = await row.getAttribute('aria-label');
|
|
206
|
+
return label && label.split(' ').length >= 3;
|
|
207
|
+
}, { timeout: 2000, pollInterval: 100 });
|
|
195
208
|
const label = (await row.getAttribute('aria-label')).split(' ');
|
|
196
209
|
return `${label[1]}: ${label[2]}`;
|
|
197
210
|
}
|
|
@@ -51,7 +51,8 @@ class ContentAssist extends __1.Menu {
|
|
|
51
51
|
}
|
|
52
52
|
if (!lastItem) {
|
|
53
53
|
await scrollable.sendKeys(selenium_webdriver_1.Key.PAGE_DOWN);
|
|
54
|
-
|
|
54
|
+
// Minimal delay for scroll to render new items
|
|
55
|
+
await this.getWaitHelper().sleep(100);
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
}
|
|
@@ -70,7 +70,8 @@ class EditorView extends AbstractElement_1.AbstractElement {
|
|
|
70
70
|
}
|
|
71
71
|
while (groups.length > 0 && (await groups[0].getOpenEditorTitles()).length > 0) {
|
|
72
72
|
await groups[0].closeAllEditors();
|
|
73
|
-
|
|
73
|
+
// Brief wait for DOM to settle after closing editors
|
|
74
|
+
await this.getWaitHelper().sleep(500);
|
|
74
75
|
groups = await this.getEditorGroups();
|
|
75
76
|
}
|
|
76
77
|
}
|
|
@@ -86,7 +87,15 @@ class EditorView extends AbstractElement_1.AbstractElement {
|
|
|
86
87
|
}
|
|
87
88
|
const titles = [];
|
|
88
89
|
for (const group of groups) {
|
|
89
|
-
|
|
90
|
+
try {
|
|
91
|
+
titles.push(...(await group.getOpenEditorTitles()));
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
if (e instanceof selenium_webdriver_1.error.StaleElementReferenceError) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
throw e;
|
|
98
|
+
}
|
|
90
99
|
}
|
|
91
100
|
return titles;
|
|
92
101
|
}
|
|
@@ -112,7 +121,15 @@ class EditorView extends AbstractElement_1.AbstractElement {
|
|
|
112
121
|
}
|
|
113
122
|
const tabs = [];
|
|
114
123
|
for (const group of groups) {
|
|
115
|
-
|
|
124
|
+
try {
|
|
125
|
+
tabs.push(...(await group.getOpenTabs()));
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
if (e instanceof selenium_webdriver_1.error.StaleElementReferenceError) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
throw e;
|
|
132
|
+
}
|
|
116
133
|
}
|
|
117
134
|
return tabs;
|
|
118
135
|
}
|
|
@@ -123,8 +140,16 @@ class EditorView extends AbstractElement_1.AbstractElement {
|
|
|
123
140
|
async getActiveTab() {
|
|
124
141
|
const tabs = await this.getOpenTabs();
|
|
125
142
|
for (const tab of tabs) {
|
|
126
|
-
|
|
127
|
-
|
|
143
|
+
try {
|
|
144
|
+
if (await tab.isSelected()) {
|
|
145
|
+
return tab;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
if (e instanceof selenium_webdriver_1.error.StaleElementReferenceError) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
throw e;
|
|
128
153
|
}
|
|
129
154
|
}
|
|
130
155
|
return undefined;
|
|
@@ -305,8 +330,16 @@ class EditorGroup extends AbstractElement_1.AbstractElement {
|
|
|
305
330
|
async getActiveTab() {
|
|
306
331
|
const tabs = await this.getOpenTabs();
|
|
307
332
|
for (const tab of tabs) {
|
|
308
|
-
|
|
309
|
-
|
|
333
|
+
try {
|
|
334
|
+
if (await tab.isSelected()) {
|
|
335
|
+
return tab;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
catch (e) {
|
|
339
|
+
if (e instanceof selenium_webdriver_1.error.StaleElementReferenceError) {
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
throw e;
|
|
310
343
|
}
|
|
311
344
|
}
|
|
312
345
|
return undefined;
|
|
@@ -336,8 +369,16 @@ class EditorGroup extends AbstractElement_1.AbstractElement {
|
|
|
336
369
|
const predicate = typeof predicateOrTitle === 'string' ? async (action) => (await action.getTitle()) === predicateOrTitle : predicateOrTitle;
|
|
337
370
|
const actions = await this.getActions();
|
|
338
371
|
for (const action of actions) {
|
|
339
|
-
|
|
340
|
-
|
|
372
|
+
try {
|
|
373
|
+
if (await predicate(action)) {
|
|
374
|
+
return action;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
catch (e) {
|
|
378
|
+
if (e instanceof selenium_webdriver_1.error.StaleElementReferenceError) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
throw e;
|
|
341
382
|
}
|
|
342
383
|
}
|
|
343
384
|
return undefined;
|
|
@@ -28,7 +28,15 @@ class ExtensionEditorDetailsSection extends __1.ExtensionEditorView {
|
|
|
28
28
|
const container = await this.findElement(ExtensionEditorDetailsSection.locators.ExtensionEditorDetailsSection.categoriesContainer);
|
|
29
29
|
const categoriesInContainer = await container.findElements(ExtensionEditorDetailsSection.locators.ExtensionEditorDetailsSection.category);
|
|
30
30
|
for (const cat of categoriesInContainer) {
|
|
31
|
-
|
|
31
|
+
try {
|
|
32
|
+
categories.push(await cat.getText());
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
if (e.name === 'StaleElementReferenceError') {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
throw e;
|
|
39
|
+
}
|
|
32
40
|
}
|
|
33
41
|
return categories;
|
|
34
42
|
}
|
|
@@ -41,7 +49,15 @@ class ExtensionEditorDetailsSection extends __1.ExtensionEditorView {
|
|
|
41
49
|
const container = await this.findElement(ExtensionEditorDetailsSection.locators.ExtensionEditorDetailsSection.resourcesContainer);
|
|
42
50
|
const resourcesInContainer = await container.findElements(ExtensionEditorDetailsSection.locators.ExtensionEditorDetailsSection.resource);
|
|
43
51
|
for (const res of resourcesInContainer) {
|
|
44
|
-
|
|
52
|
+
try {
|
|
53
|
+
resources.push(await res.getText());
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
if (e.name === 'StaleElementReferenceError') {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
throw e;
|
|
60
|
+
}
|
|
45
61
|
}
|
|
46
62
|
return resources;
|
|
47
63
|
}
|
|
@@ -54,11 +70,19 @@ class ExtensionEditorDetailsSection extends __1.ExtensionEditorView {
|
|
|
54
70
|
const container = await this.findElement(ExtensionEditorDetailsSection.locators.ExtensionEditorDetailsSection.moreInfoContainer);
|
|
55
71
|
const moreInfoInContainer = await container.findElements(ExtensionEditorDetailsSection.locators.ExtensionEditorDetailsSection.moreInfo);
|
|
56
72
|
for (const entry of moreInfoInContainer) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
73
|
+
try {
|
|
74
|
+
const elmnts = await entry.findElements(ExtensionEditorDetailsSection.locators.ExtensionEditorDetailsSection.moreInfoElements);
|
|
75
|
+
const name = await elmnts.at(0)?.getText();
|
|
76
|
+
const value = await elmnts.at(1)?.getText();
|
|
77
|
+
if (name !== undefined && value !== undefined) {
|
|
78
|
+
moreInfo[name] = value;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
if (e.name === 'StaleElementReferenceError') {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
throw e;
|
|
62
86
|
}
|
|
63
87
|
}
|
|
64
88
|
return moreInfo;
|