[Stateful sidenav] Functional tests helpers (#189804)

This commit is contained in:
Sébastien Loix 2024-08-26 10:47:30 +01:00 committed by GitHub
parent c6126d9235
commit 3db781d1f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 849 additions and 376 deletions

View file

@ -272,6 +272,7 @@ enabled:
- x-pack/test/functional/config.firefox.js
- x-pack/test/functional/config.upgrade_assistant.ts
- x-pack/test/functional_cloud/config.ts
- x-pack/test/functional_solution_sidenav/config.ts
- x-pack/test/kubernetes_security/basic/config.ts
- x-pack/test/licensing_plugin/config.public.ts
- x-pack/test/licensing_plugin/config.ts

2
.github/CODEOWNERS vendored
View file

@ -1762,6 +1762,8 @@ x-pack/plugins/observability_solution/observability_shared/public/components/pro
# Shared UX
packages/react @elastic/appex-sharedux
test/functional/page_objects/solution_navigation.ts @elastic/appex-sharedux
/x-pack/test_serverless/functional/page_objects/svl_common_navigation.ts @elastic/appex-sharedux
# OpenAPI spec files
/x-pack/plugins/fleet/common/openapi @elastic/platform-docs

View file

@ -20,6 +20,7 @@ import type {
} from '@kbn/core-chrome-browser';
import type { InternalHttpStart } from '@kbn/core-http-browser-internal';
import {
Subject,
BehaviorSubject,
combineLatest,
map,
@ -32,6 +33,7 @@ import {
of,
type Observable,
type Subscription,
timer,
} from 'rxjs';
import { type Location, createLocation } from 'history';
import deepEqual from 'react-fast-compare';
@ -326,20 +328,50 @@ export class ProjectNavigationService {
}
const { sideNavComponent, homePage = '' } = definition;
const homePageLink = this.navLinksService?.get(homePage);
if (sideNavComponent) {
this.setSideNavComponent(sideNavComponent);
}
if (homePageLink) {
this.setProjectHome(homePageLink.href);
}
this.waitForLink(homePage, (navLink: ChromeNavLink) => {
this.setProjectHome(navLink.href);
});
this.initNavigation(nextId, definition.navigationTree$);
});
}
/**
* This method waits for the chrome nav link to be available and then calls the callback.
* This is necessary to avoid race conditions when we register the solution navigation
* before the deep links are available (plugins can register them later).
*
* @param linkId The chrome nav link id
* @param cb The callback to call when the link is found
* @returns
*/
private waitForLink(linkId: string, cb: (chromeNavLink: ChromeNavLink) => undefined): void {
if (!this.navLinksService) return;
let navLink: ChromeNavLink | undefined = this.navLinksService.get(linkId);
if (navLink) {
cb(navLink);
return;
}
const stop$ = new Subject<void>();
const tenSeconds = timer(10000);
this.deepLinksMap$.pipe(takeUntil(tenSeconds), takeUntil(stop$)).subscribe((navLinks) => {
navLink = navLinks[linkId];
if (navLink) {
cb(navLink);
stop$.next();
}
});
}
private setProjectHome(homeHref: string) {
this.projectHome$.next(homeHref);
}

View file

@ -73,6 +73,7 @@ export const NavigationItemOpenPanel: FC<Props> = ({ item, navigateToUrl, active
[`nav-item-isActive`]: isActive,
});
const buttonDataTestSubj = classNames(`panelOpener`, `panelOpener-${path}`, {
[`panelOpener-id-${id}`]: id,
[`panelOpener-deepLinkId-${deepLink?.id}`]: !!deepLink,
});

View file

