mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[ftr] migrate "find" service to FtrService class (#100509)
Co-authored-by: spalger <spalger@users.noreply.github.com>
This commit is contained in:
parent
f915b6fe73
commit
28d2343fce
1 changed files with 450 additions and 487 deletions
|
@ -7,511 +7,474 @@
|
|||
*/
|
||||
|
||||
import { WebDriver, WebElement, By, until } from 'selenium-webdriver';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
import { Browsers } from '../remote/browsers';
|
||||
import { FtrService, FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { WebElementWrapper } from '../lib/web_element_wrapper';
|
||||
|
||||
export async function FindProvider({ getService }: FtrProviderContext) {
|
||||
const log = getService('log');
|
||||
const config = getService('config');
|
||||
const { driver, browserType } = await getService('__webdriver__').init();
|
||||
const retry = getService('retry');
|
||||
export class FindService extends FtrService {
|
||||
private readonly log = this.ctx.getService('log');
|
||||
private readonly config = this.ctx.getService('config');
|
||||
private readonly retry = this.ctx.getService('retry');
|
||||
|
||||
const WAIT_FOR_EXISTS_TIME = config.get('timeouts.waitForExists');
|
||||
const POLLING_TIME = 500;
|
||||
const defaultFindTimeout = config.get('timeouts.find');
|
||||
const fixedHeaderHeight = config.get('layout.fixedHeaderHeight');
|
||||
private readonly WAIT_FOR_EXISTS_TIME = this.config.get('timeouts.waitForExists');
|
||||
private readonly POLLING_TIME = 500;
|
||||
private readonly defaultFindTimeout = this.config.get('timeouts.find');
|
||||
private readonly fixedHeaderHeight = this.config.get('layout.fixedHeaderHeight');
|
||||
|
||||
const wrap = (webElement: WebElement | WebElementWrapper, locator: By | null = null) =>
|
||||
WebElementWrapper.create(
|
||||
webElement,
|
||||
locator,
|
||||
driver,
|
||||
defaultFindTimeout,
|
||||
fixedHeaderHeight,
|
||||
log,
|
||||
browserType
|
||||
);
|
||||
public currentWait = this.defaultFindTimeout;
|
||||
|
||||
const wrapAll = (webElements: Array<WebElement | WebElementWrapper>) =>
|
||||
webElements.map((e) => wrap(e));
|
||||
constructor(
|
||||
ctx: FtrProviderContext,
|
||||
private readonly browserType: Browsers,
|
||||
private readonly driver: WebDriver
|
||||
) {
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
const findAndWrap = async (locator: By, timeout: number): Promise<WebElementWrapper> => {
|
||||
const webElement = await driver.wait(until.elementLocated(locator), timeout);
|
||||
return wrap(webElement, locator);
|
||||
};
|
||||
public async byName(
|
||||
selector: string,
|
||||
timeout: number = this.defaultFindTimeout
|
||||
): Promise<WebElementWrapper> {
|
||||
this.log.debug(`Find.byName('${selector}') with timeout=${timeout}`);
|
||||
return await this.findAndWrap(By.name(selector), timeout);
|
||||
}
|
||||
|
||||
class Find {
|
||||
public currentWait = defaultFindTimeout;
|
||||
public async byCssSelector(
|
||||
selector: string,
|
||||
timeout: number = this.defaultFindTimeout
|
||||
): Promise<WebElementWrapper> {
|
||||
this.log.debug(`Find.findByCssSelector('${selector}') with timeout=${timeout}`);
|
||||
return this.findAndWrap(By.css(selector), timeout);
|
||||
}
|
||||
|
||||
public async byName(
|
||||
selector: string,
|
||||
timeout: number = defaultFindTimeout
|
||||
): Promise<WebElementWrapper> {
|
||||
log.debug(`Find.byName('${selector}') with timeout=${timeout}`);
|
||||
return await findAndWrap(By.name(selector), timeout);
|
||||
}
|
||||
public async byXPath(
|
||||
selector: string,
|
||||
timeout: number = this.defaultFindTimeout
|
||||
): Promise<WebElementWrapper> {
|
||||
this.log.debug(`Find.byXPath('${selector}') with timeout=${timeout}`);
|
||||
return this.findAndWrap(By.xpath(selector), timeout);
|
||||
}
|
||||
|
||||
public async byCssSelector(
|
||||
selector: string,
|
||||
timeout: number = defaultFindTimeout
|
||||
): Promise<WebElementWrapper> {
|
||||
log.debug(`Find.findByCssSelector('${selector}') with timeout=${timeout}`);
|
||||
return findAndWrap(By.css(selector), timeout);
|
||||
}
|
||||
public async byClassName(
|
||||
selector: string,
|
||||
timeout: number = this.defaultFindTimeout
|
||||
): Promise<WebElementWrapper> {
|
||||
this.log.debug(`Find.findByClassName('${selector}') with timeout=${timeout}`);
|
||||
return this.findAndWrap(By.className(selector), timeout);
|
||||
}
|
||||
|
||||
public async byXPath(
|
||||
selector: string,
|
||||
timeout: number = defaultFindTimeout
|
||||
): Promise<WebElementWrapper> {
|
||||
log.debug(`Find.byXPath('${selector}') with timeout=${timeout}`);
|
||||
return findAndWrap(By.xpath(selector), timeout);
|
||||
}
|
||||
public async activeElement(): Promise<WebElementWrapper> {
|
||||
return this.wrap(await this.driver.switchTo().activeElement());
|
||||
}
|
||||
|
||||
public async byClassName(
|
||||
selector: string,
|
||||
timeout: number = defaultFindTimeout
|
||||
): Promise<WebElementWrapper> {
|
||||
log.debug(`Find.findByClassName('${selector}') with timeout=${timeout}`);
|
||||
return findAndWrap(By.className(selector), timeout);
|
||||
}
|
||||
public async setValue(selector: string, text: string, topOffset?: number): Promise<void> {
|
||||
this.log.debug(`Find.setValue('${selector}', '${text}')`);
|
||||
return await this.retry.try(async () => {
|
||||
const element = await this.byCssSelector(selector);
|
||||
await element.click(topOffset);
|
||||
|
||||
public async activeElement(): Promise<WebElementWrapper> {
|
||||
return wrap(await driver.switchTo().activeElement());
|
||||
}
|
||||
|
||||
public async setValue(selector: string, text: string, topOffset?: number): Promise<void> {
|
||||
log.debug(`Find.setValue('${selector}', '${text}')`);
|
||||
return await retry.try(async () => {
|
||||
const element = await this.byCssSelector(selector);
|
||||
await element.click(topOffset);
|
||||
|
||||
// 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();
|
||||
if (input) {
|
||||
await input.clearValue();
|
||||
await input.type(text);
|
||||
} else {
|
||||
await element.clearValue();
|
||||
await element.type(text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async selectValue(selector: string, value: string): Promise<void> {
|
||||
log.debug(`Find.selectValue('${selector}', option[value="${value}"]')`);
|
||||
const combobox = await this.byCssSelector(selector);
|
||||
const $ = await combobox.parseDomContent();
|
||||
const text = $(`option[value="${value}"]`).text();
|
||||
await combobox.type(text);
|
||||
}
|
||||
|
||||
public async filterElementIsDisplayed(elements: WebElementWrapper[]) {
|
||||
if (elements.length === 0) {
|
||||
return [];
|
||||
// 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();
|
||||
if (input) {
|
||||
await input.clearValue();
|
||||
await input.type(text);
|
||||
} else {
|
||||
const displayed = [];
|
||||
// tslint:disable-next-line:prefer-for-of
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
const isDisplayed = await elements[i].isDisplayed();
|
||||
if (isDisplayed) {
|
||||
displayed.push(elements[i]);
|
||||
}
|
||||
}
|
||||
return displayed;
|
||||
await element.clearValue();
|
||||
await element.type(text);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async allByCustom(
|
||||
findAllFunction: (drive: WebDriver) => WebElementWrapper[],
|
||||
timeout = defaultFindTimeout
|
||||
): Promise<WebElementWrapper[]> {
|
||||
await this._withTimeout(timeout);
|
||||
return await retry.try(async () => {
|
||||
let elements = await findAllFunction(driver);
|
||||
if (!elements) {
|
||||
elements = [];
|
||||
public async selectValue(selector: string, value: string): Promise<void> {
|
||||
this.log.debug(`Find.selectValue('${selector}', option[value="${value}"]')`);
|
||||
const combobox = await this.byCssSelector(selector);
|
||||
const $ = await combobox.parseDomContent();
|
||||
const text = $(`option[value="${value}"]`).text();
|
||||
await combobox.type(text);
|
||||
}
|
||||
|
||||
public async filterElementIsDisplayed(elements: WebElementWrapper[]) {
|
||||
if (elements.length === 0) {
|
||||
return [];
|
||||
} else {
|
||||
const displayed = [];
|
||||
// tslint:disable-next-line:prefer-for-of
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
const isDisplayed = await elements[i].isDisplayed();
|
||||
if (isDisplayed) {
|
||||
displayed.push(elements[i]);
|
||||
}
|
||||
// Force isStale checks for all the retrieved elements.
|
||||
await Promise.all(elements.map(async (element) => await element.isEnabled()));
|
||||
await this._withTimeout(defaultFindTimeout);
|
||||
return elements;
|
||||
});
|
||||
}
|
||||
|
||||
public async allByLinkText(
|
||||
selector: string,
|
||||
timeout: number = defaultFindTimeout
|
||||
): Promise<WebElementWrapper[]> {
|
||||
log.debug(`Find.allByLinkText('${selector}') with timeout=${timeout}`);
|
||||
await this._withTimeout(timeout);
|
||||
const elements = await driver.findElements(By.linkText(selector));
|
||||
await this._withTimeout(defaultFindTimeout);
|
||||
return wrapAll(elements);
|
||||
}
|
||||
|
||||
public async allByButtonText(
|
||||
buttonText: string,
|
||||
element: WebDriver | WebElement | WebElementWrapper = driver,
|
||||
timeout: number = defaultFindTimeout
|
||||
): Promise<string[]> {
|
||||
log.debug(`Find.byButtonText('${buttonText}') with timeout=${timeout}`);
|
||||
return await retry.tryForTime(timeout, async () => {
|
||||
// tslint:disable-next-line:variable-name
|
||||
const _element = element instanceof WebElementWrapper ? element._webElement : element;
|
||||
await this._withTimeout(0);
|
||||
const allButtons = wrapAll(await _element.findElements(By.tagName('button')));
|
||||
await this._withTimeout(defaultFindTimeout);
|
||||
const buttonTexts = await Promise.all(
|
||||
allButtons.map(async (el) => {
|
||||
return el.getVisibleText();
|
||||
})
|
||||
);
|
||||
return buttonTexts.filter((text) => text.trim() === buttonText.trim());
|
||||
});
|
||||
}
|
||||
|
||||
public async allByCssSelector(
|
||||
selector: string,
|
||||
timeout: number = defaultFindTimeout
|
||||
): Promise<WebElementWrapper[]> {
|
||||
log.debug(`Find.allByCssSelector('${selector}') with timeout=${timeout}`);
|
||||
await this._withTimeout(timeout);
|
||||
const elements = await driver.findElements(By.css(selector));
|
||||
await this._withTimeout(defaultFindTimeout);
|
||||
return wrapAll(elements);
|
||||
}
|
||||
|
||||
public async descendantExistsByCssSelector(
|
||||
selector: string,
|
||||
parentElement: WebElementWrapper,
|
||||
timeout: number = WAIT_FOR_EXISTS_TIME
|
||||
): Promise<boolean> {
|
||||
log.debug(`Find.descendantExistsByCssSelector('${selector}') with timeout=${timeout}`);
|
||||
const els = await parentElement._webElement.findElements(By.css(selector));
|
||||
return await this.exists(async () => wrapAll(els), timeout);
|
||||
}
|
||||
|
||||
public async descendantDisplayedByCssSelector(
|
||||
selector: string,
|
||||
parentElement: WebElementWrapper
|
||||
): Promise<WebElementWrapper | never> {
|
||||
log.debug(`Find.descendantDisplayedByCssSelector('${selector}')`);
|
||||
const element = await parentElement._webElement.findElement(By.css(selector));
|
||||
const descendant = wrap(element, By.css(selector));
|
||||
const isDisplayed = await descendant.isDisplayed();
|
||||
if (isDisplayed) {
|
||||
return descendant;
|
||||
} else {
|
||||
throw new Error(`Element "${selector}" is not displayed`);
|
||||
}
|
||||
}
|
||||
|
||||
public async allDescendantDisplayedByCssSelector(
|
||||
selector: string,
|
||||
parentElement: WebElementWrapper
|
||||
): Promise<WebElementWrapper[]> {
|
||||
log.debug(`Find.allDescendantDisplayedByCssSelector('${selector}')`);
|
||||
const allElements = await wrapAll(
|
||||
await parentElement._webElement.findElements(By.css(selector))
|
||||
);
|
||||
return await this.filterElementIsDisplayed(allElements);
|
||||
}
|
||||
|
||||
public async allDescendantDisplayedByTagName(
|
||||
tagName: string,
|
||||
parentElement: WebElementWrapper
|
||||
): Promise<WebElementWrapper[]> {
|
||||
log.debug(`Find.allDescendantDisplayedByTagName('${tagName}')`);
|
||||
const allElements = await wrapAll(
|
||||
await parentElement._webElement.findElements(By.tagName(tagName))
|
||||
);
|
||||
return await this.filterElementIsDisplayed(allElements);
|
||||
}
|
||||
|
||||
public async displayedByLinkText(
|
||||
linkText: string,
|
||||
timeout: number = defaultFindTimeout
|
||||
): Promise<WebElementWrapper> {
|
||||
log.debug(`Find.displayedByLinkText('${linkText}') with timeout=${timeout}`);
|
||||
const element = await this.byLinkText(linkText, timeout);
|
||||
log.debug(`Wait for element become visible: ${linkText} with timeout=${timeout}`);
|
||||
await driver.wait(until.elementIsVisible(element._webElement), timeout);
|
||||
return wrap(element, By.linkText(linkText));
|
||||
}
|
||||
|
||||
public async displayedByCssSelector(
|
||||
selector: string,
|
||||
timeout: number = defaultFindTimeout
|
||||
): Promise<WebElementWrapper> {
|
||||
log.debug(`Find.displayedByCssSelector(${selector})`);
|
||||
const element = await this.byCssSelector(selector, timeout);
|
||||
log.debug(`Wait for element become visible: ${selector} with timeout=${timeout}`);
|
||||
await driver.wait(until.elementIsVisible(element._webElement), timeout);
|
||||
return wrap(element, By.css(selector));
|
||||
}
|
||||
|
||||
public async byLinkText(
|
||||
selector: string,
|
||||
timeout: number = defaultFindTimeout
|
||||
): Promise<WebElementWrapper> {
|
||||
log.debug(`Find.byLinkText('${selector}') with timeout=${timeout}`);
|
||||
return findAndWrap(By.linkText(selector), timeout);
|
||||
}
|
||||
|
||||
public async byPartialLinkText(
|
||||
partialLinkText: string,
|
||||
timeout: number = defaultFindTimeout
|
||||
): Promise<WebElementWrapper> {
|
||||
log.debug(`Find.byPartialLinkText('${partialLinkText}') with timeout=${timeout}`);
|
||||
return findAndWrap(By.partialLinkText(partialLinkText), timeout);
|
||||
}
|
||||
|
||||
public async exists(
|
||||
findFunction: (
|
||||
el: WebDriver
|
||||
) =>
|
||||
| WebElementWrapper
|
||||
| WebElementWrapper[]
|
||||
| Promise<WebElementWrapper[]>
|
||||
| Promise<WebElementWrapper>,
|
||||
timeout: number = WAIT_FOR_EXISTS_TIME
|
||||
): Promise<boolean> {
|
||||
await this._withTimeout(timeout);
|
||||
try {
|
||||
const found = await findFunction(driver);
|
||||
await this._withTimeout(defaultFindTimeout);
|
||||
if (Array.isArray(found)) {
|
||||
return found.length > 0;
|
||||
} else {
|
||||
return found instanceof WebElementWrapper;
|
||||
}
|
||||
} catch (err) {
|
||||
await this._withTimeout(defaultFindTimeout);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async existsByLinkText(
|
||||
linkText: string,
|
||||
timeout: number = WAIT_FOR_EXISTS_TIME
|
||||
): Promise<boolean> {
|
||||
log.debug(`Find.existsByLinkText('${linkText}') with timeout=${timeout}`);
|
||||
return await this.exists(
|
||||
async (drive) => wrapAll(await drive.findElements(By.linkText(linkText))),
|
||||
timeout
|
||||
);
|
||||
}
|
||||
|
||||
public async existsByDisplayedByCssSelector(
|
||||
selector: string,
|
||||
timeout: number = WAIT_FOR_EXISTS_TIME
|
||||
): Promise<boolean> {
|
||||
log.debug(`Find.existsByDisplayedByCssSelector('${selector}') with timeout=${timeout}`);
|
||||
try {
|
||||
await retry.tryForTime(timeout, async () => {
|
||||
// make sure that the find timeout is not longer than the retry timeout
|
||||
await this._withTimeout(Math.min(timeout, WAIT_FOR_EXISTS_TIME));
|
||||
const elements = await driver.findElements(By.css(selector));
|
||||
await this._withTimeout(defaultFindTimeout);
|
||||
const displayed = await this.filterElementIsDisplayed(wrapAll(elements));
|
||||
if (displayed.length === 0) {
|
||||
throw new Error(`${selector} is not displayed`);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
await this._withTimeout(defaultFindTimeout);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public async existsByCssSelector(
|
||||
selector: string,
|
||||
timeout: number = WAIT_FOR_EXISTS_TIME
|
||||
): Promise<boolean> {
|
||||
log.debug(`Find.existsByCssSelector('${selector}') with timeout=${timeout}`);
|
||||
return await this.exists(async (drive) => {
|
||||
return wrapAll(await drive.findElements(By.css(selector)));
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
public async clickByCssSelectorWhenNotDisabled(
|
||||
selector: string,
|
||||
{ timeout } = { timeout: defaultFindTimeout }
|
||||
): Promise<void> {
|
||||
log.debug(`Find.clickByCssSelectorWhenNotDisabled('${selector}') with timeout=${timeout}`);
|
||||
|
||||
// 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();
|
||||
await driver.wait(until.elementIsEnabled(element._webElement), timeout);
|
||||
await element.click();
|
||||
}
|
||||
|
||||
public async clickByPartialLinkText(
|
||||
linkText: string,
|
||||
timeout: number = defaultFindTimeout
|
||||
): Promise<void> {
|
||||
log.debug(`Find.clickByPartialLinkText('${linkText}') with timeout=${timeout}`);
|
||||
await retry.try(async () => {
|
||||
const element = await this.byPartialLinkText(linkText, timeout);
|
||||
await element.moveMouseTo();
|
||||
await element.click();
|
||||
});
|
||||
}
|
||||
|
||||
public async clickByLinkText(
|
||||
linkText: string,
|
||||
timeout: number = defaultFindTimeout
|
||||
): Promise<void> {
|
||||
log.debug(`Find.clickByLinkText('${linkText}') with timeout=${timeout}`);
|
||||
await retry.try(async () => {
|
||||
const element = await this.byLinkText(linkText, timeout);
|
||||
await element.moveMouseTo();
|
||||
await element.click();
|
||||
});
|
||||
}
|
||||
|
||||
public async byButtonText(
|
||||
buttonText: string,
|
||||
element: WebDriver | WebElement | WebElementWrapper = driver,
|
||||
timeout: number = defaultFindTimeout
|
||||
): Promise<WebElementWrapper> {
|
||||
log.debug(`Find.byButtonText('${buttonText}') with timeout=${timeout}`);
|
||||
return await retry.tryForTime(timeout, async () => {
|
||||
// tslint:disable-next-line:variable-name
|
||||
const _element = element instanceof WebElementWrapper ? element._webElement : element;
|
||||
const allButtons = wrapAll(await _element.findElements(By.tagName('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 allButtons[index];
|
||||
});
|
||||
}
|
||||
|
||||
public async clickByButtonText(
|
||||
buttonText: string,
|
||||
element: WebDriver | WebElement | WebElementWrapper = driver,
|
||||
timeout: number = defaultFindTimeout
|
||||
): Promise<void> {
|
||||
log.debug(`Find.clickByButtonText('${buttonText}') with timeout=${timeout}`);
|
||||
await retry.try(async () => {
|
||||
const button = await this.byButtonText(buttonText, element, timeout);
|
||||
await button.click();
|
||||
});
|
||||
}
|
||||
|
||||
public async clickByCssSelector(
|
||||
selector: string,
|
||||
timeout: number = defaultFindTimeout,
|
||||
topOffset?: number
|
||||
): Promise<void> {
|
||||
log.debug(`Find.clickByCssSelector('${selector}') with timeout=${timeout}`);
|
||||
await retry.try(async () => {
|
||||
const element = await this.byCssSelector(selector, timeout);
|
||||
if (element) {
|
||||
// await element.moveMouseTo();
|
||||
await element.click(topOffset);
|
||||
} else {
|
||||
throw new Error(`Element with css='${selector}' is not found`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async clickByDisplayedLinkText(
|
||||
linkText: string,
|
||||
timeout: number = defaultFindTimeout
|
||||
): Promise<void> {
|
||||
log.debug(`Find.clickByDisplayedLinkText('${linkText}') with timeout=${timeout}`);
|
||||
await retry.try(async () => {
|
||||
const element = await this.displayedByLinkText(linkText, timeout);
|
||||
if (element) {
|
||||
await element.moveMouseTo();
|
||||
await element.click();
|
||||
} else {
|
||||
throw new Error(`Element with linkText='${linkText}' is not found`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async clickDisplayedByCssSelector(
|
||||
selector: string,
|
||||
timeout: number = defaultFindTimeout
|
||||
) {
|
||||
log.debug(`Find.clickDisplayedByCssSelector('${selector}') with timeout=${timeout}`);
|
||||
await retry.try(async () => {
|
||||
const element = await this.displayedByCssSelector(selector, timeout);
|
||||
if (element) {
|
||||
await element.moveMouseTo();
|
||||
await element.click();
|
||||
} else {
|
||||
throw new Error(`Element with css='${selector}' is not found`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async waitForDeletedByCssSelector(
|
||||
selector: string,
|
||||
timeout: number = defaultFindTimeout
|
||||
) {
|
||||
log.debug(`Find.waitForDeletedByCssSelector('${selector}') with timeout=${timeout}`);
|
||||
await this._withTimeout(POLLING_TIME);
|
||||
await driver.wait(
|
||||
async () => {
|
||||
const found = await driver.findElements(By.css(selector));
|
||||
return found.length === 0;
|
||||
},
|
||||
timeout,
|
||||
`The element ${selector} was still present when it should have disappeared.`
|
||||
);
|
||||
await this._withTimeout(defaultFindTimeout);
|
||||
}
|
||||
|
||||
public async waitForAttributeToChange(
|
||||
selector: string,
|
||||
attribute: string,
|
||||
value: string
|
||||
): Promise<void> {
|
||||
log.debug(`Find.waitForAttributeToChange('${selector}', '${attribute}', '${value}')`);
|
||||
await retry.waitFor(`${attribute} to equal "${value}"`, async () => {
|
||||
const el = await this.byCssSelector(selector);
|
||||
return value === (await el.getAttribute(attribute));
|
||||
});
|
||||
}
|
||||
|
||||
public async waitForElementStale(
|
||||
element: WebElementWrapper,
|
||||
timeout: number = defaultFindTimeout
|
||||
): Promise<void> {
|
||||
log.debug(`Find.waitForElementStale with timeout=${timeout}`);
|
||||
await driver.wait(until.stalenessOf(element._webElement), timeout);
|
||||
}
|
||||
|
||||
public async waitForElementHidden(
|
||||
element: WebElementWrapper,
|
||||
timeout: number = defaultFindTimeout
|
||||
) {
|
||||
log.debug(`Find.waitForElementHidden with timeout=${timeout}`);
|
||||
await driver.wait(until.elementIsNotVisible(element._webElement), timeout);
|
||||
}
|
||||
|
||||
private async _withTimeout(timeout: number) {
|
||||
if (timeout !== this.currentWait) {
|
||||
this.currentWait = timeout;
|
||||
await driver.manage().setTimeouts({ implicit: timeout });
|
||||
}
|
||||
return displayed;
|
||||
}
|
||||
}
|
||||
|
||||
return new Find();
|
||||
public async allByCssSelector(
|
||||
selector: string,
|
||||
timeout: number = this.defaultFindTimeout
|
||||
): Promise<WebElementWrapper[]> {
|
||||
this.log.debug(`Find.allByCssSelector('${selector}') with timeout=${timeout}`);
|
||||
await this._withTimeout(timeout);
|
||||
const elements = await this.driver.findElements(By.css(selector));
|
||||
await this._withTimeout(this.defaultFindTimeout);
|
||||
return this.wrapAll(elements);
|
||||
}
|
||||
|
||||
public async descendantExistsByCssSelector(
|
||||
selector: string,
|
||||
parentElement: WebElementWrapper,
|
||||
timeout: number = this.WAIT_FOR_EXISTS_TIME
|
||||
): Promise<boolean> {
|
||||
this.log.debug(`Find.descendantExistsByCssSelector('${selector}') with timeout=${timeout}`);
|
||||
const els = await parentElement._webElement.findElements(By.css(selector));
|
||||
return await this.exists(async () => this.wrapAll(els), timeout);
|
||||
}
|
||||
|
||||
public async descendantDisplayedByCssSelector(
|
||||
selector: string,
|
||||
parentElement: WebElementWrapper
|
||||
): Promise<WebElementWrapper | never> {
|
||||
this.log.debug(`Find.descendantDisplayedByCssSelector('${selector}')`);
|
||||
const element = await parentElement._webElement.findElement(By.css(selector));
|
||||
const descendant = this.wrap(element, By.css(selector));
|
||||
const isDisplayed = await descendant.isDisplayed();
|
||||
if (isDisplayed) {
|
||||
return descendant;
|
||||
} else {
|
||||
throw new Error(`Element "${selector}" is not displayed`);
|
||||
}
|
||||
}
|
||||
|
||||
public async allDescendantDisplayedByCssSelector(
|
||||
selector: string,
|
||||
parentElement: WebElementWrapper
|
||||
): Promise<WebElementWrapper[]> {
|
||||
this.log.debug(`Find.allDescendantDisplayedByCssSelector('${selector}')`);
|
||||
const allElements = await this.wrapAll(
|
||||
await parentElement._webElement.findElements(By.css(selector))
|
||||
);
|
||||
return await this.filterElementIsDisplayed(allElements);
|
||||
}
|
||||
|
||||
public async allDescendantDisplayedByTagName(
|
||||
tagName: string,
|
||||
parentElement: WebElementWrapper
|
||||
): Promise<WebElementWrapper[]> {
|
||||
this.log.debug(`Find.allDescendantDisplayedByTagName('${tagName}')`);
|
||||
const allElements = await this.wrapAll(
|
||||
await parentElement._webElement.findElements(By.tagName(tagName))
|
||||
);
|
||||
return await this.filterElementIsDisplayed(allElements);
|
||||
}
|
||||
|
||||
public async displayedByLinkText(
|
||||
linkText: string,
|
||||
timeout: number = this.defaultFindTimeout
|
||||
): Promise<WebElementWrapper> {
|
||||
this.log.debug(`Find.displayedByLinkText('${linkText}') with timeout=${timeout}`);
|
||||
const element = await this.byLinkText(linkText, timeout);
|
||||
this.log.debug(`Wait for element become visible: ${linkText} with timeout=${timeout}`);
|
||||
await this.driver.wait(until.elementIsVisible(element._webElement), timeout);
|
||||
return this.wrap(element, By.linkText(linkText));
|
||||
}
|
||||
|
||||
public async displayedByCssSelector(
|
||||
selector: string,
|
||||
timeout: number = this.defaultFindTimeout
|
||||
): Promise<WebElementWrapper> {
|
||||
this.log.debug(`Find.displayedByCssSelector(${selector})`);
|
||||
const element = await this.byCssSelector(selector, timeout);
|
||||
this.log.debug(`Wait for element become visible: ${selector} with timeout=${timeout}`);
|
||||
await this.driver.wait(until.elementIsVisible(element._webElement), timeout);
|
||||
return this.wrap(element, By.css(selector));
|
||||
}
|
||||
|
||||
public async byLinkText(
|
||||
selector: string,
|
||||
timeout: number = this.defaultFindTimeout
|
||||
): Promise<WebElementWrapper> {
|
||||
this.log.debug(`Find.byLinkText('${selector}') with timeout=${timeout}`);
|
||||
return this.findAndWrap(By.linkText(selector), timeout);
|
||||
}
|
||||
|
||||
public async byPartialLinkText(
|
||||
partialLinkText: string,
|
||||
timeout: number = this.defaultFindTimeout
|
||||
): Promise<WebElementWrapper> {
|
||||
this.log.debug(`Find.byPartialLinkText('${partialLinkText}') with timeout=${timeout}`);
|
||||
return this.findAndWrap(By.partialLinkText(partialLinkText), timeout);
|
||||
}
|
||||
|
||||
public async exists(
|
||||
findFunction: (
|
||||
el: WebDriver
|
||||
) =>
|
||||
| WebElementWrapper
|
||||
| WebElementWrapper[]
|
||||
| Promise<WebElementWrapper[]>
|
||||
| Promise<WebElementWrapper>,
|
||||
timeout: number = this.WAIT_FOR_EXISTS_TIME
|
||||
): Promise<boolean> {
|
||||
await this._withTimeout(timeout);
|
||||
try {
|
||||
const found = await findFunction(this.driver);
|
||||
await this._withTimeout(this.defaultFindTimeout);
|
||||
if (Array.isArray(found)) {
|
||||
return found.length > 0;
|
||||
} else {
|
||||
return found instanceof WebElementWrapper;
|
||||
}
|
||||
} catch (err) {
|
||||
await this._withTimeout(this.defaultFindTimeout);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async existsByLinkText(
|
||||
linkText: string,
|
||||
timeout: number = this.WAIT_FOR_EXISTS_TIME
|
||||
): Promise<boolean> {
|
||||
this.log.debug(`Find.existsByLinkText('${linkText}') with timeout=${timeout}`);
|
||||
return await this.exists(
|
||||
async (drive) => this.wrapAll(await drive.findElements(By.linkText(linkText))),
|
||||
timeout
|
||||
);
|
||||
}
|
||||
|
||||
public async existsByDisplayedByCssSelector(
|
||||
selector: string,
|
||||
timeout: number = this.WAIT_FOR_EXISTS_TIME
|
||||
): Promise<boolean> {
|
||||
this.log.debug(`Find.existsByDisplayedByCssSelector('${selector}') with timeout=${timeout}`);
|
||||
try {
|
||||
await this.retry.tryForTime(timeout, async () => {
|
||||
// make sure that the find timeout is not longer than the retry timeout
|
||||
await this._withTimeout(Math.min(timeout, this.WAIT_FOR_EXISTS_TIME));
|
||||
const elements = await this.driver.findElements(By.css(selector));
|
||||
await this._withTimeout(this.defaultFindTimeout);
|
||||
const displayed = await this.filterElementIsDisplayed(this.wrapAll(elements));
|
||||
if (displayed.length === 0) {
|
||||
throw new Error(`${selector} is not displayed`);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
await this._withTimeout(this.defaultFindTimeout);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public async existsByCssSelector(
|
||||
selector: string,
|
||||
timeout: number = this.WAIT_FOR_EXISTS_TIME
|
||||
): Promise<boolean> {
|
||||
this.log.debug(`Find.existsByCssSelector('${selector}') with timeout=${timeout}`);
|
||||
return await this.exists(async (drive) => {
|
||||
return this.wrapAll(await drive.findElements(By.css(selector)));
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
public async clickByCssSelectorWhenNotDisabled(
|
||||
selector: string,
|
||||
{ timeout } = { timeout: this.defaultFindTimeout }
|
||||
): Promise<void> {
|
||||
this.log.debug(`Find.clickByCssSelectorWhenNotDisabled('${selector}') with timeout=${timeout}`);
|
||||
|
||||
// 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();
|
||||
await this.driver.wait(until.elementIsEnabled(element._webElement), timeout);
|
||||
await element.click();
|
||||
}
|
||||
|
||||
public async clickByPartialLinkText(
|
||||
linkText: string,
|
||||
timeout: number = this.defaultFindTimeout
|
||||
): Promise<void> {
|
||||
this.log.debug(`Find.clickByPartialLinkText('${linkText}') with timeout=${timeout}`);
|
||||
await this.retry.try(async () => {
|
||||
const element = await this.byPartialLinkText(linkText, timeout);
|
||||
await element.moveMouseTo();
|
||||
await element.click();
|
||||
});
|
||||
}
|
||||
|
||||
public async clickByLinkText(
|
||||
linkText: string,
|
||||
timeout: number = this.defaultFindTimeout
|
||||
): Promise<void> {
|
||||
this.log.debug(`Find.clickByLinkText('${linkText}') with timeout=${timeout}`);
|
||||
await this.retry.try(async () => {
|
||||
const element = await this.byLinkText(linkText, timeout);
|
||||
await element.moveMouseTo();
|
||||
await element.click();
|
||||
});
|
||||
}
|
||||
|
||||
public async byButtonText(
|
||||
buttonText: string,
|
||||
element: WebDriver | WebElement | WebElementWrapper = this.driver,
|
||||
timeout: number = this.defaultFindTimeout
|
||||
): Promise<WebElementWrapper> {
|
||||
this.log.debug(`Find.byButtonText('${buttonText}') with timeout=${timeout}`);
|
||||
return await this.retry.tryForTime(timeout, async () => {
|
||||
// tslint:disable-next-line:variable-name
|
||||
const _element = element instanceof WebElementWrapper ? element._webElement : element;
|
||||
const allButtons = this.wrapAll(await _element.findElements(By.tagName('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 allButtons[index];
|
||||
});
|
||||
}
|
||||
|
||||
public async clickByButtonText(
|
||||
buttonText: string,
|
||||
element: WebDriver | WebElement | WebElementWrapper = this.driver,
|
||||
timeout: number = this.defaultFindTimeout
|
||||
): Promise<void> {
|
||||
this.log.debug(`Find.clickByButtonText('${buttonText}') with timeout=${timeout}`);
|
||||
await this.retry.try(async () => {
|
||||
const button = await this.byButtonText(buttonText, element, timeout);
|
||||
await button.click();
|
||||
});
|
||||
}
|
||||
|
||||
public async clickByCssSelector(
|
||||
selector: string,
|
||||
timeout: number = this.defaultFindTimeout,
|
||||
topOffset?: number
|
||||
): Promise<void> {
|
||||
this.log.debug(`Find.clickByCssSelector('${selector}') with timeout=${timeout}`);
|
||||
await this.retry.try(async () => {
|
||||
const element = await this.byCssSelector(selector, timeout);
|
||||
if (element) {
|
||||
// await element.moveMouseTo();
|
||||
await element.click(topOffset);
|
||||
} else {
|
||||
throw new Error(`Element with css='${selector}' is not found`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async clickByDisplayedLinkText(
|
||||
linkText: string,
|
||||
timeout: number = this.defaultFindTimeout
|
||||
): Promise<void> {
|
||||
this.log.debug(`Find.clickByDisplayedLinkText('${linkText}') with timeout=${timeout}`);
|
||||
await this.retry.try(async () => {
|
||||
const element = await this.displayedByLinkText(linkText, timeout);
|
||||
if (element) {
|
||||
await element.moveMouseTo();
|
||||
await element.click();
|
||||
} else {
|
||||
throw new Error(`Element with linkText='${linkText}' is not found`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async clickDisplayedByCssSelector(
|
||||
selector: string,
|
||||
timeout: number = this.defaultFindTimeout
|
||||
) {
|
||||
this.log.debug(`Find.clickDisplayedByCssSelector('${selector}') with timeout=${timeout}`);
|
||||
await this.retry.try(async () => {
|
||||
const element = await this.displayedByCssSelector(selector, timeout);
|
||||
if (element) {
|
||||
await element.moveMouseTo();
|
||||
await element.click();
|
||||
} else {
|
||||
throw new Error(`Element with css='${selector}' is not found`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async waitForDeletedByCssSelector(
|
||||
selector: string,
|
||||
timeout: number = this.defaultFindTimeout
|
||||
) {
|
||||
this.log.debug(`Find.waitForDeletedByCssSelector('${selector}') with timeout=${timeout}`);
|
||||
await this._withTimeout(this.POLLING_TIME);
|
||||
await this.driver.wait(
|
||||
async () => {
|
||||
const found = await this.driver.findElements(By.css(selector));
|
||||
return found.length === 0;
|
||||
},
|
||||
timeout,
|
||||
`The element ${selector} was still present when it should have disappeared.`
|
||||
);
|
||||
await this._withTimeout(this.defaultFindTimeout);
|
||||
}
|
||||
|
||||
public async waitForAttributeToChange(
|
||||
selector: string,
|
||||
attribute: string,
|
||||
value: string
|
||||
): Promise<void> {
|
||||
this.log.debug(`Find.waitForAttributeToChange('${selector}', '${attribute}', '${value}')`);
|
||||
await this.retry.waitFor(`${attribute} to equal "${value}"`, async () => {
|
||||
const el = await this.byCssSelector(selector);
|
||||
return value === (await el.getAttribute(attribute));
|
||||
});
|
||||
}
|
||||
|
||||
public async waitForElementStale(
|
||||
element: WebElementWrapper,
|
||||
timeout: number = this.defaultFindTimeout
|
||||
): Promise<void> {
|
||||
this.log.debug(`Find.waitForElementStale with timeout=${timeout}`);
|
||||
await this.driver.wait(until.stalenessOf(element._webElement), timeout);
|
||||
}
|
||||
|
||||
public async waitForElementHidden(
|
||||
element: WebElementWrapper,
|
||||
timeout: number = this.defaultFindTimeout
|
||||
) {
|
||||
this.log.debug(`Find.waitForElementHidden with timeout=${timeout}`);
|
||||
await this.driver.wait(until.elementIsNotVisible(element._webElement), timeout);
|
||||
}
|
||||
|
||||
private async _withTimeout(timeout: number) {
|
||||
if (timeout !== this.currentWait) {
|
||||
this.currentWait = timeout;
|
||||
await this.driver.manage().setTimeouts({ implicit: timeout });
|
||||
}
|
||||
}
|
||||
|
||||
private wrap(webElement: WebElement | WebElementWrapper, locator: By | null = null) {
|
||||
return WebElementWrapper.create(
|
||||
webElement,
|
||||
locator,
|
||||
this.driver,
|
||||
this.defaultFindTimeout,
|
||||
this.fixedHeaderHeight,
|
||||
this.log,
|
||||
this.browserType
|
||||
);
|
||||
}
|
||||
|
||||
private wrapAll(webElements: Array<WebElement | WebElementWrapper>) {
|
||||
return webElements.map((e) => this.wrap(e));
|
||||
}
|
||||
|
||||
private async findAndWrap(locator: By, timeout: number): Promise<WebElementWrapper> {
|
||||
const webElement = await this.driver.wait(until.elementLocated(locator), timeout);
|
||||
return this.wrap(webElement, locator);
|
||||
}
|
||||
}
|
||||
|
||||
export async function FindProvider(ctx: FtrProviderContext) {
|
||||
const { browserType, driver } = await ctx.getService('__webdriver__').init();
|
||||
return new FindService(ctx, browserType, driver);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue