mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Screenshotting] Add captureBeyondViewport: false to workaround a res… (#131877)
This commit is contained in:
parent
6375d90683
commit
8cf334468e
24 changed files with 615 additions and 144 deletions
|
@ -35,12 +35,6 @@ export interface ElementPosition {
|
|||
};
|
||||
}
|
||||
|
||||
export interface Viewport {
|
||||
zoom: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
interface OpenOptions {
|
||||
context?: Context;
|
||||
headers: Headers;
|
||||
|
@ -203,7 +197,7 @@ export class HeadlessChromiumDriver {
|
|||
}
|
||||
|
||||
/*
|
||||
* Call Page.screenshot and return a base64-encoded string of the image
|
||||
* Receive a PNG buffer of the page screenshot from Chromium
|
||||
*/
|
||||
async screenshot(elementPosition: ElementPosition): Promise<Buffer | undefined> {
|
||||
const { boundingClientRect, scroll } = elementPosition;
|
||||
|
@ -214,6 +208,7 @@ export class HeadlessChromiumDriver {
|
|||
height: boundingClientRect.height,
|
||||
width: boundingClientRect.width,
|
||||
},
|
||||
captureBeyondViewport: false, // workaround for an internal resize. See: https://github.com/puppeteer/puppeteer/issues/7043
|
||||
});
|
||||
|
||||
if (Buffer.isBuffer(screenshot)) {
|
||||
|
@ -263,14 +258,18 @@ export class HeadlessChromiumDriver {
|
|||
await this.page.waitForFunction(fn, { timeout, polling: WAIT_FOR_DELAY_MS }, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting the viewport is required to ensure that all capture elements are visible: anything not in the
|
||||
* viewport can not be captured.
|
||||
*/
|
||||
async setViewport(
|
||||
{ width: _width, height: _height, zoom }: Viewport,
|
||||
{ width: _width, height: _height, zoom }: { zoom: number; width: number; height: number },
|
||||
logger: Logger
|
||||
): Promise<void> {
|
||||
const width = Math.floor(_width);
|
||||
const height = Math.floor(_height);
|
||||
|
||||
logger.debug(`Setting viewport to: width=${width} height=${height} zoom=${zoom}`);
|
||||
logger.debug(`Setting viewport to: width=${width} height=${height} scaleFactor=${zoom}`);
|
||||
|
||||
await this.page.setViewport({
|
||||
width,
|
||||
|
|
|
@ -5,13 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
import type { ScreenshotModePluginSetup } from '@kbn/screenshot-mode-plugin/server';
|
||||
import puppeteer from 'puppeteer';
|
||||
import * as Rx from 'rxjs';
|
||||
import { mergeMap, take } from 'rxjs/operators';
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
import type { ScreenshotModePluginSetup } from '@kbn/screenshot-mode-plugin/server';
|
||||
import { DEFAULT_VIEWPORT, HeadlessChromiumDriverFactory } from '.';
|
||||
import { ConfigType } from '../../../config';
|
||||
import { HeadlessChromiumDriverFactory, DEFAULT_VIEWPORT } from '.';
|
||||
|
||||
jest.mock('puppeteer');
|
||||
|
||||
|
|
|
@ -5,31 +5,30 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
import type { ScreenshotModePluginSetup } from '@kbn/screenshot-mode-plugin/server';
|
||||
import { getDataPath } from '@kbn/utils';
|
||||
import { spawn } from 'child_process';
|
||||
import _ from 'lodash';
|
||||
import del from 'del';
|
||||
import fs from 'fs';
|
||||
import { uniq } from 'lodash';
|
||||
import path from 'path';
|
||||
import puppeteer, { Browser, ConsoleMessage, HTTPRequest, Page } from 'puppeteer';
|
||||
import puppeteer, { Browser, ConsoleMessage, HTTPRequest, Page, Viewport } from 'puppeteer';
|
||||
import { createInterface } from 'readline';
|
||||
import * as Rx from 'rxjs';
|
||||
import {
|
||||
catchError,
|
||||
concatMap,
|
||||
ignoreElements,
|
||||
map,
|
||||
concatMap,
|
||||
mergeMap,
|
||||
reduce,
|
||||
takeUntil,
|
||||
tap,
|
||||
} from 'rxjs/operators';
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
import type { ScreenshotModePluginSetup } from '@kbn/screenshot-mode-plugin/server';
|
||||
import { ConfigType } from '../../../config';
|
||||
import { errors } from '../../../../common';
|
||||
import { getChromiumDisconnectedError } from '..';
|
||||
import { errors } from '../../../../common';
|
||||
import { ConfigType } from '../../../config';
|
||||
import { safeChildProcess } from '../../safe_child_process';
|
||||
import { HeadlessChromiumDriver } from '../driver';
|
||||
import { args } from './args';
|
||||
|
@ -37,12 +36,7 @@ import { getMetrics, PerformanceMetrics } from './metrics';
|
|||
|
||||
interface CreatePageOptions {
|
||||
browserTimezone?: string;
|
||||
defaultViewport: {
|
||||
/** Size in pixels */
|
||||
width?: number;
|
||||
/** Size in pixels */
|
||||
height?: number;
|
||||
};
|
||||
defaultViewport: { width?: number };
|
||||
openUrlTimeout: number;
|
||||
}
|
||||
|
||||
|
@ -63,10 +57,16 @@ interface ClosePageResult {
|
|||
metrics?: PerformanceMetrics;
|
||||
}
|
||||
|
||||
export const DEFAULT_VIEWPORT = {
|
||||
width: 1950,
|
||||
height: 1200,
|
||||
};
|
||||
/**
|
||||
* Size of the desired initial viewport. This is needed to render the app before elements load into their
|
||||
* layout. Once the elements are positioned, the viewport must be *resized* to include the entire element container.
|
||||
*/
|
||||
export const DEFAULT_VIEWPORT: Required<Pick<Viewport, 'width' | 'height' | 'deviceScaleFactor'>> =
|
||||
{
|
||||
width: 1950,
|
||||
height: 1200,
|
||||
deviceScaleFactor: 1,
|
||||
};
|
||||
|
||||
// Default args used by pptr
|
||||
// https://github.com/puppeteer/puppeteer/blob/13ea347/src/node/Launcher.ts#L168
|
||||
|
@ -138,6 +138,19 @@ export class HeadlessChromiumDriverFactory {
|
|||
|
||||
const chromiumArgs = this.getChromiumArgs();
|
||||
logger.debug(`Chromium launch args set to: ${chromiumArgs}`);
|
||||
|
||||
// We set the viewport width using the client-side layout info to reduce the chances of
|
||||
// browser reflow. Only the window height is expected to be adjusted dramatically
|
||||
// before taking a screenshot, to ensure the elements to capture are contained in the viewport.
|
||||
const viewport = {
|
||||
...DEFAULT_VIEWPORT,
|
||||
width: defaultViewport.width ?? DEFAULT_VIEWPORT.width,
|
||||
};
|
||||
|
||||
logger.debug(
|
||||
`Launching with viewport: width=${viewport.width} height=${viewport.height} scaleFactor=${viewport.deviceScaleFactor}`
|
||||
);
|
||||
|
||||
(async () => {
|
||||
let browser: Browser | undefined;
|
||||
try {
|
||||
|
@ -148,13 +161,7 @@ export class HeadlessChromiumDriverFactory {
|
|||
ignoreHTTPSErrors: true,
|
||||
handleSIGHUP: false,
|
||||
args: chromiumArgs,
|
||||
|
||||
// We optionally set this at page creation to reduce the chances of
|
||||
// browser reflow. In most cases only the height needs to be adjusted
|
||||
// before taking a screenshot.
|
||||
// NOTE: _.defaults assigns to the target object, so we copy it.
|
||||
// NOTE NOTE: _.defaults is not the same as { ...DEFAULT_VIEWPORT, ...defaultViewport }
|
||||
defaultViewport: _.defaults({ ...defaultViewport }, DEFAULT_VIEWPORT),
|
||||
defaultViewport: viewport,
|
||||
env: {
|
||||
TZ: browserTimezone,
|
||||
},
|
||||
|
|
|
@ -6,16 +6,17 @@
|
|||
*/
|
||||
|
||||
import { NEVER, of } from 'rxjs';
|
||||
import type { HeadlessChromiumDriver, HeadlessChromiumDriverFactory } from './chromium';
|
||||
import {
|
||||
CONTEXT_SKIPTELEMETRY,
|
||||
CONTEXT_GETNUMBEROFITEMS,
|
||||
CONTEXT_INJECTCSS,
|
||||
CONTEXT_WAITFORRENDER,
|
||||
CONTEXT_GETTIMERANGE,
|
||||
CONTEXT_DEBUG,
|
||||
CONTEXT_ELEMENTATTRIBUTES,
|
||||
CONTEXT_GETNUMBEROFITEMS,
|
||||
CONTEXT_GETRENDERERRORS,
|
||||
CONTEXT_GETTIMERANGE,
|
||||
CONTEXT_INJECTCSS,
|
||||
CONTEXT_SKIPTELEMETRY,
|
||||
CONTEXT_WAITFORRENDER,
|
||||
} from '../screenshots/constants';
|
||||
import type { HeadlessChromiumDriver, HeadlessChromiumDriverFactory } from './chromium';
|
||||
|
||||
const selectors = {
|
||||
renderComplete: 'renderedSelector',
|
||||
|
@ -40,6 +41,7 @@ function getElementsPositionAndAttributes(title: string, description: string) {
|
|||
export function createMockBrowserDriver(): jest.Mocked<HeadlessChromiumDriver> {
|
||||
const evaluate = jest.fn(async (_, { context }) => {
|
||||
switch (context) {
|
||||
case CONTEXT_DEBUG:
|
||||
case CONTEXT_SKIPTELEMETRY:
|
||||
case CONTEXT_INJECTCSS:
|
||||
case CONTEXT_WAITFORRENDER:
|
||||
|
|
|
@ -48,9 +48,16 @@ export abstract class BaseLayout {
|
|||
pageSizeParams: PageSizeParams
|
||||
): CustomPageSize | PredefinedPageSize;
|
||||
|
||||
// Return the dimensions unscaled dimensions (before multiplying the zoom factor)
|
||||
// driver.setViewport() Adds a top and left margin to the viewport, and then multiplies by the scaling factor
|
||||
public abstract getViewport(itemsCount: number): ViewZoomWidthHeight | null;
|
||||
/**
|
||||
* Return the unscaled dimensions (before multiplying the zoom factor)
|
||||
*
|
||||
* `itemsCount` is only needed for the `print` layout implementation, where the number of items to capture
|
||||
* affects the viewport size
|
||||
*
|
||||
* @param {number} [itemsCount=1] - The number of items to capture. Default is 1.
|
||||
* @returns ViewZoomWidthHeight - Viewport data
|
||||
*/
|
||||
public abstract getViewport(itemsCount?: number): ViewZoomWidthHeight | null;
|
||||
|
||||
public abstract getBrowserZoom(): number;
|
||||
|
||||
|
|
|
@ -61,6 +61,7 @@ describe('Create Layout', () => {
|
|||
},
|
||||
"useReportingBranding": true,
|
||||
"viewport": Object {
|
||||
"deviceScaleFactor": 1,
|
||||
"height": 1200,
|
||||
"width": 1950,
|
||||
},
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
*/
|
||||
|
||||
import { PageOrientation, PredefinedPageSize } from 'pdfmake/interfaces';
|
||||
import type { LayoutParams, LayoutSelectorDictionary } from '../../common/layout';
|
||||
import { LayoutTypes } from '../../common';
|
||||
import type { Layout } from '.';
|
||||
import { DEFAULT_SELECTORS } from '.';
|
||||
import { LayoutTypes } from '../../common';
|
||||
import type { LayoutParams, LayoutSelectorDictionary } from '../../common/layout';
|
||||
import { DEFAULT_VIEWPORT } from '../browsers';
|
||||
import { BaseLayout } from './base_layout';
|
||||
|
||||
|
@ -40,7 +40,7 @@ export class PrintLayout extends BaseLayout implements Layout {
|
|||
return this.zoom;
|
||||
}
|
||||
|
||||
public getViewport(itemsCount: number) {
|
||||
public getViewport(itemsCount = 1) {
|
||||
return {
|
||||
zoom: this.zoom,
|
||||
width: this.viewport.width,
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { APP_WRAPPER_CLASS } from '@kbn/core/server';
|
||||
export const DEFAULT_PAGELOAD_SELECTOR = `.${APP_WRAPPER_CLASS}`;
|
||||
|
||||
// FIXME: cleanup: remove this file and use the EventLogger's Actions enum instead
|
||||
export const CONTEXT_GETNUMBEROFITEMS = 'GetNumberOfItems';
|
||||
export const CONTEXT_INJECTCSS = 'InjectCss';
|
||||
export const CONTEXT_WAITFORRENDER = 'WaitForRender';
|
||||
|
@ -17,3 +18,4 @@ export const CONTEXT_ELEMENTATTRIBUTES = 'ElementPositionAndAttributes';
|
|||
export const CONTEXT_WAITFORELEMENTSTOBEINDOM = 'WaitForElementsToBeInDOM';
|
||||
export const CONTEXT_SKIPTELEMETRY = 'SkipTelemetry';
|
||||
export const CONTEXT_READMETADATA = 'ReadVisualizationsMetadata';
|
||||
export const CONTEXT_DEBUG = 'Debug';
|
||||
|
|
|
@ -32,11 +32,12 @@ describe('getElementPositionAndAttributes', () => {
|
|||
|
||||
elements.forEach((element) =>
|
||||
Object.assign(element, {
|
||||
scrollIntoView: () => {},
|
||||
getBoundingClientRect: () => ({
|
||||
width: parseFloat(element.style.width),
|
||||
height: parseFloat(element.style.height),
|
||||
top: parseFloat(element.style.top),
|
||||
left: parseFloat(element.style.left),
|
||||
y: parseFloat(element.style.top),
|
||||
x: parseFloat(element.style.left),
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
|
|
@ -60,9 +60,8 @@ export const getElementPositionAndAttributes = async (
|
|||
results.push({
|
||||
position: {
|
||||
boundingClientRect: {
|
||||
// modern browsers support x/y, but older ones don't
|
||||
top: boundingClientRect.y || boundingClientRect.top,
|
||||
left: boundingClientRect.x || boundingClientRect.left,
|
||||
top: boundingClientRect.y,
|
||||
left: boundingClientRect.x,
|
||||
width: boundingClientRect.width,
|
||||
height: boundingClientRect.height,
|
||||
},
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
import { loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
import { createMockBrowserDriver } from '../browsers/mock';
|
||||
import { ConfigType } from '../config';
|
||||
import { Layout } from '../layouts';
|
||||
import { createMockLayout } from '../layouts/mock';
|
||||
import { EventLogger } from './event_logger';
|
||||
import { getScreenshots } from './get_screenshots';
|
||||
|
||||
|
@ -31,12 +33,14 @@ describe('getScreenshots', () => {
|
|||
let browser: ReturnType<typeof createMockBrowserDriver>;
|
||||
let eventLogger: EventLogger;
|
||||
let config = {} as ConfigType;
|
||||
let layout: Layout;
|
||||
|
||||
beforeEach(async () => {
|
||||
browser = createMockBrowserDriver();
|
||||
config = { capture: { zoom: 2 } } as ConfigType;
|
||||
eventLogger = new EventLogger(loggingSystemMock.createLogger(), config);
|
||||
browser.evaluate.mockImplementation(({ fn, args }) => (fn as Function)(...args));
|
||||
layout = createMockLayout();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -44,8 +48,8 @@ describe('getScreenshots', () => {
|
|||
});
|
||||
|
||||
it('should return screenshots', async () => {
|
||||
await expect(getScreenshots(browser, eventLogger, elementsPositionAndAttributes)).resolves
|
||||
.toMatchInlineSnapshot(`
|
||||
await expect(getScreenshots(browser, eventLogger, elementsPositionAndAttributes, layout))
|
||||
.resolves.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"data": Object {
|
||||
|
@ -90,7 +94,7 @@ describe('getScreenshots', () => {
|
|||
});
|
||||
|
||||
it('should forward elements positions', async () => {
|
||||
await getScreenshots(browser, eventLogger, elementsPositionAndAttributes);
|
||||
await getScreenshots(browser, eventLogger, elementsPositionAndAttributes, layout);
|
||||
|
||||
expect(browser.screenshot).toHaveBeenCalledTimes(2);
|
||||
expect(browser.screenshot).toHaveBeenNthCalledWith(
|
||||
|
@ -107,7 +111,7 @@ describe('getScreenshots', () => {
|
|||
browser.screenshot.mockResolvedValue(Buffer.from(''));
|
||||
|
||||
await expect(
|
||||
getScreenshots(browser, eventLogger, elementsPositionAndAttributes)
|
||||
getScreenshots(browser, eventLogger, elementsPositionAndAttributes, layout)
|
||||
).rejects.toBeInstanceOf(Error);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,15 +5,59 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { HeadlessChromiumDriver } from '../browsers';
|
||||
import { Logger } from '@kbn/logging';
|
||||
import { HeadlessChromiumDriver } from '../browsers';
|
||||
import { Layout } from '../layouts';
|
||||
import { Actions, EventLogger } from './event_logger';
|
||||
import type { ElementsPositionAndAttribute } from './get_element_position_data';
|
||||
import type { Screenshot } from './types';
|
||||
|
||||
/**
|
||||
* Resize the viewport to contain the element to capture.
|
||||
*
|
||||
* @async
|
||||
* @param {HeadlessChromiumDriver} browser - used for its methods to control the page
|
||||
* @param {ElementsPositionAndAttribute['position']} position - position data for the element to capture
|
||||
* @param {Layout} layout - used for client-side layout data from the job params
|
||||
* @param {Logger} logger
|
||||
*/
|
||||
const resizeViewport = async (
|
||||
browser: HeadlessChromiumDriver,
|
||||
position: ElementsPositionAndAttribute['position'],
|
||||
layout: Layout,
|
||||
logger: Logger
|
||||
) => {
|
||||
const { boundingClientRect, scroll } = position;
|
||||
|
||||
// Using width from the layout is preferred, it avoids the elements moving around horizontally,
|
||||
// which would invalidate the position data that was passed in.
|
||||
const width = layout.width || boundingClientRect.left + scroll.x + boundingClientRect.width;
|
||||
|
||||
await browser.setViewport(
|
||||
{
|
||||
width,
|
||||
height: boundingClientRect.top + scroll.y + boundingClientRect.height,
|
||||
zoom: layout.getBrowserZoom(),
|
||||
},
|
||||
logger
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get screenshots of multiple areas of the page
|
||||
*
|
||||
* @async
|
||||
* @param {HeadlessChromiumDriver} browser - used for its methods to control the page
|
||||
* @param {EventLogger} eventLogger
|
||||
* @param {ElementsPositionAndAttribute[]} elements[] - position data about all the elements to capture
|
||||
* @param {Layout} layout - used for client-side layout data from the job params
|
||||
* @returns {Promise<Screenshot[]>}
|
||||
*/
|
||||
export const getScreenshots = async (
|
||||
browser: HeadlessChromiumDriver,
|
||||
eventLogger: EventLogger,
|
||||
elementsPositionAndAttributes: ElementsPositionAndAttribute[]
|
||||
elements: ElementsPositionAndAttribute[],
|
||||
layout: Layout
|
||||
): Promise<Screenshot[]> => {
|
||||
const { kbnLogger } = eventLogger;
|
||||
kbnLogger.info(`taking screenshots`);
|
||||
|
@ -21,16 +65,20 @@ export const getScreenshots = async (
|
|||
const screenshots: Screenshot[] = [];
|
||||
|
||||
try {
|
||||
for (let i = 0; i < elementsPositionAndAttributes.length; i++) {
|
||||
const item = elementsPositionAndAttributes[i];
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
const element = elements[i];
|
||||
const { position, attributes } = element;
|
||||
|
||||
await resizeViewport(browser, position, layout, eventLogger.kbnLogger);
|
||||
|
||||
const endScreenshot = eventLogger.logScreenshottingEvent(
|
||||
'screenshot capture',
|
||||
Actions.GET_SCREENSHOT,
|
||||
'read',
|
||||
eventLogger.getPixelsFromElementPosition(item.position)
|
||||
eventLogger.getPixelsFromElementPosition(position)
|
||||
);
|
||||
|
||||
const data = await browser.screenshot(item.position);
|
||||
const data = await browser.screenshot(position);
|
||||
|
||||
if (!data?.byteLength) {
|
||||
throw new Error(`Failure in getScreenshots! Screenshot data is void`);
|
||||
|
@ -38,8 +86,8 @@ export const getScreenshots = async (
|
|||
|
||||
screenshots.push({
|
||||
data,
|
||||
title: item.attributes.title,
|
||||
description: item.attributes.description,
|
||||
title: attributes.title,
|
||||
description: attributes.description,
|
||||
});
|
||||
|
||||
endScreenshot({ byte_length: data.byteLength });
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { CloudSetup } from '@kbn/cloud-plugin/server';
|
||||
import type { HttpServiceSetup, KibanaRequest, Logger, PackageInfo } from '@kbn/core/server';
|
||||
import type { ExpressionAstExpression } from '@kbn/expressions-plugin/common';
|
||||
import type { Optional } from '@kbn/utility-types';
|
||||
|
@ -22,16 +23,15 @@ import {
|
|||
tap,
|
||||
toArray,
|
||||
} from 'rxjs/operators';
|
||||
import type { CloudSetup } from '@kbn/cloud-plugin/server';
|
||||
import {
|
||||
errors,
|
||||
LayoutParams,
|
||||
SCREENSHOTTING_APP_ID,
|
||||
SCREENSHOTTING_EXPRESSION,
|
||||
SCREENSHOTTING_EXPRESSION_INPUT,
|
||||
errors,
|
||||
} from '../../common';
|
||||
import { HeadlessChromiumDriverFactory, PerformanceMetrics } from '../browsers';
|
||||
import { systemHasInsufficientMemory } from '../cloud';
|
||||
import type { HeadlessChromiumDriverFactory, PerformanceMetrics } from '../browsers';
|
||||
import type { ConfigType } from '../config';
|
||||
import { durationToNumber } from '../config';
|
||||
import {
|
||||
|
@ -42,8 +42,7 @@ import {
|
|||
toPdf,
|
||||
toPng,
|
||||
} from '../formats';
|
||||
import type { Layout } from '../layouts';
|
||||
import { createLayout } from '../layouts';
|
||||
import { createLayout, Layout } from '../layouts';
|
||||
import { EventLogger, Transactions } from './event_logger';
|
||||
import type { ScreenshotObservableOptions, ScreenshotObservableResult } from './observable';
|
||||
import { ScreenshotObservableHandler, UrlOrUrlWithContext } from './observable';
|
||||
|
@ -109,13 +108,6 @@ export class Screenshots {
|
|||
this.semaphore = new Semaphore(config.poolSize);
|
||||
}
|
||||
|
||||
private createLayout(options: CaptureOptions): Layout {
|
||||
const layout = createLayout(options.layout ?? {});
|
||||
this.logger.debug(`Layout: width=${layout.width} height=${layout.height}`);
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
private captureScreenshots(
|
||||
eventLogger: EventLogger,
|
||||
layout: Layout,
|
||||
|
@ -128,7 +120,7 @@ export class Screenshots {
|
|||
{
|
||||
browserTimezone,
|
||||
openUrlTimeout: durationToNumber(this.config.capture.timeouts.openUrl),
|
||||
defaultViewport: { height: layout.height, width: layout.width },
|
||||
defaultViewport: { width: layout.width },
|
||||
},
|
||||
this.logger
|
||||
)
|
||||
|
@ -233,7 +225,7 @@ export class Screenshots {
|
|||
const eventLogger = new EventLogger(this.logger, this.config);
|
||||
const transactionEnd = eventLogger.startTransaction(Transactions.SCREENSHOTTING);
|
||||
|
||||
const layout = this.createLayout(options);
|
||||
const layout = createLayout(options.layout ?? {});
|
||||
const captureOptions = this.getCaptureOptions(options);
|
||||
|
||||
return this.captureScreenshots(eventLogger, layout, captureOptions).pipe(
|
||||
|
|
|
@ -9,24 +9,28 @@ import type { Headers } from '@kbn/core/server';
|
|||
import { defer, forkJoin, Observable, throwError } from 'rxjs';
|
||||
import { catchError, mergeMap, switchMapTo, timeoutWith } from 'rxjs/operators';
|
||||
import { errors, LayoutTypes } from '../../common';
|
||||
import type { Context, HeadlessChromiumDriver } from '../browsers';
|
||||
import { DEFAULT_VIEWPORT, getChromiumDisconnectedError } from '../browsers';
|
||||
import {
|
||||
Context,
|
||||
DEFAULT_VIEWPORT,
|
||||
getChromiumDisconnectedError,
|
||||
HeadlessChromiumDriver,
|
||||
} from '../browsers';
|
||||
import { ConfigType, durationToNumber as toNumber } from '../config';
|
||||
import type { Layout } from '../layouts';
|
||||
import type { PdfScreenshotOptions } from '../formats';
|
||||
import { Layout } from '../layouts';
|
||||
import { Actions, EventLogger } from './event_logger';
|
||||
import type { ElementsPositionAndAttribute } from './get_element_position_data';
|
||||
import { getElementPositionAndAttributes } from './get_element_position_data';
|
||||
import { getNumberOfItems } from './get_number_of_items';
|
||||
import { getRenderErrors } from './get_render_errors';
|
||||
import type { Screenshot } from './types';
|
||||
import { getScreenshots } from './get_screenshots';
|
||||
import { getPdf } from './get_pdf';
|
||||
import { getRenderErrors } from './get_render_errors';
|
||||
import { getScreenshots } from './get_screenshots';
|
||||
import { getTimeRange } from './get_time_range';
|
||||
import { injectCustomCss } from './inject_css';
|
||||
import { openUrl } from './open_url';
|
||||
import type { Screenshot } from './types';
|
||||
import { waitForRenderComplete } from './wait_for_render';
|
||||
import { waitForVisualizations } from './wait_for_visualizations';
|
||||
import type { PdfScreenshotOptions } from '../formats';
|
||||
|
||||
type CaptureTimeouts = ConfigType['capture']['timeouts'];
|
||||
export interface PhaseTimeouts extends CaptureTimeouts {
|
||||
|
@ -109,15 +113,6 @@ const getDefaultElementPosition = (
|
|||
];
|
||||
};
|
||||
|
||||
/*
|
||||
* If Kibana is showing a non-HTML error message, the viewport might not be
|
||||
* provided by the browser.
|
||||
*/
|
||||
const getDefaultViewPort = () => ({
|
||||
...DEFAULT_VIEWPORT,
|
||||
zoom: 1,
|
||||
});
|
||||
|
||||
export class ScreenshotObservableHandler {
|
||||
constructor(
|
||||
private readonly driver: HeadlessChromiumDriver,
|
||||
|
@ -173,15 +168,9 @@ export class ScreenshotObservableHandler {
|
|||
const waitTimeout = toNumber(this.config.capture.timeouts.waitForElements);
|
||||
|
||||
return defer(() => getNumberOfItems(driver, this.eventLogger, waitTimeout, this.layout)).pipe(
|
||||
mergeMap(async (itemsCount) => {
|
||||
// set the viewport to the dimensions from the job, to allow elements to flow into the expected layout
|
||||
const viewport = this.layout.getViewport(itemsCount) || getDefaultViewPort();
|
||||
|
||||
// Set the viewport allowing time for the browser to handle reflow and redraw
|
||||
// before checking for readiness of visualizations.
|
||||
await driver.setViewport(viewport, this.eventLogger.kbnLogger);
|
||||
await waitForVisualizations(driver, this.eventLogger, waitTimeout, itemsCount, this.layout);
|
||||
}),
|
||||
mergeMap((itemsCount) =>
|
||||
waitForVisualizations(driver, this.eventLogger, waitTimeout, itemsCount, this.layout)
|
||||
),
|
||||
this.waitUntil(waitTimeout, 'wait for elements')
|
||||
);
|
||||
}
|
||||
|
@ -266,7 +255,7 @@ export class ScreenshotObservableHandler {
|
|||
this.checkPageIsOpen(); // fail the report job if the browser has closed
|
||||
const elements =
|
||||
data.elementsPositionAndAttributes ??
|
||||
getDefaultElementPosition(this.layout.getViewport(1));
|
||||
getDefaultElementPosition(this.layout.getViewport());
|
||||
let screenshots: Screenshot[] = [];
|
||||
try {
|
||||
screenshots = this.shouldCapturePdf()
|
||||
|
@ -276,7 +265,7 @@ export class ScreenshotObservableHandler {
|
|||
this.getTitle(data.timeRange),
|
||||
(this.options as PdfScreenshotOptions).logo
|
||||
)
|
||||
: await getScreenshots(this.driver, this.eventLogger, elements);
|
||||
: await getScreenshots(this.driver, this.eventLogger, elements, this.layout);
|
||||
} catch (e) {
|
||||
throw new errors.FailedToCaptureScreenshot(e.message);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import type { Headers } from '@kbn/core/server';
|
||||
import type { Context, HeadlessChromiumDriver } from '../browsers';
|
||||
import { DEFAULT_PAGELOAD_SELECTOR } from './constants';
|
||||
import { CONTEXT_DEBUG, DEFAULT_PAGELOAD_SELECTOR } from './constants';
|
||||
import { Actions, EventLogger } from './event_logger';
|
||||
|
||||
export const openUrl = async (
|
||||
|
@ -29,6 +29,27 @@ export const openUrl = async (
|
|||
|
||||
try {
|
||||
await browser.open(url, { context, headers, waitForSelector, timeout }, kbnLogger);
|
||||
|
||||
// Debug logging for viewport size and resizing
|
||||
await browser.evaluate(
|
||||
{
|
||||
fn() {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
`Navigating URL with viewport size: width=${window.innerWidth} height=${window.innerHeight} scaleFactor:${window.devicePixelRatio}`
|
||||
);
|
||||
window.addEventListener('resize', () => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
`Detected a viewport resize: width=${window.innerWidth} height=${window.innerHeight} scaleFactor:${window.devicePixelRatio}`
|
||||
);
|
||||
});
|
||||
},
|
||||
args: [],
|
||||
},
|
||||
{ context: CONTEXT_DEBUG },
|
||||
kbnLogger
|
||||
);
|
||||
} catch (err) {
|
||||
kbnLogger.error(err);
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
};
|
||||
|
||||
describe('Captures', () => {
|
||||
it('PNG that matches the baseline', async () => {
|
||||
it('PNG file matches the baseline image', async () => {
|
||||
await PageObjects.common.navigateToApp(appId);
|
||||
|
||||
await (await testSubjects.find('shareButton')).click();
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.3 MiB |
|
@ -24,14 +24,21 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
const reporting = getService('reporting');
|
||||
const ecommerceSOPath = 'x-pack/test/functional/fixtures/kbn_archiver/reporting/ecommerce.json';
|
||||
|
||||
const loadEcommerce = async () => {
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/reporting/ecommerce');
|
||||
await kibanaServer.importExport.load(ecommerceSOPath);
|
||||
await kibanaServer.uiSettings.replace({
|
||||
defaultIndex: '5193f870-d861-11e9-a311-0fa548c5f953',
|
||||
});
|
||||
};
|
||||
const unloadEcommerce = async () => {
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/reporting/ecommerce');
|
||||
await kibanaServer.importExport.unload(ecommerceSOPath);
|
||||
};
|
||||
|
||||
describe('Dashboard Reporting Screenshots', () => {
|
||||
before('initialize tests', async () => {
|
||||
await kibanaServer.uiSettings.replace({
|
||||
defaultIndex: '5193f870-d861-11e9-a311-0fa548c5f953',
|
||||
});
|
||||
|
||||
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/reporting/ecommerce');
|
||||
await kibanaServer.importExport.load(ecommerceSOPath);
|
||||
await loadEcommerce();
|
||||
await browser.setWindowSize(1600, 850);
|
||||
|
||||
await security.role.create('test_dashboard_user', {
|
||||
|
@ -39,7 +46,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
cluster: [],
|
||||
indices: [
|
||||
{
|
||||
names: ['ecommerce'],
|
||||
names: ['ecommerce', 'kibana_sample_data_ecommerce'],
|
||||
privileges: ['read'],
|
||||
field_security: { grant: ['*'], except: [] },
|
||||
},
|
||||
|
@ -61,8 +68,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
]);
|
||||
});
|
||||
after('clean up archives', async () => {
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/reporting/ecommerce');
|
||||
await kibanaServer.importExport.unload(ecommerceSOPath);
|
||||
await unloadEcommerce();
|
||||
await es.deleteByQuery({
|
||||
index: '.reporting-*',
|
||||
refresh: true,
|
||||
|
@ -88,6 +94,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
describe('Print Layout', () => {
|
||||
before(async () => {
|
||||
await loadEcommerce();
|
||||
});
|
||||
after(async () => {
|
||||
await unloadEcommerce();
|
||||
});
|
||||
|
||||
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.
|
||||
|
@ -107,6 +120,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
describe('Print PNG button', () => {
|
||||
before(async () => {
|
||||
await loadEcommerce();
|
||||
});
|
||||
after(async () => {
|
||||
await unloadEcommerce();
|
||||
});
|
||||
|
||||
it('is available if new', async () => {
|
||||
await PageObjects.common.navigateToApp('dashboard');
|
||||
await PageObjects.dashboard.clickNewDashboard();
|
||||
|
@ -123,7 +143,14 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
describe('PNG Layout', () => {
|
||||
it('downloads a PNG file: small dashboard', async function () {
|
||||
before(async () => {
|
||||
await loadEcommerce();
|
||||
});
|
||||
after(async () => {
|
||||
await unloadEcommerce();
|
||||
});
|
||||
|
||||
it('PNG file matches the baseline: small dashboard', async function () {
|
||||
this.timeout(300000);
|
||||
|
||||
await PageObjects.common.navigateToApp('dashboard');
|
||||
|
@ -152,7 +179,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
expect(percentDiff).to.be.lessThan(0.09);
|
||||
});
|
||||
|
||||
it('downloads a PNG file: large dashboard', async function () {
|
||||
it('PNG file matches the baseline: large dashboard', async function () {
|
||||
this.timeout(300000);
|
||||
|
||||
await PageObjects.common.navigateToApp('dashboard');
|
||||
|
@ -183,6 +210,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
describe('Preserve Layout', () => {
|
||||
before(async () => {
|
||||
await loadEcommerce();
|
||||
});
|
||||
after(async () => {
|
||||
await unloadEcommerce();
|
||||
});
|
||||
|
||||
it('downloads a PDF file: small dashboard', async function () {
|
||||
this.timeout(300000);
|
||||
await PageObjects.common.navigateToApp('dashboard');
|
||||
|
@ -227,5 +261,57 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
await kibanaServer.uiSettings.replace({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Sample data from Kibana 7.6', () => {
|
||||
const reportFileName = 'sample_data_ecommerce_76';
|
||||
let sessionReportPath: string;
|
||||
|
||||
before(async () => {
|
||||
await kibanaServer.uiSettings.replace({
|
||||
defaultIndex: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
|
||||
});
|
||||
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/reporting/ecommerce_76');
|
||||
await kibanaServer.importExport.load(
|
||||
'x-pack/test/functional/fixtures/kbn_archiver/reporting/ecommerce_76.json'
|
||||
);
|
||||
|
||||
await PageObjects.common.navigateToApp('dashboard');
|
||||
await PageObjects.dashboard.loadSavedDashboard('[K7.6-eCommerce] Revenue 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);
|
||||
sessionReportPath = await PageObjects.reporting.writeSessionReport(
|
||||
reportFileName,
|
||||
'png',
|
||||
reportData,
|
||||
REPORTS_FOLDER
|
||||
);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/reporting/ecommerce_76');
|
||||
await kibanaServer.importExport.unload(
|
||||
'x-pack/test/functional/fixtures/kbn_archiver/reporting/ecommerce_76.json'
|
||||
);
|
||||
});
|
||||
|
||||
it('PNG file matches the baseline image', async function () {
|
||||
this.timeout(300000);
|
||||
const percentDiff = await reporting.checkIfPngsMatch(
|
||||
sessionReportPath,
|
||||
PageObjects.reporting.getBaselineReportPath(reportFileName, 'png', REPORTS_FOLDER),
|
||||
config.get('screenshots.directory'),
|
||||
log
|
||||
);
|
||||
|
||||
expect(percentDiff).to.be.lessThan(0.09);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
const log = getService('log');
|
||||
const reporting = getService('reporting');
|
||||
|
||||
describe('dashboard reporting', () => {
|
||||
describe('dashboard reporting: creates a map report', () => {
|
||||
// helper function to check the difference between the new image and the baseline
|
||||
const measurePngDifference = async (fileName: string) => {
|
||||
const url = await PageObjects.reporting.getReportURL(60000);
|
||||
|
@ -43,7 +43,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
await reporting.deleteAllReports();
|
||||
});
|
||||
|
||||
it('creates a map report using sample geo data', async function () {
|
||||
it('PNG file matches the baseline image, using sample geo data', async function () {
|
||||
await reporting.initEcommerce();
|
||||
|
||||
await PageObjects.common.navigateToApp('dashboard');
|
||||
|
@ -57,7 +57,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
await reporting.teardownEcommerce();
|
||||
});
|
||||
|
||||
it('creates a map report using embeddable example', async function () {
|
||||
it('PNG file matches the baseline image, using embeddable example', async function () {
|
||||
await PageObjects.common.navigateToApp('dashboard');
|
||||
await PageObjects.dashboard.loadSavedDashboard('map embeddable example');
|
||||
await PageObjects.reporting.openPngReportingPanel();
|
||||
|
|
|
@ -6,15 +6,19 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import path from 'path';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
const REPORTS_FOLDER = path.resolve(__dirname, 'reports');
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const es = getService('es');
|
||||
const esArchiver = getService('esArchiver');
|
||||
const browser = getService('browser');
|
||||
const log = getService('log');
|
||||
const config = getService('config');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const ecommerceSOPath = 'x-pack/test/functional/fixtures/kbn_archiver/reporting/ecommerce.json';
|
||||
const reporting = getService('reporting');
|
||||
|
||||
const PageObjects = getPageObjects([
|
||||
'reporting',
|
||||
|
@ -27,28 +31,42 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
describe('Visualize Reporting Screenshots', function () {
|
||||
this.tags(['smoke']);
|
||||
before('initialize tests', async () => {
|
||||
log.debug('ReportingPage:initTests');
|
||||
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/reporting/ecommerce');
|
||||
await kibanaServer.importExport.load(ecommerceSOPath);
|
||||
before(async () => {
|
||||
await browser.setWindowSize(1600, 850);
|
||||
await kibanaServer.uiSettings.replace({
|
||||
'timepicker:timeDefaults':
|
||||
'{ "from": "2019-04-27T23:56:51.374Z", "to": "2019-08-23T16:18:51.821Z"}',
|
||||
});
|
||||
});
|
||||
after('clean up archives', async () => {
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/reporting/ecommerce');
|
||||
await kibanaServer.importExport.unload(ecommerceSOPath);
|
||||
after(async () => {
|
||||
await es.deleteByQuery({
|
||||
index: '.reporting-*',
|
||||
refresh: true,
|
||||
body: { query: { match_all: {} } },
|
||||
});
|
||||
await kibanaServer.uiSettings.unset('timepicker:timeDefaults');
|
||||
});
|
||||
|
||||
describe('Print PDF button', () => {
|
||||
const ecommerceSOPath =
|
||||
'x-pack/test/functional/fixtures/kbn_archiver/reporting/ecommerce.json';
|
||||
|
||||
before('initialize tests', async () => {
|
||||
log.debug('ReportingPage:initTests');
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/reporting/ecommerce');
|
||||
await kibanaServer.importExport.load(ecommerceSOPath);
|
||||
await kibanaServer.uiSettings.replace({
|
||||
'timepicker:timeDefaults':
|
||||
'{ "from": "2019-04-27T23:56:51.374Z", "to": "2019-08-23T16:18:51.821Z"}',
|
||||
defaultIndex: '5193f870-d861-11e9-a311-0fa548c5f953',
|
||||
});
|
||||
});
|
||||
after('clean up archives', async () => {
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/reporting/ecommerce');
|
||||
await kibanaServer.importExport.unload(ecommerceSOPath);
|
||||
await es.deleteByQuery({
|
||||
index: '.reporting-*',
|
||||
refresh: true,
|
||||
body: { query: { match_all: {} } },
|
||||
});
|
||||
await kibanaServer.uiSettings.unset('timepicker:timeDefaults');
|
||||
});
|
||||
|
||||
it('is available if new', async () => {
|
||||
await PageObjects.common.navigateToUrl('visualize', 'new', { useActualUrl: true });
|
||||
await PageObjects.visualize.clickAggBasedVisualizations();
|
||||
|
@ -66,21 +84,69 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.reporting.openPdfReportingPanel();
|
||||
expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be(null);
|
||||
});
|
||||
});
|
||||
|
||||
it('downloaded PDF has OK status', async function () {
|
||||
// Generating and then comparing reports can take longer than the default 60s timeout
|
||||
this.timeout(180000);
|
||||
describe('PNG reports: sample data created in 7.6', () => {
|
||||
const reportFileName = 'tsvb';
|
||||
|
||||
await PageObjects.common.navigateToApp('dashboard');
|
||||
await PageObjects.dashboard.loadSavedDashboard('Ecom Dashboard');
|
||||
await PageObjects.reporting.openPdfReportingPanel();
|
||||
before(async () => {
|
||||
await kibanaServer.uiSettings.replace({
|
||||
defaultIndex: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
|
||||
});
|
||||
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/reporting/ecommerce_76');
|
||||
await kibanaServer.importExport.load(
|
||||
'x-pack/test/functional/fixtures/kbn_archiver/reporting/ecommerce_76.json'
|
||||
);
|
||||
|
||||
log.debug('navigate to visualize');
|
||||
await PageObjects.common.navigateToApp('visualize');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/reporting/ecommerce_76');
|
||||
await kibanaServer.importExport.unload(
|
||||
'x-pack/test/functional/fixtures/kbn_archiver/reporting/ecommerce_76.json'
|
||||
);
|
||||
});
|
||||
|
||||
it('TSVB Gauge: PNG file matches the baseline image', async function () {
|
||||
log.debug('load saved visualization');
|
||||
await PageObjects.visualize.loadSavedVisualization(
|
||||
'[K7.6-eCommerce] Sold Products per Day',
|
||||
{ navigateToVisualize: false }
|
||||
);
|
||||
log.debug('set time range');
|
||||
await PageObjects.timePicker.setAbsoluteRange(
|
||||
'Apr 15, 2022 @ 00:00:00.000',
|
||||
'May 22, 2022 @ 00:00:00.000'
|
||||
);
|
||||
|
||||
log.debug('open png reporting panel');
|
||||
await PageObjects.reporting.openPngReportingPanel();
|
||||
log.debug('click generate report button');
|
||||
await PageObjects.reporting.clickGenerateReportButton();
|
||||
|
||||
log.debug('get the report download URL');
|
||||
const url = await PageObjects.reporting.getReportURL(60000);
|
||||
const res = await PageObjects.reporting.getResponse(url);
|
||||
log.debug('download the report');
|
||||
const reportData = await PageObjects.reporting.getRawPdfReportData(url);
|
||||
const sessionReportPath = await PageObjects.reporting.writeSessionReport(
|
||||
reportFileName,
|
||||
'png',
|
||||
reportData,
|
||||
REPORTS_FOLDER
|
||||
);
|
||||
|
||||
expect(res.status).to.equal(200);
|
||||
expect(res.get('content-type')).to.equal('application/pdf');
|
||||
// check the file
|
||||
const percentDiff = await reporting.checkIfPngsMatch(
|
||||
sessionReportPath,
|
||||
PageObjects.reporting.getBaselineReportPath(reportFileName, 'png', REPORTS_FOLDER),
|
||||
config.get('screenshots.directory'),
|
||||
log
|
||||
);
|
||||
|
||||
expect(percentDiff).to.be.lessThan(0.09);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
BIN
x-pack/test/functional/apps/visualize/reports/baseline/tsvb.png
Normal file
BIN
x-pack/test/functional/apps/visualize/reports/baseline/tsvb.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
Binary file not shown.
|
@ -0,0 +1,219 @@
|
|||
{
|
||||
"type": "index",
|
||||
"value": {
|
||||
"aliases": {
|
||||
},
|
||||
"index": "kibana_sample_data_ecommerce",
|
||||
"mappings": {
|
||||
"properties": {
|
||||
"category": {
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword"
|
||||
}
|
||||
},
|
||||
"type": "text"
|
||||
},
|
||||
"currency": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"customer_birth_date": {
|
||||
"type": "date"
|
||||
},
|
||||
"customer_first_name": {
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"ignore_above": 256,
|
||||
"type": "keyword"
|
||||
}
|
||||
},
|
||||
"type": "text"
|
||||
},
|
||||
"customer_full_name": {
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"ignore_above": 256,
|
||||
"type": "keyword"
|
||||
}
|
||||
},
|
||||
"type": "text"
|
||||
},
|
||||
"customer_gender": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"customer_id": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"customer_last_name": {
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"ignore_above": 256,
|
||||
"type": "keyword"
|
||||
}
|
||||
},
|
||||
"type": "text"
|
||||
},
|
||||
"customer_phone": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"day_of_week": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"day_of_week_i": {
|
||||
"type": "integer"
|
||||
},
|
||||
"email": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"event": {
|
||||
"properties": {
|
||||
"dataset": {
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
},
|
||||
"geoip": {
|
||||
"properties": {
|
||||
"city_name": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"continent_name": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"country_iso_code": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"location": {
|
||||
"type": "geo_point"
|
||||
},
|
||||
"region_name": {
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
},
|
||||
"manufacturer": {
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword"
|
||||
}
|
||||
},
|
||||
"type": "text"
|
||||
},
|
||||
"order_date": {
|
||||
"type": "date"
|
||||
},
|
||||
"order_id": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"products": {
|
||||
"properties": {
|
||||
"_id": {
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"ignore_above": 256,
|
||||
"type": "keyword"
|
||||
}
|
||||
},
|
||||
"type": "text"
|
||||
},
|
||||
"base_price": {
|
||||
"type": "half_float"
|
||||
},
|
||||
"base_unit_price": {
|
||||
"type": "half_float"
|
||||
},
|
||||
"category": {
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword"
|
||||
}
|
||||
},
|
||||
"type": "text"
|
||||
},
|
||||
"created_on": {
|
||||
"type": "date"
|
||||
},
|
||||
"discount_amount": {
|
||||
"type": "half_float"
|
||||
},
|
||||
"discount_percentage": {
|
||||
"type": "half_float"
|
||||
},
|
||||
"manufacturer": {
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword"
|
||||
}
|
||||
},
|
||||
"type": "text"
|
||||
},
|
||||
"min_price": {
|
||||
"type": "half_float"
|
||||
},
|
||||
"price": {
|
||||
"type": "half_float"
|
||||
},
|
||||
"product_id": {
|
||||
"type": "long"
|
||||
},
|
||||
"product_name": {
|
||||
"analyzer": "english",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword"
|
||||
}
|
||||
},
|
||||
"type": "text"
|
||||
},
|
||||
"quantity": {
|
||||
"type": "integer"
|
||||
},
|
||||
"sku": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"tax_amount": {
|
||||
"type": "half_float"
|
||||
},
|
||||
"taxful_price": {
|
||||
"type": "half_float"
|
||||
},
|
||||
"taxless_price": {
|
||||
"type": "half_float"
|
||||
},
|
||||
"unit_discount_amount": {
|
||||
"type": "half_float"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sku": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"taxful_total_price": {
|
||||
"type": "half_float"
|
||||
},
|
||||
"taxless_total_price": {
|
||||
"type": "half_float"
|
||||
},
|
||||
"total_quantity": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_unique_products": {
|
||||
"type": "integer"
|
||||
},
|
||||
"type": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"user": {
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"index": {
|
||||
"auto_expand_replicas": "0-1",
|
||||
"number_of_replicas": "0",
|
||||
"number_of_shards": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue