Protractor Element is not clickable at point (x, y) - javascript

This is the most frustrated i've been in a while. I'm writing tests in Javascript (Protractor) and I just want it to click elements on my page. Probably 10% (or less) of the time it actually works correctly, the other 90% of the time I get this error:
Failed: element click intercepted: Element is not clickable at point (x,y)
Yes. I have seen other posts about this and I have tried all the "solutions" but none of them make it work 100% of the time.
it('Full Width Video test', async() => {
const fwv = await blocks.fullWidthVideo;
browser.executeScript("arguments[0].scrollIntoView(true);", fwv);
await expect(await fwv).toBeDisplayed();
await expect(await blocks.fullWidthVideoImg).toBeDisplayed();
// tried this
// browser.manage().timeouts().implicitlyWait(10000)
// also tried this
// const EC = new protractor.ProtractorExpectedConditions();
// await browser.wait(EC.elementToBeClickable(
// element(blocks.fullWidthVideoImg))),
// 5000,' item is not clickable after 5 seconds');
// also also tried this
// let ele = browser.findElement(blocks.fullWidthVideoImg));
// browser.actions().mouseMove(ele).click().perform();
await blocks.fullWidthVideoImg.click();
await expect(await blocks.fullWidthVideoIframe).toBeDisplayed();
});
export class Blocks {
// Full Width Video
get fullWidthVideo() {
return element(by.xpath('//div[#data-test="fullwidthvideo"]'))
}
get fullWidthVideoImg() {
const fwv = this.fullWidthVideo;
return fwv.element(by.xpath('./div[#data-test="video-img"]')))
}
get fullWidthVideoIframe() {
const fwv = this.fullWidthVideo;
return fwv.element(by.xpath('./div[#data-test="video-iframe"]')))
}
}

Related

Simulating a long press of mouse left button in JS with testing-library

I am writing an integration test and I need to click on an html object for longer than 0.5s.
In the same test I have been able to use userEvent to handle various keys' actions, like:
const user = userEvent.setup();
await user.keyboard("[ShiftLeft>]"); // Press Shift (without releasing it)
const pointer = await screen.findByText(objectName);
await user.click(pointer);
await user.keyboard("[/ShiftLeft]"); // Release Shift
I am wondering if there is a way of doing something similar (with or without userEvent) to perform a long click on an object in the page.
Something like:
mouse left click on X without releasing button
wait 1s
release mouse left button
Thanks!
I found this solution to my problem, maybe it can be useful for others :)
It uses the userEvent pointer from testing-library/user-event.
export async function longPress(target: string) {
const myTarget = await screen.findByText(target);
const user = userEvent.setup();
await user.pointer({
keys: "[MouseLeft>]",
target: myTarget ,
});
await new Promise((resolve) => {
setTimeout(resolve, 1000);
});
await user.pointer({ keys: "[/MouseLeft]", target: myTarget });
}

Webdriver.io: Problems when switching to new window/tab

