diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index 9e48ac1ea7e36..215d552d07f7d 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -411,9 +411,18 @@ export class CommonPageObject extends FtrService { * Clicks cancel button on modal * @param overlayWillStay pass in true if your test will show multiple modals in succession */ - async clickCancelOnModal(overlayWillStay = true) { + async clickCancelOnModal(overlayWillStay = true, ignorePageLeaveWarning = false) { this.log.debug('Clicking modal cancel'); - await this.testSubjects.click('confirmModalCancelButton'); + await this.testSubjects.exists('confirmModalTitleText'); + + await this.retry.try(async () => { + const warning = await this.testSubjects.exists('confirmModalTitleText'); + if (warning) { + await this.testSubjects.click( + ignorePageLeaveWarning ? 'confirmModalConfirmButton' : 'confirmModalCancelButton' + ); + } + }); if (!overlayWillStay) { await this.ensureModalOverlayHidden(); } diff --git a/test/functional/services/apps_menu.ts b/test/functional/services/apps_menu.ts index 9fb8e36476f3e..0f63166477883 100644 --- a/test/functional/services/apps_menu.ts +++ b/test/functional/services/apps_menu.ts @@ -59,6 +59,7 @@ export class AppsMenuService extends FtrService { if (!(await this.testSubjects.exists('collapsibleNav'))) { await this.testSubjects.click('toggleNavButton'); } + await this.testSubjects.exists('collapsibleNav'); } /** diff --git a/test/plugin_functional/plugins/core_plugin_appleave/public/plugin.tsx b/test/plugin_functional/plugins/core_plugin_appleave/public/plugin.tsx index aab64fc657696..a5107a375d8a0 100644 --- a/test/plugin_functional/plugins/core_plugin_appleave/public/plugin.tsx +++ b/test/plugin_functional/plugins/core_plugin_appleave/public/plugin.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { Plugin, CoreSetup } from '@kbn/core/public'; +import { Plugin, CoreSetup, DEFAULT_APP_CATEGORIES } from '@kbn/core/public'; export class CoreAppLeavePlugin implements Plugin @@ -15,6 +15,8 @@ export class CoreAppLeavePlugin core.application.register({ id: 'appleave1', title: 'AppLeave 1', + appRoute: '/app/appleave1', + category: DEFAULT_APP_CATEGORIES.kibana, async mount(params) { const { renderApp } = await import('./application'); params.onAppLeave((actions) => actions.confirm('confirm-message', 'confirm-title')); @@ -24,9 +26,11 @@ export class CoreAppLeavePlugin core.application.register({ id: 'appleave2', title: 'AppLeave 2', + appRoute: '/app/appleave2', + category: DEFAULT_APP_CATEGORIES.kibana, async mount(params) { const { renderApp } = await import('./application'); - params.onAppLeave((actions) => actions.default()); + params.onAppLeave((actions) => actions.confirm('confirm-message', 'confirm-title')); return renderApp('AppLeave 2', params); }, }); diff --git a/test/plugin_functional/plugins/core_plugin_deep_links/public/application.tsx b/test/plugin_functional/plugins/core_plugin_deep_links/public/application.tsx index 345ffbd5b47d2..d816254dbf39b 100644 --- a/test/plugin_functional/plugins/core_plugin_deep_links/public/application.tsx +++ b/test/plugin_functional/plugins/core_plugin_deep_links/public/application.tsx @@ -50,7 +50,7 @@ const PageA = () => ( -

DL Page A

+

DL page A

@@ -70,7 +70,7 @@ const PageB = () => ( -

DL Page B

+

DL page B

diff --git a/test/plugin_functional/plugins/core_plugin_deep_links/public/plugin.tsx b/test/plugin_functional/plugins/core_plugin_deep_links/public/plugin.tsx index f7e3102f0b230..2c1af49e7eb6b 100644 --- a/test/plugin_functional/plugins/core_plugin_deep_links/public/plugin.tsx +++ b/test/plugin_functional/plugins/core_plugin_deep_links/public/plugin.tsx @@ -29,7 +29,7 @@ export class CorePluginDeepLinksPlugin }, { id: 'pageA', - title: 'DL Page A', + title: 'DL page A', path: '/page-a', navLinkStatus: AppNavLinkStatus.visible, }, @@ -39,7 +39,7 @@ export class CorePluginDeepLinksPlugin deepLinks: [ { id: 'pageB', - title: 'DL Page B', + title: 'DL page B', path: '/page-b', navLinkStatus: AppNavLinkStatus.visible, }, @@ -47,7 +47,7 @@ export class CorePluginDeepLinksPlugin }, { id: 'pageC', - title: 'DL Page C', + title: 'DL page C', path: '/page-c', // navLinkStatus hidden by default }, diff --git a/test/plugin_functional/test_suites/core_plugins/application_deep_links.ts b/test/plugin_functional/test_suites/core_plugins/application_deep_links.ts index 8d558dafa68f5..98a824c007a62 100644 --- a/test/plugin_functional/test_suites/core_plugins/application_deep_links.ts +++ b/test/plugin_functional/test_suites/core_plugins/application_deep_links.ts @@ -18,6 +18,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide const testSubjects = getService('testSubjects'); const retry = getService('retry'); const esArchiver = getService('esArchiver'); + const log = getService('log'); const loadingScreenNotShown = async () => expect(await testSubjects.exists('kbnLoadingMessage')).to.be(false); @@ -35,12 +36,20 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide const waitForUrlToBe = (pathname?: string, search?: string) => { const expectedUrl = getKibanaUrl(pathname, search); return retry.waitFor(`Url to be ${expectedUrl}`, async () => { - return (await browser.getCurrentUrl()) === expectedUrl; + const currentUrl = await browser.getCurrentUrl(); + log?.debug(`waiting for currentUrl ${currentUrl} to be expectedUrl ${expectedUrl}`); + return currentUrl === expectedUrl; }); }; - // Failing: See https://github.com/elastic/kibana/issues/166893 - describe.skip('application deep links navigation', function describeDeepLinksTests() { + const navigateToAppLinks = async (subject: string) => { + if (!(await testSubjects.exists(subject))) { + log.debug(`side nav in app not in DOM`); + } + await testSubjects.click(subject); + }; + + describe('application deep links navigation', function describeDeepLinksTests() { before(async () => { await esArchiver.emptyKibanaIndex(); await PageObjects.common.navigateToApp('dl'); @@ -51,28 +60,29 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide }); it('should navigate to page A when navlink is clicked', async () => { - await appsMenu.clickLink('DL Page A'); + await navigateToAppLinks('dlNavPageA'); await waitForUrlToBe('/app/dl/page-a'); await loadingScreenNotShown(); - await testSubjects.existOrFail('dlAppPageA'); + await testSubjects.existOrFail('dlNavPageA'); }); it('should be able to use the back button to navigate back to previous deep link', async () => { await browser.goBack(); await waitForUrlToBe('/app/dl/home'); await loadingScreenNotShown(); - await testSubjects.existOrFail('dlAppHome'); + await testSubjects.existOrFail('dlNavHome'); }); it('should navigate to nested page B when navlink is clicked', async () => { - await appsMenu.clickLink('DL Page B'); + await navigateToAppLinks('dlNavDeepPageB'); await waitForUrlToBe('/app/dl/page-b'); await loadingScreenNotShown(); - await testSubjects.existOrFail('dlAppPageB'); + await testSubjects.existOrFail('dlNavDeepPageB'); }); it('should navigate to Home when navlink is clicked inside the defined category group', async () => { await appsMenu.clickLink('DL Home', { category: 'securitySolution' }); + await navigateToAppLinks('dlAppHome'); await waitForUrlToBe('/app/dl/home'); await loadingScreenNotShown(); await testSubjects.existOrFail('dlAppHome'); @@ -82,14 +92,14 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide await testSubjects.click('dlNavDeepPageB'); await waitForUrlToBe('/app/dl/page-b'); await loadingScreenNotShown(); - await testSubjects.existOrFail('dlAppPageB'); + await testSubjects.existOrFail('dlNavDeepPageB'); }); it('should navigate to nested page A using navigateToApp deepLinkId', async () => { await testSubjects.click('dlNavDeepPageAById'); await waitForUrlToBe('/app/dl/page-a'); await loadingScreenNotShown(); - await testSubjects.existOrFail('dlAppPageA'); + await testSubjects.existOrFail('dlNavPageA'); }); it('should not display hidden deep links', async () => { diff --git a/test/plugin_functional/test_suites/core_plugins/application_leave_confirm.ts b/test/plugin_functional/test_suites/core_plugins/application_leave_confirm.ts index 987796be3a6a7..4d0f837108b73 100644 --- a/test/plugin_functional/test_suites/core_plugins/application_leave_confirm.ts +++ b/test/plugin_functional/test_suites/core_plugins/application_leave_confirm.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import url from 'url'; import expect from '@kbn/expect'; +import url from 'url'; import { PluginFunctionalProviderContext } from '../../services'; const getKibanaUrl = (pathname?: string, search?: string) => @@ -20,30 +20,96 @@ const getKibanaUrl = (pathname?: string, search?: string) => }); export default function ({ getService, getPageObjects }: PluginFunctionalProviderContext) { - const PageObjects = getPageObjects(['common']); + const PageObjects = getPageObjects(['common', 'header']); const browser = getService('browser'); const appsMenu = getService('appsMenu'); + const log = getService('log'); + const retry = getService('retry'); const testSubjects = getService('testSubjects'); + const config = getService('config'); + + const waitForUrlToBe = async (pathname?: string, search?: string) => { + const expectedUrl = getKibanaUrl(pathname, search); + return await retry.waitFor(`Url to be ${expectedUrl}`, async () => { + const currentUrl = await browser.getCurrentUrl(); + log.debug(`waiting for currentUrl ${currentUrl} to be expectedUrl ${expectedUrl}`); + return currentUrl === expectedUrl; + }); + }; + + const ensureModalOpen = async ( + defaultTryTimeout: number, + attempts: number, + timeMultiplier: number, + action: 'cancel' | 'confirm', + linkText: string = 'home' + ): Promise => { + let isConfirmCancelModalOpenState = false; - // Failing: See https://github.com/elastic/kibana/issues/75963 - // Failing: See https://github.com/elastic/kibana/issues/166838 - describe.skip('application using leave confirmation', () => { + await retry.tryForTime(defaultTryTimeout * timeMultiplier, async () => { + await appsMenu.clickLink(linkText); + isConfirmCancelModalOpenState = await testSubjects.exists('confirmModalTitleText', { + allowHidden: true, + timeout: defaultTryTimeout * timeMultiplier, + }); + }); + if (isConfirmCancelModalOpenState) { + log.debug(`defaultTryTimeout * ${timeMultiplier} is long enough`); + return action === 'cancel' + ? await PageObjects.common.clickCancelOnModal(true, false) + : await PageObjects.common.clickConfirmOnModal(); + } else { + log.debug(`defaultTryTimeout * ${timeMultiplier} is not long enough`); + return await ensureModalOpen( + defaultTryTimeout, + (attempts = attempts > 0 ? attempts - 1 : 0), + (timeMultiplier = timeMultiplier < 10 ? timeMultiplier + 1 : 10), + action, + linkText + ); + } + }; + + describe('application using leave confirmation', () => { + const defaultTryTimeout = config.get('timeouts.try'); + const attempts = 5; describe('when navigating to another app', () => { + const timeMultiplier = 10; + beforeEach(async () => { + await PageObjects.common.navigateToApp('home'); + }); it('prevents navigation if user click cancel on the confirmation dialog', async () => { await PageObjects.common.navigateToApp('appleave1'); - await appsMenu.clickLink('AppLeave 2'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await waitForUrlToBe('/app/appleave1'); - await testSubjects.existOrFail('appLeaveConfirmModal'); - await PageObjects.common.clickCancelOnModal(false); - expect(await browser.getCurrentUrl()).to.eql(getKibanaUrl('/app/appleave1')); + await ensureModalOpen(defaultTryTimeout, attempts, timeMultiplier, 'cancel', 'AppLeave 2'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await retry.waitFor('navigate to appleave1', async () => { + const currentUrl = await browser.getCurrentUrl(); + log.debug(`currentUrl ${currentUrl}`); + return currentUrl.includes('appleave1'); + }); + const currentUrl = await browser.getCurrentUrl(); + expect(currentUrl).to.contain('appleave1'); + await PageObjects.common.navigateToApp('home'); }); + it('allows navigation if user click confirm on the confirmation dialog', async () => { await PageObjects.common.navigateToApp('appleave1'); - await appsMenu.clickLink('AppLeave 2'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await waitForUrlToBe('/app/appleave1'); - await testSubjects.existOrFail('appLeaveConfirmModal'); - await PageObjects.common.clickConfirmOnModal(); - expect(await browser.getCurrentUrl()).to.eql(getKibanaUrl('/app/appleave2')); + await ensureModalOpen(defaultTryTimeout, attempts, timeMultiplier, 'confirm', 'AppLeave 2'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await retry.waitFor('navigate to appleave1', async () => { + const currentUrl = await browser.getCurrentUrl(); + log.debug(`currentUrl ${currentUrl}`); + return currentUrl.includes('appleave2'); + }); + const currentUrl = await browser.getCurrentUrl(); + expect(currentUrl).to.contain('appleave2'); + await PageObjects.common.navigateToApp('home'); }); }); });