Skip to content

Commit

Permalink
[Reporting] Baseline capture tests (#113910) (#115934)
Browse files Browse the repository at this point in the history
* added page to reporting example app that contains the capture tests

* first version of PNG capture for test A

* added types file to common

* added data-shared-item attr to image, also added capture menu items

* fix image CSS by providing a fixed width and height

* explicitly add layout for print, does not seem to do anything though?

* added magic numbers of image sizes

* added reporting examples test folder

* first version of capture test for generating and comparing PNGs

* added PNG service and PNG baseline fixture

* added pdf-to-img dev dependency

* refactor compare_pngs to accept a buffer

* added comment to interface

* png service -> compare images service

* export image compare service

* added test for pdf export

* clean up log

* minor fixes and added pdf print optimized test

* added pdf and pdf print fixtures

* refactor lib function name

* Update difference thresholds

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

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
jloleysens and kibanamachine authored Oct 21, 2021
1 parent 8a643cc commit a100416
Show file tree
Hide file tree
Showing 23 changed files with 710 additions and 47 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,7 @@
"ora": "^4.0.4",
"parse-link-header": "^1.0.1",
"pbf": "3.2.1",
"pdf-to-img": "^1.1.1",
"pirates": "^4.0.1",
"pixelmatch": "^5.1.0",
"postcss": "^7.0.32",
Expand Down
52 changes: 43 additions & 9 deletions test/functional/services/lib/compare_pngs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,56 @@ import { parse, join } from 'path';
import Jimp from 'jimp';
import { ToolingLog } from '@kbn/dev-utils';

interface PngDescriptor {
path: string;

/**
* If a buffer is provided this will avoid the extra step of reading from disk
*/
buffer?: Buffer;
}

const toDescriptor = (imageInfo: string | PngDescriptor): PngDescriptor => {
if (typeof imageInfo === 'string') {
return { path: imageInfo };
}
return {
...imageInfo,
};
};

/**
* Override Jimp types that expect to be mapped to either string or buffer even though Jimp
* accepts both https://www.npmjs.com/package/jimp#basic-usage.
*/
const toJimp = (imageInfo: string | Buffer): Promise<Jimp> => {
return (Jimp.read as (value: string | Buffer) => Promise<Jimp>)(imageInfo);
};

/**
* Comparing pngs and writing result to provided directory
*
* @param sessionPath
* @param baselinePath
* @param session
* @param baseline
* @param diffPath
* @param sessionDirectory
* @param log
* @returns Percent
*/
export async function comparePngs(
sessionPath: string,
baselinePath: string,
sessionInfo: string | PngDescriptor,
baselineInfo: string | PngDescriptor,
diffPath: string,
sessionDirectory: string,
log: ToolingLog
) {
log.debug(`comparePngs: ${sessionPath} vs ${baselinePath}`);
const session = (await Jimp.read(sessionPath)).clone();
const baseline = (await Jimp.read(baselinePath)).clone();
const sessionDescriptor = toDescriptor(sessionInfo);
const baselineDescriptor = toDescriptor(baselineInfo);

log.debug(`comparePngs: ${sessionDescriptor.path} vs ${baselineDescriptor.path}`);

const session = (await toJimp(sessionDescriptor.buffer ?? sessionDescriptor.path)).clone();
const baseline = (await toJimp(baselineDescriptor.buffer ?? baselineDescriptor.path)).clone();

if (
session.bitmap.width !== baseline.bitmap.width ||
Expand Down Expand Up @@ -63,8 +93,12 @@ export async function comparePngs(
image.write(diffPath);

// For debugging purposes it'll help to see the resized images and how they compare.
session.write(join(sessionDirectory, `${parse(sessionPath).name}-session-resized.png`));
baseline.write(join(sessionDirectory, `${parse(baselinePath).name}-baseline-resized.png`));
session.write(
join(sessionDirectory, `${parse(sessionDescriptor.path).name}-session-resized.png`)
);
baseline.write(
join(sessionDirectory, `${parse(baselineDescriptor.path).name}-baseline-resized.png`)
);
}
return percent;
}
2 changes: 2 additions & 0 deletions x-pack/examples/reporting_example/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
export const PLUGIN_ID = 'reportingExample';
export const PLUGIN_NAME = 'reportingExample';

export { MyForwardableState } from './types';

export {
REPORTING_EXAMPLE_LOCATOR_ID,
ReportingExampleLocatorDefinition,
Expand Down
6 changes: 4 additions & 2 deletions x-pack/examples/reporting_example/common/locator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { SerializableRecord } from '@kbn/utility-types';
import type { LocatorDefinition } from '../../../../src/plugins/share/public';
import { PLUGIN_ID } from '../common';
import type { MyForwardableState } from '../public/types';

export const REPORTING_EXAMPLE_LOCATOR_ID = 'REPORTING_EXAMPLE_LOCATOR_ID';

Expand All @@ -20,10 +21,11 @@ export class ReportingExampleLocatorDefinition implements LocatorDefinition<{}>
'1.0.0': (state: {}) => ({ ...state, migrated: true }),
};

public readonly getLocation = async (params: {}) => {
public readonly getLocation = async (params: MyForwardableState) => {
const path = Boolean(params.captureTest) ? '/captureTest' : '/';
return {
app: PLUGIN_ID,
path: '/',
path,
state: params,
};
};
Expand Down
13 changes: 13 additions & 0 deletions x-pack/examples/reporting_example/common/types.ts
Original file line number Diff line number Diff line change
@@ -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 type { Ensure, SerializableRecord } from '@kbn/utility-types';

export type MyForwardableState = Ensure<
SerializableRecord & { captureTest: 'A' },
SerializableRecord
>;
22 changes: 14 additions & 8 deletions x-pack/examples/reporting_example/public/application.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,29 @@

import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, Switch } from 'react-router-dom';
import { AppMountParameters, CoreStart } from '../../../../src/core/public';
import { ReportingExampleApp } from './components/app';
import { CaptureTest } from './containers/capture_test';
import { Main } from './containers/main';
import { ApplicationContextProvider } from './application_context';
import { SetupDeps, StartDeps, MyForwardableState } from './types';
import { ROUTES } from './constants';

export const renderApp = (
coreStart: CoreStart,
deps: Omit<StartDeps & SetupDeps, 'developerExamples'>,
{ appBasePath, element }: AppMountParameters, // FIXME: appBasePath is deprecated
{ appBasePath, element, history }: AppMountParameters, // FIXME: appBasePath is deprecated
forwardedParams: MyForwardableState
) => {
ReactDOM.render(
<ReportingExampleApp
basename={appBasePath}
{...coreStart}
{...deps}
forwardedParams={forwardedParams}
/>,
<ApplicationContextProvider forwardedState={forwardedParams}>
<Router history={history}>
<Switch>
<Route path={ROUTES.captureTest} exact render={() => <CaptureTest />} />
<Route render={() => <Main basename={appBasePath} {...coreStart} {...deps} />} />
</Switch>
</Router>
</ApplicationContextProvider>,
element
);

Expand Down
33 changes: 33 additions & 0 deletions x-pack/examples/reporting_example/public/application_context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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 React, { useContext, createContext, FC } from 'react';

import type { MyForwardableState } from './types';

interface ContextValue {
forwardedState?: MyForwardableState;
}

const ApplicationContext = createContext<undefined | ContextValue>(undefined);

export const ApplicationContextProvider: FC<{ forwardedState: ContextValue['forwardedState'] }> = ({
forwardedState,
children,
}) => {
return (
<ApplicationContext.Provider value={{ forwardedState }}>{children}</ApplicationContext.Provider>
);
};

export const useApplicationContext = (): ContextValue => {
const ctx = useContext(ApplicationContext);
if (!ctx) {
throw new Error('useApplicationContext called outside of ApplicationContext!');
}
return ctx;
};
8 changes: 8 additions & 0 deletions x-pack/examples/reporting_example/public/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* 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 { TestImageA } from './test_image_a';

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions x-pack/examples/reporting_example/public/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* 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.
*/

// Values based on A4 page size
export const VIS = {
width: 1950 / 2,
height: 1200 / 2,
};

export const ROUTES = {
captureTest: '/captureTest',
main: '/',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.reportingExample {
&__captureContainer {
display: flex;
flex-direction: column;
align-items: center;

margin-top: $euiSizeM;
margin-bottom: $euiSizeM;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* 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 type { FunctionComponent } from 'react';
import React from 'react';
import { useHistory } from 'react-router-dom';
import { parsePath } from 'history';
import {
EuiTabbedContent,
EuiTabbedContentTab,
EuiSpacer,
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiPage,
EuiPageHeader,
EuiPageBody,
EuiPageContent,
EuiPageContentBody,
} from '@elastic/eui';

import { TestImageA } from '../components';
import { useApplicationContext } from '../application_context';
import { MyForwardableState } from '../types';
import { ROUTES } from '../constants';

import './capture_test.scss';

const ItemsContainer: FunctionComponent<{ count: string }> = ({ count, children }) => (
<div
className="reportingExample__captureContainer"
data-shared-items-container
data-shared-items-count={count}
>
{children}
</div>
);

const tabs: Array<EuiTabbedContentTab & { id: MyForwardableState['captureTest'] }> = [
{
id: 'A',
name: 'Test A',
content: (
<ItemsContainer count="4">
<TestImageA />
<TestImageA />
<TestImageA />
<TestImageA />
</ItemsContainer>
),
},
];

export const CaptureTest: FunctionComponent = () => {
const { forwardedState } = useApplicationContext();
const tabToRender = forwardedState?.captureTest;
const history = useHistory();
return (
<EuiPage>
<EuiPageBody>
<EuiPageContent>
<EuiPageHeader>
<EuiFlexGroup alignItems="center" justifyContent="flexEnd">
<EuiFlexItem grow={false}>
<EuiButtonEmpty
iconType="arrowLeft"
href={history.createHref(parsePath(ROUTES.main))}
>
Back to main
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageHeader>
<EuiPageContentBody>
<EuiSpacer />
<EuiTabbedContent
tabs={tabs}
initialSelectedTab={
tabToRender ? tabs.find((tab) => tab.id === tabToRender) : undefined
}
/>
</EuiPageContentBody>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
};
Loading

0 comments on commit a100416

Please sign in to comment.