@ -19,6 +19,17 @@ import classNames from 'classnames';
import { usePanel } from './context';
import { getNavPanelStyles, getPanelWrapperStyles } from './styles';
import { PanelNavNode } from './types';
const getTestSubj = (selectedNode: PanelNavNode | null): string | undefined => {
if (!selectedNode) return;
const deeplinkId = selectedNode.deepLink?.id;
return classNames(`sideNavPanel`, {
[`sideNavPanel-id-${selectedNode.id}`]: selectedNode.id,
[`sideNavPanel-deepLinkId-${deeplinkId}`]: !!deeplinkId,
});
};
export const NavigationPanel: FC = () => {
const { euiTheme } = useEuiTheme();
@ -67,7 +78,7 @@ export const NavigationPanel: FC = () => {
hasShadow
borderRadius="none"
paddingSize="m"
data-test-subj="sideNavPanel"
data-test-subj={getTestSubj(selectedNode)}
>
{getContent()}
</EuiPanel>

View file

@ -27,7 +27,7 @@ export type ContentProvider = (nodeId: string) => PanelContent | void;
export type PanelNavNode = Pick<
ChromeProjectNavigationNode,
'id' | 'children' | 'path' | 'sideNavStatus'
'id' | 'children' | 'path' | 'sideNavStatus' | 'deepLink'
> & {
title: string | ReactNode;
};

View file

@ -0,0 +1,49 @@
/*
* 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 type { FtrProviderContext } from '../ftr_provider_context';
export function EmbeddedConsoleProvider(ctx: FtrProviderContext) {
const testSubjects = ctx.getService('testSubjects');
return {
async expectEmbeddedConsoleControlBarExists() {
await testSubjects.existOrFail('consoleEmbeddedSection');
},
async expectEmbeddedConsoleToBeOpen() {
await testSubjects.existOrFail('consoleEmbeddedBody');
},
async expectEmbeddedConsoleToBeClosed() {
await testSubjects.missingOrFail('consoleEmbeddedBody');
},
async clickEmbeddedConsoleControlBar() {
await testSubjects.click('consoleEmbeddedControlBar');
},
async expectEmbeddedConsoleNotebooksButtonExists() {
await testSubjects.existOrFail('consoleEmbeddedNotebooksButton');
},
async clickEmbeddedConsoleNotebooksButton() {
await testSubjects.click('consoleEmbeddedNotebooksButton');
},
async expectEmbeddedConsoleNotebooksToBeOpen() {
await testSubjects.existOrFail('consoleEmbeddedNotebooksContainer');
},
async expectEmbeddedConsoleNotebooksToBeClosed() {
await testSubjects.missingOrFail('consoleEmbeddedNotebooksContainer');
},
async expectEmbeddedConsoleNotebookListItemToBeAvailable(id: string) {
await testSubjects.existOrFail(`console-embedded-notebook-select-btn-${id}`);
},
async clickEmbeddedConsoleNotebook(id: string) {
await testSubjects.click(`console-embedded-notebook-select-btn-${id}`);
},
async expectEmbeddedConsoleNotebookToBeAvailable(id: string) {
await testSubjects.click(`console-embedded-notebook-select-btn-${id}`);
},
};
}

View file

@ -36,6 +36,8 @@ import { UnifiedSearchPageObject } from './unified_search_page';
import { UnifiedFieldListPageObject } from './unified_field_list';
import { FilesManagementPageObject } from './files_management';
import { AnnotationEditorPageObject } from './annotation_library_editor_page';
import { SolutionNavigationProvider } from './solution_navigation';
import { EmbeddedConsoleProvider } from './embedded_console';
export const pageObjects = {
annotationEditor: AnnotationEditorPageObject,
@ -46,12 +48,14 @@ export const pageObjects = {
dashboardControls: DashboardPageControls,
dashboardLinks: DashboardPageLinks,
discover: DiscoverPageObject,
embeddedConsole: EmbeddedConsoleProvider,
error: ErrorPageObject,
header: HeaderPageObject,
home: HomePageObject,
newsfeed: NewsfeedPageObject,
settings: SettingsPageObject,
share: SharePageObject,
solutionNavigation: SolutionNavigationProvider,
legacyDataTableVis: LegacyDataTableVisPageObject,
login: LoginPageObject,
timelion: TimelionPageObject,
@ -69,3 +73,5 @@ export const pageObjects = {
unifiedFieldList: UnifiedFieldListPageObject,
filesManagement: FilesManagementPageObject,
};
export { SolutionNavigationProvider } from './solution_navigation';

View file

@ -0,0 +1,338 @@
/*
* 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 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 { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import type { FtrProviderContext } from '../ftr_provider_context';
const getSectionIdTestSubj = (sectionId: NavigationId) => `~nav-item-${sectionId}`;
const TIMEOUT_CHECK = 3000;
export function SolutionNavigationProvider(ctx: Pick<FtrProviderContext, 'getService'>) {
const testSubjects = ctx.getService('testSubjects');
const browser = ctx.getService('browser');
const retry = ctx.getService('retry');
const log = ctx.getService('log');
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 project/solution 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}`, {
timeout: TIMEOUT_CHECK,
});
} else if ('navId' in by) {
await testSubjects.existOrFail(`~nav-item-id-${by.navId}`, { timeout: TIMEOUT_CHECK });
} else {
expect(await getByVisibleText('~nav-item', by.text)).not.be(null);
}
},
async expectLinkMissing(
by: { deepLinkId: AppDeepLinkId } | { navId: string } | { text: string }
) {
if ('deepLinkId' in by) {
await testSubjects.missingOrFail(`~nav-item-deepLinkId-${by.deepLinkId}`, {
timeout: TIMEOUT_CHECK,
});
} else if ('navId' in by) {
await testSubjects.missingOrFail(`~nav-item-id-${by.navId}`, { timeout: TIMEOUT_CHECK });
} else {
expect(await getByVisibleText('~nav-item', by.text)).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`,
{ timeout: TIMEOUT_CHECK }
);
} else if ('navId' in by) {
await testSubjects.existOrFail(`~nav-item-id-${by.navId} & ~nav-item-isActive`, {
timeout: TIMEOUT_CHECK,
});
} 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 findLink(by: { deepLinkId: AppDeepLinkId } | { navId: string } | { text: string }) {
await this.expectLinkExists(by);
if ('deepLinkId' in by) {
return testSubjects.find(`~nav-item-deepLinkId-${by.deepLinkId}`);
} else if ('navId' in by) {
return testSubjects.find(`~nav-item-id-${by.navId}`);
} else {
return retry.try(async () => {
const link = await getByVisibleText('~nav-item', by.text);
return link;
});
}
},
async expectSectionExists(sectionId: NavigationId) {
log.debug('SolutionNavigation.sidenav.expectSectionExists', sectionId);
await testSubjects.existOrFail(getSectionIdTestSubj(sectionId), { timeout: TIMEOUT_CHECK });
},
async isSectionOpen(sectionId: NavigationId) {
await this.expectSectionExists(sectionId);
const collapseBtn = await testSubjects.find(`~accordionArrow-${sectionId}`);
const isExpanded = await collapseBtn.getAttribute('aria-expanded');
return isExpanded === 'true';
},
async expectSectionOpen(sectionId: NavigationId) {
log.debug('SolutionNavigation.sidenav.expectSectionOpen', sectionId);
await this.expectSectionExists(sectionId);
await retry.waitFor(`section ${sectionId} to be open`, async () => {
const isOpen = await this.isSectionOpen(sectionId);
return isOpen;
});
},
async expectSectionClosed(sectionId: NavigationId) {
await this.expectSectionExists(sectionId);
await retry.waitFor(`section ${sectionId} to be closed`, async () => {
const isOpen = await this.isSectionOpen(sectionId);
return !isOpen;
});
},
async openSection(sectionId: NavigationId) {
log.debug('SolutionNavigation.sidenav.openSection', sectionId);
await this.expectSectionExists(sectionId);
const isOpen = await this.isSectionOpen(sectionId);
if (isOpen) return;
const collapseBtn = await testSubjects.find(`~accordionArrow-${sectionId}`, TIMEOUT_CHECK);
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 collapseBtn = await testSubjects.find(`~accordionArrow-${sectionId}`, TIMEOUT_CHECK);
await collapseBtn.click();
await this.expectSectionClosed(sectionId);
},
async expectPanelExists(sectionId: NavigationId) {
log.debug('SolutionNavigation.sidenav.expectPanelExists', sectionId);
await testSubjects.existOrFail(`~sideNavPanel-id-${sectionId}`, {
timeout: TIMEOUT_CHECK,
});
},
async isPanelOpen(sectionId: NavigationId) {
try {
const panel = await testSubjects.find(`~sideNavPanel-id-${sectionId}`, TIMEOUT_CHECK);
return !!panel;
} catch (err) {
return false;
}
},
async openPanel(sectionId: NavigationId) {
log.debug('SolutionNavigation.sidenav.openPanel', sectionId);
const isOpen = await this.isPanelOpen(sectionId);
if (isOpen) return;
const panelOpenerBtn = await testSubjects.find(
`~panelOpener-id-${sectionId}`,
TIMEOUT_CHECK
);
await panelOpenerBtn.click();
},
async isCollapsed() {
const collapseNavBtn = await testSubjects.find('euiCollapsibleNavButton', TIMEOUT_CHECK);
return (await collapseNavBtn.getAttribute('aria-expanded')) === 'false';
},
async isExpanded() {
return !(await this.isCollapsed());
},
/**
* Toggles collapsed state of sidenav
*/
async toggle(collapsed?: boolean) {
const currentlyCollapsed = await this.isCollapsed();
const shouldBeCollapsed = collapsed ?? !currentlyCollapsed;
if (currentlyCollapsed !== shouldBeCollapsed) {
log.debug(
'SolutionNavigation.sidenav.toggle',
shouldBeCollapsed ? 'Collapsing' : 'Expanding'
);
const collapseNavBtn = await testSubjects.find('euiCollapsibleNavButton', TIMEOUT_CHECK);
await collapseNavBtn.click();
}
},
},
breadcrumbs: {
async expectExists() {
await testSubjects.existOrFail('breadcrumbs', { timeout: TIMEOUT_CHECK });
},
async clickBreadcrumb(by: { deepLinkId: AppDeepLinkId } | { text: string }) {
if ('deepLinkId' in by) {
await testSubjects.click(`~breadcrumb-deepLinkId-${by.deepLinkId}`);
} else {
await (await getByVisibleText('~breadcrumb', by.text))?.click();
}
},
getBreadcrumb(by: { deepLinkId: AppDeepLinkId } | { text: string }) {
if ('deepLinkId' in by) {
return testSubjects.find(`~breadcrumb-deepLinkId-${by.deepLinkId}`, TIMEOUT_CHECK);
} else {
return getByVisibleText('~breadcrumb', by.text);
}
},
async expectBreadcrumbExists(by: { deepLinkId: AppDeepLinkId } | { text: string }) {
log.debug('SolutionNavigation.breadcrumbs.expectBreadcrumbExists', JSON.stringify(by));
if ('deepLinkId' in by) {
await testSubjects.existOrFail(`~breadcrumb-deepLinkId-${by.deepLinkId}`, {
timeout: TIMEOUT_CHECK,
});
} else {
await retry.try(async () => {
expect(await getByVisibleText('~breadcrumb', by.text)).not.be(null);
});
}
},
async expectBreadcrumbMissing(by: { deepLinkId: AppDeepLinkId } | { text: string }) {
if ('deepLinkId' in by) {
await testSubjects.missingOrFail(`~breadcrumb-deepLinkId-${by.deepLinkId}`, {
timeout: TIMEOUT_CHECK,
});
} else {
await retry.try(async () => {
expect(await getByVisibleText('~breadcrumb', by.text)).be(null);
});
}
},
async expectBreadcrumbTexts(expectedBreadcrumbTexts: string[]) {
log.debug(
'SolutionNavigation.breadcrumbs.expectBreadcrumbTexts',
JSON.stringify(expectedBreadcrumbTexts)
);
await retry.try(async () => {
const breadcrumbsContainer = await testSubjects.find('breadcrumbs', TIMEOUT_CHECK);
const breadcrumbs = await breadcrumbsContainer.findAllByTestSubject('~breadcrumb');
breadcrumbs.shift(); // remove home
expect(expectedBreadcrumbTexts.length).to.eql(breadcrumbs.length);
const texts = await Promise.all(breadcrumbs.map((b) => b.getVisibleText()));
expect(expectedBreadcrumbTexts).to.eql(texts);
});
},
},
recent: {
async expectExists() {
await testSubjects.existOrFail('nav-item-recentlyAccessed', { timeout: TIMEOUT_CHECK });
},
async expectHidden() {
await testSubjects.missingOrFail('nav-item-recentlyAccessed', { timeout: TIMEOUT_CHECK });
},
async expectLinkExists(text: string) {
await this.expectExists();
let foundLink: WebElementWrapper | null = null;
await retry.try(async () => {
foundLink = await getByVisibleText(
async () =>
(
await testSubjects.find('nav-item-recentlyAccessed', TIMEOUT_CHECK)
).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);
};
},
};
}

View file

@ -73,5 +73,10 @@
"@kbn/monaco",
"@kbn/search-types",
"@kbn/console-plugin",
"@kbn/core-chrome-browser",
"@kbn/default-nav-ml",
"@kbn/default-nav-analytics",
"@kbn/default-nav-management",
"@kbn/default-nav-devtools",
]
}

