Skip to content

Commit

Permalink
[FTR] Refactor toasts svc (#174222)
Browse files Browse the repository at this point in the history
### Summary

Refactoring general ui service (test helpers), to a kbn package.
  - Optimize methods and drop some code duplication.
  
### Why 

  - Makes the service easily available from multiple code areas. 
- This is a preparation to potentially moving tests to plugins /
packages, where they would no longer be able to import thing from test
or x-pack/test but only from a package.

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
wayneseymour and kibanamachine authored Feb 14, 2024
1 parent 9f7ed88 commit 9861161
Show file tree
Hide file tree
Showing 92 changed files with 405 additions and 400 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ import { RemoteProvider } from './remote';
import { FindProvider } from './find';
import { TestSubjects } from './test_subjects';
import { BrowserProvider } from './browser';
import { ToastsService } from './toasts';

export const services = {
retryOnStale: RetryOnStaleProvider,
__webdriver__: RemoteProvider,
find: FindProvider,
testSubjects: TestSubjects,
browser: BrowserProvider,
toasts: ToastsService,
};
156 changes: 156 additions & 0 deletions packages/kbn-ftr-common-functional-ui-services/services/toasts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import expect from '@kbn/expect';
import { FtrService } from './ftr_provider_context';
import { WebElementWrapper } from './web_element_wrapper';

export class ToastsService extends FtrService {
private readonly testSubjects = this.ctx.getService('testSubjects');
private readonly retry = this.ctx.getService('retry');
private readonly find = this.ctx.getService('find');
private readonly config = this.ctx.getService('config');
private readonly defaultFindTimeout = this.config.get('timeouts.find');
/**
* Returns the title and message of a specific error toast.
* This method is specific to toasts created via `.addError` since they contain
* an additional button, that should not be part of the message.
*
* @param index The index of the toast (1-based, NOT 0-based!) of the toast. Use first by default.
* @param titleOnly If this is true, only the title of the error message is returned. There are error messages that only contain a title, no message.
* @returns The title and message of the specified error toast.
*/
public async getErrorByIndex(
index: number = 1,
titleOnly: boolean = false
): Promise<{ title: string; message?: string }> {
const title = await this.getTitleByIndex(index);
if (titleOnly) return { title };

const toast = await this.getElementByIndex(index);
const messageElement = await this.testSubjects.findDescendant('errorToastMessage', toast);
const message: string = await messageElement.getVisibleText();

return { title, message };
}

public async toastMessageByTestSubj(testSubj = 'csp:toast-success') {
const testSubjSvc = this.testSubjects;
return {
async getElement(): Promise<WebElementWrapper> {
return await testSubjSvc.find(testSubj);
},
async clickToastMessageLink(linkTestSubj = 'csp:toast-success-link') {
const element = await this.getElement();
const link = await element.findByTestSubject(linkTestSubj);
await link.click();
},
};
}

/**
* Dismiss a specific toast from the toast list. Since toasts usually should time out themselves,
* you only need to call this for permanent toasts (e.g. error toasts).
*
* @param index The 1-based index of the toast to dismiss. Use first by default.
*/
public async dismissByIndex(index: number = 1): Promise<void> {
const toast = await this.getElementByIndex(index);
await toast.moveMouseTo();
const dismissButton = await this.testSubjects.findDescendant('toastCloseButton', toast);
await dismissButton.click();
}

public async dismissIfExists(): Promise<void> {
const toastShown = await this.find.existsByCssSelector('.euiToast');
if (toastShown) {
try {
await this.testSubjects.click('toastCloseButton');
} catch (err) {
// ignore errors, toast clear themselves after timeout
}
}
}

public async getTitleAndDismiss(): Promise<string> {
const toast = await this.find.byCssSelector('.euiToast', 6 * this.defaultFindTimeout);
await toast.moveMouseTo();
const title = await (await this.testSubjects.find('euiToastHeader__title')).getVisibleText();

await this.testSubjects.click('toastCloseButton');
return title;
}

public async dismiss(): Promise<void> {
await this.testSubjects.click('toastCloseButton', 6 * this.defaultFindTimeout);
}

public async dismissAll(): Promise<void> {
const allToastElements = await this.getAll();

if (allToastElements.length === 0) return;

for (const toastElement of allToastElements) {
try {
await toastElement.moveMouseTo();
const closeBtn = await toastElement.findByTestSubject('toastCloseButton');
await closeBtn.click();
} catch (err) {
// ignore errors, toast clear themselves after timeout
}
}
}

public async dismissAllWithChecks(): Promise<void> {
await this.retry.tryForTime(30 * 1000, async (): Promise<void> => {
await this.dismissAll();
await this.assertCount(0);
});
}

public async assertCount(expectedCount: number): Promise<void> {
await this.retry.tryForTime(5 * 1000, async (): Promise<void> => {
const toastCount = await this.getCount({ timeout: 1000 });
expect(toastCount).to.eql(
expectedCount,
`Toast count should be ${expectedCount} (got ${toastCount})`
);
});
}

public async getElementByIndex(index: number): Promise<WebElementWrapper> {
return await (await this.getGlobalList()).findByCssSelector(`.euiToast:nth-child(${index})`);
}

public async getTitleByIndex(index: number): Promise<string> {
const resultToast = await this.getElementByIndex(index);
const titleElement = await this.testSubjects.findDescendant('euiToastHeader', resultToast);
const title: string = await titleElement.getVisibleText();
return title;
}

public async getContentByIndex(index: number): Promise<string> {
const elem = await this.getElementByIndex(index);
return await elem.getVisibleText();
}

public async getAll(): Promise<WebElementWrapper[]> {
const list = await this.getGlobalList();
return await list.findAllByCssSelector(`.euiToast`);
}

private async getGlobalList(options?: { timeout?: number }): Promise<WebElementWrapper> {
return await this.testSubjects.find('globalToastList', options?.timeout);
}

public async getCount(options?: { timeout?: number }): Promise<number> {
const list = await this.getGlobalList(options);
const toasts = await list.findAllByCssSelector(`.euiToast`, options?.timeout);
return toasts.length;
}
}
3 changes: 2 additions & 1 deletion packages/kbn-ftr-common-functional-ui-services/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@kbn/repo-info",
"@kbn/test-subj-selector",
"@kbn/ftr-common-functional-services",
"@kbn/std"
"@kbn/std",
"@kbn/expect"
]
}
2 changes: 1 addition & 1 deletion test/accessibility/apps/discover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});

