Await in TestCafe - javascript

I have been having some problems with understanding when/how await is used and required in TestCafé.
Why is does this function require an await on the first line?
async getGroupCount(groupName) {
const groupFilterLinkText = await this.groupFilterLink.withText(groupName).textContent;
const openInd = groupFilterLinkText.indexOf('(');
const closeInd = groupFilterLinkText.indexOf(')');
const count = parseInt(groupFilterLinkText.slice(openInd + 1, closeInd), 10);
return count;
}
I later found in the doc that it says "Note that these methods and property getters are asynchronous, so use await to obtain an element's property." However, I'm using the function I wrote in a pretty synchronous way so this took me completely by surprise. I have ensured that the page is in the state that I want it to be in, and I just want to parse a string, but the test errors on line 2 of this function without the async/await.
Related to this, this is an example test case that calls that function:
test('Verify an account owner can create a single user', async (t) => {
const userName = 'Single User';
const userEmail = 'singleuser#wistia.com';
const origUserCount = await usersPage.getGroupCount('All Users');
await t
.click(usersPage.addUserButton);
await waitForReact();
await t
.typeText(usersPage.addUserModal.nameInput, userName)
.typeText(usersPage.addUserModal.emailInput, userEmail)
.expect(usersPage.addUserModal.saveButton.hasAttribute('disabled')).notOk()
.click(usersPage.addUserModal.saveButton)
.expect(usersPage.userCard.withText(userName).exists).ok({ timeout: 10000 });
const newUserCount = await usersPage.getGroupCount('All Users');
await t
.expect(newUserCount).eql(origUserCount + 1);
});
Originally, I had the last few lines of the test looking like this:
await t
...
.click(usersPage.addUserModal.saveButton)
.expect(usersPage.userCard.withText(userName).exists).ok({ timeout: 10000 })
.expect(await usersPage.getGroupCount('All Users')).eql(origUserCount + 1);
That is, it was included in one function chain for the entire test. This failed because the value of await usersPage.getGroupCount('All Users') was still returning the original value instead of getting updated. I don't understand why I need to separate that out into its own call to the function. Why is that getGroupCount being evaluated seemingly at the beginning of the test rather than when the test reaches that line of code? It doesn't seem to be behaving asynchronously in the way that I expected.

