I'm trying to write a test but I've got to a point where I need to wait for some text to become visible.
Basically up to this point I have uploaded a file and then navigated to another page, this new page just says "Processing.." when the file is finished being checked it will say "Success!"
The problem is this page isn't calling an API every x seconds to update the text it just does it once on a page load so I want to check if the page says "Processing.." call cy.reload() check again, call cy.wait(1000) reload and check again etc until the page says "Success!".
My issue is how do I check if text is present without it being an assert and failing the test?
This has been asked a few times, and the short answer is to use jQuery, e.g
cy.visit(...)
cy.wait(1000) // in case the page is initially slow to load
const text = Cypress.$('div').text();
if (text.trim().startsWith('Processing') {
cy.wait(1000)
cy.reload()
}
That gets you one reload, but I guess you want to repeat that until 'Success...', for which recursion seems to be the only way to repeat stuff until the right DOM appears.
function waitForText(attempt = 0) {
if (attempt > 100) { // choose cutoff point, must have this limiter
throw 'Failed'
}
cy.wait(1000);
const text = Cypress.$('div').text();
if (text.trim().startsWith('Processing') {
cy.reload();
waitForText(attempt + 1)
}
}
cy.visit(...)
waitForText()
I had the same issue and authored the following based on other answers in SO (nobody had quite what I wanted).
Add this to commands.js to make it available to all tests.
Cypress.Commands.add('reloadUntilFound', (url, selector, retries=3, retry_wait=1000) => {
if(retries==0){
throw `exhausted retries looking for ${selector} on ${url}`
}
cy.visit(url)
cy.get('body').then(body => {
let msg = `url:${url} selector:${selector} retries:${retries}`
if (body.find(selector).length===1) {
console.log(`found ${msg}`)
}else{
console.log(`NOT found ${msg}`)
cy.wait(retry_wait)
cy.reloadUntilFound(url, selector, retries - 1)
}
})
})
Invoke it as follows.
cy.reloadUntilFound('/transactions', 'td:contains($4.44)')
Related
After clicking a[target="_blank"] new tab opens. How to get code to get new page object so I can access password input field?
Using NodeJS, JavaScript, Puppeteer.
Navigation is working up to the point included below.
EDIT: I used the page.url() method to retrieve current URL and the URL of the newly created tab does not log to console, previous page logs.
I tried adjusting the script and received following errors
Cannot read properties of undefined (reading 'page') - I thought adding a time delay would solve this but no go.
I was having this error but as the code is below I do not get this error: No node found for selector: #Password
I have looked at related issues
I came across dheerajbhaskar GitHub issue and read up on related issues
#386
#3535
#978
and more
I tried to implement code from an accepted answer without any success.
Using Puppeteer to get a handle to the new page after "_blank" click?
try {
await sleep(2300)
// This block creates a new tab
// I was previously using a selector and not mouse click API
await Promise.all([
page.mouse.click(xToolsBtn, yToolsBtn, { delay: 2000 }),
])
// NEW TARGET CREATED
// Below is a snippet from an accepted answer but the the type method
// does not work
// Seems like page is still not activated
const [newTarget] = await Promise.all([
// Await new target to be created with the proper opener
new Promise((x) =>
browser.on("targetcreated", (target) => {
if (target.opener() !== page.target()) return
browser.removeListener("targetcreated", arguments.callee)
x()
})
),
// page.click('link')
])
// Trying to input password without success
const newPage = await newTarget.newPage()
await newPage.type("#Password", process.env.PASSWORD, {
delay: randomGenerator,
})
} catch (err) {
console.error(
"LOGIN BUTTON FAIL",
err.message
)
}
Alternatively atempt#1: I tried to select the input via mouse x, y co-ordinates which activates the input field but this returns the following error"
No node found for selector: #Password
Alternatively atempt#2:
//* WAIT FOR TARGET
try {
await sleep(2300)
await Promise.all([
page.mouse.click(xToolsBtn, yToolsBtn, { delay: 2000 }),
])
sleep(5000)
await page.evaluate(() => window.open(`${loginUrl3}`))
const newWindowTarget = await browser.waitForTarget(
(target) => target.url() === `${loginUrl3}`
)
console.log("GOT TARGET")
await newWindowTarget.type("#Password", process.env.PASSWORD, {
delay: randomGenerator,
})
} catch (err) {
console.log("WAIT FOR TARGET FAILED")
}
Note: URLS are randomly generated so I would be curious what if any work around there is to use current URL. I would assume the new tab created would still need to be activated...
managed to solve this together (Linker :)
Process
First, we mapped the target being created to check for focus
browser.on('targetcreated', function (target) {
console.log('New tab:');
console.log(target);
});
We saw the URL is trying to open - for some reason the URLs in the target were empty. We re-installed stuff to rule out weird dependency bugs, then figured there's a focus issue.
Workaround
To solve it, we need to wait for the .newPage() to open before goto'ing to the URL, calling bringToFront() and then waiting for it to load (short sleep is the easy way). Once we did that, we had a working POC to start from.
Relevant part from the solution:
let mappedURL = tabs
.map((e, index) => e.url())
.filter((e, idx) => idx == 2)
console.log("MAPPED URL ", mappedURL)
sleep(2500)
const page3 = await browser.newPage()
await page3.goto(`${mappedURL}`)
await page3.bringToFront()
Ref
Here's a cool SO Answer showing how to use once syntax to test the event. Happy we were able to solve it, and I hope the process helps other people out.
Addressing just the question in the title, "How to target the selector on new tab after clicking a[target="_blank"]" -
Handling newly opened tabs in Playwright is far from intuitive if you're not used to it. A summary of how they work:
If you click a link in your test with target="_blank", which opens a new tab, the page object you're working with still refers to the original page/tab you opened the link on.
To get ahold of the new page, you have to:
const [newPage] = await Promise.all([
context.waitForEvent('page'), // get `context` by destructuring with `page` in the test params; 'page' is a built-in event, and **you must wait for this like this,**, or `newPage` will just be the response object, rather than an actual Playwright page object.
page.locator('text=Click me').click() // note that, like all waiting in Playwright, this is somewhat unintuitive. This is the action which is *causing the navigation*; you have to set up the wait *before* it happens, hence the use of Promise.all().
])
await newPage.waitForLoadState(); // wait for the new tab to fully load
// now, use `newPage` to access the newly opened tab, rather than `page`, which will still refer to the original page/tab.
await expect(newPage).toHaveURL('http://www.someURL.com');
await newPage.locator('text=someText');
Login.spec.js
it.only('TC02 - Login with valid data as reseller ', () => {
const id = ut.record(testdata, 2);
cy.login(id.username, id.password);
cy.title().should('equal', 'Compass - Home');
cy.visit('/users');
cy.logout();
});
LoginPage.js -> from POM folder
checkHome() {
const path = '/license-agreement';
cy.url().then(($url) => {
if($url.includes(path)) {
cy.log("Yes")
} else {
cy.log("No")
}
})
}
After, login I am calling checkHome method to see if the page is redirected to home or agreement. If its on agreement page then I get accept button and click it.
But when I log url it outputs the previous page url.
Also, this is solved if I put wait in between. But I don't want to use wait and something dynamic solution to this
For testing purposes, you should find a way to avoid this type of conditional testing.
If you cannot then you may want to include the code that calls the checkHome() method. However, to avoid using cy.wait(), you can either add a cy.location('pathname').should('include', '/commmon-path/') if your url's are common for the home page or agreement or query a common element in both pages and append a should (ie cy.get('.common-element-in-home-and-agreement').should('be.visible')).
To wait on the url, instead of .then() try using should() with a function to handle either/or, because should will retry until condition is satisfied
cy.url().should( url => {
// Assert the url is one of the two expected, retry until passes
expect(url).to.satisfy(function(url) {
return url.includes('/license-agreement') || url.includes('/home')
})
if (url.includes(path)) {
cy.log("Yes")
} else {
cy.log("No")
}
})
as anticipated in the question, I'd like to know if is possible to attempt a click action on a button, but if it's not in the page then the test skip that action (without being stucked or throwing an error) and continue to the next one.
To be more specific, I have a modal where I can add rows to a table, but what I'd like to do is to add a new row only if a given button in the modal is not present, while if it's present the action would be to press first on that button and then proceeding to add a new row.
I ask you if it's possible because I'd like to avoid conditional statements during the test. Just let me know, thank you in advance :)
You can check if the element is present then do something if not then do nothing or something else.
const modal = page.locator('modal-selector');
if (await modal.isVisible()) {
// do thing
} else {
// do another thing
}
const locator = page.locator("some selector");
if (locator.count() > 0) {
// found locator, do something
} else {
// not found, do something else
}
I need to return the next person in line by name. I tried other versions and at one point it returned a number but not the string. Apologies, new for now, but will be great one day.
function nowServing(){
if (katzDeli.length > 0) {
return `Currently serving ${katzDeli.shift()}`;
} else {
return `There is nobody waiting to be served!`;
}
}
The above is skipping the "if" and executing the "else." If the array is empty I need it to return the else string.
-update-
It throws the following error:
deli nowServing returns an announcement about the person it is serving, and shifts the line:
Error: Expected 'There is nobody waiting to be served!' to equal 'Currently serving Steven.'
+ expected - actual
-There is nobody waiting to be served!
+Currently serving Steven.
It is in an IDE for a course I'm in. So I don't enter the values for the array. As it reads (the error) it says that there isn't anyone in the line. Also, an aside, how can I avoid downvotes? I seem to get one everytime I ask a question.
Thanks
When I try the code, it works fine:
function nowServing(){
if (katzDeli.length > 0) {
return `Currently serving ${katzDeli.shift()}`;
} else {
return `There is nobody waiting to be served!`;
}
}
var katzDeli = ['foo', 'bar'];
console.log(nowServing()) // Currently serving foo
console.log(nowServing()) // Currently serving bar
console.log(nowServing()) // There is nobody waiting to be served!
console.log(nowServing()) // There is nobody waiting to be served!
If and when you do update your question, I will also update the answer.
I'd have another suggestion. Array.prototype.shift() will return undefined if there are no elements left.
function nowServing() {
var customer = katzDeli.shift();
return `Currently serving ${customer ? customer : "nobody" }`;
}
I've tried the following example in the adobe acrobat docu (see code below).
However, it never reaches "End Job Code" line. Upon logging, global.FileCnt has always been undefined. Why is that? Isn't it supposed to be populated by the total number of pdfs selected? Am I missing something?
// Begin Job
if (typeof global.counter == "undefined") {
console.println("Begin Job Code");
global.counter = 0;
// insert beginJob code here
}
// Main Code to process each of the selected files
try {
global.counter++
console.println("Processing File #" + global.counter);
// insert batch code here.
} catch (e) {
console.println("Batch aborted on run #" + global.counter);
delete global.counter; // so we can try again, and avoid End Job code
event.rc = false; // abort batch
}
// End Job
if (global.counter == global.FileCnt) {
console.println("End Job Code");
// insert endJob code here
// may have to remove any global variables used in case user wants to run
// another batch sequence using the same variables, for example...
delete global.counter;
}
Thanks!
So, it turned out, I need to run two batch sequences for this to achieve.
1st script - for populating value for global.FileCnt
2nd script - do the process
https://forums.adobe.com/thread/1851796