Skip to content

Commit

Permalink
[Reporting/Test] Add Functional test for download CSV (#65401) (#66100)
Browse files Browse the repository at this point in the history
* [Reporting/Test] Add Functional test for download CSV

* add todo

* add fs.existsSync check to find download

* debug

* handle timeout

* validate toast

* different way of getting repo_root

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
tsullivan and elasticmachine authored May 13, 2020
1 parent 2d08f42 commit 0a07c8e
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 121 deletions.
2 changes: 1 addition & 1 deletion test/functional/services/find.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ export async function FindProvider({ getService }: FtrProviderContext) {
if (isDisplayed) {
return descendant;
} else {
throw new Error('Element is not displayed');
throw new Error(`Element "${selector}" is not displayed`);
}
}

Expand Down
68 changes: 68 additions & 0 deletions x-pack/test/functional/apps/dashboard/reporting/download_csv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { REPO_ROOT } from '@kbn/dev-utils';
import expect from '@kbn/expect';
import fs from 'fs';
import path from 'path';
import * as Rx from 'rxjs';
import { filter, first, map, timeout } from 'rxjs/operators';
import { FtrProviderContext } from '../../../ftr_provider_context';

const csvPath = path.resolve(REPO_ROOT, 'target/functional-tests/downloads/Ecommerce Data.csv');

export default function({ getService, getPageObjects }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const browser = getService('browser');
const dashboardPanelActions = getService('dashboardPanelActions');
const log = getService('log');
const testSubjects = getService('testSubjects');
const PageObjects = getPageObjects(['reporting', 'common', 'dashboard']);

describe('Reporting Download CSV', () => {
before('initialize tests', async () => {
log.debug('ReportingPage:initTests');
await esArchiver.loadIfNeeded('reporting/ecommerce');
await esArchiver.loadIfNeeded('reporting/ecommerce_kibana');
await browser.setWindowSize(1600, 850);
});

after('clean up archives and previous file download', async () => {
await esArchiver.unload('reporting/ecommerce');
await esArchiver.unload('reporting/ecommerce_kibana');
try {
fs.unlinkSync(csvPath);
} catch (e) {
// nothing to worry
}
});

it('Downloads a CSV export of a saved search panel', async function() {
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.loadSavedDashboard('Ecom Dashboard');
const savedSearchPanel = await testSubjects.find('embeddablePanelHeading-EcommerceData');
await dashboardPanelActions.toggleContextMenu(savedSearchPanel);

await testSubjects.existOrFail('embeddablePanelAction-downloadCsvReport'); // wait for the full panel to display or else the test runner could click the wrong option!
await testSubjects.click('embeddablePanelAction-downloadCsvReport');
await testSubjects.existOrFail('csvDownloadStarted'); // validate toast panel

// check every 100ms for the file to exist in the download dir
// just wait up to 5 seconds
const success$ = Rx.interval(100).pipe(
map(() => fs.existsSync(csvPath)),
filter(value => value === true),
first(),
timeout(5000)
);

const fileExists = await success$.toPromise();
expect(fileExists).to.be(true);

// no need to validate download contents, API Integration tests do that some different variations
});
});
}
122 changes: 4 additions & 118 deletions x-pack/test/functional/apps/dashboard/reporting/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,125 +3,11 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import expect from '@kbn/expect';
import fs from 'fs';
import path from 'path';
import { promisify } from 'util';
import { FtrProviderContext } from '../../../ftr_provider_context';
import { checkIfPngsMatch } from './lib/compare_pngs';

const writeFileAsync = promisify(fs.writeFile);
const mkdirAsync = promisify(fs.mkdir);

const REPORTS_FOLDER = path.resolve(__dirname, 'reports');

export default function({ getService, getPageObjects }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const browser = getService('browser');
const log = getService('log');
const config = getService('config');
const PageObjects = getPageObjects(['reporting', 'common', 'dashboard']);

describe('Reporting', () => {
before('initialize tests', async () => {
log.debug('ReportingPage:initTests');
await esArchiver.loadIfNeeded('reporting/ecommerce');
await esArchiver.loadIfNeeded('reporting/ecommerce_kibana');
await browser.setWindowSize(1600, 850);
});
after('clean up archives', async () => {
await esArchiver.unload('reporting/ecommerce');
await esArchiver.unload('reporting/ecommerce_kibana');
});

describe('Print PDF button', () => {
it('is not available if new', async () => {
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.reporting.openPdfReportingPanel();
expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be('true');
});

it('becomes available when saved', async () => {
await PageObjects.dashboard.saveDashboard('My PDF Dashboard');
await PageObjects.reporting.openPdfReportingPanel();
expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be(null);
});
});

describe('Print Layout', () => {
it('downloads a PDF file', async function() {
// Generating and then comparing reports can take longer than the default 60s timeout because the comparePngs
// function is taking about 15 seconds per comparison in jenkins.
this.timeout(300000);
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.loadSavedDashboard('Ecom Dashboard');
await PageObjects.reporting.openPdfReportingPanel();
await PageObjects.reporting.checkUsePrintLayout();
await PageObjects.reporting.clickGenerateReportButton();

const url = await PageObjects.reporting.getReportURL(60000);
const res = await PageObjects.reporting.getResponse(url);

expect(res.statusCode).to.equal(200);
expect(res.headers['content-type']).to.equal('application/pdf');
});
});

describe('Print PNG button', () => {
it('is not available if new', async () => {
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.reporting.openPngReportingPanel();
expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be('true');
});

it('becomes available when saved', async () => {
await PageObjects.dashboard.saveDashboard('My PNG Dash');
await PageObjects.reporting.openPngReportingPanel();
expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be(null);
});
});

describe('Preserve Layout', () => {
it('matches baseline report', async function() {
const writeSessionReport = async (name: string, rawPdf: Buffer, reportExt: string) => {
const sessionDirectory = path.resolve(REPORTS_FOLDER, 'session');
await mkdirAsync(sessionDirectory, { recursive: true });
const sessionReportPath = path.resolve(sessionDirectory, `${name}.${reportExt}`);
await writeFileAsync(sessionReportPath, rawPdf);
return sessionReportPath;
};
const getBaselineReportPath = (fileName: string, reportExt: string) => {
const baselineFolder = path.resolve(REPORTS_FOLDER, 'baseline');
const fullPath = path.resolve(baselineFolder, `${fileName}.${reportExt}`);
log.debug(`getBaselineReportPath (${fullPath})`);
return fullPath;
};

this.timeout(300000);

await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.loadSavedDashboard('Ecom Dashboard');
await PageObjects.reporting.openPngReportingPanel();
await PageObjects.reporting.forceSharedItemsContainerSize({ width: 1405 });
await PageObjects.reporting.clickGenerateReportButton();
await PageObjects.reporting.removeForceSharedItemsContainerSize();

const url = await PageObjects.reporting.getReportURL(60000);
const reportData = await PageObjects.reporting.getRawPdfReportData(url);
const reportFileName = 'dashboard_preserve_layout';
const sessionReportPath = await writeSessionReport(reportFileName, reportData, 'png');
const percentSimilar = await checkIfPngsMatch(
sessionReportPath,
getBaselineReportPath(reportFileName, 'png'),
config.get('screenshots.directory'),
log
);

expect(percentSimilar).to.be.lessThan(0.1);
});
});
export default function({ loadTestFile }: FtrProviderContext) {
describe('Reporting', function() {
loadTestFile(require.resolve('./screenshots'));
loadTestFile(require.resolve('./download_csv'));
});
}
127 changes: 127 additions & 0 deletions x-pack/test/functional/apps/dashboard/reporting/screenshots.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import expect from '@kbn/expect';
import fs from 'fs';
import path from 'path';
import { promisify } from 'util';
import { FtrProviderContext } from '../../../ftr_provider_context';
import { checkIfPngsMatch } from './lib/compare_pngs';

const writeFileAsync = promisify(fs.writeFile);
const mkdirAsync = promisify(fs.mkdir);

const REPORTS_FOLDER = path.resolve(__dirname, 'reports');

export default function({ getService, getPageObjects }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const browser = getService('browser');
const log = getService('log');
const config = getService('config');
const PageObjects = getPageObjects(['reporting', 'common', 'dashboard']);

describe('Screenshots', () => {
before('initialize tests', async () => {
log.debug('ReportingPage:initTests');
await esArchiver.loadIfNeeded('reporting/ecommerce');
await esArchiver.loadIfNeeded('reporting/ecommerce_kibana');
await browser.setWindowSize(1600, 850);
});
after('clean up archives', async () => {
await esArchiver.unload('reporting/ecommerce');
await esArchiver.unload('reporting/ecommerce_kibana');
});

describe('Print PDF button', () => {
it('is not available if new', async () => {
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.reporting.openPdfReportingPanel();
expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be('true');
});

it('becomes available when saved', async () => {
await PageObjects.dashboard.saveDashboard('My PDF Dashboard');
await PageObjects.reporting.openPdfReportingPanel();
expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be(null);
});
});

describe('Print Layout', () => {
it('downloads a PDF file', async function() {
// Generating and then comparing reports can take longer than the default 60s timeout because the comparePngs
// function is taking about 15 seconds per comparison in jenkins.
this.timeout(300000);
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.loadSavedDashboard('Ecom Dashboard');
await PageObjects.reporting.openPdfReportingPanel();
await PageObjects.reporting.checkUsePrintLayout();
await PageObjects.reporting.clickGenerateReportButton();

const url = await PageObjects.reporting.getReportURL(60000);
const res = await PageObjects.reporting.getResponse(url);

expect(res.statusCode).to.equal(200);
expect(res.headers['content-type']).to.equal('application/pdf');
});
});

describe('Print PNG button', () => {
it('is not available if new', async () => {
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.reporting.openPngReportingPanel();
expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be('true');
});

it('becomes available when saved', async () => {
await PageObjects.dashboard.saveDashboard('My PNG Dash');
await PageObjects.reporting.openPngReportingPanel();
expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be(null);
});
});

describe('Preserve Layout', () => {
it('matches baseline report', async function() {
const writeSessionReport = async (name: string, rawPdf: Buffer, reportExt: string) => {
const sessionDirectory = path.resolve(REPORTS_FOLDER, 'session');
await mkdirAsync(sessionDirectory, { recursive: true });
const sessionReportPath = path.resolve(sessionDirectory, `${name}.${reportExt}`);
await writeFileAsync(sessionReportPath, rawPdf);
return sessionReportPath;
};
const getBaselineReportPath = (fileName: string, reportExt: string) => {
const baselineFolder = path.resolve(REPORTS_FOLDER, 'baseline');
const fullPath = path.resolve(baselineFolder, `${fileName}.${reportExt}`);
log.debug(`getBaselineReportPath (${fullPath})`);
return fullPath;
};

this.timeout(300000);

await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.loadSavedDashboard('Ecom Dashboard');
await PageObjects.reporting.openPngReportingPanel();
await PageObjects.reporting.forceSharedItemsContainerSize({ width: 1405 });
await PageObjects.reporting.clickGenerateReportButton();
await PageObjects.reporting.removeForceSharedItemsContainerSize();

const url = await PageObjects.reporting.getReportURL(60000);
const reportData = await PageObjects.reporting.getRawPdfReportData(url);
const reportFileName = 'dashboard_preserve_layout';
const sessionReportPath = await writeSessionReport(reportFileName, reportData, 'png');
const percentSimilar = await checkIfPngsMatch(
sessionReportPath,
getBaselineReportPath(reportFileName, 'png'),
config.get('screenshots.directory'),
log
);

expect(percentSimilar).to.be.lessThan(0.1);
});
});
});
}
5 changes: 3 additions & 2 deletions x-pack/test/functional/page_objects/reporting_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import { FtrProviderContext } from 'test/functional/ftr_provider_context';
import { parse } from 'url';

export function ReportingPageProvider({ getService, getPageObjects }: FtrProviderContext) {
const retry = getService('retry');
const browser = getService('browser');
const log = getService('log');
const retry = getService('retry');
const testSubjects = getService('testSubjects');
const browser = getService('browser');

const PageObjects = getPageObjects(['common', 'security' as any, 'share', 'timePicker']); // FIXME: Security PageObject is not Typescript

class ReportingPage {
Expand Down

0 comments on commit 0a07c8e

Please sign in to comment.