This is an automated email from the ASF dual-hosted git repository.
chanholee pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zeppelin.git
The following commit(s) were added to refs/heads/master by this push:
new 1662cbdaf0 [ZEPPELIN-6358] simplify utils, promote POM usage, and
consolidate base logic from #5101
1662cbdaf0 is described below
commit 1662cbdaf057d375d34cf911cb5a96a03744cd71
Author: YONGJAE LEE(이용재) <[email protected]>
AuthorDate: Sun Feb 22 20:40:14 2026 +0900
[ZEPPELIN-6358] simplify utils, promote POM usage, and consolidate base
logic from #5101
### What is this PR for?
### PR Description
This PR improves the readability and maintainability of the E2E notebook
tests.
- Removed over-abstracted util and wrapper methods
- Moved test logic from util files into the test cases
- Simplified page objects to focus on direct UI interactions
- Consolidated shared logic into a base page class
As a result, the tests are clearer, flatter, and easier to maintain.
### What type of PR is it?
Refactoring
### Todos
### What is the Jira issue?
ZEPPELIN-6358
### How should this be tested?
### Screenshots (if appropriate)
### Questions:
* Does the license files need to update? No
* Is there breaking changes for older versions? No
* Does this needs documentation? No
Closes #5131 from dididy/e2e/notebook-edited.
Signed-off-by: ChanHo Lee <[email protected]>
---
zeppelin-web-angular/e2e/models/base-page.ts | 69 +++++-
.../models/{theme.page.ts => dark-mode-page.ts} | 14 +-
zeppelin-web-angular/e2e/models/home-page.ts | 144 +++---------
zeppelin-web-angular/e2e/models/home-page.util.ts | 233 -------------------
zeppelin-web-angular/e2e/models/login-page.ts | 20 +-
.../e2e/models/notebook-repo-item.util.ts | 37 +++
.../e2e/models/notebook-repos-page.ts | 29 ++-
.../e2e/models/notebook-repos-page.util.ts | 137 -----------
zeppelin-web-angular/e2e/models/notebook.util.ts | 24 +-
.../e2e/models/published-paragraph-page.ts | 32 +--
.../e2e/models/published-paragraph-page.util.ts | 222 +-----------------
zeppelin-web-angular/e2e/models/workspace-page.ts | 9 -
.../e2e/models/workspace-page.util.ts | 38 +---
zeppelin-web-angular/e2e/tests/app.spec.ts | 14 +-
.../anonymous-login-redirect.spec.ts | 102 +++++----
.../e2e/tests/home/home-page-elements.spec.ts | 63 +++---
.../home/home-page-enhanced-functionality.spec.ts | 60 +++--
.../tests/home/home-page-external-links.spec.ts | 48 ++--
.../e2e/tests/home/home-page-layout.spec.ts | 14 +-
.../tests/home/home-page-note-operations.spec.ts | 43 ++--
.../tests/home/home-page-notebook-actions.spec.ts | 67 ++++--
.../notebook/published/published-paragraph.spec.ts | 251 ++++++++++++++++-----
.../e2e/tests/theme/dark-mode.spec.ts | 86 +++----
.../notebook-repo-item-display.spec.ts | 2 +-
.../notebook-repos/notebook-repo-item-edit.spec.ts | 15 +-
.../notebook-repo-item-form-validation.spec.ts | 42 +---
.../notebook-repo-item-settings.spec.ts | 26 +--
.../notebook-repo-item-workflow.spec.ts | 33 +--
.../notebook-repos-page-structure.spec.ts | 13 +-
.../e2e/tests/workspace/workspace-main.spec.ts | 37 ++-
zeppelin-web-angular/e2e/utils.ts | 2 +-
31 files changed, 707 insertions(+), 1219 deletions(-)
diff --git a/zeppelin-web-angular/e2e/models/base-page.ts
b/zeppelin-web-angular/e2e/models/base-page.ts
index c3d9004fde..539f096cbb 100644
--- a/zeppelin-web-angular/e2e/models/base-page.ts
+++ b/zeppelin-web-angular/e2e/models/base-page.ts
@@ -10,7 +10,7 @@
* limitations under the License.
*/
-import { Locator, Page } from '@playwright/test';
+import { expect, Locator, Page } from '@playwright/test';
export const E2E_TEST_FOLDER = 'E2E_TEST_FOLDER';
export const BASE_URL = 'http://localhost:4200';
@@ -23,12 +23,32 @@ export class BasePage {
readonly zeppelinPageHeader: Locator;
readonly zeppelinHeader: Locator;
+ readonly modalTitle: Locator;
+ readonly modalBody: Locator;
+ readonly modalContent: Locator;
+
+ readonly okButton: Locator;
+ readonly cancelButton: Locator;
+ readonly runButton: Locator;
+
+ readonly welcomeTitle: Locator;
+
constructor(page: Page) {
this.page = page;
this.zeppelinNodeList = page.locator('zeppelin-node-list');
this.zeppelinWorkspace = page.locator('zeppelin-workspace');
this.zeppelinPageHeader = page.locator('zeppelin-page-header');
this.zeppelinHeader = page.locator('zeppelin-header');
+
+ this.modalTitle = page.locator('.ant-modal-confirm-title,
.ant-modal-title');
+ this.modalBody = page.locator('.ant-modal-confirm-content,
.ant-modal-body');
+ this.modalContent = page.locator('.ant-modal-body');
+
+ this.okButton = page.locator('button:has-text("OK")');
+ this.cancelButton = page.locator('button:has-text("Cancel")');
+ this.runButton = page.locator('button:has-text("Run")');
+
+ this.welcomeTitle = page.getByRole('heading', { name: 'Welcome to
Zeppelin!' });
}
async waitForPageLoad(): Promise<void> {
@@ -63,4 +83,51 @@ export class BasePage {
async getElementText(locator: Locator): Promise<string> {
return (await locator.textContent()) || '';
}
+
+ async waitForFormLabels(labelTexts: string[], timeout = 10000):
Promise<void> {
+ await this.page.waitForFunction(
+ texts => {
+ const labels = Array.from(document.querySelectorAll('nz-form-label'));
+ return texts.some(text => labels.some(l =>
l.textContent?.includes(text)));
+ },
+ labelTexts,
+ { timeout }
+ );
+ }
+
+ async waitForElementAttribute(
+ selector: string,
+ attribute: string,
+ exists: boolean = true,
+ timeout = 10000
+ ): Promise<void> {
+ const locator = this.page.locator(selector);
+ if (exists) {
+ await expect(locator).toHaveAttribute(attribute, { timeout });
+ } else {
+ await expect(locator).not.toHaveAttribute(attribute, { timeout });
+ }
+ }
+
+ async waitForRouterOutletChild(timeout = 10000): Promise<void> {
+ await expect(this.page.locator('zeppelin-workspace router-outlet +
*')).toHaveCount(1, { timeout });
+ }
+
+ async fillAndVerifyInput(
+ locator: Locator,
+ value: string,
+ options?: { timeout?: number; clearFirst?: boolean }
+ ): Promise<void> {
+ const { timeout = 10000, clearFirst = true } = options || {};
+
+ await expect(locator).toBeVisible({ timeout });
+ await expect(locator).toBeEnabled({ timeout: 5000 });
+
+ if (clearFirst) {
+ await locator.clear();
+ }
+
+ await locator.fill(value);
+ await expect(locator).toHaveValue(value);
+ }
}
diff --git a/zeppelin-web-angular/e2e/models/theme.page.ts
b/zeppelin-web-angular/e2e/models/dark-mode-page.ts
similarity index 81%
rename from zeppelin-web-angular/e2e/models/theme.page.ts
rename to zeppelin-web-angular/e2e/models/dark-mode-page.ts
index 5285ac4590..98f77c8933 100644
--- a/zeppelin-web-angular/e2e/models/theme.page.ts
+++ b/zeppelin-web-angular/e2e/models/dark-mode-page.ts
@@ -11,36 +11,36 @@
*/
import { expect, Locator, Page } from '@playwright/test';
+import { BasePage } from './base-page';
-export class ThemePage {
- readonly page: Page;
+export class DarkModePage extends BasePage {
readonly themeToggleButton: Locator;
readonly rootElement: Locator;
constructor(page: Page) {
- this.page = page;
+ super(page);
this.themeToggleButton = page.locator('zeppelin-theme-toggle button');
this.rootElement = page.locator('html');
}
async toggleTheme() {
- await this.themeToggleButton.click();
+ await this.themeToggleButton.click({ timeout: 15000 });
}
async assertDarkTheme() {
- await expect(this.rootElement).toHaveClass(/dark/);
+ await expect(this.rootElement).toHaveClass(/dark/, { timeout: 10000 });
await expect(this.rootElement).toHaveAttribute('data-theme', 'dark');
await expect(this.themeToggleButton).toHaveText('dark_mode');
}
async assertLightTheme() {
- await expect(this.rootElement).toHaveClass(/light/);
+ await expect(this.rootElement).toHaveClass(/light/, { timeout: 10000 });
await expect(this.rootElement).toHaveAttribute('data-theme', 'light');
await expect(this.themeToggleButton).toHaveText('light_mode');
}
async assertSystemTheme() {
- await expect(this.themeToggleButton).toHaveText('smart_toy');
+ await expect(this.themeToggleButton).toHaveText('smart_toy', { timeout:
60000 });
}
async setThemeInLocalStorage(theme: 'light' | 'dark' | 'system') {
diff --git a/zeppelin-web-angular/e2e/models/home-page.ts
b/zeppelin-web-angular/e2e/models/home-page.ts
index 872784dfa0..52c39df8b3 100644
--- a/zeppelin-web-angular/e2e/models/home-page.ts
+++ b/zeppelin-web-angular/e2e/models/home-page.ts
@@ -11,18 +11,12 @@
*/
import { expect, Locator, Page } from '@playwright/test';
-import { getCurrentPath, waitForUrlNotContaining } from '../utils';
import { BasePage } from './base-page';
export class HomePage extends BasePage {
- readonly welcomeHeading: Locator;
readonly notebookSection: Locator;
readonly helpSection: Locator;
readonly communitySection: Locator;
- readonly createNewNoteButton: Locator;
- readonly importNoteButton: Locator;
- readonly searchInput: Locator;
- readonly filterInput: Locator;
readonly zeppelinLogo: Locator;
readonly anonymousUserIndicator: Locator;
readonly welcomeSection: Locator;
@@ -31,11 +25,12 @@ export class HomePage extends BasePage {
readonly helpCommunityColumn: Locator;
readonly welcomeDescription: Locator;
readonly refreshNoteButton: Locator;
- readonly refreshIcon: Locator;
- readonly notebookList: Locator;
readonly notebookHeading: Locator;
readonly helpHeading: Locator;
readonly communityHeading: Locator;
+ readonly createNoteModal: Locator;
+ readonly createNoteButton: Locator;
+ readonly notebookNameInput: Locator;
readonly externalLinks: {
documentation: Locator;
mailingList: Locator;
@@ -52,27 +47,13 @@ export class HomePage extends BasePage {
clearOutput: Locator;
moveToTrash: Locator;
};
- folderActions: {
- createNote: Locator;
- renameFolder: Locator;
- moveToTrash: Locator;
- };
- trashActions: {
- restoreAll: Locator;
- emptyAll: Locator;
- };
};
constructor(page: Page) {
super(page);
- this.welcomeHeading = page.locator('h1', { hasText: 'Welcome to Zeppelin!'
});
this.notebookSection = page.locator('text=Notebook').first();
this.helpSection = page.locator('text=Help').first();
this.communitySection = page.locator('text=Community').first();
- this.createNewNoteButton = page.locator('text=Create new Note');
- this.importNoteButton = page.locator('text=Import Note');
- this.searchInput = page.locator('textbox', { hasText: 'Search' });
- this.filterInput = page.locator('input[placeholder*="Filter"]');
this.zeppelinLogo = page.locator('text=Zeppelin').first();
this.anonymousUserIndicator = page.locator('text=anonymous');
this.welcomeSection = page.locator('.welcome');
@@ -81,11 +62,12 @@ export class HomePage extends BasePage {
this.helpCommunityColumn = page.locator('[nz-col]').last();
this.welcomeDescription = page.locator('.welcome').getByText('Zeppelin is
web-based notebook');
this.refreshNoteButton = page.locator('a.refresh-note');
- this.refreshIcon = page.locator('a.refresh-note i[nz-icon]');
- this.notebookList = page.locator('zeppelin-node-list');
this.notebookHeading = this.notebookColumn.locator('h3');
this.helpHeading = page.locator('h3').filter({ hasText: 'Help' });
this.communityHeading = page.locator('h3').filter({ hasText: 'Community'
});
+ this.createNoteModal = page.locator('div.ant-modal-content');
+ this.createNoteButton = this.createNoteModal.locator('button', { hasText:
'Create' });
+ this.notebookNameInput =
this.createNoteModal.locator('input[name="noteName"]');
this.externalLinks = {
documentation: page.locator('a[href*="zeppelin.apache.org/docs"]'),
@@ -103,67 +85,30 @@ export class HomePage extends BasePage {
renameNote: page.locator('.file .operation a[nztooltiptitle*="Rename
note"]'),
clearOutput: page.locator('.file .operation a[nztooltiptitle*="Clear
output"]'),
moveToTrash: page.locator('.file .operation a[nztooltiptitle*="Move
note to Trash"]')
- },
- folderActions: {
- createNote: page.locator('.folder .operation a[nztooltiptitle*="Create
new note"]'),
- renameFolder: page.locator('.folder .operation
a[nztooltiptitle*="Rename folder"]'),
- moveToTrash: page.locator('.folder .operation a[nztooltiptitle*="Move
folder to Trash"]')
- },
- trashActions: {
- restoreAll: page.locator('.folder .operation
a[nztooltiptitle*="Restore all"]'),
- emptyAll: page.locator('.folder .operation a[nztooltiptitle*="Empty
all"]')
}
};
}
- async navigateToHome(): Promise<void> {
- await this.page.goto('/', { waitUntil: 'load' });
- await this.waitForPageLoad();
- }
-
async navigateToLogin(): Promise<void> {
- await this.page.goto('/#/login', { waitUntil: 'load' });
- await this.waitForPageLoad();
+ await this.navigateToRoute('/login');
// Wait for potential redirect to complete by checking URL change
- await waitForUrlNotContaining(this.page, '#/login');
+ await this.waitForUrlNotContaining('#/login');
}
async isHomeContentDisplayed(): Promise<boolean> {
- try {
- await expect(this.welcomeHeading).toBeVisible();
- return true;
- } catch {
- return false;
- }
+ return this.welcomeTitle.isVisible();
}
async isAnonymousUser(): Promise<boolean> {
- try {
- await expect(this.anonymousUserIndicator).toBeVisible();
- return true;
- } catch {
- return false;
- }
+ return this.anonymousUserIndicator.isVisible();
}
async clickZeppelinLogo(): Promise<void> {
- await this.zeppelinLogo.click();
- }
-
- async getCurrentURL(): Promise<string> {
- return this.page.url();
- }
-
- getCurrentPath(): string {
- return getCurrentPath(this.page);
- }
-
- async getPageTitle(): Promise<string> {
- return this.page.title();
+ await this.zeppelinLogo.click({ timeout: 15000 });
}
async getWelcomeHeadingText(): Promise<string> {
- const text = await this.welcomeHeading.textContent();
+ const text = await this.welcomeTitle.textContent();
return text || '';
}
@@ -173,65 +118,48 @@ export class HomePage extends BasePage {
}
async clickRefreshNotes(): Promise<void> {
- await this.refreshNoteButton.click();
+ await this.refreshNoteButton.click({ timeout: 15000 });
}
async isNotebookListVisible(): Promise<boolean> {
- return this.notebookList.isVisible();
+ return this.zeppelinNodeList.isVisible();
}
async clickCreateNewNote(): Promise<void> {
- await this.nodeList.createNewNoteLink.click();
+ await this.nodeList.createNewNoteLink.click({ timeout: 15000 });
+ await this.createNoteModal.waitFor({ state: 'visible' });
}
- async clickImportNote(): Promise<void> {
- await this.nodeList.importNoteLink.click();
- }
+ async createNote(notebookName: string): Promise<void> {
+ await this.clickCreateNewNote();
- async filterNotes(searchTerm: string): Promise<void> {
- await this.nodeList.filterInput.fill(searchTerm);
- }
+ // Wait for the modal form to be fully rendered with proper labels
+ await this.page.waitForSelector('nz-form-label', { timeout: 10000 });
- async isRefreshIconSpinning(): Promise<boolean> {
- const spinAttribute = await this.refreshIcon.getAttribute('nzSpin');
- return spinAttribute === 'true' || spinAttribute === '';
- }
+ await this.waitForFormLabels(['Note Name', 'Clone Note']);
- async waitForRefreshToComplete(): Promise<void> {
- await this.page.waitForFunction(
- () => {
- const icon = document.querySelector('a.refresh-note i[nz-icon]');
- return icon && !icon.hasAttribute('nzSpin');
- },
- { timeout: 10000 }
- );
+ // Fill and verify the notebook name input
+ await this.fillAndVerifyInput(this.notebookNameInput, notebookName);
+
+ // Click the 'Create' button in the modal
+ await expect(this.createNoteButton).toBeEnabled({ timeout: 5000 });
+ await this.createNoteButton.click({ timeout: 15000 });
+ await this.waitForPageLoad();
}
- async getDocumentationLinkHref(): Promise<string | null> {
- return this.externalLinks.documentation.getAttribute('href');
+ async clickImportNote(): Promise<void> {
+ await this.nodeList.importNoteLink.click({ timeout: 15000 });
}
- async areExternalLinksVisible(): Promise<boolean> {
- const links = [
- this.externalLinks.documentation,
- this.externalLinks.mailingList,
- this.externalLinks.issuesTracking,
- this.externalLinks.github
- ];
-
- for (const link of links) {
- if (!(await link.isVisible())) {
- return false;
- }
- }
- return true;
+ async filterNotes(searchTerm: string): Promise<void> {
+ await this.nodeList.filterInput.fill(searchTerm, { timeout: 15000 });
}
- async isWelcomeSectionVisible(): Promise<boolean> {
- return this.welcomeSection.isVisible();
+ async waitForRefreshToComplete(): Promise<void> {
+ await this.waitForElementAttribute('a.refresh-note i[nz-icon]', 'nzSpin',
false);
}
- async isMoreInfoGridVisible(): Promise<boolean> {
- return this.moreInfoGrid.isVisible();
+ async getDocumentationLinkHref(): Promise<string | null> {
+ return this.externalLinks.documentation.getAttribute('href');
}
}
diff --git a/zeppelin-web-angular/e2e/models/home-page.util.ts
b/zeppelin-web-angular/e2e/models/home-page.util.ts
deleted file mode 100644
index 5a5a6ff210..0000000000
--- a/zeppelin-web-angular/e2e/models/home-page.util.ts
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { expect, Page } from '@playwright/test';
-import { getBasicPageMetadata } from '../utils';
-import { HomePage } from './home-page';
-
-export class HomePageUtil {
- private homePage: HomePage;
- private page: Page;
-
- constructor(page: Page) {
- this.page = page;
- this.homePage = new HomePage(page);
- }
-
- async verifyAnonymousUserRedirectFromLogin(): Promise<{
- isLoginUrlMaintained: boolean;
- isHomeContentDisplayed: boolean;
- isAnonymousUser: boolean;
- currentPath: string;
- }> {
- await this.homePage.navigateToLogin();
-
- const currentPath = this.homePage.getCurrentPath();
- const isLoginUrlMaintained = currentPath.includes('#/login');
- const isHomeContentDisplayed = await
this.homePage.isHomeContentDisplayed();
- const isAnonymousUser = await this.homePage.isAnonymousUser();
-
- return {
- isLoginUrlMaintained,
- isHomeContentDisplayed,
- isAnonymousUser,
- currentPath
- };
- }
-
- async verifyHomePageElements(): Promise<void> {
- await expect(this.homePage.welcomeHeading).toBeVisible();
- await expect(this.homePage.notebookSection).toBeVisible();
- await expect(this.homePage.helpSection).toBeVisible();
- await expect(this.homePage.communitySection).toBeVisible();
- }
-
- async verifyExternalLinks(): Promise<void> {
- await expect(this.homePage.externalLinks.documentation).toBeVisible();
- await expect(this.homePage.externalLinks.mailingList).toBeVisible();
- await expect(this.homePage.externalLinks.issuesTracking).toBeVisible();
- await expect(this.homePage.externalLinks.github).toBeVisible();
- }
-
- async testNavigationConsistency(): Promise<{
- pathBeforeClick: string;
- pathAfterClick: string;
- homeContentMaintained: boolean;
- }> {
- const pathBeforeClick = this.homePage.getCurrentPath();
-
- await this.homePage.clickZeppelinLogo();
- await this.homePage.waitForPageLoad();
-
- const pathAfterClick = this.homePage.getCurrentPath();
- const homeContentMaintained = await this.homePage.isHomeContentDisplayed();
-
- return {
- pathBeforeClick,
- pathAfterClick,
- homeContentMaintained
- };
- }
-
- async getHomePageMetadata(): Promise<{
- title: string;
- path: string;
- isAnonymous: boolean;
- }> {
- const basicMetadata = await getBasicPageMetadata(this.page);
- const isAnonymous = await this.homePage.isAnonymousUser();
-
- return {
- ...basicMetadata,
- isAnonymous
- };
- }
-
- async verifyWelcomeSection(): Promise<void> {
- await expect(this.homePage.welcomeSection).toBeVisible();
- await expect(this.homePage.welcomeHeading).toBeVisible();
-
- const headingText = await this.homePage.getWelcomeHeadingText();
- expect(headingText.trim()).toBe('Welcome to Zeppelin!');
-
- const welcomeText = await this.homePage.welcomeDescription.textContent();
- expect(welcomeText).toContain('web-based notebook');
- expect(welcomeText).toContain('interactive data analytics');
- }
-
- async verifyNotebookSection(): Promise<void> {
- await expect(this.homePage.notebookSection).toBeVisible();
- await expect(this.homePage.notebookHeading).toBeVisible();
- await expect(this.homePage.refreshNoteButton).toBeVisible();
-
- // Wait for notebook list to load with timeout
- await this.page.waitForSelector('zeppelin-node-list', { timeout: 10000 });
- await expect(this.homePage.notebookList).toBeVisible();
-
- // Additional wait for content to load
- await this.page.waitForTimeout(1000);
- }
-
- async verifyNotebookRefreshFunctionality(): Promise<void> {
- await this.homePage.clickRefreshNotes();
-
- // Wait for refresh operation to complete
- await this.page.waitForTimeout(2000);
-
- // Ensure the notebook list is still visible after refresh
- await expect(this.homePage.notebookList).toBeVisible();
- const isStillVisible = await this.homePage.isNotebookListVisible();
- expect(isStillVisible).toBe(true);
- }
-
- async verifyHelpSection(): Promise<void> {
- await expect(this.homePage.helpSection).toBeVisible();
- await expect(this.homePage.helpHeading).toBeVisible();
- }
-
- async verifyCommunitySection(): Promise<void> {
- await expect(this.homePage.communitySection).toBeVisible();
- await expect(this.homePage.communityHeading).toBeVisible();
- }
-
- async testExternalLinkTargets(): Promise<{
- documentationHref: string | null;
- mailingListHref: string | null;
- issuesTrackingHref: string | null;
- githubHref: string | null;
- }> {
- // Get the parent links that contain the text
- const docLink = this.page.locator('a').filter({ hasText: 'Zeppelin
documentation' });
- const mailLink = this.page.locator('a').filter({ hasText: 'Mailing list'
});
- const issuesLink = this.page.locator('a').filter({ hasText: 'Issues
tracking' });
- const githubLink = this.page.locator('a').filter({ hasText: 'Github' });
-
- return {
- documentationHref: await docLink.getAttribute('href'),
- mailingListHref: await mailLink.getAttribute('href'),
- issuesTrackingHref: await issuesLink.getAttribute('href'),
- githubHref: await githubLink.getAttribute('href')
- };
- }
-
- async verifyNotebookActions(): Promise<void> {
- await expect(this.homePage.nodeList.createNewNoteLink).toBeVisible();
- await expect(this.homePage.nodeList.importNoteLink).toBeVisible();
- await expect(this.homePage.nodeList.filterInput).toBeVisible();
- await expect(this.homePage.nodeList.tree).toBeVisible();
- }
-
- async testNotebookRefreshLoadingState(): Promise<void> {
- const refreshButton = this.page.locator('a.refresh-note');
- const refreshIcon = this.page.locator('a.refresh-note i[nz-icon]');
-
- await expect(refreshButton).toBeVisible();
- await expect(refreshIcon).toBeVisible();
-
- await this.homePage.clickRefreshNotes();
-
- await this.page.waitForTimeout(500);
-
- await expect(refreshIcon).toBeVisible();
- }
-
- async verifyCreateNewNoteWorkflow(): Promise<void> {
- await this.homePage.clickCreateNewNote();
-
- await this.page.waitForFunction(
- () => {
- return document.querySelector('zeppelin-note-create') !== null;
- },
- { timeout: 10000 }
- );
- }
-
- async verifyImportNoteWorkflow(): Promise<void> {
- await this.homePage.clickImportNote();
-
- await this.page.waitForFunction(
- () => {
- return document.querySelector('zeppelin-note-import') !== null;
- },
- { timeout: 10000 }
- );
- }
-
- async testFilterFunctionality(filterTerm: string): Promise<void> {
- await this.homePage.filterNotes(filterTerm);
-
- await this.page.waitForTimeout(1000);
-
- const filteredResults = await this.page.locator('nz-tree .node').count();
- expect(filteredResults).toBeGreaterThanOrEqual(0);
- }
-
- async verifyDocumentationVersionLink(): Promise<void> {
- const href = await this.homePage.getDocumentationLinkHref();
- expect(href).toContain('zeppelin.apache.org/docs');
- expect(href).toMatch(/\/docs\/\d+\.\d+\.\d+(-SNAPSHOT)?\//);
- }
-
- async verifyAllExternalLinksTargetBlank(): Promise<void> {
- const links = [
- this.homePage.externalLinks.documentation,
- this.homePage.externalLinks.mailingList,
- this.homePage.externalLinks.issuesTracking,
- this.homePage.externalLinks.github
- ];
-
- for (const link of links) {
- const target = await link.getAttribute('target');
- expect(target).toBe('_blank');
- }
- }
-}
diff --git a/zeppelin-web-angular/e2e/models/login-page.ts
b/zeppelin-web-angular/e2e/models/login-page.ts
index cf9e003d77..7e89786883 100644
--- a/zeppelin-web-angular/e2e/models/login-page.ts
+++ b/zeppelin-web-angular/e2e/models/login-page.ts
@@ -17,37 +17,33 @@ export class LoginPage extends BasePage {
readonly userNameInput: Locator;
readonly passwordInput: Locator;
readonly loginButton: Locator;
- readonly welcomeTitle: Locator;
readonly formContainer: Locator;
+ readonly errorMessage: Locator;
constructor(page: Page) {
super(page);
this.userNameInput = page.getByRole('textbox', { name: 'User Name' });
this.passwordInput = page.getByRole('textbox', { name: 'Password' });
this.loginButton = page.getByRole('button', { name: 'Login' });
- this.welcomeTitle = page.getByRole('heading', { name: 'Welcome to
Zeppelin!' });
this.formContainer = page.locator('form[nz-form]');
+ this.errorMessage = page.locator("text=The username and password that you
entered don't match.").first();
}
async navigate(): Promise<void> {
- await this.page.goto('/#/login');
- await this.waitForPageLoad();
+ await this.navigateToRoute('/login');
}
async login(username: string, password: string): Promise<void> {
- await this.userNameInput.fill(username);
- await this.passwordInput.fill(password);
- await this.loginButton.click();
+ await this.userNameInput.fill(username, { timeout: 15000 });
+ await this.passwordInput.fill(password, { timeout: 15000 });
+ await this.loginButton.click({ timeout: 15000 });
}
async waitForErrorMessage(): Promise<void> {
- await this.page.waitForSelector("text=The username and password that you
entered don't match.", { timeout: 5000 });
+ await this.errorMessage.waitFor({ state: 'visible', timeout: 5000 });
}
async getErrorMessageText(): Promise<string> {
- return (
- (await this.page.locator("text=The username and password that you
entered don't match.").first().textContent()) ||
- ''
- );
+ return this.getElementText(this.errorMessage);
}
}
diff --git a/zeppelin-web-angular/e2e/models/notebook-repo-item.util.ts
b/zeppelin-web-angular/e2e/models/notebook-repo-item.util.ts
new file mode 100644
index 0000000000..06cdab7ed2
--- /dev/null
+++ b/zeppelin-web-angular/e2e/models/notebook-repo-item.util.ts
@@ -0,0 +1,37 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { expect, Page } from '@playwright/test';
+import { NotebookRepoItemPage } from './notebook-repos-page';
+import { BasePage } from './base-page';
+
+export class NotebookRepoItemUtil extends BasePage {
+ private repoItemPage: NotebookRepoItemPage;
+
+ constructor(page: Page, repoName: string) {
+ super(page);
+ this.repoItemPage = new NotebookRepoItemPage(page, repoName);
+ }
+
+ async verifyDisplayMode(): Promise<void> {
+ await expect(this.repoItemPage.editButton).toBeVisible();
+ const isEditMode = await this.repoItemPage.isEditMode();
+ expect(isEditMode).toBe(false);
+ }
+
+ async verifyEditMode(): Promise<void> {
+ await expect(this.repoItemPage.saveButton).toBeVisible();
+ await expect(this.repoItemPage.cancelButton).toBeVisible();
+ const isEditMode = await this.repoItemPage.isEditMode();
+ expect(isEditMode).toBe(true);
+ }
+}
diff --git a/zeppelin-web-angular/e2e/models/notebook-repos-page.ts
b/zeppelin-web-angular/e2e/models/notebook-repos-page.ts
index 66befc4d2b..3272df477d 100644
--- a/zeppelin-web-angular/e2e/models/notebook-repos-page.ts
+++ b/zeppelin-web-angular/e2e/models/notebook-repos-page.ts
@@ -15,26 +15,24 @@ import { waitForZeppelinReady } from '../utils';
import { BasePage } from './base-page';
export class NotebookReposPage extends BasePage {
- readonly pageHeader: Locator;
readonly pageDescription: Locator;
readonly repositoryItems: Locator;
constructor(page: Page) {
super(page);
- this.pageHeader = page.locator('zeppelin-page-header[title="Notebook
Repository"]');
this.pageDescription = page.locator("text=Manage your Notebook
Repositories' settings.");
this.repositoryItems = page.locator('zeppelin-notebook-repo-item');
}
async navigate(): Promise<void> {
- await this.page.goto('/#/notebook-repos', { waitUntil: 'load' });
- await this.page.waitForURL('**/#/notebook-repos', { timeout: 15000 });
+ await this.navigateToRoute('/notebook-repos', { timeout: 60000 });
+ await this.page.waitForURL('**/#/notebook-repos', { timeout: 60000 });
await waitForZeppelinReady(this.page);
await this.page.waitForLoadState('networkidle', { timeout: 15000 });
- await this.page.waitForSelector('zeppelin-notebook-repo-item,
zeppelin-page-header[title="Notebook Repository"]', {
- state: 'visible',
- timeout: 20000
- });
+ await Promise.race([
+ this.zeppelinPageHeader.filter({ hasText: 'Notebook Repository'
}).waitFor({ state: 'visible' }),
+ this.page.waitForSelector('zeppelin-notebook-repo-item', { state:
'visible' })
+ ]);
}
async getRepositoryItemCount(): Promise<number> {
@@ -42,8 +40,7 @@ export class NotebookReposPage extends BasePage {
}
}
-export class NotebookRepoItemPage {
- readonly page: Page;
+export class NotebookRepoItemPage extends BasePage {
readonly repositoryCard: Locator;
readonly repositoryName: Locator;
readonly editButton: Locator;
@@ -53,7 +50,7 @@ export class NotebookRepoItemPage {
readonly settingRows: Locator;
constructor(page: Page, repoName: string) {
- this.page = page;
+ super(page);
this.repositoryCard = page.locator('nz-card').filter({ hasText: repoName
});
this.repositoryName = this.repositoryCard.locator('.ant-card-head-title');
this.editButton = this.repositoryCard.locator('button:has-text("Edit")');
@@ -64,15 +61,15 @@ export class NotebookRepoItemPage {
}
async clickEdit(): Promise<void> {
- await this.editButton.click();
+ await this.editButton.click({ timeout: 15000 });
}
async clickSave(): Promise<void> {
- await this.saveButton.click();
+ await this.saveButton.click({ timeout: 15000 });
}
async clickCancel(): Promise<void> {
- await this.cancelButton.click();
+ await this.cancelButton.click({ timeout: 15000 });
}
async isEditMode(): Promise<boolean> {
@@ -99,8 +96,8 @@ export class NotebookRepoItemPage {
async selectSettingDropdown(settingName: string, optionValue: string):
Promise<void> {
const row = this.repositoryCard.locator('tbody tr').filter({ hasText:
settingName });
const select = row.locator('nz-select');
- await select.click();
- await this.page.locator(`nz-option[nzvalue="${optionValue}"]`).click();
+ await select.click({ timeout: 15000 });
+ await this.page.locator(`nz-option[nzvalue="${optionValue}"]`).click({
timeout: 15000 });
}
async getSettingInputValue(settingName: string): Promise<string> {
diff --git a/zeppelin-web-angular/e2e/models/notebook-repos-page.util.ts
b/zeppelin-web-angular/e2e/models/notebook-repos-page.util.ts
deleted file mode 100644
index d2b0b1f204..0000000000
--- a/zeppelin-web-angular/e2e/models/notebook-repos-page.util.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { expect, Page } from '@playwright/test';
-import { NotebookReposPage, NotebookRepoItemPage } from
'./notebook-repos-page';
-
-export class NotebookReposPageUtil {
- private notebookReposPage: NotebookReposPage;
- private page: Page;
-
- constructor(page: Page) {
- this.page = page;
- this.notebookReposPage = new NotebookReposPage(page);
- }
-
- async verifyPageStructure(): Promise<void> {
- await expect(this.notebookReposPage.pageHeader).toBeVisible();
- await expect(this.notebookReposPage.pageDescription).toBeVisible();
- }
-
- async verifyRepositoryListDisplayed(): Promise<void> {
- const count = await this.notebookReposPage.getRepositoryItemCount();
- expect(count).toBeGreaterThan(0);
- }
-
- async verifyAllRepositoriesRendered(): Promise<number> {
- const count = await this.notebookReposPage.getRepositoryItemCount();
- expect(count).toBeGreaterThan(0);
- return count;
- }
-
- async getRepositoryItem(repoName: string): Promise<NotebookRepoItemPage> {
- return new NotebookRepoItemPage(this.page, repoName);
- }
-
- async verifyRepositoryCardDisplayed(repoName: string): Promise<void> {
- const repoItem = await this.getRepositoryItem(repoName);
- await expect(repoItem.repositoryCard).toBeVisible();
- await expect(repoItem.repositoryName).toContainText(repoName);
- }
-}
-
-export class NotebookRepoItemUtil {
- private repoItemPage: NotebookRepoItemPage;
-
- constructor(page: Page, repoName: string) {
- this.repoItemPage = new NotebookRepoItemPage(page, repoName);
- }
-
- async verifyDisplayMode(): Promise<void> {
- await expect(this.repoItemPage.editButton).toBeVisible();
- const isEditMode = await this.repoItemPage.isEditMode();
- expect(isEditMode).toBe(false);
- }
-
- async verifyEditMode(): Promise<void> {
- await expect(this.repoItemPage.saveButton).toBeVisible();
- await expect(this.repoItemPage.cancelButton).toBeVisible();
- const isEditMode = await this.repoItemPage.isEditMode();
- expect(isEditMode).toBe(true);
- }
-
- async enterEditMode(): Promise<void> {
- await this.repoItemPage.clickEdit();
- await this.verifyEditMode();
- }
-
- async exitEditModeByCancel(): Promise<void> {
- await this.repoItemPage.clickCancel();
- await this.verifyDisplayMode();
- }
-
- async exitEditModeBySave(): Promise<void> {
- await this.repoItemPage.clickSave();
- await this.verifyDisplayMode();
- }
-
- async verifySettingsDisplayed(): Promise<void> {
- const settingCount = await this.repoItemPage.getSettingCount();
- expect(settingCount).toBeGreaterThan(0);
- }
-
- async verifyInputTypeSettingInEditMode(settingName: string): Promise<void> {
- const isVisible = await this.repoItemPage.isInputVisible(settingName);
- expect(isVisible).toBe(true);
- }
-
- async verifyDropdownTypeSettingInEditMode(settingName: string):
Promise<void> {
- const isVisible = await this.repoItemPage.isDropdownVisible(settingName);
- expect(isVisible).toBe(true);
- }
-
- async updateInputSetting(settingName: string, value: string): Promise<void> {
- await this.repoItemPage.fillSettingInput(settingName, value);
- const inputValue = await
this.repoItemPage.getSettingInputValue(settingName);
- expect(inputValue).toBe(value);
- }
-
- async updateDropdownSetting(settingName: string, optionValue: string):
Promise<void> {
- await this.repoItemPage.selectSettingDropdown(settingName, optionValue);
- }
-
- async verifySaveButtonDisabled(): Promise<void> {
- const isEnabled = await this.repoItemPage.isSaveButtonEnabled();
- expect(isEnabled).toBe(false);
- }
-
- async verifySaveButtonEnabled(): Promise<void> {
- const isEnabled = await this.repoItemPage.isSaveButtonEnabled();
- expect(isEnabled).toBe(true);
- }
-
- async verifyFormReset(settingName: string, originalValue: string):
Promise<void> {
- const currentValue = await this.repoItemPage.getSettingValue(settingName);
- expect(currentValue.trim()).toBe(originalValue.trim());
- }
-
- async performCompleteEditWorkflow(settingName: string, newValue: string,
isInput: boolean = true): Promise<void> {
- await this.enterEditMode();
- if (isInput) {
- await this.updateInputSetting(settingName, newValue);
- } else {
- await this.updateDropdownSetting(settingName, newValue);
- }
- await this.verifySaveButtonEnabled();
- await this.exitEditModeBySave();
- }
-}
diff --git a/zeppelin-web-angular/e2e/models/notebook.util.ts
b/zeppelin-web-angular/e2e/models/notebook.util.ts
index 5495a1dfef..00e8dbc183 100644
--- a/zeppelin-web-angular/e2e/models/notebook.util.ts
+++ b/zeppelin-web-angular/e2e/models/notebook.util.ts
@@ -11,6 +11,7 @@
*/
import { expect, Page } from '@playwright/test';
+import { performLoginIfRequired, waitForZeppelinReady } from '../utils';
import { BasePage } from './base-page';
import { HomePage } from './home-page';
@@ -24,21 +25,20 @@ export class NotebookUtil extends BasePage {
async createNotebook(notebookName: string): Promise<void> {
await this.homePage.navigateToHome();
- await this.homePage.createNewNoteButton.click();
- // Wait for the modal to appear and fill the notebook name
- const notebookNameInput = this.page.locator('input[name="noteName"]');
- await expect(notebookNameInput).toBeVisible({ timeout: 10000 });
+ // Perform login if required
+ await performLoginIfRequired(this.page);
- // Fill notebook name
- await notebookNameInput.fill(notebookName);
+ // Wait for Zeppelin to be fully ready
+ await waitForZeppelinReady(this.page);
- // Click the 'Create' button in the modal
- const createButton = this.page.locator('button', { hasText: 'Create' });
- await createButton.click();
+ // Wait for URL to not contain 'login' and for the notebook list to appear
+ await this.page.waitForFunction(
+ () => !window.location.href.includes('#/login') &&
document.querySelector('zeppelin-node-list') !== null,
+ { timeout: 30000 }
+ );
- // Wait for the notebook to be created and navigate to it
- await this.page.waitForURL(url => url.toString().includes('/notebook/'), {
timeout: 30000 });
- await this.waitForPageLoad();
+ await expect(this.homePage.zeppelinNodeList).toBeVisible({ timeout: 90000
});
+ await this.homePage.createNote(notebookName);
}
}
diff --git a/zeppelin-web-angular/e2e/models/published-paragraph-page.ts
b/zeppelin-web-angular/e2e/models/published-paragraph-page.ts
index 73f37b1798..0bc6997cfd 100644
--- a/zeppelin-web-angular/e2e/models/published-paragraph-page.ts
+++ b/zeppelin-web-angular/e2e/models/published-paragraph-page.ts
@@ -11,58 +11,36 @@
*/
import { Locator, Page } from '@playwright/test';
+import { navigateToNotebookWithFallback } from '../utils';
import { BasePage } from './base-page';
export class PublishedParagraphPage extends BasePage {
- readonly publishedParagraphContainer: Locator;
- readonly dynamicForms: Locator;
readonly paragraphResult: Locator;
- readonly errorModal: Locator;
- readonly errorModalTitle: Locator;
readonly errorModalContent: Locator;
readonly errorModalOkButton: Locator;
readonly confirmationModal: Locator;
- readonly modalTitle: Locator;
- readonly runButton: Locator;
constructor(page: Page) {
super(page);
- this.publishedParagraphContainer =
page.locator('zeppelin-publish-paragraph');
- this.dynamicForms =
page.locator('zeppelin-notebook-paragraph-dynamic-forms');
this.paragraphResult = page.locator('zeppelin-notebook-paragraph-result');
- this.errorModal = page.locator('.ant-modal').last();
- this.errorModalTitle = page.locator('.ant-modal-title');
this.errorModalContent = this.page.locator('.ant-modal-body', { hasText:
'Paragraph Not Found' }).last();
this.errorModalOkButton = page.getByRole('button', { name: 'OK' }).last();
this.confirmationModal = page.locator('div.ant-modal-confirm').last();
- this.modalTitle =
this.confirmationModal.locator('.ant-modal-confirm-title');
- this.runButton = this.confirmationModal.locator('button', { hasText: 'Run'
});
}
async navigateToNotebook(noteId: string): Promise<void> {
- await this.page.goto(`/#/notebook/${noteId}`);
- await this.waitForPageLoad();
+ await navigateToNotebookWithFallback(this.page, noteId);
}
async navigateToPublishedParagraph(noteId: string, paragraphId: string):
Promise<void> {
- await this.page.goto(`/#/notebook/${noteId}/paragraph/${paragraphId}`);
- await this.waitForPageLoad();
+ await this.navigateToRoute(`/notebook/${noteId}/paragraph/${paragraphId}`);
}
async getErrorModalContent(): Promise<string> {
- return (await this.errorModalContent.textContent()) || '';
+ return await this.getElementText(this.errorModalContent);
}
async clickErrorModalOk(): Promise<void> {
- await this.errorModalOkButton.click();
- }
-
- async getCurrentUrl(): Promise<string> {
- return this.page.url();
- }
-
- async isOnHomePage(): Promise<boolean> {
- const url = await this.getCurrentUrl();
- return url.includes('/#/') && !url.includes('/notebook/');
+ await this.errorModalOkButton.click({ timeout: 15000 });
}
}
diff --git a/zeppelin-web-angular/e2e/models/published-paragraph-page.util.ts
b/zeppelin-web-angular/e2e/models/published-paragraph-page.util.ts
index 8f91c02094..a10cce38da 100644
--- a/zeppelin-web-angular/e2e/models/published-paragraph-page.util.ts
+++ b/zeppelin-web-angular/e2e/models/published-paragraph-page.util.ts
@@ -10,230 +10,28 @@
* limitations under the License.
*/
-import { expect, Page } from '@playwright/test';
-import { NotebookUtil } from './notebook.util';
+import { Page } from '@playwright/test';
+import { BasePage } from './base-page';
import { PublishedParagraphPage } from './published-paragraph-page';
-export class PublishedParagraphTestUtil {
- private page: Page;
+export class PublishedParagraphTestUtil extends BasePage {
private publishedParagraphPage: PublishedParagraphPage;
- private notebookUtil: NotebookUtil;
constructor(page: Page) {
- this.page = page;
+ super(page);
this.publishedParagraphPage = new PublishedParagraphPage(page);
- this.notebookUtil = new NotebookUtil(page);
}
- async verifyNonExistentParagraphError(validNoteId: string,
invalidParagraphId: string): Promise<void> {
- await
this.publishedParagraphPage.navigateToPublishedParagraph(validNoteId,
invalidParagraphId);
-
- // Try different possible error modal texts
- const possibleModals = [
- this.page.locator('.ant-modal', { hasText: 'Paragraph Not Found' }),
- this.page.locator('.ant-modal', { hasText: 'not found' }),
- this.page.locator('.ant-modal', { hasText: 'Error' }),
- this.page.locator('.ant-modal').filter({ hasText: /not
found|error|paragraph/i })
- ];
-
- let modal;
- for (const possibleModal of possibleModals) {
- const count = await possibleModal.count();
-
- for (let i = 0; i < count; i++) {
- const m = possibleModal.nth(i);
-
- if (await m.isVisible()) {
- modal = m;
- break;
- }
- }
-
- if (modal) {
- break;
- }
- }
-
- if (!modal) {
- // If no modal is found, check if we're redirected to home
- await expect(this.page).toHaveURL(/\/#\/$/, { timeout: 10000 });
- return;
- }
-
- await expect(modal).toBeVisible({ timeout: 10000 });
-
- // Try to get content and check if available
- try {
- const content = await this.publishedParagraphPage.getErrorModalContent();
- if (content && content.includes(invalidParagraphId)) {
- expect(content).toContain(invalidParagraphId);
- }
- } catch {
- throw Error('Content check failed, continue with OK button click');
- }
-
- await this.publishedParagraphPage.clickErrorModalOk();
-
- // Wait for redirect to home page instead of checking modal state
- await expect(this.page).toHaveURL(/\/#\/$/, { timeout: 10000 });
-
- expect(await this.publishedParagraphPage.isOnHomePage()).toBe(true);
+ async navigateToPublishedParagraph(noteId: string, paragraphId: string):
Promise<void> {
+ await this.publishedParagraphPage.navigateToPublishedParagraph(noteId,
paragraphId);
}
- async verifyClickLinkThisParagraphBehavior(noteId: string, paragraphId:
string): Promise<void> {
- // 1. Navigate to the normal notebook view
- await this.page.goto(`/#/notebook/${noteId}`);
- await this.page.waitForLoadState('networkidle');
-
- // 2. Find the correct paragraph result element and go up to the parent
paragraph container
- // First try with data-testid, then fallback to first paragraph if not
found
- let paragraphElement =
this.page.locator(`zeppelin-notebook-paragraph[data-testid="${paragraphId}"]`);
-
- if ((await paragraphElement.count()) === 0) {
- // Fallback to first paragraph if specific ID not found
- paragraphElement =
this.page.locator('zeppelin-notebook-paragraph').first();
- }
-
- await expect(paragraphElement).toBeVisible({ timeout: 10000 });
-
- // 3. Click the settings button to open the dropdown
- const settingsButton = paragraphElement.locator('a[nz-dropdown]');
- await settingsButton.click();
-
- // 4. Click "Link this paragraph" in the dropdown menu
- const linkParagraphButton = this.page.locator('li.list-item:has-text("Link
this paragraph")');
- await expect(linkParagraphButton).toBeVisible();
-
- // 5. Handle the new page/tab that opens
- const [newPage] = await Promise.all([this.page.waitForEvent('popup'),
linkParagraphButton.click()]);
- await newPage.waitForLoadState();
-
- // 6. Verify the new page URL shows published paragraph (not redirected)
- await expect(newPage).toHaveURL(new
RegExp(`/notebook/${noteId}/paragraph/${paragraphId}`), { timeout: 10000 });
-
- const codeEditor =
newPage.locator('zeppelin-notebook-paragraph-code-editor');
- await expect(codeEditor).toBeHidden();
-
- const controlPanel =
newPage.locator('zeppelin-notebook-paragraph-control');
- await expect(controlPanel).toBeHidden();
+ async getErrorModalContent(): Promise<string> {
+ return this.publishedParagraphPage.getErrorModalContent();
}
- async createTestNotebook(): Promise<{ noteId: string; paragraphId: string }>
{
- const notebookName = `Test Notebook ${Date.now()}`;
-
- // Use existing NotebookUtil to create notebook
- await this.notebookUtil.createNotebook(notebookName);
-
- // Extract noteId from URL
- const url = this.page.url();
- const noteIdMatch = url.match(/\/notebook\/([^\/\?]+)/);
- if (!noteIdMatch) {
- throw new Error(`Failed to extract notebook ID from URL: ${url}`);
- }
- const noteId = noteIdMatch[1];
-
- // Get first paragraph ID
- await this.page.locator('zeppelin-notebook-paragraph').first().waitFor({
state: 'visible', timeout: 10000 });
- const paragraphContainer =
this.page.locator('zeppelin-notebook-paragraph').first();
- const dropdownTrigger = paragraphContainer.locator('a[nz-dropdown]');
- await dropdownTrigger.click();
-
- const paragraphLink = this.page.locator('li.paragraph-id a').first();
- await paragraphLink.waitFor({ state: 'attached', timeout: 5000 });
-
- const paragraphId = await paragraphLink.textContent();
-
- if (!paragraphId || !paragraphId.startsWith('paragraph_')) {
- throw new Error(`Failed to find a valid paragraph ID. Found:
${paragraphId}`);
- }
-
- // Navigate back to home
- await this.page.goto('/');
- await this.page.waitForLoadState('networkidle');
- await this.page.waitForSelector('text=Welcome to Zeppelin!', { timeout:
5000 });
-
- return { noteId, paragraphId };
- }
-
- async deleteTestNotebook(noteId: string): Promise<void> {
- try {
- // Navigate to home page
- await this.page.goto('/');
- await this.page.waitForLoadState('networkidle');
-
- // Find the notebook in the tree by noteId and get its parent tree node
- const notebookLink = this.page.locator(`a[href*="/notebook/${noteId}"]`);
-
- if ((await notebookLink.count()) > 0) {
- // Hover over the tree node to make delete button visible
- const treeNode =
notebookLink.locator('xpath=ancestor::nz-tree-node[1]');
- await treeNode.hover();
-
- // Wait a bit for hover effects
- await this.page.waitForTimeout(1000);
-
- // Try multiple selectors for the delete button
- const deleteButtonSelectors = [
- 'a[nz-tooltip] i[nztype="delete"]',
- 'i[nztype="delete"]',
- '[nz-popconfirm] i[nztype="delete"]',
- 'i.anticon-delete'
- ];
-
- let deleteClicked = false;
- for (const selector of deleteButtonSelectors) {
- const deleteButton = treeNode.locator(selector);
- try {
- if (await deleteButton.isVisible({ timeout: 2000 })) {
- await deleteButton.click({ timeout: 5000 });
- deleteClicked = true;
- break;
- }
- } catch (e) {
- // Continue to next selector
- continue;
- }
- }
-
- if (!deleteClicked) {
- console.warn(`Delete button not found for notebook ${noteId}`);
- return;
- }
-
- // Confirm deletion in popconfirm with timeout
- try {
- const confirmButton = this.page.locator('button:has-text("OK")');
- await confirmButton.click({ timeout: 5000 });
-
- // Wait for the notebook to be removed with timeout
- await expect(treeNode).toBeHidden({ timeout: 10000 });
- } catch (e) {
- // If confirmation fails, try alternative OK button selectors
- const altConfirmButtons = [
- '.ant-popover button:has-text("OK")',
- '.ant-popconfirm button:has-text("OK")',
- 'button.ant-btn-primary:has-text("OK")'
- ];
-
- for (const selector of altConfirmButtons) {
- try {
- const button = this.page.locator(selector);
- if (await button.isVisible({ timeout: 1000 })) {
- await button.click({ timeout: 3000 });
- await expect(treeNode).toBeHidden({ timeout: 10000 });
- break;
- }
- } catch (altError) {
- // Continue to next selector
- continue;
- }
- }
- }
- }
- } catch (error) {
- console.warn(`Failed to delete test notebook ${noteId}:`, error);
- // Don't throw error to avoid failing the test cleanup
- }
+ async clickErrorModalOk(): Promise<void> {
+ await this.publishedParagraphPage.clickErrorModalOk();
}
generateNonExistentIds(): { noteId: string; paragraphId: string } {
diff --git a/zeppelin-web-angular/e2e/models/workspace-page.ts
b/zeppelin-web-angular/e2e/models/workspace-page.ts
index 57c0da8796..1fdcf9e5a7 100644
--- a/zeppelin-web-angular/e2e/models/workspace-page.ts
+++ b/zeppelin-web-angular/e2e/models/workspace-page.ts
@@ -14,19 +14,10 @@ import { Locator, Page } from '@playwright/test';
import { BasePage } from './base-page';
export class WorkspacePage extends BasePage {
- readonly workspaceComponent: Locator;
- readonly header: Locator;
readonly routerOutlet: Locator;
constructor(page: Page) {
super(page);
- this.workspaceComponent = page.locator('zeppelin-workspace');
- this.header = page.locator('zeppelin-header');
this.routerOutlet = page.locator('zeppelin-workspace router-outlet');
}
-
- async navigateToWorkspace(): Promise<void> {
- await this.page.goto('/', { waitUntil: 'load' });
- await this.waitForPageLoad();
- }
}
diff --git a/zeppelin-web-angular/e2e/models/workspace-page.util.ts
b/zeppelin-web-angular/e2e/models/workspace-page.util.ts
index 7ff706f93a..fd6d9c3f45 100644
--- a/zeppelin-web-angular/e2e/models/workspace-page.util.ts
+++ b/zeppelin-web-angular/e2e/models/workspace-page.util.ts
@@ -11,54 +11,28 @@
*/
import { expect, Page } from '@playwright/test';
-import { performLoginIfRequired, waitForZeppelinReady } from '../utils';
+import { BasePage } from './base-page';
import { WorkspacePage } from './workspace-page';
-export class WorkspaceTestUtil {
- private page: Page;
+export class WorkspaceUtil extends BasePage {
private workspacePage: WorkspacePage;
constructor(page: Page) {
- this.page = page;
+ super(page);
this.workspacePage = new WorkspacePage(page);
}
- async navigateAndWaitForLoad(): Promise<void> {
- await this.workspacePage.navigateToWorkspace();
- await waitForZeppelinReady(this.page);
- await performLoginIfRequired(this.page);
- }
-
- async verifyWorkspaceLayout(): Promise<void> {
- await expect(this.workspacePage.workspaceComponent).toBeVisible();
- await expect(this.workspacePage.routerOutlet).toBeAttached();
- }
-
async verifyHeaderVisibility(shouldBeVisible: boolean): Promise<void> {
if (shouldBeVisible) {
- await expect(this.workspacePage.header).toBeVisible();
+ await expect(this.workspacePage.zeppelinHeader).toBeVisible();
} else {
- await expect(this.workspacePage.header).toBeHidden();
+ await expect(this.workspacePage.zeppelinHeader).toBeHidden();
}
}
- async verifyWorkspaceContainer(): Promise<void> {
- await expect(this.workspacePage.workspaceComponent).toBeVisible();
- const contentElements = await this.page.locator('.content').count();
- expect(contentElements).toBeGreaterThan(0);
- }
-
async verifyRouterOutletActivation(): Promise<void> {
await expect(this.workspacePage.routerOutlet).toBeAttached();
-
- await this.page.waitForFunction(
- () => {
- const workspace = document.querySelector('zeppelin-workspace');
- const outlet = workspace?.querySelector('router-outlet');
- return outlet && outlet.nextElementSibling !== null;
- },
- { timeout: 10000 }
- );
+ await this.waitForRouterOutletChild();
}
async waitForComponentActivation(): Promise<void> {
diff --git a/zeppelin-web-angular/e2e/tests/app.spec.ts
b/zeppelin-web-angular/e2e/tests/app.spec.ts
index 5a02c87f38..5d956c747f 100644
--- a/zeppelin-web-angular/e2e/tests/app.spec.ts
+++ b/zeppelin-web-angular/e2e/tests/app.spec.ts
@@ -12,8 +12,7 @@
import { expect, test } from '@playwright/test';
import { BasePage } from '../models/base-page';
-import { LoginTestUtil } from '../models/login-page.util';
-import { addPageAnnotationBeforeEach, waitForZeppelinReady, PAGES } from
'../utils';
+import { addPageAnnotationBeforeEach, waitForZeppelinReady, PAGES,
performLoginIfRequired } from '../utils';
test.describe('Zeppelin App Component', () => {
addPageAnnotationBeforeEach(PAGES.APP);
@@ -23,6 +22,8 @@ test.describe('Zeppelin App Component', () => {
basePage = new BasePage(page);
await page.goto('/', { waitUntil: 'load' });
+ await waitForZeppelinReady(page);
+ await performLoginIfRequired(page);
});
test('should have correct component selector and structure', async ({ page
}) => {
@@ -56,12 +57,8 @@ test.describe('Zeppelin App Component', () => {
test('should display workspace after loading', async ({ page }) => {
await waitForZeppelinReady(page);
- const isShiroEnabled = await LoginTestUtil.isShiroEnabled();
- if (isShiroEnabled) {
- await expect(page.locator('zeppelin-login')).toBeVisible();
- } else {
- await expect(page.locator('zeppelin-workspace')).toBeVisible();
- }
+ // After the `beforeEach` hook, which handles login, the workspace should
be visible.
+ await expect(basePage.zeppelinWorkspace).toBeVisible();
});
test('should handle navigation events correctly', async ({ page }) => {
@@ -142,6 +139,7 @@ test.describe('Zeppelin App Component', () => {
test('should maintain component integrity during navigation', async ({ page
}) => {
await waitForZeppelinReady(page);
+ await performLoginIfRequired(page);
const zeppelinRoot = page.locator('zeppelin-root');
const routerOutlet = zeppelinRoot.locator('router-outlet').first();
diff --git
a/zeppelin-web-angular/e2e/tests/authentication/anonymous-login-redirect.spec.ts
b/zeppelin-web-angular/e2e/tests/authentication/anonymous-login-redirect.spec.ts
index 1c0905c282..5e73dc036d 100644
---
a/zeppelin-web-angular/e2e/tests/authentication/anonymous-login-redirect.spec.ts
+++
b/zeppelin-web-angular/e2e/tests/authentication/anonymous-login-redirect.spec.ts
@@ -11,10 +11,12 @@
*/
import { expect, test } from '@playwright/test';
-import { HomePageUtil } from '../../models/home-page.util';
+import { BasePage } from '../../models/base-page';
+import { HomePage } from '../../models/home-page';
import { LoginTestUtil } from '../../models/login-page.util';
import {
addPageAnnotationBeforeEach,
+ getBasicPageMetadata,
getCurrentPath,
waitForUrlNotContaining,
waitForZeppelinReady,
@@ -24,7 +26,8 @@ import {
test.describe('Anonymous User Login Redirect', () => {
addPageAnnotationBeforeEach(PAGES.WORKSPACE.HOME);
- let homePageUtil: HomePageUtil;
+ let homePage: HomePage;
+ let basePage: BasePage;
test.beforeAll(async () => {
const isShiroEnabled = await LoginTestUtil.isShiroEnabled();
@@ -34,74 +37,90 @@ test.describe('Anonymous User Login Redirect', () => {
});
test.beforeEach(async ({ page }) => {
- homePageUtil = new HomePageUtil(page);
+ homePage = new HomePage(page);
+ basePage = new BasePage(page);
});
test.describe('Given an anonymous user is already logged in', () => {
test.beforeEach(async ({ page }) => {
- await page.goto('/', { waitUntil: 'load' });
+ await page.goto('/#/');
await waitForZeppelinReady(page);
});
- test('When accessing login page directly, Then should redirect to home
with proper URL change', async () => {
- const redirectResult = await
homePageUtil.verifyAnonymousUserRedirectFromLogin();
-
- expect(redirectResult.isLoginUrlMaintained).toBe(false);
- expect(redirectResult.isHomeContentDisplayed).toBe(true);
- expect(redirectResult.isAnonymousUser).toBe(true);
- expect(redirectResult.currentPath).toContain('#/');
- expect(redirectResult.currentPath).not.toContain('#/login');
+ test('When accessing login page directly, Then should redirect to home
with proper URL change', async ({
+ page
+ }) => {
+ await homePage.navigateToLogin();
+
+ const currentPath = getCurrentPath(page);
+ const isLoginUrlMaintained = currentPath.includes('#/login');
+ const isHomeContentDisplayed = await homePage.isHomeContentDisplayed();
+ const isAnonymousUser = await homePage.isAnonymousUser();
+
+ expect(isLoginUrlMaintained).toBe(false);
+ expect(isHomeContentDisplayed).toBe(true);
+ expect(isAnonymousUser).toBe(true);
+ expect(currentPath).toContain('#/');
+ expect(currentPath).not.toContain('#/login');
});
test('When accessing login page directly, Then should display all home
page elements correctly', async ({
page
}) => {
- await page.goto('/#/login', { waitUntil: 'load' });
+ await page.goto('/#/login');
await waitForZeppelinReady(page);
await page.waitForURL(url => !url.toString().includes('#/login'));
- await homePageUtil.verifyHomePageElements();
+ await expect(homePage.welcomeTitle).toBeVisible();
+ await expect(homePage.notebookSection).toBeVisible();
+ await expect(homePage.helpSection).toBeVisible();
+ await expect(homePage.communitySection).toBeVisible();
});
test('When clicking Zeppelin logo after redirect, Then should maintain
home URL and content', async ({ page }) => {
- await page.goto('/#/login', { waitUntil: 'load' });
+ await page.goto('/#/login');
await waitForZeppelinReady(page);
await page.waitForURL(url => !url.toString().includes('#/login'));
- const navigationResult = await homePageUtil.testNavigationConsistency();
+ const pathBeforeClick = getCurrentPath(page);
+ await homePage.clickZeppelinLogo();
+ await basePage.waitForPageLoad();
+ const pathAfterClick = getCurrentPath(page);
+ const homeContentMaintained = await homePage.isHomeContentDisplayed();
- expect(navigationResult.pathBeforeClick).toContain('#/');
- expect(navigationResult.pathBeforeClick).not.toContain('#/login');
- expect(navigationResult.pathAfterClick).toContain('#/');
- expect(navigationResult.homeContentMaintained).toBe(true);
+ expect(pathBeforeClick).toContain('#/');
+ expect(pathBeforeClick).not.toContain('#/login');
+ expect(pathAfterClick).toContain('#/');
+ expect(homeContentMaintained).toBe(true);
});
test('When accessing login page, Then should redirect and maintain
anonymous user state', async ({ page }) => {
- await page.goto('/#/login', { waitUntil: 'load' });
+ await page.goto('/#/login');
await waitForZeppelinReady(page);
await page.waitForURL(url => !url.toString().includes('#/login'));
- const metadata = await homePageUtil.getHomePageMetadata();
+ const basicMetadata = await getBasicPageMetadata(page);
+ const isAnonymous = await homePage.isAnonymousUser();
- expect(metadata.title).toContain('Zeppelin');
- expect(metadata.path).toContain('#/');
- expect(metadata.path).not.toContain('#/login');
- expect(metadata.isAnonymous).toBe(true);
+ expect(basicMetadata.title).toContain('Zeppelin');
+ expect(basicMetadata.path).toContain('#/');
+ expect(basicMetadata.path).not.toContain('#/login');
+ expect(isAnonymous).toBe(true);
});
test('When accessing login page, Then should display welcome heading and
main sections', async ({ page }) => {
- await page.goto('/#/login', { waitUntil: 'load' });
+ await page.goto('/#/login');
await waitForZeppelinReady(page);
await page.waitForURL(url => !url.toString().includes('#/login'));
- await expect(page.locator('h1', { hasText: 'Welcome to Zeppelin!'
})).toBeVisible();
+ await expect(basePage.welcomeTitle).toBeVisible();
await expect(page.locator('text=Notebook').first()).toBeVisible();
await expect(page.locator('text=Help').first()).toBeVisible();
await expect(page.locator('text=Community').first()).toBeVisible();
});
test('When accessing login page, Then should display notebook
functionalities', async ({ page }) => {
- await page.goto('/#/login', { waitUntil: 'load' });
+ await page.goto('/#/login');
await waitForZeppelinReady(page);
await page.waitForURL(url => !url.toString().includes('#/login'));
@@ -117,7 +136,7 @@ test.describe('Anonymous User Login Redirect', () => {
test('When accessing login page, Then should display external links in
help and community sections', async ({
page
}) => {
- await page.goto('/#/login', { waitUntil: 'load' });
+ await page.goto('/#/login');
await waitForZeppelinReady(page);
await page.waitForURL(url => !url.toString().includes('#/login'));
@@ -143,33 +162,36 @@ test.describe('Anonymous User Login Redirect', () => {
test('When navigating between home and login URLs, Then should maintain
consistent user experience', async ({
page
}) => {
- await page.goto('/', { waitUntil: 'load' });
+ await page.goto('/#/');
await waitForZeppelinReady(page);
- const homeMetadata = await homePageUtil.getHomePageMetadata();
+ const homeMetadata = await getBasicPageMetadata(page);
+ const isHomeAnonymous = await homePage.isAnonymousUser();
expect(homeMetadata.path).toContain('#/');
- expect(homeMetadata.isAnonymous).toBe(true);
+ expect(isHomeAnonymous).toBe(true);
- await page.goto('/#/login', { waitUntil: 'load' });
+ await page.goto('/#/login');
await waitForZeppelinReady(page);
await page.waitForURL(url => !url.toString().includes('#/login'));
- const loginMetadata = await homePageUtil.getHomePageMetadata();
+ const loginMetadata = await getBasicPageMetadata(page);
+ const isLoginAnonymous = await homePage.isAnonymousUser();
expect(loginMetadata.path).toContain('#/');
expect(loginMetadata.path).not.toContain('#/login');
- expect(loginMetadata.isAnonymous).toBe(true);
+ expect(isLoginAnonymous).toBe(true);
- const isHomeContentDisplayed = await
homePageUtil.verifyAnonymousUserRedirectFromLogin();
- expect(isHomeContentDisplayed.isHomeContentDisplayed).toBe(true);
+ await homePage.navigateToLogin();
+ const isHomeContentDisplayed = await homePage.isHomeContentDisplayed();
+ expect(isHomeContentDisplayed).toBe(true);
});
test('When multiple page loads occur on login URL, Then should
consistently redirect to home', async ({ page }) => {
for (let i = 0; i < 3; i++) {
- await page.goto('/#/login', { waitUntil: 'load' });
+ await page.goto('/#/login');
await waitForZeppelinReady(page);
await waitForUrlNotContaining(page, '#/login');
- await expect(page.locator('h1', { hasText: 'Welcome to Zeppelin!'
})).toBeVisible();
+ await expect(basePage.welcomeTitle).toBeVisible();
await expect(page.locator('text=anonymous')).toBeVisible();
const path = getCurrentPath(page);
diff --git a/zeppelin-web-angular/e2e/tests/home/home-page-elements.spec.ts
b/zeppelin-web-angular/e2e/tests/home/home-page-elements.spec.ts
index f9f27d59e5..f41c00c544 100644
--- a/zeppelin-web-angular/e2e/tests/home/home-page-elements.spec.ts
+++ b/zeppelin-web-angular/e2e/tests/home/home-page-elements.spec.ts
@@ -12,13 +12,15 @@
import { expect, test } from '@playwright/test';
import { HomePage } from '../../models/home-page';
-import { HomePageUtil } from '../../models/home-page.util';
import { addPageAnnotationBeforeEach, performLoginIfRequired,
waitForZeppelinReady, PAGES } from '../../utils';
test.describe('Home Page - Core Elements', () => {
addPageAnnotationBeforeEach(PAGES.WORKSPACE.HOME);
+ let homePage: HomePage;
+
test.beforeEach(async ({ page }) => {
+ homePage = new HomePage(page);
await page.goto('/#/');
await waitForZeppelinReady(page);
await performLoginIfRequired(page);
@@ -26,10 +28,7 @@ test.describe('Home Page - Core Elements', () => {
test.describe('Welcome Section', () => {
test('should display welcome section with correct content', async ({ page
}) => {
- const homePageUtil = new HomePageUtil(page);
-
await test.step('Given I am on the home page', async () => {
- const homePage = new HomePage(page);
await homePage.navigateToHome();
});
@@ -38,13 +37,18 @@ test.describe('Home Page - Core Elements', () => {
});
await test.step('Then I should see the welcome section with correct
content', async () => {
- await homePageUtil.verifyWelcomeSection();
+ await expect(homePage.welcomeSection).toBeVisible();
+ await expect(homePage.welcomeTitle).toBeVisible();
+ const headingText = await homePage.getWelcomeHeadingText();
+ expect(headingText.trim()).toBe('Welcome to Zeppelin!');
+ await expect(homePage.welcomeDescription).toBeVisible();
+ const welcomeText = await homePage.welcomeDescription.textContent();
+ expect(welcomeText).toContain('web-based notebook');
+ expect(welcomeText).toContain('interactive data analytics');
});
});
- test('should have proper welcome message structure', async ({ page }) => {
- const homePage = new HomePage(page);
-
+ test('should have proper welcome message structure', async () => {
await test.step('Given I am on the home page', async () => {
await homePage.navigateToHome();
});
@@ -54,7 +58,7 @@ test.describe('Home Page - Core Elements', () => {
});
await test.step('Then I should see the welcome heading', async () => {
- await expect(homePage.welcomeHeading).toBeVisible();
+ await expect(homePage.welcomeTitle).toBeVisible();
const headingText = await homePage.getWelcomeHeadingText();
expect(headingText.trim()).toBe('Welcome to Zeppelin!');
});
@@ -70,10 +74,7 @@ test.describe('Home Page - Core Elements', () => {
test.describe('Notebook Section', () => {
test('should display notebook section with all components', async ({ page
}) => {
- const homePageUtil = new HomePageUtil(page);
-
await test.step('Given I am on the home page', async () => {
- const homePage = new HomePage(page);
await homePage.navigateToHome();
});
@@ -82,14 +83,15 @@ test.describe('Home Page - Core Elements', () => {
});
await test.step('Then I should see all notebook section components',
async () => {
- await homePageUtil.verifyNotebookSection();
+ await expect(homePage.notebookSection).toBeVisible();
+ await expect(homePage.notebookHeading).toBeVisible();
+ await expect(homePage.refreshNoteButton).toBeVisible();
+ await page.waitForSelector('zeppelin-node-list', { timeout: 10000 });
+ await expect(homePage.zeppelinNodeList).toBeVisible();
});
});
- test('should have functional refresh notes button', async ({ page }) => {
- const homePage = new HomePage(page);
- const homePageUtil = new HomePageUtil(page);
-
+ test('should have functional refresh notes button', async () => {
await test.step('Given I am on the home page with notebook section
visible', async () => {
await homePage.navigateToHome();
await expect(homePage.refreshNoteButton).toBeVisible();
@@ -100,13 +102,14 @@ test.describe('Home Page - Core Elements', () => {
});
await test.step('Then the notebook list should still be visible', async
() => {
- await homePageUtil.verifyNotebookRefreshFunctionality();
+ await homePage.waitForRefreshToComplete();
+ await expect(homePage.zeppelinNodeList).toBeVisible();
+ const isStillVisible = await homePage.zeppelinNodeList.isVisible();
+ expect(isStillVisible).toBe(true);
});
});
test('should display notebook list component', async ({ page }) => {
- const homePage = new HomePage(page);
-
await test.step('Given I am on the home page', async () => {
await homePage.navigateToHome();
});
@@ -116,7 +119,7 @@ test.describe('Home Page - Core Elements', () => {
});
await test.step('Then I should see the notebook list component', async
() => {
- await expect(homePage.notebookList).toBeVisible();
+ await expect(homePage.zeppelinNodeList).toBeVisible();
const isVisible = await homePage.isNotebookListVisible();
expect(isVisible).toBe(true);
});
@@ -125,10 +128,7 @@ test.describe('Home Page - Core Elements', () => {
test.describe('Help Section', () => {
test('should display help section with documentation link', async ({ page
}) => {
- const homePageUtil = new HomePageUtil(page);
-
await test.step('Given I am on the home page', async () => {
- const homePage = new HomePage(page);
await homePage.navigateToHome();
});
@@ -137,11 +137,11 @@ test.describe('Home Page - Core Elements', () => {
});
await test.step('Then I should see the help section', async () => {
- await homePageUtil.verifyHelpSection();
+ await expect(homePage.helpSection).toBeVisible();
+ await expect(homePage.helpHeading).toBeVisible();
});
await test.step('And I should see the documentation link', async () => {
- const homePage = new HomePage(page);
await expect(homePage.externalLinks.documentation).toBeVisible();
});
});
@@ -149,10 +149,7 @@ test.describe('Home Page - Core Elements', () => {
test.describe('Community Section', () => {
test('should display community section with all links', async ({ page })
=> {
- const homePageUtil = new HomePageUtil(page);
-
await test.step('Given I am on the home page', async () => {
- const homePage = new HomePage(page);
await homePage.navigateToHome();
});
@@ -161,11 +158,15 @@ test.describe('Home Page - Core Elements', () => {
});
await test.step('Then I should see the community section', async () => {
- await homePageUtil.verifyCommunitySection();
+ await expect(homePage.communitySection).toBeVisible();
+ await expect(homePage.communityHeading).toBeVisible();
});
await test.step('And I should see all community links', async () => {
- await homePageUtil.verifyExternalLinks();
+ await expect(homePage.externalLinks.documentation).toBeVisible();
+ await expect(homePage.externalLinks.mailingList).toBeVisible();
+ await expect(homePage.externalLinks.issuesTracking).toBeVisible();
+ await expect(homePage.externalLinks.github).toBeVisible();
});
});
});
diff --git
a/zeppelin-web-angular/e2e/tests/home/home-page-enhanced-functionality.spec.ts
b/zeppelin-web-angular/e2e/tests/home/home-page-enhanced-functionality.spec.ts
index 1025a48e4f..fb3a56cbc2 100644
---
a/zeppelin-web-angular/e2e/tests/home/home-page-enhanced-functionality.spec.ts
+++
b/zeppelin-web-angular/e2e/tests/home/home-page-enhanced-functionality.spec.ts
@@ -11,54 +11,82 @@
*/
import { expect, test } from '@playwright/test';
-import { HomePageUtil } from '../../models/home-page.util';
+import { HomePage } from '../../models/home-page';
import { addPageAnnotationBeforeEach, performLoginIfRequired,
waitForZeppelinReady, PAGES } from '../../utils';
addPageAnnotationBeforeEach(PAGES.WORKSPACE.HOME);
test.describe('Home Page Enhanced Functionality', () => {
- let homeUtil: HomePageUtil;
+ let homePage: HomePage;
test.beforeEach(async ({ page }) => {
- homeUtil = new HomePageUtil(page);
- await page.goto('/');
+ homePage = new HomePage(page);
+ await page.goto('/#/');
await waitForZeppelinReady(page);
await performLoginIfRequired(page);
});
test.describe('Given documentation links are displayed', () => {
test('When documentation link is checked Then should have correct version
in URL', async () => {
- await homeUtil.verifyDocumentationVersionLink();
+ const href = await homePage.getDocumentationLinkHref();
+ expect(href).toContain('zeppelin.apache.org/docs');
+ expect(href).toMatch(/\/docs\/\d+\.\d+\.\d+(-SNAPSHOT)?\//);
});
test('When external links are checked Then should all open in new tab',
async () => {
- await homeUtil.verifyAllExternalLinksTargetBlank();
+ const links = [
+ homePage.externalLinks.documentation,
+ homePage.externalLinks.mailingList,
+ homePage.externalLinks.issuesTracking,
+ homePage.externalLinks.github
+ ];
+
+ for (const link of links) {
+ const target = await link.getAttribute('target');
+ expect(target).toBe('_blank');
+ }
});
});
test.describe('Given welcome section display', () => {
test('When page loads Then should show welcome content with proper text',
async () => {
- await homeUtil.verifyWelcomeSection();
+ await expect(homePage.welcomeSection).toBeVisible();
+ await expect(homePage.welcomeTitle).toBeVisible();
+ const headingText = await homePage.getWelcomeHeadingText();
+ expect(headingText.trim()).toBe('Welcome to Zeppelin!');
+ await expect(homePage.welcomeDescription).toBeVisible();
+ const welcomeText = await homePage.welcomeDescription.textContent();
+ expect(welcomeText).toContain('web-based notebook');
+ expect(welcomeText).toContain('interactive data analytics');
});
- test('When welcome section is displayed Then should contain interactive
elements', async () => {
- await homeUtil.verifyNotebookSection();
+ test('When welcome section is displayed Then should contain interactive
elements', async ({ page }) => {
+ await expect(homePage.notebookSection).toBeVisible();
+ await expect(homePage.notebookHeading).toBeVisible();
+ await expect(homePage.refreshNoteButton).toBeVisible();
+ await page.waitForSelector('zeppelin-node-list', { timeout: 10000 });
+ await expect(homePage.zeppelinNodeList).toBeVisible();
});
});
test.describe('Given community section content', () => {
test('When community section loads Then should display help and community
headings', async () => {
- await homeUtil.verifyHelpSection();
- await homeUtil.verifyCommunitySection();
+ await expect(homePage.helpSection).toBeVisible();
+ await expect(homePage.helpHeading).toBeVisible();
+ await expect(homePage.communitySection).toBeVisible();
+ await expect(homePage.communityHeading).toBeVisible();
});
test('When external links are displayed Then should show correct targets',
async () => {
- const linkTargets = await homeUtil.testExternalLinkTargets();
+ const docHref = await
homePage.externalLinks.documentation.getAttribute('href');
+ const mailHref = await
homePage.externalLinks.mailingList.getAttribute('href');
+ const issuesHref = await
homePage.externalLinks.issuesTracking.getAttribute('href');
+ const githubHref = await
homePage.externalLinks.github.getAttribute('href');
-
expect(linkTargets.documentationHref).toContain('zeppelin.apache.org/docs');
- expect(linkTargets.mailingListHref).toContain('community.html');
- expect(linkTargets.issuesTrackingHref).toContain('issues.apache.org');
- expect(linkTargets.githubHref).toContain('github.com/apache/zeppelin');
+ expect(docHref).toContain('zeppelin.apache.org/docs');
+ expect(mailHref).toContain('community.html');
+ expect(issuesHref).toContain('issues.apache.org');
+ expect(githubHref).toContain('github.com/apache/zeppelin');
});
});
});
diff --git
a/zeppelin-web-angular/e2e/tests/home/home-page-external-links.spec.ts
b/zeppelin-web-angular/e2e/tests/home/home-page-external-links.spec.ts
index 34e7e27de0..ce44eb967b 100644
--- a/zeppelin-web-angular/e2e/tests/home/home-page-external-links.spec.ts
+++ b/zeppelin-web-angular/e2e/tests/home/home-page-external-links.spec.ts
@@ -12,23 +12,22 @@
import { expect, test } from '@playwright/test';
import { HomePage } from '../../models/home-page';
-import { HomePageUtil } from '../../models/home-page.util';
import { addPageAnnotationBeforeEach, performLoginIfRequired,
waitForZeppelinReady, PAGES } from '../../utils';
test.describe('Home Page - External Links', () => {
addPageAnnotationBeforeEach(PAGES.WORKSPACE.HOME);
+ let homePage: HomePage;
+
test.beforeEach(async ({ page }) => {
+ homePage = new HomePage(page);
await page.goto('/#/');
await waitForZeppelinReady(page);
await performLoginIfRequired(page);
});
test.describe('Documentation Link', () => {
- test('should have correct documentation link with dynamic version', async
({ page }) => {
- const homePage = new HomePage(page);
- const homePageUtil = new HomePageUtil(page);
-
+ test('should have correct documentation link with dynamic version', async
() => {
await test.step('Given I am on the home page', async () => {
await homePage.navigateToHome();
});
@@ -38,9 +37,9 @@ test.describe('Home Page - External Links', () => {
});
await test.step('Then it should have the correct href pattern', async ()
=> {
- const linkTargets = await homePageUtil.testExternalLinkTargets();
-
expect(linkTargets.documentationHref).toContain('zeppelin.apache.org/docs');
- expect(linkTargets.documentationHref).toContain('index.html');
+ const href = await
homePage.externalLinks.documentation.getAttribute('href');
+ expect(href).toContain('zeppelin.apache.org/docs');
+ expect(href).toContain('index.html');
});
await test.step('And it should open in a new tab', async () => {
@@ -51,10 +50,7 @@ test.describe('Home Page - External Links', () => {
});
test.describe('Community Links', () => {
- test('should have correct mailing list link', async ({ page }) => {
- const homePage = new HomePage(page);
- const homePageUtil = new HomePageUtil(page);
-
+ test('should have correct mailing list link', async () => {
await test.step('Given I am on the home page', async () => {
await homePage.navigateToHome();
});
@@ -64,8 +60,8 @@ test.describe('Home Page - External Links', () => {
});
await test.step('Then it should have the correct href', async () => {
- const linkTargets = await homePageUtil.testExternalLinkTargets();
-
expect(linkTargets.mailingListHref).toBe('http://zeppelin.apache.org/community.html');
+ const href = await
homePage.externalLinks.mailingList.getAttribute('href');
+ expect(href).toBe('http://zeppelin.apache.org/community.html');
});
await test.step('And it should open in a new tab', async () => {
@@ -79,10 +75,7 @@ test.describe('Home Page - External Links', () => {
});
});
- test('should have correct issues tracking link', async ({ page }) => {
- const homePage = new HomePage(page);
- const homePageUtil = new HomePageUtil(page);
-
+ test('should have correct issues tracking link', async () => {
await test.step('Given I am on the home page', async () => {
await homePage.navigateToHome();
});
@@ -92,10 +85,8 @@ test.describe('Home Page - External Links', () => {
});
await test.step('Then it should have the correct href', async () => {
- const linkTargets = await homePageUtil.testExternalLinkTargets();
- expect(linkTargets.issuesTrackingHref).toBe(
-
'https://issues.apache.org/jira/projects/ZEPPELIN/issues/filter=allopenissues'
- );
+ const href = await
homePage.externalLinks.issuesTracking.getAttribute('href');
+
expect(href).toBe('https://issues.apache.org/jira/projects/ZEPPELIN/issues/filter=allopenissues');
});
await test.step('And it should open in a new tab', async () => {
@@ -109,10 +100,7 @@ test.describe('Home Page - External Links', () => {
});
});
- test('should have correct GitHub link', async ({ page }) => {
- const homePage = new HomePage(page);
- const homePageUtil = new HomePageUtil(page);
-
+ test('should have correct GitHub link', async () => {
await test.step('Given I am on the home page', async () => {
await homePage.navigateToHome();
});
@@ -122,8 +110,8 @@ test.describe('Home Page - External Links', () => {
});
await test.step('Then it should have the correct href', async () => {
- const linkTargets = await homePageUtil.testExternalLinkTargets();
-
expect(linkTargets.githubHref).toBe('https://github.com/apache/zeppelin');
+ const href = await homePage.externalLinks.github.getAttribute('href');
+ expect(href).toBe('https://github.com/apache/zeppelin');
});
await test.step('And it should open in a new tab', async () => {
@@ -139,9 +127,7 @@ test.describe('Home Page - External Links', () => {
});
test.describe('Link Verification', () => {
- test('should have all external links with proper attributes', async ({
page }) => {
- const homePage = new HomePage(page);
-
+ test('should have all external links with proper attributes', async () => {
await test.step('Given I am on the home page', async () => {
await homePage.navigateToHome();
});
diff --git a/zeppelin-web-angular/e2e/tests/home/home-page-layout.spec.ts
b/zeppelin-web-angular/e2e/tests/home/home-page-layout.spec.ts
index b830f8ab03..e960c3c6cb 100644
--- a/zeppelin-web-angular/e2e/tests/home/home-page-layout.spec.ts
+++ b/zeppelin-web-angular/e2e/tests/home/home-page-layout.spec.ts
@@ -17,7 +17,10 @@ import { addPageAnnotationBeforeEach,
performLoginIfRequired, waitForZeppelinRea
test.describe('Home Page - Layout and Grid', () => {
addPageAnnotationBeforeEach(PAGES.WORKSPACE.HOME);
+ let homePage: HomePage;
+
test.beforeEach(async ({ page }) => {
+ homePage = new HomePage(page);
await page.goto('/#/');
await waitForZeppelinReady(page);
await performLoginIfRequired(page);
@@ -26,7 +29,6 @@ test.describe('Home Page - Layout and Grid', () => {
test.describe('Responsive Grid Layout', () => {
test('should display responsive grid structure', async ({ page }) => {
await test.step('Given I am on the home page', async () => {
- const homePage = new HomePage(page);
await homePage.navigateToHome();
});
@@ -35,9 +37,7 @@ test.describe('Home Page - Layout and Grid', () => {
});
});
- test('should have proper column distribution', async ({ page }) => {
- const homePage = new HomePage(page);
-
+ test('should have proper column distribution', async () => {
await test.step('Given I am on the home page', async () => {
await homePage.navigateToHome();
});
@@ -66,15 +66,12 @@ test.describe('Home Page - Layout and Grid', () => {
});
test('should maintain layout structure across different viewport sizes',
async ({ page }) => {
- const homePage = new HomePage(page);
-
await test.step('Given I am on the home page', async () => {
await homePage.navigateToHome();
});
await test.step('When I resize to tablet view', async () => {
await page.setViewportSize({ width: 768, height: 1024 });
- await page.waitForTimeout(500);
});
await test.step('Then the grid should still be visible and functional',
async () => {
@@ -85,7 +82,6 @@ test.describe('Home Page - Layout and Grid', () => {
await test.step('When I resize to mobile view', async () => {
await page.setViewportSize({ width: 375, height: 667 });
- await page.waitForTimeout(500);
});
await test.step('Then the grid should adapt to mobile layout', async ()
=> {
@@ -104,8 +100,6 @@ test.describe('Home Page - Layout and Grid', () => {
test.describe('Content Organization', () => {
test('should organize content in logical sections', async ({ page }) => {
- const homePage = new HomePage(page);
-
await test.step('Given I am on the home page', async () => {
await homePage.navigateToHome();
});
diff --git
a/zeppelin-web-angular/e2e/tests/home/home-page-note-operations.spec.ts
b/zeppelin-web-angular/e2e/tests/home/home-page-note-operations.spec.ts
index 23a6888054..018bfbf40e 100644
--- a/zeppelin-web-angular/e2e/tests/home/home-page-note-operations.spec.ts
+++ b/zeppelin-web-angular/e2e/tests/home/home-page-note-operations.spec.ts
@@ -11,16 +11,21 @@
*/
import { expect, test } from '@playwright/test';
+import { HomePage } from '../../models/home-page';
import { addPageAnnotationBeforeEach, performLoginIfRequired,
waitForZeppelinReady, PAGES } from '../../utils';
addPageAnnotationBeforeEach(PAGES.WORKSPACE.HOME);
test.describe('Home Page Note Operations', () => {
+ let homePage: HomePage;
+
test.beforeEach(async ({ page }) => {
- await page.goto('/');
+ homePage = new HomePage(page);
+ await page.goto('/#/');
await waitForZeppelinReady(page);
await performLoginIfRequired(page);
- await page.waitForSelector('zeppelin-node-list', { timeout: 15000 });
+ const noteListLocator = page.locator('zeppelin-node-list');
+ await expect(noteListLocator).toBeVisible({ timeout: 15000 });
});
test.describe('Given note operations are available', () => {
@@ -31,9 +36,9 @@ test.describe('Home Page Note Operations', () => {
const firstNote = page.locator('.node .file').first();
await firstNote.hover();
- await expect(page.locator('.file .operation a[nztooltiptitle*="Rename
note"]').first()).toBeVisible();
- await expect(page.locator('.file .operation a[nztooltiptitle*="Clear
output"]').first()).toBeVisible();
- await expect(page.locator('.file .operation a[nztooltiptitle*="Move
note to Trash"]').first()).toBeVisible();
+ await
expect(homePage.nodeList.noteActions.renameNote.first()).toBeVisible();
+ await
expect(homePage.nodeList.noteActions.clearOutput.first()).toBeVisible();
+ await
expect(homePage.nodeList.noteActions.moveToTrash.first()).toBeVisible();
} else {
console.log('No notes available for testing operations');
}
@@ -50,22 +55,18 @@ test.describe('Home Page Note Operations', () => {
const firstNote = page.locator('.node .file').first();
await firstNote.hover();
- const renameIcon = page.locator('.file .operation
a[nztooltiptitle*="Rename note"]').first();
- const clearIcon = page.locator('.file .operation
a[nztooltiptitle*="Clear output"]').first();
- const deleteIcon = page.locator('.file .operation
a[nztooltiptitle*="Move note to Trash"]').first();
-
- await expect(renameIcon).toBeVisible();
- await expect(clearIcon).toBeVisible();
- await expect(deleteIcon).toBeVisible();
+ await expect(homePage.nodeList.noteActions.renameNote).toBeVisible();
+ await expect(homePage.nodeList.noteActions.clearOutput).toBeVisible();
+ await expect(homePage.nodeList.noteActions.moveToTrash).toBeVisible();
// Test tooltip visibility by hovering over each icon
- await renameIcon.hover();
+ await homePage.nodeList.noteActions.renameNote.hover();
await expect(page.locator('.ant-tooltip', { hasText: 'Rename note'
})).toBeVisible();
- await clearIcon.hover();
+ await homePage.nodeList.noteActions.clearOutput.hover();
await expect(page.locator('.ant-tooltip', { hasText: 'Clear output'
})).toBeVisible();
- await deleteIcon.hover();
+ await homePage.nodeList.noteActions.moveToTrash.hover();
await expect(page.locator('.ant-tooltip', { hasText: 'Move note to
Trash' })).toBeVisible();
}
});
@@ -83,7 +84,7 @@ test.describe('Home Page Note Operations', () => {
const noteItem = page.locator('.node .file').first();
await noteItem.hover();
- const renameButton = page.locator('.file .operation
a[nztooltiptitle*="Rename note"]').first();
+ const renameButton = homePage.nodeList.noteActions.renameNote.first();
await expect(renameButton).toBeVisible();
await renameButton.click();
@@ -114,7 +115,7 @@ test.describe('Home Page Note Operations', () => {
const noteItem = page.locator('.node .file').first();
await noteItem.hover();
- const clearButton = page.locator('.file .operation
a[nztooltiptitle*="Clear output"]').first();
+ const clearButton = homePage.nodeList.noteActions.clearOutput.first();
await expect(clearButton).toBeVisible();
await clearButton.click();
@@ -133,7 +134,7 @@ test.describe('Home Page Note Operations', () => {
const noteItem = page.locator('.node .file').first();
await noteItem.hover();
- const clearButton = page.locator('.file .operation
a[nztooltiptitle*="Clear output"]').first();
+ const clearButton = homePage.nodeList.noteActions.clearOutput.first();
await expect(clearButton).toBeVisible();
await clearButton.click();
@@ -157,7 +158,7 @@ test.describe('Home Page Note Operations', () => {
const noteItem = page.locator('.node .file').first();
await noteItem.hover();
- const deleteButton = page.locator('.file .operation
a[nztooltiptitle*="Move note to Trash"]').first();
+ const deleteButton = homePage.nodeList.noteActions.moveToTrash.first();
await expect(deleteButton).toBeVisible();
await deleteButton.click();
@@ -176,7 +177,7 @@ test.describe('Home Page Note Operations', () => {
const noteItem = page.locator('.node .file').first();
await noteItem.hover();
- const deleteButton = page.locator('.file .operation
a[nztooltiptitle*="Move note to Trash"]').first();
+ const deleteButton = homePage.nodeList.noteActions.moveToTrash.first();
await expect(deleteButton).toBeVisible();
await deleteButton.click();
@@ -184,8 +185,6 @@ test.describe('Home Page Note Operations', () => {
if (await confirmButton.isVisible()) {
await confirmButton.click();
- await page.waitForTimeout(2000);
-
const trashFolder = page.locator('.node .folder').filter({ hasText:
'Trash' });
await expect(trashFolder).toBeVisible();
}
diff --git
a/zeppelin-web-angular/e2e/tests/home/home-page-notebook-actions.spec.ts
b/zeppelin-web-angular/e2e/tests/home/home-page-notebook-actions.spec.ts
index c323573b28..3cb9725dcb 100644
--- a/zeppelin-web-angular/e2e/tests/home/home-page-notebook-actions.spec.ts
+++ b/zeppelin-web-angular/e2e/tests/home/home-page-notebook-actions.spec.ts
@@ -10,59 +10,80 @@
* limitations under the License.
*/
-import { test } from '@playwright/test';
-import { HomePageUtil } from '../../models/home-page.util';
+import { expect, test } from '@playwright/test';
+import { HomePage } from '../../models/home-page';
import { addPageAnnotationBeforeEach, performLoginIfRequired,
waitForZeppelinReady, PAGES } from '../../utils';
addPageAnnotationBeforeEach(PAGES.WORKSPACE.HOME);
test.describe('Home Page Notebook Actions', () => {
- let homeUtil: HomePageUtil;
+ let homePage: HomePage;
test.beforeEach(async ({ page }) => {
- homeUtil = new HomePageUtil(page);
- await page.goto('/');
+ homePage = new HomePage(page);
+ await page.goto('/#/');
await waitForZeppelinReady(page);
await performLoginIfRequired(page);
});
test.describe('Given notebook list is displayed', () => {
test('When page loads Then should show notebook actions', async () => {
- await homeUtil.verifyNotebookActions();
+ await expect(homePage.nodeList.createNewNoteLink).toBeVisible();
+ await expect(homePage.nodeList.importNoteLink).toBeVisible();
+ await expect(homePage.nodeList.filterInput).toBeVisible();
+ await expect(homePage.nodeList.tree).toBeVisible();
});
- test('When refresh button is clicked Then should trigger reload with
loading state', async () => {
- await homeUtil.testNotebookRefreshLoadingState();
+ test('When refresh button is clicked Then should trigger reload with
loading state', async ({ page }) => {
+ const refreshButton = page.locator('a.refresh-note');
+ const refreshIcon = page.locator('a.refresh-note i[nz-icon]');
+
+ await expect(refreshButton).toBeVisible();
+ await expect(refreshIcon).toBeVisible();
+
+ await homePage.clickRefreshNotes();
+
+ await page.waitForTimeout(500);
+
+ await expect(refreshIcon).toBeVisible();
});
- test('When filter is used Then should filter notebook list', async () => {
- await homeUtil.testFilterFunctionality('test');
+ test('When filter is used Then should filter notebook list', async ({ page
}) => {
+ // Note (ZEPPELIN-6386):
+ // The Notebook search filter in the New UI is currently too slow,
+ // so this test is temporarily skipped. The skip will be removed
+ // once the performance issue is resolved.
+ test.skip();
+ await homePage.filterNotes('test');
+ await page.waitForLoadState('networkidle', { timeout: 15000 });
+ const filteredResults = await page.locator('nz-tree .node').count();
+ expect(filteredResults).toBeGreaterThanOrEqual(0);
});
});
test.describe('Given create new note action', () => {
- test('When create new note is clicked Then should open note creation
modal', async () => {
- try {
- await homeUtil.verifyCreateNewNoteWorkflow();
- } catch (error) {
- console.log('Note creation modal might not appear immediately');
- }
+ test('When create new note is clicked Then should open note creation
modal', async ({ page }) => {
+ await homePage.clickCreateNewNote();
+ await page.waitForSelector('zeppelin-note-create', { timeout: 10000 });
+ await expect(page.locator('zeppelin-note-create')).toBeVisible();
});
});
test.describe('Given import note action', () => {
- test('When import note is clicked Then should open import modal', async ()
=> {
- try {
- await homeUtil.verifyImportNoteWorkflow();
- } catch (error) {
- console.log('Import modal might not appear immediately');
- }
+ test('When import note is clicked Then should open import modal', async ({
page }) => {
+ await homePage.clickImportNote();
+ await page.waitForSelector('zeppelin-note-import', { timeout: 10000 });
+ await expect(page.locator('zeppelin-note-import')).toBeVisible();
});
});
test.describe('Given notebook refresh functionality', () => {
test('When refresh is triggered Then should maintain notebook list
visibility', async () => {
- await homeUtil.verifyNotebookRefreshFunctionality();
+ await homePage.clickRefreshNotes();
+ await homePage.waitForRefreshToComplete();
+ await expect(homePage.zeppelinNodeList).toBeVisible();
+ const isStillVisible = await homePage.zeppelinNodeList.isVisible();
+ expect(isStillVisible).toBe(true);
});
});
});
diff --git
a/zeppelin-web-angular/e2e/tests/notebook/published/published-paragraph.spec.ts
b/zeppelin-web-angular/e2e/tests/notebook/published/published-paragraph.spec.ts
index b3388cd087..2c35369bd0 100644
---
a/zeppelin-web-angular/e2e/tests/notebook/published/published-paragraph.spec.ts
+++
b/zeppelin-web-angular/e2e/tests/notebook/published/published-paragraph.spec.ts
@@ -13,7 +13,14 @@
import { expect, test } from '@playwright/test';
import { PublishedParagraphPage } from 'e2e/models/published-paragraph-page';
import { PublishedParagraphTestUtil } from
'../../../models/published-paragraph-page.util';
-import { addPageAnnotationBeforeEach, performLoginIfRequired,
waitForZeppelinReady, PAGES } from '../../../utils';
+import {
+ addPageAnnotationBeforeEach,
+ performLoginIfRequired,
+ waitForNotebookLinks,
+ waitForZeppelinReady,
+ PAGES,
+ createTestNotebook
+} from '../../../utils';
test.describe('Published Paragraph', () => {
addPageAnnotationBeforeEach(PAGES.WORKSPACE.PUBLISHED_PARAGRAPH);
@@ -24,24 +31,18 @@ test.describe('Published Paragraph', () => {
test.beforeEach(async ({ page }) => {
publishedParagraphPage = new PublishedParagraphPage(page);
- await page.goto('/');
+ await page.goto('/#/');
await waitForZeppelinReady(page);
await performLoginIfRequired(page);
+ await waitForNotebookLinks(page);
- // Handle the welcome modal if it appears
- const cancelButton = page.locator('.ant-modal-root button', { hasText:
'Cancel' });
- if ((await cancelButton.count()) > 0) {
- await cancelButton.click();
+ if ((await publishedParagraphPage.cancelButton.count()) > 0) {
+ await publishedParagraphPage.cancelButton.click();
+ await publishedParagraphPage.cancelButton.waitFor({ state: 'detached',
timeout: 5000 });
}
testUtil = new PublishedParagraphTestUtil(page);
- testNotebook = await testUtil.createTestNotebook();
- });
-
- test.afterEach(async () => {
- if (testNotebook?.noteId) {
- await testUtil.deleteTestNotebook(testNotebook.noteId);
- }
+ testNotebook = await createTestNotebook(page);
});
test.describe('Error Handling', () => {
@@ -50,22 +51,33 @@ test.describe('Published Paragraph', () => {
await
publishedParagraphPage.navigateToPublishedParagraph(nonExistentIds.noteId,
nonExistentIds.paragraphId);
+ // Directly assert that the modal appears and contains the expected text
const modal = page.locator('.ant-modal:has-text("Notebook not
found")').last();
- const isModalVisible = await modal.isVisible({ timeout: 10000 });
+ await expect(modal).toBeVisible({ timeout: 10000 }); // Expect the modal
to be visible
- if (isModalVisible) {
- const modalContent = await modal.textContent();
- expect(modalContent?.toLowerCase()).toContain('not found');
- } else {
- await expect(page).toHaveURL(/\/#\/$/, { timeout: 5000 });
- }
+ const modalContent = await modal.textContent();
+ expect(modalContent?.toLowerCase()).toContain('not found');
});
- test('should show error modal when paragraph does not exist in valid
notebook', async () => {
+ test('should show error modal when paragraph does not exist in valid
notebook', async ({ page }) => {
const validNoteId = testNotebook.noteId;
const nonExistentParagraphId =
testUtil.generateNonExistentIds().paragraphId;
- await testUtil.verifyNonExistentParagraphError(validNoteId,
nonExistentParagraphId);
+ await testUtil.navigateToPublishedParagraph(validNoteId,
nonExistentParagraphId);
+
+ // Expect a specific error modal
+ const errorModal = page.locator('.ant-modal', { hasText: /Paragraph Not
Found|not found|Error/i });
+ await expect(errorModal).toBeVisible({ timeout: 10000 });
+
+ // Verify modal content includes the invalid paragraph ID
+ const content = await testUtil.getErrorModalContent();
+ expect(content).toBeDefined();
+ expect(content).toContain(nonExistentParagraphId);
+
+ await testUtil.clickErrorModalOk();
+
+ // Wait for redirect to home page
+ await expect(page).toHaveURL(/\/#\/$/, { timeout: 10000 });
});
test('should redirect to home page after error modal dismissal', async ({
page }) => {
@@ -77,8 +89,7 @@ test.describe('Published Paragraph', () => {
const isModalVisible = await modal.isVisible();
if (isModalVisible) {
- const okButton = page.locator('button:has-text("OK"),
button:has-text("확인"), [role="button"]:has-text("OK")');
- await okButton.click();
+ await publishedParagraphPage.okButton.click();
await expect(page).toHaveURL(/\/#\/$/, { timeout: 10000 });
} else {
@@ -87,55 +98,193 @@ test.describe('Published Paragraph', () => {
});
});
- test.describe('Valid Paragraph Display', () => {
- test('should enter published paragraph by clicking', async () => {
- await testUtil.verifyClickLinkThisParagraphBehavior(testNotebook.noteId,
testNotebook.paragraphId);
+ test.describe('Navigation and URL Patterns', () => {
+ test('should enter published paragraph by clicking link', async ({ page })
=> {
+ const { noteId, paragraphId } = testNotebook;
+
+ // Navigate to the normal notebook view
+ await page.goto(`/#/notebook/${noteId}`);
+ await page.waitForLoadState('networkidle');
+
+ // Find the first paragraph
+ let paragraphElement =
page.locator(`zeppelin-notebook-paragraph[data-testid="${paragraphId}"]`);
+ if ((await paragraphElement.count()) === 0) {
+ paragraphElement = page.locator('zeppelin-notebook-paragraph').first();
+ }
+
+ await expect(paragraphElement).toBeVisible({ timeout: 10000 });
+
+ // Click the settings button to open the dropdown
+ const settingsButton = paragraphElement.locator('a[nz-dropdown]');
+ await settingsButton.click();
+
+ // Click "Link this paragraph" in the dropdown menu
+ const linkParagraphButton = page.locator('li.list-item:has-text("Link
this paragraph")');
+ await expect(linkParagraphButton).toBeVisible();
+
+ // Handle the new page/tab that opens
+ const [newPage] = await Promise.all([page.waitForEvent('popup'),
linkParagraphButton.click()]);
+ await newPage.waitForLoadState();
+
+ // Verify the new page URL shows published paragraph
+ await expect(newPage).toHaveURL(new
RegExp(`/notebook/${noteId}/paragraph/${paragraphId}`), { timeout: 10000 });
+
+ const codeEditor =
newPage.locator('zeppelin-notebook-paragraph-code-editor');
+ await expect(codeEditor).toBeHidden();
+
+ const controlPanel =
newPage.locator('zeppelin-notebook-paragraph-control');
+ await expect(controlPanel).toBeHidden();
});
- test('should enter published paragraph by URL', async ({ page }) => {
+ test('should enter published paragraph by direct URL navigation', async ({
page }) => {
await
page.goto(`/#/notebook/${testNotebook.noteId}/paragraph/${testNotebook.paragraphId}`);
await page.waitForLoadState('networkidle');
await
expect(page).toHaveURL(`/#/notebook/${testNotebook.noteId}/paragraph/${testNotebook.paragraphId}`,
{
timeout: 10000
});
});
+
+ test('should allow running paragraph via confirmation modal in published
mode', async ({ page }) => {
+ const { noteId, paragraphId } = testNotebook;
+
+ // Given: Navigate to a specific paragraph's published URL
+ await page.goto(`/#/notebook/${noteId}/paragraph/${paragraphId}`);
+ await page.waitForLoadState('networkidle');
+
+ // Then: URL should correctly preserve both notebook and paragraph
identifiers
+ await expect(page).toHaveURL(new
RegExp(`/notebook/${noteId}/paragraph/${paragraphId}`), { timeout: 15000 });
+
+ // Verify URL contains the specific notebook and paragraph context
+ expect(page.url()).toContain(noteId);
+ expect(page.url()).toContain(paragraphId);
+
+ // Then: Published paragraph component should be loaded (indicating
published mode is active)
+ const publishedContainer = page.locator('zeppelin-publish-paragraph');
+ await publishedContainer.waitFor({ state: 'attached', timeout: 10000 });
+
+ // Then: Confirmation modal should appear for paragraph execution
+ const modal = page.locator('.ant-modal');
+ await expect(modal).toBeVisible({ timeout: 20000 });
+
+ // Handle the execution confirmation to complete the published mode setup
+ await expect(publishedParagraphPage.runButton).toBeVisible();
+ await publishedParagraphPage.runButton.click();
+ await expect(modal).not.toBeVisible({ timeout: 10000 });
+
+ // Then: Published container should remain attached and page should be
in published mode
+ await expect(publishedContainer).toBeAttached({ timeout: 10000 });
+
+ // Verify we're in published mode by checking for the published component
+ const isPublishedMode = await page.evaluate(() =>
document.querySelector('zeppelin-publish-paragraph') !== null);
+ expect(isPublishedMode).toBe(true);
+
+ const paragraphContainer = page.locator('zeppelin-publish-paragraph');
+
+ // Published component should be present
+ await expect(paragraphContainer).toBeAttached();
+ });
});
- test('should show confirmation modal and allow running the paragraph', async
({ page }) => {
- const { noteId, paragraphId } = testNotebook;
+ test.describe('Published Mode Functionality', () => {
+ test('should hide editing controls in published mode', async ({ page }) =>
{
+ const { noteId, paragraphId } = testNotebook;
+
+ await page.goto(`/#/notebook/${noteId}/paragraph/${paragraphId}`);
+ await page.waitForLoadState('networkidle');
+
+ // In published mode, code editor and control panel should be hidden
+ const codeEditor =
page.locator('zeppelin-notebook-paragraph-code-editor');
+ const controlPanel = page.locator('zeppelin-notebook-paragraph-control');
+
+ await expect(codeEditor).toBeHidden();
+ await expect(controlPanel).toBeHidden();
+ });
+ });
+
+ test.describe('Confirmation Modal and Execution', () => {
+ test('should show confirmation modal and allow running the paragraph',
async ({ page }) => {
+ const { noteId, paragraphId } = testNotebook;
+
+ await publishedParagraphPage.navigateToNotebook(noteId);
- await publishedParagraphPage.navigateToNotebook(noteId);
+ const paragraphElement =
page.locator('zeppelin-notebook-paragraph').first();
+ const paragraphResult =
paragraphElement.locator('zeppelin-notebook-paragraph-result');
- const paragraphElement =
page.locator('zeppelin-notebook-paragraph').first();
- const paragraphResult =
paragraphElement.locator('zeppelin-notebook-paragraph-result');
+ // Only clear output if result exists
+ if (await paragraphResult.isVisible()) {
+ const settingsButton = paragraphElement.locator('a[nz-dropdown]');
+ await settingsButton.click();
- // Only clear output if result exists
- if (await paragraphResult.isVisible()) {
+ const clearOutputButton = page.locator('li.list-item:has-text("Clear
output")');
+ await clearOutputButton.click();
+ await expect(paragraphResult).toBeHidden();
+ }
+
+ await publishedParagraphPage.navigateToPublishedParagraph(noteId,
paragraphId);
+
+ await expect(page).toHaveURL(new RegExp(`/paragraph/${paragraphId}`));
+
+ const modal = publishedParagraphPage.confirmationModal;
+ await expect(modal).toBeVisible();
+
+ // Check for the enhanced modal content
+ await expect(publishedParagraphPage.modalTitle).toHaveText('Run
Paragraph?');
+
+ // Verify that the modal shows code preview
+ await
expect(publishedParagraphPage.modalBody.locator('.ant-modal-confirm-content')).toContainText(
+ 'This paragraph contains the following code:'
+ );
+ await
expect(publishedParagraphPage.modalBody.locator('.ant-modal-confirm-content')).toContainText(
+ 'Would you like to execute this code?'
+ );
+
+ // Click the Run button in the modal (OK button in confirmation modal)
+ const runButton = modal.locator('.ant-modal-confirm-btns
.ant-btn-primary');
+ await expect(runButton).toBeVisible();
+ await runButton.click();
+ await expect(modal).toBeHidden();
+ });
+
+ test('should show confirmation modal for paragraphs without results',
async ({ page }) => {
+ const { noteId, paragraphId } = testNotebook;
+
+ await publishedParagraphPage.navigateToNotebook(noteId);
+
+ const paragraphElement =
page.locator('zeppelin-notebook-paragraph').first();
const settingsButton = paragraphElement.locator('a[nz-dropdown]');
await settingsButton.click();
const clearOutputButton = page.locator('li.list-item:has-text("Clear
output")');
await clearOutputButton.click();
- await expect(paragraphResult).toBeHidden();
- }
+ await
expect(paragraphElement.locator('[data-testid="paragraph-result"]')).toBeHidden();
- await publishedParagraphPage.navigateToPublishedParagraph(noteId,
paragraphId);
+ await publishedParagraphPage.navigateToPublishedParagraph(noteId,
paragraphId);
- const modal = publishedParagraphPage.confirmationModal;
- await expect(modal).toBeVisible();
+ const modal = publishedParagraphPage.confirmationModal;
+ await expect(modal).toBeVisible();
- // Check for the new enhanced modal content
- await expect(publishedParagraphPage.modalTitle).toHaveText('Run
Paragraph?');
+ // Check for the enhanced modal content
+ await expect(publishedParagraphPage.modalTitle).toContainText('Run
Paragraph?');
- // Verify that the modal shows code preview
- const modalContent =
publishedParagraphPage.confirmationModal.locator('.ant-modal-confirm-content');
- await expect(modalContent).toContainText('This paragraph contains the
following code:');
- await expect(modalContent).toContainText('Would you like to execute this
code?');
+ // Check that code preview is shown
+ await expect(publishedParagraphPage.modalBody.first()).toContainText(
+ 'This paragraph contains the following code:'
+ );
+ await
expect(publishedParagraphPage.modalBody.first()).toContainText('Would you like
to execute this code?');
- // Click the Run button in the modal (OK button in confirmation modal)
- const runButton = modal.locator('.ant-modal-confirm-btns
.ant-btn-primary');
- await expect(runButton).toBeVisible();
- await runButton.click();
- await expect(modal).toBeHidden();
+ // Verify that the code preview area exists
+ const codePreview = publishedParagraphPage.modalBody
+ .locator('pre, code, .code-preview, .highlight, [class*="code"]')
+ .first();
+ await expect(codePreview).toBeVisible();
+
+ // Check for Run and Cancel buttons
+ await expect(publishedParagraphPage.runButton).toBeVisible();
+ await expect(publishedParagraphPage.cancelButton).toBeVisible();
+
+ // Click the Run button in the modal
+ await publishedParagraphPage.runButton.click();
+ await expect(modal).toBeHidden();
+ });
});
});
diff --git a/zeppelin-web-angular/e2e/tests/theme/dark-mode.spec.ts
b/zeppelin-web-angular/e2e/tests/theme/dark-mode.spec.ts
index 2099180632..76e9f77e61 100644
--- a/zeppelin-web-angular/e2e/tests/theme/dark-mode.spec.ts
+++ b/zeppelin-web-angular/e2e/tests/theme/dark-mode.spec.ts
@@ -11,79 +11,65 @@
*/
import { expect, test } from '@playwright/test';
-import { ThemePage } from '../../models/theme.page';
+import { DarkModePage } from '../../models/dark-mode-page';
import { addPageAnnotationBeforeEach, performLoginIfRequired,
waitForZeppelinReady, PAGES } from '../../utils';
test.describe('Dark Mode Theme Switching', () => {
addPageAnnotationBeforeEach(PAGES.SHARE.THEME_TOGGLE);
- let themePage: ThemePage;
+ let darkModePage: DarkModePage;
test.beforeEach(async ({ page }) => {
- themePage = new ThemePage(page);
- await page.goto('/');
+ darkModePage = new DarkModePage(page);
+ await page.goto('/#/');
await waitForZeppelinReady(page);
// Handle authentication if shiro.ini exists
await performLoginIfRequired(page);
// Ensure a clean localStorage for each test
- await themePage.clearLocalStorage();
+ await darkModePage.clearLocalStorage();
});
- test('Scenario: User can switch to dark mode and persistence is maintained',
async ({ page, context }) => {
- let currentPage = page;
-
+ test('Scenario: User can switch to dark mode and persistence is maintained',
async ({ page }) => {
// GIVEN: User is on the main page, which starts in 'system' mode by
default (localStorage cleared).
await test.step('GIVEN the page starts in system mode', async () => {
- await themePage.assertSystemTheme(); // Robot icon for system theme
+ await darkModePage.assertSystemTheme(); // Robot icon for system theme
});
// WHEN: Explicitly set theme to light mode for the rest of the test.
await test.step('WHEN the user explicitly sets theme to light mode', async
() => {
- await themePage.setThemeInLocalStorage('light');
+ await darkModePage.setThemeInLocalStorage('light');
+ await page.waitForTimeout(500);
+ // Reload the page to apply localStorage theme changes
await page.reload();
await waitForZeppelinReady(page);
- await themePage.assertLightTheme(); // Now it should be light mode with
sun icon
+ await darkModePage.assertLightTheme(); // Now it should be light mode
with sun icon
});
// WHEN: User switches to dark mode by setting localStorage and reloading.
- await test.step('WHEN the user switches to dark mode', async () => {
- await themePage.setThemeInLocalStorage('dark');
- const newPage = await context.newPage();
- await newPage.goto(currentPage.url());
- await waitForZeppelinReady(newPage);
-
- // Update themePage to use newPage and verify dark mode
- themePage = new ThemePage(newPage);
- currentPage = newPage;
- await themePage.assertDarkTheme();
- });
-
- // AND: User refreshes the page.
- await test.step('AND the user refreshes the page', async () => {
- await currentPage.reload();
- await waitForZeppelinReady(currentPage);
- });
-
- // THEN: Dark mode is maintained after refresh.
- await test.step('THEN dark mode is maintained after refresh', async () => {
- await themePage.assertDarkTheme();
+ await test.step('WHEN the user explicitly sets theme to dark mode', async
() => {
+ await darkModePage.setThemeInLocalStorage('dark');
+ await page.waitForTimeout(500);
+ // Reload the page to apply localStorage theme changes
+ await page.reload();
+ await waitForZeppelinReady(page);
+ await darkModePage.assertDarkTheme();
});
// AND: User clicks the toggle again to switch back to light mode.
await test.step('AND the user clicks the toggle to switch back to light
mode', async () => {
- await themePage.toggleTheme();
+ await darkModePage.toggleTheme();
});
// THEN: The theme switches to system mode.
await test.step('THEN the theme switches to system mode', async () => {
- await themePage.assertSystemTheme();
+ await darkModePage.assertSystemTheme();
});
});
test('Scenario: System Theme and Local Storage Interaction', async ({ page
}) => {
// Ensure localStorage is clear for each sub-scenario
- await themePage.clearLocalStorage();
+ await darkModePage.clearLocalStorage();
await test.step('GIVEN: No localStorage, System preference is Light',
async () => {
await page.emulateMedia({ colorScheme: 'light' });
@@ -91,44 +77,44 @@ test.describe('Dark Mode Theme Switching', () => {
await waitForZeppelinReady(page);
// When no explicit theme is set, it defaults to 'system' mode
// Even in system mode with light preference, the icon should be robot
- await expect(themePage.rootElement).toHaveClass(/light/);
- await expect(themePage.rootElement).toHaveAttribute('data-theme',
'light');
- await themePage.assertSystemTheme(); // Should show robot icon
+ await expect(darkModePage.rootElement).toHaveClass(/light/);
+ await expect(darkModePage.rootElement).toHaveAttribute('data-theme',
'light');
+ await darkModePage.assertSystemTheme(); // Should show robot icon
});
await test.step('GIVEN: No localStorage, System preference is Dark
(initial system state)', async () => {
- await themePage.setThemeInLocalStorage('system');
+ await darkModePage.setThemeInLocalStorage('system');
await page.goto('/');
await waitForZeppelinReady(page);
- await themePage.assertSystemTheme(); // Robot icon for system theme
+ await darkModePage.assertSystemTheme(); // Robot icon for system theme
});
await test.step("GIVEN: localStorage is 'dark', System preference is
Light", async () => {
- await themePage.setThemeInLocalStorage('dark');
+ await darkModePage.setThemeInLocalStorage('dark');
await page.emulateMedia({ colorScheme: 'light' });
await page.goto('/');
await waitForZeppelinReady(page);
- await themePage.assertDarkTheme(); // localStorage should override system
+ await darkModePage.assertDarkTheme(); // localStorage should override
system
});
await test.step("GIVEN: localStorage is 'system', THEN: Emulate system
preference change to Light", async () => {
- await themePage.setThemeInLocalStorage('system');
+ await darkModePage.setThemeInLocalStorage('system');
await page.emulateMedia({ colorScheme: 'light' });
await page.goto('/');
await waitForZeppelinReady(page);
- await expect(themePage.rootElement).toHaveClass(/light/);
- await expect(themePage.rootElement).toHaveAttribute('data-theme',
'light');
- await themePage.assertSystemTheme(); // Robot icon for system theme
+ await expect(darkModePage.rootElement).toHaveClass(/light/);
+ await expect(darkModePage.rootElement).toHaveAttribute('data-theme',
'light');
+ await darkModePage.assertSystemTheme(); // Robot icon for system theme
});
await test.step("GIVEN: localStorage is 'system', THEN: Emulate system
preference change to Dark", async () => {
- await themePage.setThemeInLocalStorage('system');
+ await darkModePage.setThemeInLocalStorage('system');
await page.emulateMedia({ colorScheme: 'dark' });
await page.goto('/');
await waitForZeppelinReady(page);
- await expect(themePage.rootElement).toHaveClass(/dark/);
- await expect(themePage.rootElement).toHaveAttribute('data-theme',
'dark');
- await themePage.assertSystemTheme(); // Robot icon for system theme
+ await expect(darkModePage.rootElement).toHaveClass(/dark/);
+ await expect(darkModePage.rootElement).toHaveAttribute('data-theme',
'dark');
+ await darkModePage.assertSystemTheme(); // Robot icon for system theme
});
});
});
diff --git
a/zeppelin-web-angular/e2e/tests/workspace/notebook-repos/notebook-repo-item-display.spec.ts
b/zeppelin-web-angular/e2e/tests/workspace/notebook-repos/notebook-repo-item-display.spec.ts
index 342c67e7a8..6e887b1924 100644
---
a/zeppelin-web-angular/e2e/tests/workspace/notebook-repos/notebook-repo-item-display.spec.ts
+++
b/zeppelin-web-angular/e2e/tests/workspace/notebook-repos/notebook-repo-item-display.spec.ts
@@ -22,7 +22,7 @@ test.describe('Notebook Repository Item - Display Mode', ()
=> {
let firstRepoName: string;
test.beforeEach(async ({ page }) => {
- await page.goto('/');
+ await page.goto('/#/');
await waitForZeppelinReady(page);
await performLoginIfRequired(page);
notebookReposPage = new NotebookReposPage(page);
diff --git
a/zeppelin-web-angular/e2e/tests/workspace/notebook-repos/notebook-repo-item-edit.spec.ts
b/zeppelin-web-angular/e2e/tests/workspace/notebook-repos/notebook-repo-item-edit.spec.ts
index 13c870df38..1ee350c21d 100644
---
a/zeppelin-web-angular/e2e/tests/workspace/notebook-repos/notebook-repo-item-edit.spec.ts
+++
b/zeppelin-web-angular/e2e/tests/workspace/notebook-repos/notebook-repo-item-edit.spec.ts
@@ -12,7 +12,7 @@
import { expect, test } from '@playwright/test';
import { NotebookReposPage, NotebookRepoItemPage } from
'../../../models/notebook-repos-page';
-import { NotebookRepoItemUtil } from
'../../../models/notebook-repos-page.util';
+import { NotebookRepoItemUtil } from '../../../models/notebook-repo-item.util';
import { addPageAnnotationBeforeEach, performLoginIfRequired,
waitForZeppelinReady, PAGES } from '../../../utils';
test.describe('Notebook Repository Item - Edit Mode', () => {
@@ -24,7 +24,7 @@ test.describe('Notebook Repository Item - Edit Mode', () => {
let firstRepoName: string;
test.beforeEach(async ({ page }) => {
- await page.goto('/');
+ await page.goto('/#/');
await waitForZeppelinReady(page);
await performLoginIfRequired(page);
notebookReposPage = new NotebookReposPage(page);
@@ -66,22 +66,13 @@ test.describe('Notebook Repository Item - Edit Mode', () =>
{
});
test('should reset form when cancel is clicked', async () => {
- const settingRows = await repoItemPage.settingRows.count();
- if (settingRows === 0) {
- test.skip();
- return;
- }
-
const firstRow = repoItemPage.settingRows.first();
const settingName = (await firstRow.locator('td').first().textContent())
|| '';
const originalValue = await repoItemPage.getSettingValue(settingName);
await repoItemPage.clickEdit();
- const isInputVisible = await repoItemPage.isInputVisible(settingName);
- if (isInputVisible) {
- await repoItemPage.fillSettingInput(settingName, 'temp-value');
- }
+ await repoItemPage.fillSettingInput(settingName, 'temp-value');
await repoItemPage.clickCancel();
diff --git
a/zeppelin-web-angular/e2e/tests/workspace/notebook-repos/notebook-repo-item-form-validation.spec.ts
b/zeppelin-web-angular/e2e/tests/workspace/notebook-repos/notebook-repo-item-form-validation.spec.ts
index 51f5d232c1..aedf7e1675 100644
---
a/zeppelin-web-angular/e2e/tests/workspace/notebook-repos/notebook-repo-item-form-validation.spec.ts
+++
b/zeppelin-web-angular/e2e/tests/workspace/notebook-repos/notebook-repo-item-form-validation.spec.ts
@@ -22,7 +22,7 @@ test.describe('Notebook Repository Item - Form Validation',
() => {
let firstRepoName: string;
test.beforeEach(async ({ page }) => {
- await page.goto('/');
+ await page.goto('/#/');
await waitForZeppelinReady(page);
await performLoginIfRequired(page);
notebookReposPage = new NotebookReposPage(page);
@@ -34,58 +34,32 @@ test.describe('Notebook Repository Item - Form Validation',
() => {
});
test('should disable save button when form is invalid', async () => {
- const settingRows = await repoItemPage.settingRows.count();
- if (settingRows === 0) {
- test.skip();
- return;
- }
-
await repoItemPage.clickEdit();
const firstRow = repoItemPage.settingRows.first();
const settingName = (await firstRow.locator('td').first().textContent())
|| '';
- const isInputVisible = await repoItemPage.isInputVisible(settingName);
- if (isInputVisible) {
- await repoItemPage.fillSettingInput(settingName, '');
+ await repoItemPage.fillSettingInput(settingName, '');
- const isSaveEnabled = await repoItemPage.isSaveButtonEnabled();
- expect(isSaveEnabled).toBe(false);
- } else {
- test.skip();
- }
+ const isSaveEnabled = await repoItemPage.isSaveButtonEnabled();
+ expect(isSaveEnabled).toBe(false);
});
test('should enable save button when form is valid', async () => {
- const settingRows = await repoItemPage.settingRows.count();
- if (settingRows === 0) {
- test.skip();
- return;
- }
-
await repoItemPage.clickEdit();
const firstRow = repoItemPage.settingRows.first();
const settingName = (await firstRow.locator('td').first().textContent())
|| '';
- const isInputVisible = await repoItemPage.isInputVisible(settingName);
- if (isInputVisible) {
- const originalValue = await
repoItemPage.getSettingInputValue(settingName);
- await repoItemPage.fillSettingInput(settingName, originalValue ||
'valid-value');
+ const originalValue = await repoItemPage.getSettingInputValue(settingName);
+ await repoItemPage.fillSettingInput(settingName, originalValue ||
'valid-value');
- const isSaveEnabled = await repoItemPage.isSaveButtonEnabled();
- expect(isSaveEnabled).toBe(true);
- } else {
- test.skip();
- }
+ const isSaveEnabled = await repoItemPage.isSaveButtonEnabled();
+ expect(isSaveEnabled).toBe(true);
});
test('should validate required fields on form controls', async () => {
const settingRows = await repoItemPage.settingRows.count();
- if (settingRows === 0) {
- test.skip();
- return;
- }
await repoItemPage.clickEdit();
diff --git
a/zeppelin-web-angular/e2e/tests/workspace/notebook-repos/notebook-repo-item-settings.spec.ts
b/zeppelin-web-angular/e2e/tests/workspace/notebook-repos/notebook-repo-item-settings.spec.ts
index e25fbfd911..68cc608bb3 100644
---
a/zeppelin-web-angular/e2e/tests/workspace/notebook-repos/notebook-repo-item-settings.spec.ts
+++
b/zeppelin-web-angular/e2e/tests/workspace/notebook-repos/notebook-repo-item-settings.spec.ts
@@ -22,7 +22,7 @@ test.describe('Notebook Repository Item - Settings', () => {
let firstRepoName: string;
test.beforeEach(async ({ page }) => {
- await page.goto('/');
+ await page.goto('/#/');
await waitForZeppelinReady(page);
await performLoginIfRequired(page);
notebookReposPage = new NotebookReposPage(page);
@@ -48,10 +48,6 @@ test.describe('Notebook Repository Item - Settings', () => {
test('should show input controls for INPUT type settings in edit mode',
async () => {
const settingRows = await repoItemPage.settingRows.count();
- if (settingRows === 0) {
- test.skip();
- return;
- }
await repoItemPage.clickEdit();
@@ -70,10 +66,6 @@ test.describe('Notebook Repository Item - Settings', () => {
test('should show dropdown controls for DROPDOWN type settings in edit
mode', async () => {
const settingRows = await repoItemPage.settingRows.count();
- if (settingRows === 0) {
- test.skip();
- return;
- }
await repoItemPage.clickEdit();
@@ -91,14 +83,9 @@ test.describe('Notebook Repository Item - Settings', () => {
test('should update input value in edit mode', async () => {
const settingRows = await repoItemPage.settingRows.count();
- if (settingRows === 0) {
- test.skip();
- return;
- }
await repoItemPage.clickEdit();
- let foundInput = false;
for (let i = 0; i < settingRows; i++) {
const row = repoItemPage.settingRows.nth(i);
const settingName = (await row.locator('td').first().textContent()) ||
'';
@@ -109,23 +96,12 @@ test.describe('Notebook Repository Item - Settings', () =>
{
await repoItemPage.fillSettingInput(settingName, testValue);
const inputValue = await
repoItemPage.getSettingInputValue(settingName);
expect(inputValue).toBe(testValue);
- foundInput = true;
break;
}
}
-
- if (!foundInput) {
- test.skip();
- }
});
test('should display setting name and value in display mode', async () => {
- const settingRows = await repoItemPage.settingRows.count();
- if (settingRows === 0) {
- test.skip();
- return;
- }
-
const firstRow = repoItemPage.settingRows.first();
const nameCell = firstRow.locator('td').first();
const valueCell = firstRow.locator('td').nth(1);
diff --git
a/zeppelin-web-angular/e2e/tests/workspace/notebook-repos/notebook-repo-item-workflow.spec.ts
b/zeppelin-web-angular/e2e/tests/workspace/notebook-repos/notebook-repo-item-workflow.spec.ts
index a765eb82dd..52f3e42909 100644
---
a/zeppelin-web-angular/e2e/tests/workspace/notebook-repos/notebook-repo-item-workflow.spec.ts
+++
b/zeppelin-web-angular/e2e/tests/workspace/notebook-repos/notebook-repo-item-workflow.spec.ts
@@ -12,7 +12,7 @@
import { expect, test } from '@playwright/test';
import { NotebookReposPage, NotebookRepoItemPage } from
'../../../models/notebook-repos-page';
-import { NotebookRepoItemUtil } from
'../../../models/notebook-repos-page.util';
+import { NotebookRepoItemUtil } from '../../../models/notebook-repo-item.util';
import { addPageAnnotationBeforeEach, performLoginIfRequired,
waitForZeppelinReady, PAGES } from '../../../utils';
test.describe('Notebook Repository Item - Edit Workflow', () => {
@@ -24,7 +24,7 @@ test.describe('Notebook Repository Item - Edit Workflow', ()
=> {
let firstRepoName: string;
test.beforeEach(async ({ page }) => {
- await page.goto('/');
+ await page.goto('/#/');
await waitForZeppelinReady(page);
await performLoginIfRequired(page);
notebookReposPage = new NotebookReposPage(page);
@@ -36,19 +36,14 @@ test.describe('Notebook Repository Item - Edit Workflow',
() => {
repoItemUtil = new NotebookRepoItemUtil(page, firstRepoName);
});
- test('should complete full edit workflow with save', async ({ page }) => {
+ test('should complete full edit workflow with save', async () => {
const settingRows = await repoItemPage.settingRows.count();
- if (settingRows === 0) {
- test.skip();
- return;
- }
await repoItemUtil.verifyDisplayMode();
await repoItemPage.clickEdit();
await repoItemUtil.verifyEditMode();
- let foundSetting = false;
for (let i = 0; i < settingRows; i++) {
const row = repoItemPage.settingRows.nth(i);
const settingName = (await row.locator('td').first().textContent()) ||
'';
@@ -57,33 +52,19 @@ test.describe('Notebook Repository Item - Edit Workflow',
() => {
if (isInputVisible) {
const originalValue = await
repoItemPage.getSettingInputValue(settingName);
await repoItemPage.fillSettingInput(settingName, originalValue ||
'test-value');
- foundSetting = true;
break;
}
}
- if (!foundSetting) {
- test.skip();
- return;
- }
-
const isSaveEnabled = await repoItemPage.isSaveButtonEnabled();
expect(isSaveEnabled).toBe(true);
await repoItemPage.clickSave();
- await page.waitForTimeout(1000);
-
await repoItemUtil.verifyDisplayMode();
});
test('should complete full edit workflow with cancel', async () => {
- const settingRows = await repoItemPage.settingRows.count();
- if (settingRows === 0) {
- test.skip();
- return;
- }
-
await repoItemUtil.verifyDisplayMode();
const firstRow = repoItemPage.settingRows.first();
@@ -93,13 +74,7 @@ test.describe('Notebook Repository Item - Edit Workflow', ()
=> {
await repoItemPage.clickEdit();
await repoItemUtil.verifyEditMode();
- const isInputVisible = await repoItemPage.isInputVisible(settingName);
- if (isInputVisible) {
- await repoItemPage.fillSettingInput(settingName, 'temp-modified-value');
- } else {
- test.skip();
- return;
- }
+ await repoItemPage.fillSettingInput(settingName, 'temp-modified-value');
await repoItemPage.clickCancel();
await repoItemUtil.verifyDisplayMode();
diff --git
a/zeppelin-web-angular/e2e/tests/workspace/notebook-repos/notebook-repos-page-structure.spec.ts
b/zeppelin-web-angular/e2e/tests/workspace/notebook-repos/notebook-repos-page-structure.spec.ts
index 957a7a8a3d..747037ef47 100644
---
a/zeppelin-web-angular/e2e/tests/workspace/notebook-repos/notebook-repos-page-structure.spec.ts
+++
b/zeppelin-web-angular/e2e/tests/workspace/notebook-repos/notebook-repos-page-structure.spec.ts
@@ -12,26 +12,23 @@
import { expect, test } from '@playwright/test';
import { NotebookReposPage } from '../../../models/notebook-repos-page';
-import { NotebookReposPageUtil } from
'../../../models/notebook-repos-page.util';
import { addPageAnnotationBeforeEach, performLoginIfRequired,
waitForZeppelinReady, PAGES } from '../../../utils';
test.describe('Notebook Repository Page - Structure', () => {
addPageAnnotationBeforeEach(PAGES.WORKSPACE.NOTEBOOK_REPOS);
let notebookReposPage: NotebookReposPage;
- let notebookReposUtil: NotebookReposPageUtil;
test.beforeEach(async ({ page }) => {
- await page.goto('/');
+ await page.goto('/#/');
await waitForZeppelinReady(page);
await performLoginIfRequired(page);
notebookReposPage = new NotebookReposPage(page);
- notebookReposUtil = new NotebookReposPageUtil(page);
await notebookReposPage.navigate();
});
test('should display page header with correct title and description', async
() => {
- await expect(notebookReposPage.pageHeader).toBeVisible();
+ await expect(notebookReposPage.zeppelinPageHeader).toBeVisible();
await expect(notebookReposPage.pageDescription).toBeVisible();
});
@@ -42,10 +39,6 @@ test.describe('Notebook Repository Page - Structure', () => {
test('should display all repository items', async () => {
const count = await notebookReposPage.getRepositoryItemCount();
- if (count === 0) {
- test.skip();
- return;
- }
- await notebookReposUtil.verifyAllRepositoriesRendered();
+ expect(count).toBeGreaterThan(0);
});
});
diff --git a/zeppelin-web-angular/e2e/tests/workspace/workspace-main.spec.ts
b/zeppelin-web-angular/e2e/tests/workspace/workspace-main.spec.ts
index c6292cbaec..a3e42474c0 100644
--- a/zeppelin-web-angular/e2e/tests/workspace/workspace-main.spec.ts
+++ b/zeppelin-web-angular/e2e/tests/workspace/workspace-main.spec.ts
@@ -10,58 +10,57 @@
* limitations under the License.
*/
-import { test } from '@playwright/test';
-import { WorkspaceTestUtil } from '../../models/workspace-page.util';
-import { addPageAnnotationBeforeEach, PAGES } from '../../utils';
+import { expect, test } from '@playwright/test';
+import { WorkspacePage } from 'e2e/models/workspace-page';
+import { WorkspaceUtil } from '../../models/workspace-page.util';
+import { addPageAnnotationBeforeEach, PAGES, performLoginIfRequired,
waitForZeppelinReady } from '../../utils';
addPageAnnotationBeforeEach(PAGES.WORKSPACE.MAIN);
test.describe('Workspace Main Component', () => {
- let workspaceUtil: WorkspaceTestUtil;
+ let workspaceUtil: WorkspaceUtil;
+ let workspacePage: WorkspacePage;
test.beforeEach(async ({ page }) => {
- workspaceUtil = new WorkspaceTestUtil(page);
+ await page.goto('/#/');
+ await waitForZeppelinReady(page);
+ await performLoginIfRequired(page);
+
+ workspacePage = new WorkspacePage(page);
+ workspaceUtil = new WorkspaceUtil(page);
});
test.describe('Given user accesses workspace container', () => {
- test('When workspace loads Then should display main container structure',
async () => {
- await workspaceUtil.navigateAndWaitForLoad();
+ test('When workspace loads Then should display main container structure',
async ({ page }) => {
+ await expect(workspacePage.zeppelinWorkspace).toBeVisible();
+ await expect(workspacePage.routerOutlet).toBeAttached();
- await workspaceUtil.verifyWorkspaceLayout();
- await workspaceUtil.verifyWorkspaceContainer();
+ await expect(workspacePage.zeppelinWorkspace).toBeVisible();
+ const contentElements = await page.locator('.content').count();
+ expect(contentElements).toBeGreaterThan(0);
});
test('When workspace loads Then should display header component', async ()
=> {
- await workspaceUtil.navigateAndWaitForLoad();
-
await workspaceUtil.verifyHeaderVisibility(true);
});
test('When workspace loads Then should activate router outlet', async ()
=> {
- await workspaceUtil.navigateAndWaitForLoad();
-
await workspaceUtil.verifyRouterOutletActivation();
});
test('When component activates Then should trigger onActivate event',
async () => {
- await workspaceUtil.navigateAndWaitForLoad();
-
await workspaceUtil.waitForComponentActivation();
});
});
test.describe('Given workspace header visibility', () => {
test('When not in publish mode Then should show header', async () => {
- await workspaceUtil.navigateAndWaitForLoad();
-
await workspaceUtil.verifyHeaderVisibility(true);
});
});
test.describe('Given router outlet functionality', () => {
test('When navigating to workspace Then should load child components',
async () => {
- await workspaceUtil.navigateAndWaitForLoad();
-
await workspaceUtil.verifyRouterOutletActivation();
await workspaceUtil.waitForComponentActivation();
});
diff --git a/zeppelin-web-angular/e2e/utils.ts
b/zeppelin-web-angular/e2e/utils.ts
index bc57c35352..dab04a1325 100644
--- a/zeppelin-web-angular/e2e/utils.ts
+++ b/zeppelin-web-angular/e2e/utils.ts
@@ -374,7 +374,7 @@ const navigateViaHomePageFallback = async (page: Page,
baseNotebookName: string)
};
const extractFirstParagraphId = async (page: Page): Promise<string> => {
- await page.locator('zeppelin-notebook-paragraph').first().waitFor({ state:
'visible', timeout: 10000 });
+ await page.locator('zeppelin-notebook-paragraph').first().waitFor({ state:
'visible', timeout: 20000 });
const paragraphContainer =
page.locator('zeppelin-notebook-paragraph').first();
const dropdownTrigger = paragraphContainer.locator('a[nz-dropdown]');