diff --git a/docs/migration/migrate_7_0.asciidoc b/docs/migration/migrate_7_0.asciidoc index b242fa907bc9..81870f087ac6 100644 --- a/docs/migration/migrate_7_0.asciidoc +++ b/docs/migration/migrate_7_0.asciidoc @@ -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 diff --git a/docs/reporting/reporting-troubleshooting.asciidoc b/docs/reporting/reporting-troubleshooting.asciidoc index b30fe28bd707..caa74b554215 100644 --- a/docs/reporting/reporting-troubleshooting.asciidoc +++ b/docs/reporting/reporting-troubleshooting.asciidoc @@ -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, diff --git a/docs/settings/reporting-settings.asciidoc b/docs/settings/reporting-settings.asciidoc index 89f4efc8e5fd..81e7c4ef72b0 100644 --- a/docs/settings/reporting-settings.asciidoc +++ b/docs/settings/reporting-settings.asciidoc @@ -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 <> -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]] diff --git a/packages/kbn-babel-preset/webpack_preset.js b/packages/kbn-babel-preset/webpack_preset.js index 511641497c4d..97bfa44a049d 100644 --- a/packages/kbn-babel-preset/webpack_preset.js +++ b/packages/kbn-babel-preset/webpack_preset.js @@ -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, diff --git a/src/dev/build/tasks/clean_tasks.js b/src/dev/build/tasks/clean_tasks.js index 05e99db85977..ec2c3ac7aceb 100644 --- a/src/dev/build/tasks/clean_tasks.js +++ b/src/dev/build/tasks/clean_tasks.js @@ -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; diff --git a/src/optimize/postcss.config.js b/src/optimize/postcss.config.js index 3a983ee2f967..fd4ce222dba5 100644 --- a/src/optimize/postcss.config.js +++ b/src/optimize/postcss.config.js @@ -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 ] }) ] diff --git a/src/ui/public/utils/obj_define.js b/src/ui/public/utils/obj_define.js index 29f4ef126e18..cd7181a28eb7 100644 --- a/src/ui/public/utils/obj_define.js +++ b/src/ui/public/utils/obj_define.js @@ -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]; diff --git a/x-pack/README.md b/x-pack/README.md index a8b8fd8d643e..f1ced9aa33ad 100644 --- a/x-pack/README.md +++ b/x-pack/README.md @@ -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. diff --git a/x-pack/package.json b/x-pack/package.json index ecc4464b53c4..f18d223460c3 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -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", diff --git a/x-pack/plugins/reporting/export_types/common/layouts/preserve_layout.css b/x-pack/plugins/reporting/export_types/common/layouts/preserve_layout.css index d834f382df8b..e82263a73662 100644 --- a/x-pack/plugins/reporting/export_types/common/layouts/preserve_layout.css +++ b/x-pack/plugins/reporting/export_types/common/layouts/preserve_layout.css @@ -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 ****** */ diff --git a/x-pack/plugins/reporting/export_types/common/layouts/print.css b/x-pack/plugins/reporting/export_types/common/layouts/print.css index 4dc1629a444b..a484eaf20681 100644 --- a/x-pack/plugins/reporting/export_types/common/layouts/print.css +++ b/x-pack/plugins/reporting/export_types/common/layouts/print.css @@ -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. */ diff --git a/x-pack/plugins/reporting/index.js b/x-pack/plugins/reporting/index.js index 014a38bc2066..4371764b3b1f 100644 --- a/x-pack/plugins/reporting/index.js +++ b/x-pack/plugins/reporting/index.js @@ -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"), diff --git a/x-pack/plugins/reporting/server/browsers/browser_types.js b/x-pack/plugins/reporting/server/browsers/browser_types.js index 7cc691db47ae..0d67678c183a 100644 --- a/x-pack/plugins/reporting/server/browsers/browser_types.js +++ b/x-pack/plugins/reporting/server/browsers/browser_types.js @@ -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'; \ No newline at end of file +export const CHROMIUM = 'chromium'; diff --git a/x-pack/plugins/reporting/server/browsers/browsers.js b/x-pack/plugins/reporting/server/browsers/browsers.js index c647572d0173..75f06de9a2a4 100644 --- a/x-pack/plugins/reporting/server/browsers/browsers.js +++ b/x-pack/plugins/reporting/server/browsers/browsers.js @@ -5,9 +5,7 @@ */ import * as chromium from './chromium'; -import * as phantom from './phantom'; export const BROWSERS_BY_TYPE = { chromium, - phantom, }; diff --git a/x-pack/plugins/reporting/server/browsers/default_browser.js b/x-pack/plugins/reporting/server/browsers/default_browser.js deleted file mode 100644 index 1745bbdf7483..000000000000 --- a/x-pack/plugins/reporting/server/browsers/default_browser.js +++ /dev/null @@ -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; - } -} diff --git a/x-pack/plugins/reporting/server/browsers/extract/extract_error.js b/x-pack/plugins/reporting/server/browsers/extract/extract_error.js index da1de3fab4b9..a0f3c40dd999 100644 --- a/x-pack/plugins/reporting/server/browsers/extract/extract_error.js +++ b/x-pack/plugins/reporting/server/browsers/extract/extract_error.js @@ -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; diff --git a/x-pack/plugins/reporting/server/browsers/index.js b/x-pack/plugins/reporting/server/browsers/index.js index 5dfe8aea8c3a..3f97a1368246 100644 --- a/x-pack/plugins/reporting/server/browsers/index.js +++ b/x-pack/plugins/reporting/server/browsers/index.js @@ -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'; diff --git a/x-pack/plugins/reporting/server/browsers/phantom/driver/index.js b/x-pack/plugins/reporting/server/browsers/phantom/driver/index.js deleted file mode 100644 index f7e6568cd9a3..000000000000 --- a/x-pack/plugins/reporting/server/browsers/phantom/driver/index.js +++ /dev/null @@ -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}`); - } -} diff --git a/x-pack/plugins/reporting/server/browsers/phantom/driver/transform_fn.js b/x-pack/plugins/reporting/server/browsers/phantom/driver/transform_fn.js deleted file mode 100644 index 8a29b650ee98..000000000000 --- a/x-pack/plugins/reporting/server/browsers/phantom/driver/transform_fn.js +++ /dev/null @@ -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); -}); diff --git a/x-pack/plugins/reporting/server/browsers/phantom/driver_factory/index.js b/x-pack/plugins/reporting/server/browsers/phantom/driver_factory/index.js deleted file mode 100644 index 0a4da006aa80..000000000000 --- a/x-pack/plugins/reporting/server/browsers/phantom/driver_factory/index.js +++ /dev/null @@ -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; - }; - }); - } -} diff --git a/x-pack/plugins/reporting/server/browsers/phantom/driver_factory/phantom_options.js b/x-pack/plugins/reporting/server/browsers/phantom/driver_factory/phantom_options.js deleted file mode 100644 index 5841c78b4b2f..000000000000 --- a/x-pack/plugins/reporting/server/browsers/phantom/driver_factory/phantom_options.js +++ /dev/null @@ -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 - }, - }; -} \ No newline at end of file diff --git a/x-pack/plugins/reporting/server/browsers/phantom/index.js b/x-pack/plugins/reporting/server/browsers/phantom/index.js deleted file mode 100644 index 795e8e365354..000000000000 --- a/x-pack/plugins/reporting/server/browsers/phantom/index.js +++ /dev/null @@ -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); -} diff --git a/x-pack/plugins/reporting/server/browsers/phantom/paths.js b/x-pack/plugins/reporting/server/browsers/phantom/paths.js deleted file mode 100644 index 9af8f0c4f9f2..000000000000 --- a/x-pack/plugins/reporting/server/browsers/phantom/paths.js +++ /dev/null @@ -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' - }] -}; diff --git a/x-pack/plugins/rollup/public/crud_app/services/humanized_numbers.js b/x-pack/plugins/rollup/public/crud_app/services/humanized_numbers.js index 88e97034db70..a3c108a4c1ba 100644 --- a/x-pack/plugins/rollup/public/crud_app/services/humanized_numbers.js +++ b/x-pack/plugins/rollup/public/crud_app/services/humanized_numbers.js @@ -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(); diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index 6949128b57a5..a97748f87c9d 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -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'), diff --git a/x-pack/test/reporting/README.md b/x-pack/test/reporting/README.md index 3c9b5ad9d3ea..30859fa96c01 100644 --- a/x-pack/test/reporting/README.md +++ b/x-pack/test/reporting/README.md @@ -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. diff --git a/x-pack/test/reporting/api/phantom_tests.js b/x-pack/test/reporting/api/phantom_tests.js deleted file mode 100644 index 62993ec40208..000000000000 --- a/x-pack/test/reporting/api/phantom_tests.js +++ /dev/null @@ -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')); - }); -} diff --git a/x-pack/test/reporting/configs/phantom_api.js b/x-pack/test/reporting/configs/phantom_api.js deleted file mode 100644 index e21bbce23e92..000000000000 --- a/x-pack/test/reporting/configs/phantom_api.js +++ /dev/null @@ -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`, - ], - }, - }; -} diff --git a/x-pack/test/reporting/configs/phantom_functional.js b/x-pack/test/reporting/configs/phantom_functional.js deleted file mode 100644 index b0e7ba6f29ce..000000000000 --- a/x-pack/test/reporting/configs/phantom_functional.js +++ /dev/null @@ -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`, - ], - }, - }; -} diff --git a/x-pack/test/reporting/functional/reporting.js b/x-pack/test/reporting/functional/reporting.js index 81da58a921ad..1e3ccea64e21 100644 --- a/x-pack/test/reporting/functional/reporting.js +++ b/x-pack/test/reporting/functional/reporting.js @@ -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); }); }); diff --git a/yarn.lock b/yarn.lock index 8fc9521a518f..9aca24051584 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"