kibana/test/functional/services/find.js
Spencer b51987b9eb
[6.x] Wrap leadfoot elements (#26406) (#26748)
* Wrap leadfoot elements (#26406)

* [ftr] wrap all elements so we can swap out leadfoot without disturbing tests

* save

* [visualize/pie_chart] fix chart legend locator

* [services/leadfoot_element_wrapper] add getTagName function

* [services/browser] adjust moveMouseTo function

* [leadfoot/element] remove old args, document new ones

* [leadfootElementWrapper] add getSpecAttribute() method
2018-12-06 10:58:48 -08:00

325 lines
12 KiB
JavaScript

/*
* 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 { LeadfootElementWrapper } from './lib/leadfoot_element_wrapper';
// Many of our tests use the `exists` functions to determine where the user is. For
// example, you'll see a lot of code like:
// if (!testSubjects.exists('someElementOnPageA')) {
// navigateToPageA();
// }
// If the element doesn't exist, selenium would wait up to defaultFindTimeout for it to
// appear. Because there are many times when we expect it to not be there, we don't want
// to wait the full amount of time, or it would greatly slow our tests down. We used to have
// this value at 1 second, but this caused flakiness because sometimes the element was deemed missing
// only because the page hadn't finished loading.
// The best path forward it to prefer functions like `testSubjects.existOrFail` or
// `testSubjects.missingOrFail` instead of just the `exists` checks, and be deterministic about
// where your user is and what they should click next.
export const WAIT_FOR_EXISTS_TIME = 2500;
export function FindProvider({ getService }) {
const log = getService('log');
const config = getService('config');
const leadfoot = getService('__leadfoot__');
const retry = getService('retry');
const defaultFindTimeout = config.get('timeouts.find');
const wrap = leadfootElement => (
new LeadfootElementWrapper(leadfootElement, leadfoot)
);
const wrapAll = leadfootElements => (
leadfootElements.map(wrap)
);
class Find {
async _withTimeout(timeout, block) {
try {
const leadfootWithTimeout = leadfoot.setFindTimeout(timeout);
return await block(leadfootWithTimeout);
} finally {
leadfoot.setFindTimeout(defaultFindTimeout);
}
}
async _ensureElement(getElementFunction) {
return await retry.try(async () => {
const element = await getElementFunction();
// Calling any method forces a staleness check
element.isEnabled();
return element;
});
}
async _ensureElementWithTimeout(timeout, getElementFunction) {
try {
const leadfootWithTimeout = leadfoot.setFindTimeout(timeout);
return await retry.try(async () => {
const element = await getElementFunction(leadfootWithTimeout);
// Calling any method forces a staleness check
element.isEnabled();
return element;
});
} finally {
leadfoot.setFindTimeout(defaultFindTimeout);
}
}
async byName(selector, timeout = defaultFindTimeout) {
log.debug(`find.byName(${selector})`);
return await this._ensureElementWithTimeout(timeout, async leadfoot => {
return wrap(await leadfoot.findByName(selector));
});
}
async byCssSelector(selector, timeout = defaultFindTimeout) {
log.debug(`findByCssSelector ${selector}`);
return await this._ensureElementWithTimeout(timeout, async leadfoot => {
return wrap(await leadfoot.findByCssSelector(selector));
});
}
async byClassName(selector, timeout = defaultFindTimeout) {
log.debug(`findByCssSelector ${selector}`);
return await this._ensureElementWithTimeout(timeout, async leadfoot => {
return wrap(await leadfoot.findByClassName(selector));
});
}
async activeElement() {
return wrap(await leadfoot.getActiveElement());
}
async setValue(selector, text) {
return await retry.try(async () => {
const element = await this.byCssSelector(selector);
await element.click();
// in case the input element is actually a child of the testSubject, we
// call clearValue() and type() on the element that is focused after
// clicking on the testSubject
const input = await this.activeElement();
await input.clearValue();
await input.type(text);
});
}
async allByCustom(findAllFunction, timeout = defaultFindTimeout) {
return await this._withTimeout(timeout, async leadfoot => {
return await retry.try(async () => {
let elements = await findAllFunction(leadfoot);
if (!elements) elements = [];
// Force isStale checks for all the retrieved elements.
await Promise.all(elements.map(async element => await element.isEnabled()));
return elements;
});
});
}
async allByLinkText(selector, timeout = defaultFindTimeout) {
log.debug('find.allByLinkText: ' + selector);
return await this.allByCustom(
async leadfoot => wrapAll(await leadfoot.findAllByLinkText(selector)),
timeout
);
}
async allByCssSelector(selector, timeout = defaultFindTimeout) {
log.debug('in findAllByCssSelector: ' + selector);
return await this.allByCustom(
async leadfoot => wrapAll(await leadfoot.findAllByCssSelector(selector)),
timeout
);
}
async descendantExistsByCssSelector(selector, parentElement, timeout = WAIT_FOR_EXISTS_TIME) {
log.debug('Find.descendantExistsByCssSelector: ' + selector);
return await this.exists(
async () => wrap(await parentElement.findDisplayedByCssSelector(selector)),
timeout
);
}
async descendantDisplayedByCssSelector(selector, parentElement) {
log.debug('Find.descendantDisplayedByCssSelector: ' + selector);
return await this._ensureElement(
async () => wrap(await parentElement.findDisplayedByCssSelector(selector))
);
}
async allDescendantDisplayedByCssSelector(selector, parentElement) {
log.debug(`Find.allDescendantDisplayedByCssSelector(${selector})`);
const allElements = await parentElement.findAllByCssSelector(selector);
return await Promise.all(
allElements.map((element) => (
this._ensureElement(async () => wrap(element))
))
);
}
async displayedByCssSelector(selector, timeout = defaultFindTimeout) {
log.debug('in displayedByCssSelector: ' + selector);
return await this._ensureElementWithTimeout(timeout, async leadfoot => {
return wrap(await leadfoot.findDisplayedByCssSelector(selector));
});
}
async byLinkText(selector, timeout = defaultFindTimeout) {
log.debug('Find.byLinkText: ' + selector);
return await this._ensureElementWithTimeout(timeout, async leadfoot => {
return wrap(await leadfoot.findByLinkText(selector));
});
}
async findDisplayedByLinkText(selector, timeout = defaultFindTimeout) {
log.debug('Find.byLinkText: ' + selector);
return await this._ensureElementWithTimeout(timeout, async leadfoot => {
return wrap(await leadfoot.findDisplayedByLinkText(selector));
});
}
async byPartialLinkText(partialLinkText, timeout = defaultFindTimeout) {
log.debug(`find.byPartialLinkText(${partialLinkText})`);
return await this._ensureElementWithTimeout(timeout, async leadfoot => {
return wrap(await leadfoot.findByPartialLinkText(partialLinkText));
});
}
async exists(findFunction, timeout = WAIT_FOR_EXISTS_TIME) {
return await this._withTimeout(timeout, async leadfoot => {
try {
await findFunction(leadfoot);
return true;
} catch (error) {
return false;
}
});
}
async existsByLinkText(linkText, timeout = WAIT_FOR_EXISTS_TIME) {
log.debug(`existsByLinkText ${linkText}`);
return await this.exists(async leadfoot => wrap(await leadfoot.findDisplayedByLinkText(linkText)), timeout);
}
async existsByDisplayedByCssSelector(selector, timeout = WAIT_FOR_EXISTS_TIME) {
log.debug(`existsByDisplayedByCssSelector ${selector}`);
return await this.exists(async leadfoot => wrap(await leadfoot.findDisplayedByCssSelector(selector)), timeout);
}
async existsByCssSelector(selector, timeout = WAIT_FOR_EXISTS_TIME) {
log.debug(`existsByCssSelector ${selector}`);
return await this.exists(async leadfoot => wrap(await leadfoot.findByCssSelector(selector)), timeout);
}
async clickByCssSelectorWhenNotDisabled(selector, { timeout } = { timeout: defaultFindTimeout }) {
log.debug(`Find.clickByCssSelectorWhenNotDisabled`);
// Don't wrap this code in a retry, or stale element checks may get caught here and the element
// will never be re-grabbed. Let errors bubble, but continue checking for disabled property until
// it's gone.
const element = await this.byCssSelector(selector, timeout);
await element.moveMouseTo();
const clickIfNotDisabled = async (element, resolve) => {
const disabled = await element.getProperty('disabled');
if (disabled) {
log.debug('Element is disabled, try again');
setTimeout(() => clickIfNotDisabled(element, resolve), 250);
} else {
await element.click();
resolve();
}
};
await new Promise(resolve => clickIfNotDisabled(element, resolve));
}
async clickByPartialLinkText(linkText, timeout = defaultFindTimeout) {
log.debug(`clickByPartialLinkText(${linkText})`);
await retry.try(async () => {
const element = await this.byPartialLinkText(linkText, timeout);
await element.moveMouseTo();
await element.click();
});
}
async clickByLinkText(linkText, timeout = defaultFindTimeout) {
log.debug(`clickByLinkText(${linkText})`);
await retry.try(async () => {
const element = await this.byLinkText(linkText, timeout);
await element.moveMouseTo();
await element.click();
});
}
async byButtonText(buttonText, element = leadfoot, timeout = defaultFindTimeout) {
log.debug(`byButtonText(${buttonText})`);
return await retry.tryForTime(timeout, async () => {
const allButtons = await element.findAllByTagName('button');
const buttonTexts = await Promise.all(allButtons.map(async (el) => {
return el.getVisibleText();
}));
const index = buttonTexts.findIndex(text => text.trim() === buttonText.trim());
if (index === -1) {
throw new Error('Button not found');
}
return wrap(allButtons[index]);
});
}
async clickByButtonText(buttonText, element = leadfoot, timeout = defaultFindTimeout) {
log.debug(`clickByButtonText(${buttonText})`);
await retry.try(async () => {
const button = await this.byButtonText(buttonText, element, timeout);
await button.click();
});
}
async clickByCssSelector(selector, timeout = defaultFindTimeout) {
log.debug(`clickByCssSelector(${selector})`);
await retry.try(async () => {
const element = await this.byCssSelector(selector, timeout);
await element.moveMouseTo();
await element.click();
});
}
async clickByDisplayedLinkText(linkText, timeout = defaultFindTimeout) {
log.debug(`clickByDisplayedLinkText(${linkText})`);
await retry.try(async () => {
const element = await this.findDisplayedByLinkText(linkText, timeout);
await element.moveMouseTo();
await element.click();
});
}
async clickDisplayedByCssSelector(selector, timeout = defaultFindTimeout) {
await retry.try(async () => {
const element = await this.findDisplayedByCssSelector(selector, timeout);
await element.moveMouseTo();
await element.click();
});
}
async waitForDeletedByCssSelector(selector) {
await leadfoot.waitForDeletedByCssSelector(selector);
}
}
return new Find();
}