@theia/playwright 1.37.0-next.1 → 1.37.0-next.10

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 (81) hide show
  1. package/lib/index.d.ts +4 -0
  2. package/lib/index.d.ts.map +1 -1
  3. package/lib/index.js +4 -0
  4. package/lib/index.js.map +1 -1
  5. package/lib/tests/theia-explorer-view.test.js +25 -5
  6. package/lib/tests/theia-explorer-view.test.js.map +1 -1
  7. package/lib/tests/theia-main-menu.test.js +2 -2
  8. package/lib/tests/theia-main-menu.test.js.map +1 -1
  9. package/lib/tests/theia-output-view.test.d.ts +2 -0
  10. package/lib/tests/theia-output-view.test.d.ts.map +1 -0
  11. package/lib/tests/theia-output-view.test.js +77 -0
  12. package/lib/tests/theia-output-view.test.js.map +1 -0
  13. package/lib/tests/theia-quick-command.test.js +14 -0
  14. package/lib/tests/theia-quick-command.test.js.map +1 -1
  15. package/lib/tests/theia-sample-app.test.js +3 -30
  16. package/lib/tests/theia-sample-app.test.js.map +1 -1
  17. package/lib/tests/theia-toolbar.test.d.ts +2 -0
  18. package/lib/tests/theia-toolbar.test.d.ts.map +1 -0
  19. package/lib/tests/theia-toolbar.test.js +60 -0
  20. package/lib/tests/theia-toolbar.test.js.map +1 -0
  21. package/lib/tests/theia-workspace.test.js +5 -3
  22. package/lib/tests/theia-workspace.test.js.map +1 -1
  23. package/lib/theia-app.d.ts.map +1 -1
  24. package/lib/theia-app.js +1 -0
  25. package/lib/theia-app.js.map +1 -1
  26. package/lib/theia-explorer-view.d.ts +11 -4
  27. package/lib/theia-explorer-view.d.ts.map +1 -1
  28. package/lib/theia-explorer-view.js +54 -6
  29. package/lib/theia-explorer-view.js.map +1 -1
  30. package/lib/theia-monaco-editor.d.ts +16 -0
  31. package/lib/theia-monaco-editor.d.ts.map +1 -0
  32. package/lib/theia-monaco-editor.js +76 -0
  33. package/lib/theia-monaco-editor.js.map +1 -0
  34. package/lib/theia-output-channel.d.ts +25 -0
  35. package/lib/theia-output-channel.d.ts.map +1 -0
  36. package/lib/theia-output-channel.js +72 -0
  37. package/lib/theia-output-channel.js.map +1 -0
  38. package/lib/theia-output-view.d.ts +10 -0
  39. package/lib/theia-output-view.d.ts.map +1 -0
  40. package/lib/theia-output-view.js +82 -0
  41. package/lib/theia-output-view.js.map +1 -0
  42. package/lib/theia-quick-command-palette.d.ts +2 -1
  43. package/lib/theia-quick-command-palette.d.ts.map +1 -1
  44. package/lib/theia-quick-command-palette.js +9 -2
  45. package/lib/theia-quick-command-palette.js.map +1 -1
  46. package/lib/theia-text-editor.d.ts +2 -3
  47. package/lib/theia-text-editor.d.ts.map +1 -1
  48. package/lib/theia-text-editor.js +11 -40
  49. package/lib/theia-text-editor.js.map +1 -1
  50. package/lib/theia-toolbar-item.d.ts +11 -0
  51. package/lib/theia-toolbar-item.d.ts.map +1 -0
  52. package/lib/theia-toolbar-item.js +40 -0
  53. package/lib/theia-toolbar-item.js.map +1 -0
  54. package/lib/theia-toolbar.d.ts +20 -0
  55. package/lib/theia-toolbar.d.ts.map +1 -0
  56. package/lib/theia-toolbar.js +91 -0
  57. package/lib/theia-toolbar.js.map +1 -0
  58. package/lib/theia-tree-node.d.ts +1 -0
  59. package/lib/theia-tree-node.d.ts.map +1 -1
  60. package/lib/theia-tree-node.js +8 -0
  61. package/lib/theia-tree-node.js.map +1 -1
  62. package/package.json +13 -10
  63. package/src/index.ts +4 -0
  64. package/src/tests/resources/sample-files1/sampleFolderCompact/nestedFolder1/nestedFolder2/sampleFile1-1.txt +0 -0
  65. package/src/tests/theia-explorer-view.test.ts +26 -6
  66. package/src/tests/theia-main-menu.test.ts +2 -2
  67. package/src/tests/theia-output-view.test.ts +85 -0
  68. package/src/tests/theia-quick-command.test.ts +15 -0
  69. package/src/tests/theia-sample-app.test.ts +3 -34
  70. package/src/tests/theia-toolbar.test.ts +65 -0
  71. package/src/tests/theia-workspace.test.ts +5 -3
  72. package/src/theia-app.ts +1 -0
  73. package/src/theia-explorer-view.ts +60 -7
  74. package/src/theia-monaco-editor.ts +83 -0
  75. package/src/theia-output-channel.ts +88 -0
  76. package/src/theia-output-view.ts +87 -0
  77. package/src/theia-quick-command-palette.ts +10 -2
  78. package/src/theia-text-editor.ts +13 -44
  79. package/src/theia-toolbar-item.ts +41 -0
  80. package/src/theia-toolbar.ts +99 -0
  81. package/src/theia-tree-node.ts +10 -1
