kibana/x-pack/test/functional/page_objects/lens_page.ts
2021-06-15 09:44:43 +02:00

1073 lines
37 KiB
TypeScript

/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { delay } from 'bluebird';
import { FtrProviderContext } from '../ftr_provider_context';
import { logWrapper } from './log_wrapper';
export function LensPageProvider({ getService, getPageObjects }: FtrProviderContext) {
const log = getService('log');
const testSubjects = getService('testSubjects');
const retry = getService('retry');
const elasticChart = getService('elasticChart');
const find = getService('find');
const comboBox = getService('comboBox');
const browser = getService('browser');
const dashboardAddPanel = getService('dashboardAddPanel');
const PageObjects = getPageObjects([
'common',
'header',
'timePicker',
'common',
'visualize',
'dashboard',
'timeToVisualize',
]);
return logWrapper('lensPage', log, {
/**
* Clicks the index pattern filters toggle.
*/
async toggleIndexPatternFiltersPopover() {
await testSubjects.click('lnsIndexPatternFiltersToggle');
},
async findAllFields() {
return await testSubjects.findAll('lnsFieldListPanelField');
},
async isLensPageOrFail() {
return await testSubjects.existOrFail('lnsApp', { timeout: 1000 });
},
/**
* Move the date filter to the specified time range, defaults to
* a range that has data in our dataset.
*/
async goToTimeRange(fromTime?: string, toTime?: string) {
await PageObjects.timePicker.ensureHiddenNoDataPopover();
fromTime = fromTime || PageObjects.timePicker.defaultStartTime;
toTime = toTime || PageObjects.timePicker.defaultEndTime;
await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
},
/**
* Wait for the specified element to have text that passes the specified test.
*
* @param selector - the element selector
* @param test - the test function to run on the element's text
*/
async assertExpectedText(selector: string, test: (value?: string) => boolean) {
let actualText: string | undefined;
await retry.waitForWithTimeout('assertExpectedText', 1000, async () => {
actualText = await find.byCssSelector(selector).then((el) => el.getVisibleText());
return test(actualText);
});
if (!test(actualText)) {
throw new Error(`"${actualText}" did not match expectation.`);
}
},
/**
* Asserts that the specified element has the expected inner text.
*
* @param selector - the element selector
* @param expectedText - the expected text
*/
assertExactText(selector: string, expectedText: string) {
return this.assertExpectedText(selector, (value) => value === expectedText);
},
/**
* Clicks a visualize list item's title (in the visualize app).
*
* @param title - the title of the list item to be clicked
*/
clickVisualizeListItemTitle(title: string) {
return retry.try(async () => {
await testSubjects.click(`visListingTitleLink-${title}`);
await this.isLensPageOrFail();
});
},
/**
* Changes the specified dimension to the specified operation and (optinally) field.
*
* @param opts.dimension - the selector of the dimension being changed
* @param opts.operation - the desired operation ID for the dimension
* @param opts.field - the desired field for the dimension
* @param layerIndex - the index of the layer
*/
async configureDimension(
opts: {
dimension: string;
operation: string;
field?: string;
isPreviousIncompatible?: boolean;
keepOpen?: boolean;
palette?: string;
formula?: string;
},
layerIndex = 0
) {
await retry.try(async () => {
await testSubjects.click(`lns-layerPanel-${layerIndex} > ${opts.dimension}`);
await testSubjects.exists(`lns-indexPatternDimension-${opts.operation}`);
});
if (opts.operation === 'formula') {
await this.switchToFormula();
} else {
const operationSelector = opts.isPreviousIncompatible
? `lns-indexPatternDimension-${opts.operation} incompatible`
: `lns-indexPatternDimension-${opts.operation}`;
await testSubjects.click(operationSelector);
}
if (opts.field) {
const target = await testSubjects.find('indexPattern-dimension-field');
await comboBox.openOptionsList(target);
await comboBox.setElement(target, opts.field);
}
if (opts.formula) {
await this.typeFormula(opts.formula);
}
if (opts.palette) {
await this.setPalette(opts.palette);
}
if (!opts.keepOpen) {
await this.closeDimensionEditor();
}
},
/**
* Changes the specified dimension to the specified operation and (optinally) field.
*
* @param opts.dimension - the selector of the dimension being changed
* @param opts.operation - the desired operation ID for the dimension
* @param opts.field - the desired field for the dimension
* @param layerIndex - the index of the layer
*/
async configureReference(opts: {
operation?: string;
field?: string;
isPreviousIncompatible?: boolean;
}) {
if (opts.operation) {
const target = await testSubjects.find('indexPattern-subFunction-selection-row');
await comboBox.openOptionsList(target);
await comboBox.setElement(target, opts.operation);
}
if (opts.field) {
const target = await testSubjects.find('indexPattern-reference-field-selection-row');
await comboBox.openOptionsList(target);
await comboBox.setElement(target, opts.field);
}
},
/**
* Drags field to workspace
*
* @param field - the desired field for the dimension
* */
async dragFieldToWorkspace(field: string) {
await browser.html5DragAndDrop(
testSubjects.getCssSelector(`lnsFieldListPanelField-${field}`),
testSubjects.getCssSelector('lnsWorkspace')
);
await this.waitForLensDragDropToFinish();
await PageObjects.header.waitUntilLoadingHasFinished();
},
/**
* Drags field to geo field workspace
*
* @param field - the desired geo_point or geo_shape field
* */
async dragFieldToGeoFieldWorkspace(field: string) {
await browser.html5DragAndDrop(
testSubjects.getCssSelector(`lnsFieldListPanelField-${field}`),
testSubjects.getCssSelector('lnsGeoFieldWorkspace')
);
await this.waitForLensDragDropToFinish();
await PageObjects.header.waitUntilLoadingHasFinished();
},
/**
* Drags field to workspace
*
* @param field - the desired field for the dimension
* */
async clickField(field: string) {
await testSubjects.click(`lnsFieldListPanelField-${field}`);
},
async editField() {
await retry.try(async () => {
await testSubjects.click('lnsFieldListPanelEdit');
await testSubjects.missingOrFail('lnsFieldListPanelEdit');
});
},
async removeField() {
await retry.try(async () => {
await testSubjects.click('lnsFieldListPanelRemove');
await testSubjects.missingOrFail('lnsFieldListPanelRemove');
});
},
async searchField(name: string) {
await testSubjects.setValue('lnsIndexPatternFieldSearch', name, {
clearWithKeyboard: true,
typeCharByChar: true,
});
},
async waitForField(field: string) {
await retry.try(async () => {
await testSubjects.existOrFail(`lnsFieldListPanelField-${field}`);
});
},
async waitForFieldMissing(field: string) {
await retry.try(async () => {
await testSubjects.missingOrFail(`lnsFieldListPanelField-${field}`);
});
},
/**
* Copies field to chosen destination that is defined by distance of `steps`
* (right arrow presses) from it
*
* @param fieldName - the desired field for the dimension
* @param steps - number of steps user has to press right
* @param reverse - defines the direction of going through drops
* */
async dragFieldWithKeyboard(fieldName: string, steps = 1, reverse = false) {
const field = await find.byCssSelector(
`[data-test-subj="lnsDragDrop_draggable-${fieldName}"] [data-test-subj="lnsDragDrop-keyboardHandler"]`
);
await field.focus();
await browser.pressKeys(browser.keys.ENTER);
for (let i = 0; i < steps; i++) {
await browser.pressKeys(reverse ? browser.keys.LEFT : browser.keys.RIGHT);
}
await browser.pressKeys(browser.keys.ENTER);
await this.waitForLensDragDropToFinish();
await PageObjects.header.waitUntilLoadingHasFinished();
},
/**
* Selects draggable element and moves it by number of `steps`
*
* @param group - the group of the element
* @param index - the index of the element in the group
* @param steps - number of steps of presses right or left
* @param reverse - defines the direction of going through drops
* */
async dimensionKeyboardDragDrop(group: string, index = 0, steps = 1, reverse = false) {
const elements = await find.allByCssSelector(
`[data-test-subj="${group}"] [data-test-subj="lnsDragDrop-keyboardHandler"]`
);
const el = elements[index];
await el.focus();
await browser.pressKeys(browser.keys.ENTER);
for (let i = 0; i < steps; i++) {
await browser.pressKeys(reverse ? browser.keys.LEFT : browser.keys.RIGHT);
}
await browser.pressKeys(browser.keys.ENTER);
await this.waitForLensDragDropToFinish();
await PageObjects.header.waitUntilLoadingHasFinished();
},
/**
* Selects draggable element and reorders it by number of `steps`
*
* @param group - the group of the element
* @param index - the index of the element in the group
* @param steps - number of steps of presses right or left
* @param reverse - defines the direction of going through drops
* */
async dimensionKeyboardReorder(group: string, index = 0, steps = 1, reverse = false) {
const elements = await find.allByCssSelector(
`[data-test-subj="${group}"] [data-test-subj="lnsDragDrop-keyboardHandler"]`
);
const el = elements[index];
await el.focus();
await browser.pressKeys(browser.keys.ENTER);
for (let i = 0; i < steps; i++) {
await browser.pressKeys(reverse ? browser.keys.ARROW_UP : browser.keys.ARROW_DOWN);
}
await browser.pressKeys(browser.keys.ENTER);
await this.waitForLensDragDropToFinish();
await PageObjects.header.waitUntilLoadingHasFinished();
},
async waitForLensDragDropToFinish() {
await retry.try(async () => {
const exists = await find.existsByCssSelector('.lnsDragDrop-isActiveGroup');
if (exists) {
throw new Error('UI still in drag/drop mode');
}
});
},
/**
* Drags field to dimension trigger
*
* @param field - the desired field for the dimension
* @param dimension - the selector of the dimension being changed
* */
async dragFieldToDimensionTrigger(field: string, dimension: string) {
await browser.html5DragAndDrop(
testSubjects.getCssSelector(`lnsFieldListPanelField-${field}`),
testSubjects.getCssSelector(dimension)
);
await this.waitForLensDragDropToFinish();
await PageObjects.header.waitUntilLoadingHasFinished();
},
/**
* Drags field to dimension trigger
*
* @param from - the selector of the dimension being moved
* @param to - the selector of the dimension being dropped to
* */
async dragDimensionToDimension(from: string, to: string) {
await browser.html5DragAndDrop(
testSubjects.getCssSelector(from),
testSubjects.getCssSelector(to)
);
await this.waitForLensDragDropToFinish();
await PageObjects.header.waitUntilLoadingHasFinished();
},
/**
* Reorder elements within the group
*
* @param startIndex - the index of dragging element starting from 1
* @param endIndex - the index of drop starting from 1
* */
async reorderDimensions(dimension: string, startIndex: number, endIndex: number) {
const dragging = `[data-test-subj='${dimension}']:nth-of-type(${startIndex}) .lnsDragDrop`;
const dropping = `[data-test-subj='${dimension}']:nth-of-type(${endIndex}) [data-test-subj='lnsDragDrop-reorderableDropLayer'`;
await browser.html5DragAndDrop(dragging, dropping);
await this.waitForLensDragDropToFinish();
await PageObjects.header.waitUntilLoadingHasFinished();
},
async assertPalette(palette: string) {
await retry.try(async () => {
await testSubjects.click('lns-palettePicker');
const currentPalette = await (
await find.byCssSelector('[role=option][aria-selected=true]')
).getAttribute('id');
expect(currentPalette).to.equal(palette);
});
},
async toggleToolbarPopover(buttonTestSub: string) {
await testSubjects.click(buttonTestSub);
},
/**
* Open the specified dimension.
*
* @param dimension - the selector of the dimension panel to open
* @param layerIndex - the index of the layer
*/
async openDimensionEditor(dimension: string, layerIndex = 0) {
await retry.try(async () => {
await testSubjects.click(`lns-layerPanel-${layerIndex} > ${dimension}`);
});
},
async isDimensionEditorOpen() {
return await testSubjects.exists('lns-indexPattern-dimensionContainerBack');
},
// closes the dimension editor flyout
async closeDimensionEditor() {
await retry.try(async () => {
await testSubjects.click('lns-indexPattern-dimensionContainerBack');
await testSubjects.missingOrFail('lns-indexPattern-dimensionContainerBack');
});
},
async enableTimeShift() {
await testSubjects.click('indexPattern-advanced-popover');
await retry.try(async () => {
await testSubjects.click('indexPattern-time-shift-enable');
});
},
async setTimeShift(shift: string) {
await comboBox.setCustom('indexPattern-dimension-time-shift', shift);
},
async hasFixAction() {
return await testSubjects.exists('errorFixAction');
},
async useFixAction() {
await testSubjects.click('errorFixAction');
},
async isTopLevelAggregation() {
return await testSubjects.isEuiSwitchChecked('indexPattern-nesting-switch');
},
/**
* Removes the dimension matching a specific test subject
*/
async removeDimension(dimensionTestSubj: string) {
await testSubjects.click(`${dimensionTestSubj} > indexPattern-dimension-remove`);
},
/**
* adds new filter to filters agg
*/
async addFilterToAgg(queryString: string) {
await testSubjects.click('lns-newBucket-add');
const queryInput = await testSubjects.find('indexPattern-filters-queryStringInput');
await queryInput.type(queryString);
// Problem here is that after typing in the queryInput a dropdown will fetch the server
// with suggestions and show up. Depending on the cursor position and some other factors
// pressing Enter at this point may lead to auto-complete the queryInput with random stuff from the
// dropdown which was not intended originally.
// To close the Filter popover we need to move to the label input and then press Enter:
// solution is to press Tab 2 twice (first Tab will close the dropdown) instead of Enter to avoid
// race condition with the dropdown
await PageObjects.common.pressTabKey();
await PageObjects.common.pressTabKey();
// Now it is safe to press Enter as we're in the label input
await PageObjects.common.pressEnterKey();
await PageObjects.common.sleep(1000); // give time for debounced components to rerender
},
/**
* Save the current Lens visualization.
*/
async save(
title: string,
saveAsNew?: boolean,
redirectToOrigin?: boolean,
saveToLibrary?: boolean,
addToDashboard?: 'new' | 'existing' | null,
dashboardId?: string
) {
await PageObjects.header.waitUntilLoadingHasFinished();
await testSubjects.click('lnsApp_saveButton');
await PageObjects.timeToVisualize.setSaveModalValues(title, {
saveAsNew,
redirectToOrigin,
addToDashboard: addToDashboard ? addToDashboard : null,
dashboardId,
saveToLibrary,
});
await testSubjects.click('confirmSaveSavedObjectButton');
await retry.waitForWithTimeout('Save modal to disappear', 1000, () =>
testSubjects
.missingOrFail('confirmSaveSavedObjectButton')
.then(() => true)
.catch(() => false)
);
},
async saveAndReturn() {
await testSubjects.click('lnsApp_saveAndReturnButton');
},
async editDimensionLabel(label: string) {
await testSubjects.setValue('indexPattern-label-edit', label, { clearWithKeyboard: true });
},
async editDimensionFormat(format: string) {
const formatInput = await testSubjects.find('indexPattern-dimension-format');
await comboBox.openOptionsList(formatInput);
await comboBox.setElement(formatInput, format);
},
async editDimensionColor(color: string) {
const colorPickerInput = await testSubjects.find('colorPickerAnchor');
await colorPickerInput.type(color);
await PageObjects.common.sleep(1000); // give time for debounced components to rerender
},
async openVisualOptions() {
await retry.try(async () => {
await testSubjects.click('lnsVisualOptionsButton');
await testSubjects.exists('lnsVisualOptionsButton');
});
},
async useCurvedLines() {
await testSubjects.click('lnsCurveStyleToggle');
},
async editMissingValues(option: string) {
await testSubjects.click('lnsMissingValuesSelect');
const optionSelector = await find.byCssSelector(`#${option}`);
await optionSelector.click();
},
getTitle() {
return testSubjects.getAttribute('lns_ChartTitle', 'innerText');
},
async getFiltersAggLabels() {
const labels = [];
const filters = await testSubjects.findAll('indexPattern-filters-existingFilterContainer');
for (let i = 0; i < filters.length; i++) {
labels.push(await filters[i].getVisibleText());
}
log.debug(`Found ${labels.length} filters on current page`);
return labels;
},
/**
* Uses the Lens visualization switcher to switch visualizations.
*
* @param subVisualizationId - the ID of the sub-visualization to switch to, such as
* lnsDatatable or bar_stacked
*/
async switchToVisualization(subVisualizationId: string, searchTerm?: string) {
await this.openChartSwitchPopover();
await this.searchOnChartSwitch(subVisualizationId, searchTerm);
await testSubjects.click(`lnsChartSwitchPopover_${subVisualizationId}`);
await PageObjects.header.waitUntilLoadingHasFinished();
},
async openChartSwitchPopover() {
if (await testSubjects.exists('lnsChartSwitchList')) {
return;
}
await retry.try(async () => {
await testSubjects.click('lnsChartSwitchPopover');
await testSubjects.existOrFail('lnsChartSwitchList');
});
},
async changeAxisSide(newSide: string) {
await testSubjects.click(`lnsXY_axisSide_groups_${newSide}`);
},
/** Counts the visible warnings in the config panel */
async getErrorCount() {
const moreButton = await testSubjects.exists('configuration-failure-more-errors');
if (moreButton) {
await retry.try(async () => {
await testSubjects.click('configuration-failure-more-errors');
await testSubjects.missingOrFail('configuration-failure-more-errors');
});
}
const errors = await testSubjects.findAll('configuration-failure-error');
const expressionErrors = await testSubjects.findAll('expression-failure');
return (errors?.length ?? 0) + (expressionErrors?.length ?? 0);
},
async searchOnChartSwitch(subVisualizationId: string, searchTerm?: string) {
// Because the new chart switcher is now a virtualized list, the process needs some help
// So either pass a search string or pick the last 3 letters from the id (3 because pie
// is the smallest chart name) and use them to search
const queryTerm = searchTerm ?? subVisualizationId.substring(subVisualizationId.length - 3);
return await testSubjects.setValue('lnsChartSwitchSearch', queryTerm, {
clearWithKeyboard: true,
});
},
/**
* Checks a specific subvisualization in the chart switcher for a "data loss" indicator
*
* @param subVisualizationId - the ID of the sub-visualization to switch to, such as
* lnsDatatable or bar_stacked
*/
async hasChartSwitchWarning(subVisualizationId: string, searchTerm?: string) {
await this.openChartSwitchPopover();
await this.searchOnChartSwitch(subVisualizationId, searchTerm);
const element = await testSubjects.find(`lnsChartSwitchPopover_${subVisualizationId}`);
return await testSubjects.descendantExists(
`lnsChartSwitchPopoverAlert_${subVisualizationId}`,
element
);
},
/**
* Uses the Lens layer switcher to switch seriesType for xy charts.
*
* @param subVisualizationId - the ID of the sub-visualization to switch to, such as
* line,
*/
async switchLayerSeriesType(seriesType: string) {
await retry.try(async () => {
await testSubjects.click('lns_layer_settings');
await testSubjects.exists(`lnsXY_seriesType-${seriesType}`);
});
return await testSubjects.click(`lnsXY_seriesType-${seriesType}`);
},
/**
* Returns the number of layers visible in the chart configuration
*/
async getLayerCount() {
const elements = await testSubjects.findAll('lnsLayerRemove');
return elements.length;
},
/**
* Adds a new layer to the chart, fails if the chart does not support new layers
*/
async createLayer() {
await testSubjects.click('lnsLayerAddButton');
},
/**
* Changes the index pattern in the data panel
*/
async switchDataPanelIndexPattern(name: string) {
await testSubjects.click('indexPattern-switch-link');
await find.clickByCssSelector(`[title="${name}"]`);
await PageObjects.header.waitUntilLoadingHasFinished();
},
/**
* Changes the index pattern for the first layer
*/
async switchFirstLayerIndexPattern(name: string) {
await testSubjects.click('lns_layerIndexPatternLabel');
await find.clickByCssSelector(`[title="${name}"]`);
await PageObjects.header.waitUntilLoadingHasFinished();
},
/**
* Returns the current index pattern of the data panel
*/
async getDataPanelIndexPattern() {
return await (await testSubjects.find('indexPattern-switch-link')).getAttribute('title');
},
/**
* Returns the current index pattern of the first layer
*/
async getFirstLayerIndexPattern() {
return await (await testSubjects.find('lns_layerIndexPatternLabel')).getAttribute('title');
},
async linkedToOriginatingApp() {
await PageObjects.header.waitUntilLoadingHasFinished();
await testSubjects.existOrFail('lnsApp_saveAndReturnButton');
},
async notLinkedToOriginatingApp() {
await PageObjects.header.waitUntilLoadingHasFinished();
await testSubjects.missingOrFail('lnsApp_saveAndReturnButton');
},
/**
* Gets label of dimension trigger in dimension panel
*
* @param dimension - the selector of the dimension
* @param index - the index of the dimension trigger in group
*/
async getDimensionTriggerText(dimension: string, index = 0) {
const dimensionTexts = await this.getDimensionTriggersTexts(dimension);
return dimensionTexts[index];
},
/**
* Gets label of all dimension triggers in dimension group
*
* @param dimension - the selector of the dimension
*/
async getDimensionTriggersTexts(dimension: string) {
return retry.try(async () => {
const dimensionElements = await testSubjects.findAll(`${dimension} > lns-dimensionTrigger`);
const dimensionTexts = await Promise.all(
await dimensionElements.map(async (el) => await el.getVisibleText())
);
return dimensionTexts;
});
},
async isShowingNoResults() {
return (
(await (await testSubjects.find('lnsWorkspace')).getVisibleText()) === 'No results found'
);
},
async getCurrentChartDebugState() {
return await elasticChart.getChartDebugData('lnsWorkspace');
},
/**
* Gets text of the specified datatable header cell
*
* @param index - index of th element in datatable
*/
async getDatatableHeaderText(index = 0) {
const el = await this.getDatatableHeader(index);
return el.getVisibleText();
},
/**
* Gets text of the specified datatable cell
*
* @param rowIndex - index of row of the cell
* @param colIndex - index of column of the cell
*/
async getDatatableCellText(rowIndex = 0, colIndex = 0) {
const el = await this.getDatatableCell(rowIndex, colIndex);
return el.getVisibleText();
},
async getDatatableCellStyle(rowIndex = 0, colIndex = 0) {
const el = await this.getDatatableCell(rowIndex, colIndex);
const styleString = await el.getAttribute('style');
return styleString.split(';').reduce<Record<string, string>>((memo, cssLine) => {
const [prop, value] = cssLine.split(':');
if (prop && value) {
memo[prop.trim()] = value.trim();
}
return memo;
}, {});
},
async getDatatableHeader(index = 0) {
return find.byCssSelector(
`[data-test-subj="lnsDataTable"] [data-test-subj="dataGridHeader"] [role=columnheader]:nth-child(${
index + 1
})`
);
},
async getDatatableCell(rowIndex = 0, colIndex = 0) {
const table = await find.byCssSelector('.euiDataGrid');
const $ = await table.parseDomContent();
const columnNumber = $('.euiDataGridHeaderCell__content').length;
return await find.byCssSelector(
`[data-test-subj="lnsDataTable"] [data-test-subj="dataGridRowCell"]:nth-child(${
rowIndex * columnNumber + colIndex + 2
})`
);
},
async isDatatableHeaderSorted(index = 0) {
return find.existsByCssSelector(
`[data-test-subj="lnsDataTable"] [data-test-subj="dataGridHeader"] [role=columnheader]:nth-child(${
index + 1
}) [data-test-subj^="dataGridHeaderCellSortingIcon"]`
);
},
async changeTableSortingBy(colIndex = 0, direction: 'none' | 'ascending' | 'descending') {
const el = await this.getDatatableHeader(colIndex);
await el.click();
let buttonEl;
if (direction !== 'none') {
buttonEl = await find.byCssSelector(
`[data-test-subj^="dataGridHeaderCellActionGroup"] [title="Sort ${direction}"]`
);
} else {
buttonEl = await find.byCssSelector(
`[data-test-subj^="dataGridHeaderCellActionGroup"] li[class$="selected"] [title^="Sort"]`
);
}
return buttonEl.click();
},
async setTableSummaryRowFunction(
summaryFunction: 'none' | 'sum' | 'avg' | 'count' | 'min' | 'max'
) {
await testSubjects.click('lnsDatatable_summaryrow_function');
await testSubjects.click('lns-datatable-summary-' + summaryFunction);
},
async setTableSummaryRowLabel(newLabel: string) {
await testSubjects.setValue('lnsDatatable_summaryrow_label', newLabel, {
clearWithKeyboard: true,
typeCharByChar: true,
});
},
async setTableDynamicColoring(coloringType: 'none' | 'cell' | 'text') {
await testSubjects.click('lnsDatatable_dynamicColoring_groups_' + coloringType);
},
async openTablePalettePanel() {
await testSubjects.click('lnsDatatable_dynamicColoring_trigger');
},
async closeTablePalettePanel() {
await testSubjects.click('lns-indexPattern-PalettePanelContainerBack');
},
// different picker from the next one
async changePaletteTo(paletteName: string) {
await testSubjects.click('lnsDatatable_dynamicColoring_palette_picker');
await testSubjects.click(`${paletteName}-palette`);
},
async setPalette(paletteName: string) {
await testSubjects.click('lns-palettePicker');
await find.clickByCssSelector(`#${paletteName}`);
},
async closePaletteEditor() {
await retry.try(async () => {
await testSubjects.click('lns-indexPattern-PalettePanelContainerBack');
await testSubjects.missingOrFail('lns-indexPattern-PalettePanelContainerBack');
});
},
async openColorStopPopup(index = 0) {
const stopEls = await testSubjects.findAll('euiColorStopThumb');
if (stopEls[index]) {
await stopEls[index].click();
}
},
async setColorStopValue(value: number | string) {
await testSubjects.setValue(
'lnsDatatable_dynamicColoring_progression_custom_stops_value',
String(value)
);
},
async toggleColumnVisibility(dimension: string) {
await this.openDimensionEditor(dimension);
const id = 'lns-table-column-hidden';
const isChecked = await testSubjects.isEuiSwitchChecked(id);
await testSubjects.setEuiSwitch(id, isChecked ? 'uncheck' : 'check');
await this.closeDimensionEditor();
await PageObjects.header.waitUntilLoadingHasFinished();
},
async clickTableCellAction(rowIndex = 0, colIndex = 0, actionTestSub: string) {
const el = await this.getDatatableCell(rowIndex, colIndex);
await el.focus();
const action = await el.findByTestSubject(actionTestSub);
return action.click();
},
/**
* Asserts that metric has expected title and count
*
* @param title - expected title
* @param count - expected count of metric
*/
async assertMetric(title: string, count: string) {
await this.assertExactText('[data-test-subj="lns_metric_title"]', title);
await this.assertExactText('[data-test-subj="lns_metric_value"]', count);
},
async assertMissingValues(option: string) {
await this.assertExactText('[data-test-subj="lnsMissingValuesSelect"]', option);
},
async assertColor(color: string) {
// TODO: target dimensionTrigger color element after merging https://github.com/elastic/kibana/pull/76871
await testSubjects.getAttribute('colorPickerAnchor', color);
},
/**
* Creates and saves a lens visualization from a dashboard
*
* @param title - title for the new lens. If left undefined, the panel will be created by value
* @param redirectToOrigin - whether to redirect back to the dashboard after saving the panel
*/
async createAndAddLensFromDashboard({
title,
redirectToOrigin,
}: {
title?: string;
redirectToOrigin?: boolean;
}) {
log.debug(`createAndAddLens${title}`);
const inViewMode = await PageObjects.dashboard.getIsInViewMode();
if (inViewMode) {
await PageObjects.dashboard.switchToEditMode();
}
await dashboardAddPanel.clickCreateNewLink();
await this.goToTimeRange();
await this.configureDimension({
dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension',
operation: 'date_histogram',
field: '@timestamp',
});
await this.configureDimension({
dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension',
operation: 'average',
field: 'bytes',
});
await this.configureDimension({
dimension: 'lnsXY_splitDimensionPanel > lns-empty-dimension',
operation: 'terms',
field: 'ip',
});
if (title) {
await this.save(title, false, redirectToOrigin);
} else {
await this.saveAndReturn();
}
},
/**
* Asserts that the focused element is a field with a specified text
*
* @param name - the element visible text
*/
async assertFocusedField(name: string) {
const input = await find.activeElement();
const fieldAncestor = await input.findByXpath('./../../..');
const focusedElementText = await fieldAncestor.getVisibleText();
const dataTestSubj = await fieldAncestor.getAttribute('data-test-subj');
expect(focusedElementText).to.eql(name);
expect(dataTestSubj).to.eql('lnsFieldListPanelField');
},
/**
* Asserts that the focused element is a dimension with with a specified text
*
* @param name - the element visible text
*/
async assertFocusedDimension(name: string) {
const input = await find.activeElement();
const fieldAncestor = await input.findByXpath('./../../..');
const focusedElementText = await fieldAncestor.getVisibleText();
expect(focusedElementText).to.eql(name);
},
async waitForVisualization() {
async function getRenderingCount() {
const visualizationContainer = await testSubjects.find('lnsVisualizationContainer');
const renderingCount = await visualizationContainer.getAttribute('data-rendering-count');
return Number(renderingCount);
}
await PageObjects.header.waitUntilLoadingHasFinished();
await retry.waitFor('rendering count to stabilize', async () => {
const firstCount = await getRenderingCount();
await PageObjects.common.sleep(1000);
const secondCount = await getRenderingCount();
return firstCount === secondCount;
});
},
async clickAddField() {
await testSubjects.click('lnsIndexPatternActions');
await testSubjects.existOrFail('indexPattern-add-field');
await testSubjects.click('indexPattern-add-field');
},
/** resets visualization/layer or removes a layer */
async removeLayer() {
await testSubjects.click('lnsLayerRemove');
},
/**
* Starts dragging @param dragging, drags over @param draggedOver and drops it into @dropTarget
*/
async dragEnterDrop(dragging: string, draggedOver: string, dropTarget: string) {
await browser.execute(
`
function createEvent(typeOfEvent) {
const event = document.createEvent("CustomEvent");
event.initCustomEvent(typeOfEvent, true, true, null);
event.dataTransfer = {
data: {},
setData: function(key, value) {
this.data[key] = value;
},
getData: function(key) {
return this.data[key];
}
};
return event;
}
function dispatchEvent(element, event, transferData) {
if (transferData !== undefined) {
event.dataTransfer = transferData;
}
if (element.dispatchEvent) {
element.dispatchEvent(event);
} else if (element.fireEvent) {
element.fireEvent("on" + event.type, event);
}
}
const origin = document.querySelector(arguments[0]);
const dragStartEvent = createEvent('dragstart');
dispatchEvent(origin, dragStartEvent);
setTimeout(() => {
const target = document.querySelector(arguments[1]);
const dragenter = createEvent('dragenter');
const dragover = createEvent('dragover');
dispatchEvent(target, dragenter, dragStartEvent.dataTransfer);
dispatchEvent(target, dragover, dragStartEvent.dataTransfer);
setTimeout(() => {
const target = document.querySelector(arguments[2]);
const dropEvent = createEvent('drop');
dispatchEvent(target, dropEvent, dragStartEvent.dataTransfer);
const dragEndEvent = createEvent('dragend');
dispatchEvent(origin, dragEndEvent, dropEvent.dataTransfer);
}, 100)
}, 100);
`,
dragging,
draggedOver,
dropTarget
);
await delay(150);
},
/**
* Drags field to dimension trigger to extra drop type
*
* @param from - the selector of the dimension being moved
* @param to - the selector of the main drop type of dimension being dropped to
* @param type - extra drop type
* */
async dragDimensionToExtraDropType(from: string, to: string, type: 'duplicate' | 'swap') {
await this.dragEnterDrop(
testSubjects.getCssSelector(from),
testSubjects.getCssSelector(`${to} > lnsDragDrop`),
testSubjects.getCssSelector(`${to} > lnsDragDrop-${type}`)
);
await PageObjects.header.waitUntilLoadingHasFinished();
},
async switchToQuickFunctions() {
await testSubjects.click('lens-dimensionTabs-quickFunctions');
},
async switchToFormula() {
await testSubjects.click('lens-dimensionTabs-formula');
},
async toggleFullscreen() {
await testSubjects.click('lnsFormula-fullscreen');
},
async typeFormula(formula: string) {
// Formula takes time to open
await PageObjects.common.sleep(500);
await find.byCssSelector('.monaco-editor');
await find.clickByCssSelectorWhenNotDisabled('.monaco-editor');
const input = await find.activeElement();
await input.clearValueWithKeyboard();
await input.type(formula);
},
});
}