await retry.try(async () => {
await toasts.dismissAllToasts();
await toasts.dismissAll();
});

await a11y.testAppSnapshot();
Expand Down
13 changes: 4 additions & 9 deletions test/examples/search/warnings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@
import type { estypes } from '@elastic/elasticsearch';
import expect from '@kbn/expect';
import assert from 'assert';
import type { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import type { FtrProviderContext } from '../../functional/ftr_provider_context';

// eslint-disable-next-line import/no-default-export
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['common', 'timePicker']);
const testSubjects = getService('testSubjects');
const find = getService('find');
const retry = getService('retry');
const es = getService('es');
const log = getService('log');
Expand All @@ -25,6 +23,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const kibanaServer = getService('kibanaServer');
const esArchiver = getService('esArchiver');
const monacoEditor = getService('monacoEditor');
const toasts = getService('toasts');

describe('handling warnings with search source fetch', function () {
const dataViewTitle = 'sample-01,sample-01-rollup';
Expand All @@ -34,7 +33,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const testIndex = 'sample-01';
const testRollupIndex = 'sample-01-rollup';
const testRollupField = 'kubernetes.container.memory.usage.bytes';
const toastsSelector = '[data-test-subj=globalToastList] [data-test-subj=euiToastHeader]';
const shardFailureType = 'unsupported_aggregation_on_downsampled_index';
const shardFailureReason = `Field [${testRollupField}] of type [aggregate_metric_double] is not supported for aggregation [percentiles]`;

Expand Down Expand Up @@ -97,15 +95,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});

afterEach(async () => {
await PageObjects.common.clearAllToasts();
await toasts.dismissAll();
});

it('should show search warnings as toasts', async () => {
await testSubjects.click('searchSourceWithOther');

await retry.try(async () => {
const toasts = await find.allByCssSelector(toastsSelector);
expect(toasts.length).to.be(2);
expect(await toasts.getCount()).to.be(2);
await testSubjects.click('viewWarningBtn');
});

Expand Down Expand Up @@ -152,10 +149,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await testSubjects.click('searchSourceWithoutOther');

// wait for toasts - toasts appear after the response is rendered
let toasts: WebElementWrapper[] = [];
await retry.try(async () => {
toasts = await find.allByCssSelector(toastsSelector);
expect(toasts.length).to.be(2);
expect(await toasts.getCount()).to.be(2);
});

// warnings tab
Expand Down
2 changes: 1 addition & 1 deletion test/functional/apps/console/_context_menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.console.clickContextMenu();
await PageObjects.console.clickCopyAsCurlButton();

const resultToast = await toasts.getToastElement(1);
const resultToast = await toasts.getElementByIndex(1);
const toastText = await resultToast.getVisibleText();

if (toastText.includes('Write permission denied')) {
Expand Down
2 changes: 1 addition & 1 deletion test/functional/apps/console/_text_input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});

await retry.try(async () => {
expect(await toasts.getToastCount()).to.equal(1);
expect(await toasts.getCount()).to.equal(1);
});
});
});
Expand Down
4 changes: 2 additions & 2 deletions test/functional/apps/dashboard/group3/bwc_shared_urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const query = await queryBar.getQueryString();
expect(query).to.equal('memory:>220000');

