2026-06-24 21:10:34 +05:00

1162 lines
39 KiB
TypeScript

import {Page, Response, Route, expect} from '@playwright/test';
import {
ControlQA,
DashCommonQa,
DashEntryQa,
DashRelationTypes,
DatalensTabsQa,
DialogConfirmQA,
DialogDashWidgetQA,
DialogGroupControlQa,
DialogTabsQA,
EntryDialogQA,
LOADED_DASH_CLASS,
SelectQa,
WysiwygEditorQa,
YfmQa,
} from '../../../src/shared/constants';
import {COMMON_DASH_SELECTORS} from '../../suites/dash/constants';
import {
clickDropDownOption,
clickGSelectOption,
cssSlct,
deleteEntity,
entryDialogFillAndSave,
getAddress,
getUniqueTimestamp,
isEnabledFeature,
openTestPage,
slct,
waitForCondition,
} from '../../utils';
import {COMMON_SELECTORS} from '../../utils/constants';
import {BasePage, BasePageProps} from '../BasePage';
import Revisions from '../common/Revisions';
import {Locator} from 'playwright-core';
import {
DashboardDialogSettingsQa,
DialogDashTitleQA,
DialogDashWidgetItemQA,
Feature,
} from '../../../src/shared';
import {
ActionPanelDashSaveControlsQa,
ActionPanelEntryContextMenuQa,
} from '../../../src/shared/constants/qa/action-panel';
import {
DashBodyQa,
DashKitOverlayMenuQa,
DashTabsQA,
DashboardAddWidgetQa,
DashkitQa,
} from '../../../src/shared/constants/qa/dash';
import {WorkbookPageQa} from '../../../src/shared/constants/qa/workbooks';
import {WorkbookIds, WorkbooksUrls} from '../../constants/constants';
import {getUrlStateParam} from '../../suites/dash/helpers';
import {COMMON_CHARTKIT_SELECTORS} from '../constants/chartkit';
import {CommonUrls} from '../constants/common-urls';
import {DialogCreateEntry} from '../workbook/DialogCreateEntry';
import {EditEntityButton} from '../workbook/EditEntityButton';
import {Workbook} from '../workbook/Workbook';
import {ChartkitControl} from './ChartkitControl';
import ControlActions from './controlActions/ControlActions';
import {DashTabs} from './DashTabs';
import DashboardSettings from './DashboardSettings';
import Description from './Description';
import {FixedHeader} from './FixedHeader';
import TableOfContent from './TableOfContent';
export const BUTTON_CHECK_TIMEOUT = 3000;
export const RENDER_TIMEOUT = 4000;
export const DASH_LOADER_CHECK_TIMEOUT = 2000;
export const API_TIMEOUT = 3000;
export const URLS = {
navigationOnDelete: '/dashboards',
createLock: '/gateway/root/us/createLock',
deleteLock: '/gateway/root/us/deleteLock',
savePath: '/api/dash/v1/dashboards',
};
export interface DashboardPageProps extends BasePageProps {}
type LocatorOptionsType = Parameters<Page['locator']>[1];
type LocatorClickOptionsType = Parameters<Locator['click']>[0];
class DashboardPage extends BasePage {
static selectors = {
title: '.dashkit-plugin-title',
text: '.dashkit-plugin-text',
mobileModal: '.g-mobile-modal',
tabsContainer: '.gc-adaptive-tabs',
tabsList: '.gc-adaptive-tabs__tabs-list',
tabItem: '.gc-adaptive-tabs__tab',
tabItemActive: '.gc-adaptive-tabs__tab_active',
tabItemDisabled: '.gc-adaptive-tabs__tab_disabled',
selectItems: '.g-select-list',
selectItemsMobile: '.g-select-list_mobile',
selectItemTitle: '.g-select-list__option',
selectItemTitleDisabled: '.g-select-list__option_disabled',
chartGridItemContainer: `${slct(DashkitQa.GRID_ITEM)} .chartkit`,
};
revisions: Revisions;
tableOfContent: TableOfContent;
description: Description;
dashTabs: DashTabs;
chartkitControl: ChartkitControl;
dialogCreateEntry: DialogCreateEntry;
editEntityButton: EditEntityButton;
controlActions: ControlActions;
fixedHeader: FixedHeader;
constructor({page}: DashboardPageProps) {
super({page});
this.revisions = new Revisions(page);
this.description = new Description(page);
this.tableOfContent = new TableOfContent(page, this);
this.dashTabs = new DashTabs(page);
this.chartkitControl = new ChartkitControl(page);
this.dialogCreateEntry = new DialogCreateEntry(page);
this.editEntityButton = new EditEntityButton(page);
this.controlActions = new ControlActions(page);
this.fixedHeader = new FixedHeader(page);
}
async waitForResponses(url: string, timeout = API_TIMEOUT): Promise<Array<Response>> {
return new Promise((resolve: any) => {
const res: Array<Response> = [];
const onResponse = (response: Response) => {
const responseUrl = new URL(response.url());
if (responseUrl.pathname === url) {
res.push(response);
}
};
this.page.on('response', onResponse);
setTimeout(() => {
this.page.off('response', onResponse);
resolve(res);
}, timeout);
});
}
async getMarkdownHTML() {
const makrdownNode = await this.page.waitForSelector('.yfm');
return makrdownNode.innerHTML();
}
async getMarkdownText(gridItemLocator?: Locator) {
const yfmLocator = (gridItemLocator || this.page).locator(slct(YfmQa.WrapperHtml));
return yfmLocator.innerText();
}
// Fill in the input with the name of the entity being created in the EntryDialog (the dialog that appears when saving entities) and click the "Create" button
async entryNavigationDialogFillAndSave(page: Page, entryName: string) {
// waiting for the save dialog to open
const entryDialog = await page.waitForSelector(slct('entry-dialog-content'));
const entryDialogInput = await entryDialog!.waitForSelector('[data-qa=path-select] input');
// filling in the input
await entryDialogInput!.fill(entryName);
// save
await page.click(slct(EntryDialogQA.Apply));
}
async expectControlsRequests({
controlTitles,
action,
waitForLoader,
}: {
controlTitles: string[];
waitForLoader?: boolean;
action?: () => Promise<void>;
}) {
const loader = this.page.locator(slct(ControlQA.groupCommonLockedBlock));
const handler = async (route: Route) => {
await expect(loader).toBeVisible();
await route.continue();
};
if (waitForLoader) {
// check for loader appearence and passing request without changes
await this.page.route(CommonUrls.ApiRun, handler);
}
// check that requests for passed selectors have completed successfully
const controlResponses = controlTitles.map((title) => {
const predicate = (response: Response) => {
const isCorrespondingRequest =
response.status() === 200 &&
response.url().includes(CommonUrls.ApiRun) &&
response.request().postDataJSON()?.config?.data?.shared?.title === title;
return isCorrespondingRequest;
};
return this.page.waitForResponse(predicate);
});
await action?.();
await Promise.all(controlResponses);
if (waitForLoader) {
await expect(loader).toBeHidden();
await this.page.unroute(CommonUrls.ApiRun, handler);
}
}
async createDashboard({
editDash,
waitingRequestOptions,
workbookId,
}: {
editDash: () => Promise<void>;
waitingRequestOptions?: {
controlTitles: string[];
waitForLoader?: boolean;
};
workbookId?: string;
}) {
// some page need to be loaded so we can get data of feature flag from DL var
await openTestPage(this.page, '/');
const isCollectionsE2ETestsMode = await isEnabledFeature(
this.page,
Feature.CollectionsE2ETestsMode,
);
const createDashUrl = isCollectionsE2ETestsMode
? `/workbooks/${workbookId ?? WorkbookIds.E2EWorkbook}/dashboards`
: '/dashboards/new';
await openTestPage(this.page, createDashUrl);
// callback with start actions with dash in edit mode
await editDash();
await this.clickSaveButton();
const dashName = `e2e-entry-${getUniqueTimestamp()}`;
const expectRequestPromise = waitingRequestOptions
? this.expectControlsRequests({
controlTitles: waitingRequestOptions.controlTitles,
waitForLoader: waitingRequestOptions.waitForLoader,
})
: null;
// waiting for the dialog to open, specify the name, save
if (isCollectionsE2ETestsMode) {
await this.dialogCreateEntry.createEntryWithName(dashName);
} else {
await entryDialogFillAndSave(this.page, dashName);
}
// check that the dashboard has loaded by its name
await this.page.waitForSelector(`${slct(DashEntryQa.EntryName)} >> text=${dashName}`);
await expectRequestPromise;
}
async duplicateDashboard({dashId, useUserFolder}: {dashId?: string; useUserFolder?: boolean}) {
const newDashName = `e2e-test-dash-copy-${getUniqueTimestamp()}`;
const isCollectionsE2ETestsMode = await isEnabledFeature(
this.page,
Feature.CollectionsE2ETestsMode,
);
if (isCollectionsE2ETestsMode) {
await this.duplicateDashboardFromWorkbook(dashId as string, newDashName);
return;
}
await this.copyDashboard(newDashName, useUserFolder);
}
async copyDashboard(newDashName: string, useUserFolder?: boolean) {
// click on the ellipsis in the upper panel
await this.page.click(slct(COMMON_SELECTORS.ENTRY_PANEL_MORE_BTN));
await this.page.waitForSelector(cssSlct(COMMON_SELECTORS.ENTRY_CONTEXT_MENU_KEY));
// select Duplicate item
await clickDropDownOption(this.page, ActionPanelEntryContextMenuQa.Copy);
// click the Apply button in the dashboard copy dialog
await this.page.waitForSelector(slct(EntryDialogQA.Apply));
// waiting for the dialog to open, specify the name, save
// waiting for the transition to the dashboard page
await Promise.all([
this.page.waitForNavigation(),
entryDialogFillAndSave(this.page, newDashName, useUserFolder),
]);
}
async duplicateDashboardFromWorkbook(dashId: string, newDashName: string) {
const workbookPO = new Workbook(this.page);
await workbookPO.openE2EWorkbookPage();
await this.page.locator(slct(DatalensTabsQa.Item), {hasText: 'Dashboards'}).click();
await workbookPO.openWorkbookItemMenu(dashId);
await this.page.locator(slct(WorkbookPageQa.MenuItemDuplicate)).click();
// waiting for the dialog to open, specify the name, save
await workbookPO.dialogCreateEntry.createEntryWithName(newDashName);
await this.page
.locator(`${slct(WorkbookPageQa.ListItem)}:has-text('${newDashName}')`)
.click();
}
async clickAddChart() {
await this.page.click(slct(DashboardAddWidgetQa.AddWidget));
}
async addChart({
url,
name,
hideTitle,
enableAutoHeight,
addChartTab,
}: {
url: string;
name: string;
hideTitle?: boolean;
enableAutoHeight?: boolean;
addChartTab?: boolean;
}) {
// adding a chart
if (!addChartTab) {
await this.clickAddChart();
}
// click on "specify link
await this.page.click(slct('navigation-input-use-link-button'));
// specify the link
await this.page.fill('[data-qa=navigation-input] input', getAddress(url));
// adding
await this.page.click(slct('navigation-input-ok-button'));
// making sure that the necessary chart is selected
await this.page.waitForSelector(`[data-qa=entry-title] * >> text=${name}`);
if (hideTitle) {
await this.page.locator(slct(DashCommonQa.WidgetShowTitleCheckbox)).click();
}
if (enableAutoHeight) {
await this.page.click(slct(DashCommonQa.WidgetEnableAutoHeightCheckbox));
}
// adding to the dashboard
await this.page.click(slct(DialogDashWidgetQA.Apply));
}
async copyWidget(counter?: number) {
await this.page
.locator(slct(ControlQA.controlMenu))
.nth(counter || 0)
.click();
await this.page.locator(slct(DashKitOverlayMenuQa.CopyButton)).click();
}
async clickAddText() {
await this.page.click(slct(DashboardAddWidgetQa.AddText));
}
async chooseMarkupText() {
await this.page.click(slct(WysiwygEditorQa.SettingsButton));
await this.page.click(slct(WysiwygEditorQa.ModeMarkupItemMenu));
}
async addText({
text,
timeout,
markup,
options,
}: {
text: string;
timeout?: number;
markup?: boolean;
options?: {autoHeight?: boolean};
}) {
await this.clickAddText();
await this.page.waitForSelector(slct(DialogDashWidgetItemQA.Text));
if (markup) {
await this.chooseMarkupText();
}
const isCollectionsE2ETestsMode = await isEnabledFeature(
this.page,
Feature.CollectionsE2ETestsMode,
);
if (isCollectionsE2ETestsMode) {
await this.page.fill(`${slct(WysiwygEditorQa.Editor)} [contenteditable=true]`, text);
} else {
await this.page.fill(
`${slct(DialogDashWidgetItemQA.Text)} [contenteditable=true]`,
text,
{timeout},
);
}
if (options) {
if (options.autoHeight) {
await this.page.click(slct(DashCommonQa.WidgetEnableAutoHeightCheckbox));
}
}
await this.page.click(slct(DialogDashWidgetQA.Apply));
}
async clickAddTitle() {
await this.page.click(slct(DashboardAddWidgetQa.AddTitle));
}
async addTitle(text: string) {
await this.clickAddTitle();
await this.page.waitForSelector(slct(DialogDashWidgetItemQA.Title));
await this.page.fill(`${slct(DialogDashTitleQA.Input)} input`, text);
await this.page.click(slct(DialogDashWidgetQA.Apply));
}
getDashKitTextItem(text: string) {
return this.page.locator(slct(DashkitQa.GRID_ITEM)).getByText(text, {exact: true});
}
async deleteSelector(controlTitle: string) {
const control = this.page.locator(slct('dashkit-grid-item'), {
has: this.page.locator(slct(ControlQA.chartkitControl, controlTitle)),
});
const controlSwitcher = control.locator(slct(ControlQA.controlMenu));
await expect(
controlSwitcher,
`The control menu ${controlTitle} was not found, make sure the dashboard is in edit mode and the controlTitle is correct`,
).toBeVisible();
await controlSwitcher.click();
await this.page.click(slct(DashKitOverlayMenuQa.RemoveButton));
}
async editDashboard({editDash}: {editDash: () => Promise<void>}) {
await this.enterEditMode();
await editDash();
await this.saveChanges();
}
async enterEditMode() {
// switch to edit mode from view
const createLockPromise = this.page.waitForRequest(URLS.createLock);
await this.page.waitForSelector(slct(COMMON_SELECTORS.ACTION_PANEL_EDIT_BTN));
await this.page.click(slct(COMMON_SELECTORS.ACTION_PANEL_EDIT_BTN));
await createLockPromise;
await this.page.waitForSelector(slct(COMMON_SELECTORS.ACTION_PANEL_CANCEL_BTN));
}
async forceEnterEditMode() {
// switch to edit mode from view
const createLockPromise = this.page.waitForRequest(URLS.createLock);
await this.page.click(slct(COMMON_SELECTORS.ACTION_PANEL_EDIT_BTN));
// waiting for the opening of the warning dialog or the Cancel edit button
const elem = await this.page.waitForSelector(
`${slct(DialogConfirmQA.Dialog)}, ${slct(COMMON_SELECTORS.ACTION_PANEL_CANCEL_BTN)}`,
);
const qaAttr = await elem.getAttribute('data-qa');
if (qaAttr !== DialogConfirmQA.Dialog) {
await createLockPromise;
return;
}
// click "Edit anyway"
await this.page.click(slct(DialogConfirmQA.ApplyButton));
await createLockPromise;
await this.page.waitForSelector(slct(COMMON_SELECTORS.ACTION_PANEL_CANCEL_BTN));
}
async exitEditMode() {
// exiting the default editing mode of an empty dashboard
try {
const deleteLockPromise = this.page.waitForRequest(URLS.deleteLock);
await this.page.waitForSelector(slct(COMMON_SELECTORS.ACTION_PANEL_CANCEL_BTN));
await this.page.click(slct(COMMON_SELECTORS.ACTION_PANEL_CANCEL_BTN));
// if there are changes, a dialog with a warning about the unsaved changes will appear
const warningCancelDialog = this.page.locator(slct(DialogConfirmQA.Dialog));
const editButton = this.page.locator(slct(COMMON_SELECTORS.ACTION_PANEL_EDIT_BTN));
await expect(editButton.or(warningCancelDialog)).toBeVisible();
if (await editButton.isVisible()) {
await deleteLockPromise;
return;
}
// if there is a dialog, click the apply button
const applyBtn = await this.page.waitForSelector(slct(DialogConfirmQA.ApplyButton));
await applyBtn.click();
await deleteLockPromise;
await this.page.waitForSelector(slct(COMMON_SELECTORS.ACTION_PANEL_EDIT_BTN));
} catch {
// dash is not in edit mode
}
}
async editDashWithoutSaving() {
// adding a plug selector to the dashboard
await this.controlActions.addSelector({
appearance: {title: 'testSelector'},
fieldName: 'testName',
});
}
async makeDraft() {
// creating a draft version (you need to enter edit mode in advance)
await this.editDashWithoutSaving();
await this.saveChangesAsDraft();
}
async clickOnLinksBtn() {
// click on the "connections" button
await this.page.click(slct(COMMON_SELECTORS.ACTION_BTN_CONNECTIONS));
}
async openDashConnections() {
// switch to edit mode from view
await this.page.click(slct(COMMON_SELECTORS.ACTION_PANEL_EDIT_BTN));
// waiting for the opening of the warning dialog or the Cancel edit button
const elem = await this.page.waitForSelector(
`${slct(DialogConfirmQA.Dialog)}, ${slct(COMMON_SELECTORS.ACTION_PANEL_CANCEL_BTN)}`,
);
const qaAttr = await elem.getAttribute('data-qa');
if (qaAttr === DialogConfirmQA.Dialog) {
// click "Edit anyway"
await this.page.click(slct(DialogConfirmQA.ApplyButton));
}
// click on the "connections" button
await this.clickOnLinksBtn();
}
async applyAliasesChanges() {
await this.page.locator(slct(DashCommonQa.AliasAddApplyBtn)).click();
}
async cancelAliasesChanges() {
await this.page.locator(slct(DashCommonQa.AliasesCancelBtn)).click();
}
async applyRelationsChanges() {
await this.page.locator(slct(DashCommonQa.RelationsApplyBtn)).click();
}
async cancelRelationsChanges() {
await this.page.locator(slct(DashCommonQa.RelationsCancelBtn)).click();
}
async openControlRelationsDialog() {
await this.enterEditMode();
// open dialog relations by control icon click
const selectorElem = await this.controlActions.getDashControlLinksIconElem();
await selectorElem.click();
}
async setupIgnoreAllLinks(widgetElem: Locator) {
await widgetElem.click();
await this.page.locator(slct(DashCommonQa.RelationsDisconnectAllSwitcher)).click();
await this.page.locator(slct(DashCommonQa.RelationsDisconnectAllWidgets)).click();
await this.applyRelationsChanges();
}
async setupIgnoreLink(widgetElem: Locator) {
// open dialog relations by click on control item links icon
await widgetElem.click();
// choose new link
await this.page.click(slct(DashCommonQa.RelationTypeButton));
await this.page.click(slct(DashRelationTypes.ignore));
await this.applyRelationsChanges();
}
async setupNewLinks({
widgetElem,
selectorName,
linkType,
firstParamName,
secondParamName,
}: {
linkType: DashRelationTypes;
firstParamName: string;
secondParamName: string;
} & (
| {widgetElem: Locator; selectorName?: undefined}
| {selectorName: string; widgetElem?: undefined}
)) {
if (widgetElem) {
// open dialog relations by click on control item links icon
await widgetElem.click();
} else if (selectorName) {
// click on global links button
await this.clickOnLinksBtn();
await clickGSelectOption({
page: this.page,
key: DashCommonQa.RelationsWidgetSelect,
optionText: selectorName,
});
} else {
throw new Error('Relation dialog needs selectorName or widgetElement param');
}
// choose new link
await this.page.click(slct(DashCommonQa.RelationTypeButton));
await this.page.click(slct(linkType));
// select field for first item
await this.page.click(slct(DashCommonQa.AliasSelectLeft));
await this.page.locator(slct(SelectQa.POPUP, firstParamName)).click();
// select field for second item
await this.page.click(slct(DashCommonQa.AliasSelectRight));
await this.page.locator(slct(SelectQa.POPUP, secondParamName)).click();
// click apply in all relation dialogs
await this.page.click(slct(DashCommonQa.AliasAddBtn));
await this.applyAliasesChanges();
await this.applyRelationsChanges();
}
async hasChanges() {
const saveButton = await this.page.locator(slct(ActionPanelDashSaveControlsQa.Save));
const disabledAttribute = await saveButton.getAttribute('disabled');
return disabledAttribute === null;
}
async clickSaveButton() {
// save the changes made on the dashboard
await this.page.click(slct(ActionPanelDashSaveControlsQa.Save));
}
async saveChanges() {
const savePromise = this.page.waitForRequest((request) =>
request.url().includes(URLS.savePath),
);
const deleteLockPromise = this.page.waitForRequest(URLS.deleteLock);
await this.clickSaveButton();
await Promise.all([deleteLockPromise, savePromise]);
await this.page.waitForSelector(slct(COMMON_SELECTORS.ACTION_PANEL_EDIT_BTN));
}
async saveChangesAsDraft() {
// save the changes made on the dashboard as a draft
await this.page
.waitForSelector(slct(ActionPanelDashSaveControlsQa.SaveAsDraft), {
timeout: BUTTON_CHECK_TIMEOUT,
})
.then(async () => {
// in ActionPanel, the default save button is "Save as Draft"
const deleteLockPromise = this.page.waitForRequest(URLS.deleteLock);
const savePromise = this.page.waitForRequest(URLS.savePath);
await this.page.click(slct(ActionPanelDashSaveControlsQa.SaveAsDraft));
await Promise.all([deleteLockPromise, savePromise]);
await this.page.waitForSelector(slct(COMMON_SELECTORS.ACTION_PANEL_EDIT_BTN));
})
.catch(async () => {
// in ActionPanel, the default save button is "Save"
await this.page.click(slct(COMMON_SELECTORS.ACTION_PANEL_SAVE_AS_BTN));
await clickDropDownOption(
this.page,
ActionPanelDashSaveControlsQa.SaveAsDraftDropdownItem,
);
});
}
async saveChangesAndPublish() {
// save and publish the changes made on the dashboard
await this.page
.waitForSelector(slct(ActionPanelDashSaveControlsQa.SaveAsDraft), {
timeout: BUTTON_CHECK_TIMEOUT,
})
.then(async () => {
// in ActionPanel, the default save button is "Save as Draft"
await this.page.click(slct(COMMON_SELECTORS.ACTION_PANEL_SAVE_AS_BTN));
await clickDropDownOption(
this.page,
ActionPanelDashSaveControlsQa.SaveAndPublishDropdownItem,
);
})
.catch(async () => {
// in ActionPanel, the default save button is "Save"
await this.page.click(slct(ActionPanelDashSaveControlsQa.Save));
});
}
async clickSaveChangesAsNewDash() {
await this.page.waitForSelector(slct(COMMON_SELECTORS.ACTION_PANEL_SAVE_AS_BTN));
await this.page.click(slct(COMMON_SELECTORS.ACTION_PANEL_SAVE_AS_BTN));
await clickDropDownOption(this.page, ActionPanelDashSaveControlsQa.SaveAsNewDropdownItem);
}
async saveChangesAsNewDash(dashName: string) {
// save your changes as a new dashboard
await this.clickSaveChangesAsNewDash();
// click the Apply button in the dashboard copy dialog
await this.page.waitForSelector(slct(EntryDialogQA.Apply));
await Promise.all([
this.page.waitForNavigation(),
entryDialogFillAndSave(this.page, dashName),
]);
}
async deleteDash(args?: {workbookId?: string}) {
const {workbookId = WorkbooksUrls.E2EWorkbook} = args ?? {};
const isCollectionsE2ETestsMode = await isEnabledFeature(
this.page,
Feature.CollectionsE2ETestsMode,
);
const urlOnDelete = isCollectionsE2ETestsMode ? workbookId : '/dashboards';
await deleteEntity(this.page, urlOnDelete);
}
async deleteDashFromEditMode() {
try {
await this.exitEditMode();
await this.deleteDash();
} catch {
// can't delete dash from edit mode
}
}
async deleteDashFromViewMode() {
try {
await this.deleteDash();
} catch {
// can't delete dash from view mode
}
}
async closeMobileModal() {
this.page.mouse.click(0, 0);
await waitForCondition(async () => {
const elements = await this.page.$$(DashboardPage.selectors.mobileModal);
return elements.length === 0;
});
}
async clickTabs() {
// click on the "tabs" button
await this.page.click(slct(COMMON_SELECTORS.ACTION_BTN_TABS));
}
async addTab(name?: string) {
// adding tab (by default: Tab 2)
await this.clickTabs();
await this.page.click(slct(DialogTabsQA.RowAdd));
if (name) {
await this.page.locator(slct(DialogTabsQA.ReadOnlyTabItem)).last().dblclick();
await this.page.locator(`${slct(DialogTabsQA.EditTabItem)} input`).fill(name);
}
await this.page.click(slct(DialogTabsQA.Save));
}
async changeTab({tabName, index}: {tabName?: string; index?: number}) {
const tabsContainerLocator = this.page.locator(slct(DashTabsQA.Root));
await expect(tabsContainerLocator).toBeVisible();
let tabLocator;
if (tabName) {
tabLocator = tabsContainerLocator.getByText(tabName);
} else if (typeof index === 'number') {
tabLocator = tabsContainerLocator
.locator(slct(DatalensTabsQa.Item))
.or(tabsContainerLocator.locator(slct(DatalensTabsQa.MobileItem)))
.nth(index);
} else {
throw new Error('Tabs selector not found');
}
const isTabLocatorVisible = await tabLocator.isVisible();
if (isTabLocatorVisible) {
await tabLocator.click();
return;
}
// it can be single switcher with tab name or some tabs and "more" switcher
const switcherLocator = tabsContainerLocator.locator(slct(DatalensTabsQa.SwitcherItem));
const switcherTabLocator = tabsContainerLocator
.locator(slct(DatalensTabsQa.MobileItem))
.first();
const isSwitcherVisible = await switcherLocator.isVisible();
const isSwitcherTabVisible = await switcherTabLocator.isVisible();
let mobileTabLocator;
if (isSwitcherVisible) {
mobileTabLocator = switcherLocator;
} else if (isSwitcherTabVisible) {
mobileTabLocator = switcherTabLocator;
} else {
throw new Error('Tabs selector not found');
}
await mobileTabLocator.click();
await expect(this.page.locator(slct(SelectQa.SHEET))).toBeVisible();
if (tabName) {
await this.page
.locator(
`${DashboardPage.selectors.selectItems}${DashboardPage.selectors.selectItemsMobile} ${DashboardPage.selectors.selectItemTitle} >> text=${tabName}`,
)
.click();
} else if (typeof index === 'number') {
await this.page
.locator(
`${DashboardPage.selectors.selectItems}${DashboardPage.selectors.selectItemsMobile} ${DashboardPage.selectors.selectItemTitle}`,
)
.nth(index)
.click();
}
return;
}
async changeTabAndGetState({tabName, timeout}: {tabName: string; timeout: number}) {
await Promise.all([this.page.waitForNavigation(), this.changeTab({tabName})]);
await this.page.waitForTimeout(timeout);
const stateParam = getUrlStateParam(this.page);
return stateParam;
}
async changeWidgetTab(tabName: string) {
const tabsContainer = await this.page.waitForSelector(
`${slct(DashkitQa.GRID_ITEM)} ${DashboardPage.selectors.tabsContainer}`,
);
// check for desktop tabs
const fullTab = await tabsContainer.$(DashboardPage.selectors.tabsList);
if (fullTab) {
const tab = await fullTab.waitForSelector(
`${DashboardPage.selectors.tabItem}:not(${DashboardPage.selectors.tabItemDisabled}) >> text=${tabName}`,
);
await tab.click();
return;
}
// check for mobile tabs
const shortTab = await tabsContainer.$(
`${DashboardPage.selectors.tabItem}${DashboardPage.selectors.tabItemActive}`,
);
if (shortTab) {
await shortTab.click();
const tab = await this.page.waitForSelector(
`${DashboardPage.selectors.selectItems} ${DashboardPage.selectors.selectItemTitle}:not(${DashboardPage.selectors.selectItemTitleDisabled}) >> text=${tabName}`,
);
await tab.click();
return;
}
throw new Error('Tabs selector not found');
}
async enableDashboardTOC() {
const dashboardSettings = new DashboardSettings(this.page);
await dashboardSettings.open();
await this.page.click(slct(DashboardDialogSettingsQa.TOCSwitch));
await dashboardSettings.save();
}
async waitForLoaderDisappear() {
try {
const dashLoader = await this.page.waitForSelector(
`.${COMMON_DASH_SELECTORS.DASH_LOADER}`,
{
timeout: DASH_LOADER_CHECK_TIMEOUT,
},
);
if (dashLoader) {
await this.page.waitForSelector(`.${COMMON_DASH_SELECTORS.DASH_LOADER}`, {
state: 'detached',
});
}
} catch {
// the loader was not appeared
}
}
async waitForOpeningRevisionsList() {
await this.revisions.openList();
}
async waitForOpeningDraft() {
await this.revisions.openDraft();
}
async waitForOpeningActual() {
await this.revisions.openActualVersion();
await this.waitForLoaderDisappear();
}
async waitForOpenTableOfContent() {
await this.tableOfContent.open();
}
async waitForCloseTableOfContent() {
await this.tableOfContent.close();
}
async waitForOpenDescription() {
await this.description.open();
}
async waitForCloseDescription() {
await this.description.close();
}
async isDescriptionViewMode() {
await this.description.isViewMode();
}
async isDescriptionEditMode() {
await this.description.isEditMode();
}
getSelectorLocatorByTitle({
title,
counter,
type = 'select',
}: {
title: string;
counter?: number;
type?: 'select' | 'input';
}) {
const controlSelector =
type === 'select' ? slct(ControlQA.controlSelect) : slct(ControlQA.controlInput);
return this.page
.locator(slct(ControlQA.chartkitControl))
.filter({hasText: title})
.locator(controlSelector)
.nth(counter === undefined ? 0 : counter);
}
async checkSelectValueByTitle({
title,
value,
}: {
title: string;
counter?: number;
value: string;
}) {
const selectLocator = this.getSelectorLocatorByTitle({title});
await expect(selectLocator).toContainText(value);
}
async setSelectWithTitle(
{title, counter}: {title: string; counter?: number},
valueTitle: string,
) {
const selectLocator = this.getSelectorLocatorByTitle({title, counter});
await selectLocator.click();
await this.page
.locator(slct(ControlQA.controlSelectItems))
.locator(`[data-value] >> text="${valueTitle}"`)
.click();
await expect(selectLocator).toContainText(valueTitle);
}
async waitForSomeItemVisible() {
await this.page.waitForSelector(slct('dashkit-grid-item'));
}
async waitForSomeChartItemVisible() {
const chartItemLocator = this.page.locator(DashboardPage.selectors.chartGridItemContainer);
await expect(chartItemLocator).toBeVisible();
}
async shouldNotContainsChartItems() {
await waitForCondition(async () => {
const elems = await this.page.$$(DashboardPage.selectors.chartGridItemContainer);
return elems.length === 0;
});
}
async clickFirstControlSettingsButton() {
const controlSettingsButton = await this.page.waitForSelector(
slct(ControlQA.controlSettings),
);
await controlSettingsButton.click();
}
async disableAutoupdateInFirstControl() {
await this.enterEditMode();
await this.clickFirstControlSettingsButton();
await this.page.locator(slct(DialogGroupControlQa.groupSettingsTab)).click();
await this.page
.locator(`${slct(DialogGroupControlQa.updateControlOnChangeCheckbox)} input`)
.setChecked(false);
await this.controlActions.applyControlSettings();
await this.saveChanges();
}
/**
* Selector for gridItem
*
* @param filter
* filter.byHeader - search by header text in tabs,
* if tab is not shown currently in tabs list then filter will fail
*
* filter.byEntiryId - filters only currently rendered charts entities
* if element is not presented in DOM selector will fail
*
* Otherwise default Locator options can be used: `has`, `hasNot`, `hasText`, `hasNotText`
*
* @return {Locator}
*/
async getGridItem(filter: {byHeader?: string; byEntiryId?: string} & LocatorOptionsType) {
let gridItemFilter: LocatorOptionsType;
if (filter.byHeader) {
gridItemFilter = {
has: this.page.locator(slct('widget-chart-tab', filter.byHeader)),
};
} else if (filter.byEntiryId) {
gridItemFilter = {
has: this.page.locator(slct(`chartkit-body-entry-${filter.byEntiryId}`)),
};
} else {
gridItemFilter = filter;
}
return this.page.locator(`.${DashkitQa.GRID_ITEM}`, gridItemFilter);
}
async waitForChartsRender(gridItemLocators: Locator[]) {
return Promise.all(
gridItemLocators.map((item) => {
return waitForCondition(() =>
item.locator(`.${COMMON_CHARTKIT_SELECTORS.chartkit}`).isVisible(),
);
}),
);
}
async waitForWidgetsRender() {
// content-wrapper has loaded class when all widgets are rendered
const loadedWrapper = this.page.locator(slct(DashBodyQa.ContentWrapper));
await expect(loadedWrapper).toHaveClass(new RegExp(LOADED_DASH_CLASS));
}
async getTableFirstRowTexts(gridItemLocator: Locator) {
const firstCellLocator = gridItemLocator.locator('tbody tr td:first-child');
return firstCellLocator.allInnerTexts();
}
async filterTableByText(
gridItemLocator: Locator,
text: string,
options?: LocatorClickOptionsType,
) {
const cellLocator = gridItemLocator.locator('tbody tr td:first-child').getByText(text);
return Promise.all([
this.waitForSuccessfulResponse(CommonUrls.PartialCreateDashState),
cellLocator.first().click(options),
]);
}
async resetChartFiltering(gridItemLocator: Locator) {
return gridItemLocator.locator(slct(ControlQA.filtersClear)).click();
}
// it cannot be used with single selectors as they have different loading times
async getGroupSelectorLabels() {
const controlLabelLocator = this.page.locator(slct(ControlQA.controlLabel));
const firstLabelLocator = controlLabelLocator.first();
// if one of selectors is visible, group selector is loaded
await expect(firstLabelLocator).toBeVisible();
const allLabelsLocators = await controlLabelLocator.all();
const labels = await Promise.all(allLabelsLocators.map((locator) => locator.textContent()));
return labels;
}
async getNewStateHashAfterAction(action: () => Promise<void>) {
const requestPromise = this.page.waitForRequest(CommonUrls.CreateDashState);
await action();
const finishedPromise = await requestPromise;
// check new state
const responseState = await finishedPromise.response();
const jsonState = await responseState?.json();
return jsonState?.hash;
}
async checkNoScroll({selector, locator}: {selector?: string; locator?: Locator}) {
const elementLocator = selector ? this.page.locator(selector) : locator;
if (!elementLocator) {
return;
}
await waitForCondition(async () => {
const hasNoScroll = await elementLocator.evaluate((element) => {
return element?.clientHeight === element?.scrollHeight;
});
return hasNoScroll;
});
}
/**
* Scroll page to the bottom using keyboard End key
* @returns Promise that resolves when scrolling is complete
*/
async scrollToBottom(): Promise<void> {
await this.page.locator(slct(DashBodyQa.App)).click();
await this.page.keyboard.press('End');
}
/**
* Click on action panel more button (ellipsis in the upper panel)
* @returns Promise that resolves when click is complete
*/
async clickActionPanelMoreButton(): Promise<void> {
await this.page.click(slct(COMMON_SELECTORS.ENTRY_PANEL_MORE_BTN));
}
}
export default DashboardPage;