diff --git a/test/functional/page_objects/time_picker.ts b/test/functional/page_objects/time_picker.ts index be43b0d2b29ec..495d4089321b4 100644 --- a/test/functional/page_objects/time_picker.ts +++ b/test/functional/page_objects/time_picker.ts @@ -92,7 +92,9 @@ export class TimePickerPageObject extends FtrService { * @param option 'Today' | 'This_week' | 'Last_15 minutes' | 'Last_24 hours' ... */ async setCommonlyUsedTime(option: CommonlyUsed | string) { + await this.testSubjects.exists('superDatePickerToggleQuickMenuButton', { timeout: 5000 }); await this.testSubjects.click('superDatePickerToggleQuickMenuButton'); + await this.testSubjects.exists(`superDatePickerCommonlyUsed_${option}`, { timeout: 5000 }); await this.testSubjects.click(`superDatePickerCommonlyUsed_${option}`); } diff --git a/x-pack/test/functional_execution_context/test_utils.ts b/x-pack/test/functional_execution_context/test_utils.ts index 65d0d7ece02c4..75215a5f42110 100644 --- a/x-pack/test/functional_execution_context/test_utils.ts +++ b/x-pack/test/functional_execution_context/test_utils.ts @@ -4,18 +4,15 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import Fs from 'fs/promises'; import Path from 'path'; +import JSON5 from 'json5'; +import Fs from 'fs/promises'; import { isEqualWith } from 'lodash'; import type { Ecs, KibanaExecutionContext } from '@kbn/core/server'; -import type { RetryService } from '@kbn/ftr-common-functional-services'; -import { concatMap, defer, filter, firstValueFrom, ReplaySubject, scan, timeout } from 'rxjs'; export const logFilePath = Path.resolve(__dirname, './kibana.log'); export const ANY = Symbol('any'); -let logstream$: ReplaySubject | undefined; - export function getExecutionContextFromLogRecord(record: Ecs | undefined): KibanaExecutionContext { if (record?.log?.logger !== 'execution_context' || !record?.message) { throw new Error(`The record is not an entry of execution context`); @@ -37,95 +34,41 @@ export function isExecutionContextLog( } } -// to avoid splitting log record containing \n symbol -const endOfLine = /(?<=})\s*\n/; -export async function assertLogContains({ - description, +/** + * Checks the provided log records against the provided predicate + */ +export function assertLogContains({ + logs, predicate, - retry, + description, }: { - description: string; + logs: Ecs[]; predicate: (record: Ecs) => boolean; - retry: RetryService; -}): Promise { - // logs are written to disk asynchronously. I sacrificed performance to reduce flakiness. - await retry.waitFor(description, async () => { - if (!logstream$) { - logstream$ = getLogstream$(); - } - try { - await firstValueFrom(logstream$.pipe(filter(predicate), timeout(5_000))); - return true; - } catch (err) { - return false; - } - }); + description: string; +}) { + if (!logs.some(predicate)) { + throw new Error(`Unable to find log entries: ${description}`); + } } /** - * Creates an observable that continuously tails the log file. + * Reads the log file and parses the JSON objects that it contains. */ -function getLogstream$(): ReplaySubject { - const stream$ = new ReplaySubject(); - - defer(async function* () { - const fd = await Fs.open(logFilePath, 'rs'); - while (!stream$.isStopped) { - const { bytesRead, buffer } = await fd.read(); - if (bytesRead) { - yield buffer.toString('utf8', 0, bytesRead); - } - } - await fd.close(); - }) - .pipe( - scan( - ({ buffer }, chunk) => { - const logString = buffer.concat(chunk); - const lines = logString.split(endOfLine); - const lastLine = lines.pop(); - const records = lines.map((s) => JSON.parse(s)); - - let leftover = ''; - if (lastLine) { - try { - const validRecord = JSON.parse(lastLine); - records.push(validRecord); - } catch (err) { - leftover = lastLine; - } - } - - return { buffer: leftover, records }; - }, - { - records: [], // The ECS entries in the logs - buffer: '', // Accumulated leftovers from the previous operation - } - ), - concatMap(({ records }) => records) - ) - .subscribe(stream$); - - // let the content start flowing - stream$.subscribe(); - - return stream$; -} - -export function closeLogstream() { - logstream$?.complete(); - logstream$ = undefined; +export async function readLogFile(): Promise { + await forceSyncLogFile(); + const logFileContent = await Fs.readFile(logFilePath, 'utf-8'); + return logFileContent + .split('\n') + .filter(Boolean) + .map((str) => JSON5.parse(str)); } /** * Truncates the log file to avoid tests looking at the logs from previous executions. */ export async function clearLogFile() { - closeLogstream(); await Fs.writeFile(logFilePath, '', 'utf8'); await forceSyncLogFile(); - logstream$ = getLogstream$(); } /** diff --git a/x-pack/test/functional_execution_context/tests/browser.ts b/x-pack/test/functional_execution_context/tests/browser.ts index a67a1e64cea67..c168bf1783d55 100644 --- a/x-pack/test/functional_execution_context/tests/browser.ts +++ b/x-pack/test/functional_execution_context/tests/browser.ts @@ -7,14 +7,12 @@ import type { Ecs, KibanaExecutionContext } from '@kbn/core/server'; import type { FtrProviderContext } from '../ftr_provider_context'; -import { assertLogContains, forceSyncLogFile, isExecutionContextLog } from '../test_utils'; +import { assertLogContains, isExecutionContextLog, readLogFile } from '../test_utils'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'dashboard', 'header', 'home', 'timePicker']); - const retry = getService('retry'); - // Failing: See https://github.com/elastic/kibana/issues/149611 - describe.skip('Browser apps', () => { + describe('Browser apps', () => { before(async () => { await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', { useActualUrl: true, @@ -32,11 +30,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); describe('discover app', () => { + let logs: Ecs[]; + before(async () => { await PageObjects.common.navigateToApp('discover'); await PageObjects.timePicker.setCommonlyUsedTime('Last_7 days'); await PageObjects.header.waitUntilLoadingHasFinished(); - await forceSyncLogFile(); + logs = await readLogFile(); }); function checkExecutionContextEntry(expectedExecutionContext: KibanaExecutionContext) { @@ -56,7 +56,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { description: 'execution context propagates to Elasticsearch via "x-opaque-id" header', predicate: (record) => Boolean(record.http?.request?.id?.includes('kibana:application:discover')), - retry, + logs, }); }); @@ -76,7 +76,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { description: 'fetch documents', }, }), - retry, + logs, }); }); @@ -104,20 +104,22 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }, }, }), - retry, + logs, }); }); }); }); describe('dashboard app', () => { + let logs: Ecs[]; + before(async () => { await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.loadSavedDashboard('[Flights] Global Flight Dashboard'); await PageObjects.timePicker.setCommonlyUsedTime('Last_7 days'); await PageObjects.dashboard.waitForRenderComplete(); await PageObjects.header.waitUntilLoadingHasFinished(); - await forceSyncLogFile(); + logs = await readLogFile(); }); function checkHttpRequestId(suffix: string) { @@ -172,7 +174,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { predicate: checkHttpRequestId( 'dashboard:dashboards:7adfa750-4c81-11e8-b3d7-01146121b73d;lens:lnsXY:086ac2e9-dd16-4b45-92b8-1e43ff7e3f65' ), - retry, + logs, }); }); @@ -199,19 +201,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }, }, }), - retry, + logs, }); }); }); - describe('lnsMetric', () => { + describe.skip('lnsMetric', () => { it('propagates to Elasticsearch via "x-opaque-id" header', async () => { await assertLogContains({ description: 'execution context propagates to Elasticsearch via "x-opaque-id" header', predicate: checkHttpRequestId( 'dashboard:dashboards:7adfa750-4c81-11e8-b3d7-01146121b73d;lens:lnsLegacyMetric:b766e3b8-4544-46ed-99e6-9ecc4847e2a2' ), - retry, + logs, }); }); @@ -238,7 +240,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }, }, }), - retry, + logs, }); }); }); @@ -250,7 +252,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { predicate: checkHttpRequestId( 'dashboard:dashboards:7adfa750-4c81-11e8-b3d7-01146121b73d;lens:lnsDatatable:fb86b32f-fb7a-45cf-9511-f366fef51bbd' ), - retry, + logs, }); }); @@ -277,7 +279,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }, }, }), - retry, + logs, }); }); }); @@ -289,7 +291,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { predicate: checkHttpRequestId( 'dashboard:dashboards:7adfa750-4c81-11e8-b3d7-01146121b73d;lens:lnsPie:5d53db36-2d5a-4adc-af7b-cec4c1a294e0' ), - retry, + logs, }); }); @@ -316,7 +318,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }, }, }), - retry, + logs, }); }); }); @@ -329,7 +331,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { predicate: checkHttpRequestId( 'dashboard:dashboards:7adfa750-4c81-11e8-b3d7-01146121b73d;search:discover:571aaf70-4c88-11e8-b3d7-01146121b73d' ), - retry, + logs, }); }); @@ -356,17 +358,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }, }, }), - retry, + logs, }); }); }); - describe('propagates context for TSVB visualizations', () => { + describe.skip('propagates context for TSVB visualizations', () => { it('propagates to Elasticsearch via "x-opaque-id" header', async () => { await assertLogContains({ description: 'execution context propagates to Elasticsearch via "x-opaque-id" header', predicate: checkHttpRequestId('agg_based:metrics:bcb63b50-4c89-11e8-b3d7-01146121b73d'), - retry, + logs, }); }); @@ -387,7 +389,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { url: '/app/visualize#/edit/bcb63b50-4c89-11e8-b3d7-01146121b73d', }, }), - retry, + logs, }); }); }); @@ -399,7 +401,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { predicate: checkHttpRequestId( 'dashboard:dashboards:7adfa750-4c81-11e8-b3d7-01146121b73d;agg_based:vega:ed78a660-53a0-11e8-acbd-0be0ad9d822b' ), - retry, + logs, }); }); @@ -426,19 +428,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }, }, }), - retry, + logs, }); }); }); - describe('propagates context for Tag Cloud visualization', () => { + describe.skip('propagates context for Tag Cloud visualization', () => { it('propagates to Elasticsearch via "x-opaque-id" header', async () => { await assertLogContains({ description: 'execution context propagates to Elasticsearch via "x-opaque-id" header', predicate: checkHttpRequestId( 'dashboard:dashboards:7adfa750-4c81-11e8-b3d7-01146121b73d;agg_based:tagcloud:293b5a30-4c8f-11e8-b3d7-01146121b73d' ), - retry, + logs, }); }); @@ -465,19 +467,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }, }, }), - retry, + logs, }); }); }); - describe('propagates context for Vertical bar visualization', () => { + describe.skip('propagates context for Vertical bar visualization', () => { it('propagates to Elasticsearch via "x-opaque-id" header', async () => { await assertLogContains({ description: 'execution context propagates to Elasticsearch via "x-opaque-id" header', predicate: checkHttpRequestId( 'dashboard:dashboards:7adfa750-4c81-11e8-b3d7-01146121b73d;agg_based:histogram:9886b410-4c8b-11e8-b3d7-01146121b73d' ), - retry, + logs, }); }); @@ -504,7 +506,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }, }, }), - retry, + logs, }); }); }); diff --git a/x-pack/test/functional_execution_context/tests/index.ts b/x-pack/test/functional_execution_context/tests/index.ts index ac83d4246bb8c..b50d2411a694c 100644 --- a/x-pack/test/functional_execution_context/tests/index.ts +++ b/x-pack/test/functional_execution_context/tests/index.ts @@ -6,7 +6,7 @@ */ import { FtrProviderContext } from '../ftr_provider_context'; -import { clearLogFile, closeLogstream } from '../test_utils'; +import { clearLogFile } from '../test_utils'; export default function ({ loadTestFile }: FtrProviderContext) { describe('Execution context', function () { @@ -15,9 +15,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { // If any of the tests rely on logs generating during bootstrap, we might need to change this. await clearLogFile(); }); - after(() => { - closeLogstream(); - }); loadTestFile(require.resolve('./browser')); loadTestFile(require.resolve('./server')); diff --git a/x-pack/test/functional_execution_context/tests/log_correlation.ts b/x-pack/test/functional_execution_context/tests/log_correlation.ts index 35585f092bbcf..2dade69e6f1ee 100644 --- a/x-pack/test/functional_execution_context/tests/log_correlation.ts +++ b/x-pack/test/functional_execution_context/tests/log_correlation.ts @@ -6,10 +6,9 @@ */ import expect from '@kbn/expect'; import type { FtrProviderContext } from '../ftr_provider_context'; -import { assertLogContains, forceSyncLogFile } from '../test_utils'; +import { readLogFile, assertLogContains } from '../test_utils'; export default function ({ getService }: FtrProviderContext) { - const retry = getService('retry'); const supertest = getService('supertest'); describe('Log Correlation', () => { @@ -24,7 +23,7 @@ export default function ({ getService }: FtrProviderContext) { expect(response2.body.traceId).not.to.be(response1.body.traceId); - await forceSyncLogFile(); + const logs = await readLogFile(); let responseTraceId: string | undefined; await assertLogContains({ @@ -41,7 +40,7 @@ export default function ({ getService }: FtrProviderContext) { } return false; }, - retry, + logs, }); expect(responseTraceId).to.be.a('string'); @@ -56,7 +55,7 @@ export default function ({ getService }: FtrProviderContext) { record.message?.includes('HEAD /') ), - retry, + logs, }); }); }); diff --git a/x-pack/test/functional_execution_context/tests/server.ts b/x-pack/test/functional_execution_context/tests/server.ts index 64035a0077966..9f8f3a9dfa3f7 100644 --- a/x-pack/test/functional_execution_context/tests/server.ts +++ b/x-pack/test/functional_execution_context/tests/server.ts @@ -11,14 +11,13 @@ import { } from '@kbn/core-http-common'; import expect from '@kbn/expect'; import type { FtrProviderContext } from '../ftr_provider_context'; -import { assertLogContains, isExecutionContextLog, ANY } from '../test_utils'; +import { readLogFile, assertLogContains, isExecutionContextLog, ANY } from '../test_utils'; function delay(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } export default function ({ getService }: FtrProviderContext) { - const retry = getService('retry'); const supertest = getService('supertest'); const log = getService('log'); @@ -69,6 +68,7 @@ export default function ({ getService }: FtrProviderContext) { const alertId = createdAlert.id; await waitForStatus(alertId, new Set(['ok']), 90_000); + const logs = await readLogFile(); await assertLogContains({ description: @@ -80,7 +80,7 @@ export default function ({ getService }: FtrProviderContext) { `kibana:task%20manager:run%20alerting%3Atest.executionContext:` ) ), - retry, + logs, }); await assertLogContains({ @@ -90,7 +90,7 @@ export default function ({ getService }: FtrProviderContext) { Boolean( record.http?.request?.id?.includes(`alert:execute%20test.executionContext:${alertId}`) ), - retry, + logs, }); await assertLogContains({ @@ -109,7 +109,7 @@ export default function ({ getService }: FtrProviderContext) { description: 'execute [test.executionContext] with name [abc] in [default] namespace', }, }), - retry, + logs, }); }); @@ -122,6 +122,8 @@ export default function ({ getService }: FtrProviderContext) { .send({ unencrypted: false }) .expect(200); + const logs = await readLogFile(); + await assertLogContains({ description: 'usage_collection execution context propagates to Elasticsearch via "x-opaque-id" header', @@ -132,7 +134,7 @@ export default function ({ getService }: FtrProviderContext) { `kibana:usage_collection:collector.fetch:application_usage` ) ), - retry, + logs, }); await assertLogContains({ @@ -144,7 +146,7 @@ export default function ({ getService }: FtrProviderContext) { id: 'application_usage', description: 'Fetch method in the Collector "application_usage"', }), - retry, + logs, }); }); });