best practice puppeteer waitForSelector or setTimeout - javascript

I try to find the best way to "wait until the complete website is loaded". And this seems to be a tricky thing. I was googleing alot and saw that there are 2 ways: waitForSelector and SetTimout.
My problem is, even if I wait for the Selector #CheckSelectAll to check this Checkbox it seems it's always too early. So I had to add a delay of 2 seconds. And this looks very unprofessional for me. I want to use the best practice for this.
This should be an issue everybody always needs when using puppeteer with different pages and forms.
Is it possible that this waitForSelector doesn't work when the selector is inside an iFrame?
Thanks for any advice and help!
function delay(time) {
return new Promise(function(resolve) {
setTimeout(resolve, time)
});
await page.waitForSelector('#CheckSelectAll');
await delay(2000);
await page.click('#CheckSelectAll');

If you want to make it real "puppeteer" way, take a look at this.
There is a few options to choose, but in my practice I found the most useful networkidle2.
As said in documentation, your script will wait until
there are no more than 2 network connections for at least 500 ms.
await page.waitForNavigation({ waitUntil: 'networkidle2' })
But, if for some reason in-box solution can't handle your case, it's O.K. to make custom wait function as you did.
Here is nice one line, to write less code:
await new Promise(resolve => setTimeout(resolve, 2000))
And the last one option, you can access DOM with page.evaluate() and verify if element is visible.
const visibleVerification = await page.evaluate(() => {
// your verify logic.
// return boolean, if element exists on page
return true;
})

Related

Playwright equivalent to Protractor's browser.wait to loop while condition is not met

I'm trying to find a way to loop in Playwright while certain condition is not met. In Protractor I could use browser.wait. In the following example which I'm migration from Protractor to Playwright, the code clicks on the refreshButton while the testButton is not visible, and once it's visible it clicks on the testButton:
await browser.wait(async function () {
return testButton.isPresent().then(async function (present) {
// Refresh the grid until testButton is present
if (present) {
await testButton.click();
return true;
} else {
await refreshButton.click();
return false;
}
});
I've seen that Playwright offers a set of calls to wait for specific conditions such as waitForSelector, but I'm having troubles trying to get them to work in a loop, since a plain while wouldn't be resolved before the promises.
Since Playwright v1.29, you are able to retry blocks of code until all assertions within pass:
await expect(async () => {
await expect(testButtonLocatorHere).toBeVisible();
}).toPass();
await testButtonLocatorHere.click();
I think that might be what you are looking for :)
You could also look at polling as a possible solution. I've used that for something similar to your issue before.

Undefined value for input fill

Follow my previous article Built method time is not a function, I managed to successfully implement the functions with an appropriate wait time by following a combination of #ggorlen's comment and #Konrad Linkowski answer, additionally, this article puppeteer: wait N seconds before continuing to the next line that #ggorlen answered in, this comment especially helped: -
Something else? Run an evaluate block and add your own code to wait for a DOM mutation or poll with setInterval or requestAnimationFrame and effectively reimplement waitForFunction as fits your needs.
Instead I incorporated waitForSelector, produces the following script:
const puppeteer = require('puppeteer')
const EXTENSION = '/Users/usr/Library/Application Support/Google/Chrome/Profile 1/Extensions/gidnphnamcemailggkemcgclnjeeokaa/1.14.4_0'
class Agent {
constructor(extension) {
this._extension = extension
}
async runBrowser() {
const browser = await puppeteer.launch({
headless:false,
devtools:true,
args:[`--disable-extensions-except=${this._extension}`,
`--load-extension=${this._extension}`,
'--enable-automation']
})
return browser
}
async getPage(twitch) {
const page = await (await this.runBrowser()).newPage()
await page.goto('chrome-extension://gidnphnamcemailggkemcgclnjeeokaa/popup.html')
const nextEvent = await page.evaluate(async () => {
document.getElementById('launch-trace').click()
})
const waitSelector = await page.waitForSelector('.popup-body')
const finalEvent = (twitch) => new Promise(async (twitch) => page.evaluate(async (twitch) => {
const input = document.getElementById('user-trace-id')
input.focus()
input.value = twitch
}))
await finalEvent(twitch)
}
}
const test = new Agent(EXTENSION)
test.getPage('test')
However, my webpage produces undefined rather than test, I am a little confused by the parameters twich and k, and how to properly assert the parameter twitch so its entered inside the function finalEvent.
Alternatively, I have also tried wrapping finalEvent into a Promise so I can assert the parameter twitch into it as a function, but this does not fill any value:
const finalEvent = (val) => new Promise(async () => await page.evaluate(async () => {
const nextTime = () => new Promise(async () => setInterval(async () => {
const input = document.getElementById('user-trace-id')
input.focus()
input.value = val
}, 3000))
//await nextTime(k)
}))
await finalEvent(twitch)
There are a few issues here. First,
const page = await (await this.runBrowser()).newPage()
hangs the browser handle and leaks memory which keeps the process alive. Always close the browser when you finish using it:
const browser = await this.runBrowser();
const page = await browser.newPage();
// ... do your work ...
await browser.close();
Here, though, Puppeteer can throw, again leaking the browser and preventing your app from cleanly exiting, so I suggest adding a try/catch block with a finally block that closes the browser.
Generally speaking, try to get the logic working first, then do a refactor to break code into functions and classes. Writing abstractions and thinking about design while you're still battling bugs and logical problems winds up making both tasks harder.
Secondly, there's no need to async a function if you never use await in it, as in:
const nextEvent = await page.evaluate(async () => {
document.getElementById('launch-trace').click()
})
Here, nextEvent is undefined because evaluate()'s callback returned nothing. Luckily, you didn't attempt to use it. You also have const waitSelector = await page.waitForSelector('.popup-body') which does return the element, but it goes unused. I suggest enabling eslint no-unused-vars, because these unused variables make a confusing situation worse and often indicate typos and bugs.
On to the main problem,
const finalEvent = (twitch) => new Promise(async (twitch) => page.evaluate(async (twitch) => {
const input = document.getElementById('user-trace-id')
input.focus()
input.value = twitch
}))
await finalEvent(twitch)
There are a number of misunderstandings here.
The first is the age-old Puppeteer gotcha, confusing which code executes in the browser process and which code executes in the Node process. Everything in an evaluate() callback (or any of its family, $eval, evaluateHandle, etc) executes in the browser, so Node variables that look like they should be in scope won't be. You have to pass and return serializable data or element handles to and from these callbacks. In this case, twitch isn't in scope of the evaluate callback. See the canonical How can I pass a variable into an evaluate function? for details.
The second misunderstanding is technically cosmetic in that you can make the code work with it, but it's a serious code smell that indicates significant confusion and should be fixed. See What is the explicit promise construction antipattern and how do I avoid it? for details, but the gist is that when you're working with a promise-based API like Puppeteer, you should never need to use new Promise(). Puppeteer's methods already return promises, so it's superfluous at best to wrap more promises on top of the them, and at worst, introduces bugs and messes up error handling.
A third issue is that the first parameter to new Promise((resolve, reject) => {}) is always a resolve function, so twitch is a confusing mislabel. Luckily, it won't matter as we'll be dispensing with the new Promise idiom when using Puppeteer 99.9% of the time.
So let's fix the code, keeping these points in mind:
await page.evaluate(twitch => {
const input = document.getElementById('user-trace-id');
input.focus();
input.value = twitch;
},
twitch
);
Note that I'm not assigning the return value to anything because there's nothing being returned by the evaluate() callback.
"Selecting, then doing something with the selected element" is such a common pattern that Puppeteer provides a handy method to shorten the above code:
await page.$eval("#user-trace-id", (input, twitch) => {
input.focus();
input.value = twitch;
},
twitch
);
Now, I can't run or reproduce your code as I don't have your extension, and I'm not sure what goal you're trying to achieve, but even the above code looks potentially problematic.
Usually, you want to use Puppeteer's page.type() method rather than a raw DOM input.value = ..., which doesn't fire any event handlers that might be attached to the input. Many inputs won't register such a change, and it's an untrusted event.
Also, it's weird that you'd have to .focus() on the input before setting its value. Usually focus is irrelevant to setting a value property, and the value will be set either way.
So there may be more work to do, but hopefully this will point you in the right direction by resolving the first layer of immediate issues at hand. If you're still stuck, I suggest taking a step back and providing context in your next question of what you're really trying to accomplish here in order to avoid an xy problem. There's a strong chance that there's a fundamentally better approach than this.

Is it wrong to use await within jest's expect?

I read the official jest documentation on async/await as well as numerous blog posts dealing with the topic. All are describing different ways of awaiting a value on which assertions are made as well as waiting for the test function to complete. None of them included the following pattern, however I don't see why it should cause trouble: Assume I have a
const getValue: () => Promise<string>
and that I'm testing it with
test("await in expect", async () => {
expect(await getValue()).toBe("b")
})
I can't reproduce the issue with an implementation like
const getValue = () => new Promise((resolve) => {
setTimeout(() => {
resolve("b")
}, 4000)
})
however I'm experiencing about 20% fails of an integration test where getValue runs queries against a real database because afterAll function is called early and terminates the connection.
I'm aware that I can overcome this with resolves and other use of await or done or else, e.g.
test("await in expect", async () => {
await expect(getValue()).resolves.toBe("b")
})
and I already managed to overcome the issue in my real world integration test with this approach.
However, I'd like to broaden my understanding of what's going on and what I'm doing when using await inside expect().
I'm using Jest 27.
There's a good reason why one should create real reproducers for issues and questions: The issue might turn out to be something completely different.
In this case the TypeORM Repository.save promise apparently returns before the saved instance is flushed into the database or cache or whatever which is queried by Repository.find.
I might investigate further or just go with a 100ms sleep after save. And yes, you can quote that in yet another blog post about why ORMs are troublesome.

how to add a wait in time in a for ..of?

I am building a code that allows me to wait for a promise to be resolved to advance to the next, I am also adding a separate time for each wait, but only the first loop is printed, if I remove the wait in seconds this works well.
in play code I get this error, in other editors I simply print the first loop
error: Infinity loop on line 8, char 6. You can increase loop timeout in settings.
https://playcode.io/309050?tabs=console&script.js&output
in the stackoverflow code editor if the code works but in my project or in another code editor they do not work
const urls = [
'https://jsonplaceholder.typicode.com/todos/1',
'https://jsonplaceholder.typicode.com/todos/2',
'https://jsonplaceholder.typicode.com/todos/3'
];
async function getTodos() {
for (const [idx, url] of urls.entries()) {
const todo = await fetch(url);
console.log(`Received Todo ${idx+1}:`, todo);
await wait(1000)
}
console.log('Finished!');
}
getTodos();
function wait(ms) {
return new Promise(r => setTimeout(r, ms));
}
Some online code playgrounds, like CodePen, have built-in infinite-loop detection. This is to prevent the UI from freezing in the event that you accidentally write an infinite loop while scratch coding. This is usually achieved by statically analyzing your code. Because of this, it cannot tell what your code actually does at runtime (and it may not be infinite at all). The best they can do is guess based on structure.
In your case, your code editor thinks that you're doing an infinite loop. There's probably settings in your editor to disable that.

Best practice for sequential url requests nodejs

I've got a list of urls i need to request from an API, however in order to avoid causing a lot of load i would ideally like to perform these requests with a gap of x seconds. Once all the requests are completed, certain logic that doesnt matter follows.
There are many ways to go about it, i've implemented a couple.
A) Using a recursive function that goes over an array that holds all the urls and calls itself when each request is done and a timeout has happened
B) Setting timeouts for every request in a loop with incremental delays and returning promises which upon resolution using Promise.all execute the rest of the logic and so on.
These both work. However, what would you say is the recommended way to go about this? This is more of an academic type of question and as im doing this to learn i would rather avoid using a library that abstracts the juice.
Your solutions are almost identical. Thought I would choose a bit different approach. I would make initial promise and sleep promise function, then I would chain them together.
function sleep(time){
return new Promise(resolve => setTimeout(resolve, ms));
}
ApiCall()
.then(sleep(1000))
.then(nextApiCall())...
Or more modular version
var promise = Promise.resolve()
myApiCalls.forEach(call => {
promise = promise.then(call).then(() => sleep(1000))
})
In the end, go with what you understand, what make you most sense and what you will understand in month. The one that you can read best is you preferred solution, performance won’t matter here.
You could use something like this to throttle per period.
If you want all urls to be processed even when some fail you could catch the failed ones and pick them out in the result.
Code would look something like this:
const Fail = function(details){this.details=details;};
twoPerSecond = throttlePeriod(2,1000);
urls = ["http://url1","http://url2",..."http://url100"];
Promise.all(//even though a 100 promises are created only 2 per second will be started
urls.map(
(url)=>
//pass fetch function to twoPerSecond, twoPerSecond will return a promise
// immediately but will not start fetch untill there is an available timeslot
twoPerSecond(fetch)(url)
.catch(e=>new Fail([e,url]))
)
)
.then(
results=>{
const failed = results.map(result=>(result&&result.constuctor)===Fail);
const succeeded = results.map(result=>(result&&result.constuctor)!==Fail);
}
)

Categories

Resources