Skip to content

Commit 50eed38

Browse files
committed
refactoring tests
1 parent 6010891 commit 50eed38

24 files changed

+289
-310
lines changed

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

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

1818
export class BasePage {
1919
readonly page: Page;
20-
readonly e2eTestFolder: Locator;
20+
21+
// Common Zeppelin component locators
22+
readonly zeppelinNodeList: Locator;
23+
readonly zeppelinWorkspace: Locator;
24+
readonly zeppelinHeader: Locator;
25+
readonly zeppelinPageHeader: Locator;
2126

2227
constructor(page: Page) {
2328
this.page = page;
24-
this.e2eTestFolder = page.locator(`[data-testid="folder-${E2E_TEST_FOLDER}"]`);
29+
this.zeppelinNodeList = page.locator('zeppelin-node-list');
30+
this.zeppelinWorkspace = page.locator('zeppelin-workspace');
31+
this.zeppelinHeader = page.locator('zeppelin-header');
32+
this.zeppelinPageHeader = page.locator('zeppelin-page-header');
2533
}
2634

2735
async waitForPageLoad(): Promise<void> {
2836
await this.page.waitForLoadState('domcontentloaded', { timeout: 15000 });
2937
}
38+
39+
// Common navigation patterns
40+
async navigateToRoute(
41+
route: string,
42+
options?: { timeout?: number; waitUntil?: 'load' | 'domcontentloaded' | 'networkidle' }
43+
): Promise<void> {
44+
await this.page.goto(`/#${route}`, {
45+
waitUntil: 'domcontentloaded',
46+
timeout: 60000,
47+
...options
48+
});
49+
await this.waitForPageLoad();
50+
}
51+
52+
async navigateToHome(): Promise<void> {
53+
await this.navigateToRoute('/');
54+
}
55+
56+
getCurrentPath(): string {
57+
const url = new URL(this.page.url());
58+
return url.hash || url.pathname;
59+
}
60+
61+
async waitForUrlNotContaining(fragment: string): Promise<void> {
62+
await this.page.waitForURL(url => !url.toString().includes(fragment));
63+
}
64+
65+
// Common form interaction patterns
66+
async fillInput(locator: Locator, value: string, options?: { timeout?: number; force?: boolean }): Promise<void> {
67+
await locator.fill(value, { timeout: 15000, ...options });
68+
}
69+
70+
async clickElement(locator: Locator, options?: { timeout?: number; force?: boolean }): Promise<void> {
71+
await locator.click({ timeout: 15000, ...options });
72+
}
73+
74+
async getInputValue(locator: Locator): Promise<string> {
75+
return await locator.inputValue();
76+
}
77+
78+
async isElementVisible(locator: Locator): Promise<boolean> {
79+
return await locator.isVisible();
80+
}
81+
82+
async isElementEnabled(locator: Locator): Promise<boolean> {
83+
return await locator.isEnabled();
84+
}
85+
86+
async getElementText(locator: Locator): Promise<string> {
87+
return (await locator.textContent()) || '';
88+
}
3089
}

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.clickElement(this.themeToggleButton);
2828
}
2929

3030
async assertDarkTheme() {

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: 36 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@
1010
* limitations under the License.
1111
*/
1212

13-
import { expect, Locator, Page } from '@playwright/test';
14-
import { getCurrentPath, waitForUrlNotContaining } from '../utils';
13+
import { Locator, Page } from '@playwright/test';
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.isElementVisible(this.welcomeHeading);
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.isElementVisible(this.anonymousUserIndicator);
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.clickElement(this.zeppelinLogo);
152130
}
153131

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

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

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

172150
async clickCreateNewNote(): Promise<void> {
173-
await this.nodeList.createNewNoteLink.click();
151+
await this.clickElement(this.nodeList.createNewNoteLink);
152+
await this.createNoteModal.waitFor({ state: 'visible' });
153+
}
154+
155+
async createNote(notebookName: string): Promise<void> {
156+
await this.clickCreateNewNote();
157+
await this.fillInput(this.notebookNameInput, notebookName, { force: true });
158+
159+
// Wait for input value to be set (especially important for Webkit)
160+
await this.page.waitForFunction(
161+
name => {
162+
const input = document.querySelector('input[name="noteName"]') as HTMLInputElement;
163+
return input && input.value === name;
164+
},
165+
notebookName,
166+
{ timeout: 5000 }
167+
);
168+
169+
await this.clickElement(this.createNoteButton);
170+
await this.waitForPageLoad();
174171
}
175172

176173
async clickImportNote(): Promise<void> {
177-
await this.nodeList.importNoteLink.click();
174+
await this.clickElement(this.nodeList.importNoteLink);
178175
}
179176

180177
async filterNotes(searchTerm: string): Promise<void> {
181-
await this.nodeList.filterInput.fill(searchTerm);
178+
await this.fillInput(this.nodeList.filterInput, searchTerm);
182179
}
183180

184181
async waitForRefreshToComplete(): Promise<void> {

0 commit comments

Comments
 (0)