Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Vega] add functional tests for Vega visualization #74097

Merged
merged 16 commits into from
Aug 12, 2020
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,13 @@ export const SpecViewer = ({ vegaAdapter, ...rest }: SpecViewerProps) => {
<div className="eui-textRight">
<EuiCopy textToCopy={spec}>
{(copy) => (
<EuiButtonEmpty size="xs" flush="right" iconType="copyClipboard" onClick={copy}>
<EuiButtonEmpty
size="xs"
flush="right"
iconType="copyClipboard"
onClick={copy}
data-test-subj="vegaDataInspectorCopyClipboardButton"
>
{copyToClipboardLabel}
</EuiButtonEmpty>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,21 @@ export const VegaDataInspector = ({ adapters }: VegaDataInspectorProps) => {
id: 'data-viewer--id',
name: dataSetsLabel,
content: <DataViewer vegaAdapter={adapters.vega} />,
'data-test-subj': 'vegaDataInspectorDataViewerButton',
},
{
id: 'signal-viewer--id',
name: signalValuesLabel,
content: <SignalViewer vegaAdapter={adapters.vega} />,
'data-test-subj': 'vegaDataInspectorSignalViewerButton',
},
{
id: 'spec-viewer--id',
name: specLabel,
content: (
<SpecViewer className="vgaVegaDataInspector__specViewer" vegaAdapter={adapters.vega} />
),
'data-test-subj': 'vegaDataInspectorSpecViewerButton',
},
];

Expand Down
194 changes: 193 additions & 1 deletion test/functional/apps/visualize/_vega_chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,24 @@
* specific language governing permissions and limitations
* under the License.
*/

import { unzip } from 'lodash';
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';

const getTestSpec = (expression: string) => `{
config: { "kibana": {"renderer": "svg"} }
$schema: https://vega.github.io/schema/vega/v5.json
marks: [{
type: text
encode: { update: { text: { value: "Test" } } }
}]
signals: [ {
on: [{
events: click
update: ${expression}
}]
}]}`;

export default function ({ getPageObjects, getService }: FtrProviderContext) {
const PageObjects = getPageObjects([
'timePicker',
Expand All @@ -29,7 +43,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
'vegaChart',
]);
const filterBar = getService('filterBar');
const inspector = getService('inspector');
const vegaDebugInspectorView = getService('vegaDebugInspector');
const log = getService('log');
const retry = getService('retry');
const browser = getService('browser');

describe('vega chart in visualize app', () => {
before(async () => {
Expand Down Expand Up @@ -88,5 +106,179 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
});
});
});

describe('Inspector Panel', () => {
it('should have inspector enabled', async () => {
await inspector.expectIsEnabled();
});

describe('Request Tab', () => {
beforeEach(async () => {
await inspector.open();
});

afterEach(async () => {
await inspector.close();
});

it('should contain "Statistics", "Request", "Response" tabs', async () => {
await inspector.openInspectorRequestsView();

for (const getFn of [
inspector.getOpenRequestDetailRequestButton,
inspector.getOpenRequestDetailResponseButton,
inspector.getOpenRequestStatisticButton,
]) {
await retry.try(async () => {
const requestStatisticTab = await getFn();

expect(await requestStatisticTab.isEnabled()).to.be(true);
});
}
});

it('should set the default query name if not given in the schema', async () => {
const requests = await inspector.getRequestNames();

expect(requests).to.be('Unnamed request #0');
});

it('should log the request statistic', async () => {
await inspector.openInspectorRequestsView();
const rawTableData = await inspector.getTableData();

expect(unzip(rawTableData)[0].join(', ')).to.be(
'Hits, Hits (total), Query time, Request timestamp'
);
});
});

describe('Debug Tab', () => {
beforeEach(async () => {
await inspector.open();
});

afterEach(async () => {
await inspector.close();
});

it('should contain "Data Sets", "Signal Values", "Spec" tabs', async () => {
await vegaDebugInspectorView.openVegaDebugInspectorView();

for (const getFn of [
vegaDebugInspectorView.getOpenDataViewerButton,
vegaDebugInspectorView.getOpenSignalViewerButton,
vegaDebugInspectorView.getOpenSpecViewerButton,
]) {
await retry.try(async () => {
const requestStatisticTab = await getFn();

expect(await requestStatisticTab.isEnabled()).to.be(true);
});
}
});

it('should contain data on "Signal Values" tab', async () => {
await vegaDebugInspectorView.openVegaDebugInspectorView();
await vegaDebugInspectorView.navigateToSignalViewerTab();

const { rows, columns } = await vegaDebugInspectorView.getGridTableData();

expect(columns.join(', ')).to.be('Signal, Value');
expect(rows.length).to.be.greaterThan(0);
expect(rows[0].length).to.be(2);
});

it('should contain data on "Signal Values" tab', async () => {
await vegaDebugInspectorView.openVegaDebugInspectorView();
await vegaDebugInspectorView.navigateToDataViewerTab();

const { rows, columns } = await vegaDebugInspectorView.getGridTableData();

expect(columns.length).to.be.greaterThan(0);
expect(rows.length).to.be.greaterThan(0);
});

it('should be able to copy vega spec to clipboard', async () => {
await vegaDebugInspectorView.openVegaDebugInspectorView();
await vegaDebugInspectorView.navigateToSpecViewerTab();

const copyCopyToClipboardButton = await vegaDebugInspectorView.getCopyClipboardButton();

expect(await copyCopyToClipboardButton.isEnabled()).to.be(true);

// The "clipboard-read" permission of the Permissions API must be granted
if (!(await browser.checkBrowserPermission('clipboard-read'))) {
return;
}
dmlemeshko marked this conversation as resolved.
Show resolved Hide resolved

await copyCopyToClipboardButton.click();

expect(
(await browser.getClipboardValue()).includes(
'"$schema": "https://vega.github.io/schema/vega-lite/'
)
).to.be(true);
});
});
});

describe('Vega extension functions', () => {
beforeEach(async () => {
await PageObjects.vegaChart.cleanSpec();
await filterBar.removeAllFilters();
});

const fillSpecAndGo = async (newSpec: string) => {
await PageObjects.vegaChart.cleanSpec();
await PageObjects.vegaChart.typeInSpec(newSpec);
await PageObjects.visEditor.clickGo();

const viewContainer = await PageObjects.vegaChart.getViewContainer();
const textElement = await viewContainer.findByTagName('text');

await textElement.click();
};

it('should update global time range by calling "kibanaSetTimeFilter" expression', async () => {
await fillSpecAndGo(getTestSpec('kibanaSetTimeFilter("2019", "2020")'));

const currentTimeRange = await PageObjects.timePicker.getTimeConfig();

expect(currentTimeRange.start).to.be('Jan 1, 2019 @ 00:00:00.000');
expect(currentTimeRange.end).to.be('Jan 1, 2020 @ 00:00:00.000');
});

it('should set filter by calling "kibanaAddFilter" expression', async () => {
await fillSpecAndGo(
getTestSpec('kibanaAddFilter({ query_string: { query: "response:200" }})')
);

expect(await filterBar.getFilterCount()).to.be(1);
});

it('should remove filter by calling "kibanaRemoveFilter" expression', async () => {
await filterBar.addFilter('response', 'is', '200');

expect(await filterBar.getFilterCount()).to.be(1);

await fillSpecAndGo(
getTestSpec('kibanaRemoveFilter({ match_phrase: { response: "200" }})')
);

expect(await filterBar.getFilterCount()).to.be(0);
});

it('should remove all filters by calling "kibanaRemoveAllFilters" expression', async () => {
await filterBar.addFilter('response', 'is', '200');
await filterBar.addFilter('response', 'is', '500');

expect(await filterBar.getFilterCount()).to.be(2);

await fillSpecAndGo(getTestSpec('kibanaRemoveAllFilters()'));

expect(await filterBar.getFilterCount()).to.be(0);
});
});
});
}
27 changes: 20 additions & 7 deletions test/functional/page_objects/vega_chart_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,21 @@ export function VegaChartPageProvider({
const { common } = getPageObjects(['common']);

class VegaChartPage {
public getEditor() {
return testSubjects.find('vega-editor');
}

public getViewContainer() {
return find.byCssSelector('div.vgaVis__view');
}

public getControlContainer() {
return find.byCssSelector('div.vgaVis__controls');
}

public async getSpec() {
// Adapted from console_page.js:getVisibleTextFromAceEditor(). Is there a common utilities file?
const editor = await testSubjects.find('vega-editor');
const editor = await this.getEditor();
const lines = await editor.findAllByClassName('ace_line_group');
const linesText = await Promise.all(
lines.map(async (line) => {
Expand All @@ -43,7 +55,7 @@ export function VegaChartPageProvider({
}

public async typeInSpec(text: string) {
const editor = await testSubjects.find('vega-editor');
const editor = await this.getEditor();
const textarea = await editor.findByClassName('ace_content');
await textarea.click();
let repeats = 20;
Expand All @@ -55,12 +67,13 @@ export function VegaChartPageProvider({
await browser.pressKeys(text);
}

public async getViewContainer() {
return await find.byCssSelector('div.vgaVis__view');
}
public async cleanSpec() {
const editor = await this.getEditor();
const aceGutter = await editor.findByClassName('ace_gutter');

public async getControlContainer() {
return await find.byCssSelector('div.vgaVis__controls');
// select all spec
await aceGutter.doubleClick();
await browser.pressKeys(Key.BACK_SPACE);
}

public async getYAxisLabels() {
Expand Down
12 changes: 12 additions & 0 deletions test/functional/services/common/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -481,5 +481,17 @@ export async function BrowserProvider({ getService }: FtrProviderContext) {
const _id = idOrElement instanceof WebElementWrapper ? idOrElement._webElement : idOrElement;
await driver.switchTo().frame(_id);
}

public async checkBrowserPermission(permission: string): Promise<boolean> {
const result: any = await driver.executeAsyncScript(
`navigator.permissions.query({name:'${permission}'}).then(arguments[0])`
);

return Boolean(result?.state === 'granted');
}

public getClipboardValue(): Promise<string> {
dmlemeshko marked this conversation as resolved.
Show resolved Hide resolved
return driver.executeAsyncScript('navigator.clipboard.readText().then(arguments[0])');
}
})();
}
70 changes: 70 additions & 0 deletions test/functional/services/data_grid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you 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 { FtrProviderContext } from '../ftr_provider_context';

interface TabbedGridData {
columns: string[];
rows: string[][];
}

export function DataGridProvider({ getService }: FtrProviderContext) {
const retry = getService('retry');
const testSubjects = getService('testSubjects');

class DataGrid {
async getColumnsData() {
const columns: TabbedGridData['columns'] = [];
const header = await retry.try(async () => await testSubjects.find('dataGridHeader'));

for (const column of await header.findAllByClassName('euiDataGridHeaderCell__content')) {
columns.push(await column.getVisibleText());
}

return columns;
}

async getRowsData() {
const rows: TabbedGridData['rows'] = [];
const rowsElements = await retry.try(async () => await testSubjects.findAll('dataGridRow'));

for (const rowElement of rowsElements) {
const rowData = [];
const dataGridRowCells = await rowElement.findAllByTestSubject('dataGridRowCell');

for (const cell of dataGridRowCells) {
rowData.push(await cell.getVisibleText());
}

rows.push(rowData);
}

return rows;
}

async getDataGridTableData(): Promise<TabbedGridData> {
return {
columns: await this.getColumnsData(),
rows: await this.getRowsData(),
};
}
alexwizp marked this conversation as resolved.
Show resolved Hide resolved
}

return new DataGrid();
}
Loading