This is an automated email from the ASF dual-hosted git repository.
chanholee pushed a commit to branch branch-0.12
in repository https://gitbox.apache.org/repos/asf/zeppelin.git
The following commit(s) were added to refs/heads/branch-0.12 by this push:
new cf894ffec4 [ZEPPELIN-6373] Add E2E tests about share area
cf894ffec4 is described below
commit cf894ffec487f2727f3e2ccbd3c6376ba07d205c
Author: YONGJAE LEE(이용재) <[email protected]>
AuthorDate: Thu Feb 26 09:52:16 2026 +0900
[ZEPPELIN-6373] Add E2E tests about share area
### What is this PR for?
ABOUT_ZEPPELIN:'src/app/share/about-zeppelin/about-zeppelin.component',
CODE_EDITOR:'src/app/share/code-editor/code-editor.component',
FOLDER_RENAME:'src/app/share/folder-rename/folder-rename.component',
HEADER:'src/app/share/header/header.component',
NODE_LIST:'src/app/share/node-list/node-list.component',
NOTE_CREATE:'src/app/share/note-create/note-create.component',
NOTE_IMPORT:'src/app/share/note-import/note-import.component',
NOTE_RENAME:'src/app/share/note-rename/note-rename.component',
NOTE_TOC:'src/app/share/note-toc/note-toc.component',
PAGE_HEADER:'src/app/share/page-header/page-header.component',
RESIZE_HANDLE:'src/app/share/resize-handle/resize-handle.component',
SHORTCUT:'src/app/share/shortcut/shortcut.component',
SPIN:'src/app/share/spin/spin.component',
THEME_TOGGLE:'src/app/share/theme-toggle/theme-toggle.component'
### What type of PR is it?
Improvement
*Please leave your type of PR only*
### Todos
### What is the Jira issue?
ZEPPELIN-6373
### 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 #5114 from dididy/e2e/share.
Signed-off-by: ChanHo Lee <[email protected]>
(cherry picked from commit cf766dbcbc0373be73424b0ce8859d5bb747c948)
Signed-off-by: ChanHo Lee <[email protected]>
---
.../e2e/models/about-zeppelin-modal.ts | 57 +++++++++++
zeppelin-web-angular/e2e/models/header-page.ts | 113 +++++++++++++++++++++
.../e2e/models/header-page.util.ts | 109 ++++++++++++++++++++
zeppelin-web-angular/e2e/models/node-list-page.ts | 78 ++++++++++++++
.../e2e/models/note-create-modal.ts | 54 ++++++++++
.../e2e/models/note-create-modal.util.ts | 40 ++++++++
.../e2e/models/note-import-modal.ts | 95 +++++++++++++++++
.../tests/home/home-page-note-operations.spec.ts | 12 +--
.../about-zeppelin/about-zeppelin-modal.spec.ts | 69 +++++++++++++
.../tests/share/header/header-navigation.spec.ts | 73 +++++++++++++
.../e2e/tests/share/header/header-search.spec.ts | 42 ++++++++
.../node-list/node-list-functionality.spec.ts | 113 +++++++++++++++++++++
.../share/note-create/note-create-modal.spec.ts | 108 ++++++++++++++++++++
.../share/note-import/note-import-modal.spec.ts | 105 +++++++++++++++++++
zeppelin-web-angular/e2e/utils.ts | 2 +-
15 files changed, 1063 insertions(+), 7 deletions(-)
diff --git a/zeppelin-web-angular/e2e/models/about-zeppelin-modal.ts
b/zeppelin-web-angular/e2e/models/about-zeppelin-modal.ts
new file mode 100644
index 0000000000..d5a44add77
--- /dev/null
+++ b/zeppelin-web-angular/e2e/models/about-zeppelin-modal.ts
@@ -0,0 +1,57 @@
+/*
+ * 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 { Locator, Page } from '@playwright/test';
+import { BasePage } from './base-page';
+
+export class AboutZeppelinModal extends BasePage {
+ readonly modal: Locator;
+ readonly modalTitle: Locator;
+ readonly closeButton: Locator;
+ readonly logo: Locator;
+ readonly heading: Locator;
+ readonly versionText: Locator;
+ readonly getInvolvedLink: Locator;
+ readonly licenseLink: Locator;
+
+ constructor(page: Page) {
+ super(page);
+ this.modal = page.locator('[role="dialog"]').filter({ has:
page.getByText('About Zeppelin') });
+ this.modalTitle = page.locator('.ant-modal-title', { hasText: 'About
Zeppelin' });
+ this.closeButton = page.getByRole('button', { name: 'Close' });
+ this.logo = page.locator('img[alt="Apache Zeppelin"]');
+ this.heading = page.locator('h3', { hasText: 'Apache Zeppelin' });
+ this.versionText = page.locator('.about-version');
+ this.getInvolvedLink = page.getByRole('link', { name: 'Get involved!' });
+ this.licenseLink = page.getByRole('link', { name: 'Licensed under the
Apache License, Version 2.0' });
+ }
+
+ async close(): Promise<void> {
+ await this.closeButton.click();
+ }
+
+ async getVersionText(): Promise<string> {
+ return (await this.versionText.textContent()) || '';
+ }
+
+ async isLogoVisible(): Promise<boolean> {
+ return this.logo.isVisible();
+ }
+
+ async getGetInvolvedHref(): Promise<string | null> {
+ return this.getInvolvedLink.getAttribute('href');
+ }
+
+ async getLicenseHref(): Promise<string | null> {
+ return this.licenseLink.getAttribute('href');
+ }
+}
diff --git a/zeppelin-web-angular/e2e/models/header-page.ts
b/zeppelin-web-angular/e2e/models/header-page.ts
new file mode 100644
index 0000000000..2f5c1c496f
--- /dev/null
+++ b/zeppelin-web-angular/e2e/models/header-page.ts
@@ -0,0 +1,113 @@
+/*
+ * 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 { Locator, Page } from '@playwright/test';
+import { BasePage } from './base-page';
+
+export class HeaderPage extends BasePage {
+ readonly header: Locator;
+ readonly brandLogo: Locator;
+ readonly brandLink: Locator;
+ readonly notebookMenuItem: Locator;
+ readonly notebookDropdownTrigger: Locator;
+ readonly notebookDropdown: Locator;
+ readonly jobMenuItem: Locator;
+ readonly userDropdownTrigger: Locator;
+ readonly userBadge: Locator;
+ readonly searchInput: Locator;
+ readonly themeToggleButton: Locator;
+
+ readonly userMenuItems: {
+ aboutZeppelin: Locator;
+ interpreter: Locator;
+ notebookRepos: Locator;
+ credential: Locator;
+ configuration: Locator;
+ logout: Locator;
+ switchToClassicUI: Locator;
+ };
+
+ constructor(page: Page) {
+ super(page);
+ this.header = page.locator('.header');
+ this.brandLogo = page.locator('.header .brand .logo');
+ this.brandLink = page.locator('.header .brand');
+ this.notebookMenuItem = page.locator('[nz-menu-item]').filter({ hasText:
'Notebook' });
+ this.notebookDropdownTrigger = page.locator('.node-list-trigger');
+ this.notebookDropdown =
page.locator('zeppelin-node-list.ant-dropdown-menu');
+ this.jobMenuItem = page.getByRole('link', { name: 'Job' });
+ this.userDropdownTrigger = page.locator('.header .user .status');
+ this.userBadge = page.locator('.header .user nz-badge');
+ this.searchInput = page.locator('.header .search input[type="text"]');
+ this.themeToggleButton = page.locator('zeppelin-theme-toggle button');
+
+ this.userMenuItems = {
+ aboutZeppelin: page.getByText('About Zeppelin', { exact: true }),
+ interpreter: page.getByRole('link', { name: 'Interpreter' }),
+ notebookRepos: page.getByRole('link', { name: 'Notebook Repos' }),
+ credential: page.getByRole('link', { name: 'Credential' }),
+ configuration: page.getByRole('link', { name: 'Configuration' }),
+ logout: page.getByText('Logout', { exact: true }),
+ switchToClassicUI: page.getByRole('link', { name: 'Switch to Classic UI'
})
+ };
+ }
+
+ async clickBrandLogo(): Promise<void> {
+ await this.brandLink.waitFor({ state: 'visible', timeout: 10000 });
+ await this.brandLink.click();
+ }
+
+ async clickNotebookMenu(): Promise<void> {
+ await this.notebookDropdownTrigger.waitFor({ state: 'visible', timeout:
10000 });
+ await this.notebookDropdownTrigger.click();
+ }
+
+ async clickJobMenu(): Promise<void> {
+ await this.jobMenuItem.waitFor({ state: 'visible', timeout: 10000 });
+ await this.jobMenuItem.click();
+ }
+
+ async clickUserDropdown(): Promise<void> {
+ await this.userDropdownTrigger.waitFor({ state: 'visible', timeout: 10000
});
+ await this.userDropdownTrigger.click();
+ }
+
+ async clickAboutZeppelin(): Promise<void> {
+ await this.userMenuItems.aboutZeppelin.click();
+ }
+
+ async clickInterpreter(): Promise<void> {
+ await this.userMenuItems.interpreter.click();
+ }
+
+ async clickNotebookRepos(): Promise<void> {
+ await this.userMenuItems.notebookRepos.click();
+ }
+
+ async clickCredential(): Promise<void> {
+ await this.userMenuItems.credential.click();
+ }
+
+ async clickConfiguration(): Promise<void> {
+ await this.userMenuItems.configuration.click();
+ }
+
+ async getUsernameText(): Promise<string> {
+ return (await this.userBadge.textContent()) || '';
+ }
+
+ async searchNote(query: string): Promise<void> {
+ await this.searchInput.waitFor({ state: 'visible', timeout: 10000 });
+ await this.searchInput.fill(query);
+ await this.page.keyboard.press('Enter');
+ }
+}
diff --git a/zeppelin-web-angular/e2e/models/header-page.util.ts
b/zeppelin-web-angular/e2e/models/header-page.util.ts
new file mode 100644
index 0000000000..14a369eb0e
--- /dev/null
+++ b/zeppelin-web-angular/e2e/models/header-page.util.ts
@@ -0,0 +1,109 @@
+/*
+ * 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 { HeaderPage } from './header-page';
+import { NodeListPage } from './node-list-page';
+
+export class HeaderPageUtil {
+ constructor(
+ private readonly page: Page,
+ private readonly headerPage: HeaderPage
+ ) {}
+
+ async verifyHeaderIsDisplayed(): Promise<void> {
+ await expect(this.headerPage.header).toBeVisible();
+ await expect(this.headerPage.brandLogo).toBeVisible();
+ await expect(this.headerPage.notebookMenuItem).toBeVisible();
+ await expect(this.headerPage.jobMenuItem).toBeVisible();
+ await expect(this.headerPage.userDropdownTrigger).toBeVisible();
+ await expect(this.headerPage.searchInput).toBeVisible();
+ await expect(this.headerPage.themeToggleButton).toBeVisible();
+ }
+
+ async verifyNavigationToHomePage(): Promise<void> {
+ await this.headerPage.clickBrandLogo();
+ await this.page.waitForURL(/\/(#\/)?$/);
+ const url = this.page.url();
+ expect(url).toMatch(/\/(#\/)?$/);
+ }
+
+ async verifyNavigationToJobManager(): Promise<void> {
+ await this.headerPage.clickJobMenu();
+ await this.page.waitForURL(/jobmanager/);
+ expect(this.page.url()).toContain('jobmanager');
+ }
+
+ async verifyUserDropdownOpens(): Promise<void> {
+ await this.headerPage.clickUserDropdown();
+ await expect(this.headerPage.userMenuItems.aboutZeppelin).toBeVisible();
+ }
+
+ async verifyNotebookDropdownOpens(): Promise<void> {
+ await this.headerPage.clickNotebookMenu();
+ await expect(this.headerPage.notebookDropdown).toBeVisible();
+
+ const nodeList = new NodeListPage(this.page);
+ await expect(nodeList.createNewNoteButton).toBeVisible();
+ }
+
+ async verifySearchNavigation(query: string): Promise<void> {
+ await this.headerPage.searchNote(query);
+ await this.page.waitForURL(/search/);
+ expect(this.page.url()).toContain('search');
+ expect(this.page.url()).toContain(query);
+ }
+
+ async verifyUserMenuItemsVisible(isLoggedIn: boolean): Promise<void> {
+ await this.headerPage.clickUserDropdown();
+ await expect(this.headerPage.userMenuItems.aboutZeppelin).toBeVisible();
+ await expect(this.headerPage.userMenuItems.interpreter).toBeVisible();
+ await expect(this.headerPage.userMenuItems.notebookRepos).toBeVisible();
+ await expect(this.headerPage.userMenuItems.credential).toBeVisible();
+ await expect(this.headerPage.userMenuItems.configuration).toBeVisible();
+ await
expect(this.headerPage.userMenuItems.switchToClassicUI).toBeVisible();
+
+ if (isLoggedIn) {
+ const username = await this.headerPage.getUsernameText();
+ expect(username).not.toBe('anonymous');
+ await expect(this.headerPage.userMenuItems.logout).toBeVisible();
+ }
+ }
+
+ async navigateToInterpreterSettings(): Promise<void> {
+ await this.headerPage.clickUserDropdown();
+ await this.headerPage.clickInterpreter();
+ await this.page.waitForURL(/interpreter/);
+ expect(this.page.url()).toContain('interpreter');
+ }
+
+ async navigateToNotebookRepos(): Promise<void> {
+ await this.headerPage.clickUserDropdown();
+ await this.headerPage.clickNotebookRepos();
+ await this.page.waitForURL(/notebook-repos/);
+ expect(this.page.url()).toContain('notebook-repos');
+ }
+
+ async navigateToCredential(): Promise<void> {
+ await this.headerPage.clickUserDropdown();
+ await this.headerPage.clickCredential();
+ await this.page.waitForURL(/credential/);
+ expect(this.page.url()).toContain('credential');
+ }
+
+ async navigateToConfiguration(): Promise<void> {
+ await this.headerPage.clickUserDropdown();
+ await this.headerPage.clickConfiguration();
+ await this.page.waitForURL(/configuration/);
+ expect(this.page.url()).toContain('configuration');
+ }
+}
diff --git a/zeppelin-web-angular/e2e/models/node-list-page.ts
b/zeppelin-web-angular/e2e/models/node-list-page.ts
new file mode 100644
index 0000000000..17bd93de33
--- /dev/null
+++ b/zeppelin-web-angular/e2e/models/node-list-page.ts
@@ -0,0 +1,78 @@
+/*
+ * 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 { Locator, Page } from '@playwright/test';
+import { BasePage } from './base-page';
+
+export class NodeListPage extends BasePage {
+ readonly nodeListContainer: Locator;
+ readonly importNoteButton: Locator;
+ readonly createNewNoteButton: Locator;
+ readonly filterInput: Locator;
+ readonly treeView: Locator;
+ readonly notes: Locator;
+ readonly trashFolder: Locator;
+
+ constructor(page: Page) {
+ super(page);
+ this.nodeListContainer = page.locator('zeppelin-node-list');
+ this.importNoteButton = page.getByText('Import Note', { exact: true
}).first();
+ this.createNewNoteButton = page.getByText('Create new Note', { exact: true
}).first();
+ this.filterInput = page.locator('zeppelin-node-list
input[placeholder*="Filter"]');
+ this.treeView = page.locator('zeppelin-node-list nz-tree');
+ this.notes = page.locator('nz-tree-node').filter({ has:
page.locator('.ant-tree-node-content-wrapper .file') });
+ this.trashFolder = page.locator('nz-tree-node').filter({ hasText: '~Trash'
});
+ }
+
+ async clickImportNote(): Promise<void> {
+ await this.importNoteButton.click();
+ }
+
+ async clickCreateNewNote(): Promise<void> {
+ await this.createNewNoteButton.click();
+ }
+
+ getFolderByName(folderName: string): Locator {
+ return this.page.locator('nz-tree-node').filter({ hasText: folderName
}).first();
+ }
+
+ getNoteByName(noteName: string): Locator {
+ return this.page.locator('nz-tree-node').filter({ hasText: noteName
}).first();
+ }
+
+ async clickNote(noteName: string): Promise<void> {
+ const note = this.getNoteByName(noteName);
+ // Target the specific link that navigates to the notebook (has href with
"#/notebook/")
+ const noteLink = note.locator('a[href*="#/notebook/"]');
+ await noteLink.click();
+ }
+
+ async isFilterInputVisible(): Promise<boolean> {
+ return this.filterInput.isVisible();
+ }
+
+ async isTrashFolderVisible(): Promise<boolean> {
+ return this.trashFolder.isVisible();
+ }
+
+ async getAllVisibleNoteNames(): Promise<string[]> {
+ const noteElements = await this.notes.all();
+ const names: string[] = [];
+ for (const note of noteElements) {
+ const text = await note.textContent();
+ if (text) {
+ names.push(text.trim());
+ }
+ }
+ return names;
+ }
+}
diff --git a/zeppelin-web-angular/e2e/models/note-create-modal.ts
b/zeppelin-web-angular/e2e/models/note-create-modal.ts
new file mode 100644
index 0000000000..1e1a0c4808
--- /dev/null
+++ b/zeppelin-web-angular/e2e/models/note-create-modal.ts
@@ -0,0 +1,54 @@
+/*
+ * 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 { Locator, Page } from '@playwright/test';
+import { BasePage } from './base-page';
+
+export class NoteCreateModal extends BasePage {
+ readonly modal: Locator;
+ readonly closeButton: Locator;
+ readonly noteNameInput: Locator;
+ readonly interpreterDropdown: Locator;
+ readonly folderInfoAlert: Locator;
+ readonly createButton: Locator;
+
+ constructor(page: Page) {
+ super(page);
+ this.modal = page.locator('[role="dialog"]').filter({ has:
page.locator('input[name="noteName"]') });
+ this.closeButton = page.getByRole('button', { name: 'Close' });
+ this.noteNameInput = page.locator('input[name="noteName"]');
+ this.interpreterDropdown =
page.locator('nz-select[name="defaultInterpreter"]');
+ this.folderInfoAlert = page.getByText("Use '/' to create folders");
+ this.createButton = page.getByRole('button', { name: 'Create' });
+ }
+
+ async close(): Promise<void> {
+ await this.closeButton.click();
+ }
+
+ async getNoteName(): Promise<string> {
+ return (await this.noteNameInput.inputValue()) || '';
+ }
+
+ async setNoteName(name: string): Promise<void> {
+ await this.noteNameInput.clear();
+ await this.noteNameInput.fill(name);
+ }
+
+ async clickCreate(): Promise<void> {
+ await this.createButton.click();
+ }
+
+ async isFolderInfoVisible(): Promise<boolean> {
+ return this.folderInfoAlert.isVisible();
+ }
+}
diff --git a/zeppelin-web-angular/e2e/models/note-create-modal.util.ts
b/zeppelin-web-angular/e2e/models/note-create-modal.util.ts
new file mode 100644
index 0000000000..7553325c1e
--- /dev/null
+++ b/zeppelin-web-angular/e2e/models/note-create-modal.util.ts
@@ -0,0 +1,40 @@
+/*
+ * 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 } from '@playwright/test';
+import { NoteCreateModal } from './note-create-modal';
+
+export class NoteCreateModalUtil {
+ constructor(private readonly modal: NoteCreateModal) {}
+
+ async verifyModalIsOpen(): Promise<void> {
+ await expect(this.modal.modal).toBeVisible();
+ await expect(this.modal.noteNameInput).toBeVisible();
+ await expect(this.modal.createButton).toBeVisible();
+ }
+
+ async verifyDefaultNoteName(expectedPattern: RegExp): Promise<void> {
+ const noteName = await this.modal.getNoteName();
+ expect(noteName).toMatch(expectedPattern);
+ }
+
+ async verifyFolderCreationInfo(): Promise<void> {
+ await expect(this.modal.folderInfoAlert).toBeVisible();
+ const text = await this.modal.folderInfoAlert.textContent();
+ expect(text).toContain('/');
+ }
+
+ async verifyModalClose(): Promise<void> {
+ await this.modal.close();
+ await expect(this.modal.modal).not.toBeVisible();
+ }
+}
diff --git a/zeppelin-web-angular/e2e/models/note-import-modal.ts
b/zeppelin-web-angular/e2e/models/note-import-modal.ts
new file mode 100644
index 0000000000..11db6d5da4
--- /dev/null
+++ b/zeppelin-web-angular/e2e/models/note-import-modal.ts
@@ -0,0 +1,95 @@
+/*
+ * 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 { Locator, Page } from '@playwright/test';
+import { BasePage } from './base-page';
+
+export class NoteImportModal extends BasePage {
+ readonly modal: Locator;
+ readonly modalTitle: Locator;
+ readonly closeButton: Locator;
+ readonly importAsInput: Locator;
+ readonly jsonFileTab: Locator;
+ readonly urlTab: Locator;
+ readonly uploadArea: Locator;
+ readonly uploadText: Locator;
+ readonly fileSizeLimit: Locator;
+ readonly urlInput: Locator;
+ readonly importNoteButton: Locator;
+ readonly errorAlert: Locator;
+
+ constructor(page: Page) {
+ super(page);
+ this.modal = page.locator('[role="dialog"]').filter({ has:
page.locator('input[name="noteImportName"]') });
+ this.modalTitle = page.locator('.ant-modal-title', { hasText: 'Import New
Note' });
+ this.closeButton = page.getByRole('button', { name: 'Close' });
+ this.importAsInput = page.locator('input[name="noteImportName"]');
+ this.jsonFileTab = page.getByRole('tab', { name: 'Import From JSON File'
});
+ this.urlTab = page.getByRole('tab', { name: 'Import From URL' });
+ this.uploadArea = page.locator('nz-upload[nztype="drag"]');
+ this.uploadText = page.getByText('Click or drag JSON file to this area to
upload');
+ this.fileSizeLimit = page.locator('.ant-upload-hint strong');
+ this.urlInput = page.locator('input[name="importUrl"]');
+ this.importNoteButton = page.getByRole('button', { name: 'Import Note' });
+ this.errorAlert = page.locator('nz-alert[nztype="error"]');
+ }
+
+ async close(): Promise<void> {
+ await this.closeButton.click();
+ }
+
+ async setImportAsName(name: string): Promise<void> {
+ await this.importAsInput.fill(name);
+ }
+
+ async getImportAsName(): Promise<string> {
+ return (await this.importAsInput.inputValue()) || '';
+ }
+
+ async switchToUrlTab(): Promise<void> {
+ await this.urlTab.click();
+ }
+
+ async isJsonFileTabSelected(): Promise<boolean> {
+ const ariaSelected = await this.jsonFileTab.getAttribute('aria-selected');
+ return ariaSelected === 'true';
+ }
+
+ async isUrlTabSelected(): Promise<boolean> {
+ const ariaSelected = await this.urlTab.getAttribute('aria-selected');
+ return ariaSelected === 'true';
+ }
+
+ async setImportUrl(url: string): Promise<void> {
+ await this.urlInput.fill(url);
+ }
+
+ async clickImportNote(): Promise<void> {
+ await this.importNoteButton.click();
+ }
+
+ async isImportNoteButtonDisabled(): Promise<boolean> {
+ return this.importNoteButton.isDisabled();
+ }
+
+ async getFileSizeLimit(): Promise<string> {
+ return (await this.fileSizeLimit.textContent()) || '';
+ }
+
+ async isErrorAlertVisible(): Promise<boolean> {
+ return this.errorAlert.isVisible();
+ }
+
+ async getErrorMessage(): Promise<string> {
+ return (await this.errorAlert.textContent()) || '';
+ }
+}
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 018bfbf40e..f385de5f58 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
@@ -55,18 +55,18 @@ test.describe('Home Page Note Operations', () => {
const firstNote = page.locator('.node .file').first();
await firstNote.hover();
- await expect(homePage.nodeList.noteActions.renameNote).toBeVisible();
- await expect(homePage.nodeList.noteActions.clearOutput).toBeVisible();
- await expect(homePage.nodeList.noteActions.moveToTrash).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();
// Test tooltip visibility by hovering over each icon
- await homePage.nodeList.noteActions.renameNote.hover();
+ await homePage.nodeList.noteActions.renameNote.first().hover();
await expect(page.locator('.ant-tooltip', { hasText: 'Rename note'
})).toBeVisible();
- await homePage.nodeList.noteActions.clearOutput.hover();
+ await homePage.nodeList.noteActions.clearOutput.first().hover();
await expect(page.locator('.ant-tooltip', { hasText: 'Clear output'
})).toBeVisible();
- await homePage.nodeList.noteActions.moveToTrash.hover();
+ await homePage.nodeList.noteActions.moveToTrash.first().hover();
await expect(page.locator('.ant-tooltip', { hasText: 'Move note to
Trash' })).toBeVisible();
}
});
diff --git
a/zeppelin-web-angular/e2e/tests/share/about-zeppelin/about-zeppelin-modal.spec.ts
b/zeppelin-web-angular/e2e/tests/share/about-zeppelin/about-zeppelin-modal.spec.ts
new file mode 100644
index 0000000000..2e8ab234a7
--- /dev/null
+++
b/zeppelin-web-angular/e2e/tests/share/about-zeppelin/about-zeppelin-modal.spec.ts
@@ -0,0 +1,69 @@
+/*
+ * 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 { test, expect } from '@playwright/test';
+import { HeaderPage } from '../../../models/header-page';
+import { AboutZeppelinModal } from '../../../models/about-zeppelin-modal';
+import { addPageAnnotationBeforeEach, PAGES, performLoginIfRequired,
waitForZeppelinReady } from '../../../utils';
+
+test.describe('About Zeppelin Modal', () => {
+ let headerPage: HeaderPage;
+ let aboutModal: AboutZeppelinModal;
+
+ addPageAnnotationBeforeEach(PAGES.SHARE.ABOUT_ZEPPELIN);
+
+ test.beforeEach(async ({ page }) => {
+ headerPage = new HeaderPage(page);
+ aboutModal = new AboutZeppelinModal(page);
+
+ await page.goto('/');
+ await waitForZeppelinReady(page);
+ await performLoginIfRequired(page);
+
+ await headerPage.clickUserDropdown();
+ await headerPage.clickAboutZeppelin();
+ });
+
+ test('Given user clicks About Zeppelin menu item, When modal opens, Then
modal should display all required elements', async () => {
+ await expect(aboutModal.modal).toBeVisible();
+ await expect(aboutModal.modalTitle).toBeVisible();
+ await expect(aboutModal.heading).toBeVisible();
+ await expect(aboutModal.logo).toBeVisible();
+ await expect(aboutModal.versionText).toBeVisible();
+ await expect(aboutModal.getInvolvedLink).toBeVisible();
+ await expect(aboutModal.licenseLink).toBeVisible();
+ });
+
+ test('Given About Zeppelin modal is open, When viewing version information,
Then version should be displayed', async () => {
+ const version = await aboutModal.getVersionText();
+ expect(version).toBeTruthy();
+ expect(version.length).toBeGreaterThan(0);
+ });
+
+ test('Given About Zeppelin modal is open, When checking external links, Then
links should have correct URLs', async () => {
+ const getInvolvedHref = await aboutModal.getGetInvolvedHref();
+ const licenseHref = await aboutModal.getLicenseHref();
+
+ expect(getInvolvedHref).toContain('zeppelin.apache.org');
+ expect(licenseHref).toContain('apache.org/licenses');
+ });
+
+ test('Given About Zeppelin modal is open, When clicking close button, Then
modal should close', async () => {
+ await aboutModal.close();
+ await expect(aboutModal.modal).not.toBeVisible();
+ });
+
+ test('Given About Zeppelin modal is open, When checking logo, Then logo
should be visible and properly loaded', async () => {
+ const isLogoVisible = await aboutModal.isLogoVisible();
+ expect(isLogoVisible).toBe(true);
+ });
+});
diff --git
a/zeppelin-web-angular/e2e/tests/share/header/header-navigation.spec.ts
b/zeppelin-web-angular/e2e/tests/share/header/header-navigation.spec.ts
new file mode 100644
index 0000000000..18ae43faba
--- /dev/null
+++ b/zeppelin-web-angular/e2e/tests/share/header/header-navigation.spec.ts
@@ -0,0 +1,73 @@
+/*
+ * 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 { test } from '@playwright/test';
+import { HeaderPage } from '../../../models/header-page';
+import { HeaderPageUtil } from '../../../models/header-page.util';
+import { addPageAnnotationBeforeEach, PAGES, performLoginIfRequired,
waitForZeppelinReady } from '../../../utils';
+
+test.describe('Header Navigation', () => {
+ let headerPage: HeaderPage;
+ let headerUtil: HeaderPageUtil;
+
+ addPageAnnotationBeforeEach(PAGES.SHARE.HEADER);
+
+ test.beforeEach(async ({ page }) => {
+ headerPage = new HeaderPage(page);
+ headerUtil = new HeaderPageUtil(page, headerPage);
+
+ await page.goto('/');
+ await waitForZeppelinReady(page);
+ await performLoginIfRequired(page);
+ });
+
+ test('Given user is on any page, When viewing the header, Then all header
elements should be visible', async () => {
+ await headerUtil.verifyHeaderIsDisplayed();
+ });
+
+ test('Given user is on any page, When clicking the Zeppelin logo, Then user
should navigate to home page', async () => {
+ await headerUtil.verifyNavigationToHomePage();
+ });
+
+ test('Given user is on home page, When clicking the Job menu item, Then user
should navigate to Job Manager page', async () => {
+ await headerUtil.verifyNavigationToJobManager();
+ });
+
+ test('Given user is on home page, When clicking the Notebook dropdown, Then
dropdown with node list should open', async () => {
+ await headerUtil.verifyNotebookDropdownOpens();
+ });
+
+ test('Given user is on home page, When clicking the user dropdown, Then user
menu should open', async () => {
+ await headerUtil.verifyUserDropdownOpens();
+ });
+
+ test('Given user opens user dropdown, When all menu items are displayed,
Then menu items should include settings and configuration options', async () =>
{
+ const isAnonymous = (await
headerPage.getUsernameText()).includes('anonymous');
+ await headerUtil.verifyUserMenuItemsVisible(!isAnonymous);
+ });
+
+ test('Given user opens user dropdown, When clicking Interpreter menu item,
Then user should navigate to Interpreter settings page', async () => {
+ await headerUtil.navigateToInterpreterSettings();
+ });
+
+ test('Given user opens user dropdown, When clicking Notebook Repos menu
item, Then user should navigate to Notebook Repos page', async () => {
+ await headerUtil.navigateToNotebookRepos();
+ });
+
+ test('Given user opens user dropdown, When clicking Credential menu item,
Then user should navigate to Credential page', async () => {
+ await headerUtil.navigateToCredential();
+ });
+
+ test('Given user opens user dropdown, When clicking Configuration menu item,
Then user should navigate to Configuration page', async () => {
+ await headerUtil.navigateToConfiguration();
+ });
+});
diff --git a/zeppelin-web-angular/e2e/tests/share/header/header-search.spec.ts
b/zeppelin-web-angular/e2e/tests/share/header/header-search.spec.ts
new file mode 100644
index 0000000000..171f2d5255
--- /dev/null
+++ b/zeppelin-web-angular/e2e/tests/share/header/header-search.spec.ts
@@ -0,0 +1,42 @@
+/*
+ * 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 { test, expect } from '@playwright/test';
+import { HeaderPage } from '../../../models/header-page';
+import { HeaderPageUtil } from '../../../models/header-page.util';
+import { addPageAnnotationBeforeEach, PAGES, performLoginIfRequired,
waitForZeppelinReady } from '../../../utils';
+
+test.describe('Header Search Functionality', () => {
+ let headerPage: HeaderPage;
+ let headerUtil: HeaderPageUtil;
+
+ addPageAnnotationBeforeEach(PAGES.SHARE.HEADER);
+
+ test.beforeEach(async ({ page }) => {
+ headerPage = new HeaderPage(page);
+ headerUtil = new HeaderPageUtil(page, headerPage);
+
+ await page.goto('/');
+ await waitForZeppelinReady(page);
+ await performLoginIfRequired(page);
+ });
+
+ test('Given user is on home page, When entering search query and pressing
Enter, Then user should navigate to search results page', async () => {
+ const searchQuery = 'test';
+ await headerUtil.verifySearchNavigation(searchQuery);
+ });
+
+ test('Given user is on home page, When viewing search input, Then search
input should be visible and accessible', async () => {
+ await expect(headerPage.searchInput).toBeVisible();
+ await expect(headerPage.searchInput).toBeEditable();
+ });
+});
diff --git
a/zeppelin-web-angular/e2e/tests/share/node-list/node-list-functionality.spec.ts
b/zeppelin-web-angular/e2e/tests/share/node-list/node-list-functionality.spec.ts
new file mode 100644
index 0000000000..111d01011f
--- /dev/null
+++
b/zeppelin-web-angular/e2e/tests/share/node-list/node-list-functionality.spec.ts
@@ -0,0 +1,113 @@
+/*
+ * 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 { test, expect } from '@playwright/test';
+import { HomePage } from '../../../models/home-page';
+import { NodeListPage } from '../../../models/node-list-page';
+import { addPageAnnotationBeforeEach, PAGES, performLoginIfRequired,
waitForZeppelinReady } from '../../../utils';
+
+test.describe('Node List Functionality', () => {
+ let nodeListPage: NodeListPage;
+
+ addPageAnnotationBeforeEach(PAGES.SHARE.NODE_LIST);
+
+ test.beforeEach(async ({ page }) => {
+ nodeListPage = new NodeListPage(page);
+
+ await page.goto('/');
+ await waitForZeppelinReady(page);
+ await performLoginIfRequired(page);
+ });
+
+ test('Given user is on home page, When viewing node list, Then node list
should display tree structure', async () => {
+ await expect(nodeListPage.nodeListContainer).toBeVisible();
+ await expect(nodeListPage.treeView).toBeVisible();
+ });
+
+ test('Given user is on home page, When viewing node list, Then action
buttons should be visible', async () => {
+ await expect(nodeListPage.createNewNoteButton).toBeVisible();
+ await expect(nodeListPage.importNoteButton).toBeVisible();
+ });
+
+ test('Given user is on home page, When viewing node list, Then filter input
should be visible', async () => {
+ const isFilterVisible = await nodeListPage.isFilterInputVisible();
+ expect(isFilterVisible).toBe(true);
+ });
+
+ test('Given a note has been moved to trash, When viewing node list, Then
trash folder should be visible', async ({
+ page
+ }) => {
+ const homePage = new HomePage(page);
+
+ // Create a test note to ensure there is something to trash
+ await homePage.createNote('_e2e_trash_test');
+
+ // Navigate back to home
+ await page.goto('/');
+ await waitForZeppelinReady(page);
+
+ // Wait for the created note to appear in the node list, then hover
+ const testNote = page.locator('.node .file').filter({ hasText:
'_e2e_trash_test' });
+ await expect(testNote).toBeVisible({ timeout: 15000 });
+ await testNote.hover();
+
+ // Click the delete icon (nz-popconfirm is on the <i> element)
+ const deleteIcon = testNote.locator('.operation i[nztype="delete"]');
+ await deleteIcon.click();
+
+ // Confirm the popconfirm dialog (ng-zorro en_US default is "OK", not
"Yes")
+ await expect(page.locator('text=This note will be moved to
trash.')).toBeVisible();
+ const confirmButton = page.locator('.ant-popover button:has-text("OK")');
+ await confirmButton.click();
+
+ // Wait for the trash folder to appear and verify
+ await expect(nodeListPage.trashFolder).toBeVisible({ timeout: 10000 });
+ const isTrashVisible = await nodeListPage.isTrashFolderVisible();
+ expect(isTrashVisible).toBe(true);
+ });
+
+ test('Given there are notes in node list, When clicking a note, Then user
should navigate to that note', async ({
+ page
+ }) => {
+ await expect(nodeListPage.treeView).toBeVisible();
+ const notes = await nodeListPage.getAllVisibleNoteNames();
+
+ if (notes.length > 0 && notes[0]) {
+ const noteName = notes[0].trim();
+
+ await nodeListPage.clickNote(noteName);
+ await page.waitForURL(/notebook\//);
+
+ expect(page.url()).toContain('notebook/');
+ }
+ });
+
+ test('Given user clicks Create New Note button, When modal opens, Then note
create modal should be displayed', async ({
+ page
+ }) => {
+ await nodeListPage.clickCreateNewNote();
+ await page.waitForSelector('input[name="noteName"]');
+
+ const noteNameInput = page.locator('input[name="noteName"]');
+ await expect(noteNameInput).toBeVisible();
+ });
+
+ test('Given user clicks Import Note button, When modal opens, Then note
import modal should be displayed', async ({
+ page
+ }) => {
+ await nodeListPage.clickImportNote();
+ await page.waitForSelector('input[name="noteImportName"]');
+
+ const importNameInput = page.locator('input[name="noteImportName"]');
+ await expect(importNameInput).toBeVisible();
+ });
+});
diff --git
a/zeppelin-web-angular/e2e/tests/share/note-create/note-create-modal.spec.ts
b/zeppelin-web-angular/e2e/tests/share/note-create/note-create-modal.spec.ts
new file mode 100644
index 0000000000..a2674b4c4a
--- /dev/null
+++ b/zeppelin-web-angular/e2e/tests/share/note-create/note-create-modal.spec.ts
@@ -0,0 +1,108 @@
+/*
+ * 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 { test, expect } from '@playwright/test';
+import { HomePage } from '../../../models/home-page';
+import { NoteCreateModal } from '../../../models/note-create-modal';
+import { NoteCreateModalUtil } from '../../../models/note-create-modal.util';
+import { addPageAnnotationBeforeEach, PAGES, performLoginIfRequired,
waitForZeppelinReady } from '../../../utils';
+
+test.describe('Note Create Modal', () => {
+ let homePage: HomePage;
+ let noteCreateModal: NoteCreateModal;
+ let noteCreateUtil: NoteCreateModalUtil;
+
+ addPageAnnotationBeforeEach(PAGES.SHARE.NOTE_CREATE);
+
+ test.beforeEach(async ({ page }) => {
+ homePage = new HomePage(page);
+ noteCreateModal = new NoteCreateModal(page);
+ noteCreateUtil = new NoteCreateModalUtil(noteCreateModal);
+
+ await page.goto('/');
+ await waitForZeppelinReady(page);
+ await performLoginIfRequired(page);
+
+ await homePage.clickCreateNewNote();
+ await page.waitForSelector('input[name="noteName"]');
+ });
+
+ test('Given user clicks Create New Note, When modal opens, Then modal should
display all required elements', async () => {
+ await noteCreateUtil.verifyModalIsOpen();
+ await expect(noteCreateModal.interpreterDropdown).toBeVisible();
+ await noteCreateUtil.verifyFolderCreationInfo();
+ });
+
+ test('Given Create Note modal is open, When checking default note name, Then
auto-generated name should follow pattern', async () => {
+ await noteCreateUtil.verifyDefaultNoteName(/Untitled Note \d+/);
+ });
+
+ test('Given Create Note modal is open, When entering custom note name and
creating, Then new note should be created successfully', async ({
+ page
+ }) => {
+ const uniqueName = `Test Note ${Date.now()}`;
+ await noteCreateModal.setNoteName(uniqueName);
+ await noteCreateModal.clickCreate();
+
+ // Wait for modal to disappear
+ await expect(noteCreateModal.modal).not.toBeVisible();
+
+ await page.waitForURL(/notebook\//);
+ expect(page.url()).toContain('notebook/');
+
+ // Verify the note was created with the correct name
+ const notebookTitle = page.locator('p, .notebook-title, .note-title, h1,
[data-testid="notebook-title"]').first();
+ await expect(notebookTitle).toContainText(uniqueName);
+
+ // Verify in the navigation tree if available
+ await page.goto('/');
+ await page.waitForLoadState('networkidle');
+ const noteInTree = page.getByRole('link', { name: uniqueName });
+ await expect(noteInTree).toBeVisible();
+ });
+
+ test('Given Create Note modal is open, When entering note name with folder
path, Then note should be created in folder', async ({
+ page
+ }) => {
+ const folderPath = `/TestFolder/SubFolder`;
+ const noteName = `Note ${Date.now()}`;
+ const fullPath = `${folderPath}/${noteName}`;
+
+ await noteCreateModal.setNoteName(fullPath);
+ await noteCreateModal.clickCreate();
+
+ // Wait for modal to disappear
+ await expect(noteCreateModal.modal).not.toBeVisible();
+
+ await page.waitForURL(/notebook\//);
+ expect(page.url()).toContain('notebook/');
+
+ // Verify the note was created with the correct name (without folder path)
+ const notebookTitle = page.locator('p, .notebook-title, .note-title, h1,
[data-testid="notebook-title"]').first();
+ await expect(notebookTitle).toContainText(noteName);
+
+ // Verify the folder structure was created
+ await page.goto('/');
+ await page.waitForLoadState('networkidle');
+ const folder = page.locator('nz-tree-node').filter({ hasText: 'TestFolder'
});
+ await expect(folder).toBeVisible();
+ });
+
+ test('Given Create Note modal is open, When clicking close button, Then
modal should close', async () => {
+ await noteCreateUtil.verifyModalClose();
+ });
+
+ test('Given Create Note modal is open, When viewing folder info alert, Then
alert should contain folder creation instructions', async () => {
+ const isInfoVisible = await noteCreateModal.isFolderInfoVisible();
+ expect(isInfoVisible).toBe(true);
+ });
+});
diff --git
a/zeppelin-web-angular/e2e/tests/share/note-import/note-import-modal.spec.ts
b/zeppelin-web-angular/e2e/tests/share/note-import/note-import-modal.spec.ts
new file mode 100644
index 0000000000..b20bee0902
--- /dev/null
+++ b/zeppelin-web-angular/e2e/tests/share/note-import/note-import-modal.spec.ts
@@ -0,0 +1,105 @@
+/*
+ * 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 { test, expect } from '@playwright/test';
+import { HomePage } from '../../../models/home-page';
+import { NoteImportModal } from '../../../models/note-import-modal';
+import { addPageAnnotationBeforeEach, PAGES, performLoginIfRequired,
waitForZeppelinReady } from '../../../utils';
+
+test.describe('Note Import Modal', () => {
+ let homePage: HomePage;
+ let noteImportModal: NoteImportModal;
+
+ addPageAnnotationBeforeEach(PAGES.SHARE.NOTE_IMPORT);
+
+ test.beforeEach(async ({ page }) => {
+ homePage = new HomePage(page);
+ noteImportModal = new NoteImportModal(page);
+
+ await page.goto('/');
+ await waitForZeppelinReady(page);
+ await performLoginIfRequired(page);
+
+ await homePage.clickImportNote();
+ await page.waitForSelector('input[name="noteImportName"]');
+ });
+
+ test('Given user clicks Import Note, When modal opens, Then modal should
display all required elements', async () => {
+ await expect(noteImportModal.modal).toBeVisible();
+ await expect(noteImportModal.modalTitle).toBeVisible();
+ await expect(noteImportModal.importAsInput).toBeVisible();
+ await expect(noteImportModal.jsonFileTab).toBeVisible();
+ await expect(noteImportModal.urlTab).toBeVisible();
+ });
+
+ test('Given Import Note modal is open, When viewing default tab, Then JSON
File tab should be selected', async () => {
+ const isJsonTabSelected = await noteImportModal.isJsonFileTabSelected();
+ expect(isJsonTabSelected).toBe(true);
+
+ await expect(noteImportModal.uploadArea).toBeVisible();
+ await expect(noteImportModal.uploadText).toBeVisible();
+ });
+
+ test('Given Import Note modal is open, When switching to URL tab, Then URL
input should be visible', async () => {
+ await noteImportModal.switchToUrlTab();
+
+ const isUrlTabSelected = await noteImportModal.isUrlTabSelected();
+ expect(isUrlTabSelected).toBe(true);
+
+ await expect(noteImportModal.urlInput).toBeVisible();
+ await expect(noteImportModal.importNoteButton).toBeVisible();
+ });
+
+ test('Given URL tab is selected, When URL is empty, Then import button
should be disabled', async () => {
+ await noteImportModal.switchToUrlTab();
+
+ const isDisabled = await noteImportModal.isImportNoteButtonDisabled();
+ expect(isDisabled).toBe(true);
+ });
+
+ test('Given URL tab is selected, When entering URL, Then import button
should be enabled', async () => {
+ await noteImportModal.switchToUrlTab();
+ await noteImportModal.setImportUrl('https://example.com/note.json');
+
+ const isDisabled = await noteImportModal.isImportNoteButtonDisabled();
+ expect(isDisabled).toBe(false);
+ });
+
+ test('Given Import Note modal is open, When entering import name, Then name
should be set', async () => {
+ const importName = `Imported Note ${Date.now()}`;
+ await noteImportModal.setImportAsName(importName);
+
+ const actualName = await noteImportModal.getImportAsName();
+ expect(actualName).toBe(importName);
+ });
+
+ test('Given JSON File tab is selected, When viewing file size limit, Then
limit should be displayed', async () => {
+ const fileSizeLimit = await noteImportModal.getFileSizeLimit();
+ expect(fileSizeLimit).toBeTruthy();
+ expect(fileSizeLimit.length).toBeGreaterThan(0);
+ });
+
+ test('Given Import Note modal is open, When clicking close button, Then
modal should close', async () => {
+ await noteImportModal.close();
+ await expect(noteImportModal.modal).not.toBeVisible();
+ });
+
+ test('Given URL tab is selected, When entering invalid URL and clicking
import, Then error should be displayed', async () => {
+ await noteImportModal.switchToUrlTab();
+ await noteImportModal.setImportUrl('invalid-url');
+ await noteImportModal.clickImportNote();
+
+ await expect(noteImportModal.errorAlert).toBeVisible();
+ const errorMessage = await noteImportModal.getErrorMessage();
+ expect(errorMessage).toBeTruthy();
+ });
+});
diff --git a/zeppelin-web-angular/e2e/utils.ts
b/zeppelin-web-angular/e2e/utils.ts
index dab04a1325..d4fd455a97 100644
--- a/zeppelin-web-angular/e2e/utils.ts
+++ b/zeppelin-web-angular/e2e/utils.ts
@@ -215,7 +215,7 @@ export const waitForZeppelinReady = async (page: Page):
Promise<void> => {
if (isOnLoginPage) {
console.log('On login page - checking if authentication is enabled');
- // If we're on login dlpage, this is expected when authentication is
required
+ // If we're on login page, this is expected when authentication is
required
// Just wait for login elements to be ready instead of waiting for app
content
await page.waitForFunction(
() => {