const warningToast = await toasts.getToastElement(1);
const warningToast = await toasts.getElementByIndex(1);
expect(await warningToast.getVisibleText()).to.contain('Cannot load panels');

await PageObjects.dashboard.waitForRenderComplete();
Expand All @@ -96,7 +96,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const query = await queryBar.getQueryString();
expect(query).to.equal('memory:>220000');

const warningToast = await toasts.getToastElement(1);
const warningToast = await toasts.getElementByIndex(1);
expect(await warningToast.getVisibleText()).to.contain('Cannot load panels');
await PageObjects.dashboard.waitForRenderComplete();
});
Expand Down
5 changes: 3 additions & 2 deletions test/functional/apps/dashboard/group6/dashboard_snapshots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export default function ({
const dashboardAddPanel = getService('dashboardAddPanel');
const testSubjects = getService('testSubjects');
const retry = getService('retry');
const toasts = getService('toasts');

describe('dashboard snapshots', function describeIndexTests() {
before(async function () {
Expand Down Expand Up @@ -58,7 +59,7 @@ export default function ({
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.timePicker.setLogstashDataRange();
await dashboardAddPanel.addVisualization('Rendering Test: tsvb-ts');
await PageObjects.common.closeToastIfExists();
await toasts.dismissIfExists();

await PageObjects.dashboard.saveDashboard('tsvb');
await PageObjects.dashboard.clickFullScreenMode();
Expand All @@ -80,7 +81,7 @@ export default function ({
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.timePicker.setLogstashDataRange();
await dashboardAddPanel.addVisualization('Rendering Test: area with not filter');
await PageObjects.common.closeToastIfExists();
await toasts.dismissIfExists();

await PageObjects.dashboard.saveDashboard('area');
await PageObjects.dashboard.clickFullScreenMode();
Expand Down
6 changes: 3 additions & 3 deletions test/functional/apps/discover/group1/_shared_links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(resolvedTime.start).to.equal(actualTime.start);
expect(resolvedTime.end).to.equal(actualTime.end);
});
await toasts.dismissAllToasts();
await toasts.dismissAll();
});

it("sharing hashed url shouldn't crash the app", async () => {
Expand All @@ -173,12 +173,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await browser.get(currentUrl, false);
const resolvedUrl = await browser.getCurrentUrl();
expect(resolvedUrl).to.match(/discover/);
const { title } = await toasts.getErrorToast(1, true);
const { title } = await toasts.getErrorByIndex(1, true);
expect(title).to.contain(
'Unable to completely restore the URL, be sure to use the share functionality.'
);
});
await toasts.dismissAllToasts();
await toasts.dismissAll();
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
).to.be(true);
}

expect(await toasts.getToastCount()).to.be(1);
await toasts.dismissAllToasts();
expect(await toasts.getCount()).to.be(1);
await toasts.dismissAll();

await dataGrid.clickCopyColumnValues('_source');

Expand All @@ -68,8 +68,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(copiedSourceData.endsWith('}')).to.be(true);
}

expect(await toasts.getToastCount()).to.be(1);
await toasts.dismissAllToasts();
expect(await toasts.getCount()).to.be(1);
await toasts.dismissAll();
});

it('should be able to copy a column name to clipboard', async () => {
Expand All @@ -82,8 +82,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(copiedTimestampName).to.be('@timestamp');
}

expect(await toasts.getToastCount()).to.be(1);
await toasts.dismissAllToasts();
expect(await toasts.getCount()).to.be(1);
await toasts.dismissAll();

await dataGrid.clickCopyColumnName('_source');

Expand All @@ -92,8 +92,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(copiedSourceName).to.be('Document');
}

expect(await toasts.getToastCount()).to.be(1);
await toasts.dismissAllToasts();
expect(await toasts.getCount()).to.be(1);
await toasts.dismissAll();
});
});
}
4 changes: 2 additions & 2 deletions test/functional/apps/discover/group4/_adhoc_data_views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,12 +255,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const second = await PageObjects.discover.getCurrentDataViewId();
expect(first).not.equal(second);

await toasts.dismissAllToasts();
await toasts.dismissAll();

await browser.goBack();
await PageObjects.header.waitUntilLoadingHasFinished();

const [firstToast, secondToast] = await toasts.getAllToastElements();
const [firstToast, secondToast] = await toasts.getAll();

expect([await firstToast.getVisibleText(), await secondToast.getVisibleText()].sort()).to.eql(
[
Expand Down
Loading

0 comments on commit 9861161

Please sign in to comment.