import {Page} from '@playwright/test'; import {ChartkitMenuDialogsQA, ChartKitTableQa, ChartQa, MenuItemsQA} from '../../../src/shared'; import {slct, waitForCondition} from '../../utils'; import {readDownload} from '../../utils/playwright/utils'; import {COMMON_CHARTKIT_SELECTORS} from '../constants/chartkit'; export enum DOMNamedAttributes { StrokeDashArray = 'stroke-dasharray', Stroke = 'stroke', } export const CHARTKIT_MENU_ITEMS_SELECTORS = { menuCommentsQA: 'comments', menuShowCommentsQA: 'showComments', menuHideCommentsQA: 'hideComments', inspectorQA: 'inspector', shareQA: 'getLink', exportQA: 'export', alertsQA: 'alerts', }; export default class ChartKit { geopointSelector = '.chartkit-yandex-map-chips'; tooltipSelector = '.chartkit-tooltip'; yMapSelector = '.chartkit-ymap'; legendItemSelector = COMMON_CHARTKIT_SELECTORS.chartLegendItem; chartMenuSwitcher = slct('chart-dropdown-switcher'); chartMenuList = slct('chart-dropdown-menu'); commentsSelector = '.highcharts-comment'; alertLimitSelector = '.highcharts-plot-band'; xAxisLabel = '.highcharts-xaxis-labels text, .gcharts-x-axis__label'; chartTitle = ['.highcharts-title', slct('g-charts-title')].join(); navigator = '.highcharts-navigator, .gcharts-range-slider'; metricItemSelector = ['.chartkit-indicator__item', slct(ChartQa.Chart)].join(); metricItemValueSelector = '.chartkit-indicator__item-value,.markup-metric-value'; metricItemTitleSelector = '.chartkit-indicator__item-title,.markup-metric-title'; private drillArrowsSelector = '.chartkit-drill__drill-action'; private paginatorSelector = '.chartkit-table-paginator'; private tableHeadCellSelector = '.data-table__head-cell'; private layerLegendSelector = '.chartkit .chartkit-ymap-legend-layer'; private labelsSelector = '.highcharts-data-labels .highcharts-data-label, .gcharts-line__label, .gcharts-bar-x__label'; private chartkitSeriesRect = '.chartkit-highcharts rect.highcharts-point, .gcharts-bar-x__segment'; private page: Page; constructor(page: Page) { this.page = page; } async openChartMenu() { await this.page.hover(slct(ChartkitMenuDialogsQA.chartWidget)); await this.page.waitForSelector(this.chartMenuSwitcher); await this.page.locator(this.chartMenuSwitcher).click(); } async waitForItemInMenu(qaName: string) { await this.page.waitForSelector(`${this.chartMenuList} [data-qa="${qaName}"]`); } async openChartMenuAndClickItem(menuItemSelector: string) { await waitForCondition(async () => { await this.openChartMenu(); try { await this.page.click(menuItemSelector, {timeout: 2000}); return true; } catch { return false; } }); } async clickMenuItem(qaSelector: string) { await this.page.locator(slct(ChartkitMenuDialogsQA.chartWidget)).hover(); await this.page.waitForSelector(this.chartMenuSwitcher); await this.openChartMenu(); await this.waitForItemInMenu(qaSelector); await this.page.click(`[data-qa="${qaSelector}"]`); } async getComments() { // while in the tests we take into account only comments-lines, for areas and so on, the logic may be different const locator = this.page .locator(`path${this.commentsSelector}`) .or(this.page.locator(slct(ChartQa.CommentLabel))); return await locator.all(); } async openScreenshotDialog() { await this.openExportMenuAndClickSubItem(slct(MenuItemsQA.EXPORT_SCREENSHOT)); } async drillDown() { await this.page.waitForSelector(this.drillArrowsSelector); const [_leftArrow, rightArrow] = await this.page.$$(this.drillArrowsSelector); await rightArrow.click(); } async drillUp() { await this.page.waitForSelector(this.drillArrowsSelector); const [leftArrow] = await this.page.$$(this.drillArrowsSelector); await leftArrow.click(); } async waitForErrorTitle(errorQa: string) { await this.page.waitForSelector(slct(errorQa)); } getTableLocator() { return this.page .locator('.chartkit-table') .or(this.page.locator(slct(ChartKitTableQa.Widget))); } async waitForSuccessfulRender() { const locator = this.page .locator(COMMON_CHARTKIT_SELECTORS.chart) .or(this.getTableLocator()); await locator.waitFor(); } async openExportMenuAndClickSubItem(subItemSelector: string) { await this.openChartMenu(); await this.page.hover(slct(MenuItemsQA.EXPORT)); const subItem = await this.page.waitForSelector(subItemSelector); await subItem.click(); } async openCsvExportDialog() { await this.openExportMenuAndClickSubItem(slct(MenuItemsQA.EXPORT_CSV)); return this.page.waitForSelector(slct(ChartkitMenuDialogsQA.chartMenuExportCsvDialog)); } async exportCsv(options?: {fractionDelimiter?: string}) { const downloadPromise = this.page.waitForEvent('download'); const dialog = await this.openCsvExportDialog(); if (options?.fractionDelimiter) { const selectFloatSelector = await dialog.waitForSelector( slct(ChartkitMenuDialogsQA.chartMenuExportCsvSelectFloat), ); await selectFloatSelector.click(); await this.page.locator(slct(options.fractionDelimiter)).click(); } await this.page.click(slct(ChartkitMenuDialogsQA.chartMenuExportModalApplyBtn)); const download = await downloadPromise; const content = await readDownload(download); await download.delete(); return content?.toString('utf8'); } async exportMarkdown() { await this.openExportMenuAndClickSubItem(slct(MenuItemsQA.EXPORT_MARKDOWN)); return await this.page.evaluate(async () => { return await navigator.clipboard.readText(); }); } async openInNewTab() { const newTabPagePromise: Promise = new Promise((resolve) => this.page.context().on('page', resolve), ); const selector = slct(MenuItemsQA.NEW_WINDOW); await this.openChartMenuAndClickItem(selector); return newTabPagePromise; } async saveAsPicture() { const newTabPagePromise: Promise = new Promise((resolve) => this.page.context().on('page', resolve), ); await this.openScreenshotDialog(); await this.page.click(slct('download-screenshot-modal-download-btn')); return newTabPagePromise; } async waitForPaginationExist() { await this.page.waitForSelector(this.paginatorSelector); } async waitForPaginationNotExist() { await this.page.waitForSelector(this.paginatorSelector, {state: 'detached'}); } async getTableRowsCount() { return await this.getTableLocator().locator('tbody tr').count(); } getLayerLegend() { return this.page.$(this.layerLegendSelector); } async getHeadRowsHtml() { const headerRows = this.getTableLocator().locator('thead').first().locator('tr'); const headCellContent = this.page .locator(this.tableHeadCellSelector) .or(this.page.locator(slct(ChartKitTableQa.HeadCellContent))); await headerRows.first().waitFor(); const rows = await headerRows.all(); return await Promise.all( rows.map(async (row) => Promise.all( (await row.locator(headCellContent).all()).map((cell) => cell.innerHTML()), ), ), ); } async getHeadRowsTexts() { const headerRows = this.getTableLocator().locator('thead').first().locator('tr'); await headerRows.first().waitFor(); const rows = await headerRows.all(); return await Promise.all(rows.map((row) => row.locator('th').allTextContents())); } async getRowsTexts() { const rowLocator = this.getTableLocator().locator('tbody tr'); await rowLocator.first().waitFor(); const rows = await rowLocator.all(); const content = await Promise.all(rows.map((row) => row.locator('td').allTextContents())); return content.map((row) => { return row.map((rowValue) => rowValue.replace(/\u00a0/g, ' ')); }); } async waitUntilLoaderExists() { await waitForCondition(async () => { return !(await this.page.$('.chartkit-loader.chartkit-loader_veil')); }).catch(() => { throw new Error("Loader didn't disappear"); }); } async getAttributeFromLines(attributeName: DOMNamedAttributes) { let lines: string[] = []; await waitForCondition(async () => { lines = await this.page.evaluate( ({attribute}: {attribute: DOMNamedAttributes}) => { const lineNodes = document.querySelectorAll( '.highcharts-series .highcharts-graph, .gcharts-line path', ); return Array.from(lineNodes) .map((lineEl) => lineEl.attributes.getNamedItem(attribute)?.value) .filter(Boolean) as string[]; }, {attribute: attributeName} as {attribute: DOMNamedAttributes}, ); return lines.length; }); return lines; } async navigateToNextTablePage(times: number) { for (let i = 0; i < times; i++) { await this.page.click(slct('chartkit-table-paginator-next-btn')); } } async getSeriesClientRect() { const locator = this.page.locator(this.chartkitSeriesRect); return await locator.evaluateAll((elements) => elements.map((el) => el.getBoundingClientRect()), ); } async getLabelsClientRect() { const locator = this.page.locator(this.labelsSelector); return await locator.evaluateAll((elements) => elements.map((el) => el.getBoundingClientRect()), ); } async getLabelsTexts() { await this.page.waitForSelector(this.labelsSelector); const locator = this.page.locator(this.labelsSelector); await locator.first().waitFor(); const labels = await locator.allTextContents(); return labels.map((rowValue) => rowValue.replace(/\u00a0/g, ' ')); } }