View file

@ -10,8 +10,22 @@ import Axios from 'axios';
import Https from 'https';
import { format as formatUrl } from 'url';
import util from 'util';
import Chance from 'chance';
import Url from 'url';
import { FtrProviderContext } from '../ftr_provider_context';
const chance = new Chance();
interface SpaceCreate {
name?: string;
id?: string;
description?: string;
color?: string;
initials?: string;
solution?: 'es' | 'oblt' | 'security' | 'classic';
disabledFeatures?: string[];
}
export function SpacesServiceProvider({ getService }: FtrProviderContext) {
const log = getService('log');
const config = getService('config');
@ -35,7 +49,9 @@ export function SpacesServiceProvider({ getService }: FtrProviderContext) {
});
return new (class SpacesService {
public async create(space: any) {
public async create(_space?: SpaceCreate) {
const space = { id: chance.guid(), name: 'foo', ..._space };
log.debug(`creating space ${space.id}`);
const { data, status, statusText } = await axios.post('/api/spaces/space', space);
@ -45,6 +61,15 @@ export function SpacesServiceProvider({ getService }: FtrProviderContext) {
);
}
log.debug(`created space ${space.id}`);
const cleanUp = async () => {
return this.delete(space.id);
};
return {
cleanUp,
space,
};
}
public async delete(spaceId: string) {
@ -72,5 +97,17 @@ export function SpacesServiceProvider({ getService }: FtrProviderContext) {
return data;
}
/** Return the full URL that points to the root of the space */
public getRootUrl(spaceId: string) {
const { protocol, hostname, port } = config.get('servers.kibana');
return Url.format({
protocol,
hostname,
port,
pathname: `/s/${spaceId}`,
});
}
})();
}

