mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Serverless] Add navigation functional tests (#161856)
## Summary close https://github.com/elastic/kibana/issues/160011 This PR adds helpers for testing serverless specific navigation. There are helpers for sidenav, breadcrumbs, global search, recent items, logo, checking that no page reload happened during nav. This PR also adds some serverless specific navigation tests. The should serve as a navigation smoke check and testing helpers example. Solution teams can improve them as they see fit.
This commit is contained in:
parent
0aa5b217c7
commit
f6e6b77efc
15 changed files with 508 additions and 38 deletions
|
@ -20,5 +20,6 @@ export const createHomeBreadcrumb = ({
|
|||
text: <EuiIcon type="home" />,
|
||||
title: i18n.translate('core.ui.chrome.breadcrumbs.homeLink', { defaultMessage: 'Home' }),
|
||||
href: homeHref,
|
||||
'data-test-subj': 'breadcrumb-home',
|
||||
};
|
||||
};
|
||||
|
|
|
@ -80,6 +80,7 @@ describe('breadcrumbs', () => {
|
|||
expect(breadcrumbs).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"data-test-subj": "breadcrumb-home",
|
||||
"href": "/",
|
||||
"text": <EuiIcon
|
||||
type="home"
|
||||
|
@ -87,6 +88,7 @@ describe('breadcrumbs', () => {
|
|||
"title": "Home",
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "breadcrumb-deepLinkId-navItem1",
|
||||
"href": "/foo/item1",
|
||||
"text": "Nav Item 1",
|
||||
},
|
||||
|
@ -117,6 +119,7 @@ describe('breadcrumbs', () => {
|
|||
expect(breadcrumbs).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"data-test-subj": "breadcrumb-home",
|
||||
"href": "/",
|
||||
"text": <EuiIcon
|
||||
type="home"
|
||||
|
|
|
@ -19,6 +19,7 @@ import type { HttpStart } from '@kbn/core-http-browser';
|
|||
import { BehaviorSubject, Observable, combineLatest, map, takeUntil, ReplaySubject } from 'rxjs';
|
||||
import type { Location } from 'history';
|
||||
import deepEqual from 'react-fast-compare';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import { createHomeBreadcrumb } from './home_breadcrumbs';
|
||||
import { findActiveNodes, flattenNav, stripQueryParams } from './utils';
|
||||
|
@ -113,6 +114,9 @@ export class ProjectNavigationService {
|
|||
(node): ChromeProjectBreadcrumb => ({
|
||||
href: node.deepLink?.url ?? node.href,
|
||||
text: node.title,
|
||||
'data-test-subj': classnames({
|
||||
[`breadcrumb-deepLinkId-${node.deepLink?.id}`]: !!node.deepLink,
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
@ -58,16 +58,16 @@ describe('<Navigation />', () => {
|
|||
jest.advanceTimersByTime(SET_NAVIGATION_DELAY);
|
||||
});
|
||||
|
||||
expect(await findByTestId('nav-item-group1.item1')).toBeVisible();
|
||||
expect(await findByTestId('nav-item-group1.item2')).toBeVisible();
|
||||
expect(await findByTestId('nav-item-group1.group1A')).toBeVisible();
|
||||
expect(await findByTestId('nav-item-group1.group1A.item1')).toBeVisible();
|
||||
expect(await findByTestId('nav-item-group1.group1A.group1A_1')).toBeVisible();
|
||||
expect(await findByTestId(/nav-item-group1.item1/)).toBeVisible();
|
||||
expect(await findByTestId(/nav-item-group1.item2/)).toBeVisible();
|
||||
expect(await findByTestId(/nav-item-group1.group1A\s/)).toBeVisible();
|
||||
expect(await findByTestId(/nav-item-group1.group1A.item1/)).toBeVisible();
|
||||
expect(await findByTestId(/nav-item-group1.group1A.group1A_1/)).toBeVisible();
|
||||
|
||||
// Click the last group to expand and show the last depth
|
||||
(await findByTestId('nav-item-group1.group1A.group1A_1')).click();
|
||||
(await findByTestId(/nav-item-group1.group1A.group1A_1/)).click();
|
||||
|
||||
expect(await findByTestId('nav-item-group1.group1A.group1A_1.item1')).toBeVisible();
|
||||
expect(await findByTestId(/nav-item-group1.group1A.group1A_1.item1/)).toBeVisible();
|
||||
|
||||
expect(onProjectNavigationChange).toHaveBeenCalled();
|
||||
const lastCall =
|
||||
|
@ -266,8 +266,8 @@ describe('<Navigation />', () => {
|
|||
jest.advanceTimersByTime(SET_NAVIGATION_DELAY);
|
||||
});
|
||||
|
||||
expect(await findByTestId('nav-item-root.group1.item1')).toBeVisible();
|
||||
expect(await findByTestId('nav-item-root.group1.item1')).toBeVisible();
|
||||
expect(await findByTestId(/nav-item-root.group1.item1/)).toBeVisible();
|
||||
expect(await findByTestId(/nav-item-root.group1.item1/)).toBeVisible();
|
||||
|
||||
expect(onProjectNavigationChange).toHaveBeenCalled();
|
||||
const lastCall =
|
||||
|
@ -342,8 +342,8 @@ describe('<Navigation />', () => {
|
|||
jest.advanceTimersByTime(SET_NAVIGATION_DELAY);
|
||||
});
|
||||
|
||||
expect(queryByTestId('nav-group-root.group1')).toBeNull();
|
||||
expect(queryByTestId('nav-item-root.group2.item1')).toBeVisible();
|
||||
expect(queryByTestId(/nav-group-root.group1/)).toBeNull();
|
||||
expect(queryByTestId(/nav-item-root.group2.item1/)).toBeVisible();
|
||||
|
||||
expect(onProjectNavigationChange).toHaveBeenCalled();
|
||||
const lastCall =
|
||||
|
@ -649,10 +649,10 @@ describe('<Navigation />', () => {
|
|||
</NavigationProvider>
|
||||
);
|
||||
|
||||
expect(await findByTestId('nav-item-group1.item1')).toHaveClass(
|
||||
expect(await findByTestId(/nav-item-group1.item1/)).toHaveClass(
|
||||
'euiSideNavItemButton-isSelected'
|
||||
);
|
||||
expect(await findByTestId('nav-item-group1.item2')).not.toHaveClass(
|
||||
expect(await findByTestId(/nav-item-group1.item2/)).not.toHaveClass(
|
||||
'euiSideNavItemButton-isSelected'
|
||||
);
|
||||
|
||||
|
@ -673,10 +673,10 @@ describe('<Navigation />', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
expect(await findByTestId('nav-item-group1.item1')).not.toHaveClass(
|
||||
expect(await findByTestId(/nav-item-group1.item1/)).not.toHaveClass(
|
||||
'euiSideNavItemButton-isSelected'
|
||||
);
|
||||
expect(await findByTestId('nav-item-group1.item2')).toHaveClass(
|
||||
expect(await findByTestId(/nav-item-group1.item2/)).toHaveClass(
|
||||
'euiSideNavItemButton-isSelected'
|
||||
);
|
||||
});
|
||||
|
@ -730,7 +730,7 @@ describe('<Navigation />', () => {
|
|||
|
||||
jest.advanceTimersByTime(SET_NAVIGATION_DELAY);
|
||||
|
||||
expect(await findByTestId('nav-item-group1.item1')).toHaveClass(
|
||||
expect(await findByTestId(/nav-item-group1.item1/)).toHaveClass(
|
||||
'euiSideNavItemButton-isSelected'
|
||||
);
|
||||
});
|
||||
|
@ -752,17 +752,17 @@ describe('<Navigation />', () => {
|
|||
</NavigationProvider>
|
||||
);
|
||||
|
||||
expect(await findByTestId('nav-item-group1.cloudLink1')).toBeVisible();
|
||||
expect(await findByTestId('nav-item-group1.cloudLink2')).toBeVisible();
|
||||
expect(await findByTestId('nav-item-group1.cloudLink3')).toBeVisible();
|
||||
expect(await findByTestId(/nav-item-group1.cloudLink1/)).toBeVisible();
|
||||
expect(await findByTestId(/nav-item-group1.cloudLink2/)).toBeVisible();
|
||||
expect(await findByTestId(/nav-item-group1.cloudLink3/)).toBeVisible();
|
||||
|
||||
expect(await (await findByTestId('nav-item-group1.cloudLink1')).textContent).toBe(
|
||||
expect(await (await findByTestId(/nav-item-group1.cloudLink1/)).textContent).toBe(
|
||||
'Mock Users & RolesExternal link'
|
||||
);
|
||||
expect(await (await findByTestId('nav-item-group1.cloudLink2')).textContent).toBe(
|
||||
expect(await (await findByTestId(/nav-item-group1.cloudLink2/)).textContent).toBe(
|
||||
'Mock PerformanceExternal link'
|
||||
);
|
||||
expect(await (await findByTestId('nav-item-group1.cloudLink3')).textContent).toBe(
|
||||
expect(await (await findByTestId(/nav-item-group1.cloudLink3/)).textContent).toBe(
|
||||
'Mock Billing & SubscriptionsExternal link'
|
||||
);
|
||||
});
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
EuiSideNavItemType,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import classnames from 'classnames';
|
||||
import type { BasePathService, NavigateToUrlFn } from '../../../types/internal';
|
||||
import { navigationStyles as styles } from '../../styles';
|
||||
import { useNavigation as useServices } from '../../services';
|
||||
|
@ -31,7 +32,12 @@ const navigationNodeToEuiItem = (
|
|||
const href = item.deepLink?.url ?? item.href;
|
||||
const id = item.path ? item.path.join('.') : item.id;
|
||||
const isExternal = Boolean(href) && isAbsoluteLink(href!);
|
||||
const dataTestSubj = `nav-item-${id}`;
|
||||
const isSelected = item.children && item.children.length > 0 ? false : item.isActive;
|
||||
const dataTestSubj = classnames(`nav-item`, `nav-item-${id}`, {
|
||||
[`nav-item-deepLinkId-${item.deepLink?.id}`]: !!item.deepLink,
|
||||
[`nav-item-id-${item.id}`]: item.id,
|
||||
[`nav-item-isActive`]: isSelected,
|
||||
});
|
||||
|
||||
const getRenderItem = (): RenderItem | undefined => {
|
||||
if (!isExternal || item.renderItem) {
|
||||
|
@ -47,8 +53,6 @@ const navigationNodeToEuiItem = (
|
|||
);
|
||||
};
|
||||
|
||||
const isSelected = item.children && item.children.length > 0 ? false : item.isActive;
|
||||
|
||||
return {
|
||||
id,
|
||||
name: item.title,
|
||||
|
|
|
@ -90,16 +90,16 @@ describe('<DefaultNavigation />', () => {
|
|||
jest.advanceTimersByTime(SET_NAVIGATION_DELAY);
|
||||
});
|
||||
|
||||
expect(await findByTestId('nav-item-group1.item1')).toBeVisible();
|
||||
expect(await findByTestId('nav-item-group1.item2')).toBeVisible();
|
||||
expect(await findByTestId('nav-item-group1.group1A')).toBeVisible();
|
||||
expect(await findByTestId('nav-item-group1.group1A.item1')).toBeVisible();
|
||||
expect(await findByTestId('nav-item-group1.group1A.group1A_1')).toBeVisible();
|
||||
expect(await findByTestId(/nav-item-group1.item1/)).toBeVisible();
|
||||
expect(await findByTestId(/nav-item-group1.item2/)).toBeVisible();
|
||||
expect(await findByTestId(/nav-item-group1.group1A\s/)).toBeVisible();
|
||||
expect(await findByTestId(/nav-item-group1.group1A.item1/)).toBeVisible();
|
||||
expect(await findByTestId(/nav-item-group1.group1A.group1A_1/)).toBeVisible();
|
||||
|
||||
// Click the last group to expand and show the last depth
|
||||
(await findByTestId('nav-item-group1.group1A.group1A_1')).click();
|
||||
(await findByTestId(/nav-item-group1.group1A.group1A_1/)).click();
|
||||
|
||||
expect(await findByTestId('nav-item-group1.group1A.group1A_1.item1')).toBeVisible();
|
||||
expect(await findByTestId(/nav-item-group1.group1A.group1A_1.item1/)).toBeVisible();
|
||||
|
||||
expect(onProjectNavigationChange).toHaveBeenCalled();
|
||||
const lastCall =
|
||||
|
@ -556,10 +556,10 @@ describe('<DefaultNavigation />', () => {
|
|||
jest.advanceTimersByTime(SET_NAVIGATION_DELAY);
|
||||
});
|
||||
|
||||
expect(await findByTestId('nav-item-group1.item1')).toHaveClass(
|
||||
expect(await findByTestId(/nav-item-group1.item1/)).toHaveClass(
|
||||
'euiSideNavItemButton-isSelected'
|
||||
);
|
||||
expect(await findByTestId('nav-item-group1.item2')).not.toHaveClass(
|
||||
expect(await findByTestId(/nav-item-group1.item2/)).not.toHaveClass(
|
||||
'euiSideNavItemButton-isSelected'
|
||||
);
|
||||
});
|
||||
|
@ -619,7 +619,7 @@ describe('<DefaultNavigation />', () => {
|
|||
jest.advanceTimersByTime(SET_NAVIGATION_DELAY);
|
||||
});
|
||||
|
||||
expect(await findByTestId('nav-item-group1.item1')).toHaveClass(
|
||||
expect(await findByTestId(/nav-item-group1.item1/)).toHaveClass(
|
||||
'euiSideNavItemButton-isSelected'
|
||||
);
|
||||
});
|
||||
|
@ -705,7 +705,7 @@ describe('<DefaultNavigation />', () => {
|
|||
expect(
|
||||
await (
|
||||
await findByTestId(
|
||||
'nav-item-project_settings_project_nav.settings.cloudLinkUserAndRoles'
|
||||
/nav-item-project_settings_project_nav.settings.cloudLinkUserAndRoles/
|
||||
)
|
||||
).textContent
|
||||
).toBe('Mock Users & RolesExternal link');
|
||||
|
@ -713,14 +713,14 @@ describe('<DefaultNavigation />', () => {
|
|||
expect(
|
||||
await (
|
||||
await findByTestId(
|
||||
'nav-item-project_settings_project_nav.settings.cloudLinkPerformance'
|
||||
/nav-item-project_settings_project_nav.settings.cloudLinkPerformance/
|
||||
)
|
||||
).textContent
|
||||
).toBe('Mock PerformanceExternal link');
|
||||
|
||||
expect(
|
||||
await (
|
||||
await findByTestId('nav-item-project_settings_project_nav.settings.cloudLinkBilling')
|
||||
await findByTestId(/nav-item-project_settings_project_nav.settings.cloudLinkBilling/)
|
||||
).textContent
|
||||
).toBe('Mock Billing & SubscriptionsExternal link');
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
|
||||
import { pageObjects as xpackFunctionalPageObjects } from '../../../test/functional/page_objects';
|
||||
import { SvlCommonPageProvider } from './svl_common_page';
|
||||
import { SvlCommonNavigationProvider } from './svl_common_navigation';
|
||||
import { SvlObltOnboardingPageProvider } from './svl_oblt_onboarding_page';
|
||||
import { SvlObltOnboardingStreamLogFilePageProvider } from './svl_oblt_onboarding_stream_log_file';
|
||||
import { SvlObltOverviewPageProvider } from './svl_oblt_overview_page';
|
||||
|
@ -18,6 +19,7 @@ export const pageObjects = {
|
|||
...xpackFunctionalPageObjects,
|
||||
|
||||
svlCommonPage: SvlCommonPageProvider,
|
||||
svlCommonNavigation: SvlCommonNavigationProvider,
|
||||
svlObltOnboardingPage: SvlObltOnboardingPageProvider,
|
||||
SvlObltOnboardingStreamLogFilePage: SvlObltOnboardingStreamLogFilePageProvider,
|
||||
svlObltOverviewPage: SvlObltOverviewPageProvider,
|
||||
|
|
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import type { AppDeepLinkId } from '@kbn/core-chrome-browser';
|
||||
|
||||
import type { NavigationID as MlNavId } from '@kbn/default-nav-ml';
|
||||
import type { NavigationID as AlNavId } from '@kbn/default-nav-analytics';
|
||||
import type { NavigationID as MgmtNavId } from '@kbn/default-nav-management';
|
||||
import type { NavigationID as DevNavId } from '@kbn/default-nav-devtools';
|
||||
|
||||
// use this for nicer type suggestions, but allow any string anyway
|
||||
type NavigationId = MlNavId | AlNavId | MgmtNavId | DevNavId | string;
|
||||
|
||||
import type { FtrProviderContext } from '../ftr_provider_context';
|
||||
import type { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
|
||||
|
||||
export function SvlCommonNavigationProvider(ctx: FtrProviderContext) {
|
||||
const testSubjects = ctx.getService('testSubjects');
|
||||
const browser = ctx.getService('browser');
|
||||
const retry = ctx.getService('retry');
|
||||
|
||||
async function getByVisibleText(
|
||||
selector: string | (() => Promise<WebElementWrapper[]>),
|
||||
text: string
|
||||
) {
|
||||
const subjects =
|
||||
typeof selector === 'string' ? await testSubjects.findAll(selector) : await selector();
|
||||
let found: WebElementWrapper | null = null;
|
||||
for (const subject of subjects) {
|
||||
const visibleText = await subject.getVisibleText();
|
||||
if (visibleText === text) {
|
||||
found = subject;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
return {
|
||||
// check that chrome ui is in the serverless (project) mode
|
||||
async expectExists() {
|
||||
await testSubjects.existOrFail('kibanaProjectHeader');
|
||||
},
|
||||
async clickLogo() {
|
||||
await testSubjects.click('nav-header-logo');
|
||||
},
|
||||
// side nav related actions
|
||||
sidenav: {
|
||||
async expectLinkExists(
|
||||
by: { deepLinkId: AppDeepLinkId } | { navId: string } | { text: string }
|
||||
) {
|
||||
if ('deepLinkId' in by) {
|
||||
await testSubjects.existOrFail(`~nav-item-deepLinkId-${by.deepLinkId}`);
|
||||
} else if ('navId' in by) {
|
||||
await testSubjects.existOrFail(`~nav-item-id-${by.navId}`);
|
||||
} else {
|
||||
expect(await getByVisibleText('~nav-item', by.text)).not.be(null);
|
||||
}
|
||||
},
|
||||
async expectLinkActive(
|
||||
by: { deepLinkId: AppDeepLinkId } | { navId: string } | { text: string }
|
||||
) {
|
||||
await this.expectLinkExists(by);
|
||||
if ('deepLinkId' in by) {
|
||||
await testSubjects.existOrFail(
|
||||
`~nav-item-deepLinkId-${by.deepLinkId} & ~nav-item-isActive`
|
||||
);
|
||||
} else if ('navId' in by) {
|
||||
await testSubjects.existOrFail(`~nav-item-id-${by.navId} & ~nav-item-isActive`);
|
||||
} else {
|
||||
await retry.try(async () => {
|
||||
const link = await getByVisibleText('~nav-item', by.text);
|
||||
expect(await link!.elementHasClass(`nav-item-isActive`)).to.be(true);
|
||||
});
|
||||
}
|
||||
},
|
||||
async clickLink(by: { deepLinkId: AppDeepLinkId } | { navId: string } | { text: string }) {
|
||||
await this.expectLinkExists(by);
|
||||
if ('deepLinkId' in by) {
|
||||
await testSubjects.click(`~nav-item-deepLinkId-${by.deepLinkId}`);
|
||||
} else if ('navId' in by) {
|
||||
await testSubjects.click(`~nav-item-id-${by.navId}`);
|
||||
} else {
|
||||
await retry.try(async () => {
|
||||
const link = await getByVisibleText('~nav-item', by.text);
|
||||
await link!.click();
|
||||
});
|
||||
}
|
||||
},
|
||||
async expectSectionExists(sectionId: NavigationId) {
|
||||
await testSubjects.existOrFail(`~nav-bucket-${sectionId}`);
|
||||
},
|
||||
async isSectionOpen(sectionId: NavigationId) {
|
||||
await this.expectSectionExists(sectionId);
|
||||
const section = await testSubjects.find(`~nav-bucket-${sectionId}`);
|
||||
const collapseBtn = await section.findByCssSelector(`[aria-controls="${sectionId}"]`);
|
||||
const isExpanded = await collapseBtn.getAttribute('aria-expanded');
|
||||
return isExpanded === 'true';
|
||||
},
|
||||
async expectSectionOpen(sectionId: NavigationId) {
|
||||
await this.expectSectionExists(sectionId);
|
||||
const isOpen = await this.isSectionOpen(sectionId);
|
||||
expect(isOpen).to.be(true);
|
||||
},
|
||||
async expectSectionClosed(sectionId: NavigationId) {
|
||||
await this.expectSectionExists(sectionId);
|
||||
const isOpen = await this.isSectionOpen(sectionId);
|
||||
expect(isOpen).to.be(false);
|
||||
},
|
||||
async openSection(sectionId: NavigationId) {
|
||||
await this.expectSectionExists(sectionId);
|
||||
const isOpen = await this.isSectionOpen(sectionId);
|
||||
if (isOpen) return;
|
||||
const section = await testSubjects.find(`~nav-bucket-${sectionId}`);
|
||||
const collapseBtn = await section.findByCssSelector(`[aria-controls="${sectionId}"]`);
|
||||
await collapseBtn.click();
|
||||
await this.expectSectionOpen(sectionId);
|
||||
},
|
||||
async closeSection(sectionId: NavigationId) {
|
||||
await this.expectSectionExists(sectionId);
|
||||
const isOpen = await this.isSectionOpen(sectionId);
|
||||
if (!isOpen) return;
|
||||
const section = await testSubjects.find(`~nav-bucket-${sectionId}`);
|
||||
const collapseBtn = await section.findByCssSelector(`[aria-controls="${sectionId}"]`);
|
||||
await collapseBtn.click();
|
||||
await this.expectSectionClosed(sectionId);
|
||||
},
|
||||
},
|
||||
breadcrumbs: {
|
||||
async expectExists() {
|
||||
await testSubjects.existOrFail('breadcrumbs');
|
||||
},
|
||||
async clickHome() {
|
||||
await testSubjects.click('~breadcrumb-home');
|
||||
},
|
||||
async expectBreadcrumbExists(by: { deepLinkId: AppDeepLinkId } | { text: string }) {
|
||||
if ('deepLinkId' in by) {
|
||||
await testSubjects.existOrFail(`~breadcrumb-deepLinkId-${by.deepLinkId}`);
|
||||
} else {
|
||||
await retry.try(async () => {
|
||||
expect(await getByVisibleText('~breadcrumb', by.text)).not.be(null);
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
search: new SvlNavigationSearchPageObject(ctx),
|
||||
recent: {
|
||||
async expectExists() {
|
||||
await testSubjects.existOrFail('nav-bucket-recentlyAccessed');
|
||||
},
|
||||
async expectHidden() {
|
||||
await testSubjects.missingOrFail('nav-bucket-recentlyAccessed', { timeout: 1000 });
|
||||
},
|
||||
async expectLinkExists(text: string) {
|
||||
await this.expectExists();
|
||||
let foundLink: WebElementWrapper | null = null;
|
||||
await retry.try(async () => {
|
||||
foundLink = await getByVisibleText(
|
||||
async () =>
|
||||
(await testSubjects.find('nav-bucket-recentlyAccessed')).findAllByTagName('a'),
|
||||
text
|
||||
);
|
||||
expect(!!foundLink).to.be(true);
|
||||
});
|
||||
|
||||
return foundLink!;
|
||||
},
|
||||
async clickLink(text: string) {
|
||||
const link = await this.expectLinkExists(text);
|
||||
await link!.click();
|
||||
},
|
||||
},
|
||||
|
||||
// helper to assert that the page did not reload
|
||||
async createNoPageReloadCheck() {
|
||||
const trackReloadTs = Date.now();
|
||||
await browser.execute(
|
||||
({ ts }) => {
|
||||
// @ts-ignore
|
||||
window.__testTrackReload__ = ts;
|
||||
},
|
||||
{
|
||||
ts: trackReloadTs,
|
||||
}
|
||||
);
|
||||
|
||||
return async () => {
|
||||
const noReload = await browser.execute(
|
||||
({ ts }) => {
|
||||
// @ts-ignore
|
||||
return window.__testTrackReload__ && window.__testTrackReload__ === ts;
|
||||
},
|
||||
{
|
||||
ts: trackReloadTs,
|
||||
}
|
||||
);
|
||||
expect(noReload).to.be(true);
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
|
||||
import { NavigationalSearchPageObject } from '../../../test/functional/page_objects/navigational_search';
|
||||
class SvlNavigationSearchPageObject extends NavigationalSearchPageObject {
|
||||
constructor(ctx: FtrProviderContext) {
|
||||
// @ts-expect-error -- this expects FtrProviderContext from x-pack/test/functional/ftr_provider_context.ts
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
async showSearch() {
|
||||
await this.ctx.getService('testSubjects').click('nav-search-reveal');
|
||||
}
|
||||
async hideSearch() {
|
||||
await this.ctx.getService('testSubjects').click('nav-search-conceal');
|
||||
}
|
||||
}
|
|
@ -10,5 +10,6 @@ import { FtrProviderContext } from '../../ftr_provider_context';
|
|||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('serverless observability UI', function () {
|
||||
loadTestFile(require.resolve('./landing_page'));
|
||||
loadTestFile(require.resolve('./navigation'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default function ({ getPageObject, getService }: FtrProviderContext) {
|
||||
const svlObltOnboardingPage = getPageObject('svlObltOnboardingPage');
|
||||
const svlObltNavigation = getService('svlObltNavigation');
|
||||
const svlCommonNavigation = getPageObject('svlCommonNavigation');
|
||||
const browser = getService('browser');
|
||||
|
||||
describe('navigation', function () {
|
||||
before(async () => {
|
||||
await svlObltNavigation.navigateToLandingPage();
|
||||
});
|
||||
|
||||
it('navigate observability sidenav & breadcrumbs', async () => {
|
||||
const expectNoPageReload = await svlCommonNavigation.createNoPageReloadCheck();
|
||||
|
||||
// check serverless search side nav exists
|
||||
await svlCommonNavigation.expectExists();
|
||||
await svlCommonNavigation.breadcrumbs.expectExists();
|
||||
await svlObltOnboardingPage.assertQuickstartBadgeExists();
|
||||
|
||||
// check side nav links
|
||||
await svlCommonNavigation.sidenav.expectSectionOpen('observability_project_nav');
|
||||
await svlCommonNavigation.sidenav.expectLinkActive({ deepLinkId: 'observabilityOnboarding' });
|
||||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({
|
||||
deepLinkId: 'observabilityOnboarding',
|
||||
});
|
||||
await svlCommonNavigation.sidenav.expectSectionClosed('project_settings_project_nav');
|
||||
|
||||
// TODO: test something oblt project specific instead of generic discover
|
||||
// navigate to discover
|
||||
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'discover' });
|
||||
await svlCommonNavigation.sidenav.expectLinkActive({ deepLinkId: 'discover' });
|
||||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ deepLinkId: 'discover' });
|
||||
await expect(await browser.getCurrentUrl()).contain('/app/discover');
|
||||
|
||||
// check the aiops subsection
|
||||
await svlCommonNavigation.sidenav.clickLink({ navId: 'aiops' }); // open ai ops subsection
|
||||
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'ml:anomalyDetection' });
|
||||
await svlCommonNavigation.sidenav.expectLinkActive({ deepLinkId: 'ml:anomalyDetection' });
|
||||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'AIOps' });
|
||||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({
|
||||
deepLinkId: 'ml:anomalyDetection',
|
||||
});
|
||||
|
||||
// navigate to a different section
|
||||
await svlCommonNavigation.sidenav.openSection('project_settings_project_nav');
|
||||
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management' });
|
||||
await svlCommonNavigation.sidenav.expectLinkActive({ deepLinkId: 'management' });
|
||||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ deepLinkId: 'management' });
|
||||
|
||||
// navigate back to serverless oblt overview
|
||||
await svlCommonNavigation.breadcrumbs.clickHome();
|
||||
await svlCommonNavigation.sidenav.expectLinkActive({ deepLinkId: 'observabilityOnboarding' });
|
||||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({
|
||||
deepLinkId: 'observabilityOnboarding',
|
||||
});
|
||||
await svlCommonNavigation.sidenav.expectSectionOpen(`project_settings_project_nav`); // remains open
|
||||
|
||||
await expectNoPageReload();
|
||||
});
|
||||
|
||||
it('active sidenav section is auto opened on load', async () => {
|
||||
await svlCommonNavigation.sidenav.openSection('project_settings_project_nav');
|
||||
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management' });
|
||||
await browser.refresh();
|
||||
await svlCommonNavigation.expectExists();
|
||||
await svlCommonNavigation.sidenav.expectSectionOpen('project_settings_project_nav');
|
||||
});
|
||||
|
||||
it('navigate using search', async () => {
|
||||
await svlCommonNavigation.search.showSearch();
|
||||
// TODO: test something oblt project specific instead of generic discover
|
||||
await svlCommonNavigation.search.searchFor('discover');
|
||||
await svlCommonNavigation.search.clickOnOption(0);
|
||||
await svlCommonNavigation.search.hideSearch();
|
||||
|
||||
await expect(await browser.getCurrentUrl()).contain('/app/discover');
|
||||
});
|
||||
});
|
||||
}
|
|
@ -10,5 +10,6 @@ import { FtrProviderContext } from '../../ftr_provider_context';
|
|||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('serverless search UI', function () {
|
||||
loadTestFile(require.resolve('./landing_page'));
|
||||
loadTestFile(require.resolve('./navigation'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default function ({ getPageObject, getService }: FtrProviderContext) {
|
||||
const svlSearchLandingPage = getPageObject('svlSearchLandingPage');
|
||||
const svlSearchNavigation = getService('svlSearchNavigation');
|
||||
const svlCommonNavigation = getPageObject('svlCommonNavigation');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const browser = getService('browser');
|
||||
|
||||
describe('navigation', function () {
|
||||
before(async () => {
|
||||
await svlSearchNavigation.navigateToLandingPage();
|
||||
});
|
||||
|
||||
it('navigate search sidenav & breadcrumbs', async () => {
|
||||
const expectNoPageReload = await svlCommonNavigation.createNoPageReloadCheck();
|
||||
|
||||
// check serverless search side nav exists
|
||||
await svlCommonNavigation.expectExists();
|
||||
await svlCommonNavigation.breadcrumbs.expectExists();
|
||||
await svlSearchLandingPage.assertSvlSearchSideNavExists();
|
||||
|
||||
// check side nav links
|
||||
await testSubjects.existOrFail(`svlSearchOverviewPage`);
|
||||
await svlCommonNavigation.sidenav.expectSectionOpen('search_project_nav');
|
||||
await svlCommonNavigation.sidenav.expectLinkActive({
|
||||
deepLinkId: 'serverlessElasticsearch',
|
||||
});
|
||||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({
|
||||
deepLinkId: 'serverlessElasticsearch',
|
||||
});
|
||||
await svlCommonNavigation.sidenav.expectSectionClosed('rootNav:ml');
|
||||
|
||||
// TODO: test something search project specific instead of generic discover
|
||||
// navigate to discover
|
||||
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'discover' });
|
||||
await svlCommonNavigation.sidenav.expectLinkActive({ deepLinkId: 'discover' });
|
||||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: `Explore` });
|
||||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ deepLinkId: 'discover' });
|
||||
await expect(await browser.getCurrentUrl()).contain('/app/discover');
|
||||
|
||||
// navigate to a different section
|
||||
await svlCommonNavigation.sidenav.openSection('rootNav:ml');
|
||||
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'ml:notifications' });
|
||||
await svlCommonNavigation.sidenav.expectLinkActive({ deepLinkId: 'ml:notifications' });
|
||||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: `Machine Learning` });
|
||||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({
|
||||
deepLinkId: 'ml:notifications',
|
||||
});
|
||||
await testSubjects.existOrFail(`mlPageNotifications`);
|
||||
|
||||
// navigate back to serverless search overview
|
||||
await svlCommonNavigation.breadcrumbs.clickHome();
|
||||
await svlCommonNavigation.sidenav.expectLinkActive({
|
||||
deepLinkId: 'serverlessElasticsearch',
|
||||
});
|
||||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: `Getting started` });
|
||||
await testSubjects.existOrFail(`svlSearchOverviewPage`);
|
||||
await svlCommonNavigation.sidenav.expectSectionOpen(`rootNav:ml`); // remains open
|
||||
|
||||
await expectNoPageReload();
|
||||
});
|
||||
|
||||
it('active sidenav section is auto opened on load', async () => {
|
||||
await svlCommonNavigation.sidenav.openSection('rootNav:ml');
|
||||
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'ml:notifications' });
|
||||
await browser.refresh();
|
||||
await testSubjects.existOrFail(`mlPageNotifications`);
|
||||
await svlCommonNavigation.sidenav.expectSectionOpen('rootNav:ml');
|
||||
});
|
||||
|
||||
it('navigate using search', async () => {
|
||||
await svlCommonNavigation.search.showSearch();
|
||||
// TODO: test something search project specific instead of generic discover
|
||||
await svlCommonNavigation.search.searchFor('discover');
|
||||
await svlCommonNavigation.search.clickOnOption(0);
|
||||
await svlCommonNavigation.search.hideSearch();
|
||||
|
||||
await expect(await browser.getCurrentUrl()).contain('/app/discover');
|
||||
});
|
||||
});
|
||||
}
|
|
@ -10,6 +10,7 @@ import { FtrProviderContext } from '../../ftr_provider_context';
|
|||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('serverless security UI', function () {
|
||||
loadTestFile(require.resolve('./landing_page'));
|
||||
loadTestFile(require.resolve('./navigation'));
|
||||
loadTestFile(require.resolve('./management'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default function ({ getPageObject, getService }: FtrProviderContext) {
|
||||
const svlSecLandingPage = getPageObject('svlSecLandingPage');
|
||||
const svlSecNavigation = getService('svlSecNavigation');
|
||||
const svlCommonNavigation = getPageObject('svlCommonNavigation');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const browser = getService('browser');
|
||||
|
||||
describe('navigation', function () {
|
||||
before(async () => {
|
||||
await svlSecNavigation.navigateToLandingPage();
|
||||
});
|
||||
|
||||
it('has security serverless side nav', async () => {
|
||||
await svlSecLandingPage.assertSvlSecSideNavExists();
|
||||
await svlCommonNavigation.expectExists();
|
||||
});
|
||||
|
||||
it('breadcrumbs reflect navigation state', async () => {
|
||||
await svlCommonNavigation.breadcrumbs.expectExists();
|
||||
// TODO: use `deepLinkId` instead of `text`, once security deep links are available in @kbn/core-chrome-browser
|
||||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Get started' });
|
||||
await testSubjects.click('solutionSideNavItemLink-alerts');
|
||||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Alerts' });
|
||||
await svlCommonNavigation.breadcrumbs.clickHome();
|
||||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Get started' });
|
||||
});
|
||||
|
||||
it('navigate using search', async () => {
|
||||
await svlCommonNavigation.search.showSearch();
|
||||
await svlCommonNavigation.search.searchFor('dashboards');
|
||||
await svlCommonNavigation.search.clickOnOption(1);
|
||||
await svlCommonNavigation.search.hideSearch();
|
||||
|
||||
await expect(await browser.getCurrentUrl()).contain('app/security/dashboards');
|
||||
});
|
||||
});
|
||||
}
|
|
@ -27,5 +27,10 @@
|
|||
"@kbn/telemetry-collection-xpack-plugin",
|
||||
"@kbn/telemetry-tools",
|
||||
"@kbn/ftr-common-functional-services",
|
||||
"@kbn/core-chrome-browser",
|
||||
"@kbn/default-nav-ml",
|
||||
"@kbn/default-nav-analytics",
|
||||
"@kbn/default-nav-management",
|
||||
"@kbn/default-nav-devtools",
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue