[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
This commit is contained in:
Spencer 2018-12-06 10:58:48 -08:00 committed by GitHub
parent b045151e3b
commit b51987b9eb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 420 additions and 51 deletions

View file

@ -129,6 +129,7 @@ export default function ({ getService, getPageObjects }) {
it('should apply correct filter on other bucket by clicking on a legend', async () => {
const expectedTableData = [ 'Missing', 'osx' ];
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.visualize.filterLegend('Other');
await PageObjects.header.waitUntilLoadingHasFinished();
const pieData = await PageObjects.visualize.getPieChartLabels();

View file

@ -203,17 +203,10 @@ export function DiscoverPageProvider({ getService, getPageObjects }) {
return await testSubjects.getVisibleText('discoverQueryHits');
}
query(queryString) {
return find.byCssSelector('input[aria-label="Search input"]')
.clearValue()
.type(queryString)
.then(() => {
return find.byCssSelector('button[aria-label="Search"]')
.click();
})
.then(() => {
return PageObjects.header.waitUntilLoadingHasFinished();
});
async query(queryString) {
await find.setValue('input[aria-label="Search input"]', queryString);
await find.clickByCssSelector('button[aria-label="Search"]');
await PageObjects.header.waitUntilLoadingHasFinished();
}
async getDocHeader() {

View file

@ -97,7 +97,7 @@ export function PointSeriesPageProvider({ getService }) {
}
async setValue(newValue) {
await find.click('button[ng-click="numberListCntr.add()"]');
await find.clickByCssSelector('button[ng-click="numberListCntr.add()"]');
await find.setValue('input[ng-model="numberListCntr.getList()[$index]"]', newValue);
}

View file

@ -597,7 +597,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) {
async getSavedObjectsInTable() {
const table = await testSubjects.find('savedObjectsTable');
const cells = await table.findAll('css selector', 'td:nth-child(3)');
const cells = await table.findAllByCssSelector('td:nth-child(3)');
const objects = [];
for (const cell of cells) {

View file

@ -23,6 +23,7 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }) {
const find = getService('find');
const retry = getService('retry');
const log = getService('log');
const browser = getService('browser');
const testSubjects = getService('testSubjects');
const comboBox = getService('comboBox');
const PageObjects = getPageObjects(['common', 'header', 'visualize']);
@ -63,9 +64,13 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }) {
// Since we use ACE editor and that isn't really storing its value inside
// a textarea we must really select all text and remove it, and cannot use
// clearValue().
await input.session.pressKeys([Keys.CONTROL, 'a']); // Select all text
await input.session.pressKeys(Keys.NULL); // Release modifier keys
await input.session.pressKeys(Keys.BACKSPACE); // Delete all content
if (process.platform === 'darwin') {
await browser.pressKeys([Keys.COMMAND, 'a']); // Select all Mac
} else {
await browser.pressKeys([Keys.CONTROL, 'a']); // Select all for everything else
}
await browser.pressKeys(Keys.NULL); // Release modifier keys
await browser.pressKeys(Keys.BACKSPACE); // Delete all content
await input.type(markdown);
await PageObjects.header.waitUntilLoadingHasFinished();
}
@ -100,7 +105,7 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }) {
async getRhythmChartLegendValue() {
const metricValue = await find.byCssSelector('.tvbLegend__itemValue');
await metricValue.session.moveMouseTo(metricValue);
await metricValue.moveMouseTo();
return await metricValue.getVisibleText();
}
@ -204,7 +209,7 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }) {
const el = await testSubjects.find('comboBoxSearchInput');
await el.clearValue();
await el.type(timeField);
await el.session.pressKeys(Keys.RETURN);
await browser.pressKeys(Keys.RETURN);
await PageObjects.header.waitUntilLoadingHasFinished();
}
}

View file

@ -492,14 +492,14 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
const advancedLinkState = await advancedLink.getAttribute('class');
if (advancedLinkState.includes('fa-caret-right')) {
await advancedLink.session.moveMouseTo(advancedLink);
await advancedLink.moveMouseTo();
log.debug('click advancedLink');
await advancedLink.click();
}
const checkbox = await find.byCssSelector('input[ng-model="axis.scale.setYExtents"]');
const checkboxState = await checkbox.getAttribute('class');
if (checkboxState.includes('ng-empty')) {
await checkbox.session.moveMouseTo(checkbox);
await checkbox.moveMouseTo();
await checkbox.click();
}
const maxField = await find.byCssSelector('[ng-model="axis.scale.max"]');
@ -1139,10 +1139,12 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
}
async toggleLegend(show = true) {
const isVisible = find.byCssSelector('vislib-legend .legend-ul');
if ((show && !isVisible) || (!show && isVisible)) {
await testSubjects.click('vislibToggleLegend');
}
await retry.try(async () => {
const isVisible = find.byCssSelector('vislib-legend');
if ((show && !isVisible) || (!show && isVisible)) {
await testSubjects.click('vislibToggleLegend');
}
});
}
async filterLegend(name) {

View file

@ -93,8 +93,12 @@ export function BrowserProvider({ getService }) {
* @param {number} yOffset Optional
* @return {Promise<void>}
*/
async moveMouseTo(...args) {
await leadfoot.moveMouseTo(...args);
async moveMouseTo(element, xOffset, yOffset) {
if (element) {
await element.moveMouseTo(xOffset, yOffset);
} else {
await leadfoot.moveMouseTo(null, xOffset, yOffset);
}
}
/**

View file

@ -17,6 +17,8 @@
* 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')) {
@ -40,6 +42,14 @@ export function FindProvider({ getService }) {
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 {
@ -76,26 +86,26 @@ export function FindProvider({ getService }) {
async byName(selector, timeout = defaultFindTimeout) {
log.debug(`find.byName(${selector})`);
return await this._ensureElementWithTimeout(timeout, async leadfoot => {
return await leadfoot.findByName(selector);
return wrap(await leadfoot.findByName(selector));
});
}
async byCssSelector(selector, timeout = defaultFindTimeout) {
log.debug(`findByCssSelector ${selector}`);
return await this._ensureElementWithTimeout(timeout, async leadfoot => {
return await leadfoot.findByCssSelector(selector);
return wrap(await leadfoot.findByCssSelector(selector));
});
}
async byClassName(selector, timeout = defaultFindTimeout) {
log.debug(`findByCssSelector ${selector}`);
return await this._ensureElementWithTimeout(timeout, async leadfoot => {
return await leadfoot.findByClassName(selector);
return wrap(await leadfoot.findByClassName(selector));
});
}
async activeElement() {
return await leadfoot.getActiveElement();
return wrap(await leadfoot.getActiveElement());
}
async setValue(selector, text) {
@ -126,57 +136,70 @@ export function FindProvider({ getService }) {
async allByLinkText(selector, timeout = defaultFindTimeout) {
log.debug('find.allByLinkText: ' + selector);
return await this.allByCustom(leadfoot => leadfoot.findAllByLinkText(selector), timeout);
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(leadfoot => leadfoot.findAllByCssSelector(selector), timeout);
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 () => await parentElement.findDisplayedByCssSelector(selector), timeout);
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 () => await parentElement.findDisplayedByCssSelector(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 () => element))
allElements.map((element) => (
this._ensureElement(async () => wrap(element))
))
);
}
async displayedByCssSelector(selector, timeout = defaultFindTimeout, parentElement) {
async displayedByCssSelector(selector, timeout = defaultFindTimeout) {
log.debug('in displayedByCssSelector: ' + selector);
return await this._ensureElementWithTimeout(timeout, async leadfoot => {
return await leadfoot.findDisplayedByCssSelector(selector);
}, parentElement);
return wrap(await leadfoot.findDisplayedByCssSelector(selector));
});
}
async byLinkText(selector, timeout = defaultFindTimeout) {
log.debug('Find.byLinkText: ' + selector);
return await this._ensureElementWithTimeout(timeout, async leadfoot => {
return await leadfoot.findByLinkText(selector);
return wrap(await leadfoot.findByLinkText(selector));
});
}
async findDisplayedByLinkText(selector, timeout = defaultFindTimeout) {
log.debug('Find.byLinkText: ' + selector);
return await this._ensureElementWithTimeout(timeout, async leadfoot => {
return await leadfoot.findDisplayedByLinkText(selector);
return wrap(await leadfoot.findDisplayedByLinkText(selector));
});
}
async byPartialLinkText(partialLinkText, timeout = defaultFindTimeout) {
log.debug(`find.byPartialLinkText(${partialLinkText})`);
return await this._ensureElementWithTimeout(timeout, async leadfoot => {
return await leadfoot.findByPartialLinkText(partialLinkText);
return wrap(await leadfoot.findByPartialLinkText(partialLinkText));
});
}
@ -193,26 +216,27 @@ export function FindProvider({ getService }) {
async existsByLinkText(linkText, timeout = WAIT_FOR_EXISTS_TIME) {
log.debug(`existsByLinkText ${linkText}`);
return await this.exists(async leadfoot => await leadfoot.findDisplayedByLinkText(linkText), timeout);
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 => await leadfoot.findDisplayedByCssSelector(selector), timeout);
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 => await leadfoot.findByCssSelector(selector), timeout);
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 leadfoot.moveMouseTo(element);
await element.moveMouseTo();
const clickIfNotDisabled = async (element, resolve) => {
const disabled = await element.getProperty('disabled');
@ -232,7 +256,7 @@ export function FindProvider({ getService }) {
log.debug(`clickByPartialLinkText(${linkText})`);
await retry.try(async () => {
const element = await this.byPartialLinkText(linkText, timeout);
await leadfoot.moveMouseTo(element);
await element.moveMouseTo();
await element.click();
});
}
@ -241,7 +265,7 @@ export function FindProvider({ getService }) {
log.debug(`clickByLinkText(${linkText})`);
await retry.try(async () => {
const element = await this.byLinkText(linkText, timeout);
await leadfoot.moveMouseTo(element);
await element.moveMouseTo();
await element.click();
});
}
@ -257,7 +281,7 @@ export function FindProvider({ getService }) {
if (index === -1) {
throw new Error('Button not found');
}
return allButtons[index];
return wrap(allButtons[index]);
});
}
@ -273,7 +297,7 @@ export function FindProvider({ getService }) {
log.debug(`clickByCssSelector(${selector})`);
await retry.try(async () => {
const element = await this.byCssSelector(selector, timeout);
await leadfoot.moveMouseTo(element);
await element.moveMouseTo();
await element.click();
});
}
@ -281,14 +305,14 @@ export function FindProvider({ getService }) {
log.debug(`clickByDisplayedLinkText(${linkText})`);
await retry.try(async () => {
const element = await this.findDisplayedByLinkText(linkText, timeout);
await leadfoot.moveMouseTo(element);
await element.moveMouseTo();
await element.click();
});
}
async clickDisplayedByCssSelector(selector, timeout = defaultFindTimeout) {
await retry.try(async () => {
const element = await this.findDisplayedByCssSelector(selector, timeout);
await leadfoot.moveMouseTo(element);
await element.moveMouseTo();
await element.click();
});
}

View file

@ -0,0 +1,340 @@
/*
* 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.
*/
export class LeadfootElementWrapper {
constructor(leadfootElement, leadfoot) {
if (leadfootElement instanceof LeadfootElementWrapper) {
return leadfootElement;
}
this._leadfootElement = leadfootElement;
this._leadfoot = leadfoot;
}
_wrap(otherLeadfootElement) {
return new LeadfootElementWrapper(otherLeadfootElement, this._leadfoot);
}
_wrapAll(otherLeadfootElements) {
return otherLeadfootElements.map(e => this._wrap(e));
}
/**
* Clicks the element. This method works on both mouse and touch platforms
* https://theintern.io/leadfoot/module-leadfoot_Element.html#click
*
* @return {Promise<void>}
*/
async click() {
await this._leadfootElement.click();
}
/**
* Gets all elements inside this element matching the given CSS class name.
* https://theintern.io/leadfoot/module-leadfoot_Element.html#findAllByClassName
*
* @param {string} className
* @return {Promise<LeadfootElementWrapper[]>}
*/
async findAllByClassName(className) {
return await this._wrapAll(
await this._leadfootElement.findAllByClassName(className)
);
}
/**
* Clears the value of a form element.
* https://theintern.io/leadfoot/module-leadfoot_Element.html#clearValue
*
* @return {Promise<void>}
*/
async clearValue() {
await this._leadfootElement.clearValue();
}
/**
* Types into the element. This method works the same as the leadfoot/Session#pressKeys method
* except that any modifier keys are automatically released at the end of the command. This
* method should be used instead of leadfoot/Session#pressKeys to type filenames into file
* upload fields.
*
* Since 1.5, if the WebDriver server supports remote file uploads, and you type a path to
* a file on your local computer, that file will be transparently uploaded to the remote
* server and the remote filename will be typed instead. If you do not want to upload local
* files, use leadfoot/Session#pressKeys instead.
*
* @param {string|string[]} value
* @return {Promise<void>}
*/
async type(value) {
await this._leadfootElement.type(value);
}
/**
* Gets the first element inside this element matching the given CSS class name.
* https://theintern.io/leadfoot/module-leadfoot_Element.html#findByClassName
*
* @param {string} className
* @return {Promise<LeadfootElementWrapper>}
*/
async findByClassName(className) {
return this._wrap(await this._leadfootElement.findByClassName(className));
}
/**
* Returns whether or not the element would be visible to an actual user. This means
* that the following types of elements are considered to be not displayed:
*
* - Elements with display: none
* - Elements with visibility: hidden
* - Elements positioned outside of the viewport that cannot be scrolled into view
* - Elements with opacity: 0
* - Elements with no offsetWidth or offsetHeight
*
* https://theintern.io/leadfoot/module-leadfoot_Element.html#isDisplayed
*
* @return {Promise<boolean>}
*/
async isDisplayed() {
return await this._leadfootElement.isDisplayed();
}
/**
* Gets an attribute of the element.
* https://theintern.io/leadfoot/module-leadfoot_Element.html#getAttribute
*
* @param {string} name
*/
async getAttribute(name) {
return await this._leadfootElement.getAttribute(name);
}
/**
* Gets the visible text within the element. <br> elements are converted to line breaks
* in the returned text, and whitespace is normalised per the usual XML/HTML whitespace
* normalisation rules.
* https://theintern.io/leadfoot/module-leadfoot_Element.html#getVisibleText
*
* @return {Promise<string>}
*/
async getVisibleText() {
return await this._leadfootElement.getVisibleText();
}
/**
* Gets the tag name of the element. For HTML documents, the value is always lowercase.
* https://theintern.io/leadfoot/module-leadfoot_Element.html#getTagName
*
* @return {Promise<string>}
*/
async getTagName() {
return await this._leadfootElement.getTagName();
}
/**
* Gets the position of the element relative to the top-left corner of the document,
* taking into account scrolling and CSS transformations (if they are supported).
* https://theintern.io/leadfoot/module-leadfoot_Element.html#getPosition
*
* @return {Promise<{x: number, y: number}>}
*/
async getPosition() {
return await this._leadfootElement.getPosition();
}
/**
* Gets all elements inside this element matching the given CSS selector.
* https://theintern.io/leadfoot/module-leadfoot_Element.html#findAllByCssSelector
*
* @param {string} selector
* @return {Promise<LeadfootElementWrapper[]>}
*/
async findAllByCssSelector(selector) {
return this._wrapAll(await this._leadfootElement.findAllByCssSelector(selector));
}
/**
* Gets the first element inside this element matching the given CSS selector.
* https://theintern.io/leadfoot/module-leadfoot_Element.html#findByCssSelector
*
* @param {string} selector
* @return {Promise<LeadfootElementWrapper>}
*/
async findByCssSelector(selector) {
return this._wrap(await this._leadfootElement.findByCssSelector(selector));
}
/**
* Returns whether or not a form element can be interacted with.
* https://theintern.io/leadfoot/module-leadfoot_Element.html#isEnabled
*
* @return {Promise<boolean>}
*/
async isEnabled() {
return await this._leadfootElement.isEnabled();
}
/**
* Gets all elements inside this element matching the given HTML tag name.
* https://theintern.io/leadfoot/module-leadfoot_Element.html#findAllByTagName
*
* @param {string} tagName
* @return {Promise<LeadfootElementWrapper[]>}
*/
async findAllByTagName(tagName) {
return this._wrapAll(await this._leadfootElement.findAllByTagName(tagName));
}
/**
* Gets a property of the element.
* https://theintern.io/leadfoot/module-leadfoot_Element.html#getProperty
*
* @param {string} name
* @return {Promise<any>}
*/
async getProperty(name) {
return await this._leadfootElement.getProperty(name);
}
/**
* Moves the remote environments mouse cursor to this element. If the element is outside
* of the viewport, the remote driver will attempt to scroll it into view automatically.
* https://theintern.io/leadfoot/module-leadfoot_Session.html#moveMouseTo
*
* @param {number} xOffset optional - The x-offset of the cursor, maybe in CSS pixels, relative to the left edge of the specified elements bounding client rectangle.
* @param {number} yOffset optional - The y-offset of the cursor, maybe in CSS pixels, relative to the top edge of the specified elements bounding client rectangle.
* @return {Promise<void>}
*/
async moveMouseTo(xOffset, yOffset) {
return await this._leadfoot.moveMouseTo(this._leadfootElement, xOffset, yOffset);
}
/**
* Gets a CSS computed property value for the element.
* https://theintern.io/leadfoot/module-leadfoot_Element.html#getComputedStyle
*
* @param {string} propertyName
* @return {Promise<string>}
*/
async getComputedStyle(propertyName) {
return await this._leadfootElement.getComputedStyle(propertyName);
}
/**
* Gets the size of the element, taking into account CSS transformations (if they are supported).
* https://theintern.io/leadfoot/module-leadfoot_Element.html#getSize
*
* @return {Promise<{width: number, height: number}>}
*/
async getSize() {
return await this._leadfootElement.getSize();
}
/**
* Gets the first element inside this element matching the given HTML tag name.
* https://theintern.io/leadfoot/module-leadfoot_Element.html#findByTagName
*
* @param {string} tagName
* @return {Promise<LeadfootElementWrapper>}
*/
async findByTagName(tagName) {
return this._wrap(await this._leadfootElement.findByTagName(tagName));
}
/**
* Returns whether or not a form element is currently selected (for drop-down options and radio buttons),
* or whether or not the element is currently checked (for checkboxes).
* https://theintern.io/leadfoot/module-leadfoot_Element.html#isSelected
*
* @return {Promise<boolean>}
*/
async isSelected() {
return await this._leadfootElement.isSelected();
}
/**
* Gets the first displayed element inside this element matching the given CSS selector. This is
* inherently slower than leadfoot/Element#find, so should only be used in cases where the
* visibility of an element cannot be ensured in advance.
* https://theintern.io/leadfoot/module-leadfoot_Element.html#findDisplayedByCssSelector
*
* @param {string} selector
* @return {Promise<LeadfootElementWrapper>}
*/
async findDisplayedByCssSelector(selector) {
return this._wrap(await this._leadfootElement.findDisplayedByCssSelector(selector));
}
/**
* Waits for all elements inside this element matching the given CSS class name to be destroyed.
* https://theintern.io/leadfoot/module-leadfoot_Element.html#waitForDeletedByClassName
*
* @param {string} className
* @return {Promise<void>}
*/
async waitForDeletedByClassName(className) {
await this._leadfootElement.waitForDeletedByClassName(className);
}
/**
* Gets the first element inside this element partially matching the given case-insensitive link text.
* https://theintern.io/leadfoot/module-leadfoot_Element.html#findByPartialLinkText
*
* @param {string} text
* @return {Promise<LeadfootElementWrapper>}
*/
async findByPartialLinkText(text) {
return this._wrap(await this._leadfootElement.findByPartialLinkText(text));
}
/**
* Gets a property or attribute of the element according to the WebDriver specification algorithm. Use of this method is not recommended; instead, use leadfoot/Element#getAttribute to retrieve DOM attributes and leadfoot/Element#getProperty to retrieve DOM properties.
*
* This method uses the following algorithm on the server to determine what value to return:
*
* 1. If name is 'style', returns the style.cssText property of the element.
* 2. If the attribute exists and is a boolean attribute, returns 'true' if the attribute is true, or null otherwise.
* 3. If the element is an <option> element and name is 'value', returns the value attribute if it exists, otherwise returns the visible text content of the option.
* 4. If the element is a checkbox or radio button and name is 'selected', returns 'true' if the element is checked, or null otherwise.
* 5. If the returned value is expected to be a URL (e.g. element is <a> and attribute is href), returns the fully resolved URL from the href/src property of the element, not the attribute.
* 6. If name is 'class', returns the className property of the element.
* 7. If name is 'readonly', returns 'true' if the readOnly property is true, or null otherwise.
* 8. If name corresponds to a property of the element, and the property is not an Object, return the property value coerced to a string.
* 9. If name corresponds to an attribute of the element, return the attribute value.
*
* https://theintern.io/leadfoot/module-leadfoot_Element.html#getSpecAttribute
*
* @param {string} name
* @return {Promise<string>}
*/
async getSpecAttribute(name) {
return await this._leadfootElement.getSpecAttribute(name);
}
/**
* https://theintern.io/leadfoot/module-leadfoot_Element.html#findByXpath
*
* @deprecated
* @param {string} xpath
* @return {Promise<LeadfootElementWrapper>}
*/
async findByXpath(xpath) {
return this._wrap(await this._leadfootElement.findByXpath(xpath));
}
}