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.
Related
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
I am attempting to try click a button using code without an id or class, but my terminal always responds with:
document.getElementsByTagName("Accept Cookies");
^
ReferenceError: document is not defined
This is my code:
const puppeteer = require('puppeteer');
const product_url = "https://www.nike.com/launch"
async function givePage() {
const browser = await puppeteer.launch({headless: false});
const page = await browser.newPage();
return page;
}
async function acceptCookies(page) {
await page.goto(product_url);
const btn = await page.waitForSelector('#cookie-settings-layout > div > div > div >
div:nth-child(3) > div.ncss-col-md-6.ncss-col-sm-12.mb5-sm > button')
await btn.click()
}
async function notifyMe(page) {
await page.goto(product_url);
document.querySelector("button[type=\"submit\"]").click("Notify Me");
}
async function checkout() {
var page = await givePage();
await acceptCookies(page);
await notifyMe(page);
}
checkout();
What did I do wrong and how can I fix this?
There's no built-in variable in NodeJS named document, since it doesn't run in the browser.
If you want to access document, in Puppeteer there's a page.evaluate() function where you can access the document variable (as well as everything else inside client-side JS):
// ...
await page.evaluate(() => {
document.querySelector("button[type=\"submit\"]").click();
});
Please note though, that all the JavaScript you run will be run on the browser, not in NodeJS, so if you want to get the value back you can return:
const result = await page.evaluate(() => {
var something = document.getElementById("something");
return something.innerText;
});
console.log(result); // will print in the console "blah blah blah"
Likewise if you want to pass variables to the callback you have to give them to the evaluate function:
await page.evaluate((name, age) => {
// do something with 'name' and 'age'
}, "John", 34);
You already have an example on your code on how to access elements. Instead of document.querySelector, use page.waitForSelector like what you did on line 12.
document.querySelector('button[type="submit"]').click()
should be
(await page.waitForSelector('button[type="submit"]')).click()
In Nodejs, you don't have access to web APIs like a window, document, etc. so you can't use document.querySelector to select elements here.
Instead of handling clicks on DOM elements on server side, you should handle those clicks on the client-side only and then fetch the data from the server accordingly.
I build something that you can execute in a browser console that searches the site for links and returns an array of links. After that I want to go trough each link, open the linkpage and search for all tags and get the source.
The pages have the same domain and only the part in the end is different. But when I try to execute my code it opens the first window. Returns undefined and closes the window. After that I get this error: VM79:32 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'getVideoSources') at <anonymous>:32:51
The part where I get the links works perfectly and it returns a list of links. Now the part that causes me problems is where I try to open each link.
async function grabLinks(links) {
const videoList = [];
for (const link of links) {
console.log("New link!")
// Go trough each link and get the video sources
const sources = await getVideoSources(link);
videoList.push(...sources);
}
console.log(videoList);
showLinks(videoList)
}
function getVideoSources(link) {
return new Promise((resolve, reject) => {
console.log("Opening Window");
const openedWindow = window.open(link, "_blank");
openedWindow.focus();
openedWindow.addEventListener("DOMContentLoaded", () => {
try {
const videoElements = openedWindow.document.querySelectorAll("div video");
var source = videoElements[0].getVideoSources;
console.log(source)
resolve(source);
} catch (e) {
reject(e);
} finally {
openedWindow.close();
console.log("Closing Window");
}
});
});
}
How would I go about and fix this problem. Should I add a timeout of like 5s and let the page fully load or what exactly goes wrong here.
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);
}
I'd really like to submit a form in an iframe using Puppeteer, which I've found I can do pretty easily by going
page.keyboard.press('Enter');
However, for nearly everything else I want to do, all I need to pass around is a reference to the iframe I'm interested in. For instance, I may have a method that fills out and submits a form like so:
// Some other setup script
const page = await context.newPage();
const frame = page.frames().find(frame => frame.name() === 'myFrame'); // Iframe ref
// Utility method
function useTheForm(frame) {
// ...
// Do other misc form setup
// ...
await frame.type('myInput', 'Some Value');
// TODO: Submit the form... somehow...
// "frame.keyboard" doesn't exist. Need some kind of ref like "frame.page"
// frame._frameManager._page.keyboard.press('Enter') works, but is kind of dirty...
}
// Use our utility method
useTheForm(frame);
I'd really like a way to submit the form using the "Enter" key without having to also keep track of and pass around a reference to page as well, but I'm hesitant to use intended-to-be-internal properties that aren't documented in the API.
You can focus an element in the iframe and then press a key with page.keyboard. Here is an example that press Enter on a focused link in an iframe causing iframe navigation (though this navigation seems failed due to site iframe policy):
const puppeteer = require('puppeteer');
(async function main() {
try {
const browser = await puppeteer.launch(
{ headless: false, defaultViewport: null });
const [page] = await browser.pages();
await page.goto('https://example.org/');
const data = await page.evaluate(() => {
document.body.appendChild(document.createElement('iframe')).src =
'https://example.org/?foo=bar';
});
await page.waitFor(3000);
console.log(page.frames().map(frame => frame.url()));
await page.frames()[1].focus('a');
await page.keyboard.press('Enter');
//await browser.close();
} catch (err) {
console.error(err);
}
})();