Skip to content

Commit 450cb9c

Browse files
committed
refactoring tests
1 parent 6010891 commit 450cb9c

28 files changed

+307
-355
lines changed

zeppelin-web-angular/e2e/models/base-page.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,50 @@ export const BASE_URL = 'http://localhost:4200';
1717

1818
export class BasePage {
1919
readonly page: Page;
20-
readonly e2eTestFolder: Locator;
20+
21+
readonly zeppelinNodeList: Locator;
22+
readonly zeppelinWorkspace: Locator;
23+
readonly zeppelinPageHeader: Locator;
24+
readonly zeppelinHeader: Locator;
2125

2226
constructor(page: Page) {
2327
this.page = page;
24-
this.e2eTestFolder = page.locator(`[data-testid="folder-${E2E_TEST_FOLDER}"]`);
28+
this.zeppelinNodeList = page.locator('zeppelin-node-list');
29+
this.zeppelinWorkspace = page.locator('zeppelin-workspace');
30+
this.zeppelinPageHeader = page.locator('zeppelin-page-header');
31+
this.zeppelinHeader = page.locator('zeppelin-header');
2532
}
2633

2734
async waitForPageLoad(): Promise<void> {
2835
await this.page.waitForLoadState('domcontentloaded', { timeout: 15000 });
2936
}
37+
38+
async navigateToRoute(
39+
route: string,
40+
options?: { timeout?: number; waitUntil?: 'load' | 'domcontentloaded' | 'networkidle' }
41+
): Promise<void> {
42+
await this.page.goto(`/#${route}`, {
43+
waitUntil: 'domcontentloaded',
44+
timeout: 60000,
45+
...options
46+
});
47+
await this.waitForPageLoad();
48+
}
49+
50+
async navigateToHome(): Promise<void> {
51+
await this.navigateToRoute('/');
52+
}
53+
54+
getCurrentPath(): string {
55+
const url = new URL(this.page.url());
56+
return url.hash || url.pathname;
57+
}
58+
59+
async waitForUrlNotContaining(fragment: string): Promise<void> {
60+
await this.page.waitForURL(url => !url.toString().includes(fragment));
61+
}
62+
63+
async getElementText(locator: Locator): Promise<string> {
64+
return (await locator.textContent()) || '';
65+
}
3066
}

zeppelin-web-angular/e2e/models/dark-mode-page.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export class DarkModePage extends BasePage {
2424
}
2525

2626
async toggleTheme() {
27-
await this.themeToggleButton.click();
27+
await this.themeToggleButton.click({ timeout: 15000 });
2828
}
2929

