Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions zeppelin-web-angular/e2e/models/about-zeppelin-modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,6 @@ export class AboutZeppelinModal extends BasePage {
return (await this.versionText.textContent()) || '';
}

async isLogoVisible(): Promise<boolean> {
return this.logo.isVisible();
}

async getGetInvolvedHref(): Promise<string | null> {
return this.getInvolvedLink.getAttribute('href');
}
Expand Down
34 changes: 7 additions & 27 deletions zeppelin-web-angular/e2e/models/base-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ export class BasePage {
readonly zeppelinHeader: Locator;

readonly modalTitle: Locator;
readonly modalBody: Locator;
readonly modalContent: Locator;

readonly okButton: Locator;
readonly cancelButton: Locator;
Expand All @@ -41,8 +39,6 @@ export class BasePage {
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")');
Expand Down Expand Up @@ -71,11 +67,6 @@ export class BasePage {
await this.navigateToRoute('/');
}

getCurrentPath(): string {
const url = new URL(this.page.url());
return url.hash || url.pathname;
}

async waitForUrlNotContaining(fragment: string): Promise<void> {
await this.page.waitForURL(url => !url.toString().includes(fragment));
}
Expand All @@ -85,14 +76,8 @@ export class BasePage {
}

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 }
);
const locators = labelTexts.map(text => this.page.locator('nz-form-label', { hasText: text }));
await Promise.race(locators.map(l => l.waitFor({ state: 'attached', timeout })));
}

async waitForElementAttribute(
Expand All @@ -109,25 +94,20 @@ export class BasePage {
}
}

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 || {};
const { timeout = 10000 } = options || {};

await expect(locator).toBeVisible({ timeout });
await expect(locator).toBeEnabled({ timeout: 5000 });

if (clearFirst) {
await locator.clear();
}

