[Reporting] Remove Phantom (#27142)

* remove some phantom stuff and tests

* remove phantom

* remove phantom

* remove phantom

* todo comments

* remove from yarn.lock

* edit fix

* use constant in init

* readme edit

* update migration guide

* remove refs to non-existing docs
This commit is contained in:
Tim Sullivan 2019-01-03 14:35:15 -07:00 committed by GitHub
parent 241949b350
commit 237e446ba3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 52 additions and 775 deletions

View file

@ -23,6 +23,30 @@ Kibana 7.0 will only use the Node.js distribution included in the package.
*Impact:* There is no expected impact unless Kibana is installed in a non-standard way.
[float]
=== Removed support for using PhantomJS browser for screenshots in Reporting
*Details:* Since the first release of Kibana Reporting, PhantomJS was used as
the headless browser to capture screenshots of Kibana dashboards and
visualizations. In that short time, Chromium has started offering a new
headless browser library and the PhantomJS maintainers abandoned their project.
We started planning for a transition in 6.5.0, when we made Chromium the
default option, but allowed users to continue using Phantom with the
`xpack.reporting.capture.browser.type: phantom` setting. In 7.0, that setting
will still exist for compatibility, but the only valid option will be
`chromium`.
*Impact:* Before upgrading to 7.0, if you have `xpack.reporting.capture.browser.type`
set in kibana.yml, make sure it is set to `chromium`.
[NOTE]
============
Reporting 7.0 uses a version of the Chromium headless browser that RHEL 6,
CentOS 6.x, and other old versions of Linux derived from RHEL 6. This change
effectively removes RHEL 6 OS server support from Kibana Reporting. Users with
RHEL 6 must upgrade to RHEL 7 to use Kibana Reporting starting with version
7.0.0 of the Elastic stack.
============
[float]
=== Advanced setting query:queryString:options no longer applies to filters
*Details:* In previous versions of Kibana the Advanced Setting `query:queryString:options` was applied to both queries

View file

@ -22,8 +22,9 @@ There is currently a known limitation with the Data Table visualization that onl
[float]
==== `You must install fontconfig and freetype for Reporting to work'`
Reporting using PhantomJS, the default browser, relies on system packages. Install the appropriate fontconfig and freetype
packages for your distribution.
Reporting uses a headless browser on the Kibana server, which relies on some
system packages. Install the appropriate fontconfig and freetype packages for
your distribution.
[float]
==== `Max attempts reached (3)`
@ -54,11 +55,6 @@ the CAP_SYS_ADMIN capability.
Elastic recommends that you research the feasibility of enabling unprivileged user namespaces before disabling the sandbox. An exception
is if you are running Kibana in Docker because the container runs in a user namespace with the built-in seccomp/bpf filters.
[float]
==== `spawn EACCES`
Ensure that the `phantomjs` binary in your Kibana data directory is owned by the user who is running Kibana, that the user has the execute permission,
and if applicable, that the filesystem is mounted with the `exec` option.
[float]
==== `Caught error spawning Chromium`
Ensure that the `headless_shell` binary located in your Kibana data directory is owned by the user who is running Kibana, that the user has the execute permission,

View file

@ -91,16 +91,8 @@ visualizations, try increasing this value.
Defaults to `3000` (3 seconds).
[[xpack-reporting-browser]]`xpack.reporting.capture.browser.type`::
Specifies the browser to use to capture screenshots. Valid options are `phantom`
and `chromium`. When `chromium` is set, the settings specified in the <<reporting-chromium-settings, Chromium settings>>
are respected. Defaults to `chromium`.
[NOTE]
============
Starting in 7.0, Phantom support will be removed from Kibana, and `chromium`
will be the only valid option for the `xpack.reporting.capture.browser.type` setting.
============
Specifies the browser to use to capture screenshots. This setting exists for
backward compatibility. The only valid option is `chromium`.
[float]
[[reporting-chromium-settings]]

View file

@ -26,7 +26,7 @@ module.exports = {
browsers: [
'last 2 versions',
'> 5%',
'Safari 7', // for PhantomJS support
'Safari 7', // for PhantomJS support: https://github.com/elastic/kibana/issues/27136
],
},
useBuiltIns: true,

View file

@ -195,27 +195,21 @@ export const CleanExtraBrowsersTask = {
async run(config, log, build) {
const getBrowserPathsForPlatform = platform => {
const reportingDir = 'node_modules/x-pack/plugins/reporting';
const phantomDir = '.phantom';
const chromiumDir = '.chromium';
const phantomPath = p =>
build.resolvePathForPlatform(platform, reportingDir, phantomDir, p);
const chromiumPath = p =>
build.resolvePathForPlatform(platform, reportingDir, chromiumDir, p);
return platforms => {
const paths = [];
if (platforms.windows) {
paths.push(phantomPath('phantomjs-*-windows.zip'));
paths.push(chromiumPath('chromium-*-win32.zip'));
paths.push(chromiumPath('chromium-*-windows.zip'));
}
if (platforms.darwin) {
paths.push(phantomPath('phantomjs-*-macosx.zip'));
paths.push(chromiumPath('chromium-*-darwin.zip'));
}
if (platforms.linux) {
paths.push(phantomPath('phantomjs-*-linux-x86_64.tar.bz2'));
paths.push(chromiumPath('chromium-*-linux.zip'));
}
return paths;

View file

@ -23,7 +23,7 @@ module.exports = {
browsers: [
'last 2 versions',
'> 5%',
'Safari 7' // for PhantomJS support
'Safari 7' // for PhantomJS support: https://github.com/elastic/kibana/issues/27136
]
})
]

View file

@ -93,6 +93,7 @@ ObjDefine.prototype.create = function () {
// clone the object on serialization and choose which properties
// to include or trim manually. This is currently only in use in PhantomJS
// due to https://github.com/ariya/phantomjs/issues/11856
// TODO: remove this: https://github.com/elastic/kibana/issues/27136
self.obj.toJSON = function () {
return _.transform(self.obj, function (json, val, key) {
const desc = self.descs[key];

View file

@ -127,18 +127,3 @@ For both of the above commands, it's crucial that you pass in `--config` to spec
Read more about how the scripts work [here](scripts/README.md).
For a deeper dive, read more about the way functional tests and servers work [here](packages/kbn-test/README.md).
### Issues starting dev more of creating builds
You may see an error like this when you are getting started:
```
[14:08:15] Error: Linux x86 checksum failed
at download_phantom.js:42:15
at process._tickDomainCallback (node.js:407:9)
```
That's thanks to the binary Phantom downloads that have to happen, and Bitbucket being annoying with throttling and redirecting or... something. The real issue eludes me, but you have 2 options to resolve it.
1. Just keep re-running the command until it passes. Eventually the downloads will work, and since they are cached, it won't ever be an issue again.
1. Download them by hand [from Bitbucket](https://bitbucket.org/ariya/phantomjs/downloads) and copy them into the `.phantom` path. We're currently using 1.9.8, and you'll need the Window, Mac, and Linux builds.

View file

@ -121,7 +121,6 @@
"@elastic/datemath": "5.0.2",
"@elastic/eui": "6.0.1",
"@elastic/node-crypto": "0.1.2",
"@elastic/node-phantom-simple": "2.2.4",
"@elastic/numeral": "2.3.2",
"@kbn/babel-preset": "1.0.0",
"@kbn/es-query": "1.0.0",

View file

@ -1,7 +1,7 @@
/*
******
****** This is a collection of CSS overrides that make Kibana look better for
****** generating PDF reports with Phantom.
****** generating PDF reports with headless browser
******
*/

View file

@ -1,7 +1,7 @@
/*
******
****** This is a collection of CSS overrides that make Kibana look better for
****** generating PDF reports with Phantom.
****** generating PDF reports with headless browser
******
*/
@ -117,7 +117,7 @@ visualize-app .visEditor__canvas {
* reporting/export_types/printable_pdf/server/lib/screenshot.js or this will also hide the visualization
* we want to capture.
* 2. React grid item's transform affects the visualizations, even when they are using fixed positioning. Chrome seems
* to handle this fine, but firefox moves the visualizations around, as I suspect Phantom since it had a problem.
* to handle this fine, but firefox moves the visualizations around.
*/
dashboard-app .react-grid-item {
height: 0 !important; /* 1. */

View file

@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { get } from 'lodash';
import { resolve } from 'path';
import { UI_SETTINGS_CUSTOM_PDF_LOGO } from './common/constants';
import { mirrorPluginStatus } from '../../server/lib/mirror_plugin_status';
@ -17,7 +16,7 @@ import { checkLicenseFactory } from './server/lib/check_license';
import { validateConfig } from './server/lib/validate_config';
import { validateMaxContentLength } from './server/lib/validate_max_content_length';
import { exportTypesRegistryFactory } from './server/lib/export_types_registry';
import { PHANTOM, createBrowserDriverFactory, getDefaultBrowser, getDefaultChromiumSandboxDisabled } from './server/browsers';
import { CHROMIUM, createBrowserDriverFactory, getDefaultChromiumSandboxDisabled } from './server/browsers';
import { logConfiguration } from './log_configuration';
import { getReportingUsageCollector } from './server/usage';
@ -94,7 +93,7 @@ export const reporting = (kibana) => {
settleTime: Joi.number().integer().default(1000), //deprecated
concurrency: Joi.number().integer().default(appConfig.concurrency), //deprecated
browser: Joi.object({
type: Joi.any().valid('phantom', 'chromium').default(await getDefaultBrowser()), // TODO: make chromium the only valid option in 7.0
type: Joi.any().valid(CHROMIUM).default(CHROMIUM),
autoDownload: Joi.boolean().when('$dev', {
is: true,
then: Joi.default(true),
@ -178,15 +177,6 @@ export const reporting = (kibana) => {
deprecations: function ({ unused }) {
return [
(settings, log) => {
const isPhantom = get(settings, 'capture.browser.type') === PHANTOM;
if (isPhantom) {
log(
'Phantom browser support for Reporting will be removed and Chromium will be the only valid option starting in 7.0.0. ' +
'Use the default `chromium` value for `xpack.reporting.capture.browser.type` to dismiss this warning.'
);
}
},
unused("capture.concurrency"),
unused("capture.timeout"),
unused("capture.settleTime"),

View file

@ -4,5 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
export const PHANTOM = 'phantom';
export const CHROMIUM = 'chromium';
export const CHROMIUM = 'chromium';

View file

@ -5,9 +5,7 @@
*/
import * as chromium from './chromium';
import * as phantom from './phantom';
export const BROWSERS_BY_TYPE = {
chromium,
phantom,
};

View file

@ -1,31 +0,0 @@
/*
* 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 getosSync from 'getos';
import { promisify } from 'bluebird';
import { PHANTOM, CHROMIUM } from './browser_types';
const getos = promisify(getosSync);
// Chromium is unsupported on RHEL/CentOS before 7.0.
const distroSupportsChromium = (distro, release) => {
if (distro.toLowerCase() !== 'centos' && distro.toLowerCase () !== 'red hat linux') {
return true;
}
const releaseNumber = parseInt(release, 10);
return releaseNumber >= 7;
};
export async function getDefaultBrowser() {
const os = await getos();
if (os.os === 'linux' && !distroSupportsChromium(os.dist, os.release)) {
return PHANTOM;
} else {
return CHROMIUM;
}
}

View file

@ -5,7 +5,7 @@
*/
export class ExtractError extends Error {
constructor(cause, message = 'Failed to extract the phantom.js archive') {
constructor(cause, message = 'Failed to extract the browser archive') {
super(message);
this.message = message;
this.name = this.constructor.name;

View file

@ -6,6 +6,5 @@
export { ensureAllBrowsersDownloaded } from './download';
export { createBrowserDriverFactory } from './create_browser_driver_factory';
export { getDefaultBrowser } from './default_browser';
export { getDefaultChromiumSandboxDisabled } from './default_chromium_sandbox_disabled';
export { PHANTOM, CHROMIUM } from './browser_types';
export { CHROMIUM } from './browser_types';

View file

@ -1,360 +0,0 @@
/*
* 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 { randomBytes } from 'crypto';
import { fromCallback } from 'bluebird';
import { transformFn } from './transform_fn';
export function PhantomDriver({ page, browser, zoom, logger }) {
this.browser = browser;
this.page = page;
this.logger = logger;
const validateInstance = () => {
if (page === false || browser === false) {
throw new Error('Phantom instance is closed: ' + JSON.stringify({ page, browser }));
}
};
const configurePage = (pageOptions) => {
const RESOURCE_TIMEOUT = 5000;
return fromCallback(cb => page.set('resourceTimeout', RESOURCE_TIMEOUT, cb))
.then(() => {
if (zoom) return fromCallback(cb => page.set('zoomFactor', zoom, cb));
})
.then(() => {
if (pageOptions.conditionalHeaders) {
const headers = pageOptions.conditionalHeaders.headers;
const conditions = pageOptions.conditionalHeaders.conditions;
const escape = (str) => {
return str
.replace(/'/g, `\\'`)
.replace(/\\/g, `\\\\`)
.replace(/\r?\n/g, '\\n');
};
// we're using base64 encoding for any user generated values that we need to eval
// to be sure that we're handling these properly
const btoa = (str) => {
return Buffer.from(str).toString('base64');
};
const fn = `function (requestData, networkRequest) {
var log = function (msg) {
if (!page.onConsoleMessage) {
return;
}
page.onConsoleMessage(msg);
};
var parseUrl = function (url) {
var link = document.createElement('a');
link.href = url;
return {
protocol: link.protocol,
port: link.port,
hostname: link.hostname,
pathname: link.pathname,
};
};
var shouldUseCustomHeadersForPort = function (port) {
if ('${escape(conditions.protocol)}' === 'http' && ${conditions.port} === 80) {
return port === undefined || port === null || port === '' || port === '${conditions.port}';
}
if ('${escape(conditions.protocol)}' === 'https' && ${conditions.port} === 443) {
return port === undefined || port === null || port === '' || port === '${conditions.port}';
}
return port === '${conditions.port}';
};
var url = parseUrl(requestData.url);
if (
url.hostname === '${escape(conditions.hostname)}' &&
url.protocol === '${escape(conditions.protocol)}:' &&
shouldUseCustomHeadersForPort(url.port) &&
url.pathname.indexOf('${escape(conditions.basePath)}/') === 0
) {
log('Using custom headers for ' + requestData.url);
${Object.keys(headers).map(key => `networkRequest.setHeader(atob('${btoa(key)}'), atob('${btoa(headers[key])}'));`)
.join('\n')}
} else {
log('No custom headers for ' + requestData.url);
}
}`;
return fromCallback(cb => page.setFn('onResourceRequested', fn, cb));
}
});
};
return {
open(url, pageOptions) {
validateInstance();
return configurePage(pageOptions)
.then(() => logger.debug('Configured page'))
.then(() => fromCallback(cb => page.open(url, cb)))
.then(status => {
logger.debug(`Page opened with status ${status}`);
if (status !== 'success') {
throw new Error(`URL open failed with status [${status}]. Is the server running?`);
}
if (pageOptions.waitForSelector) {
return this.waitForSelector(pageOptions.waitForSelector);
}
});
},
setScrollPosition(position) {
return fromCallback(cb => page.set('scrollPosition', position, cb));
},
setViewport(size) {
return fromCallback(cb => page.set('viewportSize', size, cb));
},
evaluate({ fn, args }) {
validateInstance();
const uniqId = [
randomBytes(6).toString('base64'),
randomBytes(9).toString('base64'),
randomBytes(6).toString('base64'),
].join('-');
const intlPath = require.resolve('intl/dist/Intl.min.js');
const promisePath = require.resolve('bluebird/js/browser/bluebird.js');
return injectPolyfill(
page,
intlPath,
function hasIntl() {
return (window.Intl !== undefined);
}
)
.then(() =>
injectPolyfill(
page,
promisePath,
function hasPromise() {
return (window.Promise !== undefined);
}
))
.then(() => {
return fromCallback(cb => {
page.evaluate(transformFn(evaluateWrapper), transformFn(fn).toString(), uniqId, args, cb);
// The original function is passed here as a string, and eval'd in phantom's context.
// It's then executed in phantom's context and the result is attached to a __reporting
// property on window. Promises can be used, and the result will be handled in the next
// block. If the original function does not return a promise, its result is passed on.
function evaluateWrapper(userFnStr, cbIndex, origArgs) {
// you can't pass a function to phantom, so we pass the string and eval back into a function
let userFn;
eval('userFn = ' + userFnStr); // eslint-disable-line no-eval
// keep a record of the resulting execution for future calls (used when async)
window.__reporting = window.__reporting || {};
window.__reporting[cbIndex] = undefined;
// used to format the response consistently
function done(err, res) {
if (window.__reporting[cbIndex]) {
return;
}
const isErr = err instanceof Error;
if (isErr) {
const keys = Object.getOwnPropertyNames(err);
err = keys.reduce(function copyErr(obj, key) {
obj[key] = err[key];
return obj;
}, {});
}
return window.__reporting[cbIndex] = {
err: err,
res: res,
};
}
try {
// execute the original function
const res = userFn.apply(this, origArgs);
if (res && typeof res.then === 'function') {
// handle async resolution via Promises
res.then((val) => {
done(null, val);
}, (err) => {
if (!(err instanceof Error)) {
err = new Error(err || 'Unspecified error');
}
done(err);
});
return '__promise__';
} else {
// if not given a promise, execute as sync
return done(null, res);
}
} catch (err) {
// any error during execution should be dealt with
return done(err);
}
}
})
.then((res) => {
// if the response is not a promise, pass it along
if (res !== '__promise__') {
return res;
}
// promise response means async, so wait for its resolution
return this.waitFor({
fn: function (cbIndex) {
// resolves when the result object is no longer undefined
return !!window.__reporting[cbIndex];
},
args: [uniqId],
toEqual: true,
})
.then(() => {
// once the original promise is resolved, pass along its value
return fromCallback(cb => {
page.evaluate(function (cbIndex) {
return window.__reporting[cbIndex];
}, uniqId, cb);
});
});
})
.then((res) => {
if (res.err) {
// Make long/normal stack traces work
res.err.name = res.err.name || 'Error';
if (!res.err.stack) {
res.err.stack = res.err.toString();
}
res.err.stack.replace(/\n*$/g, '\n');
if (res.err.stack) {
res.err.toString = function () {
return this.name + ': ' + this.message;
};
}
return Promise.reject(res.err);
}
return res.res;
});
});
},
wait(timeout) {
validateInstance();
return new Promise(resolve => setTimeout(resolve, timeout));
},
waitFor({ fn, args, toEqual }) {
const INTERVAL_TIME = 250;
if (typeof toEqual === 'undefined') return Promise.resolve();
validateInstance();
return new Promise((resolve, reject) => {
const self = this;
(function waitForCheck() {
if (self.killed) {
return;
}
return self.evaluate({ fn, args })
.then(res => {
if (res === toEqual) {
return resolve();
}
setTimeout(waitForCheck, INTERVAL_TIME);
})
.catch(err => {
reject(err);
});
}());
});
},
waitForSelector(selector) {
logger.debug(`PhantomDriver: waitForSelector ${selector}`);
validateInstance();
return this.waitFor({
fn: function (cssSelector) {
return !!document.querySelector(cssSelector);
},
args: [selector],
toEqual: true,
})
.then(() => {
logger.debug(`Finished waiting for selector ${selector}`);
});
},
async screenshot(position) {
const { boundingClientRect, scroll = { x: 0, y: 0 } } = position;
validateInstance();
const zoomFactor = await fromCallback(cb => page.get('zoomFactor', cb));
const previousClipRect = await fromCallback(cb => page.get('clipRect', cb));
const clipRect = {
top: (boundingClientRect.top * zoomFactor) + scroll.y,
left: (boundingClientRect.left * zoomFactor) + scroll.x,
height: boundingClientRect.height * zoomFactor,
width: boundingClientRect.width * zoomFactor
};
await fromCallback(cb => page.set('clipRect', clipRect, cb));
const data = await fromCallback(cb => page.renderBase64('PNG', cb));
await fromCallback(cb => page.set('clipRect', previousClipRect, cb));
return data;
},
};
}
async function injectPolyfill(page, pathToPolyfillFile, checkFunction) {
const hasPolyfill = await fromCallback(cb => {
page.evaluate(checkFunction, cb);
});
if (hasPolyfill) {
return;
}
const status = await fromCallback(cb => page.injectJs(pathToPolyfillFile, cb));
if (!status) {
return Promise.reject(`Failed to load ${pathToPolyfillFile} library`);
}
const hasPolyfillLoaded = await fromCallback(cb => {
page.evaluate(checkFunction, cb);
});
if (!hasPolyfillLoaded) {
return Promise.reject(`Failed to inject ${pathToPolyfillFile}`);
}
}

View file

@ -1,29 +0,0 @@
/*
* 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 { transform as babelTransform } from 'babel-core';
import { memoize } from 'lodash';
const safeWrap = (obj) => {
const code = obj.toString();
return new Function(`return (${code}).apply(null, arguments);`);
};
const transform = (code) => {
const result = babelTransform(code, {
ast: false,
babelrc: false,
presets: [
[ require.resolve('babel-preset-es2015'), { 'modules': false } ]
]
});
return result.code;
};
export const transformFn = memoize((fn) => {
const code = transform(safeWrap(fn));
return safeWrap(code);
});

View file

@ -1,101 +0,0 @@
/*
* 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 * as Rx from 'rxjs';
import { mergeMap } from 'rxjs/operators';
import phantom from '@elastic/node-phantom-simple';
import { getPhantomOptions } from './phantom_options';
import { PhantomDriver } from '../driver';
import { promisify } from 'bluebird';
import { safeChildProcess, exitCodeSuggestion } from '../../safe_child_process';
export class PhantomDriverFactory {
constructor(binaryPath) {
this.binaryPath = binaryPath;
}
type = 'phantom';
create({ bridgePort, viewport, zoom, logger }) {
return Rx.Observable.create(observer => {
let killed = false;
let browser;
let page;
(async () => {
const phantomOptions = getPhantomOptions({
phantomPath: this.binaryPath,
bridgePort
});
try {
browser = await promisify(phantom.create)(phantomOptions);
if (killed) {
return;
}
safeChildProcess(browser.process, observer);
page = await promisify(browser.createPage)();
if (killed) {
return;
}
await promisify(page.set)('viewportSize', viewport);
if (killed) {
return;
}
} catch (err) {
const message = err.toString();
if (message.includes('Phantom immediately exited with: 126')) {
observer.error(new Error('Cannot execute phantom binary, incorrect format'));
return;
}
if (message.includes('Phantom immediately exited with: 127')) {
observer.error(Error('You must install fontconfig and freetype for Reporting to work'));
return;
}
observer.error(err);
return;
}
const exit$ = Rx.fromEvent(browser.process, 'exit').pipe(
mergeMap(([code]) => Rx.throwError(new Error(`Phantom exited with code: ${code}. ${exitCodeSuggestion(code)}`)))
);
const driver = new PhantomDriver({
page,
browser,
zoom,
logger,
});
const driver$ = Rx.of(driver);
const consoleMessage$ = Rx.fromEventPattern(handler => {
page.onConsoleMessage = handler;
}, () => {
page.onConsoleMessage = null;
});
const message$ = Rx.never();
observer.next({
driver$,
message$,
consoleMessage$,
exit$
});
})();
return () => {
killed = true;
};
});
}
}

View file

@ -1,19 +0,0 @@
/*
* 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.
*/
export function getPhantomOptions({ bridgePort, phantomPath }) {
return {
parameters: {
'load-images': true,
'ssl-protocol': 'any',
'ignore-ssl-errors': true,
},
path: phantomPath,
bridge: {
port: bridgePort
},
};
}

View file

@ -1,13 +0,0 @@
/*
* 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 { PhantomDriverFactory } from './driver_factory';
export { paths } from './paths';
export async function createDriverFactory(binaryPath) {
return new PhantomDriverFactory(binaryPath);
}

View file

@ -1,31 +0,0 @@
/*
* 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 path from 'path';
export const paths = {
archivesPath: path.resolve(__dirname, '../../../.phantom'),
baseUrl: 'https://github.com/Medium/phantomjs/releases/download/v2.1.1/',
packages: [{
platforms: ['darwin', 'freebsd', 'openbsd'],
archiveFilename: 'phantomjs-2.1.1-macosx.zip',
archiveChecksum: 'b0c038bd139b9ecaad8fd321070c1651',
rawChecksum: 'bbebe2381435309431c9d4e989aefdeb',
binaryRelativePath: 'phantomjs-2.1.1-macosx/bin/phantomjs',
}, {
platforms: ['linux'],
archiveFilename: 'phantomjs-2.1.1-linux-x86_64.tar.bz2',
archiveChecksum: '1c947d57fce2f21ce0b43fe2ed7cd361',
rawChecksum: '3f4bbbe5acd45494d8e52941936235f2',
binaryRelativePath: 'phantomjs-2.1.1-linux-x86_64/bin/phantomjs'
}, {
platforms: ['win32'],
archiveFilename: 'phantomjs-2.1.1-windows.zip',
archiveChecksum: '4104470d43ddf2a195e8869deef0aa69',
rawChecksum: '339f74c735e683502c43512a508e53d6',
binaryRelativePath: 'phantomjs-2.1.1-windows\\bin\\phantomjs.exe'
}]
};

View file

@ -40,6 +40,7 @@ export function getOrdinalValue(number) {
// defaultMessage: '{number, selectordinal, one{#st} two{#nd} few{#rd} other{#th}}',
// values: { number },
// });
// TODO: https://github.com/elastic/kibana/issues/27136
// Protects against falsey (including 0) values
const num = number && number.toString();

View file

@ -8,8 +8,6 @@ require('@kbn/plugin-helpers').babelRegister();
require('@kbn/test').runTestsCli([
require.resolve('../test/reporting/configs/chromium_api.js'),
require.resolve('../test/reporting/configs/chromium_functional.js'),
// require.resolve('../test/reporting/configs/phantom_api.js'),
// require.resolve('../test/reporting/configs/phantom_functional.js'),
require.resolve('../test/functional/config.js'),
require.resolve('../test/api_integration/config.js'),
require.resolve('../test/saml_api_integration/config.js'),

View file

@ -3,19 +3,14 @@
### Overview
Reporting tests have their own top level test folder because:
- We run the same tests with different kibana.yml settings for your browser choice - Chromium or Phantom.
- Current API tests run with `optimize.enabled=false` flag for performance reasons, but reporting actually requires UI assets.
- Reporting tests take a lot longer than other test types. This separation allows developers to run them in isolation, or to run other functional or API tests without them.
Starting in 7.0 Phantom support will be removed and we can remove the phantom test versions.
### Running the tests
There is more information on running x-pack tests here: https://github.com/elastic/kibana/blob/master/x-pack/README.md#running-functional-tests. Similar to running the API tests, you need to specify a reporting configuration file. Reporting currently has four configuration files you can point to:
There is more information on running x-pack tests here: https://github.com/elastic/kibana/blob/master/x-pack/README.md#running-functional-tests. Similar to running the API tests, you need to specify a reporting configuration file. Reporting currently has two configuration files you can point to:
- test/reporting/configs/chromium_api.js
- test/reporting/configs/phantom_api.js
- test/reporting/configs/chromium_functional.js
- test/reporting/configs/phantom_functional.js
The `api` versions hit the reporting api and ensure report generation completes successfully, but does not verify the output of the reports. This is done in the `functional` test versions, which does a snapshot comparison of the generated URL against a baseline to determine success.
@ -69,8 +64,6 @@ Install with all default options
The functional version of the reporting tests create a few pdf reports and do a snapshot comparison against a couple baselines. The baseline images are stored in `./functional/reports/baseline`.
**Note:** The snapshot comparisons use a threshold due to expected visual difference when running on different browsers and different Operating Systems. This threshold is currently very high because of differences between Chromium and Phantom versions, and also because of some slight bugs with the Phantom version. The bug is [here](https://github.com/elastic/kibana/issues/21485), the issue tracking this very high threshold is [here](https://github.com/elastic/kibana/issues/21486). Once we remove Phantom support in 7.0, we can drop this threshold and hopefully catch more bugs!
#### Updating the baselines
Every now and then visual changes will be made that will require the snapshots to be updated. This is how you go about updating it. I will discuss generating snapshots from chromium since that is the way of the future.

View file

@ -1,35 +0,0 @@
/*
* 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 { OSS_KIBANA_ARCHIVE_PATH, OSS_DATA_ARCHIVE_PATH } from './constants';
export default function ({ loadTestFile, getService }) {
const esArchiver = getService('esArchiver');
const kibanaServer = getService('kibanaServer');
describe('phantom', function () {
this.tags('ciGroup7');
before(async () => {
await esArchiver.load(OSS_KIBANA_ARCHIVE_PATH);
await esArchiver.load(OSS_DATA_ARCHIVE_PATH);
await kibanaServer.uiSettings.update({
'dateFormat:tz': 'UTC',
'defaultIndex': '0bf35f60-3dc9-11e8-8660-4d65aa086b3c'
});
});
after(async () => {
await esArchiver.unload(OSS_KIBANA_ARCHIVE_PATH);
await esArchiver.unload(OSS_DATA_ARCHIVE_PATH);
});
loadTestFile(require.resolve('./bwc_existing_indexes'));
loadTestFile(require.resolve('./bwc_generation_urls'));
loadTestFile(require.resolve('./usage'));
});
}

View file

@ -1,30 +0,0 @@
/*
* 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 { getReportingApiConfig } from './api';
export default async function ({ readConfigFile }) {
const reportingApiConfig = await getReportingApiConfig({ readConfigFile });
return {
...reportingApiConfig,
junit: {
reportName: 'X-Pack Phantom API Reporting Tests',
},
testFiles: [
require.resolve('../api/phantom_tests'),
],
kbnTestServer: {
...reportingApiConfig.kbnTestServer,
serverArgs: [
...reportingApiConfig.kbnTestServer.serverArgs,
`--xpack.reporting.capture.browser.type=phantom`,
`--xpack.spaces.enabled=false`,
],
},
};
}

View file

@ -1,27 +0,0 @@
/*
* 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 { getFunctionalConfig } from './functional';
export default async function ({ readConfigFile }) {
const functionalConfig = await getFunctionalConfig({ readConfigFile });
return {
...functionalConfig,
junit: {
reportName: 'X-Pack Phantom Functional Reporting Tests',
},
testFiles: [require.resolve('../functional')],
kbnTestServer: {
...functionalConfig.kbnTestServer,
serverArgs: [
...functionalConfig.kbnTestServer.serverArgs,
`--xpack.reporting.capture.browser.type=phantom`,
],
},
};
}

View file

@ -85,8 +85,7 @@ export default function ({ getService, getPageObjects }) {
describe.skip('Print Layout', () => {
it('matches baseline report', 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. Also Chromium takes a lot longer to generate a
// report than phantom.
// function is taking about 15 seconds per comparison in jenkins.
this.timeout(360000);
await PageObjects.dashboard.switchToEditMode();
@ -125,8 +124,7 @@ export default function ({ getService, getPageObjects }) {
it('matches same baseline report with margins turned on', 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. Also Chromium takes a lot longer to generate a
// report than phantom.
// function is taking about 15 seconds per comparison in jenkins.
this.timeout(360000);
await PageObjects.dashboard.switchToEditMode();
@ -156,12 +154,13 @@ export default function ({ getService, getPageObjects }) {
});
});
// TODO Re-enable the tests after removing Phantom:
// https://github.com/elastic/kibana/issues/21485
describe.skip('Preserve Layout', () => {
it('matches baseline report', 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. Also Chromium takes a lot longer to generate a
// report than phantom.
// function is taking about 15 seconds per comparison in jenkins.
this.timeout(360000);
await PageObjects.reporting.openPdfReportingPanel();
@ -185,9 +184,6 @@ export default function ({ getService, getPageObjects }) {
config.get('screenshots.directory'),
log
);
// After expected OS differences, the diff count came to be around 350k. Due to
// https://github.com/elastic/kibana/issues/21485 this jumped up to something like 368 when
// comparing the same baseline for chromium and phantom.
expect(percentSimilar).to.be.lessThan(0.05);
});
@ -208,12 +204,13 @@ export default function ({ getService, getPageObjects }) {
});
});
// TODO Re-enable the tests after removing Phantom:
// https://github.com/elastic/kibana/issues/21485
describe.skip('Preserve Layout', () => {
it('matches baseline report', 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. Also Chromium takes a lot longer to generate a
// report than phantom.
// function is taking about 15 seconds per comparison in jenkins.
this.timeout(360000);
await PageObjects.dashboard.switchToEditMode();
@ -249,9 +246,6 @@ export default function ({ getService, getPageObjects }) {
config.get('screenshots.directory'),
log
);
// After expected OS differences, the diff count came to be around 350k. Due to
// https://github.com/elastic/kibana/issues/21485 this jumped up to something like 368 when
// comparing the same baseline for chromium and phantom.
expect(percentSimilar).to.be.lessThan(0.05);
});
@ -310,6 +304,8 @@ export default function ({ getService, getPageObjects }) {
await expectEnabledGenerateReportButton();
});
// TODO Re-enable the tests after removing Phantom:
// https://github.com/elastic/kibana/issues/21485
it.skip('matches baseline report', 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.
@ -332,11 +328,6 @@ export default function ({ getService, getPageObjects }) {
config.get('screenshots.directory'),
log
);
// After expected OS and browser differences, the diff count came up to max 800564 that I saw.
// This is pretty bad. https://github.com/elastic/kibana/issues/21486 is filed to lower this
// which will be much easier when we only support one browser type (chromium instead of phantom).
// The reason this is so high currently is because of a phantom bug:
// https://github.com/elastic/kibana/issues/21485
expect(percentSimilar).to.be.lessThan(0.05);
});
});

View file

@ -847,13 +847,6 @@
resolved "https://registry.yarnpkg.com/@elastic/node-crypto/-/node-crypto-0.1.2.tgz#c18ac282f635e88f041cc1555d806e492ca8f3b1"
integrity sha1-wYrCgvY16I8EHMFVXYBuSSyo87E=
"@elastic/node-phantom-simple@2.2.4":
version "2.2.4"
resolved "https://registry.yarnpkg.com/@elastic/node-phantom-simple/-/node-phantom-simple-2.2.4.tgz#edca5c0001313a8a18b8663169c3a1b812f2251a"
integrity sha1-7cpcAAExOooYuGYxacOhuBLyJRo=
dependencies:
debug "^2.2.0"
"@elastic/numeral@2.3.2":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@elastic/numeral/-/numeral-2.3.2.tgz#06c9ef22f18dd8c2b39ffe353868d4d0c13ea4f9"