Skip to content

Commit

Permalink
[Reporting] test the screenshot observable
Browse files Browse the repository at this point in the history
commit 1c6a6b0
Author: Timothy Sullivan <[email protected]>
Date:   Thu Feb 13 14:10:40 2020 -0700

    fix tests

commit a3afefd
Merge: facd94b 6e4efdf
Author: Timothy Sullivan <[email protected]>
Date:   Thu Feb 13 13:56:24 2020 -0700

    Merge branch 'master' into reporting/np-server-uisettings

commit facd94b
Author: Timothy Sullivan <[email protected]>
Date:   Thu Feb 13 12:16:51 2020 -0700

    extract internals access to separate core class

commit 2f12495
Merge: dd0fd40 3f3969d
Author: Timothy Sullivan <[email protected]>
Date:   Thu Feb 13 09:57:07 2020 -0700

    Merge branch 'master' into reporting/np-server-uisettings

commit dd0fd40
Author: Timothy Sullivan <[email protected]>
Date:   Wed Feb 12 14:44:03 2020 -0700

    add more tests

commit 25dc761
Author: Timothy Sullivan <[email protected]>
Date:   Wed Feb 12 13:54:47 2020 -0700

    simplify reporting usage collector setup

commit e8aa02d
Author: Timothy Sullivan <[email protected]>
Date:   Wed Feb 12 13:44:13 2020 -0700

    consistent name for reportingPlugin

commit d235b16
Author: Timothy Sullivan <[email protected]>
Date:   Wed Feb 12 13:52:07 2020 -0700

    Prettier changes

commit e8ddc7b
Author: Timothy Sullivan <[email protected]>
Date:   Wed Feb 12 13:28:35 2020 -0700

    [Reporting/New Platform] Provide async access to server-side
  • Loading branch information
tsullivan committed Feb 16, 2020
1 parent 34ae99b commit 24ea6b3
Show file tree
Hide file tree
Showing 22 changed files with 597 additions and 264 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import path from 'path';
import { EvaluateFn, SerializableOrJSHandle } from 'puppeteer';
import { LevelLogger } from '../../../server/lib';
import { HeadlessChromiumDriver } from '../../../server/browsers/chromium/driver';
import { HeadlessChromiumDriver } from '../../../server/browsers';
import { ServerFacade } from '../../../types';
import { LayoutTypes } from '../constants';
import { getDefaultLayoutSelectors, Layout, LayoutSelectorDictionary, Size } from './layout';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { i18n } from '@kbn/i18n';
import { ElementHandle } from 'puppeteer';
import { HeadlessChromiumDriver as HeadlessBrowser } from '../../../../server/browsers/chromium/driver';
import { HeadlessChromiumDriver as HeadlessBrowser } from '../../../../server/browsers';
import { LevelLogger } from '../../../../server/lib';
import { LayoutInstance } from '../../layouts/layout';

