Upgrade smoke tests (#92893) (#94477)

* Upgrade smoke tests

* Fix lint issues

* Remove duplicate line

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
liza-mae 2021-03-11 19:39:29 -07:00 committed by GitHub
parent e3c3c7c3f2
commit 5d1707a101
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 746 additions and 0 deletions

View file

@ -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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const retry = getService('retry');
const PageObjects = getPageObjects(['common', 'header']);
const testSubjects = getService('testSubjects');
describe('canvas smoke tests', function describeIndexTests() {
const spaces = [
{ space: 'default', basePath: '' },
{ space: 'automation', basePath: 's/automation' },
];
const canvasTests = [
{
name: 'flights',
id: 'workpad-a474e74b-aedc-47c3-894a-db77e62c41e0/page/1',
numElements: 35,
},
{ name: 'logs', id: 'workpad-5563cc40-5760-4afe-bf33-9da72fac53b7/page/1', numElements: 57 },
{
name: 'ecommerce',
id: 'workpad-e08b9bdb-ec14-4339-94c4-063bddfd610e/page/1',
numElements: 16,
},
{
name: 'ecommerce',
id: 'workpad-e08b9bdb-ec14-4339-94c4-063bddfd610e/page/2',
numElements: 9,
},
];
spaces.forEach(({ space, basePath }) => {
canvasTests.forEach(({ name, id, numElements }) => {
describe('space ' + space + ' name ' + name, () => {
beforeEach(async () => {
await PageObjects.common.navigateToActualUrl('canvas', 'workpad/' + id, {
basePath,
});
await PageObjects.header.waitUntilLoadingHasFinished();
});
it('renders elements on workpad', async () => {
await retry.try(async () => {
const elements = await testSubjects.findAll(
'canvasWorkpadPage > canvasWorkpadPageElementContent'
);
expect(elements).to.have.length(numElements);
});
});
});
});
});
});
}

View file

@ -0,0 +1,12 @@
/*
* 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.
*/
export default ({ loadTestFile }) => {
describe('upgrade', function () {
loadTestFile(require.resolve('./canvas_smoke_tests'));
});
};

View file

@ -0,0 +1,81 @@
/*
* 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 expect from '@kbn/expect';
import moment from 'moment';
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const find = getService('find');
const log = getService('log');
const pieChart = getService('pieChart');
const renderable = getService('renderable');
const dashboardExpect = getService('dashboardExpect');
const PageObjects = getPageObjects(['common', 'header', 'home', 'dashboard', 'timePicker']);
describe('dashboard smoke tests', function describeIndexTests() {
const spaces = [
{ space: 'default', basePath: '' },
{ space: 'automation', basePath: 's/automation' },
];
const dashboardTests = [
{ name: 'flights', numPanels: 19 },
{ name: 'logs', numPanels: 11 },
{ name: 'ecommerce', numPanels: 12 },
];
spaces.forEach(({ space, basePath }) => {
describe('space ' + space, () => {
beforeEach(async () => {
await PageObjects.common.navigateToActualUrl('home', '/tutorial_directory/sampleData', {
basePath,
});
await PageObjects.header.waitUntilLoadingHasFinished();
});
dashboardTests.forEach(({ name, numPanels }) => {
it('should launch sample ' + name + ' data set dashboard', async () => {
await PageObjects.home.launchSampleDashboard(name);
await PageObjects.header.waitUntilLoadingHasFinished();
await renderable.waitForRender();
const todayYearMonthDay = moment().format('MMM D, YYYY');
const fromTime = `${todayYearMonthDay} @ 00:00:00.000`;
const toTime = `${todayYearMonthDay} @ 23:59:59.999`;
await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.be(numPanels);
});
});
it('should render visualizations', async () => {
await PageObjects.home.launchSampleDashboard('flights');
await PageObjects.header.waitUntilLoadingHasFinished();
await renderable.waitForRender();
log.debug('Checking pie charts rendered');
await pieChart.expectPieSliceCount(4);
// https://github.com/elastic/kibana/issues/92887
// log.debug('Checking area, bar and heatmap charts rendered');
// await dashboardExpect.seriesElementCount(15);
log.debug('Checking saved searches rendered');
await dashboardExpect.savedSearchRowCount(50);
log.debug('Checking input controls rendered');
await dashboardExpect.inputControlItemCount(3);
log.debug('Checking tag cloud rendered');
await dashboardExpect.tagCloudWithValuesFound([
'Sunny',
'Rain',
'Clear',
'Cloudy',
'Hail',
]);
log.debug('Checking vega chart rendered');
const tsvb = await find.existsByCssSelector('.vgaVis__view');
expect(tsvb).to.be(true);
});
});
});
});
}

View file

@ -0,0 +1,12 @@
/*
* 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.
*/
export default ({ loadTestFile }) => {
describe('upgrade', function () {
loadTestFile(require.resolve('./dashboard_smoke_tests'));
});
};

