@theia/playwright 1.37.0-next.1 → 1.37.0-next.11
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/lib/index.d.ts +4 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +4 -0
- package/lib/index.js.map +1 -1
- package/lib/tests/theia-explorer-view.test.js +25 -5
- package/lib/tests/theia-explorer-view.test.js.map +1 -1
- package/lib/tests/theia-main-menu.test.js +2 -2
- package/lib/tests/theia-main-menu.test.js.map +1 -1
- package/lib/tests/theia-output-view.test.d.ts +2 -0
- package/lib/tests/theia-output-view.test.d.ts.map +1 -0
- package/lib/tests/theia-output-view.test.js +77 -0
- package/lib/tests/theia-output-view.test.js.map +1 -0
- package/lib/tests/theia-quick-command.test.js +14 -0
- package/lib/tests/theia-quick-command.test.js.map +1 -1
- package/lib/tests/theia-sample-app.test.js +3 -30
- package/lib/tests/theia-sample-app.test.js.map +1 -1
- package/lib/tests/theia-terminal-view.test.d.ts +2 -0
- package/lib/tests/theia-terminal-view.test.d.ts.map +1 -0
- package/lib/tests/theia-terminal-view.test.js +76 -0
- package/lib/tests/theia-terminal-view.test.js.map +1 -0
- package/lib/tests/theia-toolbar.test.d.ts +2 -0
- package/lib/tests/theia-toolbar.test.d.ts.map +1 -0
- package/lib/tests/theia-toolbar.test.js +60 -0
- package/lib/tests/theia-toolbar.test.js.map +1 -0
- package/lib/tests/theia-workspace.test.js +5 -3
- package/lib/tests/theia-workspace.test.js.map +1 -1
- package/lib/theia-app.d.ts +7 -0
- package/lib/theia-app.d.ts.map +1 -1
- package/lib/theia-app.js +31 -1
- package/lib/theia-app.js.map +1 -1
- package/lib/theia-explorer-view.d.ts +11 -4
- package/lib/theia-explorer-view.d.ts.map +1 -1
- package/lib/theia-explorer-view.js +54 -6
- package/lib/theia-explorer-view.js.map +1 -1
- package/lib/theia-monaco-editor.d.ts +16 -0
- package/lib/theia-monaco-editor.d.ts.map +1 -0
- package/lib/theia-monaco-editor.js +76 -0
- package/lib/theia-monaco-editor.js.map +1 -0
- package/lib/theia-output-channel.d.ts +25 -0
- package/lib/theia-output-channel.d.ts.map +1 -0
- package/lib/theia-output-channel.js +72 -0
- package/lib/theia-output-channel.js.map +1 -0
- package/lib/theia-output-view.d.ts +10 -0
- package/lib/theia-output-view.d.ts.map +1 -0
- package/lib/theia-output-view.js +82 -0
- package/lib/theia-output-view.js.map +1 -0
- package/lib/theia-quick-command-palette.d.ts +2 -1
- package/lib/theia-quick-command-palette.d.ts.map +1 -1
- package/lib/theia-quick-command-palette.js +9 -2
- package/lib/theia-quick-command-palette.js.map +1 -1
- package/lib/theia-terminal.d.ts +14 -0
- package/lib/theia-terminal.d.ts.map +1 -0
- package/lib/theia-terminal.js +60 -0
- package/lib/theia-terminal.js.map +1 -0
- package/lib/theia-text-editor.d.ts +2 -3
- package/lib/theia-text-editor.d.ts.map +1 -1
- package/lib/theia-text-editor.js +11 -40
- package/lib/theia-text-editor.js.map +1 -1
- package/lib/theia-toolbar-item.d.ts +11 -0
- package/lib/theia-toolbar-item.d.ts.map +1 -0
- package/lib/theia-toolbar-item.js +40 -0
- package/lib/theia-toolbar-item.js.map +1 -0
- package/lib/theia-toolbar.d.ts +20 -0
- package/lib/theia-toolbar.d.ts.map +1 -0
- package/lib/theia-toolbar.js +91 -0
- package/lib/theia-toolbar.js.map +1 -0
- package/lib/theia-tree-node.d.ts +1 -0
- package/lib/theia-tree-node.d.ts.map +1 -1
- package/lib/theia-tree-node.js +8 -0
- package/lib/theia-tree-node.js.map +1 -1
- package/package.json +13 -10
- package/src/index.ts +4 -0
- package/src/tests/resources/sample-files1/sampleFolderCompact/nestedFolder1/nestedFolder2/sampleFile1-1.txt +0 -0
- package/src/tests/theia-explorer-view.test.ts +26 -6
- package/src/tests/theia-main-menu.test.ts +2 -2
- package/src/tests/theia-output-view.test.ts +85 -0
- package/src/tests/theia-quick-command.test.ts +15 -0
- package/src/tests/theia-sample-app.test.ts +3 -34
- package/src/tests/theia-terminal-view.test.ts +85 -0
- package/src/tests/theia-toolbar.test.ts +65 -0
- package/src/tests/theia-workspace.test.ts +5 -3
- package/src/theia-app.ts +38 -1
- package/src/theia-explorer-view.ts +60 -7
- package/src/theia-monaco-editor.ts +83 -0
- package/src/theia-output-channel.ts +88 -0
- package/src/theia-output-view.ts +87 -0
- package/src/theia-quick-command-palette.ts +10 -2
- package/src/theia-terminal.ts +69 -0
- package/src/theia-text-editor.ts +13 -44
- package/src/theia-toolbar-item.ts +41 -0
- package/src/theia-toolbar.ts +99 -0
- package/src/theia-tree-node.ts +10 -1
|
@@ -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
|
+
// [ ] => \u00a0 -- NO-BREAK SPACE
|
|
79
|
+
// [·] · => \u00b7 -- MIDDLE DOT
|
|
80
|
+
// [] ‌ => \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(
|
|
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(
|
|
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
|
|
|
@@ -0,0 +1,69 @@
|
|
|
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 { TheiaApp } from './theia-app';
|
|
19
|
+
import { TheiaContextMenu } from './theia-context-menu';
|
|
20
|
+
import { TheiaMenu } from './theia-menu';
|
|
21
|
+
import { TheiaView } from './theia-view';
|
|
22
|
+
|
|
23
|
+
export class TheiaTerminal extends TheiaView {
|
|
24
|
+
|
|
25
|
+
constructor(tabId: string, app: TheiaApp) {
|
|
26
|
+
super({
|
|
27
|
+
tabSelector: `#shell-tab-terminal-${getTerminalId(tabId)}`,
|
|
28
|
+
viewSelector: `#terminal-${getTerminalId(tabId)}`
|
|
29
|
+
}, app);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async submit(text: string): Promise<void> {
|
|
33
|
+
await this.write(text);
|
|
34
|
+
const input = await this.waitForInputArea();
|
|
35
|
+
await input.press('Enter');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async write(text: string): Promise<void> {
|
|
39
|
+
await this.activate();
|
|
40
|
+
const input = await this.waitForInputArea();
|
|
41
|
+
await input.type(text);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async contents(): Promise<string> {
|
|
45
|
+
await this.activate();
|
|
46
|
+
await (await this.openContextMenu()).clickMenuItem('Select All');
|
|
47
|
+
await (await this.openContextMenu()).clickMenuItem('Copy');
|
|
48
|
+
return this.page.evaluate('navigator.clipboard.readText()');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
protected async openContextMenu(): Promise<TheiaMenu> {
|
|
52
|
+
await this.activate();
|
|
53
|
+
return TheiaContextMenu.open(this.app, () => this.waitForVisibleView());
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
protected async waitForInputArea(): Promise<ElementHandle<SVGElement | HTMLElement>> {
|
|
57
|
+
const view = await this.waitForVisibleView();
|
|
58
|
+
return view.waitForSelector('.xterm-helper-textarea');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
protected async waitForVisibleView(): Promise<ElementHandle<SVGElement | HTMLElement>> {
|
|
62
|
+
return this.page.waitForSelector(this.viewSelector, { state: 'visible' });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getTerminalId(tabId: string): string {
|
|
68
|
+
return tabId.substring(tabId.lastIndexOf('-') + 1);
|
|
69
|
+
}
|
package/src/theia-text-editor.ts
CHANGED
|
@@ -20,9 +20,12 @@ import { join } from 'path';
|
|
|
20
20
|
import { TheiaApp } from './theia-app';
|
|
21
21
|
import { TheiaEditor } from './theia-editor';
|
|
22
22
|
import { normalizeId, OSUtil, urlEncodePath } from './util';
|
|
23
|
+
import { TheiaMonacoEditor } from './theia-monaco-editor';
|
|
23
24
|
|
|
24
25
|
export class TheiaTextEditor extends TheiaEditor {
|
|
25
26
|
|
|
27
|
+
protected monacoEditor: TheiaMonacoEditor;
|
|
28
|
+
|
|
26
29
|
constructor(filePath: string, app: TheiaApp) {
|
|
27
30
|
// shell-tab-code-editor-opener:file:///c%3A/Users/user/AppData/Local/Temp/cloud-ws-JBUhb6/sample.txt:1
|
|
28
31
|
// code-editor-opener:file:///c%3A/Users/user/AppData/Local/Temp/cloud-ws-JBUhb6/sample.txt:1
|
|
@@ -30,19 +33,16 @@ export class TheiaTextEditor extends TheiaEditor {
|
|
|
30
33
|
tabSelector: normalizeId(`#shell-tab-code-editor-opener:file://${urlEncodePath(join(app.workspace.escapedPath, OSUtil.fileSeparator, filePath))}:1`),
|
|
31
34
|
viewSelector: normalizeId(`#code-editor-opener:file://${urlEncodePath(join(app.workspace.escapedPath, OSUtil.fileSeparator, filePath))}:1`) + '.theia-editor'
|
|
32
35
|
}, app);
|
|
36
|
+
this.monacoEditor = new TheiaMonacoEditor(this.viewSelector, app);
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
async numberOfLines(): Promise<number | undefined> {
|
|
36
40
|
await this.activate();
|
|
37
|
-
|
|
38
|
-
const lineElements = await viewElement?.$$('.view-lines .view-line');
|
|
39
|
-
return lineElements?.length;
|
|
41
|
+
return this.monacoEditor.numberOfLines();
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
async textContentOfLineByLineNumber(lineNumber: number): Promise<string | undefined> {
|
|
43
|
-
|
|
44
|
-
const content = await lineElement?.textContent();
|
|
45
|
-
return content ? this.replaceEditorSymbolsWithSpace(content) : undefined;
|
|
45
|
+
return this.monacoEditor.textContentOfLineByLineNumber(lineNumber);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
async replaceLineWithLineNumber(text: string, lineNumber: number): Promise<void> {
|
|
@@ -57,14 +57,14 @@ export class TheiaTextEditor extends TheiaEditor {
|
|
|
57
57
|
|
|
58
58
|
async selectLineWithLineNumber(lineNumber: number): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
|
|
59
59
|
await this.activate();
|
|
60
|
-
const lineElement = await this.lineByLineNumber(lineNumber);
|
|
60
|
+
const lineElement = await this.monacoEditor.lineByLineNumber(lineNumber);
|
|
61
61
|
await this.selectLine(lineElement);
|
|
62
62
|
return lineElement;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
async placeCursorInLineWithLineNumber(lineNumber: number): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
|
|
66
66
|
await this.activate();
|
|
67
|
-
const lineElement = await this.lineByLineNumber(lineNumber);
|
|
67
|
+
const lineElement = await this.monacoEditor.lineByLineNumber(lineNumber);
|
|
68
68
|
await this.placeCursorInLine(lineElement);
|
|
69
69
|
return lineElement;
|
|
70
70
|
}
|
|
@@ -74,28 +74,9 @@ export class TheiaTextEditor extends TheiaEditor {
|
|
|
74
74
|
await this.page.keyboard.press('Backspace');
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
protected async lineByLineNumber(lineNumber: number): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
|
|
78
|
-
await this.activate();
|
|
79
|
-
const viewElement = await this.viewElement();
|
|
80
|
-
const lines = await viewElement?.$$('.view-lines .view-line');
|
|
81
|
-
if (!lines) {
|
|
82
|
-
throw new Error(`Couldn't retrieve lines of text editor ${this.tabSelector}`);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const linesWithXCoordinates = [];
|
|
86
|
-
for (const lineElement of lines) {
|
|
87
|
-
const box = await lineElement.boundingBox();
|
|
88
|
-
linesWithXCoordinates.push({ x: box ? box.x : Number.MAX_VALUE, lineElement });
|
|
89
|
-
}
|
|
90
|
-
linesWithXCoordinates.sort((a, b) => a.x.toString().localeCompare(b.x.toString()));
|
|
91
|
-
return linesWithXCoordinates[lineNumber - 1].lineElement;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
77
|
async textContentOfLineContainingText(text: string): Promise<string | undefined> {
|
|
95
78
|
await this.activate();
|
|
96
|
-
|
|
97
|
-
const content = await lineElement?.textContent();
|
|
98
|
-
return content ? this.replaceEditorSymbolsWithSpace(content) : undefined;
|
|
79
|
+
return this.monacoEditor.textContentOfLineContainingText(text);
|
|
99
80
|
}
|
|
100
81
|
|
|
101
82
|
async replaceLineContainingText(newText: string, oldText: string): Promise<void> {
|
|
@@ -105,14 +86,14 @@ export class TheiaTextEditor extends TheiaEditor {
|
|
|
105
86
|
|
|
106
87
|
async selectLineContainingText(text: string): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
|
|
107
88
|
await this.activate();
|
|
108
|
-
const lineElement = await this.lineContainingText(text);
|
|
89
|
+
const lineElement = await this.monacoEditor.lineContainingText(text);
|
|
109
90
|
await this.selectLine(lineElement);
|
|
110
91
|
return lineElement;
|
|
111
92
|
}
|
|
112
93
|
|
|
113
94
|
async placeCursorInLineContainingText(text: string): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
|
|
114
95
|
await this.activate();
|
|
115
|
-
const lineElement = await this.lineContainingText(text);
|
|
96
|
+
const lineElement = await this.monacoEditor.lineContainingText(text);
|
|
116
97
|
await this.placeCursorInLine(lineElement);
|
|
117
98
|
return lineElement;
|
|
118
99
|
}
|
|
@@ -123,7 +104,7 @@ export class TheiaTextEditor extends TheiaEditor {
|
|
|
123
104
|
}
|
|
124
105
|
|
|
125
106
|
async addTextToNewLineAfterLineContainingText(textContainedByExistingLine: string, newText: string): Promise<void> {
|
|
126
|
-
const existingLine = await this.lineContainingText(textContainedByExistingLine);
|
|
107
|
+
const existingLine = await this.monacoEditor.lineContainingText(textContainedByExistingLine);
|
|
127
108
|
await this.placeCursorInLine(existingLine);
|
|
128
109
|
await this.page.keyboard.press('End');
|
|
129
110
|
await this.page.keyboard.press('Enter');
|
|
@@ -131,18 +112,13 @@ export class TheiaTextEditor extends TheiaEditor {
|
|
|
131
112
|
}
|
|
132
113
|
|
|
133
114
|
async addTextToNewLineAfterLineByLineNumber(lineNumber: number, newText: string): Promise<void> {
|
|
134
|
-
const existingLine = await this.lineByLineNumber(lineNumber);
|
|
115
|
+
const existingLine = await this.monacoEditor.lineByLineNumber(lineNumber);
|
|
135
116
|
await this.placeCursorInLine(existingLine);
|
|
136
117
|
await this.page.keyboard.press('End');
|
|
137
118
|
await this.page.keyboard.press('Enter');
|
|
138
119
|
await this.page.keyboard.type(newText);
|
|
139
120
|
}
|
|
140
121
|
|
|
141
|
-
protected async lineContainingText(text: string): Promise<ElementHandle<SVGElement | HTMLElement> | undefined> {
|
|
142
|
-
const viewElement = await this.viewElement();
|
|
143
|
-
return viewElement?.waitForSelector(`.view-lines .view-line:has-text("${text}")`);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
122
|
protected async selectLine(lineElement: ElementHandle<SVGElement | HTMLElement> | undefined): Promise<void> {
|
|
147
123
|
await lineElement?.click({ clickCount: 3 });
|
|
148
124
|
}
|
|
@@ -151,13 +127,6 @@ export class TheiaTextEditor extends TheiaEditor {
|
|
|
151
127
|
await lineElement?.click();
|
|
152
128
|
}
|
|
153
129
|
|
|
154
|
-
protected replaceEditorSymbolsWithSpace(content: string): string | Promise<string | undefined> {
|
|
155
|
-
// [ ] => \u00a0 -- NO-BREAK SPACE
|
|
156
|
-
// [·] · => \u00b7 -- MIDDLE DOT
|
|
157
|
-
// [] ‌ => \u200c -- ZERO WIDTH NON-JOINER
|
|
158
|
-
return content.replace(/[\u00a0\u00b7]/g, ' ').replace(/[\u200c]/g, '');
|
|
159
|
-
}
|
|
160
|
-
|
|
161
130
|
protected async selectedSuggestion(): Promise<ElementHandle<SVGElement | HTMLElement>> {
|
|
162
131
|
return this.page.waitForSelector(this.viewSelector + ' .monaco-list-row.show-file-icons.focused');
|
|
163
132
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
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 { TheiaApp } from './theia-app';
|
|
19
|
+
import { TheiaPageObject } from './theia-page-object';
|
|
20
|
+
|
|
21
|
+
export class TheiaToolbarItem extends TheiaPageObject {
|
|
22
|
+
constructor(app: TheiaApp, protected element: ElementHandle<SVGElement | HTMLElement>) {
|
|
23
|
+
super(app);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async commandId(): Promise<string | null> {
|
|
27
|
+
return this.element.getAttribute('id');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async isEnabled(): Promise<boolean> {
|
|
31
|
+
const classAttribute = await this.element.getAttribute('class');
|
|
32
|
+
if (classAttribute === undefined || classAttribute === null) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return classAttribute.includes('enabled');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async trigger(): Promise<void> {
|
|
39
|
+
await this.element.click();
|
|
40
|
+
}
|
|
41
|
+
}
|