Looking at the documentations (https://devexpress.github.io/testcafe/documentation/test-api/built-in-waiting-mechanisms.html), it seems like you don't have to separate it out into it's own call but you'll have to shift the await to the beginning of the chain?
So for example, instead of:
<something>
.click(usersPage.addUserModal.saveButton)
.expect(usersPage.userCard.withText(userName).exists).ok({ timeout: 10000 })
.expect(await usersPage.getGroupCount('All Users')).eql(origUserCount + 1);
You can do:
await <something>
.click(usersPage.addUserModal.saveButton)
.expect(usersPage.userCard.withText(userName).exists).ok({ timeout: 10000 })
.expect(usersPage.getGroupCount('All Users')).eql(origUserCount + 1);

Related

Function does not pass object to the constant

I'm new to javascript so maybe it's a dumb mistake. I'm trying to pass the values ​​of the object that I get in this webscrapping function to the constant but I'm not succeeding. Every time I try to print the menu it prints as "undefined".
`
const puppeteer = require("puppeteer");
async function getMenu() {
console.log("Opening the browser...");
const browser = await puppeteer.launch({
headless: true
});
const page = await browser.newPage();
await page.goto('https://pra.ufpr.br/ru/ru-centro-politecnico/', {waitUntil: 'domcontentloaded'});
console.log("Content loaded...");
// Get the viewport of the page
const fullMenu = await page.evaluate(() => {
return {
day: document.querySelector('#conteudo div:nth-child(3) p strong').innerText,
breakfastFood: document.querySelector('tbody tr:nth-child(2)').innerText,
lunchFood: document.querySelector('tbody tr:nth-child(4)').innerText,
dinnerFood: document.querySelector('tbody tr:nth-child(6)').innerText
};
});
await browser.close();
return {
breakfast: fullMenu.day + "\nCafé da Manhã:\n" + fullMenu.breakfastFood,
lunch: fullMenu.day + "\nAlmoço:\n" + fullMenu.lunchFood,
dinner: fullMenu.day + "\nJantar:\n" + fullMenu.dinnerFood
};
};
const menu = getMenu();
console.log(menu.breakfast);
`
I've tried to pass these values ​​in several ways to a variable but I'm not succeeding. I also accept other methods of passing these strings, I'm doing it this way because it's the simplest I could think of.
Your getMenu() is an async function.
In your last bit of code, can you change it to,
(async () => {
let menu = await getMenu();
console.log(menu.breakfast);
})();
credit to this post.
I have no access to the package that you imported. You may try changing the last part of your code to:
const menu = await getMenu();
if (menu) {
console.log(menu.breakfast);
}
Explanation
getMenu() and await getMenu() are different things in JS. getMenu() is a Promise Object which does not represent any string / number / return value. await getMenu() tells JS to run other code first to wait for the result of getMenu().
Despite await tells JS to wait for getMenu() to be resolved, it doesn't stop console.log(menu.breakfast) from running. Your code will try to access menu - which at that moment it is a Promise object. Therefore, breakfast property doesn't exist in the Promise object, so you get undefined.
By adding a if (menu) {...} statement, javascript will wait until menu is resolved before going inside the if-statement. This is useful when you want to do console.log() on a async/await return value.

Variable is used before its assignment typescript

I have a set of async tasks which is independent of each other. I have two questions, when considering the below code.
const parallelTasks: any = []
let email:string
//parallelTasks.push(otherPromises)
const PromiseTask1 = new Promise(async(resolve,reject)=>{
//some code
email = await GetEmail(connection)
resolve('')
})
parallelTasks.push(PromiseTask1)
// Scenario 1
await Promise.all(parallelTasks)
console.log(email) //this is giving me lint error "variable email is used before assignment"
// Scenario 2
Promise.all(parallelTasks).then(()=>{
console.log(email) //this is working fine
})
if i declare just like below, its not showing me any lint error. my questions are
let email: any
Is any includes Promise type as well?
Just consider am using scenario 1, Is await Promise.all() blocks the execution of all its below code, if yes, then I should not be getting the lint error when i used email: string. if no then, I Shouldnt be getting the value of email in console right. but surprisingly I got the resolved value. what am missing here?
Re #1
Yes, any includes any type, including Promise.
Re #2
I think you mean you're getting:
Variable 'email' is used before being assigned.(2454)
from the line using email in code similar to this:
(async () => {
const parallelTasks: any = []
let email:string
//parallelTasks.push(otherPromises)
const PromiseTask1 = new Promise(async(resolve,reject)=>{
//some code
email = await GetEmail(/*connection*/)
resolve('')
})
parallelTasks.push(PromiseTask1)
// Scenario 1
await Promise.all(parallelTasks)
console.log(email) //this is giving me error
})();
async function GetEmail() {
return "foo";
}
Playground link
If so, it's just that you've hit a limit on TypeScript's ability to determine that email has been assigned to. You're right, it has been assigned to, but it's done too indirectly for TypeScript to see it.
There are at least two ways to deal with that.
The first way
You could use the result of Promise.all, by removing your extra unnecessary new Promise call and using GetEmail directly:
(async () => {
const parallelTasks: any = [];
parallelTasks.push(GetEmail(/*connection*/))
const [email] = await Promise.all(parallelTasks);
console.log(email); // No error now
})();
async function GetEmail() {
return "foo";
}
Playground link
You'd adjust the destructuring to allow for the results of the other tasks you're putting into parallelTasks. For instance:
parallelTasks.push(task1());
parallelTasks.push(task2());
parallelTasks.push(GetEmail(connection));
parallelTasks.push(task4());
then it might be:
const [result1, result2, email, result4] = await Promise.all(paralleltasks);
or if you didn't need the others, just put GetEmail(connection) in at the front and use the original destructuring above (const [email] = ...).
The second way
You can tell TypeScript that you know email will be definitely assigned before you use it, using the definitely-assigned assertion (!):
let email!:string;
// ^

There are 2 identical receive values ​from a promise: in one case it works, in another it gives an TypeError: x is not a function

I use the element search in my autotest and take the name from the list. My code works, everything is fine. But in autotest I use this code several times. Therefore, I decided to put it into a function and call it when I need to.
Code operates:
await driver.wait(until.elementLocated(By.className("item")), 20000);
let findItems1 = await driver.findElements(By.className("item"));
let items1 = findItems1.map(async elem => await elem.getText());
await Promise.all(items1);
let currentItem1 = findItems1[findItems1.length - 1];
await currentItem1.click();
currentName = await currentItem1.getText(); // This string operates
await Promise.all(currentName)
console.log(currentName)
I infer the value of the variable from the function in which the promise lies. I can click on this item. But when I want to get a text value from a promise, the string "currentName = await currentItem1.getText()" throws an error. Although in my first code this line works. I don’t understand what could be the reason.
Code doesn't operate:
async function findCurrentItem(){
await driver.wait(until.elementLocated(By.className("item")), 20000);
let findItems = await driver.findElements(By.className("item"));
let items = findItems.map(async elem => await elem.getText());
await Promise.all(items);
let currentItem = findItems[findItems.length - 1];
return currentItem;
}
let current = findCurrentItem();
await currentItem1.click();
console.log(current, 1) // console displays promise
let currentName = await current.getText(); // This string doesn't operate
await Promise.all(currentName)
console.log(currentName, 2) // console displays error
Error:
TypeError: currentItem.getText is not a function
What can I do?
You made findCurrentItem async function but don't await its result when using it.
Change to let current = await findCurrentItem();

Assert presence of text in an html tag attribute using Puppeteer, Mocha and Chai

I'm beginning my journey with these technologies (including Javascript), so, a beginners question. I'm struggling to work out how to assert that the given text within an HTML attribute is as expected.
The HTML snippet:
<input name="8hv3a" type="radio" id="email-optout-8hv3a" value="1|optin|out" data-com.user-edited="yes">
Here is my .it function thus far, using Mochai, Puppeteer and Chai (setup and teardown elided for clarity:
it('opt out of email', async function () {
await page.setDefaultNavigationTimeout();
await page.waitForSelector('.widget-title');
const frame = page.frames().find(frame => frame.name() === 'iframe');
const emailOptOutButton = await frame.$('#email-optout-8hv3a');
await emailOptOutButton.click();
const emailOptOutConfirmedValue = await frame.$('#email-optout-8hv3a', e => e.getAttribute('data-com.user-edited'))
expect(emailOptOutConfirmedValue).to.include('yes')
})
Everything works up until the click event, but my assertion is clearly wrong. The error is:
AssertionError: object tested must be an array, a map, an object, a set, a string, or a weakset, but object given
I've tried
it('opt out of email', async function () {
await page.setDefaultNavigationTimeout();
await page.waitForSelector('.widget-title');
const frame = page.frames().find(frame => frame.name() === 'iframe');
const emailOptOutButton = await frame.$('#email-optout-8hv3a');
await emailOptOutButton.click();
await page.$eval.emailOptOutConfirmedValue.getAttribute('data-com.user-edited')
expect(emailOptOutConfirmedValue).to.include('yes')
})
Which gives:
TypeError: Cannot read property 'getAttribute' of undefined
And also:
it('opt out of email', async function () {
await page.setDefaultNavigationTimeout();
await page.waitForSelector('.widget-title');
const frame = page.frames().find(frame => frame.name() === 'iframe');
const emailOptOutButton = await frame.$('#email-optout-8hv3a');
await emailOptOutButton.click();
const emailOptOutConfirmedValue = await frame.$('#email-optout-8hv3a', e => e.getAttribute('data-com.user-edited'))
assert.equal(emailOptOutConfirmedValue, 'yes')
})
Which gives:
ReferenceError: assert is not defined
As I said, I'm a beginner so go any help appreciated!
This code is wrong:
const emailOptOutConfirmedValue = await frame.$('#email-optout-8hv3a', e => e.getAttribute('data-com.user-edited'))
expect(emailOptOutConfirmedValue).to.include('yes')
You're using frame.$(selector). Notice how it takes only one argument. The second argument is ignored and your call returns a handle to a DOM element, which causes the expect(...).to.include(...) to fail with a complaint that it cannot test that thing.
You should use frame.$eval(selector, pageFunction[, ...args])
instead. This call will call the function passed as a second argument on the result of applying the selector. So the code should be:
const emailOptOutConfirmedValue = await frame.$eval('#email-optout-8hv3a', e => e.getAttribute('data-com.user-edited'))
expect(emailOptOutConfirmedValue).to.include('yes')
I've left your test intact above, but it seems to me what you'd be looking for here is for a specific value for the attribute, so I'd use this test:
expect(emailOptOutConfirmedValue).to.equal('yes')
The inclusion test would pass if the attribute's value is baryesfoo.

Puppeteer Async Await Loop in NodeJS

I am trying to make a script that :
Grabs all urls from a sitemap
Takes a screenshot of it with puppeteer
I am currently trying to understand how to code asynchronously but I still have troubles with finding the right coding pattern for this problem.
Here is the code I currently have :
// const spider = require('./spider');
const Promise = require('bluebird');
const puppeteer = require('puppeteer');
const SpiderConstructor = require('sitemapper');
async function crawl(url, timeout) {
const results = await spider(url, timeout);
await Promise.each(results, async (result, index) => {
await screen(result, index);
});
}
async function screen(result, index) {
const browser = await puppeteer.launch();
console.log('doing', index);
const page = await browser.newPage();
await page.goto(result);
const path = await 'screenshots/' + index + page.title() + '.png';
await page.screenshot({path});
browser.close();
}
async function spider(url, timeout) {
const spider = await new SpiderConstructor({
url: url,
timeout: timeout
});
const data = await spider.fetch();
console.log(data.sites.length);
return data.sites;
};
crawl('https://www.google.com/sitemap.xml', 15000)
.catch(err => {
console.error(err);
});
I am having the following problems :
The length of the results array is not a constant, it varies every time I launch the script, which I guess resides in the fact it is not fully resolved when I display it, but I thought the whole point of await was so that we are guarantied that on next line the promise is resolved.
The actual screenshotting action part of the script doesn't work half the time and I am pretty sure I have unresolved promises but I have no of the actual pattern for looping over an async function, right now it seems like it does a screenshot after the other (linear and incremental) but I get alot of duplicates.
Any help is appreciated. Thank you for your time

Categories

Resources