Skip to content

Commit 9f41d7d

Browse files
committed
update from e2e/notebook
1 parent 6010891 commit 9f41d7d

File tree

5 files changed

+104
-34
lines changed

5 files changed

+104
-34
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/home-page.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ export class HomePage extends BasePage {
3333
readonly notebookHeading: Locator;
3434
readonly helpHeading: Locator;
3535
readonly communityHeading: Locator;
36+
readonly createNoteModal: Locator;
37+
readonly createNoteButton: Locator;
38+
readonly notebookNameInput: Locator;
3639
readonly externalLinks: {
3740
documentation: Locator;
3841
mailingList: Locator;
@@ -80,6 +83,9 @@ export class HomePage extends BasePage {
8083
this.notebookHeading = this.notebookColumn.locator('h3');
8184
this.helpHeading = page.locator('h3').filter({ hasText: 'Help' });
8285
this.communityHeading = page.locator('h3').filter({ hasText: 'Community' });
86+
this.createNoteModal = page.locator('div.ant-modal-content');
87+
this.createNoteButton = this.createNoteModal.locator('button', { hasText: 'Create' });
88+
this.notebookNameInput = this.createNoteModal.locator('input[name="noteName"]');
8389

8490
this.externalLinks = {
8591
documentation: page.locator('a[href*="zeppelin.apache.org/docs"]'),
@@ -171,6 +177,25 @@ export class HomePage extends BasePage {
171177

172178
async clickCreateNewNote(): Promise<void> {
173179
await this.nodeList.createNewNoteLink.click();
180+
await this.createNoteModal.waitFor({ state: 'visible' });
181+
}
182+
183+
async createNote(notebookName: string): Promise<void> {
184+
await this.clickCreateNewNote();
185+
await this.fillInput(this.notebookNameInput, notebookName, { force: true });
186+
187+
// Wait for input value to be set (especially important for Webkit)
188+
await this.page.waitForFunction(
189+
name => {
190+
const input = document.querySelector('input[name="noteName"]') as HTMLInputElement;
191+
return input && input.value === name;
192+
},
193+
notebookName,
194+
{ timeout: 5000 }
195+
);
196+
197+
await this.clickElement(this.createNoteButton);
198+
await this.waitForPageLoad();
174199
}
175200

176201
async clickImportNote(): Promise<void> {

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

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export class LoginPage extends BasePage {
1919
readonly loginButton: Locator;
2020
readonly welcomeTitle: Locator;
2121
readonly formContainer: Locator;
22+
readonly errorMessage: Locator;
2223

2324
constructor(page: Page) {
2425
super(page);
@@ -27,27 +28,24 @@ export class LoginPage extends BasePage {
2728
this.loginButton = page.getByRole('button', { name: 'Login' });
2829
this.welcomeTitle = page.getByRole('heading', { name: 'Welcome to Zeppelin!' });
2930
this.formContainer = page.locator('form[nz-form]');
31+
this.errorMessage = page.locator("text=The username and password that you entered don't match.").first();
3032
}
3133

3234
async navigate(): Promise<void> {
33-
await this.page.goto('/#/login');
34-
await this.waitForPageLoad();
35+
await this.navigateToRoute('/login');
3536
}
3637

3738
async login(username: string, password: string): Promise<void> {
38-
await this.userNameInput.fill(username);
39-
await this.passwordInput.fill(password);
40-
await this.loginButton.click();
39+
await this.fillInput(this.userNameInput, username);
40+
await this.fillInput(this.passwordInput, password);
41+
await this.clickElement(this.loginButton);
4142
}
4243

4344
async waitForErrorMessage(): Promise<void> {
44-
await this.page.waitForSelector("text=The username and password that you entered don't match.", { timeout: 5000 });
45+
await this.errorMessage.waitFor({ state: 'visible', timeout: 5000 });
4546
}
4647

4748
async getErrorMessageText(): Promise<string> {
48-
return (
49-
(await this.page.locator("text=The username and password that you entered don't match.").first().textContent()) ||
50-
''
51-
);
49+
return this.getElementText(this.errorMessage);
5250
}
5351
}

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

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,10 @@ import { Locator, Page } from '@playwright/test';
1414
import { BasePage } from './base-page';
1515

1616
export class WorkspacePage extends BasePage {
17-
readonly workspaceComponent: Locator;
18-
readonly header: Locator;
1917
readonly routerOutlet: Locator;
2018

2119
constructor(page: Page) {
2220
super(page);
23-
this.workspaceComponent = page.locator('zeppelin-workspace');
24-
this.header = page.locator('zeppelin-header');
2521
this.routerOutlet = page.locator('zeppelin-workspace router-outlet');
2622
}
27-
28-
async navigateToWorkspace(): Promise<void> {
29-
await this.page.goto('/');
30-
await this.waitForPageLoad();
31-
}
3223
}

zeppelin-web-angular/e2e/utils.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import { NotebookUtil } from './models/notebook.util';
1717

1818
export const NOTEBOOK_PATTERNS = {
1919
URL_REGEX: /\/notebook\/[^\/\?]+/,
20-
URL_EXTRACT_NOTEBOOK_ID_REGEX: /\/notebook\/([^\/\?]+)/
20+
URL_EXTRACT_NOTEBOOK_ID_REGEX: /\/notebook\/([^\/\?]+)/,
21+
LINK_SELECTOR: 'a[href*="/notebook/"]'
2122
} as const;
2223

2324
export const PAGES = {
@@ -159,6 +160,7 @@ export const getBasicPageMetadata = async (
159160
path: getCurrentPath(page)
160161
});
161162

163+
import { LoginPage } from './models/login-page';
162164
export const performLoginIfRequired = async (page: Page): Promise<boolean> => {
163165
const isShiroEnabled = await LoginTestUtil.isShiroEnabled();
164166
if (!isShiroEnabled) {
@@ -178,13 +180,8 @@ export const performLoginIfRequired = async (page: Page): Promise<boolean> => {
178180

179181
const isLoginVisible = await page.locator('zeppelin-login').isVisible();
180182
if (isLoginVisible) {
181-
const userNameInput = page.getByRole('textbox', { name: 'User Name' });
182-
const passwordInput = page.getByRole('textbox', { name: 'Password' });
183-
const loginButton = page.getByRole('button', { name: 'Login' });
184-
185-
await userNameInput.fill(testUser.username);
186-
await passwordInput.fill(testUser.password);
187-
await loginButton.click();
183+
const loginPage = new LoginPage(page);
184+
await loginPage.login(testUser.username, testUser.password);
188185

189186
// for webkit
190187
await page.waitForTimeout(200);
@@ -269,7 +266,7 @@ export const waitForZeppelinReady = async (page: Page): Promise<void> => {
269266
};
270267

271268
export const waitForNotebookLinks = async (page: Page, timeout: number = 30000) => {
272-
const locator = page.locator('a[href*="#/notebook/"]');
269+
const locator = page.locator(NOTEBOOK_PATTERNS.LINK_SELECTOR);
273270

274271
// If there are no notebook links on the page, there's no reason to wait
275272
const count = await locator.count();
@@ -316,7 +313,7 @@ export const navigateToNotebookWithFallback = async (
316313

317314
// The link text in the UI is the base name of the note, not the full path.
318315
const baseName = notebookName.split('/').pop();
319-
const notebookLink = page.locator(`a[href*="/notebook/"]`).filter({ hasText: baseName! });
316+
const notebookLink = page.locator(NOTEBOOK_PATTERNS.LINK_SELECTOR).filter({ hasText: baseName! });
320317
// Use the click action's built-in wait.
321318
await notebookLink.click({ timeout: 10000 });
322319

@@ -349,16 +346,16 @@ const navigateViaHomePageFallback = async (page: Page, baseNotebookName: string)
349346
await page.waitForLoadState('networkidle', { timeout: 15000 });
350347
await page.waitForSelector('zeppelin-node-list', { timeout: 15000 });
351348

352-
await page.waitForFunction(() => document.querySelectorAll('a[href*="/notebook/"]').length > 0, {
349+
await page.waitForFunction(() => document.querySelectorAll(NOTEBOOK_PATTERNS.LINK_SELECTOR).length > 0, {
353350
timeout: 15000
354351
});
355352
await page.waitForLoadState('domcontentloaded', { timeout: 15000 });
356353

357-
const notebookLink = page.locator(`a[href*="/notebook/"]`).filter({ hasText: baseNotebookName });
354+
const notebookLink = page.locator(NOTEBOOK_PATTERNS.LINK_SELECTOR).filter({ hasText: baseNotebookName });
358355

359356
const browserName = page.context().browser()?.browserType().name();
360357
if (browserName === 'firefox') {
361-
await page.waitForSelector(`a[href*="/notebook/"]:has-text("${baseNotebookName}")`, {
358+
await page.waitForSelector(`${NOTEBOOK_PATTERNS.LINK_SELECTOR}:has-text("${baseNotebookName}")`, {
362359
state: 'visible',
363360
timeout: 90000
364361
});

0 commit comments

Comments
 (0)