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

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);
}

Related

How to handle new tabs with the page object model with Playwright

I want to know how to handle a tab or multiple tabs when using Page object model.
My test runs successfully if i don't use the page object model function to run it.
Basically when i click and navigate to the new tab i am using this on the normal test without the POM:
const [newPage] = await Promise.all([
page.waitForEvent('popup'),
page.locator(button...).click();
]);
and then using the newPage as my new tab and it's working.
await newPage.locator(field).fill(testing);
...SNIP..
When using the POM I cant do that and I am not able to continue with the rest of the test, it doesnt recognise the new tab as i cant declare the new page in the POM.
Can someone point me in the right direction ?
How can i implement the same logic using the POM ?
Thanks
Maybe this will help. I also puzzled over how to do it.
constructor(page) {
this.page = page;
this.wait_for_event = page.waitForEvent('popup');
this.your_button = page.locator(button...);
}
async f(){
const [newPage] = await Promise.all([
this.wait_for_event,
this.your_button.click(),
]);
await newPage.getByPlaceholder or another method('placeholder text').fill('');
}
In my case something like this works:
import { test } from '#playwright/test';
import { LoginPage } from '../../pages/LoginPage';
import { ProductsPage } from '../../pages/ProductsPage';
const purchasedProduct = 'Sauce Labs Backpack';
test.beforeEach(async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goTo('inventory.html');
});
test('As a user I want to open a new tab and visit it', async ({
page,
context,
}) => {
const productsPage = new ProductsPage(page);
const [newPage] = await Promise.all([
context.waitForEvent('page'),
productsPage.openNewTabWithProduct(purchasedProduct),
]);
await newPage.locator('text=Sauce Labs Onesie').click();
});
The crucial part is you have to put "context" instead of page as your test attribute. Then just use your newly created page

Replace innerHTML with another method to avoid destroying event handler

In my app when I search in the search bar, a dropdown menu would appear, and when I click on the dropdown-item, new info is parsed using innerHTML method.
However, the associated event handler is also destroyed. I'm trying to find ways to replace innerHTML but haven't been able to.
The thing is, inside innerHTML it is a whole block of HTML code, including tags and texts. Plus, whenever a new dropdown item is clicked, the previous HTML code should be removed and new ones parsed in.
I've been trying with removeChild() then appendChild() or insertAdjacentHTML(), to no avail.
Weirdly, it seems even with insertAdjacentHTML() the event handler is still destroyed. (I tried insertAdjacentChild() without removing the previous code, and event handler works only on the previous item, not the item added by insertAdjacentChild()).
Original code:
async function init (name) {
await fetchCurrentData(name)
await weatherBackground(name)
const forecastHoursList = await hoursList(name)
document.querySelector('.forecast ul').innerHTML = renderHourlyForecast(forecastHoursList)
const forecastDailyList = await dailyList(name)
document.querySelector('.future ul').innerHTML = renderDailyForecast(forecastDailyList)
}
What I tried but didn't work:
async function init (name) {
await fetchCurrentData(name)
await weatherBackground(name)
const forecast = document.querySelector('.forecast ul')
const future = document.querySelector('.future ul')
const forecastHoursList = await hoursList(name)
const forecastChildren = forecast.childNodes
for(var i = 0; i < forecastChildren.length; i++) {
forecast.removeChild(forecastChildren[i]);
}
forecast.insertAdjacentHTML('afterbegin', renderHourlyForecast(forecastHoursList))
const forecastDailyList = await dailyList(name)
const futureChildren = future.childNodes
for(var i = 0; i < futureChildren.length; i++) {
future.removeChild(futureChildren[i]);
}
future.insertAdjacentHTML('afterbegin', renderDailyForecast(forecastDailyList))
}

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

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"]')))
}
}

Code to navigate web pages does not produce the expected result (Selenium)

Code
(async function() {
let driver = await new Builder().forBrowser('chrome').build();
try {
await driver.get(stockLink);
if (await driver.findElement(By.linkText('View at Amazon.co.uk')) === true) {
let amazonLink = await driver
.wait(until.elementLocated(By.linkText('View at Amazon.co.uk')), 5000);
await amazonLink.click();
}
} finally {
await driver.quit();
}
})();
Result
Calling await driver.get(stockLink); opens the page (stockLink), however the link in the if statement (amazonLink) does not open (which is part of the content on the stockLink page)
Expected result
Both stockLink and amazonLink should open (where amazonLink is a link on stockLink's page)
Major edit
I appear to have fixed the issue. I'm not sure what I did to fix this (apart from removing the if statement), but it appears to be fixed. If I encounter another error, I will search for it or open another question.

How to use a loop to request a URL, scrape data, grab a new URL from that page and move on to the next page X times

I am looking to:
Open a known URL (www.source.com/1 below)
scrape all URLs on that page (e.g. www.urllookingfor.com/1 to .../10) and log to console
scrape a new URL (e.g. www.source.com/2) from that page
load the next page and repeat the process X number of times
Imagine a list of 50 URLs dividend across 5 pages where you need to click the next button to move on a page.
The first two steps work fine, but I think the issue is that the nextLink isn't updated before the loop runs again. Essentially what happens is that step four gets repeated with the original URL and not the 'new' URL. The steps above are within an if loop.
I've tried using setTimeout, async...await as I think the issue is that it doesn't have time to load the 'new' URL before the next function is complete but this did not work.
If I add console.log(URL) within the if function, it will print the original URL. But when I add console.log to outside the if loop it prints the updated URL which makes me think 'nextLink' isn't updated until after the if loop.
I've also tried repeating the functions over and over (essentially a repeated if statement), but this also does not seem to update 'nextLink' before the next function runs which goes against the above.
let nextLink = www.source.com/1
//this pulls source page and scrapes required URLs
const getDatafromPage = () => {
request(nextLink, (error, response, html) => {
if((!error) && (response.statusCode == 200))
{
let $ = cheerio.load(html);
$('.class1').each((i, el) => {
let link = $(el).find('.class2').attr('href');
console.log(`${link});
})
}
})
}
//this gets the next URL
const getNextLink = () => {
request(nextLink, (error, response, html) => {
if((!error) && (response.statusCode == 200))
{
let $ = cheerio.load(html);
nextLink = $('.class3').attr('href');
}
})
}
for (let i = 0; i <= 4; i++) {
getDatafromPage();
getNextLink();
}
console.log(nextLink)
Expected results (all 50 URLs from the pages and ends by logging the last source URL)
www.urllookingfor.com/1
...
www.urllookingfor.com/50
www.source.com/5
Actual results (repeats the first page, but then logs the next page at the end):
www.urllookingfor.com/1
...
www.urllookingfor.com/10
www.urllookingfor.com/1
...
www.urllookingfor.com/10
www.source.com/2
Here's more or less what it might look like when I do it:
const doPage = async ($) => {
// do stuff here
}
;(async function(){
let response = await request(url)
let $ = cheerio.load(response)
await doPage($)
let a
// keep following next links
while(a = $('[rel=next]')[0]){
url = new URL($(a).attr('href'), url).href
response = await request(url)
$ = cheerio.load(response)
await doPage($)
}
})()

Categories