@eclipse-che/che-e2e 7.106.0-next-5d2b584 → 7.106.0-next-45f4fb4
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/configs/inversify.config.ts +8 -0
- package/configs/inversify.types.ts +5 -1
- package/constants/TIMEOUT_CONSTANTS.ts +6 -0
- package/dist/configs/inversify.config.js +8 -0
- package/dist/configs/inversify.config.js.map +1 -1
- package/dist/configs/inversify.types.js +5 -1
- package/dist/configs/inversify.types.js.map +1 -1
- package/dist/constants/TIMEOUT_CONSTANTS.js +4 -0
- package/dist/constants/TIMEOUT_CONSTANTS.js.map +1 -1
- package/dist/driver/ChromeDriver.js +5 -1
- package/dist/driver/ChromeDriver.js.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/pageobjects/ide/CommandPalette.js +117 -0
- package/dist/pageobjects/ide/CommandPalette.js.map +1 -0
- package/dist/pageobjects/ide/ExplorerView.js +108 -0
- package/dist/pageobjects/ide/ExplorerView.js.map +1 -0
- package/dist/pageobjects/ide/ExtensionsView.js +162 -0
- package/dist/pageobjects/ide/ExtensionsView.js.map +1 -0
- package/dist/pageobjects/ide/NotificationHandler.js +83 -0
- package/dist/pageobjects/ide/NotificationHandler.js.map +1 -0
- package/dist/pageobjects/ide/ViewsMoreActionsButton.js +9 -0
- package/dist/pageobjects/ide/ViewsMoreActionsButton.js.map +1 -1
- package/dist/specs/dashboard-samples/RecommendedExtensions.spec.js +13 -2
- package/dist/specs/dashboard-samples/RecommendedExtensions.spec.js.map +1 -1
- package/dist/specs/miscellaneous/VsixInstallationDisableTest.spec.js +205 -0
- package/dist/specs/miscellaneous/VsixInstallationDisableTest.spec.js.map +1 -0
- package/dist/utils/DevWorkspaceConfigurationHelper.js +11 -13
- package/dist/utils/DevWorkspaceConfigurationHelper.js.map +1 -1
- package/driver/ChromeDriver.ts +5 -1
- package/index.ts +4 -0
- package/package.json +1 -1
- package/pageobjects/ide/CommandPalette.ts +114 -0
- package/pageobjects/ide/ExplorerView.ts +106 -0
- package/pageobjects/ide/ExtensionsView.ts +167 -0
- package/pageobjects/ide/NotificationHandler.ts +70 -0
- package/pageobjects/ide/ViewsMoreActionsButton.ts +11 -0
- package/resources/configmap-disable-vsix-installation.yaml +22 -0
- package/resources/configmap-enable-vsix-installation.yaml +22 -0
- package/resources/default-extensions-configmap.yaml +12 -0
- package/specs/dashboard-samples/RecommendedExtensions.spec.ts +26 -2
- package/specs/miscellaneous/VsixInstallationDisableTest.spec.ts +243 -0
- package/utils/DevWorkspaceConfigurationHelper.ts +12 -15
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/** *******************************************************************
|
|
2
|
+
* copyright (c) 2025 Red Hat, Inc.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made
|
|
5
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
6
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
7
|
+
*
|
|
8
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
9
|
+
**********************************************************************/
|
|
10
|
+
import { inject, injectable } from 'inversify';
|
|
11
|
+
import { CLASSES } from '../../configs/inversify.types';
|
|
12
|
+
import { By, Key, WebElement } from 'selenium-webdriver';
|
|
13
|
+
import { DriverHelper } from '../../utils/DriverHelper';
|
|
14
|
+
import { Logger } from '../../utils/Logger';
|
|
15
|
+
import { TIMEOUT_CONSTANTS } from '../../constants/TIMEOUT_CONSTANTS';
|
|
16
|
+
|
|
17
|
+
@injectable()
|
|
18
|
+
export class CommandPalette {
|
|
19
|
+
private static readonly COMMAND_PALETTE_CONTAINER: By = By.css('.quick-input-widget');
|
|
20
|
+
private static readonly COMMAND_PALETTE_LIST: By = By.css('#quickInput_list');
|
|
21
|
+
private static readonly COMMAND_PALETTE_ITEMS: By = By.css('#quickInput_list [role="option"]');
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
@inject(CLASSES.DriverHelper)
|
|
25
|
+
private readonly driverHelper: DriverHelper
|
|
26
|
+
) {}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* open the Command Palette using keyboard shortcut
|
|
30
|
+
* tries F1 first, then Ctrl+Shift+P if needed
|
|
31
|
+
*/
|
|
32
|
+
async openCommandPalette(): Promise<void> {
|
|
33
|
+
Logger.debug();
|
|
34
|
+
|
|
35
|
+
await this.driverHelper.getDriver().actions().keyDown(Key.F1).keyUp(Key.F1).perform();
|
|
36
|
+
|
|
37
|
+
const paletteVisible: boolean = await this.driverHelper.waitVisibilityBoolean(CommandPalette.COMMAND_PALETTE_CONTAINER);
|
|
38
|
+
|
|
39
|
+
if (!paletteVisible) {
|
|
40
|
+
await this.driverHelper
|
|
41
|
+
.getDriver()
|
|
42
|
+
.actions()
|
|
43
|
+
.keyDown(Key.CONTROL)
|
|
44
|
+
.keyDown(Key.SHIFT)
|
|
45
|
+
.sendKeys('p')
|
|
46
|
+
.keyUp(Key.SHIFT)
|
|
47
|
+
.keyUp(Key.CONTROL)
|
|
48
|
+
.perform();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await this.driverHelper.waitVisibility(CommandPalette.COMMAND_PALETTE_LIST);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* search for a command in the Command Palette
|
|
56
|
+
*
|
|
57
|
+
* @param commandText Text to search for
|
|
58
|
+
*/
|
|
59
|
+
async searchCommand(commandText: string): Promise<void> {
|
|
60
|
+
Logger.debug(`"${commandText}"`);
|
|
61
|
+
|
|
62
|
+
await this.driverHelper.wait(TIMEOUT_CONSTANTS.TS_SELENIUM_DEFAULT_POLLING);
|
|
63
|
+
await this.driverHelper.getDriver().actions().sendKeys(commandText).perform();
|
|
64
|
+
await this.driverHelper.wait(TIMEOUT_CONSTANTS.TS_SELENIUM_DEFAULT_POLLING);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* get all visible commands in the Command Palette
|
|
69
|
+
*
|
|
70
|
+
* @returns Array of command texts
|
|
71
|
+
*/
|
|
72
|
+
async getVisibleCommands(): Promise<string[]> {
|
|
73
|
+
Logger.debug();
|
|
74
|
+
|
|
75
|
+
const listVisible: boolean = await this.driverHelper.waitVisibilityBoolean(CommandPalette.COMMAND_PALETTE_LIST);
|
|
76
|
+
|
|
77
|
+
if (!listVisible) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
await this.driverHelper.wait(TIMEOUT_CONSTANTS.TS_SELENIUM_DEFAULT_POLLING);
|
|
82
|
+
const items: WebElement[] = await this.driverHelper.getDriver().findElements(CommandPalette.COMMAND_PALETTE_ITEMS);
|
|
83
|
+
const itemTexts: string[] = [];
|
|
84
|
+
|
|
85
|
+
for (const item of items) {
|
|
86
|
+
try {
|
|
87
|
+
const ariaLabel: string = await item.getAttribute('aria-label');
|
|
88
|
+
if (ariaLabel) {
|
|
89
|
+
itemTexts.push(ariaLabel);
|
|
90
|
+
}
|
|
91
|
+
} catch (err) {
|
|
92
|
+
// skip items that cannot be read
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return itemTexts;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async isCommandVisible(commandText: string): Promise<boolean> {
|
|
100
|
+
Logger.debug(`"${commandText}"`);
|
|
101
|
+
await this.driverHelper.wait(TIMEOUT_CONSTANTS.TS_SELENIUM_DEFAULT_POLLING);
|
|
102
|
+
|
|
103
|
+
const availableCommands: string[] = await this.getVisibleCommands();
|
|
104
|
+
Logger.debug(`Available commands: ${availableCommands.join(', ')}`);
|
|
105
|
+
return availableCommands.some((command: string): boolean => command.toLowerCase().includes(commandText.toLowerCase()));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async closeCommandPalette(): Promise<void> {
|
|
109
|
+
Logger.debug();
|
|
110
|
+
|
|
111
|
+
await this.driverHelper.getDriver().actions().sendKeys(Key.ESCAPE).perform();
|
|
112
|
+
await this.driverHelper.waitDisappearance(CommandPalette.COMMAND_PALETTE_CONTAINER);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/** *******************************************************************
|
|
2
|
+
* copyright (c) 2025 Red Hat, Inc.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made
|
|
5
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
6
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
7
|
+
*
|
|
8
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
9
|
+
**********************************************************************/
|
|
10
|
+
import { inject, injectable } from 'inversify';
|
|
11
|
+
import { CLASSES } from '../../configs/inversify.types';
|
|
12
|
+
import { By, Key } from 'selenium-webdriver';
|
|
13
|
+
import { ActivityBar, ViewControl, ViewItem, ViewSection } from 'monaco-page-objects';
|
|
14
|
+
import { DriverHelper } from '../../utils/DriverHelper';
|
|
15
|
+
import { Logger } from '../../utils/Logger';
|
|
16
|
+
import { ProjectAndFileTests } from '../../tests-library/ProjectAndFileTests';
|
|
17
|
+
import { TIMEOUT_CONSTANTS } from '../../constants/TIMEOUT_CONSTANTS';
|
|
18
|
+
|
|
19
|
+
@injectable()
|
|
20
|
+
export class ExplorerView {
|
|
21
|
+
private static readonly CONTEXT_MENU_CONTAINER: By = By.css('.monaco-menu-container');
|
|
22
|
+
private static readonly CONTEXT_MENU_ITEMS: By = By.css('.monaco-menu-container .action-item');
|
|
23
|
+
|
|
24
|
+
constructor(
|
|
25
|
+
@inject(CLASSES.DriverHelper)
|
|
26
|
+
private readonly driverHelper: DriverHelper,
|
|
27
|
+
@inject(CLASSES.ProjectAndFileTests)
|
|
28
|
+
private readonly projectAndFileTests: ProjectAndFileTests
|
|
29
|
+
) {}
|
|
30
|
+
|
|
31
|
+
async openExplorerView(): Promise<void> {
|
|
32
|
+
Logger.debug();
|
|
33
|
+
|
|
34
|
+
const explorerCtrl: ViewControl | undefined = await new ActivityBar().getViewControl('Explorer');
|
|
35
|
+
await explorerCtrl?.openView();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* open the context menu for a file in the Explorer view
|
|
40
|
+
*
|
|
41
|
+
* @param fileName Name of the file to open context menu for
|
|
42
|
+
*/
|
|
43
|
+
async openFileContextMenu(fileName: string): Promise<void> {
|
|
44
|
+
Logger.debug(`"${fileName}"`);
|
|
45
|
+
|
|
46
|
+
await this.openExplorerView();
|
|
47
|
+
|
|
48
|
+
const projectSection: ViewSection = await this.projectAndFileTests.getProjectViewSession();
|
|
49
|
+
const fileItem: ViewItem | undefined = await this.projectAndFileTests.getProjectTreeItem(projectSection, fileName);
|
|
50
|
+
|
|
51
|
+
if (!fileItem) {
|
|
52
|
+
throw new Error(`Could not find ${fileName} file in explorer`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await this.driverHelper.wait(TIMEOUT_CONSTANTS.TS_SELENIUM_DEFAULT_POLLING);
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
await fileItem.openContextMenu();
|
|
59
|
+
await this.waitContextMenuVisible();
|
|
60
|
+
} catch (error) {
|
|
61
|
+
Logger.error(`Context menu failed for "${fileName}": ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
62
|
+
throw new Error(`Context menu failed to open for "${fileName}"`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* check if a specific item is visible in the context menu
|
|
68
|
+
*
|
|
69
|
+
* @param menuItemAriaLabel Aria label of the menu item to check for
|
|
70
|
+
* @returns True if the item is visible
|
|
71
|
+
*/
|
|
72
|
+
async isContextMenuItemVisible(menuItemAriaLabel: string): Promise<boolean> {
|
|
73
|
+
Logger.debug(`"${menuItemAriaLabel}"`);
|
|
74
|
+
|
|
75
|
+
await this.driverHelper.wait(TIMEOUT_CONSTANTS.TS_SELENIUM_DEFAULT_POLLING);
|
|
76
|
+
|
|
77
|
+
const contextMenuItemLocator: By = By.css(`.monaco-menu-container [aria-label="${menuItemAriaLabel}"]`);
|
|
78
|
+
return await this.driverHelper.waitVisibilityBoolean(contextMenuItemLocator);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async closeContextMenu(): Promise<void> {
|
|
82
|
+
Logger.debug();
|
|
83
|
+
|
|
84
|
+
await this.driverHelper.getDriver().actions().sendKeys(Key.ESCAPE).perform();
|
|
85
|
+
await this.driverHelper.wait(TIMEOUT_CONSTANTS.TS_SELENIUM_DEFAULT_POLLING);
|
|
86
|
+
await this.driverHelper.waitDisappearance(ExplorerView.CONTEXT_MENU_CONTAINER);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private async waitContextMenuVisible(): Promise<void> {
|
|
90
|
+
Logger.debug();
|
|
91
|
+
|
|
92
|
+
const containerVisible: boolean = await this.driverHelper.waitVisibilityBoolean(ExplorerView.CONTEXT_MENU_CONTAINER);
|
|
93
|
+
|
|
94
|
+
if (!containerVisible) {
|
|
95
|
+
throw new Error('Context menu container did not appear');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
await this.driverHelper.wait(TIMEOUT_CONSTANTS.TS_SELENIUM_DEFAULT_POLLING);
|
|
99
|
+
|
|
100
|
+
const menuItemsVisible: boolean = await this.driverHelper.waitVisibilityBoolean(ExplorerView.CONTEXT_MENU_ITEMS);
|
|
101
|
+
|
|
102
|
+
if (!menuItemsVisible) {
|
|
103
|
+
throw new Error('Context menu items did not load properly');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/** *******************************************************************
|
|
2
|
+
* copyright (c) 2025 Red Hat, Inc.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made
|
|
5
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
6
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
7
|
+
*
|
|
8
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
9
|
+
**********************************************************************/
|
|
10
|
+
import { inject, injectable } from 'inversify';
|
|
11
|
+
import { CLASSES } from '../../configs/inversify.types';
|
|
12
|
+
import { By, Key, WebElement } from 'selenium-webdriver';
|
|
13
|
+
import { ActivityBar, ExtensionsViewItem, ExtensionsViewSection, SideBarView, ViewControl } from 'monaco-page-objects';
|
|
14
|
+
import { DriverHelper } from '../../utils/DriverHelper';
|
|
15
|
+
import { Logger } from '../../utils/Logger';
|
|
16
|
+
import { ViewsMoreActionsButton } from './ViewsMoreActionsButton';
|
|
17
|
+
import { TIMEOUT_CONSTANTS } from '../../constants/TIMEOUT_CONSTANTS';
|
|
18
|
+
|
|
19
|
+
@injectable()
|
|
20
|
+
export class ExtensionsView {
|
|
21
|
+
private static readonly EXTENSIONS_VIEW: By = By.css('.extensions-viewlet');
|
|
22
|
+
private static readonly MENU_ITEM: By = By.css('.context-view .monaco-menu .action-item');
|
|
23
|
+
private static readonly SEARCH_BOX: By = By.css('input.monaco-inputbox-input');
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
@inject(CLASSES.DriverHelper)
|
|
27
|
+
private readonly driverHelper: DriverHelper,
|
|
28
|
+
@inject(CLASSES.ViewsMoreActionsButton)
|
|
29
|
+
private readonly viewsMoreActionsButton: ViewsMoreActionsButton
|
|
30
|
+
) {}
|
|
31
|
+
|
|
32
|
+
async openExtensionsView(): Promise<void> {
|
|
33
|
+
Logger.debug();
|
|
34
|
+
|
|
35
|
+
const viewCtrl: ViewControl | undefined = await new ActivityBar().getViewControl('Extensions');
|
|
36
|
+
await viewCtrl?.openView();
|
|
37
|
+
|
|
38
|
+
const extensionsViewVisible: boolean = await this.driverHelper.waitVisibilityBoolean(ExtensionsView.EXTENSIONS_VIEW);
|
|
39
|
+
|
|
40
|
+
if (!extensionsViewVisible) {
|
|
41
|
+
throw new Error('Extensions view could not be opened');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async openMoreActionsMenu(): Promise<void> {
|
|
46
|
+
Logger.debug();
|
|
47
|
+
|
|
48
|
+
await this.viewsMoreActionsButton.clickViewsMoreActionsButton();
|
|
49
|
+
await this.viewsMoreActionsButton.waitForContextMenu();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* get all items in the More Actions menu
|
|
54
|
+
*
|
|
55
|
+
* @returns Array of menu item texts
|
|
56
|
+
*/
|
|
57
|
+
async getMoreActionsMenuItems(): Promise<string[]> {
|
|
58
|
+
Logger.debug();
|
|
59
|
+
|
|
60
|
+
const menuItems: WebElement[] = await this.driverHelper.getDriver().findElements(ExtensionsView.MENU_ITEM);
|
|
61
|
+
const menuTexts: string[] = [];
|
|
62
|
+
|
|
63
|
+
for (const item of menuItems) {
|
|
64
|
+
try {
|
|
65
|
+
const text: string = await item.getText();
|
|
66
|
+
menuTexts.push(text);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
// skip items that cannot be read
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return menuTexts;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* check if a specific item is visible in the More Actions menu
|
|
77
|
+
*
|
|
78
|
+
* @param menuItemText Text to check for
|
|
79
|
+
* @returns True if item is visible
|
|
80
|
+
*/
|
|
81
|
+
async isMoreActionsMenuItemVisible(menuItemText: string): Promise<boolean> {
|
|
82
|
+
Logger.debug(`"${menuItemText}"`);
|
|
83
|
+
|
|
84
|
+
const menuItems: string[] = await this.getMoreActionsMenuItems();
|
|
85
|
+
return menuItems.some((item: string): boolean => item.includes(menuItemText));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async closeMoreActionsMenu(): Promise<void> {
|
|
89
|
+
Logger.debug();
|
|
90
|
+
|
|
91
|
+
await this.driverHelper.getDriver().actions().sendKeys(Key.ESCAPE).perform();
|
|
92
|
+
await this.driverHelper.wait(TIMEOUT_CONSTANTS.TS_SELENIUM_DEFAULT_POLLING);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* get names of all installed extensions
|
|
97
|
+
*/
|
|
98
|
+
async getInstalledExtensionNames(): Promise<string[]> {
|
|
99
|
+
Logger.debug();
|
|
100
|
+
|
|
101
|
+
await this.openExtensionsView();
|
|
102
|
+
|
|
103
|
+
const extensionsView: SideBarView | undefined = await (await new ActivityBar().getViewControl('Extensions'))?.openView();
|
|
104
|
+
if (!extensionsView) {
|
|
105
|
+
throw new Error('Could not open Extensions view');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const [extensionSection]: ExtensionsViewSection[] = (await extensionsView.getContent().getSections()) as ExtensionsViewSection[];
|
|
109
|
+
if (!extensionSection) {
|
|
110
|
+
throw new Error('Could not find Extensions section');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// search for installed extensions
|
|
114
|
+
await this.searchInExtensions(extensionSection, '@installed');
|
|
115
|
+
|
|
116
|
+
// wait for results to load
|
|
117
|
+
await this.driverHelper.wait(TIMEOUT_CONSTANTS.TS_SELENIUM_DEFAULT_POLLING);
|
|
118
|
+
|
|
119
|
+
const installedItems: ExtensionsViewItem[] = await extensionSection.getVisibleItems();
|
|
120
|
+
const extensionNames: string[] = [];
|
|
121
|
+
|
|
122
|
+
for (const item of installedItems) {
|
|
123
|
+
try {
|
|
124
|
+
const title: string = await item.getTitle();
|
|
125
|
+
extensionNames.push(title);
|
|
126
|
+
} catch (err) {
|
|
127
|
+
// skip items that cannot be read
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
Logger.debug(`Found ${extensionNames.length} installed extensions: ${extensionNames.join(', ')}`);
|
|
132
|
+
return extensionNames;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* search for extensions using a search term
|
|
137
|
+
*
|
|
138
|
+
* @param extensionSection The ExtensionsViewSection to search in
|
|
139
|
+
* @param searchText Text to search for
|
|
140
|
+
*/
|
|
141
|
+
private async searchInExtensions(extensionSection: ExtensionsViewSection, searchText: string): Promise<void> {
|
|
142
|
+
Logger.debug(`Searching for: "${searchText}"`);
|
|
143
|
+
|
|
144
|
+
const enclosingItem: WebElement = extensionSection.getEnclosingElement();
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const searchField: WebElement = await enclosingItem.findElement(ExtensionsView.SEARCH_BOX);
|
|
148
|
+
|
|
149
|
+
// clear existing search
|
|
150
|
+
await this.driverHelper.getDriver().actions().click(searchField).perform();
|
|
151
|
+
await this.driverHelper
|
|
152
|
+
.getDriver()
|
|
153
|
+
.actions()
|
|
154
|
+
.keyDown(Key.CONTROL)
|
|
155
|
+
.sendKeys('a')
|
|
156
|
+
.keyUp(Key.CONTROL)
|
|
157
|
+
.sendKeys(Key.DELETE)
|
|
158
|
+
.perform();
|
|
159
|
+
|
|
160
|
+
// enter new search text
|
|
161
|
+
await this.driverHelper.getDriver().actions().sendKeys(searchText).perform();
|
|
162
|
+
await this.driverHelper.wait(TIMEOUT_CONSTANTS.TS_SELENIUM_DEFAULT_POLLING);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
Logger.debug(`Could not interact with search field: ${err}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/** *******************************************************************
|
|
2
|
+
* copyright (c) 2025 Red Hat, Inc.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made
|
|
5
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
6
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
7
|
+
*
|
|
8
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
9
|
+
**********************************************************************/
|
|
10
|
+
import { inject, injectable } from 'inversify';
|
|
11
|
+
import { CLASSES } from '../../configs/inversify.types';
|
|
12
|
+
import { By } from 'selenium-webdriver';
|
|
13
|
+
import { DriverHelper } from '../../utils/DriverHelper';
|
|
14
|
+
import { Logger } from '../../utils/Logger';
|
|
15
|
+
import { TIMEOUT_CONSTANTS } from '../../constants/TIMEOUT_CONSTANTS';
|
|
16
|
+
import { WebElement } from 'monaco-page-objects';
|
|
17
|
+
|
|
18
|
+
@injectable()
|
|
19
|
+
export class NotificationHandler {
|
|
20
|
+
private static readonly NOTIFICATION_MESSAGE: By = By.css('.notification-list-item-message');
|
|
21
|
+
|
|
22
|
+
constructor(
|
|
23
|
+
@inject(CLASSES.DriverHelper)
|
|
24
|
+
private readonly driverHelper: DriverHelper
|
|
25
|
+
) {}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* check for notifications containing specific text
|
|
29
|
+
*/
|
|
30
|
+
async checkForNotification(expectedText: string, timeoutMs: number = TIMEOUT_CONSTANTS.TS_NOTIFICATION_WAIT_TIMEOUT): Promise<boolean> {
|
|
31
|
+
Logger.debug(`Checking for notification containing: "${expectedText}"`);
|
|
32
|
+
|
|
33
|
+
const startTime: number = Date.now();
|
|
34
|
+
|
|
35
|
+
if (await this.findInExistingNotifications(expectedText)) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
40
|
+
if (await this.findInExistingNotifications(expectedText)) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
await this.driverHelper.wait(TIMEOUT_CONSTANTS.TS_SELENIUM_DEFAULT_POLLING);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
Logger.debug(`Notification containing "${expectedText}" not found after ${timeoutMs}ms`);
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private async findInExistingNotifications(expectedText: string): Promise<boolean> {
|
|
51
|
+
try {
|
|
52
|
+
const elements: WebElement[] = await this.driverHelper.getDriver().findElements(NotificationHandler.NOTIFICATION_MESSAGE);
|
|
53
|
+
|
|
54
|
+
for (const element of elements) {
|
|
55
|
+
try {
|
|
56
|
+
const text: string = await element.getText();
|
|
57
|
+
if (text && text.toLowerCase().includes(expectedText.toLowerCase())) {
|
|
58
|
+
Logger.debug(`Found matching notification: "${text}"`);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
} catch (err) {
|
|
62
|
+
// continue to next element
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch (err) {
|
|
66
|
+
// no notifications found
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -53,4 +53,15 @@ export class ViewsMoreActionsButton {
|
|
|
53
53
|
|
|
54
54
|
return viewsActionsButton;
|
|
55
55
|
}
|
|
56
|
+
|
|
57
|
+
async clickViewsMoreActionsButton(): Promise<void> {
|
|
58
|
+
Logger.debug();
|
|
59
|
+
await this.driverHelper.waitAndClick(ViewsMoreActionsButton.VIEWS_AND_MORE_ACTIONS_BUTTON);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async waitForContextMenu(): Promise<void> {
|
|
63
|
+
const cheCodeLocatorLoader: CheCodeLocatorLoader = e2eContainer.get(CLASSES.CheCodeLocatorLoader);
|
|
64
|
+
const webCheCodeLocators: Locators = cheCodeLocatorLoader.webCheCodeLocators;
|
|
65
|
+
await this.driverHelper.waitVisibility(webCheCodeLocators.ContextMenu.contextView);
|
|
66
|
+
}
|
|
56
67
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
kind: ConfigMap
|
|
2
|
+
apiVersion: v1
|
|
3
|
+
metadata:
|
|
4
|
+
name: vscode-editor-configurations
|
|
5
|
+
namespace: openshift-devspaces
|
|
6
|
+
labels:
|
|
7
|
+
app.kubernetes.io/part-of: che.eclipse.org
|
|
8
|
+
app.kubernetes.io/component: workspaces-config
|
|
9
|
+
data:
|
|
10
|
+
configurations.json: |
|
|
11
|
+
{
|
|
12
|
+
"extensions.install-from-vsix-enabled": false
|
|
13
|
+
}
|
|
14
|
+
settings.json: |
|
|
15
|
+
{
|
|
16
|
+
"window.header": "VSIX INSTALL = DISABLED",
|
|
17
|
+
"window.commandCenter": false,
|
|
18
|
+
"workbench.colorCustomizations": {
|
|
19
|
+
"titleBar.activeBackground": "#CCA700",
|
|
20
|
+
"titleBar.activeForeground": "#ffffff"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
kind: ConfigMap
|
|
2
|
+
apiVersion: v1
|
|
3
|
+
metadata:
|
|
4
|
+
name: vscode-editor-configurations
|
|
5
|
+
namespace: openshift-devspaces
|
|
6
|
+
labels:
|
|
7
|
+
app.kubernetes.io/part-of: che.eclipse.org
|
|
8
|
+
app.kubernetes.io/component: workspaces-config
|
|
9
|
+
data:
|
|
10
|
+
configurations.json: |
|
|
11
|
+
{
|
|
12
|
+
"extensions.install-from-vsix-enabled": true
|
|
13
|
+
}
|
|
14
|
+
settings.json: |
|
|
15
|
+
{
|
|
16
|
+
"window.header": "VSIX INSTALL = ENABLED",
|
|
17
|
+
"window.commandCenter": false,
|
|
18
|
+
"workbench.colorCustomizations": {
|
|
19
|
+
"titleBar.activeBackground": "#CCA700",
|
|
20
|
+
"titleBar.activeForeground": "#ffffff"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
kind: ConfigMap
|
|
2
|
+
apiVersion: v1
|
|
3
|
+
metadata:
|
|
4
|
+
name: default-extensions
|
|
5
|
+
namespace: admin-devspaces
|
|
6
|
+
labels:
|
|
7
|
+
controller.devfile.io/mount-to-devworkspace: 'true'
|
|
8
|
+
controller.devfile.io/watch-configmap: 'true'
|
|
9
|
+
annotations:
|
|
10
|
+
controller.devfile.io/mount-as: env
|
|
11
|
+
data:
|
|
12
|
+
DEFAULT_EXTENSIONS: '/projects/web-nodejs-sample-with-disabled-vsix/redhat.vscode-yaml-1.17.0.vsix'
|
|
@@ -160,6 +160,7 @@ async function findItem(extSection: ExtensionsViewSection, title: string): Promi
|
|
|
160
160
|
Logger.debug(`Extension with title "${searchTitle}" not found in section "${sectionTitle}"`);
|
|
161
161
|
return undefined;
|
|
162
162
|
}
|
|
163
|
+
|
|
163
164
|
// get visible items from Extension view, transform this from array to sorted string and compares it with existed recommended extensions
|
|
164
165
|
async function getVisibleFilteredItemsAndCompareWithRecommended(recommendations: string[]): Promise<boolean> {
|
|
165
166
|
const extensionsView: SideBarView | undefined = await (await new ActivityBar().getViewControl('Extensions'))?.openView();
|
|
@@ -180,20 +181,23 @@ async function getVisibleFilteredItemsAndCompareWithRecommended(recommendations:
|
|
|
180
181
|
}
|
|
181
182
|
Logger.debug('marketplaceSection.getVisibleItems()');
|
|
182
183
|
const allFoundRecommendedItems: ExtensionsViewItem[] = await marketplaceSection.getVisibleItems();
|
|
184
|
+
|
|
183
185
|
const allFoundRecommendedAuthors: string[] = await Promise.all(
|
|
184
186
|
allFoundRecommendedItems.map(async (item: ExtensionsViewItem): Promise<string> => await item.getAuthor())
|
|
185
187
|
);
|
|
186
188
|
|
|
187
189
|
const allFoundAuthorsAsSortedString: string = allFoundRecommendedAuthors.sort().toString();
|
|
188
|
-
const
|
|
189
|
-
return allFoundAuthorsAsSortedString ===
|
|
190
|
+
const allPublisherNamesAsSortedString: string = recommendations.sort().toString();
|
|
191
|
+
return allFoundAuthorsAsSortedString === allPublisherNamesAsSortedString;
|
|
190
192
|
}
|
|
193
|
+
|
|
191
194
|
// get visible items from Extension view, transform this from array to sorted string and compares it with existed installed extensions
|
|
192
195
|
async function getVisibleFilteredItemsAndCompareWithInstalled(recommendations: string[]): Promise<boolean> {
|
|
193
196
|
const extensionsView: SideBarView | undefined = await (await new ActivityBar().getViewControl('Extensions'))?.openView();
|
|
194
197
|
const [marketplaceSection]: ExtensionsViewSection[] = (await extensionsView?.getContent().getSections()) as ExtensionsViewSection[];
|
|
195
198
|
Logger.debug('marketplaceSection.getVisibleItems()');
|
|
196
199
|
const allFoundRecommendedItems: ExtensionsViewItem[] = await marketplaceSection.getVisibleItems();
|
|
200
|
+
|
|
197
201
|
const allFoundRecommendedAuthors: string[] = await Promise.all(
|
|
198
202
|
allFoundRecommendedItems.map(async (item: ExtensionsViewItem): Promise<string> => await item.getAuthor())
|
|
199
203
|
);
|
|
@@ -203,6 +207,7 @@ async function getVisibleFilteredItemsAndCompareWithInstalled(recommendations: s
|
|
|
203
207
|
// in some cases we can have installed not only recommended extensions with some samples (for example .Net)
|
|
204
208
|
return allFoundAuthorsAsSortedString.includes(allPublisherNamesAsSortString);
|
|
205
209
|
}
|
|
210
|
+
|
|
206
211
|
for (const sample of samples) {
|
|
207
212
|
suite(`Check if recommended extensions installed for ${sample} ${BASE_TEST_CONSTANTS.TEST_ENVIRONMENT}`, function (): void {
|
|
208
213
|
const projectAndFileTests: ProjectAndFileTests = e2eContainer.get(CLASSES.ProjectAndFileTests);
|
|
@@ -292,10 +297,29 @@ for (const sample of samples) {
|
|
|
292
297
|
recommendedExtensions = JSON.parse(text.replace(/\/\*[\s\S]*?\*\/|(?<=[^:])\/\/.*|^\/\/.*/g, '').trim());
|
|
293
298
|
|
|
294
299
|
Logger.debug('recommendedExtensions.recommendations: Get recommendations clear names using map().');
|
|
300
|
+
|
|
301
|
+
// skip the test if only redhat.fabric8-analytics extension is found in Dev Spaces 3.22.0 (issue CRW-9186)
|
|
302
|
+
if (BASE_TEST_CONSTANTS.OCP_VERSION === '3.22.0' && BASE_TEST_CONSTANTS.TESTING_APPLICATION_NAME() === 'devspaces') {
|
|
303
|
+
const dependencyAnalyticsExtensionName: string = 'redhat.fabric8-analytics';
|
|
304
|
+
if (
|
|
305
|
+
recommendedExtensions.recommendations.includes(dependencyAnalyticsExtensionName) &&
|
|
306
|
+
recommendedExtensions.recommendations.length === 1
|
|
307
|
+
) {
|
|
308
|
+
throw new Error(
|
|
309
|
+
`Only '${dependencyAnalyticsExtensionName}' extension found. This extension will not be installed because of known issue https://issues.redhat.com/browse/CRW-9186`
|
|
310
|
+
);
|
|
311
|
+
} else {
|
|
312
|
+
recommendedExtensions.recommendations = recommendedExtensions.recommendations.filter(
|
|
313
|
+
(rec: string): boolean => !rec.includes(dependencyAnalyticsExtensionName)
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
295
318
|
parsedRecommendations = recommendedExtensions.recommendations.map((rec: string): { name: string; publisher: string } => {
|
|
296
319
|
const [publisher, name] = rec.split('.');
|
|
297
320
|
return { publisher, name };
|
|
298
321
|
});
|
|
322
|
+
|
|
299
323
|
Logger.debug(`Recommended extension for this workspace:\n${JSON.stringify(parsedRecommendations)}.`);
|
|
300
324
|
publisherNames = parsedRecommendations.map((rec: { name: string; publisher: string }): string => rec.publisher);
|
|
301
325
|
expect(parsedRecommendations, 'Recommendations not found').not.empty;
|