View file

@ -0,0 +1,12 @@
/*
* 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.
*/
export default ({ loadTestFile }) => {
describe('upgrade', function () {
loadTestFile(require.resolve('./maps_smoke_tests'));
});
};

View file

@ -0,0 +1,174 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
import { UI_SETTINGS } from '../../../../../src/plugins/data/common';
export default function ({
getPageObjects,
getService,
updateBaselines,
}: FtrProviderContext & { updateBaselines: boolean }) {
const PageObjects = getPageObjects(['common', 'maps', 'header', 'home', 'timePicker']);
const screenshot = getService('screenshots');
const testSubjects = getService('testSubjects');
const kibanaServer = getService('kibanaServer');
const SAMPLE_DATA_RANGE = `[
{
"from": "now-30d",
"to": "now+40d",
"display": "sample data range"
},
{
"from": "now/d",
"to": "now/d",
"display": "Today"
},
{
"from": "now/w",
"to": "now/w",
"display": "This week"
},
{
"from": "now-15m",
"to": "now",
"display": "Last 15 minutes"
},
{
"from": "now-30m",
"to": "now",
"display": "Last 30 minutes"
},
{
"from": "now-1h",
"to": "now",
"display": "Last 1 hour"
},
{
"from": "now-24h",
"to": "now",
"display": "Last 24 hours"
},
{
"from": "now-7d",
"to": "now",
"display": "Last 7 days"
},
{
"from": "now-30d",
"to": "now",
"display": "Last 30 days"
},
{
"from": "now-90d",
"to": "now",
"display": "Last 90 days"
},
{
"from": "now-1y",
"to": "now",
"display": "Last 1 year"
}
]`;
// Only update the baseline images from Jenkins session images after comparing them
// These tests might fail locally because of scaling factors and resolution.
describe('maps smoke tests', function describeIndexTests() {
const spaces = [
{ space: 'default', basePath: '' },
{ space: 'automation', basePath: 's/automation' },
];
before(async () => {
await kibanaServer.uiSettings.update({
[UI_SETTINGS.TIMEPICKER_QUICK_RANGES]: SAMPLE_DATA_RANGE,
});
});
spaces.forEach(({ space, basePath }) => {
describe('space ' + space + ' ecommerce', () => {
before(async () => {
await PageObjects.common.navigateToActualUrl('home', '/tutorial_directory/sampleData', {
basePath,
});
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.home.addSampleDataSet('ecommerce');
await PageObjects.maps.loadSavedMap('[eCommerce] Orders by Country');
await PageObjects.maps.toggleLayerVisibility('Road map');
await PageObjects.maps.toggleLayerVisibility('United Kingdom');
await PageObjects.maps.toggleLayerVisibility('France');
await PageObjects.maps.toggleLayerVisibility('United States');
await PageObjects.maps.toggleLayerVisibility('World Countries');
await PageObjects.timePicker.setCommonlyUsedTime('sample_data range');
await PageObjects.maps.enterFullScreen();
await PageObjects.maps.closeLegend();
const mapContainerElement = await testSubjects.find('mapContainer');
await mapContainerElement.moveMouseTo({ xOffset: 0, yOffset: 0 });
});
it('should load layers', async () => {
const percentDifference = await screenshot.compareAgainstBaseline(
'ecommerce_map',
updateBaselines
);
expect(percentDifference).to.be.lessThan(0.02);
});
});
describe('space ' + space + ' flights', () => {
before(async () => {
await PageObjects.common.navigateToActualUrl('home', '/tutorial_directory/sampleData', {
basePath,
});
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.home.addSampleDataSet('flights');
await PageObjects.maps.loadSavedMap('[Flights] Origin and Destination Flight Time');
await PageObjects.maps.toggleLayerVisibility('Road map');
await PageObjects.timePicker.setCommonlyUsedTime('sample_data range');
await PageObjects.maps.enterFullScreen();
await PageObjects.maps.closeLegend();
const mapContainerElement = await testSubjects.find('mapContainer');
await mapContainerElement.moveMouseTo({ xOffset: 0, yOffset: 0 });
});
it('should load saved object and display layers', async () => {
const percentDifference = await screenshot.compareAgainstBaseline(
'flights_map',
updateBaselines
);
expect(percentDifference).to.be.lessThan(0.02);
});
});
describe('space ' + space + ' web logs', () => {
before(async () => {
await PageObjects.common.navigateToActualUrl('home', '/tutorial_directory/sampleData', {
basePath,
});
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.home.addSampleDataSet('logs');
await PageObjects.maps.loadSavedMap('[Logs] Total Requests and Bytes');
await PageObjects.maps.toggleLayerVisibility('Road map');
await PageObjects.maps.toggleLayerVisibility('Total Requests by Country');
await PageObjects.timePicker.setCommonlyUsedTime('sample_data range');
await PageObjects.maps.enterFullScreen();
await PageObjects.maps.closeLegend();
const mapContainerElement = await testSubjects.find('mapContainer');
await mapContainerElement.moveMouseTo({ xOffset: 0, yOffset: 0 });
});
it('should load saved object and display layers', async () => {
const percentDifference = await screenshot.compareAgainstBaseline(
'web_logs_map',
updateBaselines
);
expect(percentDifference).to.be.lessThan(0.02);
});
});
});
});
}

