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();
+}