Expand All @@ -20,13 +20,17 @@ export const checkForToastMessage = async (
.then(async () => {
// Check for a toast message on the page. If there is one, capture the
// message and throw an error, to fail the screenshot.
const toastHeaderText: string = await browser.evaluate({
fn: selector => {
const nodeList = document.querySelectorAll(selector);
return nodeList.item(0).innerText;
const toastHeaderText: string = await browser.evaluate(
{
fn: selector => {
const nodeList = document.querySelectorAll(selector);
return nodeList.item(0).innerText;
},
args: [layout.selectors.toastHeader],
},
args: [layout.selectors.toastHeader],
});
{ context: 'CheckForToastMessage' },
logger
);

// Log an error to track the event in kibana server logs
logger.error(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,54 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { HeadlessChromiumDriver as HeadlessBrowser } from '../../../../server/browsers/chromium/driver';
import { HeadlessChromiumDriver as HeadlessBrowser } from '../../../../server/browsers';
import { LayoutInstance } from '../../layouts/layout';
import { AttributesMap, ElementsPositionAndAttribute } from './types';
import { Logger } from '../../../../types';

export const getElementPositionAndAttributes = async (
browser: HeadlessBrowser,
layout: LayoutInstance
layout: LayoutInstance,
logger: Logger
): Promise<ElementsPositionAndAttribute[]> => {
const elementsPositionAndAttributes: ElementsPositionAndAttribute[] = await browser.evaluate({
fn: (selector, attributes) => {
const elements: NodeListOf<Element> = document.querySelectorAll(selector);
const elementsPositionAndAttributes: ElementsPositionAndAttribute[] = await browser.evaluate(
{
fn: (selector: string, attributes: any) => {
const elements: NodeListOf<Element> = document.querySelectorAll(selector);

// NodeList isn't an array, just an iterator, unable to use .map/.forEach
const results: ElementsPositionAndAttribute[] = [];
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
const boundingClientRect = element.getBoundingClientRect() as DOMRect;
results.push({
position: {
boundingClientRect: {
// modern browsers support x/y, but older ones don't
top: boundingClientRect.y || boundingClientRect.top,
left: boundingClientRect.x || boundingClientRect.left,
width: boundingClientRect.width,
height: boundingClientRect.height,
// NodeList isn't an array, just an iterator, unable to use .map/.forEach
const results: ElementsPositionAndAttribute[] = [];
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
const boundingClientRect = element.getBoundingClientRect() as DOMRect;
results.push({
position: {
boundingClientRect: {
// modern browsers support x/y, but older ones don't
top: boundingClientRect.y || boundingClientRect.top,
left: boundingClientRect.x || boundingClientRect.left,
width: boundingClientRect.width,
height: boundingClientRect.height,
},
scroll: {
x: window.scrollX,
y: window.scrollY,
},
},
scroll: {
x: window.scrollX,
y: window.scrollY,
},
},
attributes: Object.keys(attributes).reduce((result: AttributesMap, key) => {
const attribute = attributes[key];
result[key] = element.getAttribute(attribute);
return result;
}, {}),
});
}
return results;
attributes: Object.keys(attributes).reduce((result: AttributesMap, key) => {
const attribute = attributes[key];
(result as any)[key] = element.getAttribute(attribute);
return result;
}, {} as AttributesMap),
});
}
return results;
},
args: [layout.selectors.screenshot, { title: 'data-title', description: 'data-description' }],
},
args: [layout.selectors.screenshot, { title: 'data-title', description: 'data-description' }],
});
{ context: 'ElementPositionAndAttributes' },
logger
);

if (elementsPositionAndAttributes.length === 0) {
throw new Error(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { HeadlessChromiumDriver as HeadlessBrowser } from '../../../../server/browsers/chromium/driver';
import { HeadlessChromiumDriver as HeadlessBrowser } from '../../../../server/browsers';
import { LevelLogger } from '../../../../server/lib';
import { LayoutInstance } from '../../layouts/layout';

Expand All @@ -17,20 +17,24 @@ export const getNumberOfItems = async (

// returns the value of the `itemsCountAttribute` if it's there, otherwise
// we just count the number of `itemSelector`
const itemsCount: number = await browser.evaluate({
fn: (selector, countAttribute) => {
const elementWithCount = document.querySelector(`[${countAttribute}]`);
if (elementWithCount && elementWithCount != null) {
const count = elementWithCount.getAttribute(countAttribute);
if (count && count != null) {
return parseInt(count, 10);
const itemsCount: number = await browser.evaluate(
{
fn: (selector, countAttribute) => {
const elementWithCount = document.querySelector(`[${countAttribute}]`);
if (elementWithCount && elementWithCount != null) {
const count = elementWithCount.getAttribute(countAttribute);
if (count && count != null) {
return parseInt(count, 10);
}
}
}

return document.querySelectorAll(selector).length;
return document.querySelectorAll(selector).length;
},
args: [layout.selectors.renderComplete, layout.selectors.itemsCountAttribute],
},
args: [layout.selectors.renderComplete, layout.selectors.itemsCountAttribute],
});
{ context: 'GetNumberOfItems' },
logger
);

return itemsCount;
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { HeadlessChromiumDriver as HeadlessBrowser } from '../../../../server/browsers/chromium/driver';
import { HeadlessChromiumDriver as HeadlessBrowser } from '../../../../server/browsers';
import { LevelLogger } from '../../../../server/lib';
import { Screenshot, ElementsPositionAndAttribute } from './types';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { HeadlessChromiumDriver as HeadlessBrowser } from '../../../../server/browsers/chromium/driver';
import { HeadlessChromiumDriver as HeadlessBrowser } from '../../../../server/browsers';
import { LevelLogger } from '../../../../server/lib';
import { LayoutInstance } from '../../layouts/layout';
import { TimeRange } from './types';
Expand All @@ -16,23 +16,27 @@ export const getTimeRange = async (
): Promise<TimeRange | null> => {
logger.debug('getting timeRange');

const timeRange: TimeRange | null = await browser.evaluate({
fn: durationAttribute => {
const durationElement = document.querySelector(`[${durationAttribute}]`);
const timeRange: TimeRange | null = await browser.evaluate(
{
fn: durationAttribute => {
const durationElement = document.querySelector(`[${durationAttribute}]`);

if (!durationElement) {
return null;
}
if (!durationElement) {
return null;
}

const duration = durationElement.getAttribute(durationAttribute);
if (!duration) {
return null;
}
const duration = durationElement.getAttribute(durationAttribute);
if (!duration) {
return null;
}

return { duration };
return { duration };
},
args: [layout.selectors.timefilterDurationAttribute],
},
args: [layout.selectors.timefilterDurationAttribute],
});
{ context: 'GetTimeRange' },
logger
);

if (timeRange) {
logger.info(`timeRange: ${timeRange.duration}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,92 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/

import * as Rx from 'rxjs';
import { concatMap, first, mergeMap, take, toArray } from 'rxjs/operators';
import { CaptureConfig, HeadlessChromiumDriverFactory, ServerFacade } from '../../../../types';
import { getElementPositionAndAttributes } from './get_element_position_data';
import { getNumberOfItems } from './get_number_of_items';
import { getScreenshots } from './get_screenshots';
import { getTimeRange } from './get_time_range';
import { injectCustomCss } from './inject_css';
import { openUrl } from './open_url';
import { scanPage } from './scan_page';
import { skipTelemetry } from './skip_telemetry';
import { ScreenshotObservableOpts, ScreenshotResults } from './types';
import { waitForElementsToBeInDOM } from './wait_for_dom_elements';
import { waitForRenderComplete } from './wait_for_render';

export function screenshotsObservableFactory(
server: ServerFacade,
browserDriverFactory: HeadlessChromiumDriverFactory
) {
const config = server.config();
const captureConfig: CaptureConfig = config.get('xpack.reporting.capture');

return function screenshotsObservable({
logger,
urls,
conditionalHeaders,
layout,
browserTimezone,
}: ScreenshotObservableOpts): Rx.Observable<ScreenshotResults[]> {
const create$ = browserDriverFactory.createPage(
{ viewport: layout.getBrowserViewport(), browserTimezone },
logger
);
return Rx.from(urls).pipe(
concatMap(url => {
return create$.pipe(
mergeMap(({ driver, exit$ }) => {
const screenshot$ = Rx.of(1).pipe(
mergeMap(() => openUrl(driver, url, conditionalHeaders, logger)),
mergeMap(() => skipTelemetry(driver, logger)),
mergeMap(() => scanPage(driver, layout, logger)),
mergeMap(() => getNumberOfItems(driver, layout, logger)),
mergeMap(async itemsCount => {
const viewport = layout.getViewport(itemsCount);
await Promise.all([
driver.setViewport(viewport, logger),
waitForElementsToBeInDOM(driver, itemsCount, layout, logger),
]);
}),
mergeMap(async () => {
// Waiting till _after_ elements have rendered before injecting our CSS
// allows for them to be displayed properly in many cases
await injectCustomCss(driver, layout, logger);

if (layout.positionElements) {
// position panel elements for print layout
await layout.positionElements(driver, logger);
}

await waitForRenderComplete(captureConfig, driver, layout, logger);
}),
mergeMap(() => getTimeRange(driver, layout, logger)),
mergeMap(
async (timeRange): Promise<ScreenshotResults> => {
const elementsPositionAndAttributes = await getElementPositionAndAttributes(
driver,
layout
);
const screenshots = await getScreenshots({
browser: driver,
elementsPositionAndAttributes,
logger,
});

return { timeRange, screenshots };
}
)
);

return Rx.race(screenshot$, exit$);
}),
first()
);
}),
take(urls.length),
toArray()
);
};
}
export { screenshotsObservableFactory } from './observable';
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import fs from 'fs';
import { promisify } from 'util';
import { LevelLogger } from '../../../../server/lib';
import { HeadlessChromiumDriver as HeadlessBrowser } from '../../../../server/browsers/chromium/driver';
import { HeadlessChromiumDriver as HeadlessBrowser } from '../../../../server/browsers';
import { Layout } from '../../layouts/layout';

const fsp = { readFile: promisify(fs.readFile) };
Expand All @@ -21,13 +21,17 @@ export const injectCustomCss = async (

const filePath = layout.getCssOverridesPath();
const buffer = await fsp.readFile(filePath);
await browser.evaluate({
fn: css => {
const node = document.createElement('style');
node.type = 'text/css';
node.innerHTML = css; // eslint-disable-line no-unsanitized/property
document.getElementsByTagName('head')[0].appendChild(node);
await browser.evaluate(
{
fn: css => {
const node = document.createElement('style');
node.type = 'text/css';
node.innerHTML = css; // eslint-disable-line no-unsanitized/property
document.getElementsByTagName('head')[0].appendChild(node);
},
args: [buffer.toString()],
},
args: [buffer.toString()],
});
{ context: 'InjectCss' },
logger
);
};
Loading

0 comments on commit 24ea6b3

Please sign in to comment.