Skip to content

Commit e54371d

Browse files
authored
Merge pull request #11 from ls1intum/4-test-delete-a-file-and-ensure-it-disappears-from-the-file-explorer
4 test delete a file and ensure it disappears from the file explorer
2 parents 9bb1ffb + 9ffdf54 commit e54371d

19 files changed

+369
-139
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ node_modules/
1010
# For Local Development
1111
/.auth/
1212
playwright.env
13+
/test-data/

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,15 @@ This repository provides E2E integration tests for the [Theia Cloud IDE](https:/
1414

1515
## Setup
1616

17-
- Install dependencies
17+
- Get the latest Theia IDE image from [here](ghcr.io/eclipse-theia/theia-ide/theia-ide:latest) and run it. Put the corresponding URL into the env file
18+
19+
- Install dependencies and playwright
1820
```bash
1921
npm install
2022
```
21-
23+
```bash
24+
npx playwright install
25+
```
2226
- To run the tests on the deployed Theia instance, run:
2327
```bash
2428
npx playwright test --project=deployed

global.config.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
export let baseURL = 'https://theia.artemis.cit.tum.de';
2-
export let localURL = 'https://compassionate_ride.orb.local';
1+
export let baseURL = 'https://theia.artemis.cit.tum.de';

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"@types/node": "^22.15.3"
1313
},
1414
"dependencies": {
15-
"dotenv": "^16.5.0"
15+
"dotenv": "^16.5.0",
16+
"fs": "^0.0.1-security"
1617
}
1718
}

pages/ide/IDEPage.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,35 @@ export class IDEPage {
3030
await this.menuBar.waitForReady();
3131
await this.editor.waitForReady();
3232
await this.sideBar.waitForReady();
33+
await this.page.locator('.gs-header').waitFor({ state: 'visible' });
34+
await new Promise( resolve => setTimeout(resolve, 2000) );
3335
}
3436

3537
/**
3638
* Creates a new file with the given name
3739
* @param fileName Name of the file to create
3840
*/
39-
async createNewFile(fileName: string = 'Test-1'): Promise<void> {
41+
async createNewFile(fileName: string = 'Test-1', directory: string = 'project'): Promise<void> {
4042
await this.menuBar.openNewFileDialog();
4143
await this.page.getByRole('combobox', { name: 'input' }).fill(fileName);
4244
await this.page.getByRole('option', { name: `Create New File (${fileName}),` }).locator('a').click();
45+
await this.page.getByRole('combobox').selectOption('file:///home/' + directory);
4346
await this.page.getByRole('button', { name: 'Create File' }).click();
4447
}
4548

49+
async createNewFolder(folderName: string = 'Folder-1'): Promise<void> {
50+
await this.menuBar.openNewFolderDialog();
51+
await this.page.locator('.dialogBlock').getByRole('textbox', { name: 'Folder Name' }).fill(folderName);
52+
await this.page.locator('.dialogBlock').getByRole('button', { name: 'OK' }).click();
53+
}
54+
4655
/**
4756
* Opens a file from the explorer
4857
* @param fileName Name of the file to open
4958
*/
5059
async openFile(fileName: string): Promise<void> {
51-
await this.sideBar.toggleExplorer();
60+
await this.sideBar.openExplorer();
5261
await this.sideBar.selectFile(fileName);
53-
await this.sideBar.toggleExplorer();
5462
}
5563

5664
/**
@@ -65,6 +73,18 @@ export class IDEPage {
6573
await this.editor.save();
6674
}
6775

76+
/**
77+
* Deletes a file from the explorer
78+
* @param fileName Name of the file to delete
79+
*/
80+
async deleteFile(fileName: string): Promise<void> {
81+
await this.sideBar.openExplorer();
82+
await this.sideBar.deleteFile(fileName);
83+
await this.page.locator('.codicon-info').first().waitFor({ state: 'hidden' });
84+
await new Promise( resolve => setTimeout(resolve, 2000) );
85+
}
86+
87+
6888
/**
6989
* Opens the integrated terminal and executes a command
7090
* @param command Command to execute
@@ -77,8 +97,14 @@ export class IDEPage {
7797

7898
// Functions that return important locators for testing
7999

80-
getOpenedFileLocator(fileName: string): Locator {
81-
return this.page.getByRole('listitem', { name: `/home/project/${fileName}` });
100+
// returns locator for file in the editor
101+
getEditorOpenedFileLocator(fileName: string): Locator {
102+
return this.editor.pagePart.getByRole('listitem', { name: `/home/project/${fileName}` });
103+
}
104+
105+
// returns locator for file in the file explorer
106+
getExplorerOpenedFileLocator(fileName: string): Locator {
107+
return this.sideBar.pagePart.locator(`#explorer-view-container`).getByTitle(`/home/project/${fileName}`);
82108
}
83109

84110
}

pages/ide/components/Editor.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,24 @@ import { BaseComponent } from './BaseComponent';
55
* Handles interactions with the code editor
66
*/
77
export class Editor extends BaseComponent {
8+
9+
readonly pagePart = this.page.locator('#theia-main-content-panel');
10+
811
async waitForReady(): Promise<void> {
912
// Wait for the editor area to be visible
10-
await this.page.locator('#theia-main-content-panel').waitFor();
13+
await this.page.locator('#theia-main-content-panel').first().waitFor();
1114
}
1215

1316
async typeText(text: string): Promise<void> {
1417
await this.page.keyboard.type(text);
1518
}
1619

1720
async getCurrentText(): Promise<string> {
18-
return await this.page.locator('.monaco-editor').textContent() || '';
21+
return await this.pagePart.locator('.monaco-editor').textContent() || '';
1922
}
2023

2124
async focusOpenedFile(fileName: string): Promise<void> {
22-
await this.page.locator('.theia-tabBar-tab-row').getByText(fileName).click();
25+
await this.page.locator('#theia-main-content-panel').locator('.theia-tabBar-tab-row').getByText(fileName).click();
2326
}
2427

2528
async selectAll(): Promise<void> {

pages/ide/components/MenuBar.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,40 @@
1+
import { expect } from '@playwright/test';
12
import { BaseComponent } from './BaseComponent';
23

34
/**
45
* Represents the top menu bar of the IDE
56
* Handles interactions with File, Edit, View, etc. menus
67
*/
78
export class MenuBar extends BaseComponent {
9+
10+
readonly pagePart = this.page.locator('#theia-top-panel');
11+
812
async waitForReady(): Promise<void> {
9-
await this.page.locator('#theia-top-panel').waitFor();
13+
await this.page.locator('#theia-top-panel').first().waitFor();
1014
}
1115

1216
async clickFileMenu(): Promise<void> {
13-
await this.page.locator('#theia-top-panel').getByText('File').click();
17+
await this.pagePart.getByText('File').click();
1418
}
1519

1620
async clickMenuItem(menu: string, item: string): Promise<void> {
17-
await this.page.locator('#theia-top-panel').getByText(menu).click();
18-
await this.page.locator('[class*="Menu-content"]').getByText(item).click();
21+
await expect(async () => {
22+
await this.pagePart.getByText(menu).click();
23+
await expect(this.page.locator('[class*="MenuBar-menu"]')).toBeVisible();
24+
}).toPass();
25+
await this.page.locator('[class*="MenuBar-menu"]').getByText(item).click();
26+
}
27+
28+
async clickMenuItemNth(menu: string, item: string, nth: number): Promise<void> {
29+
await expect(async () => {
30+
await this.pagePart.getByText(menu).click();
31+
await expect(this.page.locator('[class*="MenuBar-menu"]')).toBeVisible();
32+
}).toPass();
33+
await this.page.locator('[class*="MenuBar-menu"]').getByText(item).nth(nth).click();
34+
}
35+
36+
async saveFile(): Promise<void> {
37+
await this.clickMenuItemNth('File', 'Save', 1);
1938
}
2039

2140
async openNewFileDialog(): Promise<void> {
@@ -29,4 +48,12 @@ export class MenuBar extends BaseComponent {
2948
async openTerminal(): Promise<void> {
3049
await this.clickMenuItem('Terminal', 'New Terminal');
3150
}
51+
52+
async pressUndo(): Promise<void> {
53+
await this.clickMenuItem('Edit', 'Undo');
54+
}
55+
56+
async pressRedo(): Promise<void> {
57+
await this.clickMenuItem('Edit', 'Redo');
58+
}
3259
}

pages/ide/components/SideBar.ts

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,78 @@
1+
import { expect } from '@playwright/test';
12
import { BaseComponent } from './BaseComponent';
23

34
/**
45
* Represents the side bar of the IDE
56
* Handles interactions with file explorer, search, git, etc.
67
*/
78
export class SideBar extends BaseComponent {
9+
10+
readonly pagePart = this.page.locator('#theia-left-content-panel');
11+
812
async waitForReady(): Promise<void> {
9-
await this.page.locator('#theia-left-content-panel div').waitFor();
13+
await this.page.locator('#theia-left-content-panel').first().waitFor();
1014
}
1115

16+
// Start of Explorer Section
17+
1218
async toggleExplorer(): Promise<void> {
13-
await this.page.locator('#shell-tab-explorer-view-container').click();
19+
await this.pagePart.locator('#shell-tab-explorer-view-container').click();
20+
}
21+
22+
async openExplorer(): Promise<void> {
23+
if (await this.pagePart.locator('.theia-sidepanel-title').getByText('EXPLORER').isHidden()) {
24+
await this.pagePart.locator('#shell-tab-explorer-view-container').click();
25+
}
26+
}
27+
28+
async closeExplorer(): Promise<void> {
29+
if (await this.pagePart.locator('.theia-sidepanel-title').getByText('EXPLORER').isVisible()) {
30+
await this.pagePart.locator('#shell-tab-explorer-view-container').click();
31+
}
32+
}
33+
34+
/**
35+
* Opens all folders in the given path in the explorer
36+
* @param folderPath Path of the folder to open in type '/folder1/folder2/folder3/file'
37+
*/
38+
async openFolderPath(folderPath: string): Promise<void> {
39+
const segments = folderPath.split("/").filter(Boolean);
40+
for (const segment of segments) {
41+
if (await this.page.locator('#theia-left-side-panel').getByTitle(segment).last().locator(`.theia-mod-collapsed`).isVisible()) {
42+
await this.page.locator('#theia-left-side-panel').getByTitle(segment).last().locator(`.theia-mod-collapsed`).click();
43+
}
44+
}
1445
}
1546

47+
// End of Explorer Section
48+
49+
// Start of Search Section
50+
1651
async openSearch(): Promise<void> {
17-
await this.page.locator('#shell-tab-search-view-container').click();
52+
await this.pagePart.locator('#shell-tab-search-view-container').click();
1853
}
1954

55+
// End of Search Section
56+
57+
// Start of Git Section
58+
2059
async openGit(): Promise<void> {
21-
await this.page.locator('#shell-tab-scm-view-container').click();
60+
await this.pagePart.locator('#shell-tab-scm-view-container').click();
2261
}
2362

63+
// End of Git Section
64+
2465
async selectFile(fileName: string): Promise<void> {
25-
await this.page.locator('#theia-left-side-panel').getByTitle(`/home/project/${fileName}`).click();
66+
await this.pagePart.locator('#theia-left-side-panel').getByTitle(`/home/project/${fileName}`).dblclick();
67+
}
68+
69+
async deleteFile(fileName: string): Promise<void> {
70+
await expect(async () => {
71+
await this.pagePart.locator('#theia-left-side-panel').getByTitle(`/home/project/${fileName}`).click({ button: "right" });
72+
await expect(this.page.locator('[class*="Menu-content"]')).toBeVisible();
73+
}).toPass();
74+
await this.page.locator('[class*="Menu-content"]').locator('[class*="Menu-itemLabel"]').getByText('Delete').click();
75+
await this.page.locator('.dialogBlock').getByText('OK').click();
2676
}
2777

2878
}

pages/ide/components/Terminal.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Locator } from '@playwright/test';
1+
import { expect, Locator } from '@playwright/test';
22
import { BaseComponent } from './BaseComponent';
33

44
/**
@@ -14,10 +14,12 @@ export class Terminal extends BaseComponent {
1414
await this.page.getByRole('listitem', { name: 'Terminal' }).waitFor();
1515
}
1616

17-
1817
async open(): Promise<void> {
19-
await this.page.locator('#theia-top-panel').getByText('Terminal').click();
20-
await this.page.locator('[class*="Menu-content"]').getByText('New Terminal').nth(0).click();
18+
await expect(async () => {
19+
await this.page.locator('#theia-top-panel').getByText('Terminal').click();
20+
await expect(this.page.locator('[class*="MenuBar-menu"]')).toBeVisible();
21+
}).toPass();
22+
await this.page.locator('[class*="MenuBar-menu"]').getByText('New Terminal').nth(0).click();
2123
await this.waitForReady();
2224
}
2325

@@ -40,4 +42,8 @@ export class Terminal extends BaseComponent {
4042
async close(): Promise<void> {
4143
await this.executeCommand('exit');
4244
}
45+
46+
async focusTerminal(): Promise<void> {
47+
await this.terminalLocator.getByRole('listitem', { name: 'Terminal' }).first().click();
48+
}
4349
}

pages/landing/LandingPage.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,30 @@
1-
import { Page } from '@playwright/test';
1+
import { Locator, Page } from '@playwright/test';
22

33
/**
44
* A class which encapsulates the landing page of Theia with UI selectors.
55
*/
66
export class LandingPage {
7-
private readonly page: Page;
7+
readonly page: Page;
8+
9+
readonly languageCLocator: Locator;
10+
readonly languageJavaLocator: Locator;
11+
readonly languageJSLocator: Locator;
12+
readonly languageOcamlLocator: Locator;
13+
readonly languagePythonLocator: Locator;
14+
readonly languageRustLocator: Locator;
815

916
constructor(page: Page) {
1017
this.page = page;
18+
this.languageCLocator = this.page.getByRole('button', { name: 'Launch C' });
19+
this.languageJavaLocator = this.page.getByRole('button', { name: 'Launch Java', exact: true });
20+
this.languageJSLocator = this.page.getByRole('button', { name: 'Launch Javascript' });
21+
this.languageOcamlLocator = this.page.getByRole('button', { name: 'Launch Ocaml' });
22+
this.languagePythonLocator = this.page.getByRole('button', { name: 'Launch Python' });
23+
this.languageRustLocator = this.page.getByRole('button', { name: 'Launch Rust' });
24+
}
25+
26+
async waitForReady() {
27+
await this.page.locator('img').waitFor();
1128
}
1229

1330
async clickLoginButton() {
@@ -21,8 +38,17 @@ export class LandingPage {
2138
await this.page.getByRole('button', { name: 'Sign in' }).click();
2239
}
2340

41+
async logout() {
42+
await this.page.getByRole('link', { name: 'logout' }).click();
43+
}
44+
2445
async launchLanguage(language: string) {
2546
const languageButton = await this.page.getByRole('button', { name: `Launch ${language}` });
2647
await languageButton.click();
2748
}
49+
50+
retrieveAllLanguageLocators() {
51+
return [this.languageCLocator, this.languageJavaLocator, this.languageJSLocator, this.languageOcamlLocator, this.languagePythonLocator, this.languageRustLocator];
52+
}
53+
2854
}

0 commit comments

Comments
 (0)