blob: ae30cb3ed73131aa2a39357095c0f864859974e8 [file]
/*
* 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');
// A global getByRole('link', { name: 'Job' }) matches by substring.
// It also picks up note-list links (e.g. a note named "...Job..."), which is a strict-mode violation.
// Scoping to the menu item and matching exactly selects the single header entry.
this.jobMenuItem = page.locator('[nz-menu-item]').getByRole('link', { name: 'Job', exact: true });
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');
// Same substring and strict-mode risk as jobMenuItem above.
// Scope these links to the dropdown overlay and match exactly.
const userMenu = page.locator('.zeppelin-user-menu');
this.userMenuItems = {
aboutZeppelin: userMenu.getByText('About Zeppelin', { exact: true }),
interpreter: userMenu.getByRole('link', { name: 'Interpreter', exact: true }),
notebookRepos: userMenu.getByRole('link', { name: 'Notebook Repos', exact: true }),
credential: userMenu.getByRole('link', { name: 'Credential', exact: true }),
configuration: userMenu.getByRole('link', { name: 'Configuration', exact: true }),
logout: userMenu.getByText('Logout', { exact: true }),
switchToClassicUI: userMenu.getByRole('link', { name: 'Switch to Classic UI', exact: true })
};
}
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();
}
/** The <li nz-menu-item> row for a user-menu entry, scoped to this dropdown's overlay. */
getUserMenuItemRow(label: string): Locator {
return this.page.locator('.zeppelin-user-menu .ant-dropdown-menu-item').filter({ hasText: label });
}
/**
* Click the empty padding area near the right edge of a user-menu row, away from the
* link text. This is the dead zone that only navigates when the inner <a> fills the
* whole row; clicking here regresses to "dropdown closes, no navigation" if the
* full-row click fix is missing.
*/
async clickUserMenuItemEdge(label: string): Promise<void> {
const row = this.getUserMenuItemRow(label);
await row.waitFor({ state: 'visible', timeout: 10000 });
const box = await row.boundingBox();
if (!box) {
throw new Error(`User menu item "${label}" has no bounding box`);
}
await row.click({ position: { x: box.width - 8, y: box.height / 2 } });
}
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');
}
}