[Lens] Functional tests for drag and drop (#82796)

This commit is contained in:
Wylie Conlon 2020-11-11 06:36:04 -05:00 committed by GitHub
parent 7d3e19801f
commit ac150da49d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 258 additions and 28 deletions

View file

@ -216,13 +216,17 @@ export async function BrowserProvider({ getService }: FtrProviderContext) {
* Does a drag-and-drop action from one point to another
* https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/input_exports_Actions.html#dragAndDrop
*
* @param {{element: WebElementWrapper | {x: number, y: number}, offset: {x: number, y: number}}} from
* @param {{element: WebElementWrapper | {x: number, y: number}, offset: {x: number, y: number}}} to
* @return {Promise<void>}
*/
public async dragAndDrop(
from: { offset?: { x: any; y: any }; location: any },
to: { offset?: { x: any; y: any }; location: any }
from: {
location: WebElementWrapper | { x?: number; y?: number };
offset?: { x?: number; y?: number };
},
to: {
location: WebElementWrapper | { x?: number; y?: number };
offset?: { x?: number; y?: number };
}
) {
// The offset should be specified in pixels relative to the center of the element's bounding box
const getW3CPoint = (data: any) => {
@ -230,7 +234,11 @@ export async function BrowserProvider({ getService }: FtrProviderContext) {
data.offset = {};
}
return data.location instanceof WebElementWrapper
? { x: data.offset.x || 0, y: data.offset.y || 0, origin: data.location._webElement }
? {
x: data.offset.x || 0,
y: data.offset.y || 0,
origin: data.location._webElement,
}
: { x: data.location.x, y: data.location.y, origin: Origin.POINTER };
};
@ -240,6 +248,62 @@ export async function BrowserProvider({ getService }: FtrProviderContext) {
return await this.getActions().move(startPoint).press().move(endPoint).release().perform();
}
/**
* Performs drag and drop for html5 native drag and drop implementation
* There's a bug in Chromedriver for html5 dnd that doesn't allow to use the method `dragAndDrop` defined above
* https://github.com/SeleniumHQ/selenium/issues/6235
* This implementation simulates user's action by calling the drag and drop specific events directly.
*
* @param {string} from html selector
* @param {string} to html selector
* @return {Promise<void>}
*/
public async html5DragAndDrop(from: string, to: string) {
await this.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 target = document.querySelector(arguments[1]);
const dragStartEvent = createEvent('dragstart');
dispatchEvent(origin, dragStartEvent);
setTimeout(() => {
const dropEvent = createEvent('drop');
dispatchEvent(target, dropEvent, dragStartEvent.dataTransfer);
const dragEndEvent = createEvent('dragend');
dispatchEvent(origin, dragEndEvent, dropEvent.dataTransfer);
}, 50);
`,
from,
to
);
}
/**
* Reloads the current browser window/frame.
* https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_Navigation.html#refresh

View file

@ -300,17 +300,27 @@ describe('DragDrop', () => {
jest.runAllTimers();
component.find('[data-test-subj="lnsDragDrop-reorderableDrop"]').at(2).simulate('dragover');
expect(component.find('[data-test-subj="lnsDragDrop"]').at(0).prop('style')).toEqual({});
expect(component.find('[data-test-subj="lnsDragDrop"]').at(1).prop('style')).toEqual({
transform: 'translateY(-8px)',
expect(
component.find('[data-test-subj="lnsDragDrop-reorderableDrag"]').at(0).prop('style')
).toEqual({});
expect(
component.find('[data-test-subj="lnsDragDrop-reorderableDrag"]').at(1).prop('style')
).toEqual({
transform: 'translateY(-40px)',
});
expect(component.find('[data-test-subj="lnsDragDrop"]').at(2).prop('style')).toEqual({
transform: 'translateY(-8px)',
expect(
component.find('[data-test-subj="lnsDragDrop-reorderableDrag"]').at(2).prop('style')
).toEqual({
transform: 'translateY(-40px)',
});
component.find('[data-test-subj="lnsDragDrop-reorderableDrop"]').at(2).simulate('dragleave');
expect(component.find('[data-test-subj="lnsDragDrop"]').at(1).prop('style')).toEqual({});
expect(component.find('[data-test-subj="lnsDragDrop"]').at(2).prop('style')).toEqual({});
expect(
component.find('[data-test-subj="lnsDragDrop-reorderableDrag"]').at(1).prop('style')
).toEqual({});
expect(
component.find('[data-test-subj="lnsDragDrop-reorderableDrag"]').at(2).prop('style')
).toEqual({});
});
test(`Dropping an item runs onDrop function`, () => {
const preventDefault = jest.fn();

View file

@ -271,12 +271,12 @@ const DragDropInner = React.memo(function DragDropInner(
dropTo={dropTo}
label={label}
className={className}
dataTestSubj={props['data-test-subj'] || 'lnsDragDrop'}
draggingProps={{
className: classNames(children.props.className, classes),
draggable,
onDragEnd: dragEnd,
onDragStart: dragStart,
dataTestSubj: props['data-test-subj'] || 'lnsDragDrop',
isReorderDragging,
}}
dropProps={{
@ -338,13 +338,13 @@ export const ReorderableDragDrop = ({
label,
dropTo,
className,
dataTestSubj,
}: {
draggingProps: {
className: string;
draggable: Props['draggable'];
onDragEnd: (e: DroppableEvent) => void;
onDragStart: (e: DroppableEvent) => void;
dataTestSubj: string;
isReorderDragging: boolean;
};
dropProps: {
@ -361,6 +361,7 @@ export const ReorderableDragDrop = ({
label: string;
dropTo: DropToHandler;
className?: string;
dataTestSubj: string;
}) => {
const { itemsInGroup, dragging, id, droppable } = dropProps;
const { reorderState, setReorderState } = useContext(ReorderContext);
@ -378,7 +379,10 @@ export const ReorderableDragDrop = ({
);
return (
<div className={classNames('lnsDragDrop__reorderableContainer', className)}>
<div
className={classNames('lnsDragDrop__reorderableContainer', className)}
data-test-subj={dataTestSubj}
>
<EuiScreenReaderOnly showOnFocus>
<button
aria-label={label}
@ -442,6 +446,7 @@ export const ReorderableDragDrop = ({
/>
</EuiScreenReaderOnly>
{React.cloneElement(children, {
['data-test-subj']: 'lnsDragDrop-reorderableDrag',
draggable: draggingProps.draggable,
onDragEnd: draggingProps.onDragEnd,
onDragStart: (e: DroppableEvent) => {
@ -452,7 +457,6 @@ export const ReorderableDragDrop = ({
}));
draggingProps.onDragStart(e);
},
['data-test-subj']: draggingProps.dataTestSubj,
className: classNames(
draggingProps.className,
{

View file

@ -0,0 +1,83 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['visualize', 'lens', 'common', 'header']);
describe('lens drag and drop tests', () => {
it('should construct the basic split xy chart', async () => {
await PageObjects.visualize.navigateToNewVisualization();
await PageObjects.visualize.clickVisType('lens');
await PageObjects.lens.goToTimeRange();
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.lens.dragFieldToWorkspace('@timestamp');
expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_xDimensionPanel')).to.eql(
'@timestamp'
);
});
it('should allow dropping fields to existing and empty dimension triggers', async () => {
await PageObjects.lens.switchToVisualization('lnsDatatable');
await PageObjects.lens.dragFieldToDimensionTrigger(
'clientip',
'lnsDatatable_column > lns-dimensionTrigger'
);
expect(await PageObjects.lens.getDimensionTriggerText('lnsDatatable_column')).to.eql(
'Top values of clientip'
);
await PageObjects.lens.dragFieldToDimensionTrigger(
'bytes',
'lnsDatatable_column > lns-empty-dimension'
);
expect(await PageObjects.lens.getDimensionTriggerText('lnsDatatable_column', 1)).to.eql(
'bytes'
);
await PageObjects.lens.dragFieldToDimensionTrigger(
'@message.raw',
'lnsDatatable_column > lns-empty-dimension'
);
expect(await PageObjects.lens.getDimensionTriggerText('lnsDatatable_column', 2)).to.eql(
'Top values of @message.raw'
);
});
it('should reorder the elements for the table', async () => {
await PageObjects.lens.reorderDimensions('lnsDatatable_column', 2, 0);
await PageObjects.header.waitUntilLoadingHasFinished();
expect(await PageObjects.lens.getDimensionTriggersTexts('lnsDatatable_column')).to.eql([
'Top values of @message.raw',
'Top values of clientip',
'bytes',
]);
});
it('should move the column to compatible dimension group', async () => {
await PageObjects.lens.switchToVisualization('bar');
expect(await PageObjects.lens.getDimensionTriggersTexts('lnsXY_xDimensionPanel')).to.eql([
'Top values of @message.raw',
]);
expect(await PageObjects.lens.getDimensionTriggersTexts('lnsXY_splitDimensionPanel')).to.eql([
'Top values of clientip',
]);
await PageObjects.lens.dragDimensionToDimension(
'lnsXY_xDimensionPanel > lns-dimensionTrigger',
'lnsXY_splitDimensionPanel > lns-dimensionTrigger'
);
expect(await PageObjects.lens.getDimensionTriggersTexts('lnsXY_xDimensionPanel')).to.eql([]);
expect(await PageObjects.lens.getDimensionTriggersTexts('lnsXY_splitDimensionPanel')).to.eql([
'Top values of @message.raw',
]);
});
});
}

View file

@ -31,6 +31,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./dashboard'));
loadTestFile(require.resolve('./persistent_context'));
loadTestFile(require.resolve('./colors'));
loadTestFile(require.resolve('./drag_and_drop'));
loadTestFile(require.resolve('./lens_reporting'));
// has to be last one in the suite because it overrides saved objects

View file

@ -292,11 +292,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(await PageObjects.lens.hasChartSwitchWarning('treemap')).to.eql(false);
await PageObjects.lens.switchToVisualization('treemap');
expect(
await PageObjects.lens.getDimensionTriggerText('lnsPie_groupByDimensionPanel', 0)
).to.eql('Top values of geo.dest');
expect(
await PageObjects.lens.getDimensionTriggerText('lnsPie_groupByDimensionPanel', 1)
).to.eql('Top values of geo.src');
await PageObjects.lens.getDimensionTriggersTexts('lnsPie_groupByDimensionPanel')
).to.eql(['Top values of geo.dest', 'Top values of geo.src']);
expect(await PageObjects.lens.getDimensionTriggerText('lnsPie_sizeByDimensionPanel')).to.eql(
'Average of bytes'
);

View file

@ -14,7 +14,8 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
const retry = getService('retry');
const find = getService('find');
const comboBox = getService('comboBox');
const PageObjects = getPageObjects(['header', 'header', 'timePicker', 'common']);
const browser = getService('browser');
const PageObjects = getPageObjects(['header', 'timePicker', 'common']);
return logWrapper('lensPage', log, {
/**
@ -121,6 +122,64 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
}
},
/**
* 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 PageObjects.header.waitUntilLoadingHasFinished();
},
/**
* 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 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 PageObjects.header.waitUntilLoadingHasFinished();
},
/**
* Reorder elements within the group
*
* @param startIndex - the index of dragging element
* @param endIndex - the index of drop
* */
async reorderDimensions(dimension: string, startIndex: number, endIndex: number) {
const dragging = `[data-test-subj='${dimension}']:nth-of-type(${
startIndex + 1
}) .lnsDragDrop`;
const dropping = `[data-test-subj='${dimension}']:nth-of-type(${
endIndex + 1
}) [data-test-subj='lnsDragDrop-reorderableDrop'`;
await browser.html5DragAndDrop(dragging, dropping);
await PageObjects.header.waitUntilLoadingHasFinished();
},
async assertPalette(palette: string) {
await retry.try(async () => {
await testSubjects.click('lns-palettePicker');
@ -351,18 +410,30 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
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 dimensionElements = await testSubjects.findAll(dimension);
const trigger = await testSubjects.findDescendant(
'lns-dimensionTrigger',
dimensionElements[index]
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 await trigger.getVisibleText();
return dimensionTexts;
});
},
async isShowingNoResults() {