diff --git a/x-pack/legacy/plugins/graph/public/angular/templates/index.html b/x-pack/legacy/plugins/graph/public/angular/templates/index.html index 5aaa944d45b27..3bc3eaadf9090 100644 --- a/x-pack/legacy/plugins/graph/public/angular/templates/index.html +++ b/x-pack/legacy/plugins/graph/public/angular/templates/index.html @@ -326,7 +326,7 @@ @@ -350,13 +350,13 @@ @@ -373,7 +373,7 @@ type="button" class="kuiButton kuiButton--basic kuiButton--small gphVertexSelect__button" ng-disabled="workspace.nodes.length === 0" ng-click="setDetail(null);workspace.selectAll()" i18n-id="xpack.graph.sidebar.selections.selectAllButtonLabel" - i18n-default-message="all" + i18n-default-message="all" data-test-subj="graphSelectAll" > diff --git a/x-pack/legacy/plugins/graph/public/app.js b/x-pack/legacy/plugins/graph/public/app.js index 4dc7faaff4771..dd9b326127380 100644 --- a/x-pack/legacy/plugins/graph/public/app.js +++ b/x-pack/legacy/plugins/graph/public/app.js @@ -862,6 +862,7 @@ app.controller('graphuiPlugin', function ( canWipeWorkspace(function () { kbnUrl.change('/workspace/', {}); }); }, + testId: 'graphNewButton', }); // if saving is disabled using uiCapabilities, we don't want to render the save diff --git a/x-pack/test/functional/apps/graph/graph.js b/x-pack/test/functional/apps/graph/graph.js deleted file mode 100644 index b7e5e44450973..0000000000000 --- a/x-pack/test/functional/apps/graph/graph.js +++ /dev/null @@ -1,169 +0,0 @@ -/* - * 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 { indexBy } from 'lodash'; -export default function ({ getService, getPageObjects }) { - - const PageObjects = getPageObjects(['settings', 'common', 'graph', 'header']); - const log = getService('log'); - const esArchiver = getService('esArchiver'); - const browser = getService('browser'); - const retry = getService('retry'); - - - describe.skip('graph', function () { // eslint-disable-line jest/no-disabled-tests - before(async () => { - await browser.setWindowSize(1600, 1000); - log.debug('load graph/secrepo data'); - await esArchiver.loadIfNeeded('graph/secrepo'); - await esArchiver.load('empty_kibana'); - log.debug('create secrepo index pattern'); - await PageObjects.settings.createIndexPattern('secrepo', '@timestamp'); - log.debug('navigateTo graph'); - await PageObjects.common.navigateToApp('graph'); - }); - - const graphName = 'my Graph workspace name ' + new Date().getTime(); - - const expectedText = [ 'blog', - '/wordpress/wp-admin/', - '202.136.75.194', - '82.173.74.216', - '187.131.21.37', - 'wp', - '107.152.98.141', - 'login.php', - '181.113.155.46', - 'admin', - 'wordpress', - '/test/wp-admin/', - 'test', - '/wp-login.php', - '/blog/wp-admin/' - ]; - - - // the line width with abotu 15 decimal places of accuracy looks like it will cause problems - const expectedLineStyle = [ 'stroke-width:2px', - 'stroke-width:2px', - 'stroke-width:2px', - 'stroke-width:2px', - 'stroke-width:2px', - 'stroke-width:2px', - 'stroke-width:2px', - 'stroke-width:2px', - 'stroke-width:2px', - 'stroke-width:2px', - 'stroke-width:2px', - 'stroke-width:2px', - 'stroke-width:4.90799126435544px', - 'stroke-width:2.0042343137225673px', - 'stroke-width:5.645417023048188px', - 'stroke-width:2px', - 'stroke-width:10px', - 'stroke-width:2.377140951428095px', - 'stroke-width:2.073923530343478px', - 'stroke-width:2px', - 'stroke-width:2px', - 'stroke-width:2px', - 'stroke-width:2px' - ]; - - async function buildGraph() { - log.debug('select index pattern secrepo*'); - await PageObjects.graph.selectIndexPattern('secrepo*'); - // select fields url.parts, url, params and src - await PageObjects.graph.addField('url.parts'); - await PageObjects.graph.addField('url'); - await PageObjects.graph.addField('params'); - await PageObjects.graph.addField('src'); - await PageObjects.graph.query('admin'); - await PageObjects.common.sleep(8000); - } - - it('should show correct data circle text', async function () { - await buildGraph(); - const circlesText = await PageObjects.graph.getGraphCircleText(); - log.debug('circle count = ' + circlesText.length); - log.debug('circle values = ' + circlesText); - expect(circlesText.length).to.equal(expectedText.length); - expect(circlesText).to.eql(expectedText); - }); - - it('should show correct number of connecting lines', async function () { - const lineStyle = await PageObjects.graph.getGraphConnectingLines(); - log.debug('line count = ' + lineStyle.length); - log.debug('line style = ' + lineStyle); - expect(lineStyle.length).to.eql(expectedLineStyle.length); - expect(lineStyle).to.eql(expectedLineStyle); - }); - - it('should save Graph workspace', async function () { - const graphExists = await PageObjects.graph.saveGraph(graphName); - expect(graphExists).to.eql(true); - }); - - // open the same graph workspace again and make sure the results are the same - it.skip('should open Graph workspace', async function () { - await PageObjects.graph.openGraph(graphName); - const circlesText = await PageObjects.graph.getGraphCircleText(); - log.debug('circle count = ' + circlesText.length); - log.debug('circle values = ' + circlesText); - expect(circlesText.length).to.equal(expectedText.length); - }); - - it.skip('should delete graph', async function () { - const alertText = await PageObjects.graph.deleteGraph(graphName); - log.debug('alertText = ' + alertText); - }); - - - it('should show venn when clicking a line', async function () { - - await retry.tryForTime(120000, async function () { - // This test can fail after 60000ms defined as mochaOpts.timeout in - // kibana/src/functional_test_runner/lib/config/schema.js - log.debug('build the same graph until we can click the line with stroke-width:2.0042343137225673px'); - await PageObjects.graph.newGraph(); - await buildGraph(); - log.debug('click the line with stroke-width:2.0042343137225673px'); - await PageObjects.graph.clickGraphConnectingLine('stroke-width:2.0042343137225673px'); - }); - - const vennTerm1 = await PageObjects.graph.getVennTerm1(); - log.debug('vennTerm1 = ' + vennTerm1); - - const vennTerm2 = await PageObjects.graph.getVennTerm2(); - log.debug('vennTerm2 = ' + vennTerm2); - - const smallVennTerm1 = await PageObjects.graph.getSmallVennTerm1(); - log.debug('smallVennTerm1 = ' + smallVennTerm1); - - const smallVennTerm12 = await PageObjects.graph.getSmallVennTerm12(); - log.debug('smallVennTerm12 = ' + smallVennTerm12); - - const smallVennTerm2 = await PageObjects.graph.getSmallVennTerm2(); - log.debug('smallVennTerm2 = ' + smallVennTerm2); - - const vennEllipse1 = await PageObjects.graph.getVennEllipse1(); - log.debug('JSON.stringify(vennEllipse1) = ' + JSON.stringify(vennEllipse1)); - - const vennEllipse2 = await PageObjects.graph.getVennEllipse2(); - log.debug('JSON.stringify(vennEllipse2) = ' + JSON.stringify(vennEllipse2)); - - expect(vennTerm1).to.be('/blog/wp-admin/'); - expect(vennTerm2).to.be('admin'); - expect(smallVennTerm1).to.be('5'); - expect(smallVennTerm12).to.be(' (5) '); - expect(smallVennTerm2).to.be('21'); - expect(vennEllipse1).to.eql({ 'cx': '3.8470077339232853', 'cy': '2.5854414729132054', 'rx': '1.2615662610100802' }); - expect(vennEllipse2).to.eql({ 'cx': '5.170882945826411', 'cy': '2.5854414729132054', 'rx': '2.5854414729132054' }); - }); - - - }); -} diff --git a/x-pack/test/functional/apps/graph/graph.ts b/x-pack/test/functional/apps/graph/graph.ts new file mode 100644 index 0000000000000..6db62ea11d995 --- /dev/null +++ b/x-pack/test/functional/apps/graph/graph.ts @@ -0,0 +1,176 @@ +/* + * 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({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['settings', 'common', 'graph', 'header']); + const log = getService('log'); + const esArchiver = getService('esArchiver'); + const browser = getService('browser'); + + describe('graph', function() { + before(async () => { + await browser.setWindowSize(1600, 1000); + log.debug('load graph/secrepo data'); + await esArchiver.loadIfNeeded('graph/secrepo'); + await esArchiver.load('empty_kibana'); + log.debug('create secrepo index pattern'); + await PageObjects.settings.createIndexPattern('secrepo', '@timestamp'); + log.debug('navigateTo graph'); + await PageObjects.common.navigateToApp('graph'); + await PageObjects.graph.createWorkspace(); + }); + + const graphName = 'my Graph workspace name ' + new Date().getTime(); + + const expectedNodes = [ + 'blog', + '/wordpress/wp-admin/', + '202.136.75.194', + '190.154.27.54', + '187.131.21.37', + 'wp', + '80.5.27.16', + 'login.php', + '181.113.155.46', + 'admin', + 'wordpress', + '/test/wp-admin/', + 'test', + '/wp-login.php', + '/blog/wp-admin/', + ]; + + const expectedConnectionWidth: Record> = { + '/blog/wp-admin/': { wp: 2, blog: 5.51581 }, + wp: { + blog: 2, + '202.136.75.194': 2, + 'login.php': 2, + admin: 2, + '/test/wp-admin/': 2, + '/wp-login.php': 2, + '80.5.27.16': 2, + '/wordpress/wp-admin/': 2, + '190.154.27.54': 2, + '187.131.21.37': 2, + '181.113.155.46': 2, + }, + admin: { test: 2, blog: 2, '/blog/wp-admin/': 2 }, + '/test/wp-admin/': { admin: 2 }, + test: { wp: 2, '/test/wp-admin/': 8.54514 }, + wordpress: { wp: 2, admin: 2.0311 }, + '/wordpress/wp-admin/': { wordpress: 9.70794, admin: 2.30771 }, + }; + + async function buildGraph() { + log.debug('select index pattern secrepo*'); + await PageObjects.graph.selectIndexPattern('secrepo*'); + // wait for the saved object to be loaded + // TODO this race condition will be removed with eui-ification + // of graph bar + await PageObjects.common.sleep(1000); + // select fields url.parts, url, params and src + await PageObjects.graph.addField('url.parts'); + await PageObjects.graph.addField('url'); + await PageObjects.graph.addField('params'); + await PageObjects.graph.addField('src'); + await PageObjects.graph.query('admin'); + await PageObjects.common.sleep(8000); + } + + it('should show correct node labels', async function() { + await buildGraph(); + const { nodes } = await PageObjects.graph.getGraphObjects(); + const circlesText = nodes.map(({ label }) => label); + expect(circlesText.length).to.equal(expectedNodes.length); + expect(circlesText).to.eql(expectedNodes); + }); + + it('should show correct connections', async function() { + const epsilon = Number.EPSILON; + const expectedConnectionCount = Object.values(expectedConnectionWidth) + .map(connections => Object.values(connections).length) + .reduce((acc, n) => acc + n, 0); + const { edges } = await PageObjects.graph.getGraphObjects(); + expect(edges.length).to.be(expectedConnectionCount); + edges.forEach(edge => { + const from = edge.sourceNode.label!; + const to = edge.targetNode.label!; + // fuzzy matching to take floating point rounding issues into account + expect(expectedConnectionWidth[from][to]).to.be.within( + edge.width - epsilon, + edge.width + epsilon + ); + }); + }); + + it('should save Graph workspace', async function() { + const graphExists = await PageObjects.graph.saveGraph(graphName); + expect(graphExists).to.eql(true); + }); + + // open the same graph workspace again and make sure the results are the same + it('should open Graph workspace', async function() { + await PageObjects.graph.openGraph(graphName); + const { nodes } = await PageObjects.graph.getGraphObjects(); + const circlesText = nodes.map(({ label }) => label); + expect(circlesText.length).to.equal(expectedNodes.length); + expect(circlesText).to.eql(expectedNodes); + }); + + it('should create new Graph workspace', async function() { + await PageObjects.graph.newGraph(); + const { nodes, edges } = await PageObjects.graph.getGraphObjects(); + expect(nodes).to.be.empty(); + expect(edges).to.be.empty(); + }); + + it('should show venn when clicking a line', async function() { + await buildGraph(); + const { edges } = await PageObjects.graph.getGraphObjects(); + + const blogAdminBlogEdge = edges.find( + ({ sourceNode, targetNode }) => + sourceNode.label === '/blog/wp-admin/' && targetNode.label === 'blog' + )!; + + await PageObjects.graph.isolateEdge(blogAdminBlogEdge); + + await PageObjects.graph.clickEdge(blogAdminBlogEdge); + + const vennTerm1 = await PageObjects.graph.getVennTerm1(); + log.debug('vennTerm1 = ' + vennTerm1); + + const vennTerm2 = await PageObjects.graph.getVennTerm2(); + log.debug('vennTerm2 = ' + vennTerm2); + + const smallVennTerm1 = await PageObjects.graph.getSmallVennTerm1(); + log.debug('smallVennTerm1 = ' + smallVennTerm1); + + const smallVennTerm12 = await PageObjects.graph.getSmallVennTerm12(); + log.debug('smallVennTerm12 = ' + smallVennTerm12); + + const smallVennTerm2 = await PageObjects.graph.getSmallVennTerm2(); + log.debug('smallVennTerm2 = ' + smallVennTerm2); + + expect(vennTerm1).to.be('/blog/wp-admin/'); + expect(vennTerm2).to.be('blog'); + expect(smallVennTerm1).to.be('5'); + expect(smallVennTerm12).to.be(' (5) '); + expect(smallVennTerm2).to.be('8'); + }); + + it('should delete graph', async function() { + await PageObjects.graph.goToListingPage(); + expect(await PageObjects.graph.getWorkspaceCount()).to.equal(1); + await PageObjects.graph.deleteGraph(graphName); + expect(await PageObjects.graph.getWorkspaceCount()).to.equal(0); + }); + }); +} diff --git a/x-pack/test/functional/apps/graph/index.js b/x-pack/test/functional/apps/graph/index.ts similarity index 69% rename from x-pack/test/functional/apps/graph/index.js rename to x-pack/test/functional/apps/graph/index.ts index 1eafe341ba2db..5cfdc3859fbdd 100644 --- a/x-pack/test/functional/apps/graph/index.js +++ b/x-pack/test/functional/apps/graph/index.ts @@ -4,8 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -export default function ({ loadTestFile }) { - describe('graph app', function () { +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function({ loadTestFile }: FtrProviderContext) { + describe('graph app', function() { this.tags('ciGroup1'); loadTestFile(require.resolve('./feature_controls')); diff --git a/x-pack/test/functional/page_objects/graph_page.js b/x-pack/test/functional/page_objects/graph_page.js deleted file mode 100644 index b4990c1653f09..0000000000000 --- a/x-pack/test/functional/page_objects/graph_page.js +++ /dev/null @@ -1,153 +0,0 @@ -/* - * 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. - */ - -export function GraphPageProvider({ getService, getPageObjects }) { - const find = getService('find'); - const log = getService('log'); - const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['common', 'header', 'settings']); - const retry = getService('retry'); - - - class GraphPage { - - async selectIndexPattern(pattern) { - await find.clickDisplayedByCssSelector('.gphIndexSelect'); - await find.clickByCssSelector('.gphIndexSelect > option[label="' + pattern + '"]'); - } - - async clickAddField() { - await retry.try(async () => { - await find.clickByCssSelector('#addVertexFieldButton'); - // make sure the fieldSelectionList is not hidden - await testSubjects.exists('fieldSelectionList'); - }); - } - - async selectField(field) { - await find.clickDisplayedByCssSelector('select[id="fieldList"] > option[label="' + field + '"]'); - await find.clickDisplayedByCssSelector('button[ng-click="addFieldToSelection()"]'); - } - - async addField(field) { - log.debug('click Add Field icon'); - await this.clickAddField(); - log.debug('select field ' + field); - await this.selectField(field); - } - - async query(str) { - await find.setValue('input.kuiLocalSearchInput', str); - await find.clickDisplayedByCssSelector('button.kuiLocalSearchButton'); - } - - - async getGraphCircleText() { - const chartTypes = await find.allByCssSelector('text.gphNode__label'); - - async function getCircleText(circle) { - return circle.getVisibleText(); - } - - const getChartTypesPromises = chartTypes.map(getCircleText); - return Promise.all(getChartTypesPromises); - } - - async getGraphConnectingLines() { - const chartTypes = await find.allByCssSelector('line.edge'); - - async function getLineStyle(line) { - return line.getAttribute('style'); - } - - const getChartTypesPromises = chartTypes.map(getLineStyle); - return Promise.all(getChartTypesPromises); - } - - // click the line which matches the style - async clickGraphConnectingLine(style) { - await find.clickByCssSelector('line.edge[style="' + style + '"]'); - } - - async newGraph() { - log.debug('Click New Workspace'); - await find.clickByCssSelector('[aria-label="New Workspace"]'); - await PageObjects.common.sleep(1000); - const modal = await find.byCssSelector('#kibana-body'); - const page = await modal.getVisibleText(); - if (page.includes('This will clear the workspace - are you sure?')) { - return testSubjects.click('confirmModalConfirmButton'); - } - } - - async saveGraph(name) { - await find.clickByCssSelector('[aria-label="Save Workspace"]'); - await find.setValue('#workspaceTitle', name); - await find.clickByCssSelector('button[aria-label="Save workspace"]'); - - // Confirm that the Graph has been saved. - return await testSubjects.exists('saveGraphSuccess'); - } - - async openGraph(name) { - await find.clickByCssSelector('[aria-label="Load Saved Workspace"]'); - await find.setValue('input[name="filter"]', name); - await PageObjects.common.sleep(1000); - await find.clickByLinkText(name); - await PageObjects.common.sleep(5000); - } - - async deleteGraph() { - await find.clickByCssSelector('[aria-label="Delete Saved Workspace"]'); - await testSubjects.click('confirmModalConfirmButton'); - } - - - async getVennTerm1() { - const el = await find.byCssSelector('span.vennTerm1'); - return await el.getVisibleText(); - } - - async getVennTerm2() { - const el = await find.byCssSelector('span.vennTerm2'); - return await el.getVisibleText(); - } - - async getSmallVennTerm1() { - const el = await find.byCssSelector('small.vennTerm1'); - return await el.getVisibleText(); - } - - async getSmallVennTerm12() { - const el = await find.byCssSelector('small.vennTerm12'); - return await el.getVisibleText(); - } - - async getSmallVennTerm2() { - const el = await find.byCssSelector('small.vennTerm2'); - return await el.getVisibleText(); - } - - async getVennEllipse1() { - const el = await find.byCssSelector('ellipse.venn1'); - const cx = await el.getAttribute('cx'); - const cy = await el.getAttribute('cy'); - const rx = await el.getAttribute('rx'); - return { cx: cx, cy: cy, rx: rx }; - } - - async getVennEllipse2() { - const el = await find.byCssSelector('ellipse.venn2'); - const cx = await el.getAttribute('cx'); - const cy = await el.getAttribute('cy'); - const rx = await el.getAttribute('rx'); - return { cx: cx, cy: cy, rx: rx }; - } - - - } - return new GraphPage(); -} diff --git a/x-pack/test/functional/page_objects/graph_page.ts b/x-pack/test/functional/page_objects/graph_page.ts new file mode 100644 index 0000000000000..dd5f28e221eb1 --- /dev/null +++ b/x-pack/test/functional/page_objects/graph_page.ts @@ -0,0 +1,271 @@ +/* + * 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 { WebElementWrapper } from 'test/functional/services/lib/web_element_wrapper'; +import { FtrProviderContext } from '../ftr_provider_context'; + +interface Node { + circle: WebElementWrapper; + label: string; +} + +interface Edge { + sourceNode: Node; + targetNode: Node; + width: number; + element: WebElementWrapper; +} + +export function GraphPageProvider({ getService, getPageObjects }: FtrProviderContext) { + const find = getService('find'); + const log = getService('log'); + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['common', 'header', 'settings']); + const retry = getService('retry'); + + class GraphPage { + async selectIndexPattern(pattern: string) { + await find.clickDisplayedByCssSelector('.gphIndexSelect'); + await find.clickByCssSelector('.gphIndexSelect > option[label="' + pattern + '"]'); + } + + async clickAddField() { + await retry.try(async () => { + await find.clickByCssSelector('#addVertexFieldButton'); + // make sure the fieldSelectionList is not hidden + await testSubjects.exists('fieldSelectionList'); + }); + } + + async selectField(field: string) { + await find.clickDisplayedByCssSelector( + 'select[id="fieldList"] > option[label="' + field + '"]' + ); + await find.clickDisplayedByCssSelector('button[ng-click="addFieldToSelection()"]'); + } + + async addField(field: string) { + log.debug('click Add Field icon'); + await this.clickAddField(); + log.debug('select field ' + field); + await this.selectField(field); + } + + async query(str: string) { + await find.setValue('input.kuiLocalSearchInput', str); + await find.clickDisplayedByCssSelector('button.kuiLocalSearchButton'); + } + + private getPositionAsString(x: string, y: string) { + return `${x}-${y}`; + } + + private async getCirclePosition(element: WebElementWrapper) { + const x = await element.getAttribute('cx'); + const y = await element.getAttribute('cy'); + return this.getPositionAsString(x, y); + } + + private async getLinePositions(element: WebElementWrapper) { + const x1 = await element.getAttribute('x1'); + const y1 = await element.getAttribute('y1'); + const x2 = await element.getAttribute('x2'); + const y2 = await element.getAttribute('y2'); + return [this.getPositionAsString(x1, y1), this.getPositionAsString(x2, y2)]; + } + + async isolateEdge(edge: Edge) { + const from = edge.sourceNode.label; + const to = edge.targetNode.label; + + // select all nodes + await testSubjects.click('graphSelectAll'); + + // go through all nodes and remove every node not source or target + const selections = await find.allByCssSelector('.gphSelectionList__field'); + for (const selection of selections) { + const labelElement = await selection.findByTagName('span'); + const selectionLabel = await labelElement.getVisibleText(); + log.debug('Looking at selection ' + selectionLabel); + if (selectionLabel !== from && selectionLabel !== to) { + (await selection.findByClassName('gphNode__text')).click(); + await PageObjects.common.sleep(200); + } + } + + // invert selection to select all nodes not source or target + await testSubjects.click('graphInvertSelection'); + + // remove all other nodes + await testSubjects.click('graphRemoveSelection'); + } + + async clickEdge(edge: Edge) { + await this.stopLayout(); + await PageObjects.common.sleep(1000); + await edge.element.click(); + await this.startLayout(); + } + + async stopLayout() { + if (await testSubjects.exists('graphPauseLayout')) { + await testSubjects.click('graphPauseLayout'); + } + } + + async startLayout() { + if (await testSubjects.exists('graphResumeLayout')) { + await testSubjects.click('graphResumeLayout'); + } + } + + async getGraphObjects() { + await this.stopLayout(); + const graphElements = await find.allByCssSelector( + '#svgRootGroup line, #svgRootGroup circle, text.gphNode__label' + ); + const nodes: Node[] = []; + const nodePositionMap: Record = {}; + const edges: Edge[] = []; + + // find all nodes and save their positions + for (const element of graphElements) { + const tagName: string = await element.getTagName(); + // check the position of the circle element + if (tagName === 'circle') { + nodes.push({ circle: element, label: '' }); + const position = await this.getCirclePosition(element); + nodePositionMap[position] = nodes.length - 1; + } + // get the label for the node from the text element + if (tagName === 'text') { + const text = await element.getVisibleText(); + nodes[nodes.length - 1].label = text; + } + } + + // find all edges + for (const element of graphElements) { + const tagName: string = await element.getTagName(); + if (tagName === 'line') { + const [sourcePosition, targetPosition] = await this.getLinePositions(element); + const lineStyle = await element.getAttribute('style'); + // grep out the width of the connection from the style attribute + const strokeWidth = Number(/stroke-width: (\d+(\.\d+)?)px/.exec(lineStyle)![1]); + edges.push({ + element, + width: strokeWidth, + // look up source and target node by matching start and end coordinates + // of the edges and the nodes + sourceNode: nodes[nodePositionMap[sourcePosition]], + targetNode: nodes[nodePositionMap[targetPosition]], + }); + } + } + + await this.startLayout(); + + return { + nodes, + edges, + }; + } + + async createWorkspace() { + await testSubjects.click('graphCreateWorkspacePromptButton'); + } + + async newGraph() { + log.debug('Click New Workspace'); + await testSubjects.click('graphNewButton'); + await PageObjects.common.sleep(1000); + await PageObjects.common.clickConfirmOnModal(); + } + + async saveGraph(name: string) { + await testSubjects.click('graphSaveButton'); + await testSubjects.setValue('savedObjectTitle', name); + await testSubjects.click('confirmSaveSavedObjectButton'); + + // Confirm that the Graph has been saved. + return await testSubjects.exists('saveGraphSuccess'); + } + + async getSearchFilter() { + const searchFilter = await find.allByCssSelector('.euiFieldSearch'); + return searchFilter[0]; + } + + async searchForWorkspaceWithName(name: string) { + await retry.try(async () => { + const searchFilter = await this.getSearchFilter(); + await searchFilter.clearValue(); + await searchFilter.click(); + await searchFilter.type(name); + await PageObjects.common.pressEnterKey(); + await find.waitForDeletedByCssSelector('.euiBasicTable-loading', 5000); + }); + + await PageObjects.header.waitUntilLoadingHasFinished(); + } + + async goToListingPage() { + await testSubjects.click('graphHomeBreadcrumb'); + } + + async openGraph(name: string) { + await this.goToListingPage(); + await this.searchForWorkspaceWithName(name); + await find.clickByLinkText(name); + await PageObjects.common.sleep(5000); + } + + async deleteGraph(name: string) { + await this.goToListingPage(); + await this.searchForWorkspaceWithName(name); + await testSubjects.click('checkboxSelectAll'); + await this.clickDeleteSelectedWorkspaces(); + await PageObjects.common.clickConfirmOnModal(); + } + + async getWorkspaceCount() { + const workspaceTitles = await find.allByCssSelector( + '[data-test-subj^="graphListingTitleLink"]' + ); + return workspaceTitles.length; + } + + async clickDeleteSelectedWorkspaces() { + await testSubjects.click('deleteSelectedItems'); + } + + async getVennTerm1() { + const el = await find.byCssSelector('span.gphLinkSummary__term--1'); + return await el.getVisibleText(); + } + + async getVennTerm2() { + const el = await find.byCssSelector('span.gphLinkSummary__term--2'); + return await el.getVisibleText(); + } + + async getSmallVennTerm1() { + const el = await find.byCssSelector('small.gphLinkSummary__term--1'); + return await el.getVisibleText(); + } + + async getSmallVennTerm12() { + const el = await find.byCssSelector('small.gphLinkSummary__term--1-2'); + return await el.getVisibleText(); + } + + async getSmallVennTerm2() { + const el = await find.byCssSelector('small.gphLinkSummary__term--2'); + return await el.getVisibleText(); + } + } + return new GraphPage(); +}