diff --git a/.buildkite/scripts/steps/functional/performance_playwright.sh b/.buildkite/scripts/steps/functional/performance_playwright.sh index 596304d156cf0..9a4301e94f7fe 100644 --- a/.buildkite/scripts/steps/functional/performance_playwright.sh +++ b/.buildkite/scripts/steps/functional/performance_playwright.sh @@ -20,9 +20,6 @@ sleep 120 cd "$XPACK_DIR" -jobId=$(npx uuid) -export TEST_JOB_ID="$jobId" - journeys=("ecommerce_dashboard" "flight_dashboard" "web_logs_dashboard" "promotion_tracking_dashboard") for i in "${journeys[@]}"; do diff --git a/x-pack/test/performance/config.playwright.ts b/x-pack/test/performance/config.playwright.ts index cc290ee19d2fa..9077c58a30e15 100644 --- a/x-pack/test/performance/config.playwright.ts +++ b/x-pack/test/performance/config.playwright.ts @@ -19,8 +19,11 @@ export default async function ({ readConfigFile, log }: FtrConfigProviderContext const testFiles = [require.resolve('./tests/playwright')]; - const testJobId = process.env.TEST_JOB_ID ?? uuid(); - log.info(`👷 JOB ID ${testJobId}👷`); + const testBuildId = process.env.BUILDKITE_BUILD_ID ?? `local-${uuid()}`; + const testJobId = process.env.BUILDKITE_JOB_ID ?? `local-${uuid()}`; + const executionId = uuid(); + + log.info(` 👷‍♀️ BUILD ID ${testBuildId}\n 👷 JOB ID ${testJobId}\n 👷‍♂️ EXECUTION ID:${executionId}`); return { testFiles, diff --git a/x-pack/test/performance/services/auth.ts b/x-pack/test/performance/services/auth.ts new file mode 100644 index 0000000000000..90e20c36e59d9 --- /dev/null +++ b/x-pack/test/performance/services/auth.ts @@ -0,0 +1,62 @@ +/* + * 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 axios, { AxiosResponse } from 'axios'; +import Url from 'url'; +import { FtrService, FtrProviderContext } from '../ftr_provider_context'; + +export interface Credentials { + username: string; + password: string; +} + +function extractCookieValue(authResponse: AxiosResponse) { + return authResponse.headers['set-cookie'][0].toString().split(';')[0].split('sid=')[1] as string; +} +export class AuthService extends FtrService { + private readonly kibanaServer = this.ctx.getService('kibanaServer'); + private readonly config = this.ctx.getService('config'); + + constructor(ctx: FtrProviderContext) { + super(ctx); + } + + public async login({ username, password }: Credentials) { + const headers = { + 'content-type': 'application/json', + 'kbn-version': await this.kibanaServer.version.get(), + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'same-origin', + }; + + const baseUrl = Url.format({ + protocol: this.config.get('servers.kibana.protocol'), + hostname: this.config.get('servers.kibana.hostname'), + port: this.config.get('servers.kibana.port'), + }); + + const loginUrl = baseUrl + '/internal/security/login'; + const provider = baseUrl.includes('localhost') ? 'basic' : 'cloud-basic'; + + const authBody = { + providerType: 'basic', + providerName: provider, + currentURL: `${baseUrl}/login?next=%2F`, + params: { username, password }, + }; + + const authResponse = await axios.post(loginUrl, authBody, { headers }); + + return { + name: 'sid', + value: extractCookieValue(authResponse), + url: baseUrl, + }; + } +} + +export const AuthProvider = (ctx: FtrProviderContext) => new AuthService(ctx); diff --git a/x-pack/test/performance/services/index.ts b/x-pack/test/performance/services/index.ts index 99cc10bc14bdc..d8cd6075c91c2 100644 --- a/x-pack/test/performance/services/index.ts +++ b/x-pack/test/performance/services/index.ts @@ -8,6 +8,7 @@ import { services as functionalServices } from '../../functional/services'; import { PerformanceTestingService } from './performance'; import { InputDelaysProvider } from './input_delays'; +import { AuthProvider } from './auth'; export const services = { es: functionalServices.es, @@ -16,4 +17,5 @@ export const services = { retry: functionalServices.retry, performance: PerformanceTestingService, inputDelays: InputDelaysProvider, + auth: AuthProvider, }; diff --git a/x-pack/test/performance/services/performance.ts b/x-pack/test/performance/services/performance.ts index 3905a61bf7b33..ffe7211c63153 100644 --- a/x-pack/test/performance/services/performance.ts +++ b/x-pack/test/performance/services/performance.ts @@ -10,52 +10,46 @@ import Url from 'url'; import { inspect } from 'util'; import apm, { Span, Transaction } from 'elastic-apm-node'; -import { setTimeout } from 'timers/promises'; -import playwright, { ChromiumBrowser, Page, BrowserContext } from 'playwright'; +import playwright, { ChromiumBrowser, Page, BrowserContext, CDPSession } from 'playwright'; import { FtrService, FtrProviderContext } from '../ftr_provider_context'; -type StorageState = Awaited>; - apm.start({ - secretToken: 'Q5q5rWQEw6tKeirBpw', - serverUrl: 'https://2fad4006bf784bb8a54e52f4a5862609.apm.us-west1.gcp.cloud.es.io:443', serviceName: 'functional test runner', + serverUrl: 'https://kibana-ops-e2e-perf.apm.us-central1.gcp.cloud.es.io:443', + secretToken: 'CTs9y3cvcfq13bQqsB', }); -interface StepCtx { +export interface StepCtx { page: Page; + kibanaUrl: string; } + type StepFn = (ctx: StepCtx) => Promise; -type Steps = Array<{ name: string; fn: StepFn }>; +export type Steps = Array<{ name: string; handler: StepFn }>; export class PerformanceTestingService extends FtrService { + private readonly auth = this.ctx.getService('auth'); private readonly config = this.ctx.getService('config'); - private readonly lifecycle = this.ctx.getService('lifecycle'); - private readonly inputDelays = this.ctx.getService('inputDelays'); + private browser: ChromiumBrowser | undefined; - private storageState: StorageState | undefined; private currentSpanStack: Array = []; - private currentTransaction: Transaction | undefined | null; + private currentTransaction: Transaction | undefined | null = undefined; constructor(ctx: FtrProviderContext) { super(ctx); + } - this.lifecycle.beforeTests.add(async () => { - await this.withTransaction('Journey setup', async () => { - await this.getStorageState(); - }); - }); - - this.lifecycle.cleanup.add(async () => { - apm.flush(); - await setTimeout(5000); - await this.browser?.close(); + private getKibanaUrl() { + return Url.format({ + protocol: this.config.get('servers.kibana.protocol'), + hostname: this.config.get('servers.kibana.hostname'), + port: this.config.get('servers.kibana.port'), }); } private async withTransaction(name: string, block: () => Promise) { try { - if (this.currentTransaction !== undefined) { + if (this.currentTransaction) { throw new Error( `Transaction already started, make sure you end transaction ${this.currentTransaction?.name}` ); @@ -105,42 +99,6 @@ export class PerformanceTestingService extends FtrService { ?.traceparent; } - private async getStorageState() { - if (this.storageState) { - return this.storageState; - } - - await this.withSpan('initial login', undefined, async () => { - const kibanaUrl = Url.format({ - protocol: this.config.get('servers.kibana.protocol'), - hostname: this.config.get('servers.kibana.hostname'), - port: this.config.get('servers.kibana.port'), - }); - - const browser = await this.getBrowserInstance(); - const context = await browser.newContext(); - const page = await context.newPage(); - await this.interceptBrowserRequests(page); - await page.goto(`${kibanaUrl}`); - - const usernameLocator = page.locator('[data-test-subj=loginUsername]'); - const passwordLocator = page.locator('[data-test-subj=loginPassword]'); - const submitButtonLocator = page.locator('[data-test-subj=loginSubmit]'); - - await usernameLocator?.type('elastic', { delay: this.inputDelays.TYPING }); - await passwordLocator?.type('changeme', { delay: this.inputDelays.TYPING }); - await submitButtonLocator?.click({ delay: this.inputDelays.MOUSE_CLICK }); - - await page.waitForSelector('#headerUserMenu'); - - this.storageState = await page.context().storageState(); - await page.close(); - await context.close(); - }); - - return this.storageState; - } - private async getBrowserInstance() { if (this.browser) { return this.browser; @@ -179,70 +137,79 @@ export class PerformanceTestingService extends FtrService { }); } - public makePage(journeyName: string) { - const steps: Steps = []; - - it(journeyName, async () => { - await this.withTransaction(`Journey ${journeyName}`, async () => { - const browser = await this.getBrowserInstance(); - const context = await browser.newContext({ - viewport: { width: 1600, height: 1200 }, - storageState: await this.getStorageState(), - }); - - const page = await context.newPage(); - page.on('console', (message) => { - (async () => { - try { - const args = await Promise.all( - message.args().map(async (handle) => handle.jsonValue()) - ); + public runUserJourney( + journeyName: string, + steps: Steps, + { requireAuth }: { requireAuth: boolean } + ) { + return this.withTransaction(`Journey ${journeyName}`, async () => { + const browser = await this.getBrowserInstance(); + const viewport = { width: 1600, height: 1200 }; + const context = await browser.newContext({ viewport }); - const { url, lineNumber, columnNumber } = message.location(); + if (!requireAuth) { + const cookie = await this.auth.login({ username: 'elastic', password: 'changeme' }); + await context.addCookies([cookie]); + } - const location = `${url},${lineNumber},${columnNumber}`; + const page = await context.newPage(); + if (!process.env.NO_BROWSER_LOG) { + page.on('console', this.onConsoleEvent()); + } + const client = await this.sendCDPCommands(context, page); - const text = args.length - ? args.map((arg) => (typeof arg === 'string' ? arg : inspect(arg))).join(' ') - : message.text(); + await this.interceptBrowserRequests(page); + await this.handleSteps(steps, page); + await this.tearDown(page, client, context); + }); + } - console.log(`[console.${message.type()}]`, text); - console.log(' ', location); - } catch (e) { - console.error('Failed to evaluate console.log line', e); - } - })(); - }); - const client = await this.sendCDPCommands(context, page); + private async tearDown(page: Page, client: CDPSession, context: BrowserContext) { + if (page) { + apm.flush(); + await client.detach(); + await page.close(); + await context.close(); + } + } - await this.interceptBrowserRequests(page); + public async shutdownBrowser() { + if (this.browser) { + await (await this.getBrowserInstance()).close(); + } + } + private async handleSteps(steps: Array<{ name: string; handler: StepFn }>, page: Page) { + for (const step of steps) { + await this.withSpan(`step: ${step.name}`, 'step', async () => { try { - for (const step of steps) { - await this.withSpan(`step: ${step.name}`, 'step', async () => { - try { - await step.fn({ page }); - } catch (e) { - const error = new Error(`Step [${step.name}] failed: ${e.message}`); - error.stack = e.stack; - throw error; - } - }); - } - } finally { - if (page) { - await client.detach(); - await page.close(); - await context.close(); - } + await step.handler({ page, kibanaUrl: this.getKibanaUrl() }); + } catch (e) { + const error = new Error(`Step [${step.name}] failed: ${e.message}`); + error.stack = e.stack; } }); - }); + } + } + + private onConsoleEvent() { + return async (message: playwright.ConsoleMessage) => { + try { + const args = await Promise.all(message.args().map(async (handle) => handle.jsonValue())); + + const { url, lineNumber, columnNumber } = message.location(); - return { - step: (name: string, fn: StepFn) => { - steps.push({ name, fn }); - }, + const location = `${url},${lineNumber},${columnNumber}`; + + const text = args.length + ? args.map((arg) => (typeof arg === 'string' ? arg : inspect(arg, false, null))).join(' ') + : message.text(); + + console.log(`[console.${message.type()}]`, text); + console.log(' ', location); + } catch (e) { + console.error('Failed to evaluate console.log line', e); + } }; } } diff --git a/x-pack/test/performance/tests/playwright/ecommerce_dashboard.ts b/x-pack/test/performance/tests/playwright/ecommerce_dashboard.ts index 1f75d39a3cd62..143ca97c6d0b0 100644 --- a/x-pack/test/performance/tests/playwright/ecommerce_dashboard.ts +++ b/x-pack/test/performance/tests/playwright/ecommerce_dashboard.ts @@ -4,53 +4,64 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import Url from 'url'; +import { Page } from 'playwright'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { StepCtx } from '../../services/performance'; export default function ecommerceDashboard({ getService }: FtrProviderContext) { describe('ecommerce_dashboard', () => { - const config = getService('config'); - const performance = getService('performance'); - const logger = getService('log'); + it('ecommerce_dashboard', async () => { + const performance = getService('performance'); + const logger = getService('log'); - const { step } = performance.makePage('ecommerce_dashboard'); + await performance.runUserJourney( + 'ecommerce_dashboard', + [ + { + name: 'Go to Sample Data Page', + handler: async ({ page, kibanaUrl }: StepCtx) => { + await page.goto(`${kibanaUrl}/app/home#/tutorial_directory/sampleData`); + await page.waitForSelector('text="More ways to add data"'); + }, + }, + { + name: 'Add Ecommerce Sample Data', + handler: async ({ page }: { page: Page }) => { + const removeButton = page.locator('[data-test-subj=removeSampleDataSetecommerce]'); + try { + await removeButton.click({ timeout: 1_000 }); + } catch (e) { + logger.info('Ecommerce data does not exist'); + } + const addDataButton = page.locator('[data-test-subj=addSampleDataSetecommerce]'); + if (addDataButton) { + await addDataButton.click(); + } + }, + }, + { + name: 'Go to Ecommerce Dashboard', + handler: async ({ page }: { page: Page }) => { + await page.click('[data-test-subj=launchSampleDataSetecommerce]'); + await page.click('[data-test-subj=viewSampleDataSetecommerce-dashboard]'); - step('Go to Sample Data Page', async ({ page }) => { - const kibanaUrl = Url.format({ - protocol: config.get('servers.kibana.protocol'), - hostname: config.get('servers.kibana.hostname'), - port: config.get('servers.kibana.port'), - }); - - await page.goto(`${kibanaUrl}/app/home#/tutorial_directory/sampleData`); - await page.waitForSelector('text="More ways to add data"'); - }); - - step('Add Ecommerce Sample Data', async ({ page }) => { - const removeButton = page.locator('[data-test-subj=removeSampleDataSetecommerce]'); - try { - await removeButton.click({ timeout: 1_000 }); - } catch (e) { - logger.info('Ecommerce data does not exist'); - } - const addDataButton = page.locator('[data-test-subj=addSampleDataSetecommerce]'); - if (addDataButton) { - await addDataButton.click(); - } - }); - - step('Go to Ecommerce Dashboard', async ({ page }) => { - await page.click('[data-test-subj=launchSampleDataSetecommerce]'); - await page.click('[data-test-subj=viewSampleDataSetecommerce-dashboard]'); - - await page.waitForFunction(() => { - const visualizations = Array.from(document.querySelectorAll('[data-rendering-count]')); - const visualizationElementsLoaded = visualizations.length > 0; - const visualizationAnimationsFinished = visualizations.every( - (e) => e.getAttribute('data-render-complete') === 'true' - ); - return visualizationElementsLoaded && visualizationAnimationsFinished; - }); + await page.waitForFunction(function renderCompleted() { + const visualizations = Array.from( + document.querySelectorAll('[data-rendering-count]') + ); + const visualizationElementsLoaded = visualizations.length > 0; + const visualizationAnimationsFinished = visualizations.every( + (e) => e.getAttribute('data-render-complete') === 'true' + ); + return visualizationElementsLoaded && visualizationAnimationsFinished; + }); + }, + }, + ], + { + requireAuth: false, + } + ); }); }); } diff --git a/x-pack/test/performance/tests/playwright/flight_dashboard.ts b/x-pack/test/performance/tests/playwright/flight_dashboard.ts index 04ea397d95e34..4844265018a05 100644 --- a/x-pack/test/performance/tests/playwright/flight_dashboard.ts +++ b/x-pack/test/performance/tests/playwright/flight_dashboard.ts @@ -4,69 +4,84 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import Url from 'url'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { StepCtx } from '../../services/performance'; export default function flightDashboard({ getService }: FtrProviderContext) { - describe('flights_dashboard', () => { - const config = getService('config'); - const performance = getService('performance'); - const logger = getService('log'); - const { step } = performance.makePage('flights_dashboard'); + describe('flight_dashboard', () => { + it('flight_dashboard', async () => { + const performance = getService('performance'); + const logger = getService('log'); - step('Go to Sample Data Page', async ({ page }) => { - const kibanaUrl = Url.format({ - protocol: config.get('servers.kibana.protocol'), - hostname: config.get('servers.kibana.hostname'), - port: config.get('servers.kibana.port'), - }); + await performance.runUserJourney( + 'flight_dashboard', + [ + { + name: 'Go to Sample Data Page', + handler: async ({ page, kibanaUrl }: StepCtx) => { + await page.goto(`${kibanaUrl}/app/home#/tutorial_directory/sampleData`); + await page.waitForSelector('[data-test-subj=sampleDataSetCardflights]'); + }, + }, + { + name: 'Add Flights Sample Data', + handler: async ({ page }) => { + const removeButton = page.locator('[data-test-subj=removeSampleDataSetflights]'); + try { + await removeButton.click({ timeout: 1_000 }); + } catch (e) { + logger.info('Flights data does not exist'); + } - await page.goto(`${kibanaUrl}/app/home#/tutorial_directory/sampleData`); - await page.waitForSelector('[data-test-subj=sampleDataSetCardflights]'); - }); - - step('Add Flights Sample Data', async ({ page }) => { - const removeButton = page.locator('[data-test-subj=removeSampleDataSetflights]'); - try { - await removeButton.click({ timeout: 1_000 }); - } catch (e) { - logger.info('Flights data does not exist'); - } - - const addDataButton = page.locator('[data-test-subj=addSampleDataSetflights]'); - if (addDataButton) { - await addDataButton.click(); - } - }); - - step('Go to Flights Dashboard', async ({ page }) => { - await page.click('[data-test-subj=launchSampleDataSetflights]'); - await page.click('[data-test-subj=viewSampleDataSetflights-dashboard]'); + const addDataButton = page.locator('[data-test-subj=addSampleDataSetflights]'); + if (addDataButton) { + await addDataButton.click(); + } + }, + }, + { + name: 'Go to Flights Dashboard', + handler: async ({ page }) => { + await page.click('[data-test-subj=launchSampleDataSetflights]'); + await page.click('[data-test-subj=viewSampleDataSetflights-dashboard]'); - await page.waitForFunction(() => { - const visualizations = Array.from(document.querySelectorAll('[data-rendering-count]')); - const visualizationElementsLoaded = visualizations.length > 0; - const visualizationAnimationsFinished = visualizations.every( - (e) => e.getAttribute('data-render-complete') === 'true' - ); - return visualizationElementsLoaded && visualizationAnimationsFinished; - }); - }); - // embeddablePanelHeading-[Flights]AirportConnections(HoverOverAirport) - step('Go to Airport Connections Visualizations Edit', async ({ page }) => { - await page.click('[data-test-subj="dashboardEditMode"]'); + await page.waitForFunction(function renderCompleted() { + const visualizations = Array.from( + document.querySelectorAll('[data-rendering-count]') + ); + const visualizationElementsLoaded = visualizations.length > 0; + const visualizationAnimationsFinished = visualizations.every( + (e) => e.getAttribute('data-render-complete') === 'true' + ); + return visualizationElementsLoaded && visualizationAnimationsFinished; + }); + }, + }, + { + name: 'Go to Airport Connections Visualizations Edit', + handler: async ({ page }) => { + await page.click('[data-test-subj="dashboardEditMode"]'); - const flightsPanelHeadingSelector = `[data-test-subj="embeddablePanelHeading-[Flights]AirportConnections(HoverOverAirport)"]`; - const panelToggleMenuIconSelector = `[data-test-subj="embeddablePanelToggleMenuIcon"]`; + const flightsPanelHeadingSelector = `[data-test-subj="embeddablePanelHeading-[Flights]AirportConnections(HoverOverAirport)"]`; + const panelToggleMenuIconSelector = `[data-test-subj="embeddablePanelToggleMenuIcon"]`; - await page.click(`${flightsPanelHeadingSelector} ${panelToggleMenuIconSelector}`); + await page.click(`${flightsPanelHeadingSelector} ${panelToggleMenuIconSelector}`); - await page.click('[data-test-subj="embeddablePanelAction-editPanel"]'); + await page.click('[data-test-subj="embeddablePanelAction-editPanel"]'); - await page.waitForFunction(() => { - const visualization = document.querySelector('[data-rendering-count]'); - return visualization && visualization?.getAttribute('data-render-complete') === 'true'; - }); + await page.waitForFunction(function renderCompleted() { + const visualization = document.querySelector('[data-rendering-count]'); + return ( + visualization && visualization?.getAttribute('data-render-complete') === 'true' + ); + }); + }, + }, + ], + { + requireAuth: false, + } + ); }); }); } diff --git a/x-pack/test/performance/tests/playwright/index.ts b/x-pack/test/performance/tests/playwright/index.ts index 7f426ee047d3e..05dfe4cdfbc81 100644 --- a/x-pack/test/performance/tests/playwright/index.ts +++ b/x-pack/test/performance/tests/playwright/index.ts @@ -6,11 +6,18 @@ */ import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ loadTestFile }: FtrProviderContext) { +export default function ({ loadTestFile, getService }: FtrProviderContext) { + const performance = getService('performance'); + describe('Performance tests', () => { + loadTestFile(require.resolve('./login')); loadTestFile(require.resolve('./ecommerce_dashboard')); loadTestFile(require.resolve('./flight_dashboard')); loadTestFile(require.resolve('./web_logs_dashboard')); loadTestFile(require.resolve('./promotion_tracking_dashboard')); + + after(async () => { + await performance.shutdownBrowser(); + }); }); } diff --git a/x-pack/test/performance/tests/playwright/login.ts b/x-pack/test/performance/tests/playwright/login.ts new file mode 100644 index 0000000000000..74baabc049f86 --- /dev/null +++ b/x-pack/test/performance/tests/playwright/login.ts @@ -0,0 +1,42 @@ +/* + * 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'; +import { StepCtx } from '../../services/performance'; + +export default function ecommerceDashboard({ getService }: FtrProviderContext) { + describe('login', () => { + it('login', async () => { + const inputDelays = getService('inputDelays'); + const performance = getService('performance'); + + await performance.runUserJourney( + 'login', + [ + { + name: 'Login', + handler: async ({ page, kibanaUrl }: StepCtx) => { + await page.goto(`${kibanaUrl}`); + + const usernameLocator = page.locator('[data-test-subj=loginUsername]'); + const passwordLocator = page.locator('[data-test-subj=loginPassword]'); + const submitButtonLocator = page.locator('[data-test-subj=loginSubmit]'); + + await usernameLocator?.type('elastic', { delay: inputDelays.TYPING }); + await passwordLocator?.type('changeme', { delay: inputDelays.TYPING }); + await submitButtonLocator?.click({ delay: inputDelays.MOUSE_CLICK }); + + await page.waitForSelector('#headerUserMenu'); + }, + }, + ], + { + requireAuth: true, + } + ); + }); + }); +} diff --git a/x-pack/test/performance/tests/playwright/promotion_tracking_dashboard.ts b/x-pack/test/performance/tests/playwright/promotion_tracking_dashboard.ts index 4f36378eb1dc3..cb0fb09aafefa 100644 --- a/x-pack/test/performance/tests/playwright/promotion_tracking_dashboard.ts +++ b/x-pack/test/performance/tests/playwright/promotion_tracking_dashboard.ts @@ -4,16 +4,14 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import Url from 'url'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { StepCtx } from '../../services/performance'; export default function promotionTrackingDashboard({ getService }: FtrProviderContext) { describe('promotion_tracking_dashboard', () => { - const config = getService('config'); const performance = getService('performance'); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); - const { step } = performance.makePage('promotion_tracking_dashboard'); before(async () => { await kibanaServer.importExport.load( @@ -29,45 +27,60 @@ export default function promotionTrackingDashboard({ getService }: FtrProviderCo await esArchiver.unload('x-pack/test/performance/es_archives/ecommerce_sample_data'); }); - step('Go to Dashboards Page', async ({ page }) => { - const kibanaUrl = Url.format({ - protocol: config.get('servers.kibana.protocol'), - hostname: config.get('servers.kibana.hostname'), - port: config.get('servers.kibana.port'), - }); + it('promotion_tracking_dashboard', async () => { + await performance.runUserJourney( + 'promotion_tracking_dashboard', + [ + { + name: 'Go to Dashboards Page', + handler: async ({ page, kibanaUrl }: StepCtx) => { + await page.goto(`${kibanaUrl}/app/dashboards`); + await page.waitForSelector('#dashboardListingHeading'); + }, + }, + { + name: 'Go to Promotion Tracking Dashboard', + handler: async ({ page }) => { + const promotionDashboardButton = page.locator( + '[data-test-subj="dashboardListingTitleLink-Promotion-Dashboard"]' + ); + await promotionDashboardButton.click(); + }, + }, + { + name: 'Change time range', + handler: async ({ page }) => { + const beginningTimeRangeButton = page.locator( + '[data-test-subj="superDatePickerToggleQuickMenuButton"]' + ); + await beginningTimeRangeButton.click(); - await page.goto(`${kibanaUrl}/app/dashboards`); - await page.waitForSelector('#dashboardListingHeading'); - }); - - step('Go to Promotion Tracking Dashboard', async ({ page }) => { - const promotionDashboardButton = page.locator( - '[data-test-subj="dashboardListingTitleLink-Promotion-Dashboard"]' - ); - await promotionDashboardButton.click(); - }); - - step('Change time range', async ({ page }) => { - const beginningTimeRangeButton = page.locator( - '[data-test-subj="superDatePickerToggleQuickMenuButton"]' + const lastYearButton = page.locator( + '[data-test-subj="superDatePickerCommonlyUsed_Last_30 days"]' + ); + await lastYearButton.click(); + }, + }, + { + name: 'Wait for visualization animations to finish', + handler: async ({ page }) => { + await page.waitForFunction(function renderCompleted() { + const visualizations = Array.from( + document.querySelectorAll('[data-rendering-count]') + ); + const visualizationElementsLoaded = visualizations.length > 0; + const visualizationAnimationsFinished = visualizations.every( + (e) => e.getAttribute('data-render-complete') === 'true' + ); + return visualizationElementsLoaded && visualizationAnimationsFinished; + }); + }, + }, + ], + { + requireAuth: false, + } ); - await beginningTimeRangeButton.click(); - - const lastYearButton = page.locator( - '[data-test-subj="superDatePickerCommonlyUsed_Last_30 days"]' - ); - await lastYearButton.click(); - }); - - step('Wait for visualization animations to finish', async ({ page }) => { - await page.waitForFunction(() => { - const visualizations = Array.from(document.querySelectorAll('[data-rendering-count]')); - const visualizationElementsLoaded = visualizations.length > 0; - const visualizationAnimationsFinished = visualizations.every( - (e) => e.getAttribute('data-render-complete') === 'true' - ); - return visualizationElementsLoaded && visualizationAnimationsFinished; - }); }); }); } diff --git a/x-pack/test/performance/tests/playwright/web_logs_dashboard.ts b/x-pack/test/performance/tests/playwright/web_logs_dashboard.ts index 6da99a6662c4e..6ecee7f1244f7 100644 --- a/x-pack/test/performance/tests/playwright/web_logs_dashboard.ts +++ b/x-pack/test/performance/tests/playwright/web_logs_dashboard.ts @@ -4,53 +4,64 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import Url from 'url'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { StepCtx } from '../../services/performance'; export default function weblogDashboard({ getService }: FtrProviderContext) { describe('weblogs_dashboard', () => { - const config = getService('config'); - const performance = getService('performance'); - const logger = getService('log'); - const { step } = performance.makePage('weblogs_dashboard'); + it('weblogs_dashboard', async () => { + const performance = getService('performance'); + const logger = getService('log'); - step('Go to Sample Data Page', async ({ page }) => { - const kibanaUrl = Url.format({ - protocol: config.get('servers.kibana.protocol'), - hostname: config.get('servers.kibana.hostname'), - port: config.get('servers.kibana.port'), - }); + await performance.runUserJourney( + 'weblogs_dashboard', + [ + { + name: 'Go to Sample Data Page', + handler: async ({ page, kibanaUrl }: StepCtx) => { + await page.goto(`${kibanaUrl}/app/home#/tutorial_directory/sampleData`); + await page.waitForSelector('text="More ways to add data"'); + }, + }, + { + name: 'Add Web Logs Sample Data', + handler: async ({ page }) => { + const removeButton = page.locator('[data-test-subj=removeSampleDataSetlogs]'); + try { + await removeButton.click({ timeout: 1_000 }); + } catch (e) { + logger.info('Weblogs data does not exist'); + } - await page.goto(`${kibanaUrl}/app/home#/tutorial_directory/sampleData`); - await page.waitForSelector('text="More ways to add data"'); - }); - - step('Add Web Logs Sample Data', async ({ page }) => { - const removeButton = page.locator('[data-test-subj=removeSampleDataSetlogs]'); - try { - await removeButton.click({ timeout: 1_000 }); - } catch (e) { - logger.info('Weblogs data does not exist'); - } - - const addDataButton = page.locator('[data-test-subj=addSampleDataSetlogs]'); - if (addDataButton) { - await addDataButton.click(); - } - }); - - step('Go to Web Logs Dashboard', async ({ page }) => { - await page.click('[data-test-subj=launchSampleDataSetlogs]'); - await page.click('[data-test-subj=viewSampleDataSetlogs-dashboard]'); + const addDataButton = page.locator('[data-test-subj=addSampleDataSetlogs]'); + if (addDataButton) { + await addDataButton.click(); + } + }, + }, + { + name: 'Go to Web Logs Dashboard', + handler: async ({ page }) => { + await page.click('[data-test-subj=launchSampleDataSetlogs]'); + await page.click('[data-test-subj=viewSampleDataSetlogs-dashboard]'); - await page.waitForFunction(() => { - const visualizations = Array.from(document.querySelectorAll('[data-rendering-count]')); - const visualizationElementsLoaded = visualizations.length > 0; - const visualizationAnimationsFinished = visualizations.every( - (e) => e.getAttribute('data-render-complete') === 'true' - ); - return visualizationElementsLoaded && visualizationAnimationsFinished; - }); + await page.waitForFunction(function renderCompleted() { + const visualizations = Array.from( + document.querySelectorAll('[data-rendering-count]') + ); + const visualizationElementsLoaded = visualizations.length > 0; + const visualizationAnimationsFinished = visualizations.every( + (e) => e.getAttribute('data-render-complete') === 'true' + ); + return visualizationElementsLoaded && visualizationAnimationsFinished; + }); + }, + }, + ], + { + requireAuth: false, + } + ); }); }); }