3030
async assertDarkTheme() {

zeppelin-web-angular/e2e/models/folder-rename-page.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@ export class FolderRenamePage extends BasePage {
4747
await this.page.waitForSelector('zeppelin-node-list', { state: 'visible' });
4848

4949
const folderNode = await this.getFolderNode(folderName);
50-
51-
await folderNode.hover();
50+
await folderNode.hover({ force: true });
5251
}
5352

5453
async clickDeleteIcon(folderName: string): Promise<void> {
@@ -91,6 +90,8 @@ export class FolderRenamePage extends BasePage {
9190
}
9291

9392
async clickConfirm(): Promise<void> {
93+
// Wait for button to be enabled before clicking
94+
await expect(this.confirmButton).toBeEnabled({ timeout: 5000 });
9495
await this.confirmButton.click();
9596

9697
// Wait for validation or submission to process by monitoring modal state

zeppelin-web-angular/e2e/models/folder-rename-page.util.ts

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,20 @@
1010
* limitations under the License.
1111
*/
1212

13-
import { expect, Page } from '@playwright/test';
13+
import { expect } from '@playwright/test';
1414
import { FolderRenamePage } from './folder-rename-page';
1515

1616
export class FolderRenamePageUtil {
17-
constructor(
18-
private readonly page: Page,
19-
private readonly folderRenamePage: FolderRenamePage
20-
) {}
17+
private folderRenamePage: FolderRenamePage;
2118

22-
private getFolderNode(folderName: string) {
23-
return this.page
24-
.locator('.node')
25-
.filter({
26-
has: this.page.locator('.folder .name', { hasText: folderName })
27-
})
28-
.first();
19+
constructor(folderRenamePage: FolderRenamePage) {
20+
this.folderRenamePage = folderRenamePage;
2921
}
3022

3123
async verifyRenameButtonIsVisible(folderName: string): Promise<void> {
3224
await this.folderRenamePage.hoverOverFolder(folderName);
3325
const folderNode = this.getFolderNode(folderName);
3426
const renameButton = folderNode.locator('.folder .operation a[nz-tooltip][nztooltiptitle="Rename folder"]');
35-
// Just verify the element exists in DOM, not visibility(for Webkit & Edge)
3627
await expect(renameButton).toHaveCount(1);
3728
}
3829

@@ -53,8 +44,6 @@ export class FolderRenamePageUtil {
5344

5445
async verifyRenameModalOpens(folderName: string): Promise<void> {
5546
await this.folderRenamePage.clickRenameMenuItem(folderName);
56-
57-
// Wait for modal to appear with extended timeout
5847
await expect(this.folderRenamePage.renameModal).toBeVisible({ timeout: 10000 });
5948
}
6049

@@ -71,21 +60,17 @@ export class FolderRenamePageUtil {
7160

7261
await this.folderRenamePage.clickConfirm();
7362

74-
// Wait for the modal to disappear
7563
await expect(this.folderRenamePage.renameModal).not.toBeVisible({ timeout: 10000 });
7664

77-
// Wait for the UI to update before reloading for the old name to disappear
78-
const oldFolder = this.page.locator('.folder .name', { hasText: oldName });
65+
const oldFolder = this.folderRenamePage.page.locator('.folder .name', { hasText: oldName });
7966
await expect(oldFolder).not.toBeVisible({ timeout: 10000 });
8067

81-
// Optional: Keep the reload as a final sanity check against the backend state
82-
await this.page.reload();
83-
await this.page.waitForLoadState('networkidle', { timeout: 15000 });
68+
await this.folderRenamePage.page.reload();
69+
await this.folderRenamePage.waitForPageLoad();
8470

8571
const baseNewName = newName.split('/').pop();
8672

87-
// Ensure the folder list is stable and contains the new folder after reload
88-
await this.page.waitForFunction(
73+
await this.folderRenamePage.page.waitForFunction(
8974
([expectedBaseName]) => {
9075
if (!expectedBaseName) {
9176
throw Error('Renamed Folder name is not exist.');
@@ -103,15 +88,12 @@ export class FolderRenamePageUtil {
10388
await this.folderRenamePage.clickRenameMenuItem(folderName);
10489
await this.folderRenamePage.clearNewName();
10590

106-
// NEW ASSERTION: The confirm button should be disabled when the input is empty.
10791
await expect(this.folderRenamePage.confirmButton).toBeDisabled({ timeout: 5000 });
10892

109-
// Clean up: Click cancel to close the modal after verifying validation.
11093
await this.folderRenamePage.clickCancel();
11194
await expect(this.folderRenamePage.renameModal).not.toBeVisible({ timeout: 5000 });
11295

113-
// Verify the original folder still exists and was not renamed or deleted.
114-
const originalFolderLocator = this.page.locator('.folder .name', { hasText: folderName });
96+
const originalFolderLocator = this.folderRenamePage.page.locator('.folder .name', { hasText: folderName });
11597
await expect(originalFolderLocator).toBeVisible({ timeout: 5000 });
11698
}
11799

@@ -127,4 +109,13 @@ export class FolderRenamePageUtil {
127109
await this.verifyRenameButtonIsVisible(folderName);
128110
await this.verifyDeleteButtonIsVisible(folderName);
129111
}
112+
113+
private getFolderNode(folderName: string) {
114+
return this.folderRenamePage.page
115+
.locator('.node')
116+
.filter({
117+
has: this.folderRenamePage.page.locator('.folder .name', { hasText: folderName })
118+
})
119+
.first();
120+
}
130121
}

zeppelin-web-angular/e2e/models/home-page.ts

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
*/
1212

1313
import { expect, Locator, Page } from '@playwright/test';
14-
import { getCurrentPath, waitForUrlNotContaining } from '../utils';
1514
import { BasePage } from './base-page';
1615

1716
export class HomePage extends BasePage {
@@ -20,7 +19,6 @@ export class HomePage extends BasePage {
2019
readonly helpSection: Locator;
2120
readonly communitySection: Locator;
2221
readonly createNewNoteButton: Locator;
23-
readonly filterInput: Locator;
2422
readonly zeppelinLogo: Locator;
2523
readonly anonymousUserIndicator: Locator;
2624
readonly welcomeSection: Locator;
@@ -29,10 +27,12 @@ export class HomePage extends BasePage {
2927
readonly helpCommunityColumn: Locator;
3028
readonly welcomeDescription: Locator;
3129
readonly refreshNoteButton: Locator;
32-
readonly notebookList: Locator;
3330
readonly notebookHeading: Locator;
3431
readonly helpHeading: Locator;
3532
readonly communityHeading: Locator;
33+
readonly createNoteModal: Locator;
34+
readonly createNoteButton: Locator;
35+
readonly notebookNameInput: Locator;
3636
readonly externalLinks: {
3737
documentation: Locator;
3838
mailingList: Locator;
@@ -67,7 +67,6 @@ export class HomePage extends BasePage {
6767
this.helpSection = page.locator('text=Help').first();
6868
this.communitySection = page.locator('text=Community').first();
6969
this.createNewNoteButton = page.getByText('Create new Note', { exact: true }).first();
70-
this.filterInput = page.locator('input[placeholder*="Filter"]');
7170
this.zeppelinLogo = page.locator('text=Zeppelin').first();
7271
this.anonymousUserIndicator = page.locator('text=anonymous');
7372
this.welcomeSection = page.locator('.welcome');
@@ -76,10 +75,12 @@ export class HomePage extends BasePage {
7675
this.helpCommunityColumn = page.locator('[nz-col]').last();
7776
this.welcomeDescription = page.locator('.welcome').getByText('Zeppelin is web-based notebook');
7877
this.refreshNoteButton = page.locator('a.refresh-note');
79-
this.notebookList = page.locator('zeppelin-node-list');
8078
this.notebookHeading = this.notebookColumn.locator('h3');
8179
this.helpHeading = page.locator('h3').filter({ hasText: 'Help' });
8280
this.communityHeading = page.locator('h3').filter({ hasText: 'Community' });
81+
this.createNoteModal = page.locator('div.ant-modal-content');
82+
this.createNoteButton = this.createNoteModal.locator('button', { hasText: 'Create' });
83+
this.notebookNameInput = this.createNoteModal.locator('input[name="noteName"]');
8384

8485
this.externalLinks = {
8586
documentation: page.locator('a[href*="zeppelin.apache.org/docs"]'),
@@ -110,45 +111,22 @@ export class HomePage extends BasePage {
110111
};
111112
}
112113

113-
async navigateToHome(): Promise<void> {
114-
await this.page.goto('/#/', {
115-
waitUntil: 'load',
116-
timeout: 60000
117-
});
118-
await this.waitForPageLoad();
119-
}
120-
121114
async navigateToLogin(): Promise<void> {
122-
await this.page.goto('/#/login');
123-
await this.waitForPageLoad();
115+
await this.navigateToRoute('/login');
124116
// Wait for potential redirect to complete by checking URL change
125-
await waitForUrlNotContaining(this.page, '#/login');
117+
await this.waitForUrlNotContaining('#/login');
126118
}
127119

128120
async isHomeContentDisplayed(): Promise<boolean> {
129-
try {
130-
await expect(this.welcomeHeading).toBeVisible();
131-
return true;
132-
} catch {
133-
return false;
134-
}
121+
return this.welcomeHeading.isVisible();
135122
}
136123

137124
async isAnonymousUser(): Promise<boolean> {
138-
try {
139-
await expect(this.anonymousUserIndicator).toBeVisible();
140-
return true;
141-
} catch {
142-
return false;
143-
}
125+
return this.anonymousUserIndicator.isVisible();
144126
}
145127

146128
async clickZeppelinLogo(): Promise<void> {
147-
await this.zeppelinLogo.click();
148-
}
149-
150-
getCurrentPath(): string {
151-
return getCurrentPath(this.page);
129+
await this.zeppelinLogo.click({ timeout: 15000 });
152130
}
153131

154132
async getWelcomeHeadingText(): Promise<string> {
@@ -162,23 +140,57 @@ export class HomePage extends BasePage {
162140
}
163141

164142
async clickRefreshNotes(): Promise<void> {
165-
await this.refreshNoteButton.click();
143+
await this.refreshNoteButton.click({ timeout: 15000 });
166144
}
167145

168146
async isNotebookListVisible(): Promise<boolean> {
169-
return this.notebookList.isVisible();
147+
return this.zeppelinNodeList.isVisible();
170148
}
171149

172150
async clickCreateNewNote(): Promise<void> {
173-
await this.nodeList.createNewNoteLink.click();
151+
await this.nodeList.createNewNoteLink.click({ timeout: 15000 });
152+
await this.createNoteModal.waitFor({ state: 'visible' });
153+
}
154+
155+
async createNote(notebookName: string): Promise<void> {
156+
await this.clickCreateNewNote();
157+
158+
// Wait for the modal form to be fully rendered with proper labels
159+
await this.page.waitForSelector('nz-form-label', { timeout: 10000 });
160+
161+
await this.page.waitForFunction(
162+
() => {
163+
const labels = Array.from(document.querySelectorAll('nz-form-label'));
164+
return labels.some(
165+
label => label.textContent?.includes('Note Name') || label.textContent?.includes('Clone Note')
166+
);
167+
},
168+
{ timeout: 10000 }
169+
);
170+
171+
// Wait for the input field to be ready and enabled
172+
await expect(this.notebookNameInput).toBeVisible({ timeout: 10000 });
173+
await expect(this.notebookNameInput).toBeEnabled({ timeout: 5000 });
174+
175+
// Clear any existing content and fill notebook name
176+
await this.notebookNameInput.clear();
177+
await this.notebookNameInput.fill(notebookName);
178+
179+
// Verify the input was filled correctly
180+
await expect(this.notebookNameInput).toHaveValue(notebookName);
181+
182+
// Click the 'Create' button in the modal
183+
await expect(this.createNoteButton).toBeEnabled({ timeout: 5000 });
184+
await this.createNoteButton.click({ timeout: 15000 });
185+
await this.waitForPageLoad();
174186
}
175187

176188
async clickImportNote(): Promise<void> {
177-
await this.nodeList.importNoteLink.click();
189+
await this.nodeList.importNoteLink.click({ timeout: 15000 });
178190
}
179191

180192
async filterNotes(searchTerm: string): Promise<void> {
181-
await this.nodeList.filterInput.fill(searchTerm);
193+
await this.nodeList.filterInput.fill(searchTerm, { timeout: 15000 });
182194
}
183195

184196
async waitForRefreshToComplete(): Promise<void> {

0 commit comments

Comments
 (0)