diff --git a/.buildkite/pipelines/es_snapshots/verify.yml b/.buildkite/pipelines/es_snapshots/verify.yml index 9af2e938db49d..61212e1fcf0a8 100755 --- a/.buildkite/pipelines/es_snapshots/verify.yml +++ b/.buildkite/pipelines/es_snapshots/verify.yml @@ -91,13 +91,5 @@ steps: - wait: ~ continue_on_failure: true - - plugins: - - junit-annotate#v1.9.0: - artifacts: target/junit/**/*.xml - job-uuid-file-pattern: '-bk__(.*).xml' - - - wait: ~ - continue_on_failure: true - - command: .buildkite/scripts/lifecycle/post_build.sh label: Post-Build diff --git a/.buildkite/pipelines/hourly.yml b/.buildkite/pipelines/hourly.yml index 89541023be8e2..279c8cf96bfe3 100644 --- a/.buildkite/pipelines/hourly.yml +++ b/.buildkite/pipelines/hourly.yml @@ -159,13 +159,5 @@ steps: - wait: ~ continue_on_failure: true - - plugins: - - junit-annotate#v1.9.0: - artifacts: target/junit/**/*.xml - job-uuid-file-pattern: '-bk__(.*).xml' - - - wait: ~ - continue_on_failure: true - - command: .buildkite/scripts/lifecycle/post_build.sh label: Post-Build diff --git a/.buildkite/scripts/lifecycle/annotate_test_failures.js b/.buildkite/scripts/lifecycle/annotate_test_failures.js new file mode 100644 index 0000000000000..caf1e08c2bb4d --- /dev/null +++ b/.buildkite/scripts/lifecycle/annotate_test_failures.js @@ -0,0 +1,14 @@ +const { TestFailures } = require('kibana-buildkite-library'); + +(async () => { + try { + await TestFailures.annotateTestFailures(); + } catch (ex) { + console.error('Annotate test failures error', ex.message); + if (ex.response) { + console.error('HTTP Error Response Status', ex.response.status); + console.error('HTTP Error Response Body', ex.response.data); + } + process.exit(1); + } +})(); diff --git a/.buildkite/scripts/lifecycle/post_command.sh b/.buildkite/scripts/lifecycle/post_command.sh index 23f44a586e978..0f98035f9f828 100755 --- a/.buildkite/scripts/lifecycle/post_command.sh +++ b/.buildkite/scripts/lifecycle/post_command.sh @@ -23,4 +23,9 @@ if [[ "$IS_TEST_EXECUTION_STEP" == "true" ]]; then buildkite-agent artifact upload '.es/**/*.hprof' node scripts/report_failed_tests --build-url="${BUILDKITE_BUILD_URL}#${BUILDKITE_JOB_ID}" 'target/junit/**/*.xml' + + if [[ -d 'target/test_failures' ]]; then + buildkite-agent artifact upload 'target/test_failures/**/*' + node .buildkite/scripts/lifecycle/annotate_test_failures.js + fi fi diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index bea30908e0ced..d1324ba0622d5 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -65,6 +65,12 @@ Review important information about the {kib} 7.14.x releases. Review the following information about the 7.14.2 release. +[float] +[[known-issue-v7.14.2]] +=== Known issue + +{kib} is unable to restore *Discover* search sessions with a relative time range. When you restore the *Discover* search session, then run a new search, {kib} displays a `Your search session is still running` message. For more information, refer to {kibana-issue}101430[#101430]. + [float] [[breaking-changes-v7.14.2]] === Breaking changes @@ -817,8 +823,6 @@ Uptime:: [[release-notes-7.13.2]] == {kib} 7.13.2 -coming::[7.13.2] - The 7.13.2 release includes the following bug fixes. [float] diff --git a/docs/setup/secure-settings.asciidoc b/docs/setup/secure-settings.asciidoc index 840a18ac03bab..55b8c39e0ede8 100644 --- a/docs/setup/secure-settings.asciidoc +++ b/docs/setup/secure-settings.asciidoc @@ -18,8 +18,8 @@ To create the `kibana.keystore`, use the `create` command: bin/kibana-keystore create ---------------------------------------------------------------- -The file `kibana.keystore` will be created in the directory defined by the -<> configuration setting. +The file `kibana.keystore` will be created in the `config` directory defined by the +environment variable `KBN_PATH_CONF`. [float] [[list-settings]] diff --git a/package.json b/package.json index 7b00ede087e85..0d19121ecc35f 100644 --- a/package.json +++ b/package.json @@ -677,7 +677,7 @@ "callsites": "^3.1.0", "chai": "3.5.0", "chance": "1.0.18", - "chromedriver": "^92.0.1", + "chromedriver": "^93.0.1", "clean-webpack-plugin": "^3.0.0", "cmd-shim": "^2.1.0", "compression-webpack-plugin": "^4.0.0", diff --git a/packages/kbn-test/BUILD.bazel b/packages/kbn-test/BUILD.bazel index c4f3d0d1604e5..a4100abec1ba3 100644 --- a/packages/kbn-test/BUILD.bazel +++ b/packages/kbn-test/BUILD.bazel @@ -50,6 +50,7 @@ RUNTIME_DEPS = [ "@npm//exit-hook", "@npm//form-data", "@npm//globby", + "@npm//he", "@npm//history", "@npm//jest", "@npm//jest-cli", @@ -86,6 +87,7 @@ TYPES_DEPS = [ "@npm//xmlbuilder", "@npm//@types/chance", "@npm//@types/enzyme", + "@npm//@types/he", "@npm//@types/history", "@npm//@types/jest", "@npm//@types/joi", diff --git a/packages/kbn-test/src/failed_tests_reporter/report_failures_to_file.ts b/packages/kbn-test/src/failed_tests_reporter/report_failures_to_file.ts new file mode 100644 index 0000000000000..aca2e6838faec --- /dev/null +++ b/packages/kbn-test/src/failed_tests_reporter/report_failures_to_file.ts @@ -0,0 +1,138 @@ +/* + * 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 { createHash } from 'crypto'; +import { mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'fs'; +import { join, basename, resolve } from 'path'; + +import { ToolingLog } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; +import { escape } from 'he'; + +import { TestFailure } from './get_failures'; + +const findScreenshots = (dirPath: string, allScreenshots: string[] = []) => { + const files = readdirSync(dirPath); + + for (const file of files) { + if (statSync(join(dirPath, file)).isDirectory()) { + if (file.match(/node_modules/)) { + continue; + } + + allScreenshots = findScreenshots(join(dirPath, file), allScreenshots); + } else { + const fullPath = join(dirPath, file); + if (fullPath.match(/screenshots\/failure\/.+\.png$/)) { + allScreenshots.push(fullPath); + } + } + } + + return allScreenshots; +}; + +export function reportFailuresToFile(log: ToolingLog, failures: TestFailure[]) { + if (!failures?.length) { + return; + } + + let screenshots: string[]; + try { + screenshots = [ + ...findScreenshots(join(REPO_ROOT, 'test', 'functional')), + ...findScreenshots(join(REPO_ROOT, 'x-pack', 'test', 'functional')), + ]; + } catch (e) { + log.error(e as Error); + screenshots = []; + } + + const screenshotsByName: Record = {}; + for (const screenshot of screenshots) { + const [name] = basename(screenshot).split('.'); + screenshotsByName[name] = screenshot; + } + + // Jest could, in theory, fail 1000s of tests and write 1000s of failures + // So let's just write files for the first 20 + for (const failure of failures.slice(0, 20)) { + const hash = createHash('md5').update(failure.name).digest('hex'); + const filenameBase = `${ + process.env.BUILDKITE_JOB_ID ? process.env.BUILDKITE_JOB_ID + '_' : '' + }${hash}`; + const dir = join('target', 'test_failures'); + + const failureLog = [ + ['Test:', '-----', failure.classname, failure.name, ''], + ['Failure:', '--------', failure.failure], + failure['system-out'] ? ['', 'Standard Out:', '-------------', failure['system-out']] : [], + ] + .flat() + .join('\n'); + + const failureJSON = JSON.stringify( + { + ...failure, + hash, + buildId: process.env.BUJILDKITE_BUILD_ID || '', + jobId: process.env.BUILDKITE_JOB_ID || '', + url: process.env.BUILDKITE_BUILD_URL || '', + jobName: process.env.BUILDKITE_LABEL + ? `${process.env.BUILDKITE_LABEL}${ + process.env.BUILDKITE_PARALLEL_JOB ? ` #${process.env.BUILDKITE_PARALLEL_JOB}` : '' + }` + : '', + }, + null, + 2 + ); + + let screenshot = ''; + const screenshotName = `${failure.name.replace(/([^ a-zA-Z0-9-]+)/g, '_')}`; + if (screenshotsByName[screenshotName]) { + try { + screenshot = readFileSync(screenshotsByName[screenshotName]).toString('base64'); + } catch (e) { + log.error(e as Error); + } + } + + const screenshotHtml = screenshot + ? `` + : ''; + + const failureHTML = readFileSync( + resolve( + REPO_ROOT, + 'packages/kbn-test/src/failed_tests_reporter/report_failures_to_file_html_template.html' + ) + ) + .toString() + .replace('$TITLE', escape(failure.name)) + .replace( + '$MAIN', + ` + ${failure.classname + .split('.') + .map((part) => `
${escape(part.replace('ยท', '.'))}
`) + .join('')} +
+

${escape(failure.name)}

+
${escape(failure.failure)}
+ ${screenshotHtml} +
${escape(failure['system-out'] || '')}
+ ` + ); + + mkdirSync(dir, { recursive: true }); + writeFileSync(join(dir, `${filenameBase}.log`), failureLog, 'utf8'); + writeFileSync(join(dir, `${filenameBase}.html`), failureHTML, 'utf8'); + writeFileSync(join(dir, `${filenameBase}.json`), failureJSON, 'utf8'); + } +} diff --git a/packages/kbn-test/src/failed_tests_reporter/report_failures_to_file_html_template.html b/packages/kbn-test/src/failed_tests_reporter/report_failures_to_file_html_template.html new file mode 100644 index 0000000000000..485a2c2d4eb3f --- /dev/null +++ b/packages/kbn-test/src/failed_tests_reporter/report_failures_to_file_html_template.html @@ -0,0 +1,41 @@ + + + + + + + + $TITLE + + +
+
$MAIN
+
+ + + diff --git a/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts b/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts index 0129614fe658d..6c88b7408b628 100644 --- a/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts +++ b/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts @@ -21,6 +21,7 @@ import { readTestReport } from './test_report'; import { addMessagesToReport } from './add_messages_to_report'; import { getReportMessageIter } from './report_metadata'; import { reportFailuresToEs } from './report_failures_to_es'; +import { reportFailuresToFile } from './report_failures_to_file'; const DEFAULT_PATTERNS = [Path.resolve(REPO_ROOT, 'target/junit/**/*.xml')]; @@ -98,6 +99,8 @@ export function runFailedTestsReporterCli() { const messages = Array.from(getReportMessageIter(report)); const failures = await getFailures(report); + reportFailuresToFile(log, failures); + if (indexInEs) { await reportFailuresToEs(log, failures); } diff --git a/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts b/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts index 576f7c06c8dc8..cc96be62694ca 100644 --- a/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts +++ b/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts @@ -36,6 +36,7 @@ import { syncDashboardUrlState, diffDashboardState, areTimeRangesEqual, + areRefreshIntervalsEqual, } from '../lib'; export interface UseDashboardStateProps { @@ -222,27 +223,29 @@ export const useDashboardAppState = ({ .pipe(debounceTime(DashboardConstants.CHANGE_CHECK_DEBOUNCE)) .subscribe((states) => { const [lastSaved, current] = states; - const unsavedChanges = - current.viewMode === ViewMode.EDIT ? diffDashboardState(lastSaved, current) : {}; + const unsavedChanges = diffDashboardState(lastSaved, current); - let savedTimeChanged = false; + const savedTimeChanged = + lastSaved.timeRestore && + (!areTimeRangesEqual( + { + from: savedDashboard?.timeFrom, + to: savedDashboard?.timeTo, + }, + timefilter.getTime() + ) || + !areRefreshIntervalsEqual( + savedDashboard?.refreshInterval, + timefilter.getRefreshInterval() + )); /** - * changes to the time filter should only be considered 'unsaved changes' when + * changes to the dashboard should only be considered 'unsaved changes' when * editing the dashboard */ - if (current.viewMode === ViewMode.EDIT) { - savedTimeChanged = - lastSaved.timeRestore && - !areTimeRangesEqual( - { - from: savedDashboard?.timeFrom, - to: savedDashboard?.timeTo, - }, - timefilter.getTime() - ); - } - const hasUnsavedChanges = Object.keys(unsavedChanges).length > 0 || savedTimeChanged; + const hasUnsavedChanges = + current.viewMode === ViewMode.EDIT && + (Object.keys(unsavedChanges).length > 0 || savedTimeChanged); setDashboardAppState((s) => ({ ...s, hasUnsavedChanges })); unsavedChanges.viewMode = current.viewMode; // always push view mode into session store. diff --git a/src/plugins/dashboard/public/application/lib/dashboard_session_storage.ts b/src/plugins/dashboard/public/application/lib/dashboard_session_storage.ts index 7d0e60c0609a8..a696c8bc15b83 100644 --- a/src/plugins/dashboard/public/application/lib/dashboard_session_storage.ts +++ b/src/plugins/dashboard/public/application/lib/dashboard_session_storage.ts @@ -11,6 +11,7 @@ import { Storage } from '../../services/kibana_utils'; import { NotificationsStart } from '../../services/core'; import { panelStorageErrorStrings } from '../../dashboard_strings'; import { DashboardState } from '../../types'; +import { ViewMode } from '../../services/embeddable'; export const DASHBOARD_PANELS_UNSAVED_ID = 'unsavedDashboard'; const DASHBOARD_PANELS_SESSION_KEY = 'dashboardStateManagerPanels'; @@ -69,6 +70,7 @@ export class DashboardSessionStorage { const dashboardsWithUnsavedChanges: string[] = []; Object.keys(dashboardStatesInSpace).map((dashboardId) => { if ( + dashboardStatesInSpace[dashboardId].viewMode === ViewMode.EDIT && Object.keys(dashboardStatesInSpace[dashboardId]).some( (stateKey) => stateKey !== 'viewMode' ) diff --git a/src/plugins/dashboard/public/application/lib/filter_utils.ts b/src/plugins/dashboard/public/application/lib/filter_utils.ts index 51acc4676c543..a31b83ec2df8f 100644 --- a/src/plugins/dashboard/public/application/lib/filter_utils.ts +++ b/src/plugins/dashboard/public/application/lib/filter_utils.ts @@ -10,9 +10,10 @@ import _ from 'lodash'; import moment, { Moment } from 'moment'; import { Optional } from '@kbn/utility-types'; -import { Filter, TimeRange } from '../../services/data'; +import { Filter, TimeRange, RefreshInterval } from '../../services/data'; type TimeRangeCompare = Optional; +type RefreshIntervalCompare = Optional; /** * Converts the time to a utc formatted string. If the time is not valid (e.g. it might be in a relative format like @@ -31,9 +32,13 @@ export const convertTimeToUTCString = (time?: string | Moment): undefined | stri } }; -export const areTimeRangesEqual = (rangeA: TimeRangeCompare, rangeB: TimeRangeCompare): boolean => { - return areTimesEqual(rangeA.from, rangeB.from) && areTimesEqual(rangeA.to, rangeB.to); -}; +export const areTimeRangesEqual = (rangeA: TimeRangeCompare, rangeB: TimeRangeCompare): boolean => + areTimesEqual(rangeA.from, rangeB.from) && areTimesEqual(rangeA.to, rangeB.to); + +export const areRefreshIntervalsEqual = ( + refreshA?: RefreshIntervalCompare, + refreshB?: RefreshIntervalCompare +): boolean => refreshA?.pause === refreshB?.pause && refreshA?.value === refreshB?.value; /** * Compares the two times, making sure they are in both compared in string format. Absolute times diff --git a/test/functional/apps/dashboard/dashboard_unsaved_state.ts b/test/functional/apps/dashboard/dashboard_unsaved_state.ts index 6b71dd34b76f8..8043c8bf8cc37 100644 --- a/test/functional/apps/dashboard/dashboard_unsaved_state.ts +++ b/test/functional/apps/dashboard/dashboard_unsaved_state.ts @@ -12,6 +12,9 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['dashboard', 'header', 'visualize', 'settings', 'common']); + const browser = getService('browser'); + const queryBar = getService('queryBar'); + const filterBar = getService('filterBar'); const esArchiver = getService('esArchiver'); const testSubjects = getService('testSubjects'); const kibanaServer = getService('kibanaServer'); @@ -19,9 +22,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { let originalPanelCount = 0; let unsavedPanelCount = 0; + const testQuery = 'Test Query'; - // FLAKY: https://github.com/elastic/kibana/issues/91191 - describe.skip('dashboard unsaved panels', () => { + describe('dashboard unsaved state', () => { before(async () => { await esArchiver.load('test/functional/fixtures/es_archiver/dashboard/current/kibana'); await kibanaServer.uiSettings.replace({ @@ -31,79 +34,123 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('few panels'); await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.waitForRenderComplete(); originalPanelCount = await PageObjects.dashboard.getPanelCount(); }); - it('does not show unsaved changes badge when there are no unsaved changes', async () => { - await testSubjects.missingOrFail('dashboardUnsavedChangesBadge'); - }); + describe('view mode state', () => { + before(async () => { + await queryBar.setQuery(testQuery); + await filterBar.addFilter('bytes', 'exists'); + await queryBar.submitQuery(); + }); - it('shows the unsaved changes badge after adding panels', async () => { - await PageObjects.dashboard.switchToEditMode(); - // add an area chart by value - await dashboardAddPanel.clickEditorMenuButton(); - await dashboardAddPanel.clickAggBasedVisualizations(); - await PageObjects.visualize.clickAreaChart(); - await PageObjects.visualize.clickNewSearch(); - await PageObjects.visualize.saveVisualizationAndReturn(); + const validateQueryAndFilter = async () => { + const query = await queryBar.getQueryString(); + expect(query).to.eql(testQuery); + const filterCount = await filterBar.getFilterCount(); + expect(filterCount).to.eql(1); + }; + + it('persists after navigating to the listing page and back', async () => { + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.loadSavedDashboard('few panels'); + await PageObjects.dashboard.waitForRenderComplete(); + await validateQueryAndFilter(); + }); - // add a metric by reference - await dashboardAddPanel.addVisualization('Rendering-Test: metric'); + it('persists after navigating to Visualize and back', async () => { + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.visualize.gotoVisualizationLandingPage(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.common.navigateToApp('dashboards'); + await PageObjects.dashboard.loadSavedDashboard('few panels'); + await PageObjects.dashboard.waitForRenderComplete(); + await validateQueryAndFilter(); + }); - await PageObjects.header.waitUntilLoadingHasFinished(); - await testSubjects.existOrFail('dashboardUnsavedChangesBadge'); - }); + it('persists after a hard refresh', async () => { + await browser.refresh(); + const alert = await browser.getAlert(); + await alert?.accept(); + await PageObjects.dashboard.waitForRenderComplete(); + await validateQueryAndFilter(); + }); - it('has correct number of panels', async () => { - unsavedPanelCount = await PageObjects.dashboard.getPanelCount(); - expect(unsavedPanelCount).to.eql(originalPanelCount + 2); + after(async () => { + // discard changes made in view mode + await PageObjects.dashboard.switchToEditMode(); + await PageObjects.dashboard.clickCancelOutOfEditMode(); + }); }); - it('retains unsaved panel count after navigating to listing page and back', async () => { - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.dashboard.gotoDashboardLandingPage(); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.dashboard.loadSavedDashboard('few panels'); - await PageObjects.dashboard.switchToEditMode(); - const currentPanelCount = await PageObjects.dashboard.getPanelCount(); - expect(currentPanelCount).to.eql(unsavedPanelCount); - }); + describe('edit mode state', () => { + const addPanels = async () => { + // add an area chart by value + await dashboardAddPanel.clickEditorMenuButton(); + await dashboardAddPanel.clickAggBasedVisualizations(); + await PageObjects.visualize.clickAreaChart(); + await PageObjects.visualize.clickNewSearch(); + await PageObjects.visualize.saveVisualizationAndReturn(); + + // add a metric by reference + await dashboardAddPanel.addVisualization('Rendering-Test: metric'); + }; + + it('does not show unsaved changes badge when there are no unsaved changes', async () => { + await testSubjects.missingOrFail('dashboardUnsavedChangesBadge'); + }); - it('retains unsaved panel count after navigating to another app and back', async () => { - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.gotoVisualizationLandingPage(); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.common.navigateToApp('dashboards'); - await PageObjects.dashboard.loadSavedDashboard('few panels'); - await PageObjects.dashboard.switchToEditMode(); - const currentPanelCount = await PageObjects.dashboard.getPanelCount(); - expect(currentPanelCount).to.eql(unsavedPanelCount); - }); + it('shows the unsaved changes badge after adding panels', async () => { + await PageObjects.dashboard.switchToEditMode(); + await addPanels(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await testSubjects.existOrFail('dashboardUnsavedChangesBadge'); + }); - it('resets to original panel count upon entering view mode', async () => { - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.dashboard.clickCancelOutOfEditMode(); - await PageObjects.header.waitUntilLoadingHasFinished(); - const currentPanelCount = await PageObjects.dashboard.getPanelCount(); - expect(currentPanelCount).to.eql(originalPanelCount); - }); + it('has correct number of panels', async () => { + unsavedPanelCount = await PageObjects.dashboard.getPanelCount(); + expect(unsavedPanelCount).to.eql(originalPanelCount + 2); + }); - it('shows unsaved changes badge in view mode if changes have not been discarded', async () => { - await testSubjects.existOrFail('dashboardUnsavedChangesBadge'); - }); + it('retains unsaved panel count after navigating to listing page and back', async () => { + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.loadSavedDashboard('few panels'); + const currentPanelCount = await PageObjects.dashboard.getPanelCount(); + expect(currentPanelCount).to.eql(unsavedPanelCount); + }); - it('retains unsaved panel count after returning to edit mode', async () => { - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.dashboard.switchToEditMode(); - await PageObjects.header.waitUntilLoadingHasFinished(); - const currentPanelCount = await PageObjects.dashboard.getPanelCount(); - expect(currentPanelCount).to.eql(unsavedPanelCount); - }); + it('retains unsaved panel count after navigating to another app and back', async () => { + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.visualize.gotoVisualizationLandingPage(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.common.navigateToApp('dashboards'); + await PageObjects.dashboard.loadSavedDashboard('few panels'); + const currentPanelCount = await PageObjects.dashboard.getPanelCount(); + expect(currentPanelCount).to.eql(unsavedPanelCount); + }); - it('does not show unsaved changes badge after saving', async () => { - await PageObjects.dashboard.saveDashboard('Unsaved State Test'); - await PageObjects.header.waitUntilLoadingHasFinished(); - await testSubjects.missingOrFail('dashboardUnsavedChangesBadge'); + it('resets to original panel count after discarding changes', async () => { + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.clickCancelOutOfEditMode(); + await PageObjects.header.waitUntilLoadingHasFinished(); + const currentPanelCount = await PageObjects.dashboard.getPanelCount(); + expect(currentPanelCount).to.eql(originalPanelCount); + expect(PageObjects.dashboard.getIsInViewMode()).to.eql(true); + }); + + it('does not show unsaved changes badge after saving', async () => { + await PageObjects.dashboard.switchToEditMode(); + await addPanels(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.saveDashboard('Unsaved State Test'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await testSubjects.missingOrFail('dashboardUnsavedChangesBadge'); + }); }); }); } diff --git a/test/functional/apps/discover/_indexpattern_without_timefield.ts b/test/functional/apps/discover/_indexpattern_without_timefield.ts index ff3ad0daf69db..81fb4f92ab730 100644 --- a/test/functional/apps/discover/_indexpattern_without_timefield.ts +++ b/test/functional/apps/discover/_indexpattern_without_timefield.ts @@ -9,6 +9,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { + const retry = getService('retry'); const browser = getService('browser'); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); @@ -56,22 +57,33 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should display a timepicker after switching to an index pattern with timefield', async () => { await PageObjects.discover.selectIndexPattern('with-timefield'); + await PageObjects.discover.waitForDocTableLoadingComplete(); if (!(await PageObjects.timePicker.timePickerExists())) { throw new Error('Expected timepicker to exist'); } }); it('should switch between with and without timefield using the browser back button', async () => { await PageObjects.discover.selectIndexPattern('without-timefield'); + await PageObjects.discover.waitForDocTableLoadingComplete(); if (await PageObjects.timePicker.timePickerExists()) { throw new Error('Expected timepicker not to exist'); } await PageObjects.discover.selectIndexPattern('with-timefield'); + await PageObjects.discover.waitForDocTableLoadingComplete(); if (!(await PageObjects.timePicker.timePickerExists())) { throw new Error('Expected timepicker to exist'); } - // Navigating back to discover + // Navigating back await browser.goBack(); + await PageObjects.discover.waitForDocTableLoadingComplete(); + await retry.waitForWithTimeout( + 'index pattern to have been switched back to "without-timefield"', + 5000, + async () => + (await testSubjects.getVisibleText('indexPattern-switch-link')) === 'without-timefield' + ); + if (await PageObjects.timePicker.timePickerExists()) { throw new Error('Expected timepicker not to exist'); } diff --git a/test/functional/apps/visualize/index.ts b/test/functional/apps/visualize/index.ts index cc5e80bae1b5a..6fdcd2a6659d6 100644 --- a/test/functional/apps/visualize/index.ts +++ b/test/functional/apps/visualize/index.ts @@ -94,13 +94,18 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./_region_map')); }); + describe('visualize ciGroup8', function () { + this.tags('ciGroup8'); + + loadTestFile(require.resolve('./_tsvb_chart')); + }); + describe('visualize ciGroup11', function () { this.tags('ciGroup11'); loadTestFile(require.resolve('./_tag_cloud')); loadTestFile(require.resolve('./_vertical_bar_chart')); loadTestFile(require.resolve('./_vertical_bar_chart_nontimeindex')); - loadTestFile(require.resolve('./_tsvb_chart')); loadTestFile(require.resolve('./_tsvb_time_series')); loadTestFile(require.resolve('./_tsvb_markdown')); loadTestFile(require.resolve('./_tsvb_table')); diff --git a/x-pack/test/case_api_integration/security_and_spaces/tests/basic/index.ts b/x-pack/test/case_api_integration/security_and_spaces/tests/basic/index.ts index 90fbb10637434..3fb7d7a29af39 100644 --- a/x-pack/test/case_api_integration/security_and_spaces/tests/basic/index.ts +++ b/x-pack/test/case_api_integration/security_and_spaces/tests/basic/index.ts @@ -12,7 +12,7 @@ import { createSpacesAndUsers, deleteSpacesAndUsers } from '../../../common/lib/ export default ({ loadTestFile, getService }: FtrProviderContext): void => { describe('cases security and spaces enabled: basic', function () { // Fastest ciGroup for the moment. - this.tags('ciGroup5'); + this.tags('ciGroup13'); before(async () => { await createSpacesAndUsers(getService); diff --git a/x-pack/test/case_api_integration/security_and_spaces/tests/trial/index.ts b/x-pack/test/case_api_integration/security_and_spaces/tests/trial/index.ts index 26bc6a072450d..cd4b062c065a0 100644 --- a/x-pack/test/case_api_integration/security_and_spaces/tests/trial/index.ts +++ b/x-pack/test/case_api_integration/security_and_spaces/tests/trial/index.ts @@ -12,7 +12,7 @@ import { createSpacesAndUsers, deleteSpacesAndUsers } from '../../../common/lib/ export default ({ loadTestFile, getService }: FtrProviderContext): void => { describe('cases security and spaces enabled: trial', function () { // Fastest ciGroup for the moment. - this.tags('ciGroup5'); + this.tags('ciGroup13'); before(async () => { await createSpacesAndUsers(getService); diff --git a/x-pack/test/functional/apps/apm/index.ts b/x-pack/test/functional/apps/apm/index.ts index e4db5a66aa55f..20c2bc264a74f 100644 --- a/x-pack/test/functional/apps/apm/index.ts +++ b/x-pack/test/functional/apps/apm/index.ts @@ -9,7 +9,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('APM specs', function () { - this.tags('ciGroup6'); + this.tags('ciGroup10'); loadTestFile(require.resolve('./feature_controls')); loadTestFile(require.resolve('./correlations')); }); diff --git a/x-pack/test/functional/apps/lens/index.ts b/x-pack/test/functional/apps/lens/index.ts index 99b75bdabe6c4..1b09a4252c3f1 100644 --- a/x-pack/test/functional/apps/lens/index.ts +++ b/x-pack/test/functional/apps/lens/index.ts @@ -25,10 +25,14 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { await esArchiver.unload('x-pack/test/functional/es_archives/lens/basic'); }); + describe('', function () { + this.tags(['ciGroup3', 'skipFirefox']); + loadTestFile(require.resolve('./smokescreen')); + }); + describe('', function () { this.tags(['ciGroup4', 'skipFirefox']); - loadTestFile(require.resolve('./smokescreen')); loadTestFile(require.resolve('./add_to_dashboard')); loadTestFile(require.resolve('./table')); loadTestFile(require.resolve('./runtime_fields')); diff --git a/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js b/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js index d583e41e5e280..9cba88d1adbcc 100644 --- a/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js +++ b/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js @@ -62,6 +62,11 @@ export default function ({ getPageObjects, getService }) { const hasJoinFilter = await filterBar.hasFilter('runtime_shape_name', 'charlie'); expect(hasJoinFilter).to.be(true); }); + + after(async () => { + // Remove all filters so that when loading the dashboard again, the geo shape is still present. Related to #64861 + await filterBar.removeAllFilters(); + }); }); describe('panel actions', () => { diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/feature_importance.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/feature_importance.ts index fbd6cc70418d3..91d7b0a9347e2 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/feature_importance.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/feature_importance.ts @@ -183,7 +183,8 @@ export default function ({ getService }: FtrProviderContext) { }); for (const testData of testDataList) { - describe(`${testData.suiteTitle}`, function () { + // FLAKY: https://github.com/elastic/kibana/issues/93188 + describe.skip(`${testData.suiteTitle}`, function () { before(async () => { await ml.navigation.navigateToMl(); await ml.navigation.navigateToDataFrameAnalytics(); diff --git a/x-pack/test/functional/apps/uptime/index.ts b/x-pack/test/functional/apps/uptime/index.ts index 501fec5002666..2db29bfca4054 100644 --- a/x-pack/test/functional/apps/uptime/index.ts +++ b/x-pack/test/functional/apps/uptime/index.ts @@ -42,7 +42,7 @@ export default ({ loadTestFile, getService }: FtrProviderContext) => { const uptime = getService('uptime'); describe('Uptime app', function () { - this.tags('ciGroup6'); + this.tags('ciGroup10'); beforeEach('delete settings', async () => { await deleteUptimeSettingsObject(server); diff --git a/x-pack/test/functional/apps/uptime/synthetics_integration.ts b/x-pack/test/functional/apps/uptime/synthetics_integration.ts index 146584d138f22..08617ce2cd44a 100644 --- a/x-pack/test/functional/apps/uptime/synthetics_integration.ts +++ b/x-pack/test/functional/apps/uptime/synthetics_integration.ts @@ -78,7 +78,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); describe('When on the Synthetics Integration Policy Create Page', function () { - this.tags(['ciGroup6']); + this.tags(['ciGroup10']); const basicConfig = { name: monitorName, apmServiceName: 'Sample APM Service', diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index 4f00ef6a18c6d..4a3b657623ddd 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -33,7 +33,6 @@ export default async function ({ readConfigFile }) { resolve(__dirname, './apps/discover'), resolve(__dirname, './apps/security'), resolve(__dirname, './apps/spaces'), - resolve(__dirname, './apps/lens'), resolve(__dirname, './apps/logstash'), resolve(__dirname, './apps/grok_debugger'), resolve(__dirname, './apps/infra'), @@ -60,6 +59,7 @@ export default async function ({ readConfigFile }) { resolve(__dirname, './apps/management'), resolve(__dirname, './apps/reporting'), resolve(__dirname, './apps/observability'), + resolve(__dirname, './apps/lens'), // smokescreen tests cause flakiness in other tests //This upgrade assistant file needs to be run after the rest of the jobs because of //it being destructive. diff --git a/yarn.lock b/yarn.lock index 962b0ffbfd9f6..7d5a4afa8756a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7626,6 +7626,13 @@ axios@^0.21.1: dependencies: follow-redirects "^1.10.0" +axios@^0.21.2: + version "0.21.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== + dependencies: + follow-redirects "^1.14.0" + axobject-query@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.0.2.tgz#ea187abe5b9002b377f925d8bf7d1c561adf38f9" @@ -9375,13 +9382,13 @@ chrome-trace-event@^1.0.2: dependencies: tslib "^1.9.0" -chromedriver@^92.0.1: - version "92.0.1" - resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-92.0.1.tgz#3e28b7e0c9fb94d693cf74d51af0c29d57f18dca" - integrity sha512-LptlDVCs1GgyFNVbRoHzzy948JDVzTgGiVPXjNj385qXKQP3hjAVBIgyvb/Hco0xSEW8fjwJfsm1eQRmu6t4pQ== +chromedriver@^93.0.1: + version "93.0.1" + resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-93.0.1.tgz#3ed1f7baa98a754fc1788c42ac8e4bb1ab27db32" + integrity sha512-KDzbW34CvQLF5aTkm3b5VdlTrvdIt4wEpCzT2p4XJIQWQZEPco5pNce7Lu9UqZQGkhQ4mpZt4Ky6NKVyIS2N8A== dependencies: "@testim/chrome-version" "^1.0.7" - axios "^0.21.1" + axios "^0.21.2" del "^6.0.0" extract-zip "^2.0.1" https-proxy-agent "^5.0.0" @@ -13996,6 +14003,11 @@ follow-redirects@^1.0.0, follow-redirects@^1.10.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== +follow-redirects@^1.14.0: + version "1.14.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.3.tgz#6ada78118d8d24caee595595accdc0ac6abd022e" + integrity sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw== + font-awesome@4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/font-awesome/-/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133"