View file

@ -0,0 +1,31 @@
/*
* 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 { FtrConfigProviderContext } from '@kbn/test';
/**
* NOTE: The solution view is currently only available in the cloud environment.
* This test suite fakes a cloud environement by setting the cloud.id and cloud.base_url
*/
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const functionalConfig = await readConfigFile(require.resolve('../functional/config.base.js'));
return {
...functionalConfig.getAll(),
testFiles: [require.resolve('.')],
kbnTestServer: {
...functionalConfig.get('kbnTestServer'),
serverArgs: [
...functionalConfig.get('kbnTestServer.serverArgs'),
// Note: the base64 string in the cloud.id config contains the ES endpoint required in the functional tests
'--xpack.cloud.id=ftr_fake_cloud_id:aGVsbG8uY29tOjQ0MyRFUzEyM2FiYyRrYm4xMjNhYmM=',
'--xpack.cloud.base_url=https://cloud.elastic.co',
],
},
};
}

View file

@ -0,0 +1,13 @@
/*
* 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 { GenericFtrProviderContext } from '@kbn/test';
import { pageObjects } from '../functional/page_objects';
import { services } from './services';
export type FtrProviderContext = GenericFtrProviderContext<typeof services, typeof pageObjects>;
export { pageObjects };

View file

@ -0,0 +1,17 @@
/*
* 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.
*/
/* eslint-disable import/no-default-export */
import { FtrProviderContext } from './ftr_provider_context';
export default ({ loadTestFile }: FtrProviderContext): void => {
describe('Solution navigation smoke tests', function () {
loadTestFile(require.resolve('./tests/observability_sidenav'));
loadTestFile(require.resolve('./tests/search_sidenav'));
loadTestFile(require.resolve('./tests/security_sidenav'));
});
};

View file

@ -0,0 +1,10 @@
/*
* 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 { services as functionalServices } from '../functional/services';
export const services = functionalServices;

View file

@ -0,0 +1,79 @@
/*
* 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 { FtrProviderContext } from '../ftr_provider_context';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const { common, solutionNavigation } = getPageObjects(['common', 'solutionNavigation']);
const spaces = getService('spaces');
const browser = getService('browser');
describe('observability solution', () => {
let cleanUp: () => Promise<unknown>;
let spaceCreated: { id: string } = { id: '' };
before(async () => {
// Navigate to the spaces management page which will log us in Kibana
await common.navigateToUrl('management', 'kibana/spaces', {
shouldUseHashForSubUrl: false,
});
// Create a space with the observability solution and navigate to its home page
({ cleanUp, space: spaceCreated } = await spaces.create({ solution: 'oblt' }));
await browser.navigateTo(spaces.getRootUrl(spaceCreated.id));
});
after(async () => {
// Clean up space created
await cleanUp();
});
describe('sidenav & breadcrumbs', () => {
it('renders the correct nav and navigate to links', async () => {
const expectNoPageReload = await solutionNavigation.createNoPageReloadCheck();
await solutionNavigation.expectExists();
await solutionNavigation.breadcrumbs.expectExists();
// check side nav links
await solutionNavigation.sidenav.expectSectionExists('observability_project_nav');
await solutionNavigation.sidenav.expectLinkActive({
deepLinkId: 'observabilityOnboarding',
});
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({
deepLinkId: 'observabilityOnboarding',
});
// check the AI & ML subsection
await solutionNavigation.sidenav.openSection('observability_project_nav.aiMl'); // open AI & ML subsection
await solutionNavigation.sidenav.clickLink({ deepLinkId: 'ml:anomalyDetection' });
await solutionNavigation.sidenav.expectLinkActive({ deepLinkId: 'ml:anomalyDetection' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'AI & ML' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({
deepLinkId: 'ml:anomalyDetection',
});
// navigate to a different section
await solutionNavigation.sidenav.openSection('project_settings_project_nav');
await solutionNavigation.sidenav.clickLink({ deepLinkId: 'management' });
await solutionNavigation.sidenav.expectLinkActive({ deepLinkId: 'management' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ deepLinkId: 'management' });
// navigate back to the home page using header logo
await solutionNavigation.clickLogo();
await solutionNavigation.sidenav.expectLinkActive({
deepLinkId: 'observabilityOnboarding',
});
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({
deepLinkId: 'observabilityOnboarding',
});
await expectNoPageReload();
});
});
});
}

View file

@ -0,0 +1,82 @@
/*
* 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 { FtrProviderContext } from '../ftr_provider_context';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const { common, solutionNavigation } = getPageObjects(['common', 'solutionNavigation']);
const spaces = getService('spaces');
const browser = getService('browser');
describe('search solution', () => {
let cleanUp: () => Promise<unknown>;
let spaceCreated: { id: string } = { id: '' };
before(async () => {
// Navigate to the spaces management page which will log us in Kibana
await common.navigateToUrl('management', 'kibana/spaces', {
shouldUseHashForSubUrl: false,
});
// Create a space with the search solution and navigate to its home page
({ cleanUp, space: spaceCreated } = await spaces.create({ solution: 'es' }));
await browser.navigateTo(spaces.getRootUrl(spaceCreated.id));
});
after(async () => {
// Clean up space created
await cleanUp();
});
describe('sidenav & breadcrumbs', () => {
it('renders the correct nav and navigate to links', async () => {
const expectNoPageReload = await solutionNavigation.createNoPageReloadCheck();
await solutionNavigation.expectExists();
await solutionNavigation.breadcrumbs.expectExists();
// check side nav links
await solutionNavigation.sidenav.expectSectionExists('search_project_nav');
await solutionNavigation.sidenav.expectLinkActive({
deepLinkId: 'enterpriseSearch',
});
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({
deepLinkId: 'enterpriseSearch',
});
// check the Content > Indices section
await solutionNavigation.sidenav.clickLink({
deepLinkId: 'enterpriseSearchContent:searchIndices',
});
await solutionNavigation.sidenav.expectLinkActive({
deepLinkId: 'enterpriseSearchContent:searchIndices',
});
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Indices' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({
deepLinkId: 'enterpriseSearchContent:searchIndices',
});
// navigate to a different section
await solutionNavigation.sidenav.openSection('project_settings_project_nav');
await solutionNavigation.sidenav.clickLink({ deepLinkId: 'management' });
await solutionNavigation.sidenav.expectLinkActive({ deepLinkId: 'management' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ deepLinkId: 'management' });
// navigate back to the home page using header logo
await solutionNavigation.clickLogo();
await solutionNavigation.sidenav.expectLinkActive({
deepLinkId: 'enterpriseSearch',
});
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({
deepLinkId: 'enterpriseSearch',
});
await expectNoPageReload();
});
});
});
}

View file

@ -0,0 +1,74 @@
/*
* 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 { FtrProviderContext } from '../ftr_provider_context';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const { common, solutionNavigation } = getPageObjects(['common', 'solutionNavigation']);
const spaces = getService('spaces');
const browser = getService('browser');
const testSubjects = getService('testSubjects');
describe('security solution', () => {
let cleanUp: () => Promise<unknown>;
let spaceCreated: { id: string } = { id: '' };
before(async () => {
// Navigate to the spaces management page which will log us in Kibana
await common.navigateToUrl('management', 'kibana/spaces', {
shouldUseHashForSubUrl: false,
});
// Create a space with the security solution and navigate to its home page
({ cleanUp, space: spaceCreated } = await spaces.create({ solution: 'security' }));
await browser.navigateTo(spaces.getRootUrl(spaceCreated.id));
});
after(async () => {
// Clean up space created
await cleanUp();
});
describe('sidenav & breadcrumbs', () => {
it('renders the correct nav and navigate to links', async () => {
const expectNoPageReload = await solutionNavigation.createNoPageReloadCheck();
await solutionNavigation.expectExists();
await solutionNavigation.breadcrumbs.expectExists();
// check side nav links
await solutionNavigation.sidenav.expectSectionExists('security_solution_nav');
await solutionNavigation.sidenav.expectLinkActive({
deepLinkId: 'securitySolutionUI:get_started',
});
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({
deepLinkId: 'securitySolutionUI:get_started',
});
// check the Investigations subsection
await solutionNavigation.sidenav.openPanel('investigations'); // open Investigations panel
await testSubjects.click(`~solutionSideNavPanelLink-timelines`);
await solutionNavigation.sidenav.expectLinkActive({ navId: 'investigations' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Timelines' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({
deepLinkId: 'securitySolutionUI:timelines',
});
// navigate back to the home page using header logo
await solutionNavigation.clickLogo();
await solutionNavigation.sidenav.expectLinkActive({
deepLinkId: 'securitySolutionUI:get_started',
});
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({
deepLinkId: 'securitySolutionUI:get_started',
});
await expectNoPageReload();
});
});
});
}

View file

@ -5,334 +5,17 @@
* 2.0.
*/
import expect from '@kbn/expect';
import type { AppDeepLinkId } from '@kbn/core-chrome-browser';
import { SolutionNavigationProvider } from '@kbn/test-suites-src/functional/page_objects';
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 { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { NavigationalSearchPageObject } from '@kbn/test-suites-xpack/functional/page_objects/navigational_search';
import type { FtrProviderContext } from '../ftr_provider_context';
const getSectionIdTestSubj = (sectionId: NavigationId) => `~nav-item-${sectionId}`;
export function SvlCommonNavigationProvider(ctx: FtrProviderContext) {
const testSubjects = ctx.getService('testSubjects');
const browser = ctx.getService('browser');
const retry = ctx.getService('retry');
const log = ctx.getService('log');
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;
}
const solutionNavigation = SolutionNavigationProvider(ctx);
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 expectLinkMissing(
by: { deepLinkId: AppDeepLinkId } | { navId: string } | { text: string }
) {
if ('deepLinkId' in by) {
await testSubjects.missingOrFail(`~nav-item-deepLinkId-${by.deepLinkId}`);
} else if ('navId' in by) {
await testSubjects.missingOrFail(`~nav-item-id-${by.navId}`);
} else {
expect(await getByVisibleText('~nav-item', by.text)).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 findLink(by: { deepLinkId: AppDeepLinkId } | { navId: string } | { text: string }) {
await this.expectLinkExists(by);
if ('deepLinkId' in by) {
return testSubjects.find(`~nav-item-deepLinkId-${by.deepLinkId}`);
} else if ('navId' in by) {
return testSubjects.find(`~nav-item-id-${by.navId}`);
} else {
return retry.try(async () => {
const link = await getByVisibleText('~nav-item', by.text);
return link;
});
}
},
async expectSectionExists(sectionId: NavigationId) {
log.debug('ServerlessCommonNavigation.sidenav.expectSectionExists', sectionId);
await testSubjects.existOrFail(getSectionIdTestSubj(sectionId));
},
async isSectionOpen(sectionId: NavigationId) {
await this.expectSectionExists(sectionId);
const collapseBtn = await testSubjects.find(`~accordionArrow-${sectionId}`);
const isExpanded = await collapseBtn.getAttribute('aria-expanded');
return isExpanded === 'true';
},
async expectSectionOpen(sectionId: NavigationId) {
log.debug('ServerlessCommonNavigation.sidenav.expectSectionOpen', sectionId);
await this.expectSectionExists(sectionId);
await retry.waitFor(`section ${sectionId} to be open`, async () => {
const isOpen = await this.isSectionOpen(sectionId);
return isOpen;
});
},
async expectSectionClosed(sectionId: NavigationId) {
await this.expectSectionExists(sectionId);
await retry.waitFor(`section ${sectionId} to be closed`, async () => {
const isOpen = await this.isSectionOpen(sectionId);
return !isOpen;
});
},
async openSection(sectionId: NavigationId) {
log.debug('ServerlessCommonNavigation.sidenav.openSection', sectionId);
await this.expectSectionExists(sectionId);
const isOpen = await this.isSectionOpen(sectionId);
if (isOpen) return;
const collapseBtn = await testSubjects.find(`~accordionArrow-${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 collapseBtn = await testSubjects.find(`~accordionArrow-${sectionId}`);
await collapseBtn.click();
await this.expectSectionClosed(sectionId);
},
async isCollapsed() {
const collapseNavBtn = await testSubjects.find('euiCollapsibleNavButton');
return (await collapseNavBtn.getAttribute('aria-expanded')) === 'false';
},
async isExpanded() {
return !(await this.isCollapsed());
},
/**
* Toggles collapsed state of sidenav
*/
async toggle(collapsed?: boolean) {
const currentlyCollapsed = await this.isCollapsed();
const shouldBeCollapsed = collapsed ?? !currentlyCollapsed;
if (currentlyCollapsed !== shouldBeCollapsed) {
log.debug(
'ServerlessCommonNavigation.sidenav.toggle',
shouldBeCollapsed ? 'Collapsing' : 'Expanding'
);
const collapseNavBtn = await testSubjects.find('euiCollapsibleNavButton');
await collapseNavBtn.click();
}
},
},
breadcrumbs: {
async expectExists() {
await testSubjects.existOrFail('breadcrumbs');
},
async clickBreadcrumb(by: { deepLinkId: AppDeepLinkId } | { text: string }) {
if ('deepLinkId' in by) {
await testSubjects.click(`~breadcrumb-deepLinkId-${by.deepLinkId}`);
} else {
await (await getByVisibleText('~breadcrumb', by.text))?.click();
}
},
getBreadcrumb(by: { deepLinkId: AppDeepLinkId } | { text: string }) {
if ('deepLinkId' in by) {
return testSubjects.find(`~breadcrumb-deepLinkId-${by.deepLinkId}`);
} else {
return getByVisibleText('~breadcrumb', by.text);
}
},
async expectBreadcrumbExists(by: { deepLinkId: AppDeepLinkId } | { text: string }) {
log.debug(
'ServerlessCommonNavigation.breadcrumbs.expectBreadcrumbExists',
JSON.stringify(by)
);
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);
});
}
},
async expectBreadcrumbMissing(by: { deepLinkId: AppDeepLinkId } | { text: string }) {
if ('deepLinkId' in by) {
await testSubjects.missingOrFail(`~breadcrumb-deepLinkId-${by.deepLinkId}`);
} else {
await retry.try(async () => {
expect(await getByVisibleText('~breadcrumb', by.text)).be(null);
});
}
},
async expectBreadcrumbTexts(expectedBreadcrumbTexts: string[]) {
log.debug(
'ServerlessCommonNavigation.breadcrumbs.expectBreadcrumbTexts',
JSON.stringify(expectedBreadcrumbTexts)
);
await retry.try(async () => {
const breadcrumbsContainer = await testSubjects.find('breadcrumbs');
const breadcrumbs = await breadcrumbsContainer.findAllByTestSubject('~breadcrumb');
breadcrumbs.shift(); // remove home
expect(expectedBreadcrumbTexts.length).to.eql(breadcrumbs.length);
const texts = await Promise.all(breadcrumbs.map((b) => b.getVisibleText()));
expect(expectedBreadcrumbTexts).to.eql(texts);
});
},
},
...solutionNavigation,
search: new SvlNavigationSearchPageObject(ctx),
recent: {
async expectExists() {
await testSubjects.existOrFail('nav-item-recentlyAccessed');
},
async expectHidden() {
await testSubjects.missingOrFail('nav-item-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-item-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);
};
},
// embedded dev console
devConsole: {
async expectEmbeddedConsoleControlBarExists() {
await testSubjects.existOrFail('consoleEmbeddedSection');
},
async expectEmbeddedConsoleToBeOpen() {
await testSubjects.existOrFail('consoleEmbeddedBody');
},
async expectEmbeddedConsoleToBeClosed() {
await testSubjects.missingOrFail('consoleEmbeddedBody');
},
async clickEmbeddedConsoleControlBar() {
await testSubjects.click('consoleEmbeddedControlBar');
},
async expectEmbeddedConsoleNotebooksButtonExists() {
await testSubjects.existOrFail('consoleEmbeddedNotebooksButton');
},
async clickEmbeddedConsoleNotebooksButton() {
await testSubjects.click('consoleEmbeddedNotebooksButton');
},
async expectEmbeddedConsoleNotebooksToBeOpen() {
await testSubjects.existOrFail('consoleEmbeddedNotebooksContainer');
},
async expectEmbeddedConsoleNotebooksToBeClosed() {
await testSubjects.missingOrFail('consoleEmbeddedNotebooksContainer');
},
async expectEmbeddedConsoleNotebookListItemToBeAvailable(id: string) {
await testSubjects.existOrFail(`console-embedded-notebook-select-btn-${id}`);
},
async clickEmbeddedConsoleNotebook(id: string) {
await testSubjects.click(`console-embedded-notebook-select-btn-${id}`);
},
async expectEmbeddedConsoleNotebookToBeAvailable(id: string) {
await testSubjects.click(`console-embedded-notebook-select-btn-${id}`);
},
},
};
}

View file

@ -14,6 +14,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
'svlCommonNavigation',
'common',
'svlSearchConnectorsPage',
'embeddedConsole',
]);
const testSubjects = getService('testSubjects');
const browser = getService('browser');

View file

@ -7,8 +7,8 @@
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const pageObjects = getPageObjects(['svlCommonPage', 'svlCommonNavigation']);
export default function ({ getPageObjects }: FtrProviderContext) {
const pageObjects = getPageObjects(['svlCommonPage', 'embeddedConsole']);
describe('Console Notebooks', function () {
before(async () => {
@ -17,39 +17,39 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
it('has notebooks view available', async () => {
// Expect Console Bar & Notebooks button to exist
await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleControlBarExists();
await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleNotebooksButtonExists();
await pageObjects.embeddedConsole.expectEmbeddedConsoleControlBarExists();
await pageObjects.embeddedConsole.expectEmbeddedConsoleNotebooksButtonExists();
// Click the Notebooks button to open console to See Notebooks
await pageObjects.svlCommonNavigation.devConsole.clickEmbeddedConsoleNotebooksButton();
await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleNotebooksToBeOpen();
await pageObjects.embeddedConsole.clickEmbeddedConsoleNotebooksButton();
await pageObjects.embeddedConsole.expectEmbeddedConsoleNotebooksToBeOpen();
// Click the Notebooks button again to switch to the dev console
await pageObjects.svlCommonNavigation.devConsole.clickEmbeddedConsoleNotebooksButton();
await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleToBeOpen();
await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleNotebooksToBeClosed();
await pageObjects.embeddedConsole.clickEmbeddedConsoleNotebooksButton();
await pageObjects.embeddedConsole.expectEmbeddedConsoleToBeOpen();
await pageObjects.embeddedConsole.expectEmbeddedConsoleNotebooksToBeClosed();
// Clicking control bar should close the console
await pageObjects.svlCommonNavigation.devConsole.clickEmbeddedConsoleControlBar();
await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleNotebooksToBeClosed();
await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleToBeClosed();
await pageObjects.embeddedConsole.clickEmbeddedConsoleControlBar();
await pageObjects.embeddedConsole.expectEmbeddedConsoleNotebooksToBeClosed();
await pageObjects.embeddedConsole.expectEmbeddedConsoleToBeClosed();
// Re-open console and then open Notebooks
await pageObjects.svlCommonNavigation.devConsole.clickEmbeddedConsoleControlBar();
await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleToBeOpen();
await pageObjects.svlCommonNavigation.devConsole.clickEmbeddedConsoleNotebooksButton();
await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleNotebooksToBeOpen();
await pageObjects.embeddedConsole.clickEmbeddedConsoleControlBar();
await pageObjects.embeddedConsole.expectEmbeddedConsoleToBeOpen();
await pageObjects.embeddedConsole.clickEmbeddedConsoleNotebooksButton();
await pageObjects.embeddedConsole.expectEmbeddedConsoleNotebooksToBeOpen();
// Close the console
await pageObjects.svlCommonNavigation.devConsole.clickEmbeddedConsoleControlBar();
await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleNotebooksToBeClosed();
await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleToBeClosed();
await pageObjects.embeddedConsole.clickEmbeddedConsoleControlBar();
await pageObjects.embeddedConsole.expectEmbeddedConsoleNotebooksToBeClosed();
await pageObjects.embeddedConsole.expectEmbeddedConsoleToBeClosed();
});
it('can open notebooks', async () => {
// Click the Notebooks button to open console to See Notebooks
await pageObjects.svlCommonNavigation.devConsole.clickEmbeddedConsoleNotebooksButton();
await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleNotebooksToBeOpen();
await pageObjects.embeddedConsole.clickEmbeddedConsoleNotebooksButton();
await pageObjects.embeddedConsole.expectEmbeddedConsoleNotebooksToBeOpen();
const defaultNotebooks = [
'00_quick_start',
@ -59,13 +59,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
'04_multilingual',
];
for (const notebookId of defaultNotebooks) {
await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleNotebookListItemToBeAvailable(
notebookId
);
await pageObjects.svlCommonNavigation.devConsole.clickEmbeddedConsoleNotebook(notebookId);
await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleNotebookToBeAvailable(
await pageObjects.embeddedConsole.expectEmbeddedConsoleNotebookListItemToBeAvailable(
notebookId
);
await pageObjects.embeddedConsole.clickEmbeddedConsoleNotebook(notebookId);
await pageObjects.embeddedConsole.expectEmbeddedConsoleNotebookToBeAvailable(notebookId);
}
});
});

View file

@ -7,13 +7,13 @@
import { FtrProviderContext } from '../../ftr_provider_context';
type PageObjects = Pick<ReturnType<FtrProviderContext['getPageObjects']>, 'svlCommonNavigation'>;
type PageObjects = Pick<ReturnType<FtrProviderContext['getPageObjects']>, 'embeddedConsole'>;
export async function testHasEmbeddedConsole(pageObjects: PageObjects) {
await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleControlBarExists();
await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleToBeClosed();
await pageObjects.svlCommonNavigation.devConsole.clickEmbeddedConsoleControlBar();
await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleToBeOpen();
await pageObjects.svlCommonNavigation.devConsole.clickEmbeddedConsoleControlBar();
await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleToBeClosed();
await pageObjects.embeddedConsole.expectEmbeddedConsoleControlBarExists();
await pageObjects.embeddedConsole.expectEmbeddedConsoleToBeClosed();
await pageObjects.embeddedConsole.clickEmbeddedConsoleControlBar();
await pageObjects.embeddedConsole.expectEmbeddedConsoleToBeOpen();
await pageObjects.embeddedConsole.clickEmbeddedConsoleControlBar();
await pageObjects.embeddedConsole.expectEmbeddedConsoleToBeClosed();
}

View file

@ -12,7 +12,7 @@ import { testHasEmbeddedConsole } from './embedded_console';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const pageObjects = getPageObjects([
'svlCommonPage',
'svlCommonNavigation',
'embeddedConsole',
'common',
'header',
'indexManagement',

View file

@ -10,11 +10,7 @@ import { FtrProviderContext } from '../../ftr_provider_context';
import { testHasEmbeddedConsole } from './embedded_console';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const pageObjects = getPageObjects([
'svlSearchLandingPage',
'svlCommonPage',
'svlCommonNavigation',
]);
const pageObjects = getPageObjects(['svlSearchLandingPage', 'svlCommonPage', 'embeddedConsole']);
const svlSearchNavigation = getService('svlSearchNavigation');
describe('landing page', function () {

View file

@ -14,6 +14,7 @@ export default function ({ getPageObjects }: FtrProviderContext) {
'common',
'svlIngestPipelines',
'svlManagementPage',
'embeddedConsole',
]);
describe('ingest pipelines', function () {
before(async () => {

View file

@ -11,7 +11,12 @@ import { RoleCredentials } from '../../../shared/services';
import { testHasEmbeddedConsole } from './embedded_console';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const pageObjects = getPageObjects(['svlCommonPage', 'svlCommonNavigation', 'svlSearchHomePage']);
const pageObjects = getPageObjects([
'svlCommonPage',
'svlCommonNavigation',
'svlSearchHomePage',
'embeddedConsole',
]);
const svlUserManager = getService('svlUserManager');
const uiSettings = getService('uiSettings');
let roleAuthc: RoleCredentials;
@ -56,11 +61,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
it('has console quickstart link on page', async () => {
await pageObjects.svlSearchHomePage.expectConsoleLinkExists();
await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleToBeClosed();
await pageObjects.embeddedConsole.expectEmbeddedConsoleToBeClosed();
await pageObjects.svlSearchHomePage.clickConsoleLink();
await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleToBeOpen();
await pageObjects.svlCommonNavigation.devConsole.clickEmbeddedConsoleControlBar();
await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleToBeClosed();
await pageObjects.embeddedConsole.expectEmbeddedConsoleToBeOpen();
await pageObjects.embeddedConsole.clickEmbeddedConsoleControlBar();
await pageObjects.embeddedConsole.expectEmbeddedConsoleToBeClosed();
});
it('has endpoints link and flyout', async () => {

View file

@ -15,7 +15,12 @@ import { createLlmProxy, LlmProxy } from './utils/create_llm_proxy';
const esArchiveIndex = 'test/api_integration/fixtures/es_archiver/index_patterns/basic_index';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const pageObjects = getPageObjects(['svlCommonPage', 'svlCommonNavigation', 'searchPlayground']);
const pageObjects = getPageObjects([
'svlCommonPage',
'svlCommonNavigation',
'searchPlayground',
'embeddedConsole',
]);
const svlCommonApi = getService('svlCommonApi');
const svlUserManager = getService('svlUserManager');
const supertestWithoutAuth = getService('supertestWithoutAuth');

View file

@ -39,10 +39,6 @@
"@kbn/apm-plugin",
"@kbn/server-route-repository",
"@kbn/core-chrome-browser",
"@kbn/default-nav-ml",
"@kbn/default-nav-analytics",
"@kbn/default-nav-management",
"@kbn/default-nav-devtools",
"@kbn/security-plugin",
"@kbn/security-solution-plugin",
"@kbn/security-solution-plugin/public/management/cypress",