// Click first so Angular's form control is focused and its initial setValue cycle
// has completed before we overwrite it. Then fill() atomically sets the value.
await locator.click();
await locator.fill(value);
await expect(locator).toHaveValue(value);
await expect(locator).toHaveValue(value, { timeout: 10000 });
}
}
70 changes: 21 additions & 49 deletions zeppelin-web-angular/e2e/models/home-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ export class HomePage extends BasePage {
readonly notebookSection: Locator;
readonly helpSection: Locator;
readonly communitySection: Locator;
readonly zeppelinLogo: Locator;
readonly anonymousUserIndicator: Locator;
readonly welcomeSection: Locator;
readonly moreInfoGrid: Locator;
readonly notebookColumn: Locator;
Expand All @@ -28,9 +26,6 @@ export class HomePage extends BasePage {
readonly notebookHeading: Locator;
readonly helpHeading: Locator;
readonly communityHeading: Locator;
readonly createNoteModal: Locator;
readonly createNoteButton: Locator;
readonly notebookNameInput: Locator;
readonly externalLinks: {
documentation: Locator;
mailingList: Locator;
Expand All @@ -41,13 +36,12 @@ export class HomePage extends BasePage {
createNewNoteLink: Locator;
importNoteLink: Locator;
filterInput: Locator;
tree: Locator;
noteActions: {
renameNote: Locator;
clearOutput: Locator;
moveToTrash: Locator;
};
};
readonly anonymousUserIndicator: Locator;
private readonly zeppelinLogo: Locator;
private readonly createNoteModal: Locator;
private readonly createNoteButton: Locator;
private readonly notebookNameInput: Locator;

constructor(page: Page) {
super(page);
Expand All @@ -58,8 +52,8 @@ export class HomePage extends BasePage {
this.anonymousUserIndicator = page.locator('text=anonymous');
this.welcomeSection = page.locator('.welcome');
this.moreInfoGrid = page.locator('.more-info');
this.notebookColumn = page.locator('[nz-col]').first();
this.helpCommunityColumn = page.locator('[nz-col]').last();
this.notebookColumn = page.locator('[nz-col]').first(); // first() — left column contains the Notebook section
this.helpCommunityColumn = page.locator('[nz-col]').last(); // last() — right column contains Help and Community sections
this.welcomeDescription = page.locator('.welcome').getByText('Zeppelin is web-based notebook');
this.refreshNoteButton = page.locator('a.refresh-note');
this.notebookHeading = this.notebookColumn.locator('h3');
Expand All @@ -79,13 +73,7 @@ export class HomePage extends BasePage {
this.nodeList = {
createNewNoteLink: page.locator('zeppelin-node-list a').filter({ hasText: 'Create new Note' }),
importNoteLink: page.locator('zeppelin-node-list a').filter({ hasText: 'Import Note' }),
filterInput: page.locator('zeppelin-node-list input[placeholder*="Filter"]'),
tree: page.locator('zeppelin-node-list nz-tree'),
noteActions: {
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"]')
}
filterInput: page.locator('zeppelin-node-list input[placeholder*="Filter"]')
};
}

Expand All @@ -100,14 +88,6 @@ export class HomePage extends BasePage {
await this.waitForUrlNotContaining('#/login');
}

async isHomeContentDisplayed(): Promise<boolean> {
return this.welcomeTitle.isVisible();
}

async isAnonymousUser(): Promise<boolean> {
return this.anonymousUserIndicator.isVisible();
}

async clickZeppelinLogo(): Promise<void> {
await this.zeppelinLogo.click({ timeout: 15000 });
}
Expand All @@ -117,19 +97,11 @@ export class HomePage extends BasePage {
return text || '';
}

async getWelcomeDescriptionText(): Promise<string> {
const text = await this.welcomeDescription.textContent();
return text || '';
}

async clickRefreshNotes(): Promise<void> {
await this.refreshNoteButton.waitFor({ state: 'visible', timeout: 10000 });
await this.refreshNoteButton.click({ timeout: 15000 });
}

async isNotebookListVisible(): Promise<boolean> {
return this.zeppelinNodeList.isVisible();
}

async clickCreateNewNote(): Promise<void> {
await this.nodeList.createNewNoteLink.click({ timeout: 15000 });
await this.createNoteModal.waitFor({ state: 'visible' });
Expand All @@ -141,15 +113,17 @@ export class HomePage extends BasePage {
// Wait for the modal form to be fully rendered with proper labels
await this.page.waitForSelector('nz-form-label', { timeout: 10000 });

await this.waitForFormLabels(['Note Name', 'Clone Note']);
await this.waitForFormLabels(['Note Name']);

// 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();
// Wait for navigation to the notebook page — confirms the note was created server-side.
// waitForPageLoad() (domcontentloaded) fires instantly on SPA routing and does not guarantee this.
await this.page.waitForURL(/\/notebook\//, { timeout: 45000 });
}

async clickImportNote(): Promise<void> {
Expand All @@ -159,19 +133,17 @@ export class HomePage extends BasePage {
async filterNotes(searchTerm: string): Promise<void> {
await this.page.waitForLoadState('domcontentloaded', { timeout: 10000 });
await this.nodeList.filterInput.waitFor({ state: 'visible', timeout: 5000 });
await this.nodeList.filterInput.fill(searchTerm, { timeout: 15000 });
}

async isRefreshIconSpinning(): Promise<boolean> {
const spinAttribute = await this.refreshIcon.getAttribute('nzSpin');
return spinAttribute === 'true' || spinAttribute === '';
// pressSequentially fires real key events so Angular's ngModel detects the change (fill() does not).
// Triple-click to select all, then type to replace or Backspace to clear.
await this.nodeList.filterInput.click({ clickCount: 3 });
if (searchTerm) {
await this.nodeList.filterInput.pressSequentially(searchTerm);
} else {
await this.nodeList.filterInput.press('Backspace');
}
}

async waitForRefreshToComplete(): Promise<void> {
await this.waitForElementAttribute('a.refresh-note i[nz-icon]', 'nzSpin', false);
}

async getDocumentationLinkHref(): Promise<string | null> {
return this.externalLinks.documentation.getAttribute('href');
}
}
14 changes: 1 addition & 13 deletions zeppelin-web-angular/e2e/models/node-list-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,7 @@ export class NodeListPage extends BasePage {
await this.createNewNoteButton.click();
}

getFolderByName(folderName: string): Locator {
return this.page.locator('nz-tree-node').filter({ hasText: folderName }).first();
}

getNoteByName(noteName: string): Locator {
private getNoteByName(noteName: string): Locator {
return this.page.locator('nz-tree-node').filter({ hasText: noteName }).first();
}

Expand All @@ -56,14 +52,6 @@ export class NodeListPage extends BasePage {
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[] = [];
Expand Down
4 changes: 0 additions & 4 deletions zeppelin-web-angular/e2e/models/note-create-modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,4 @@ export class NoteCreateModal extends BasePage {
async clickCreate(): Promise<void> {
await this.createButton.click();
}

async isFolderInfoVisible(): Promise<boolean> {
return this.folderInfoAlert.isVisible();
}
}
22 changes: 0 additions & 22 deletions zeppelin-web-angular/e2e/models/note-import-modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,6 @@ export class NoteImportModal extends BasePage {
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);
}
Expand All @@ -77,19 +67,7 @@ export class NoteImportModal extends BasePage {
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()) || '';
}
}
6 changes: 2 additions & 4 deletions zeppelin-web-angular/e2e/models/notebook-repo-item.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,12 @@ export class NotebookRepoItemUtil extends BasePage {

async verifyDisplayMode(): Promise<void> {
await expect(this.repoItemPage.editButton).toBeVisible();
const isEditMode = await this.repoItemPage.isEditMode();
expect(isEditMode).toBe(false);
await expect(this.repoItemPage.repositoryCard).not.toHaveClass(/\bedit\b/);
}

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);
await expect(this.repoItemPage.repositoryCard).toHaveClass(/\bedit\b/);
}
}
36 changes: 2 additions & 34 deletions zeppelin-web-angular/e2e/models/notebook-repos-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@ export class NotebookReposPage extends BasePage {
this.page.waitForSelector('zeppelin-notebook-repo-item', { state: 'visible' })
]);
}

async getRepositoryItemCount(): Promise<number> {
return await this.repositoryItems.count();
}
}

export class NotebookRepoItemPage extends BasePage {
Expand Down Expand Up @@ -72,50 +68,22 @@ export class NotebookRepoItemPage extends BasePage {
await this.cancelButton.click({ timeout: 15000 });
}

async isEditMode(): Promise<boolean> {
return await this.repositoryCard.evaluate(el => el.classList.contains('edit'));
}

async isSaveButtonEnabled(): Promise<boolean> {
return await this.saveButton.isEnabled();
}

async getSettingValue(settingName: string): Promise<string> {
const row = this.repositoryCard.locator('tbody tr').filter({ hasText: settingName });
const valueCell = row.locator('td').nth(1);
return (await valueCell.textContent()) || '';
}

async fillSettingInput(settingName: string, value: string): Promise<void> {
const row = this.repositoryCard.locator('tbody tr').filter({ hasText: settingName });
const input = row.locator('input[nz-input]');
await input.clear();
await input.fill(value);
}

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({ timeout: 15000 });
await this.page.locator(`nz-option[nzvalue="${optionValue}"]`).click({ timeout: 15000 });
}

async getSettingInputValue(settingName: string): Promise<string> {
const row = this.repositoryCard.locator('tbody tr').filter({ hasText: settingName });
const input = row.locator('input[nz-input]');
return await input.inputValue();
}

async isInputVisible(settingName: string): Promise<boolean> {
const row = this.repositoryCard.locator('tbody tr').filter({ hasText: settingName });
const input = row.locator('input[nz-input]');
return await input.isVisible();
}

async isDropdownVisible(settingName: string): Promise<boolean> {
async getSettingValue(settingName: string): Promise<string> {
const row = this.repositoryCard.locator('tbody tr').filter({ hasText: settingName });
const select = row.locator('nz-select');
return await select.isVisible();
return (await row.locator('td').nth(1).textContent()) || '';
}

async getSettingCount(): Promise<number> {
Expand Down
Loading
Loading