Skip to content

Commit

Permalink
Fix action on invisible (getgauge#1819)
Browse files Browse the repository at this point in the history
* Remove selectHiddenElements option

Signed-off-by: NivedhaSenthil <[email protected]>

* Fix proximity searcch on invisible elements

Signed-off-by: NivedhaSenthil <[email protected]>

* introduce force option to page actions

Signed-off-by: NivedhaSenthil <[email protected]>

* add test for click and attach

Signed-off-by: NivedhaSenthil <[email protected]>

* update clear

Signed-off-by: NivedhaSenthil <[email protected]>

* update docs

Signed-off-by: NivedhaSenthil <[email protected]>

* Bump version to 1.2.0

Signed-off-by: NivedhaSenthil <[email protected]>
  • Loading branch information
NivedhaSenthil authored Feb 3, 2021
1 parent 3089b14 commit 8642f4f
Show file tree
Hide file tree
Showing 48 changed files with 18,200 additions and 460 deletions.
8 changes: 0 additions & 8 deletions docs/assertions.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,6 @@ the test fails when
All Page actions, browser actions, selectors, proximity
selectors have implicit assertions.

### Selecting hidden elements

If you do not want to assert the visibility of elements on a page
and perform actions on hidden elements use the `selectHiddenElements`
option on the API for example

await click("Google Search", {selectHiddenElements: true});

### Ignoring implicit assertions

To ignore implicit assertions use JavaScript's `try` and `catch` block to handle the error
Expand Down
4 changes: 2 additions & 2 deletions lib/actions/attach.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const { defaultConfig } = require('../config');
const domHandler = require('../handlers/domHandler');
const { highlightElement } = require('../elements/elementHelper');

const attach = async (filepath, to) => {
const attach = async (filepath, to, options) => {
let resolvedPath = filepath ? path.resolve(process.cwd(), filepath) : path.resolve(process.cwd());
if (!fs.existsSync(resolvedPath)) {
throw new Error(`File ${resolvedPath} does not exist.`);
Expand All @@ -17,7 +17,7 @@ const attach = async (filepath, to) => {
} else if (!isSelector(to) && !isElement(to)) {
throw Error('Invalid element passed as parameter');
}
const element = await waitAndGetActionableElement(to);
const element = await waitAndGetActionableElement(to, options.force);
if (defaultConfig.headful) {
await highlightElement(element);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/actions/clear.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const _clear = async (elem) => {
};

const clear = async (selector, options) => {
const element = await waitAndGetActionableElement(selector, [checksMap.writable]);
const element = await waitAndGetActionableElement(selector, options.force, [checksMap.writable]);
const desc = !selector ? 'Cleared element on focus' : 'Cleared ' + description(element, true);
await doActionAwaitingNavigation(options, async () => {
await focus(element, options);
Expand Down
16 changes: 14 additions & 2 deletions lib/actions/click.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,14 @@ async function checkIfFileType(elem) {
async function simulateInputEvents(options) {
for (let count = 0; count < options.noOfClicks; count++) {
await doActionAwaitingNavigation(options, async () => {
options.tap ? await tap(options.x, options.y) : await simulateMouseClick(options);
options.tap
? await tap(options.x, options.y)
: await simulateMouseClick({
x: options.x,
y: options.y,
clickCount: options.clickCount,
button: options.button,
});
});
}
}
Expand All @@ -47,7 +54,12 @@ async function click(selector, options = {}, ...args) {
if (isSelector(selector) || isString(selector) || isElement(selector)) {
const elementActionabilityChecks = [checksMap.covered];

const element = await waitAndGetActionableElement(selector, elementActionabilityChecks, args);
const element = await waitAndGetActionableElement(
selector,
options.force,
elementActionabilityChecks,
args,
);
await checkIfFileType(element);

let { x, y } = await domHandler.boundingBoxCenter(element.get());
Expand Down
8 changes: 4 additions & 4 deletions lib/actions/dragAndDrop.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ const { isElement, wait, isSelector, isString } = require('../helper');
const { scrollToElement } = require('./scrollTo');
const { description, waitAndGetActionableElement } = require('./pageActionChecks');

const dragAndDrop = async (source, destination) => {
let sourceElem = await waitAndGetActionableElement(source);
const dragAndDrop = async (source, destination, options) => {
let sourceElem = await waitAndGetActionableElement(source, options.force);
let dest =
isSelector(destination) || isString(destination) || isElement(destination)
? await waitAndGetActionableElement(destination)
? await waitAndGetActionableElement(destination, options.force)
: destination;
let options = setClickOptions({});
options = setClickOptions(options);
await doActionAwaitingNavigation(options, async () => {
if (defaultConfig.headful) {
await highlightElement(sourceElem);
Expand Down
2 changes: 1 addition & 1 deletion lib/actions/focus.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const { doActionAwaitingNavigation } = require('../doActionAwaitingNavigation');
const focus = async (selector, options = {}, highlight = false) => {
let elem = selector;
if (isSelector(selector) || isElement(selector) || isString(selector)) {
elem = await waitAndGetActionableElement(selector);
elem = await waitAndGetActionableElement(selector, options.force);
}
await doActionAwaitingNavigation(options, async () => {
await scrollToElement(elem);
Expand Down
2 changes: 1 addition & 1 deletion lib/actions/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const domHandler = require('../handlers/domHandler');
const inputHandler = require('../handlers/inputHandler');

async function hover(selector, options) {
const e = await waitAndGetActionableElement(selector);
const e = await waitAndGetActionableElement(selector, options.force);
await scrollToElement(e);
if (defaultConfig.headful) {
await highlightElement(e);
Expand Down
2 changes: 1 addition & 1 deletion lib/actions/mouseAction.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const mouseAction = async (selector, action, coordinates, options) => {
throw Error('Invalid Selector value passed : ' + selector);
}
if (!actions.includes(selector)) {
const elem = await waitAndGetActionableElement(selector);
const elem = await waitAndGetActionableElement(selector, options.force);
if (elem == null) {
throw Error('Please provide a valid selector, unable to find element');
}
Expand Down
6 changes: 5 additions & 1 deletion lib/actions/pageActionChecks.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ const defaultChecks = [
checksMap.stable,
];

const waitAndGetActionableElement = async (selector, checks = defaultChecks, args = []) => {
const waitAndGetActionableElement = async (selector, force, checks = defaultChecks, args = []) => {
checks = [...new Set([...checks, ...defaultChecks])];
let elements, elementsLength;
if (selector) {
Expand Down Expand Up @@ -179,6 +179,10 @@ const waitAndGetActionableElement = async (selector, checks = defaultChecks, arg
defaultConfig.retryInterval,
defaultConfig.retryTimeout,
).catch(() => {
if (force) {
actionableElement = elements[0];
return;
}
if (elementsLength !== elements.length) {
throw Error('Found too many matches. Please use a selector that is more specific');
}
Expand Down
2 changes: 1 addition & 1 deletion lib/actions/write.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const write = async (text, into, options) => {
selector = isString(into) ? new TextBoxWrapper(into) : into;
elementDesc = description(selector, true);
}
elems = await waitAndGetActionableElement(selector, [checksMap.writable]);
elems = await waitAndGetActionableElement(selector, options.force, [checksMap.writable]);

await doActionAwaitingNavigation(options, async () => {
await _write(elems, text, options);
Expand Down
33 changes: 11 additions & 22 deletions lib/elementSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,11 @@ function match(text, options = {}, ...args) {
return exactMatches.length ? exactMatches : containsMatches;
};

elements = await $function(
textSearch,
{ text: text.toString(), tagName, exactMatch: options.exactMatch },
options.selectHiddenElements,
);
elements = await $function(textSearch, {
text: text.toString(),
tagName,
exactMatch: options.exactMatch,
});

return await handleRelativeSearch(elements, args);
};
Expand All @@ -171,26 +171,15 @@ function match(text, options = {}, ...args) {
};
}

const filterVisibleNodes = async (elements) => {
let visible_nodes = [];
for (const element of elements) {
const visible = await element.isVisible();
if (visible) {
visible_nodes.push(element);
}
}
return visible_nodes;
};

const $$ = async (selector, selectHiddenElements) => {
const $$ = async (selector) => {
logQuery(`document.querySelectorAll('${selector}')`);
function customCssQuerySelector(selectorElement, args) {
return selectorElement.querySelectorAll(args);
}
return await $function(customCssQuerySelector, selector, selectHiddenElements);
return await $function(customCssQuerySelector, selector);
};

const $$xpath = async (selector, selectHiddenElements) => {
const $$xpath = async (selector) => {
logQuery(`xpath - ${selector}`);
var xpathFunc = function (selector) {
var result = [];
Expand All @@ -210,10 +199,10 @@ const $$xpath = async (selector, selectHiddenElements) => {
await runtimeHandler.findElements(xpathFunc, selector),
runtimeHandler,
);
return selectHiddenElements ? elements : await filterVisibleNodes(elements);
return elements;
};

const $function = async (callBack, args, selectHiddenElements) => {
const $function = async (callBack, args) => {
function searchThroughShadowDom(argument) {
const isString = (obj) => Object.prototype.toString.call(obj).includes('String');
let { searchElement, querySelector, args, elements } = argument;
Expand Down Expand Up @@ -246,7 +235,7 @@ const $function = async (callBack, args, selectHiddenElements) => {
}),
runtimeHandler,
);
return selectHiddenElements ? elements : await filterVisibleNodes(elements);
return elements;
};

const findFirstElement = async (selector, tag) => {
Expand Down
9 changes: 1 addition & 8 deletions lib/elementWrapper/buttonWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,12 @@ class ButtonWrapper extends ElementWrapper {
this.selector,
async () => match(this.selector.label, this._options).elements('button', 0, 0),
'button',
this._options.selectHiddenElements,
);

this.getByInput = getElementGetter(
this.selector,
async () =>
await $function(
getButtonElementWithLabel,
this.selector.label,
this._options.selectHiddenElements,
),
async () => await $function(getButtonElementWithLabel, this.selector.label),
'input[type="submit"],input[type="reset"],input[type="button"],input[type="image"]',
this._options.selectHiddenElements,
);
this._get = async () => {
const input = await this.getByInput();
Expand Down
8 changes: 1 addition & 7 deletions lib/elementWrapper/checkBoxWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,8 @@ class CheckBoxWrapper extends ElementWrapper {
super('CheckBox', 'label', attrValuePairs, _options, ...args);
this._get = getElementGetter(
this.selector,
async () =>
await $function(
getCheckBoxElementWithLabel,
this.selector.label,
this._options.selectHiddenElements,
),
async () => await $function(getCheckBoxElementWithLabel, this.selector.label),
'input[type="checkbox" i]',
this._options.selectHiddenElements,
);
}

Expand Down
8 changes: 1 addition & 7 deletions lib/elementWrapper/colorWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,8 @@ class ColorWrapper extends ValueWrapper {
super('Color', 'label', attrValuePairs, _options, ...args);
this._get = getElementGetter(
this.selector,
async () =>
await $function(
getColorElementWithLabel,
this.selector.label,
this._options.selectHiddenElements,
),
async () => await $function(getColorElementWithLabel, this.selector.label),
'input[type="color"]',
this._options.selectHiddenElements,
);
}

Expand Down
4 changes: 2 additions & 2 deletions lib/elementWrapper/dollarWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ class DollarWrapper extends ElementWrapper {
runtimeHandler,
);
} else if (this.selector.label.startsWith('/') || this.selector.label.startsWith('(')) {
element = await $$xpath(this.selector.label, this._options.selectHiddenElements);
element = await $$xpath(this.selector.label);
} else {
element = await $$(this.selector.label, this._options.selectHiddenElements);
element = await $$(this.selector.label);
}
return await handleRelativeSearch(element, this.selector.args);
};
Expand Down
8 changes: 1 addition & 7 deletions lib/elementWrapper/dropDownWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,8 @@ class DropDownWrapper extends ValueWrapper {
super('DropDown', 'label', attrValuePairs, _options, ...args);
this._get = getElementGetter(
this.selector,
async () =>
await $function(
getDropDownElementWithLabel,
this.selector.label,
this._options.selectHiddenElements,
),
async () => await $function(getDropDownElementWithLabel, this.selector.label),
'select',
this._options.selectHiddenElements,
);
}

Expand Down
8 changes: 1 addition & 7 deletions lib/elementWrapper/fileFieldWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,8 @@ class FileFieldWrapper extends ValueWrapper {
super('FileField', 'label', attrValuePairs, _options, ...args);
this._get = getElementGetter(
this.selector,
async () =>
await $function(
getFileFieldElementWithLabel,
this.selector.label,
this._options.selectHiddenElements,
),
async () => await $function(getFileFieldElementWithLabel, this.selector.label),
'input[type="file"]',
this._options.selectHiddenElements,
);
}

Expand Down
46 changes: 29 additions & 17 deletions lib/elementWrapper/helper.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,38 @@
const { isString, isElement, isSelector, isRegex } = require('../helper');
const { isString, isElement, isSelector, isRegex, waitUntil } = require('../helper');
const { RelativeSearchElement, handleRelativeSearch } = require('../proximityElementSearch');
const { $$ } = require('../elementSearch');

const firstElement = async function (retryInterval, retryTimeout) {
const elems = await this.elements(retryInterval, retryTimeout);
let elems = await this.elements(retryInterval, retryTimeout);
if (elems.length < 1) {
throw new Error(`${this.description} not found`);
}
return elems[0];
let actionableElement;
await waitUntil(
async () => {
elems = await this.elements(retryInterval, retryTimeout);
for (let elem of elems) {
try {
let actionable = await elem.isVisible();
if (!actionable) {
continue;
}
actionableElement = elem;
return true;
} catch (e) {
if (e.message.match(/Browser process with pid \d+ exited with/)) {
throw e;
}
}
}
return false;
},
retryInterval,
retryTimeout,
).catch(() => {});
return actionableElement ? actionableElement : elems[0];
};

function hasProximitySelectors(values) {
return values.selector.args.length > 0;
}

const prepareParameters = (attrValuePairs, options, ...args) => {
if (options instanceof RelativeSearchElement) {
args = [options].concat(args);
Expand All @@ -34,11 +53,6 @@ const prepareParameters = (attrValuePairs, options, ...args) => {
values.selector = { label: attrValuePairs, args: args };
}
values.options = options || {};
if (values.options.selectHiddenElements && hasProximitySelectors(values)) {
console.warn(
'WARNING: Proximity of hidden element are not available hence proximity selector will be ignored',
);
}
return values;
};

Expand All @@ -54,20 +68,18 @@ const getQuery = (attrValuePairs, tag = '') => {
return path;
};

const getElementGetter = (selector, query, tags, selectHiddenElements) => {
const getElementGetter = (selector, query, tags) => {
let get;
if (selector.attrValuePairs) {
let query = tags
.split(',')
.map((tag) => getQuery(selector.attrValuePairs, tag))
.join(',');
get = async () =>
await handleRelativeSearch(await $$(query, selectHiddenElements), selector.args);
get = async () => await handleRelativeSearch(await $$(query), selector.args);
} else if (selector.label) {
get = async () => await handleRelativeSearch(await query(), selector.args);
} else {
get = async () =>
await handleRelativeSearch(await $$(tags, selectHiddenElements), selector.args);
get = async () => await handleRelativeSearch(await $$(tags), selector.args);
}
return get;
};
Expand Down
Loading

0 comments on commit 8642f4f

Please sign in to comment.