[accessibility] implement no-animation mode, auto enable for functional tests (#21629) (#22203)

This commit is contained in:
Spencer 2018-08-22 14:53:58 -07:00 committed by GitHub
parent b0156abeb5
commit 138f45698c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 228 additions and 50 deletions

View file

@ -510,5 +510,13 @@ export function getUiSettingDefaults() {
is present and sortable in the current index pattern is used.`,
category: ['discover'],
},
'accessibility:disableAnimations': {
name: 'Disable Animations',
value: false,
description: `
Turn off all unnecessary animations in the Kibana UI. Refresh the page to apply the changes.
`,
category: ['accessibility'],
},
};
}

View file

@ -92,7 +92,7 @@ export class EsArchiver {
* @return Promise<Stats>
*/
async rebuildAll() {
return rebuildAllAction({
return await rebuildAllAction({
client: this.client,
dataDir: this.dataDir,
log: this.log
@ -106,6 +106,6 @@ export class EsArchiver {
* @return Promise<Stats>
*/
async loadIfNeeded(name) {
return this.load(name, { skipExisting: true });
return await this.load(name, { skipExisting: true });
}
}

View file

@ -138,7 +138,12 @@ export const schema = Joi.object().keys({
// settings for the esArchiver module
esArchiver: Joi.object().keys({
directory: Joi.string().default(defaultRelativeToConfigPath('fixtures/es_archiver'))
directory: Joi.string().default(defaultRelativeToConfigPath('fixtures/es_archiver')),
}).default(),
// settings for the kibanaServer.uiSettings module
uiSettings: Joi.object().keys({
defaults: Joi.object().unknown(true)
}).default(),
// settings for the screenshots module

View file

@ -31,6 +31,10 @@ export class ProviderCollection {
this._getInstance('Service', name)
)
hasService = name => (
Boolean(this._findProvider('Service', name))
)
getPageObject = name => (
this._getInstance('PageObject', name)
)
@ -68,8 +72,12 @@ export class ProviderCollection {
}
}
_findProvider(type, name) {
return this._providers.find(p => p.type === type && p.name === name);
}
_getProvider(type, name) {
const providerDef = this._providers.find(p => p.type === type && p.name === name);
const providerDef = this._findProvider(type, name);
if (!providerDef) {
throw new Error(`Unknown ${type} "${name}"`);
}
@ -87,6 +95,7 @@ export class ProviderCollection {
if (!instances.has(provider)) {
let instance = provider({
getService: this.getService,
hasService: this.hasService,
getPageObject: this.getPageObject,
getPageObjects: this.getPageObjects,
});

View file

@ -36,3 +36,6 @@ theme.applyTheme('light');
// All Kibana styles inside of the /styles dir
const context = require.context('../styles', false, /[\/\\](?!mixins|variables|_|\.)[^\/\\]+\.less/);
context.keys().forEach(key => context(key));
// manually require non-less files
require('../styles/disable_animations');

View file

@ -6,7 +6,7 @@ exports[`kbnLoadingIndicator is hidden by default 1`] = `
data-test-subj="globalLoadingIndicator-hidden"
>
<div
className="loadingIndicator__bar"
className="loadingIndicator__bar essentialAnimation"
/>
</div>
`;
@ -17,7 +17,7 @@ exports[`kbnLoadingIndicator is visible when loadingCount is > 0 1`] = `
data-test-subj="globalLoadingIndicator"
>
<div
className="loadingIndicator__bar"
className="loadingIndicator__bar essentialAnimation"
/>
</div>
`;

View file

@ -56,7 +56,7 @@ export class LoadingIndicator extends React.Component {
return (
<div className={className} data-test-subj={testSubj}>
<div className="loadingIndicator__bar" />
<div className="loadingIndicator__bar essentialAnimation" />
</div>
);
}

View file

@ -0,0 +1,14 @@
*:not(.essentialAnimation),
*:not(.essentialAnimation):before,
*:not(.essentialAnimation):after {
/**
* set the animation/transition duration to 0s so that animation callbacks are
* still triggered, allowing components that require them to remain functional
*/
-webkit-animation-duration: 0s !important;
animation-duration: 0s !important;
-webkit-transition-duration: 0s !important;
transition-duration: 0s !important;
}

View file

@ -0,0 +1,46 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import chrome from 'ui/chrome';
import disableAnimationsCss from '!!raw-loader!./disable_animations.css';
const uiSettings = chrome.getUiSettingsClient();
// rather than silently ignore when the style element is missing in the tests
// like ui/theme does, we automatically create a style tag because ordering doesn't
// really matter for these styles, they should really take top priority because
// they all use `!important`, and we don't want to silently ignore the possibility
// of accidentally removing the style element from the chrome template.
const styleElement = document.createElement('style');
styleElement.setAttribute('id', 'disableAnimationsCss');
document.head.appendChild(styleElement);
function updateStyleSheet() {
styleElement.textContent = uiSettings.get('accessibility:disableAnimations')
? disableAnimationsCss
: '';
}
updateStyleSheet();
uiSettings.subscribe(({ key }) => {
if (key === 'accessibility:disableAnimations') {
updateStyleSheet();
}
});

View file

@ -0,0 +1,20 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import './disable_animations';

View file

@ -18,12 +18,16 @@
*/
import html from './tooltip.html';
import chrome from 'ui/chrome';
require('ui-bootstrap')
.config(function ($tooltipProvider) {
// we use the uiSettings client because the config service is not available in the config phase
const uiSettings = chrome.getUiSettingsClient();
$tooltipProvider.options({
placement: 'bottom',
animation: true,
animation: !uiSettings.get('accessibility:disableAnimations'),
popupDelay: 150,
appendToBody: false
});

View file

@ -18,8 +18,9 @@
*/
import { EsArchiver } from '../../../src/es_archiver';
import * as KibanaServer from './kibana_server';
export async function EsArchiverProvider({ getService }) {
export function EsArchiverProvider({ getService, hasService }) {
const config = getService('config');
const client = getService('es');
const log = getService('log');
@ -30,9 +31,19 @@ export async function EsArchiverProvider({ getService }) {
const dataDir = config.get('esArchiver.directory');
return new EsArchiver({
const esArchiver = new EsArchiver({
client,
dataDir,
log,
});
if (hasService('kibanaServer')) {
KibanaServer.extendEsArchiver({
esArchiver,
kibanaServer: getService('kibanaServer'),
defaults: config.get('uiSettings.defaults'),
});
}
return esArchiver;
}

View file

@ -0,0 +1,45 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
const ES_ARCHIVER_LOAD_METHODS = ['load', 'loadIfNeeded'];
const KIBANA_INDEX = '.kibana';
export function extendEsArchiver({ esArchiver, kibanaServer, defaults }) {
// only extend the esArchiver if there are default uiSettings to restore
if (!defaults) {
return;
}
ES_ARCHIVER_LOAD_METHODS.forEach(method => {
const originalMethod = esArchiver[method];
esArchiver[method] = async (...args) => {
// esArchiver methods return a stats object, with information about the indexes created
const stats = await originalMethod.apply(esArchiver, args);
// if the kibana index was created by the esArchiver then update the uiSettings
// with the defaults to make sure that they are always in place initially
if (stats[KIBANA_INDEX] && stats[KIBANA_INDEX].created) {
await kibanaServer.uiSettings.update(defaults);
}
return stats;
};
});
}

View file

@ -18,3 +18,4 @@
*/
export { KibanaServerProvider } from './kibana_server';
export { extendEsArchiver } from './extend_es_archiver';

View file

@ -23,7 +23,7 @@ import { KibanaServerStatus } from './status';
import { KibanaServerUiSettings } from './ui_settings';
import { KibanaServerVersion } from './version';
export async function KibanaServerProvider({ getService }) {
export function KibanaServerProvider({ getService }) {
const log = getService('log');
const config = getService('config');
@ -32,7 +32,7 @@ export async function KibanaServerProvider({ getService }) {
const url = formatUrl(config.get('servers.kibana'));
this.status = new KibanaServerStatus(url);
this.version = new KibanaServerVersion(this.status);
this.uiSettings = new KibanaServerUiSettings(url, log, this.version);
this.uiSettings = new KibanaServerUiSettings(url, log, config.get('uiSettings.defaults'));
}
};
}

View file

@ -24,9 +24,9 @@ const MINUTE = 60 * 1000;
const HOUR = 60 * MINUTE;
export class KibanaServerUiSettings {
constructor(url, log, kibanaVersion) {
constructor(url, log, defaults) {
this._log = log;
this._kibanaVersion = kibanaVersion;
this._defaults = defaults;
this._wreck = Wreck.defaults({
headers: { 'kbn-xsrf': 'ftr/services/uiSettings' },
baseUrl: url,
@ -35,9 +35,9 @@ export class KibanaServerUiSettings {
});
}
/*
** Gets defaultIndex from the config doc.
*/
/**
* Gets defaultIndex from the config doc.
*/
async getDefaultIndex() {
const { payload } = await this._wreck.get('/api/kibana/settings');
const defaultIndex = get(payload, 'settings.defaultIndex.userValue');
@ -75,15 +75,18 @@ export class KibanaServerUiSettings {
await this._wreck.post('/api/kibana/settings', {
payload: {
changes: doc
changes: {
...this._defaults,
...doc,
}
}
});
}
/**
* Add fields to the config doc (like setting timezone and defaultIndex)
* @return {Promise} A promise that is resolved when elasticsearch has a response
*/
* Add fields to the config doc (like setting timezone and defaultIndex)
* @return {Promise} A promise that is resolved when elasticsearch has a response
*/
async update(updates) {
this._log.debug('applying update to kibana config: %j', updates);
await this._wreck.post('/api/kibana/settings', {

View file

@ -37,7 +37,7 @@ export default function ({ getService, getPageObjects }) {
describe('add new visualization link', () => {
it('adds a new visualization', async () => {
const originalPanelCount = await PageObjects.dashboard.getPanelCount();
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
await dashboardAddPanel.ensureAddPanelIsShowing();
await dashboardAddPanel.clickAddNewEmbeddableLink();
await PageObjects.visualize.clickAreaChart();

View file

@ -28,7 +28,7 @@ export default function ({ getService, getPageObjects }) {
before(async () => {
await PageObjects.dashboard.gotoDashboardLandingPage();
await PageObjects.dashboard.loadSavedDashboard('few panels');
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
});
describe('move panel', () => {

View file

@ -28,7 +28,7 @@ export default function ({ getService, getPageObjects }) {
before(async () => {
await PageObjects.dashboard.loadSavedDashboard('few panels');
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
originalTitles = await PageObjects.dashboard.getPanelTitles();
});

View file

@ -76,7 +76,7 @@ export default function ({ getService, getPageObjects }) {
async function () {
await PageObjects.dashboard.selectDashboard(dashboardName);
await PageObjects.header.awaitGlobalLoadingIndicatorHidden();
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
await PageObjects.dashboard.saveDashboard(dashboardName);
const isWarningDisplayed = await PageObjects.dashboard.isDuplicateTitleWarningDisplayed();
@ -85,7 +85,7 @@ export default function ({ getService, getPageObjects }) {
);
it('Warns you when you Save as New Dashboard, and the title is a duplicate', async function () {
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
await PageObjects.dashboard.enterDashboardTitleAndClickSave(dashboardName, { saveAsNew: true });
const isWarningDisplayed = await PageObjects.dashboard.isDuplicateTitleWarningDisplayed();
@ -102,7 +102,7 @@ export default function ({ getService, getPageObjects }) {
});
it('Warns when case is different', async function () {
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
await PageObjects.dashboard.enterDashboardTitleAndClickSave(dashboardName.toUpperCase());
// We expect isWarningDisplayed to be open, hence the retry if not found.

View file

@ -52,7 +52,7 @@ export default function ({ getService, getPageObjects }) {
await dashboardAddPanel.addVisualization(AREA_CHART_VIS_NAME);
await PageObjects.dashboard.saveDashboard('Overridden colors');
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
await PageObjects.visualize.openLegendOptionColors('Count');
await PageObjects.visualize.selectNewLegendColorChoice('#EA6460');
@ -100,7 +100,7 @@ export default function ({ getService, getPageObjects }) {
it('Saved search with column changes will not update when the saved object changes', async () => {
await PageObjects.discover.removeHeaderColumn('bytes');
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
await PageObjects.dashboard.saveDashboard('Has local edits');
await PageObjects.header.clickDiscover();
@ -129,7 +129,7 @@ export default function ({ getService, getPageObjects }) {
const tileMapData = await PageObjects.visualize.getInspectorTableData();
await PageObjects.visualize.closeInspector();
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
await dashboardPanelActions.clickEdit();
await PageObjects.visualize.clickMapZoomIn();

View file

@ -59,7 +59,7 @@ export default function ({ getPageObjects, getService }) {
describe('dashboard with stored timed', async function () {
it('is saved with quick time', async function () {
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
await PageObjects.header.setQuickTime('Today');
await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: true });
});
@ -74,7 +74,7 @@ export default function ({ getPageObjects, getService }) {
});
it('is saved with absolute time', async function () {
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
await PageObjects.header.setAbsoluteRange(fromTime, toTime);
await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: true });
});

View file

@ -55,7 +55,7 @@ export default function ({ getService, getPageObjects }) {
});
it('data-shared-item title should update a viz when using a custom panel title', async () => {
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
const CUSTOM_VIS_TITLE = 'ima custom title for a vis!';
await dashboardPanelActions.setCustomPanelTitle(CUSTOM_VIS_TITLE);
await retry.try(async () => {

View file

@ -31,7 +31,7 @@ export default function ({ getService, getPageObjects }) {
});
it('option not available in edit mode', async () => {
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
const exists = await PageObjects.dashboard.fullScreenModeMenuItemExists();
expect(exists).to.be(false);
});

View file

@ -69,7 +69,7 @@ export default function ({ getService, getPageObjects }) {
});
it('are shown in edit mode', async function () {
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
const isContextMenuIconVisible = await dashboardPanelActions.isContextMenuIconVisible();
expect(isContextMenuIconVisible).to.equal(true);
@ -115,7 +115,7 @@ export default function ({ getService, getPageObjects }) {
});
it('in edit mode hides remove icons ', async function () {
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
await dashboardPanelActions.openContextMenu();
const editLinkExists = await dashboardPanelActions.editPanelActionExists();
const removeExists = await dashboardPanelActions.removePanelActionExists();

View file

@ -67,7 +67,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: true });
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
await PageObjects.header.setAbsoluteRange('2013-09-19 06:31:44.000', '2013-09-19 06:31:44.000');
await PageObjects.dashboard.clickCancelOutOfEditMode();
@ -99,7 +99,7 @@ export default function ({ getService, getPageObjects }) {
// This may seem like a pointless line but there was a bug that only arose when the dashboard
// was loaded initially
await PageObjects.dashboard.loadSavedDashboard(dashboardName);
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
let hasFilter = await filterBar.hasFilter('animal', 'dog');
expect(hasFilter).to.be(true);
@ -157,7 +157,7 @@ export default function ({ getService, getPageObjects }) {
const newToTime = '2015-09-19 06:31:44.000';
await PageObjects.header.setAbsoluteRange('2013-09-19 06:31:44.000', '2013-09-19 06:31:44.000');
await PageObjects.dashboard.saveDashboard(dashboardName, true);
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
await PageObjects.header.setAbsoluteRange(newToTime, newToTime);
await PageObjects.dashboard.clickCancelOutOfEditMode();
@ -180,7 +180,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.dashboard.gotoDashboardEditMode(dashboardName);
await PageObjects.dashboard.setTimepickerInDataRange();
await PageObjects.dashboard.saveDashboard(dashboardName, true);
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
await PageObjects.header.setAbsoluteRange('2013-09-19 06:31:44.000', '2013-09-19 06:31:44.000');
const newFromTime = await PageObjects.header.getFromTime();
@ -205,7 +205,7 @@ export default function ({ getService, getPageObjects }) {
it('when time changed is not stored with dashboard', async function () {
await PageObjects.dashboard.gotoDashboardEditMode(dashboardName);
await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: false });
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
await PageObjects.header.setAbsoluteRange('2014-10-19 06:31:44.000', '2014-12-19 06:31:44.000');
await PageObjects.dashboard.clickCancelOutOfEditMode();

View file

@ -122,6 +122,12 @@ export default async function ({ readConfigFile }) {
],
},
uiSettings: {
defaults: {
'accessibility:disableAnimations': true,
},
},
apps: {
status_page: {
pathname: '/status',

View file

@ -173,9 +173,12 @@ export function DashboardPageProvider({ getService, getPageObjects }) {
return await testSubjects.exists('titleDupicateWarnMsg');
}
async clickEdit() {
log.debug('Clicking edit');
return await testSubjects.click('dashboardEditMode');
async switchToEditMode() {
log.debug('Switching to edit mode');
await testSubjects.click('dashboardEditMode');
await retry.waitFor('not in view mode', async () => (
!await this.getIsInViewMode()
));
}
async getIsInViewMode() {
@ -278,7 +281,7 @@ export function DashboardPageProvider({ getService, getPageObjects }) {
async gotoDashboardEditMode(dashboardName) {
await this.loadSavedDashboard(dashboardName);
await this.clickEdit();
await this.switchToEditMode();
}
async renameDashboard(dashName) {

View file

@ -30,7 +30,7 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }) {
log.debug(`createAndAddTSVBVisualization(${name})`);
const inViewMode = await PageObjects.dashboard.getIsInViewMode();
if (inViewMode) {
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
}
await dashboardAddPanel.ensureAddPanelIsShowing();
await dashboardAddPanel.clickAddNewEmbeddableLink();
@ -68,7 +68,7 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }) {
const inViewMode = await PageObjects.dashboard.getIsInViewMode();
if (inViewMode) {
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
}
await dashboardAddPanel.addSavedSearch(name);
}
@ -77,7 +77,7 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }) {
log.debug(`createAndAddMarkdown(${markdown})`);
const inViewMode = await PageObjects.dashboard.getIsInViewMode();
if (inViewMode) {
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
}
await dashboardAddPanel.ensureAddPanelIsShowing();
await dashboardAddPanel.clickAddNewEmbeddableLink();

View file

@ -86,7 +86,7 @@ export default function ({ getService, getPageObjects }) {
// report than phantom.
this.timeout(360000);
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
await PageObjects.reporting.setTimepickerInDataRange();
const visualizations = PageObjects.dashboard.getTestVisualizationNames();
@ -123,7 +123,7 @@ export default function ({ getService, getPageObjects }) {
// report than phantom.
this.timeout(360000);
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.switchToEditMode();
await PageObjects.dashboard.useMargins(true);
await PageObjects.dashboard.saveDashboard('report test');
await PageObjects.reporting.openReportingPanel();