@@ -18,6 +18,8 @@ import { expect } from '@playwright/test';
18
18
  import { TheiaAboutDialog } from '../theia-about-dialog';
19
19
  import { TheiaApp } from '../theia-app';
20
20
  import { TheiaExplorerView } from '../theia-explorer-view';
21
+ import { TheiaNotificationIndicator } from '../theia-notification-indicator';
22
+ import { TheiaNotificationOverlay } from '../theia-notification-overlay';
21
23
  import { TheiaQuickCommandPalette } from '../theia-quick-command-palette';
22
24
  import test, { page } from './fixtures/theia-fixture';
23
25
 
@@ -34,6 +36,10 @@ test.describe('Theia Quick Command', () => {
34
36
  test('should show quick command palette', async () => {
35
37
  await quickCommand.open();
36
38
  expect(await quickCommand.isOpen()).toBe(true);
39
+ await quickCommand.hide();
40
+ expect(await quickCommand.isOpen()).toBe(false);
41
+ await quickCommand.open();
42
+ expect(await quickCommand.isOpen()).toBe(true);
37
43
  });
38
44
 
39
45
  test('should trigger \'About\' command after typing', async () => {
@@ -58,4 +64,13 @@ test.describe('Theia Quick Command', () => {
58
64
  expect(await explorerView.isDisplayed()).toBe(true);
59
65
  });
60
66
 
67
+ test('should trigger \'Quick Input: Test Positive Integer\' command by confirming via Enter', async () => {
68
+ await quickCommand.type('Test Positive', true);
69
+ expect(await quickCommand.isOpen()).toBe(true);
70
+ await quickCommand.type('6', true);
71
+ const notificationIndicator = new TheiaNotificationIndicator(app);
72
+ const notification = new TheiaNotificationOverlay(app, notificationIndicator);
73
+ expect(await notification.isEntryVisible('Positive Integer: 6')).toBe(true);
74
+ });
75
+
61
76
  });
@@ -14,44 +14,13 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
 
17
- import { TheiaApp } from '../theia-app';
18
-
19
17
  import { expect } from '@playwright/test';
18
+ import { TheiaApp } from '../theia-app';
19
+ import { TheiaToolbar } from '../theia-toolbar';
20
20
  import test, { page } from './fixtures/theia-fixture';
21
- import { TheiaPageObject } from '../theia-page-object';
22
-
23
- class TheiaSampleToolbar extends TheiaPageObject {
24
- protected selector = '#main-toolbar';
25
-
26
- async show(): Promise<void> {
27
- if (!await this.isShown()) {
28
- await this.toggle();
29
- }
30
- }
31
-
32
- async toggle(): Promise<void> {
33
- const isShown = await this.isShown();
34
- const viewMenu = await this.app.menuBar.openMenu('View');
35
- await viewMenu.clickMenuItem('Toggle Toolbar');
36
- isShown ? await this.waitUntilHidden() : await this.waitUntilShown();
37
- }
38
-
39
- async waitUntilHidden(): Promise<void> {
40
- await this.page.waitForSelector(this.selector, { state: 'hidden' });
41
- }
42
-
43
- async waitUntilShown(): Promise<void> {
44
- await this.page.waitForSelector(this.selector, { state: 'visible' });
45
- }
46
-
47
- async isShown(): Promise<boolean> {
48
- const toolbar = await this.page.$(this.selector);
49
- return !!toolbar && toolbar.isVisible();
50
- }
51
- }
52
21
 
53
22
  class TheiaSampleApp extends TheiaApp {
54
- protected toolbar = new TheiaSampleToolbar(this);
23
+ protected toolbar = new TheiaToolbar(this);
55
24
 
56
25
  override async waitForInitialized(): Promise<void> {
57
26
  await this.toolbar.show();
@@ -0,0 +1,65 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2023 EclipseSource and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { expect } from '@playwright/test';
18
+ import { TheiaApp } from '../theia-app';
19
+ import { TheiaToolbar } from '../theia-toolbar';
20
+ import test, { page } from './fixtures/theia-fixture';
21
+
22
+ let app: TheiaApp;
23
+ let toolbar: TheiaToolbar;
24
+
25
+ test.describe('Theia Toolbar', () => {
26
+
27
+ test.beforeAll(async () => {
28
+ app = await TheiaApp.load(page);
29
+ toolbar = new TheiaToolbar(app);
30
+ });
31
+
32
+ test('should toggle the toolbar and check visibility', async () => {
33
+ // depending on the user settings we have different starting conditions for the toolbar
34
+ const isShownInitially = await toolbar.isShown();
35
+ expect(await toolbar.isShown()).toBe(isShownInitially);
36
+ await toolbar.toggle();
37
+ expect(await toolbar.isShown()).toBe(!isShownInitially);
38
+ await toolbar.hide();
39
+ expect(await toolbar.isShown()).toBe(false);
40
+ await toolbar.show();
41
+ expect(await toolbar.isShown()).toBe(true);
42
+ });
43
+
44
+ test('should show the default toolbar tools of the sample Theia application', async () => {
45
+ expect(await toolbar.toolbarItems()).toHaveLength(5);
46
+ expect(await toolbar.toolbarItemIds()).toStrictEqual([
47
+ 'textEditor.commands.go.back',
48
+ 'textEditor.commands.go.forward',
49
+ 'workbench.action.splitEditorRight',
50
+ 'theia-sample-toolbar-contribution',
51
+ 'workbench.action.showCommands'
52
+ ]);
53
+ });
54
+
55
+ test('should trigger the "Command Palette" toolbar tool as expect the command palette to open', async () => {
56
+ const commandPaletteTool = await toolbar.toolBarItem('workbench.action.showCommands');
57
+ expect(commandPaletteTool).toBeDefined;
58
+ expect(await commandPaletteTool!.isEnabled()).toBe(true);
59
+
60
+ await commandPaletteTool!.trigger();
61
+ expect(await app.quickCommandPalette.isOpen()).toBe(true);
62
+ await app.quickCommandPalette.hide();
63
+ expect(await app.quickCommandPalette.isOpen()).toBe(false);
64
+ });
65
+ });
@@ -14,10 +14,10 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
 
17
+ import { expect } from '@playwright/test';
17
18
  import { TheiaApp } from '../theia-app';
18
19
  import { DOT_FILES_FILTER, TheiaExplorerView } from '../theia-explorer-view';
19
20
  import { TheiaWorkspace } from '../theia-workspace';
20
- import { expect } from '@playwright/test';
21
21
  import test, { page } from './fixtures/theia-fixture';
22
22
 
23
23
  test.describe('Theia Workspace', () => {
@@ -33,8 +33,9 @@ test.describe('Theia Workspace', () => {
33
33
  const ws = new TheiaWorkspace(['src/tests/resources/sample-files1']);
34
34
  const app = await TheiaApp.load(page, ws);
35
35
  const explorer = await app.openView(TheiaExplorerView);
36
- // resources/sample-files1 contains one folder and one file
36
+ // resources/sample-files1 contains two folders and one file
37
37
  expect(await explorer.existsDirectoryNode('sampleFolder')).toBe(true);
38
+ expect(await explorer.existsDirectoryNode('sampleFolderCompact')).toBe(true);
38
39
  expect(await explorer.existsFileNode('sample.txt')).toBe(true);
39
40
  });
40
41
 
@@ -42,8 +43,9 @@ test.describe('Theia Workspace', () => {
42
43
  const ws = new TheiaWorkspace(['src/tests/resources/sample-files1', 'src/tests/resources/sample-files2']);
43
44
  const app = await TheiaApp.load(page, ws);
44
45
  const explorer = await app.openView(TheiaExplorerView);
45
- // resources/sample-files1 contains one folder and one file
46
+ // resources/sample-files1 contains two folders and one file
46
47
  expect(await explorer.existsDirectoryNode('sampleFolder')).toBe(true);
48
+ expect(await explorer.existsDirectoryNode('sampleFolderCompact')).toBe(true);
47
49
  expect(await explorer.existsFileNode('sample.txt')).toBe(true);
48
50
  // resources/sample-files2 contains one file
49
51
  expect(await explorer.existsFileNode('another-sample.txt')).toBe(true);
package/src/theia-app.ts CHANGED
@@ -109,6 +109,7 @@ export class TheiaApp {
109
109
  throw Error('TheiaExplorerView could not be opened.');
110
110
  }
111
111
  if (expectFileNodes) {
112
+ await explorer.waitForVisibleFileNodes();
112
113
  const fileStatElements = await explorer.visibleFileStatNodes(DOT_FILES_FILTER);
113
114
  if (fileStatElements.length < 1) {
114
115
  throw Error('TheiaExplorerView is empty.');
@@ -112,11 +112,11 @@ export class TheiaExplorerView extends TheiaView {
112
112
  return file;
113
113
  }
114
114
 
115
- async fileStatNode(filePath: string): Promise<TheiaExplorerFileStatNode | undefined> {
116
- return this.fileStatNodeBySegments(...filePath.split('/'));
115
+ async fileStatNode(filePath: string, compact = false): Promise<TheiaExplorerFileStatNode | undefined> {
116
+ return compact ? this.compactFileStatNode(filePath) : this.fileStatNodeBySegments(...filePath.split('/'));
117
117
  }
118
118
 
119
- async fileStatNodeBySegments(...pathFragments: string[]): Promise<TheiaExplorerFileStatNode | undefined> {
119
+ protected async fileStatNodeBySegments(...pathFragments: string[]): Promise<TheiaExplorerFileStatNode | undefined> {
120
120
  await super.activate();
121
121
  const viewElement = await this.viewElement();
122
122
 
@@ -141,6 +141,35 @@ export class TheiaExplorerView extends TheiaView {
141
141
  return currentTreeNode;
142
142
  }
143
143
 
144
+ protected async compactFileStatNode(path: string): Promise<TheiaExplorerFileStatNode | undefined> {
145
+ // default setting `explorer.compactFolders=true` renders folders in a compact form - single child folders will be compressed in a combined tree element
146
+ await super.activate();
147
+ const viewElement = await this.viewElement();
148
+
149
+ // check if first segment folder needs to be expanded first (if folder has never been expanded, it will not show the compact folder structure)
150
+ await this.waitForVisibleFileNodes();
151
+ const firstSegment = path.split('/')[0];
152
+ const selector = this.treeNodeSelector(firstSegment);
153
+ const folderElement = await viewElement?.$(selector);
154
+ if (folderElement && await folderElement.isVisible()) {
155
+ const folderNode = await viewElement?.waitForSelector(selector, { state: 'visible' });
156
+ if (!folderNode) {
157
+ throw new Error(`Tree node '${selector}' not found in explorer`);
158
+ }
159
+ const folderFileStatNode = new TheiaExplorerFileStatNode(folderNode, this);
160
+ if (await folderFileStatNode.isCollapsed()) {
161
+ await folderFileStatNode.expand();
162
+ }
163
+ }
164
+ // now get tree node via the full path
165
+ const fullPathSelector = this.treeNodeSelector(path);
166
+ const treeNode = await viewElement?.waitForSelector(fullPathSelector, { state: 'visible' });
167
+ if (!treeNode) {
168
+ throw new Error(`Tree node '${fullPathSelector}' not found in explorer`);
169
+ }
170
+ return new TheiaExplorerFileStatNode(treeNode, this);
171
+ }
172
+
144
173
  async selectTreeNode(filePath: string): Promise<void> {
145
174
  await this.activate();
146
175
  const treeNode = await this.page.waitForSelector(this.treeNodeSelector(filePath));
@@ -148,6 +177,10 @@ export class TheiaExplorerView extends TheiaView {
148
177
  await treeNode.focus();
149
178
  } else {
150
179
  await treeNode.click({ modifiers: ['Control'] });
180
+ // make sure the click has been acted-upon before returning
181
+ while (!await this.isTreeNodeSelected(filePath)) {
182
+ console.debug('Waiting for clicked tree node to be selected: ' + filePath);
183
+ }
151
184
  }
152
185
  }
153
186
 
@@ -177,8 +210,8 @@ export class TheiaExplorerView extends TheiaView {
177
210
  await menuItem.click();
178
211
  }
179
212
 
180
- protected async existsNode(path: string, isDirectory: boolean): Promise<boolean> {
181
- const fileStatNode = await this.fileStatNode(path);
213
+ protected async existsNode(path: string, isDirectory: boolean, compact = false): Promise<boolean> {
214
+ const fileStatNode = await this.fileStatNode(path, compact);
182
215
  if (!fileStatNode) {
183
216
  return false;
184
217
  }
@@ -198,8 +231,14 @@ export class TheiaExplorerView extends TheiaView {
198
231
  return this.existsNode(path, false);
199
232
  }
200
233
 
201
- async existsDirectoryNode(path: string): Promise<boolean> {
202
- return this.existsNode(path, true);
234
+ async existsDirectoryNode(path: string, compact = false): Promise<boolean> {
235
+ return this.existsNode(path, true, compact);
236
+ }
237
+
238
+ async waitForTreeNodeVisible(path: string): Promise<void> {
239
+ // wait for tree node to be visible, e.g. after triggering create
240
+ const viewElement = await this.viewElement();
241
+ await viewElement?.waitForSelector(this.treeNodeSelector(path), { state: 'visible' });
203
242
  }
204
243
 
205
244
  async getNumberOfVisibleNodes(): Promise<number> {
@@ -231,4 +270,18 @@ export class TheiaExplorerView extends TheiaView {
231
270
  await this.refresh();
232
271
  }
233
272
 
273
+ override async waitForVisible(): Promise<void> {
274
+ await super.waitForVisible();
275
+ await this.page.waitForSelector(this.tabSelector, { state: 'visible' });
276
+ }
277
+
278
+ /**
279
+ * Waits until some non-dot file nodes are visible
280
+ */
281
+ async waitForVisibleFileNodes(): Promise<void> {
282
+ while ((await this.visibleFileStatNodes(DOT_FILES_FILTER)).length === 0) {
283
+ console.debug('Awaiting for tree nodes to appear');
284
+ }
285
+ }
286
+
234
287
  }
@@ -0,0 +1,83 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2023 EclipseSource and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { ElementHandle } from '@playwright/test';
18
+ import { TheiaPageObject } from './theia-page-object';
19
+ import { TheiaApp } from './theia-app';
20
+
21
+ export class TheiaMonacoEditor extends TheiaPageObject {
22
+ constructor(protected readonly selector: string, app: TheiaApp) {
23
+ super(app);
24
+ }
25
+
26
+ async waitForVisible(): Promise<void> {
27
+ await this.page.waitForSelector(this.selector, { state: 'visible' });
28
+ }
29
+
30
+ protected viewElement(): Promise<ElementHandle<SVGElement | HTMLElement> | null> {
31
+ return this.page.$(this.selector);
32
+ }
33
+
34
+ async numberOfLines(): Promise<number | undefined> {
35
+ await this.waitForVisible();
36
+ const viewElement = await this.viewElement();
37
+ const lineElements = await viewElement?.$$('.view-lines .view-line');
38
+ return lineElements?.length;
39
+ }
40
+
41
+ async textContentOfLineByLineNumber(lineNumber: number): Promise<string | undefined> {
42
+ await this.waitForVisible();
43
+ const lineElement = await this.lineByLineNumber(lineNumber);
44
+ const content = await lineElement?.textContent();
45
+ return content ? this.replaceEditorSymbolsWithSpace(content) : undefined;
46
+ }
47
+
48
+ async lineByLineNumber(lineNumber: number): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
49
+ await this.waitForVisible();
50
+ const viewElement = await this.viewElement();
51
+ const lines = await viewElement?.$$('.view-lines > .view-line');
52
+ if (!lines) {
53
+ throw new Error('Couldn\'t retrieve lines of monaco editor');
54
+ }
55
+
56
+ const linesWithXCoordinates = [];
57
+ for (const lineElement of lines) {
58
+ const box = await lineElement.boundingBox();
59
+ linesWithXCoordinates.push({ x: box ? box.x : Number.MAX_VALUE, lineElement });
60
+ }
61
+ linesWithXCoordinates.sort((a, b) => a.x.toString().localeCompare(b.x.toString()));
62
+ return linesWithXCoordinates[lineNumber - 1].lineElement;
63
+ }
64
+
65
+ async textContentOfLineContainingText(text: string): Promise<string | undefined> {
66
+ await this.waitForVisible();
67
+ const lineElement = await this.lineContainingText(text);
68
+ const content = await lineElement?.textContent();
69
+ return content ? this.replaceEditorSymbolsWithSpace(content) : undefined;
70
+ }
71
+
72
+ async lineContainingText(text: string): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
73
+ const viewElement = await this.viewElement();
74
+ return viewElement?.waitForSelector(`.view-lines .view-line:has-text("${text}")`);
75
+ }
76
+
77
+ protected replaceEditorSymbolsWithSpace(content: string): string | Promise<string | undefined> {
78
+ // [ ] &nbsp; => \u00a0 -- NO-BREAK SPACE
79
+ // [·] &middot; => \u00b7 -- MIDDLE DOT
80
+ // [] &zwnj; => \u200c -- ZERO WIDTH NON-JOINER
81
+ return content.replace(/[\u00a0\u00b7]/g, ' ').replace(/[\u200c]/g, '');
82
+ }
83
+ }
@@ -0,0 +1,88 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2023 EclipseSource and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { ElementHandle } from '@playwright/test';
18
+ import { TheiaOutputView } from './theia-output-view';
19
+ import { TheiaPageObject } from './theia-page-object';
20
+ import { isElementVisible } from './util';
21
+ import { TheiaMonacoEditor } from './theia-monaco-editor';
22
+
23
+ export interface TheiaOutputViewChannelData {
24
+ viewSelector: string;
25
+ dataUri: string;
26
+ channelName: string;
27
+ }
28
+
29
+ export class TheiaOutputViewChannel extends TheiaPageObject {
30
+
31
+ protected monacoEditor: TheiaMonacoEditor;
32
+
33
+ constructor(protected readonly data: TheiaOutputViewChannelData, protected readonly outputView: TheiaOutputView) {
34
+ super(outputView.app);
35
+ this.monacoEditor = new TheiaMonacoEditor(this.viewSelector, outputView.app);
36
+ }
37
+
38
+ protected get viewSelector(): string {
39
+ return this.data.viewSelector;
40
+ }
41
+
42
+ protected get dataUri(): string | undefined {
43
+ return this.data.dataUri;
44
+ }
45
+
46
+ protected get channelName(): string | undefined {
47
+ return this.data.channelName;
48
+ }
49
+
50
+ async waitForVisible(): Promise<void> {
51
+ await this.page.waitForSelector(this.viewSelector, { state: 'visible' });
52
+ }
53
+
54
+ async isDisplayed(): Promise<boolean> {
55
+ return isElementVisible(this.viewElement());
56
+ }
57
+
58
+ protected viewElement(): Promise<ElementHandle<SVGElement | HTMLElement> | null> {
59
+ return this.page.$(this.viewSelector);
60
+ }
61
+
62
+ async numberOfLines(): Promise<number | undefined> {
63
+ await this.waitForVisible();
64
+ return this.monacoEditor.numberOfLines();
65
+ }
66
+
67
+ async maxSeverityOfLineByLineNumber(lineNumber: number): Promise<'error' | 'warning' | 'info'> {
68
+ await this.waitForVisible();
69
+ const lineElement = await this.monacoEditor.lineByLineNumber(lineNumber);
70
+ const contents = await lineElement?.$$('span > span.mtk1');
71
+ if (!contents || contents.length < 1) {
72
+ throw new Error(`Could not find contents of line number ${lineNumber}!`);
73
+ }
74
+ const severityClassNames = await Promise.all(contents.map(
75
+ async content => (await content.getAttribute('class'))?.split(' ')[1]));
76
+
77
+ if (severityClassNames.includes('theia-output-error')) {
78
+ return 'error';
79
+ } else if (severityClassNames.includes('theia-output-warning')) {
80
+ return 'warning';
81
+ }
82
+ return 'info';
83
+ }
84
+
85
+ async textContentOfLineByLineNumber(lineNumber: number): Promise<string | undefined> {
86
+ return this.monacoEditor.textContentOfLineByLineNumber(lineNumber);
87
+ }
88
+ }
@@ -0,0 +1,87 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2023 EclipseSource and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { TheiaApp } from './theia-app';
18
+ import { TheiaOutputViewChannel } from './theia-output-channel';
19
+ import { TheiaView } from './theia-view';
20
+ import { normalizeId } from './util';
21
+
22
+ const TheiaOutputViewData = {
23
+ tabSelector: '#shell-tab-outputView',
24
+ viewSelector: '#outputView',
25
+ viewName: 'Output'
26
+ };
27
+
28
+ export class TheiaOutputView extends TheiaView {
29
+ constructor(app: TheiaApp) {
30
+ super(TheiaOutputViewData, app);
31
+ }
32
+
33
+ async isOutputChannelSelected(outputChannelName: string): Promise<boolean> {
34
+ await this.activate();
35
+ const contentPanel = await this.page.$('#theia-bottom-content-panel');
36
+ if (contentPanel && (await contentPanel.isVisible())) {
37
+ const channelList = await contentPanel.$('#outputChannelList');
38
+ const selectedChannel = await channelList?.$('div.theia-select-component-label');
39
+ if (selectedChannel && (await selectedChannel.textContent()) === outputChannelName) {
40
+ return true;
41
+ }
42
+ }
43
+ return false;
44
+ }
45
+
46
+ async getOutputChannel(outputChannelName: string): Promise<TheiaOutputViewChannel | undefined> {
47
+ await this.activate();
48
+ const channel = new TheiaOutputViewChannel(
49
+ {
50
+ viewSelector: 'div.p-Widget.theia-editor.p-DockPanel-widget > div.monaco-editor',
51
+ dataUri: normalizeId(`output:/${encodeURIComponent(outputChannelName)}`),
52
+ channelName: outputChannelName
53
+ },
54
+ this
55
+ );
56
+ await channel.waitForVisible();
57
+ if (await channel.isDisplayed()) {
58
+ return channel;
59
+ }
60
+ return undefined;
61
+ }
62
+
63
+ async selectOutputChannel(outputChannelName: string): Promise<boolean> {
64
+ await this.activate();
65
+ const contentPanel = await this.page.$('#theia-bottom-content-panel');
66
+ if (contentPanel && (await contentPanel.isVisible())) {
67
+ const channelSelectComponent = await contentPanel.$('#outputChannelList');
68
+ if (!channelSelectComponent) {
69
+ throw Error('Output Channel List not visible.');
70
+ }
71
+ // open output channel list component
72
+ await channelSelectComponent.click();
73
+ const channelContainer = await this.page.waitForSelector('#select-component-container > div.theia-select-component-dropdown');
74
+ if (!channelContainer) {
75
+ throw Error('Output Channel List could not be opened.');
76
+ }
77
+ const channels = await channelContainer.$$('div.theia-select-component-option-value');
78
+ for (const channel of channels) {
79
+ if (await channel.textContent() === outputChannelName) {
80
+ await channel.click();
81
+ }
82
+ }
83
+ return this.isOutputChannelSelected(outputChannelName);
84
+ }
85
+ return false;
86
+ }
87
+ }
@@ -27,6 +27,11 @@ export class TheiaQuickCommandPalette extends TheiaPageObject {
27
27
  await this.page.waitForSelector(this.selector);
28
28
  }
29
29
 
30
+ async hide(): Promise<void> {
31
+ await this.page.keyboard.press('Escape');
32
+ await this.page.waitForSelector(this.selector, { state: 'hidden' });
33
+ }
34
+
30
35
  async isOpen(): Promise<boolean> {
31
36
  try {
32
37
  await this.page.waitForSelector(this.selector, { timeout: 5000 });
@@ -54,14 +59,17 @@ export class TheiaQuickCommandPalette extends TheiaPageObject {
54
59
  await this.page.keyboard.press('Enter');
55
60
  }
56
61
 
57
- async type(command: string): Promise<void> {
62
+ async type(value: string, confirm = false): Promise<void> {
58
63
  if (!await this.isOpen()) {
59
64
  this.open();
60
65
  }
61
66
  const input = await this.page.waitForSelector(`${this.selector} .monaco-inputbox .input`);
62
67
  if (input != null) {
63
68
  await input.focus();
64
- await input.type(command, { delay: USER_KEY_TYPING_DELAY });
69
+ await input.type(value, { delay: USER_KEY_TYPING_DELAY });
70
+ if (confirm) {
71
+ await this.page.keyboard.press('Enter');
72
+ }
65
73
  }
66
74
  }
67
75