I'm using Webdriver.io/Nodejs/Mocha and when I try to switch from parent tab to child tab, majority of the time it works; however, there are times that the tab will take a long time to load due to issues/bad adverts on the page and during those times, even though I get the window/tab GUID, it still doesn't switch and remains on the parent tab. It doesn't happen all the time but occassionally it fails to switch.
Does the page fully have to load to be able to switch to the chid tab? Am I missing anything? Any help would be appreciated. Thanks!
Node version: v16.13.1
WebdriverIO version: 7.16.12
Mocha version: 9.0.0
Chromedriver version: 95.0.0
Execution:
npx mocha --config ./mocharc.json ./test/test.js
Test File: test.js
it('Test Case: Switch Tabs', async () => {
let parentGUID;
let childGUID;
// get parent GUID
parentGUID = await this.driver.getWindowHandle();
// click element to launch new tab
await this.driver.elementClick('//a[#id="test"]');
// wait until new tab loads
await this.driver.pause(2000);
// get all GUID's
const allGUIDs = await this.driver.getWindowHandles();
// check all GUID's and see which one is the child
for (let i = 0; i < allGUIDs.length; i++) {
if (allGUIDs[i] !== parentGUID) {
childGUID = allGUIDs[i];
}
}
// switch to child tab
await this.driver.switchToWindow(childGUID);
// assert content on the new page here
// ...
// ...
// ...
// close tab
await this.driver.closeWindow();
// switch to parent window/tab
await this.driver.switchToWindow(parentGUID);
}
This seems like a simple coding mistake. While you are initiating your parentGUID, you are not assigning any value to it. You are then using it to compare in your for loop. Sometimes, the childGUID is assigned with parent GUID. In this case, the switch seems to be not happening. I have corrected it below.
it('Test Case: Switch Tabs', async () => {
let childGUID;
// get parent GUID
const parentGUID = await this.driver.getWindowHandle();
// click element to launch new tab
await this.driver.elementClick('//a[#id="test"]');
// wait until new tab loads
await this.driver.pause(2000);
// get all GUID's
const allGUIDs = await this.driver.getWindowHandles();
// check all GUID's and see which one is the child
for (let i = 0; i < allGUIDs.length; i++) {
if (allGUIDs[i] !== parentGUID) {
childGUID = allGUIDs[i];
}
}
// switch to child tab
await this.driver.switchToWindow(childGUID);
// assert content on the new page here
// ...
// ...
// ...
// close tab
await this.driver.closeWindow();
// switch to parent window/tab
await this.driver.switchToWindow(parentGUID);
}

What is the difference between page.$$(selector) and page.$$eval(selector, function) in puppeteer?

I'm trying to load page elements into an array and retrieve the innerHTML from both and be able to click on them.
var grabElements = await page.$$(selector);
await grabElements[0].click();
This allows me to grab my elements and click on them but it won't display innerHTML.
var elNum = await page.$$eval(selector, (element) => {
let n = []
element.forEach(e => {
n.push(e);
})
return n;
});
await elNum[0].click();
This lets me get the innerHTML if I push the innerHTML to n. If I push just the element e and try to click or get its innerHTML outside of the var declaration, it doesn't work. The innerHTML comes as undefined and if I click, I get an error saying elnum[index].click() is not a function. What am I doing wrong?
The difference between page.$$eval (and other evaluate-style methods, with the exception of evaluateHandle) and page.$$ is that the evaluate family only works with serializable values. As you discovered, you can't return elements from these methods because they're not serialiable (they have circular references and would be useless in Node anyway).
On the other hand, page.$$ returns Puppeteer ElementHandles that are references to DOM elements that can be manipulated from Puppeteer's API in Node rather than in the browser. This is useful for many reasons, one of which is that ElementHandle.click() issues a totally different set of operations than running the native DOMElement.click() in the browser.
From the comments:
An example of what I'm trying to get is: <div class = "class">This is the innerHTML text I want. </div>. On the page, it's text inside a clickable portion of the website. What i want to do is loop through the available options, then click on the ones that match an innerHTML I'm looking for.
Here's a simple example you should be able to extrapolate to your actual use case:
const puppeteer = require("puppeteer"); // ^19.1.0
const {setTimeout} = require("timers/promises");
const html = `
<div>
<div class="class">This is the innerHTML text I want.</div>
<div class="class">This is the innerHTML text I don't want.</div>
<div class="class">This is the innerHTML text I want.</div>
</div>
<script>
document.querySelectorAll(".class").forEach(e => {
e.addEventListener("click", () => e.textContent = "clicked");
});
</script>
`;
const target = "This is the innerHTML text I want.";
let browser;
(async () => {
browser = await puppeteer.launch();
const [page] = await browser.pages();
await page.setContent(html);
///////////////////////////////////////////
// approach 1 -- trusted Puppeteer click //
///////////////////////////////////////////
const handles = await page.$$(".class");
for (const handle of handles) {
if (target === (await handle.evaluate(el => el.textContent))) {
await handle.click();
}
}
// show that it worked and reset
console.log(await page.$eval("div", el => el.innerHTML));
await page.setContent(html);
//////////////////////////////////////////////
// approach 2 -- untrusted native DOM click //
//////////////////////////////////////////////
await page.$$eval(".class", (els, target) => {
els.forEach(el => {
if (target === el.textContent) {
el.click();
}
});
}, target);
// show that it worked and reset
console.log(await page.$eval("div", el => el.innerHTML));
await page.setContent(html);
/////////////////////////////////////////////////////////////////
// approach 3 -- selecting with XPath and using trusted clicks //
/////////////////////////////////////////////////////////////////
const xp = '//*[#class="class"][text()="This is the innerHTML text I want."]';
for (const handle of await page.$x(xp)) {
await handle.click();
}
// show that it worked and reset
console.log(await page.$eval("div", el => el.innerHTML));
await page.setContent(html);
///////////////////////////////////////////////////////////////////
// approach 4 -- selecting with XPath and using untrusted clicks //
///////////////////////////////////////////////////////////////////
await page.evaluate(xp => {
// https://stackoverflow.com/a/68216786/6243352
const $x = xp => {
const snapshot = document.evaluate(
xp, document, null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
);
return [...Array(snapshot.snapshotLength)]
.map((_, i) => snapshot.snapshotItem(i))
;
};
$x(xp).forEach(e => e.click());
}, xp);
// show that it worked
console.log(await page.$eval("div", el => el.innerHTML));
})()
.catch(err => console.error(err))
.finally(() => browser?.close());
Output in all cases is:
<div class="class">clicked</div>
<div class="class">This is the innerHTML text I don't want.</div>
<div class="class">clicked</div>
Note that === might be too strict without calling .trim() on the textContent first. You may want an .includes() substring test instead, although the risk there is that it's too permissive. Or a regex may be the right tool. In short, use whatever makes sense for your use case rather than (necessarily) my === test.
With respect to the XPath approach, this answer shows a few options for dealing with whitespace and substrings.

Playwright Select frame with dynamic name

I need to access an iframe in playwright that has a name that is automatically generated.
The iframe's name is always prefixed by "__privateStripeFrame" and then a randomly generated number
How i can access the frame with the page.frame({name: }) ?
From the docs it seems like i can't use a regular expression!
The frameSelector doesn't need be specified by the name.
Try an xpath with contains - this works on the W3 sample page:
await page.goto('https://www.w3schools.com/tags/tryit.asp?filename=tryhtml_iframe');
await page.frame("//iframe[contains(#title,'W3s')]");
If you want a more general approach - you also have page.frames().
That will return an array of the frames and you can iterate through and find the one you need.
This works for me:
let myFrames = page.frames();
console.log("#frames: " + myFrames.length)
myFrames.map((f) => console.log(f.name()));
(W3S is not the best demo site as there are lots of nested frames - but this outputs the top level frames that have names)
The output:
iframeResult
__tcfapiLocator
__uspapiLocator
__tcfapiLocator
__uspapiLocator
We had the issue of multiple Stripe Elements iframes loading asynchronously and very slowly, so we wound up with this workaround to retry iterating all frames and querying for the card input fields for each, until found or timed out.
Not elegant, but it worked for us.
async function findStripeElementsIframeAsync(page: Page, timeout: number) {
const startTime = Date.now();
let stripeFrame = null;
while (!stripeFrame && Date.now() - startTime < timeout) {
const stripeIframes = await page.locator('iframe[name^=__privateStripeFrame]');
const stripeIframeCount = await stripeIframes.count();
for (let i = 0; i < stripeIframeCount; i++) {
const stripeIFrameElement = await stripeIframes.nth(i).elementHandle();
if (!stripeIFrameElement)
throw 'No Stripe iframe element handle.';
const cf = await stripeIFrameElement.contentFrame();
if (!cf)
throw 'No Stripe iframe content frame.';
// Does this iframe have a CVC input? If so, it's our guy.
// 1 ms timeout did not work, because the selector requires some time to find the element.
try {
await cf.waitForSelector('input[name=cvc]', { timeout: 200 });
stripeFrame = cf;
console.log('Found Stripe iframe with CVC input');
return stripeFrame;
} catch {
// Expected for iframes without this input.
}
}
// Give some time for iframes to load before retrying.
await new Promise(resolve => setTimeout(resolve, 200));
}
return null;
}

Edit HTML through Javascript function real time

I am trying to show a "spinner loader" when a user clicks a button that fires a function which takes some time, so the user knows that stuff is being done in the back-end. I am writing in VueJS the front-end. The spinner is being shown only after the function has finished its job, which is something I do not want as my goal is that the spinner is shown as long as the function is run and the user wait.
// Unlock is a prompt page that contains a "Success" button and a "Cancel" button
<unlock v-model="password" #cancel="cancel" #success="add"/>
<icon type="spinner" class="spin" v-if="loading"></icon>
methods: {
async add() {
this.loading = true
this.error = ''
await this.$store.dispatch('DO_SOMETHING', { users: this.selected_users, password: this.password })
this.loading = false
this.$store.dispatch('TOGGLE_PROMPT', '')
}
...
...
}
I expected the spinner to show up as soon as the function is fired up, until it finishes when another page loads either way. I would like the spinner to be shown as long as we wait for the await this.$store.dispatch('DO_SOMETHING') to execute completely. Problem is, it is shown only after the whole function gets finished.
I tried playing around with the async and await, no results. Is it possible to show the element in HTML-VueJS directly, or this is something that just we cannot do it through Javascript?
Thank you in advance.
EDIT:
The action
DO_SOMETHING: ({ commit, dispatch, state }, { users, password }) => {
commit('SET_LOADING', true)
const results = JSON.stringify(users.map( x => {
if (...) delete ...
if (...) delete ...
return x
}))
API.addUser(results, password || state.password)
// refresh page
return dispatch('FETCH_PAGE')
},
The API.addUser, makes a call to the backend, where a Java function is run that makes the modifications in the database.
EDIT v2:
I added the following console.log()
console.log("1")
await this.$store.dispatch('DO_SOMETHING', { users: this.selected_users, password: this.password })
console.log("6")
console.log("2")
API.addUsers(results, password || state.password)
console.log("5")
The below is in the API/addUsers() function.
console.log("3")
const results = JSON.parse(window.UserbaseApplication.addUsers(users, password))
console.log("4")
As expected, the results are:
1
2
3
4
5
6
So the function is awaiting the return of the API as expected.
Not sure about Vue, but in vanilla JS:
Lets say you have a user press a button, which calls a function that runs some computation. During the computation, you have a loader ready to go.
const button = document.querySelector(".mybutton")
button.addEventListener('click', () => {
const loader = document.querySelector(".myloader");
loader.style.display = "inline-block";
// we are assuming the loader covers the whole width & height, with a black filter in the background
// therefore, we don't have to hide other elements
myfunction(...).then(
res => {
// hide the loader after successful promise call
loader.style.display = "none";
});
});
This example assumes that your function returns a promise
Same can be achieved with async/await
const myfunction = async function() {
const res = await .... some computation
return res
}
const button = document.querySelector(".mybutton")
button.addEventListener('click', () => {
const loader = document.querySelector(".myloader");
loader.style.display = "inline-block";
// we are assuming the loader covers the whole width & height, with a black filter in the background
// therefore, we don't have to hide other elements
const result = myfunction();
loader.style.display="none";
});

Categories