blob: 403be0f7807577ed09f4ef861471762dffd9494b [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 { expect, Locator, Page } from '@playwright/test';
export const E2E_TEST_FOLDER = 'E2E_TEST_FOLDER';
export const BASE_URL = 'http://localhost:4200';
export class BasePage {
readonly page: Page;
readonly zeppelinNodeList: Locator;
readonly zeppelinWorkspace: Locator;
readonly zeppelinPageHeader: Locator;
readonly zeppelinHeader: Locator;
readonly modalTitle: 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.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> {
await this.page.waitForLoadState('domcontentloaded', { timeout: 15000 });
}
async navigateToRoute(
route: string,
options?: { timeout?: number; waitUntil?: 'load' | 'domcontentloaded' | 'networkidle' }
): Promise<void> {
await this.page.goto(`/#${route}`, {
waitUntil: 'domcontentloaded',
timeout: 60000,
...options
});
await this.waitForPageLoad();
}
async navigateToHome(): Promise<void> {
await this.navigateToRoute('/');
}
async waitForUrlNotContaining(fragment: string): Promise<void> {
await this.page.waitForURL(url => !url.toString().includes(fragment));
}
async getElementText(locator: Locator): Promise<string> {
return (await locator.textContent()) || '';
}
async waitForFormLabels(labelTexts: string[], timeout = 10000): Promise<void> {
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(
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 fillAndVerifyInput(
locator: Locator,
value: string,
options?: { timeout?: number; clearFirst?: boolean }
): Promise<void> {
const { timeout = 10000 } = options || {};
await expect(locator).toBeVisible({ timeout });
await expect(locator).toBeEnabled({ timeout: 5000 });
// Ant-modal autofocus + Angular form initialization race: any of fill / type /
// pressSequentially can land BEFORE the form-control's initial value sync,
// after which Angular silently resets the input back to the model's initial
// value (placeholder). ng-dirty is set but the visible value is wrong.
await expect(async () => {
await locator.click();
await locator.fill(value);
await locator.evaluate((el: HTMLInputElement) => {
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
});
// Verify the value stuck — if Angular reset it, this throws and toPass retries.
const actual = await locator.inputValue();
if (actual !== value) {
throw new Error(`fillAndVerifyInput retry: expected "${value}" got "${actual}"`);
}
}).toPass({ timeout: 15000, intervals: [200, 500, 1000, 2000] });
}
}