View file

@ -0,0 +1,12 @@
/*
* 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.
*/
export default ({ loadTestFile }) => {
describe('upgrade', function () {
loadTestFile(require.resolve('./reporting_smoke_tests'));
});
};

View file

@ -0,0 +1,89 @@
/*
* 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 expect from '@kbn/expect';
import { parse } from 'url';
import { FtrProviderContext } from '../../ftr_provider_context';
import { ReportingUsageStats } from '../../reporting_services';
interface UsageStats {
reporting: ReportingUsageStats;
}
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const reportingAPI = getService('reportingAPI');
const usageAPI = getService('usageAPI');
const find = getService('find');
const browser = getService('browser');
const PageObjects = getPageObjects(['common', 'header', 'home', 'dashboard', 'share']);
const testSubjects = getService('testSubjects');
const spaces = [
{ space: 'default', basePath: '' },
{ space: 'automation', basePath: 's/automation' },
];
const reportingTests = [
{ name: 'flights', type: 'pdf', link: 'PDF Reports' },
{ name: 'flights', type: 'pdf_optimize', link: 'PDF Reports' },
{ name: 'flights', type: 'png', link: 'PNG Reports' },
{ name: 'logs', type: 'pdf', link: 'PDF Reports' },
{ name: 'logs', type: 'pdf_optimize', link: 'PDF Reports' },
{ name: 'logs', type: 'png', link: 'PNG Reports' },
{ name: 'ecommerce', type: 'pdf', link: 'PDF Reports' },
{ name: 'ecommerce', type: 'pdf_optimize', link: 'PDF Reports' },
{ name: 'ecommerce', type: 'png', link: 'PNG Reports' },
];
describe('reporting smoke tests', () => {
let completedReportCount: number;
let usage: UsageStats;
describe('initial state', () => {
before(async () => {
usage = (await usageAPI.getUsageStats()) as UsageStats;
});
it('shows reporting as available and enabled', async () => {
expect(usage.reporting.available).to.be(true);
expect(usage.reporting.enabled).to.be(true);
});
});
spaces.forEach(({ space, basePath }) => {
describe('generate report space ' + space, () => {
before(async () => {
usage = (await usageAPI.getUsageStats()) as UsageStats;
completedReportCount = reportingAPI.getCompletedReportCount(usage);
});
beforeEach(async () => {
await PageObjects.common.navigateToActualUrl('home', '/tutorial_directory/sampleData', {
basePath,
});
await PageObjects.header.waitUntilLoadingHasFinished();
});
reportingTests.forEach(({ name, type, link }) => {
it('name ' + name + ' type ' + type, async () => {
await PageObjects.home.launchSampleDashboard(name);
await PageObjects.share.openShareMenuItem(link);
if (type === 'pdf_optimize') {
await testSubjects.click('usePrintLayout');
}
const postUrl = await find.byXPath(`//button[descendant::*[text()='Copy POST URL']]`);
await postUrl.click();
const url = await browser.getClipboardValue();
await reportingAPI.expectAllJobsToFinishSuccessfully(
await Promise.all([
reportingAPI.postJob(parse(url).pathname + '?' + parse(url).query),
])
);
usage = (await usageAPI.getUsageStats()) as UsageStats;
reportingAPI.expectCompletedReportCount(usage, completedReportCount + 1);
});
});
});
});
});
}

View file

@ -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 { FtrConfigProviderContext } from '@kbn/test/types/ftr';
import { pageObjects } from './page_objects';
import { ReportingAPIProvider } from './reporting_services';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const apiConfig = await readConfigFile(require.resolve('../api_integration/config'));
const functionalConfig = await readConfigFile(require.resolve('../functional/config'));
return {
...functionalConfig.getAll(),
testFiles: [
require.resolve('./apps/canvas'),
require.resolve('./apps/dashboard'),
require.resolve('./apps/maps'),
require.resolve('./apps/reporting'),
],
pageObjects,
services: {
...apiConfig.get('services'),
...functionalConfig.get('services'),
reportingAPI: ReportingAPIProvider,
},
junit: {
reportName: 'Upgrade Tests',
},
security: {
disableTestUser: true,
},
};
}

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { GenericFtrProviderContext } from '@kbn/test/types/ftr';
import { pageObjects } from './page_objects';
import { services } from './services';
export type FtrProviderContext = GenericFtrProviderContext<typeof services, typeof pageObjects>;

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { pageObjects } from '../functional/page_objects';
export { pageObjects };

View file

@ -0,0 +1,213 @@
/*
* 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 expect from '@kbn/expect';
import { indexTimestamp } from '../../plugins/reporting/server/lib/store/index_timestamp';
import { services as xpackServices } from '../functional/services';
import { services as apiIntegrationServices } from '../api_integration/services';
import { FtrProviderContext } from './ftr_provider_context';
interface PDFAppCounts {
app: {
[appName: string]: number;
};
layout: {
[layoutType: string]: number;
};
}
export interface ReportingUsageStats {
available: boolean;
enabled: boolean;
total: number;
last_7_days: {
total: number;
printable_pdf: PDFAppCounts;
[jobType: string]: any;
};
printable_pdf: PDFAppCounts;
status: any;
[jobType: string]: any;
}
interface UsageStats {
reporting: ReportingUsageStats;
}
function removeWhitespace(str: string) {
return str.replace(/\s/g, '');
}
export function ReportingAPIProvider({ getService }: FtrProviderContext) {
const log = getService('log');
const supertest = getService('supertest');
const esSupertest = getService('esSupertest');
const retry = getService('retry');
return {
async waitForJobToFinish(downloadReportPath: string) {
log.debug(`Waiting for job to finish: ${downloadReportPath}`);
const JOB_IS_PENDING_CODE = 503;
const statusCode = await new Promise((resolve) => {
const intervalId = setInterval(async () => {
const response = (await supertest
.get(downloadReportPath)
.responseType('blob')
.set('kbn-xsrf', 'xxx')) as any;
if (response.statusCode === 503) {
log.debug(`Report at path ${downloadReportPath} is pending`);
} else if (response.statusCode === 200) {
log.debug(`Report at path ${downloadReportPath} is complete`);
} else {
log.debug(`Report at path ${downloadReportPath} returned code ${response.statusCode}`);
}
if (response.statusCode !== JOB_IS_PENDING_CODE) {
clearInterval(intervalId);
resolve(response.statusCode);
}
}, 1500);
});
expect(statusCode).to.be(200);
},
async expectAllJobsToFinishSuccessfully(jobPaths: string[]) {
await Promise.all(
jobPaths.map(async (path) => {
await this.waitForJobToFinish(path);
})
);
},
async postJob(apiPath: string): Promise<string> {
log.debug(`ReportingAPI.postJob(${apiPath})`);
const { body } = await supertest
.post(removeWhitespace(apiPath))
.set('kbn-xsrf', 'xxx')
.expect(200);
return body.path;
},
async postJobJSON(apiPath: string, jobJSON: object = {}): Promise<string> {
log.debug(`ReportingAPI.postJobJSON((${apiPath}): ${JSON.stringify(jobJSON)})`);
const { body } = await supertest.post(apiPath).set('kbn-xsrf', 'xxx').send(jobJSON);
return body.path;
},
/**
*
* @return {Promise<Function>} A function to call to clean up the index alias that was added.
*/
async coerceReportsIntoExistingIndex(indexName: string) {
log.debug(`ReportingAPI.coerceReportsIntoExistingIndex(${indexName})`);
// Adding an index alias coerces the report to be generated on an existing index which means any new
// index schema won't be applied. This is important if a point release updated the schema. Reports may still
// be inserted into an existing index before the new schema is applied.
const timestampForIndex = indexTimestamp('week', '.');
await esSupertest
.post('/_aliases')
.send({
actions: [
{
add: { index: indexName, alias: `.reporting-${timestampForIndex}` },
},
],
})
.expect(200);
return async () => {
await esSupertest
.post('/_aliases')
.send({
actions: [
{
remove: { index: indexName, alias: `.reporting-${timestampForIndex}` },
},
],
})
.expect(200);
};
},
async deleteAllReports() {
log.debug('ReportingAPI.deleteAllReports');
// ignores 409 errs and keeps retrying
await retry.tryForTime(5000, async () => {
await esSupertest
.post('/.reporting*/_delete_by_query')
.send({ query: { match_all: {} } })
.expect(200);
});
},
expectRecentPdfAppStats(stats: UsageStats, app: string, count: number) {
expect(stats.reporting.last_7_days.printable_pdf.app[app]).to.be(count);
},
expectAllTimePdfAppStats(stats: UsageStats, app: string, count: number) {
expect(stats.reporting.printable_pdf.app[app]).to.be(count);
},
expectRecentPdfLayoutStats(stats: UsageStats, layout: string, count: number) {
expect(stats.reporting.last_7_days.printable_pdf.layout[layout]).to.be(count);
},
expectAllTimePdfLayoutStats(stats: UsageStats, layout: string, count: number) {
expect(stats.reporting.printable_pdf.layout[layout]).to.be(count);
},
expectRecentJobTypeTotalStats(stats: UsageStats, jobType: string, count: number) {
expect(stats.reporting.last_7_days[jobType].total).to.be(count);
},
expectAllTimeJobTypeTotalStats(stats: UsageStats, jobType: string, count: number) {
expect(stats.reporting[jobType].total).to.be(count);
},
getCompletedReportCount(stats: UsageStats) {
return stats.reporting.status.completed;
},
expectCompletedReportCount(stats: UsageStats, count: number) {
expect(this.getCompletedReportCount(stats)).to.be(count);
},
getRecentPdfAppStats(stats: UsageStats, app: string) {
return stats.reporting.last_7_days.printable_pdf.app[app];
},
getAllTimePdfAppStats(stats: UsageStats, app: string) {
return stats.reporting.printable_pdf.app[app];
},
getRecentPdfLayoutStats(stats: UsageStats, layout: string) {
return stats.reporting.last_7_days.printable_pdf.layout[layout];
},
getAllTimePdfLayoutStats(stats: UsageStats, layout: string) {
return stats.reporting.printable_pdf.layout[layout];
},
getRecentJobTypeTotalStats(stats: UsageStats, jobType: string) {
return stats.reporting.last_7_days[jobType].total;
},
getAllTimeJobTypeTotalStats(stats: UsageStats, jobType: string) {
return stats.reporting[jobType].total;
},
};
}
export const services = {
...xpackServices,
supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth,
usageAPI: apiIntegrationServices.usageAPI,
reportingAPI: ReportingAPIProvider,
};

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { services as functionalServices } from '../functional/services';
import { services as reportingServices } from './reporting_services';
export const services = {
...functionalServices,
...reportingServices,
};