I am having an issue trying to share an alias between two steps in cypress. Basically what I am trying to do is in the first then step, I get the value and pass it in as a variable storedOddsValue. In the second then step, when I try to get that alias value and complete the step, it fails because I beleive it's coming back with undefined.
I know this because instead of using:
let storedOddsValue = oddslib.from("fractional", cy.get("#storedOddsValue"));
If I replace the alias with the hardcoded value like so:
let storedOddsValue = oddslib.from("fractional", "13/5");
The whole code below executes fine and the assertion passes. So my question is what am I doing wrong, why is it not getting the alias?
Then ("The prices are formatted in fractions by default", () => {
priceFormatElements.priceFormatDropdown().should('have.value', 'Fraction');
const oddsValue = oddsSelectionElements.oddsButton().first().invoke("text");
oddsValue.then((oddsValue) => {
expect(oddsValue.trim()).contains("/");
oddsValue.trim();
}).as("storedOddsValue")
})
Then ("The prices are formatted in {string}", (priceFormat) => {
let expectedOddsValue;
let currentOddsValue = oddsSelectionElements.oddsButton().first().invoke("text");
//let storedOddsValue = oddslib.from("fractional", "13/5");
let storedOddsValue = oddslib.from("fractional", cy.get("#storedOddsValue"));
switch (priceFormat) {
case "Fractional":
expectedOddsValue = storedOddsValue.to("fractional");
break;
case "Decimal":
expectedOddsValue = storedOddsValue.to("decimal", {precision: 2});
break;
case "American":
expectedOddsValue = "+" + storedOddsValue.to("moneyline");
break;
default:
throw new Error('unknown price format: ' + priceFormat);
}
currentOddsValue.then((oddsValue) => {
expect(oddsValue.trim()).equals(expectedOddsValue);
});
})
Not sure what oddslib.from does, but to use it with an alias you need to nest in this way
Then (... () => {
let expectedOddsValue
let currentOddsValue = oddsSelectionElements.oddsButton().first().invoke("text")
cy.get("#storedOddsValue").then(aliasValue => {
let storedOddsValue = oddslib.from("fractional", aliasValue)
switch (priceFormat) {
...
}
currentOddsValue.then((oddsValue) => {
expect(oddsValue.trim()).equals(expectedOddsValue);
});
}) // end of cy.get("#storedOddsValue")
})
Because cy.get("#storedOddsValue") is asynchronous, and the switch() runs before it gives it's value.
Related
This question already has answers here:
Cypress: How to get value of div (mixing up async and sync code)
(2 answers)
Closed 10 months ago.
I have a div that has a value of 15,000 that can be retrieved by
cy.get('.population').invoke('text').then((numbers) =>{
let people = parseInt(numbers.replaceAll(',',''))
cy.wrap(people).as('population')
})
However, I now need to get two other values in another div and then compare them to be sure they equal each other. This works, however, right after a successful assertion is throws this error cy.then() failed because you are mixing up async and sync code. In your callback function you invoked 1 or more cy commands but then returned a synchronous value. The value you synchronously returned was: {__flags: Object{5}}
Here is the entire code together.
cy.elem('population').invoke('text').then((num) =>{
let totalResidents = parseInt(num.replaceAll(',',''))
cy.wrap(totalResidents).as('population')
})
cy.get('#population').then((population) => {
cy.get(`div[col-
id=${columns.totalResidents}]`).find('span').find('div').then(($divs) => {
const nums = [...$divs].map(div => div.innerText)
const zcta1Pop = parseInt(nums[1].replaceAll(',',''))
const zcta2Pop = parseInt(nums[2].replaceAll(',',''))
const totalPop = zcta1Pop + zcta2Pop
return cy.expect(population).to.eq(totalPop) //successful assertion 15000 = 15000 but then fails right after
})
The second pair of divs can be represented by
<span>
<div style="margin-left: 10px;">
<div>7,000</div>
</div>
</span>
<span>
<div style="margin-left: 10px;">
<div>8,000</div>
</div>
</span>
The weird this is that the assertion at the end is correct assert expected 15000 to equal 15000 but then throws the error. Does anyone know whats going on here?
Unless you have a custom command, cy.expect()... is wrong syntax. It should just be expect()... - but you cannot return it.
You don't need to return anything from your last .then() because you've already done the assertion.
An alias is unnecessary if you use the value immediately after.
cy.elem('population').invoke('text')
.then((num) =>{
let totalResidents = parseInt(num.replaceAll(',',''))
return cy.wrap(totalResidents)
})
.then(totalResidents1 => {
cy.get(`div[col-id="${columns.totalResidents}"]`)
.find('span').find('div')
.then(($divs) => {
const nums = [...$divs].map(div => div.innerText)
const zcta1Pop = parseInt(nums[1].replaceAll(',',''))
const zcta2Pop = parseInt(nums[2].replaceAll(',',''))
const totalResidents2 = zcta1Pop + zcta2Pop
expect(totalResidents1).to.eq(totalResidents2)
})
})
Or with aliases
cy.elem('population').invoke('text')
.then(num => parseInt(num.replaceAll(',','')))
.as('totalResidents1')
cy.get(`div[col-id="${columns.totalResidents}"]`)
.find('span').find('div')
.then($divs => {
const nums = [...$divs].map(div => div.innerText)
const zcta1Pop = parseInt(nums[1].replaceAll(',',''))
const zcta2Pop = parseInt(nums[2].replaceAll(',',''))
const totalResidents2 = zcta1Pop + zcta2Pop
return cy.wrap(totalResidents2)
})
.as('totalResidents2')
cy.get('#totalResidents1')then(totalResidents1 => {
cy.get('#totalResidents2').then(totalResidents2 => {
expect(totalResidents1).to.eq(totalResidents2)
})
})
I am changing a user input that use to take a number of keys and values to add to an object in an array. Now there will only be a single string inside the object inside the array or atleast thats my hope. I dont want to change the backend if i dont have to. So right now I am having the trouble of the string value saving in a number of different objects with each letter typed for example:
1: "T"
2: "Th"
3: "Thi"
4: "Thin"
5: "Thing"
6: "Things"
so I'm wondering how can I have the value only saved once the user is done typing so only one array is saved?
Ive tried using an async function but I ended up with the same results my code looks like this:
... const recommendedDescription = async (value) => {
try {
let rec = await value
handleMedicationChange(rec)
}catch (error) {
console.log(error)
}
return
}
const handleMedicationChange = (value, medicationNames) => {
let medications = detailedTreatmentPlan.recommendedMedications || [];
// medications = medications.filter(
// (item) => !medicationNames.includes(item.name)
// );
console.log(value)
medications.push(value);
const data = update(detailedTreatmentPlan, {
recommendedMedications: { $set: medications }
});
props.onUpdate("detailedTreatmentPlan", data);
};
return (
<ContentBlock title="Recommended OTC Medication">
<Input
type="textarea"
note={detailedTreatmentPlan["recommendedMedications"]}
placeholder="Help"
onChange={e => recommendedDescription(e.target.value)}
className="recommendedMedications"
name="recommendedMedications"
id="recommendedMedications"
/>
</ContentBlock>
)
I have this function that is supposed to get referral codes from users. User gives a code and the referral code checked if it exists in the database then evaluated if
it does not match the current user, so that one should not refer himself and
it is a match with one of the codes in the database
This code however just does not find a match even if the code given is in the database. If the referral code matches the one of the current user, it works correctly and points that out i.e one cannot refer themselves.
But if the referral code is a match to that of another user which is how a referral system should work, it still says no match.
How can I remove this error
export const getID = functions.https.onCall(async(data, context) => {
const db = admin.firestore();
const usersSnapshot = await db.collection("user").get();
const allUIDs = usersSnapshot.docs.map(doc => doc.data().userID);
const userID = context.auth.uid;
const providedID = "cNx7IuY6rZlR9mYSfb1hY7ROFY2";
//db.collection("user").doc(providedID).collection("referrals").doc(userID);
await check();
function check() {
let result;
allUIDs.forEach(idFromDb => {
if (providedID === idFromDb && (idFromDb === userID)) {
result = "ownmatch";
} else if (providedID === idFromDb && (idFromDb !== userID)) {
result = "match";
} else {
result = "nomatch";
}
});
return result;
}
if (check() === "match") {
return {
message: `Match Found`,
};
} else if (check() === "ownmatch") {
return {
message: `Sorry, you can't use your own invite code`,
};
} else {
return {
message: `No User with that ID`
};
}
});
(This is not an answer, but a simple refactoring.)
This is what your code is currently doing (roughly, I didn't run it):
const resultMsgs = {
nomatch: 'No User With That ID',
ownmatch: 'Sorry, you can\'t use your own invite code',
match: 'Match Found',
}
function check(uids, providedId, userId) {
let result
uids.forEach(idFromDb => {
if (providedId !== idFromDb) {
result = 'nomatch'
return
}
if (userID === idFromDb) {
result = 'ownmatch'
return
}
result = 'match'
})
return result
}
export const getID = functions
.https
.onCall(async (data, context) => {
const userId = context.auth.uid
const providedId = 'cNx7IuY6rZlR9mYSfb1hY7ROFY2'
const db = admin.firestore()
const user = await db.collection('user').get()
const uids = user.docs.map(doc => doc.data().userId)
const checkResult = check(uids, providedId, userId)
return { message: resultMsgs[checkResult] }
})
(I removed the seemingly-spurious db collection operation.)
Your forEach is iterating over all of the uuids, but result will be set to whatever the last comparison was. Perhaps this is correct, but:
If you're looking for any match, this is not what you want.
If you're looking for all matches, this is not what you want.
If you're looking to match the last UUID, it's what you want, but an odd way to go about it.
So:
If you want any matches, use... ahem any form of an any function.
If you want all matches, use any form of an all function.
If you want the first match, then just check the first element.
If you want the complete set of comparisons then you'll need to use map instead of forEach, and handle each result appropriately, whatever that means in your case.
In any event, I'd recommend breaking up your code more cleanly. It'll be much easier to reason about, and fix.
I'm just starting to play around with Puppeteer (Headless Chrome) and Nodejs. I'm scraping some test sites, and things work great when all the values are present, but if the value is missing I get an error like:
Cannot read property 'src' of null (so in the code below, the first two passes might have all values, but the third pass, there is no picture, so it just errors out).
Before I was using if(!picture) continue; but I think it's not working now because of the for loop.
Any help would be greatly appreciated, thanks!
for (let i = 1; i <= 3; i++) {
//...Getting to correct page and scraping it three times
const result = await page.evaluate(() => {
let title = document.querySelector('h1').innerText;
let article = document.querySelector('.c-entry-content').innerText;
let picture = document.querySelector('.c-picture img').src;
if (!document.querySelector('.c-picture img').src) {
let picture = 'No Link'; } //throws error
let source = "The Verge";
let categories = "Tech";
if (!picture)
continue; //throws error
return {
title,
article,
picture,
source,
categories
}
});
}
let picture = document.querySelector('.c-picture img').src;
if (!document.querySelector('.c-picture img').src) {
let picture = 'No Link'; } //throws error
If there is no picture, then document.querySelector() returns null, which does not have a src property. You need to check that your query found an element before trying to read the src property.
Moving the null-check to the top of the function has the added benefit of saving unnecessary calculations when you are just going to bail out anyway.
async function scrape3() {
// ...
for (let i = 1; i <= 3; i++) {
//...Getting to correct page and scraping it three times
const result = await page.evaluate(() => {
const pictureElement = document.querySelector('.c-picture img');
if (!pictureElement) return null;
const picture = pictureElement.src;
const title = document.querySelector('h1').innerText;
const article = document.querySelector('.c-entry-content').innerText;
const source = "The Verge";
const categories = "Tech";
return {
title,
article,
picture,
source,
categories
}
});
if (!result) continue;
// ... do stuff with result
}
Answering comment question: "Is there a way just to skip anything blank, and return the rest?"
Yes. You just need to check the existence of each element that could be missing before trying to read a property off of it. In this case we can omit the early return since you're always interested in all the results.
async function scrape3() {
// ...
for (let i = 1; i <= 3; i++) {
const result = await page.evaluate(() => {
const img = document.querySelector('.c-picture img');
const h1 = document.querySelector('h1');
const content = document.querySelector('.c-entry-content');
const picture = img ? img.src : '';
const title = h1 ? h1.innerText : '';
const article = content ? content.innerText : '';
const source = "The Verge";
const categories = "Tech";
return {
title,
article,
picture,
source,
categories
}
});
// ...
}
}
Further thoughts
Since I'm still on this question, let me take this one step further, and refactor it a bit with some higher level techniques you might be interested in. Not sure if this is exactly what you are after, but it should give you some ideas about writing more maintainable code.
// Generic reusable helper to return an object property
// if object exists and has property, else a default value
//
// This is a curried function accepting one argument at a
// time and capturing each parameter in a closure.
//
const maybeGetProp = default => key => object =>
(object && object.hasOwnProperty(key)) ? object.key : default
// Pass in empty string as the default value
//
const getPropOrEmptyString = maybeGetProp('')
// Apply the second parameter, the property name, making 2
// slightly different functions which have a default value
// and a property name pre-loaded. Both functions only need
// an object passed in to return either the property if it
// exists or an empty string.
//
const maybeText = getPropOrEmptyString('innerText')
const maybeSrc = getPropOrEmptyString('src')
async function scrape3() {
// ...
// The _ parameter name is acknowledging that we expect a
// an argument passed in but saying we plan to ignore it.
//
const evaluate = _ => page.evaluate(() => {
// Attempt to retrieve the desired elements
//
const img = document.querySelector('.c-picture img');
const h1 = document.querySelector('h1')
const content = document.querySelector('.c-entry-content')
// Return the results, with empty string in
// place of any missing properties.
//
return {
title: maybeText(h1),
article: maybeText(article),
picture: maybeSrc(img),
source: 'The Verge',
categories: 'Tech'
}
}))
// Start with an empty array of length 3
//
const evaluations = Array(3).fill()
// Then map over that array ignoring the undefined
// input and return a promise for a page evaluation
//
.map(evaluate)
// All 3 scrapes are occuring concurrently. We'll
// wait for all of them to finish.
//
const results = await Promise.all(evaluations)
// Now we have an array of results, so we can
// continue using array methods to iterate over them
// or otherwise manipulate or transform them
//
results
.filter(result => result.title && result.picture)
.forEach(result => {
//
// Do something with each result
//
})
}
Try-catch worked for me:
try {
if (await page.$eval('element')!==null) {
const name = await page.$eval('element')
}
}catch(error){
name = ''
}
I use the Microsoft bot framework to come up with a "simple" PoC bot. I used a tutorial as a basis and extend it.
I've a couple of basic functions for differet intents (ie. greetings, goodbye, etc) and one with some more logic in it (reqstatus).
The simple ones (ie greeting.js) return the answer nicely but the more complex one doesn't (reqstatus.js). Running the main code of reqstatus.js (without the first "const getReqStatus = (entity) => {") in a standalone script works.
server.js (main) -> see call in "if (intent) {"...
const getFeelings = require('./intents/feelings.js')
const getGoodbyes = require('./intents/goodbyes.js')
const getGreetings = require('./intents/greetings.js')
const getHelp = require('./intents/help.js')
const getReqStatus = require('./intents/reqstatus.js')
...
const bot = new builder.UniversalBot(connector)
// Intents based on definitions on recast
const INTENTS = {
feelings: getFeelings,
goodbyes: getGoodbyes,
greetings: getGreetings,
help: getHelp,
reqstatus: getReqStatus,
}
// Event when Message received
bot.dialog('/', (session) => {
recastClient.textRequest(session.message.text)
.then(res => {
const intent = res.intent()
const entity = res.get('request_number')
console.log(`UserName: ${session.message.user.name}`)
console.log(`Msg: ${session.message.text}`)
console.log(`Intent: ${intent.slug}`)
if (intent) {
INTENTS[intent.slug](entity)
.then(res => session.send(res))
.catch(err => session.send(err))
}
})
.catch(() => session.send('Sorry I didn\'t get that. '))
})
...
greetings.js -> Returns the string ok
const getGreetings = () => {
const answers = ['Hi, my name is SuperBot. Nice to meet you!', ]
return Promise.resolve((answers))
}
module.exports = getGreetings
reqstatus.js -> Does not return anything
const getReqStatus = (entity) => {
var request = require('request');
var request_number = entity.toLowerCase()
var output = [];
// Processing
var lineReader = require('readline').createInterface({
input: fs.createReadStream('netreqs.csv')
});
lineReader.on('line', function (line) {
var jsonFromLine = {};
var lineSplit = line.split(';');
jsonFromLine.req = lineSplit[0];
jsonFromLine.req_count = lineSplit[1];
jsonFromLine.req_type = lineSplit[2];
//...
var req_lowever = jsonFromLine.req.toLowerCase()
if (req_lowever == request_number) {
output.push( `Your request ${jsonFromLine.req} was received`);
// simplified
}
});
// Output
lineReader.on('close', function (line) {
if (output == '') {
output.push( `I was not able to find a request like ${request_number}.`);
}
console.log(output); // list output
return Promise.resolve(output);
});
}
module.exports = getReqStatus
I also tried to put getReqStatus in a function but that also didn't work.
After a lot of trying and googling I'm still stuck and wanted to ask the experts here. Thanks a lot in advance.
I think that the problem is that your getReqStatus isn't really returning anything. In your example getGreetings function you're actually returning Promise.resolve(answers) as the return value of that function.
However, in your getReqStatus function, you just set up a listener lineReader close event:
lineReader.on('close', function (line) {
if (output == '') {
output.push( `I was not able to find a request like ${request_number}.`);
}
console.log(output); // list output
return Promise.resolve(output);
});
You're returning a Promise resolved inside the anonymous callback function you're passing to lineReader.on() as second parameter. That is not the return value from the getReqStatus function itself, so that getReqStatus is not returning anything, as expected.
The code of that function runs correctly as standalone code, as you say, just because it sets the listener properly and it does what it has to do. However, that code just doesn't return a Promise when wrapped in a function.
What you would need is to return a Promise that wraps the lineReader.on close handler, like:
function getReqStatus(){
//...code
return new Promise( function(resolve , reject ){
lineReader.on('close', function (line) {
if (output == '') {
output.push( `I was not able to find a request like ${request_number}.`);
}
console.log(output); // list output
return resolve(output);
});
});
}
I say would because I really don't know if this code will work, I don't have any kind of experience with the Microsoft Bot framework and not used at all with the readline module. However, even if this doesn't solve your problem, I hope it will help you a bit understanding why your function doesn't return a Promise and how could you fix it.