Varible passed to page.evaluate is undefined - javascript

When I use page.evaluate and pass varibles to it, they are undefined.
let reaction;
// First console.log actually logs the string as expected
console.log(reactionTitle);
await this.page.evaluate(function (reactionTitle, reaction) {
// Second console.log logs undefined in the website's console
console.log(reactionTitle);
document.querySelectorAll('span.embedAuthorName-3mnTWj').forEach(s => {
// Third console.log logs all innerTexts in the website's console
console.log(s.innerText)
if (s.innerText == reactionTitle) {
console.log(s);
reaction = s.offsetParent.offsetParent.querySelector('.container-1ov-mD').querySelector('.reactions-12N0jA').querySelector('div').querySelector('.reaction-1hd86g').querySelector('.reactionInner-15NvIl');
console.log(reaction)
}
});
}, (reactionTitle, reaction));
console.log(reaction)
I put some comments in the code to show where reactionTitle is undefined.

Remove the parentheses from (reactionTitle, reaction) in arguments:
await this.page.evaluate((reactionTitle, reaction) => { ... }, reactionTitle, reaction)
or
await this.page.evaluate(({reactionTitle, reaction}) => { ... }, {reactionTitle, reaction})
page.evaluate executes in page context and is separate from the puppeteer execution environment. I'd suggest taking look at the docs and the architecture. To access the variables from in-page context in puppeteer, you need to return the variables. Note that the variables must be serializable:
const result = await this.page.evaluate(({reactionTitle, reaction}) => {
...
return reaction;
}, {reactionTitle, reaction});
In case the returned value is an in-page object, use evaluateHandle.

Related

How to search an array of objects obtained by axios for an id? Vue 2

I am trying to verify if the user is inside that list that I capture by axios, the issue is that I have used the FILTER option but it always returns undefined or [], being that if the user exists in that array.
I can't think what else to do, because I validate if it is by console.log() the variable with which I ask and if it brings data.
created() {
this.getStagesDefault()
this.getSalesman()
this.getStagesAmountByUser()
},
methods: {
async getSalesman(){
const { data } = await axios.get('salesman')
this.employees = data.data
},
getStagesAmountByUser(){
console.log(this.user['id'])
var objectUser = this.employees.filter(elem => {
return elem.id === this.user['id']
})
console.log(objectUser)
},
Console
Vue data
The method getSalesman is asynchronous, meaning that getStagesAmountByUser will start executing before getSalesman finishes.
Two ways to fix the problem:
Await the getSalesman method, but you have to make the created method async as well. Change the code as follows:
async created() {
this.getStagesDefault()
await this.getSalesman()
this.getStagesAmountByUser()
}
Attach a .then to the getSalesman function, and start the next one inside the .then. Change the code as follows:
created() {
this.getStagesDefault()
this.getSalesman().then(() => this.getStagesAmountByUser())
}
getSalesman is an async method. At the time of the filter, the array being filtered is still empty.
this.getSalesman() // this runs later
this.getStagesAmountByUser() // this runs right away
Have the methods run sequentially by awaiting the async method:
await this.getSalesman()
this.getStagesAmountByUser()
You can avoid the inefficient clientside filtering if you pass the id to the backend and only select by that id.
Additionally, created only gets called once unless you destroy the component which is also inefficient, so watch when user.id changes then call your method again.
Plus don't forget you must wrap any async code in a try/catch else you will get uncaught errors when a user/salesman is not found etc, you can replace console.error then with something which tells the user the error.
{
data: () => ({
employee: {}
}),
watch: {
'user.id' (v) {
if (v) this.getEmployee()
}
},
created() {
this.getEmployee()
},
methods: {
getEmployee() {
if (typeof this.user.id === 'undefined') return
try {
const {
data
} = await axios.get(`salesman/${this.user.id}`)
this.employee = data.data
} catch (e) {
console.error(e)
}
}
}
}

Asynchronicity, and variable overrides in Cypress

My question is, how do you override the variable in the async function which is out of the scope of this?
I read here that the problem is the lack of a callback. After adding the callback, the variable outside the scope in which it was changed (but still the variable itself is in the correct scope) returns "undefined". What am I doing wrong?
Test:
const savedVariableCallback = (variable) =>
console.log(`Variable saved ${variable}`);
describe(() => {
...
it("Sample input type", () => {
let fixValue;
cy.fixture("example.json").then(({ email }) => {
actionsPage
.selectSampleInput()
.then((input) => {
checkAmountOfElements(input, 1);
checkVisiblity(input);
})
.type(email)
.then((input) => {
checkIfValue(input, email);
fixValue = "Nice work";
savedVariableCallback(fixValue);
});
});
cy.log(`fixValue is: ${fixValue}`);
});
})
I expect the first log to show Variable saved Nice work and the second log to show fixValue is: Nice work for variables. But for now, I get in the first log Variable saved Nice work but in second I get undefined.
I want to have that variable to be accessible in it() method scope.
Edit: Since the reference didn`t work I suggest approaching it with an allias
const savedVariableCallback = (variable) =>
console.log(`Variable saved ${variable}`);
describe(() => {
...
it("Sample input type", () => {
cy.fixture("example.json").then(({ email }) => {
actionsPage
.selectSampleInput()
.then((input) => {
checkAmountOfElements(input, 1);
checkVisiblity(input);
})
.type(email)
.then((input) => {
checkIfValue(input, email);
let fixValue = "Nice work";
savedVariableCallback(fixValue);
cy.wrap(fixValue).as('fixValue')
});
});
cy.get('#fixValue')
.then(fixValue => {
cy.log(`fixValue is: ${fixValue.value}`);
})
});
})
If you change
cy.log(`fixValue is: ${fixValue}`)
to
console.log(`fixValue is: ${fixValue}`)
you can see the order of logging
fixValue is: undefined
Variable saved Nice work
so cy.log() grabs the value of fixValue at the time the command is added to the queue, that is before cy.fixture() has run, even though it actually runs after the cy.fixture().
You can defer it until cy.fixture() is complete by adding another .then()
cy.fixture("example.json").then(data => {
...
})
.then(() => {
// enqueued after the fixture code
cy.log(`fixValue is: ${fixValue}`) // now it sees the changed value
})
but of course everything downstream that needs to use fixValue must be inside the .then() callback.
You can also defer the "grabbing" of the value
cy.then(() => cy.log(`fixValue is: ${fixValue}`))
or split cy.fixture() into a before(), which will resolve the variable before the test begins
let fixValue;
before(() => {
cy.fixture("example.json").then(data => {
...
fixValue = ...
})
})
it('tests', () => {
cy.log(`fixValue is: ${fixValue}`) // sees the changed value
})

Array is not array anymore when passed into Vuex action function

EDIT: Added extra code in the filterEvents snippet for more context.
I'm not quite understanding what's going on with my code. I'm trying to pass an array into an action function inside of my Vuex store. If I return a Promise inside of that action function, then the parameter being passed isn't of type Array and is instead an Object, which results in the reject() error that I have for the Promise.
Here's some code for context:
filterEvents({ commit }, events) {
console.log(Array.isArray(events)); //this ends up false
console.log(events);
return new Promise((resolve, reject) => {
if (!Array.isArray(events)) {
reject("Invalid argument: is not of type Array.");
}
let filtered = events.filter((event) => {
let now = new Date();
let event_stop = new Date(event.stop_time);
if (event_stop >= now || event_stop == null) {
return event;
}
});
resolve(filtered);
});
}
Here's where I call filterEvents; inside of getEvents;
getEvents({ state, commit, dispatch }, searchParams) {
.....
eventful.getEvents(searchParams).then(async (res) => {
.....
console.log(Array.isArray(res.data.events.event)); //this ends up true
console.log(res.data.events.event);
/* where I call it */
await dispatch("filterEvents", res.data.events.event).then((res) => {
.....
});
}).catch((err) => {
.....
});
}
Here's the output from the Chrome developer console. First two outputs are from getEvents and last two are from filterEvents
Would really like an explanation as to why this is the case. I'm going to bet it's something small, but it's 3 a.m. at the moment and my brain can't wrap around why it's not of type Array when passed into filterEvents.
I always try to check the length prop of the array which helps me out in such cases.
...
return new Promise((resolve, reject) => {
if (!Array.isArray(events) && !events.length) {
reject("Invalid argument: is not of type Array.");
}
.....
});
...
I finally understood what my issue was after taking another look at the object that was being logged on the console. I did not know that Vuex actions HAD to have two arguments if you want to pass in a payload into that function. For example, I initially did this
filterEvents(events) {
.....
}
but what I really needed to do was
filterEvents(context, events) {
.....
}
The context argument is the object that allows you to do things such as commit and dispatch. I usually destructure the context object (i.e. { commit, dispatch} ), so I for some reason never thought twice about it. You don't have to destructure the context object to use commit and dispatch; if you don't it would just be like
context.commit('function', payload);

axios promise returns correct value in "axios.all" function, but is undefined in the "then" function

I'm following a tutorial in which data from the git API is requested and a scoring algorithm will order that data.
The battle function will take an array of two elements, i.e two github users. We retrieve the profile and score for eah user from the getUserData method
module.exports = {
battle: function(players) {
return axios.all(players.map(getUserData))
.then(response => {
response.forEach( each=>console.log(each));
return response;
})
}
}
The getProfile and getRepos functions ork correctly in retrieving objects which have data on the users profile(username, followers, etc) and their repos(repo names, etc.). So I've omitted the code for both these functions as I already know they work for certain. Additionally, the calculateScore method also works and returns output as expected.
The console.log statement shows that the object with keys "profile" and "score" is correctly made, and prints out both the profile object data and the score as expected. So far so good.
function getUserData(player) {
axios.all([
getProfile(player),
getRepos(player)
])
.then(function(data) {
var profile = data[0];
var repos = data[1];
console.log({
profile: profile,
score: calculateScore(profile, repos)
})
return {
profile: profile,
score: calculateScore(profile, repos)
}
})
}
The Problem:
The callback function in "battle" should receive an array of size 2, with each element containing the profile and score for that particular player. e.g:
[
{
profile: {profile data for player1...}
score: 10 //or whatever the score is
},
{
profile: {profile data for player2...}
score: 2 //or whatever the score is
}
]
but instead the callback function is receiving [undefined, undefined] as its input from the axios.all function
Correct me if I'm wrong, but in promises, isn't the output from the "axios.all" method supposed to be the input for the "then" method. So why am I getting undefined if the console.log statement shows that axios.all is outputting the correct data?
Your getUserData function does not return anything. Change it as below:
function getUserData(player) {
return axios.all([
// ...
]);
}
That behaviour is because you return an array of undefined values when you do response.map where you replace all the items with undefined (console.log returns undefined).
Instead, return the actual result from the asynchronous call:
module.exports = {
battle: function(players) {
return axios.all(players.map(getUserData))
.then(response => {
response.forEach(each => console.log(each));
return response;
});
}
}

Mocha / Chai expect.to.throw not catching thrown errors

I'm having issues getting Chai's expect.to.throw to work in a test for my node.js app. The test keeps failing on the thrown error, but If I wrap the test case in try and catch and assert on the caught error, it works.
Does expect.to.throw not work like I think it should or something?
it('should throw an error if you try to get an undefined property', function (done) {
var params = { a: 'test', b: 'test', c: 'test' };
var model = new TestModel(MOCK_REQUEST, params);
// neither of these work
expect(model.get('z')).to.throw('Property does not exist in model schema.');
expect(model.get('z')).to.throw(new Error('Property does not exist in model schema.'));
// this works
try {
model.get('z');
}
catch(err) {
expect(err).to.eql(new Error('Property does not exist in model schema.'));
}
done();
});
The failure:
19 passing (25ms)
1 failing
1) Model Base should throw an error if you try to get an undefined property:
Error: Property does not exist in model schema.
You have to pass a function to expect. Like this:
expect(model.get.bind(model, 'z')).to.throw('Property does not exist in model schema.');
expect(model.get.bind(model, 'z')).to.throw(new Error('Property does not exist in model schema.'));
The way you are doing it, you are passing to expect the result of calling model.get('z'). But to test whether something is thrown, you have to pass a function to expect, which expect will call itself. The bind method used above creates a new function which when called will call model.get with this set to the value of model and the first argument set to 'z'.
A good explanation of bind can be found here.
As this answer says, you can also just wrap your code in an anonymous function like this:
expect(function(){
model.get('z');
}).to.throw('Property does not exist in model schema.');
And if you are already using ES6/ES2015 then you can also use an arrow function. It is basically the same as using a normal anonymous function but shorter.
expect(() => model.get('z')).to.throw('Property does not exist in model schema.');
This question has many, many duplicates, including questions not mentioning the Chai assertion library. Here are the basics collected together:
The assertion must call the function, instead of it evaluating immediately.
assert.throws(x.y.z);
// FAIL. x.y.z throws an exception, which immediately exits the
// enclosing block, so assert.throw() not called.
assert.throws(()=>x.y.z);
// assert.throw() is called with a function, which only throws
// when assert.throw executes the function.
assert.throws(function () { x.y.z });
// if you cannot use ES6 at work
function badReference() { x.y.z }; assert.throws(badReference);
// for the verbose
assert.throws(()=>model.get(z));
// the specific example given.
homegrownAssertThrows(model.get, z);
// a style common in Python, but not in JavaScript
You can check for specific errors using any assertion library:
Node
assert.throws(() => x.y.z);
assert.throws(() => x.y.z, ReferenceError);
assert.throws(() => x.y.z, ReferenceError, /is not defined/);
assert.throws(() => x.y.z, /is not defined/);
assert.doesNotThrow(() => 42);
assert.throws(() => x.y.z, Error);
assert.throws(() => model.get.z, /Property does not exist in model schema./)
Should
should.throws(() => x.y.z);
should.throws(() => x.y.z, ReferenceError);
should.throws(() => x.y.z, ReferenceError, /is not defined/);
should.throws(() => x.y.z, /is not defined/);
should.doesNotThrow(() => 42);
should.throws(() => x.y.z, Error);
should.throws(() => model.get.z, /Property does not exist in model schema./)
Chai Expect
expect(() => x.y.z).to.throw();
expect(() => x.y.z).to.throw(ReferenceError);
expect(() => x.y.z).to.throw(ReferenceError, /is not defined/);
expect(() => x.y.z).to.throw(/is not defined/);
expect(() => 42).not.to.throw();
expect(() => x.y.z).to.throw(Error);
expect(() => model.get.z).to.throw(/Property does not exist in model schema./);
You must handle exceptions that 'escape' the test
it('should handle escaped errors', function () {
try {
expect(() => x.y.z).not.to.throw(RangeError);
} catch (err) {
expect(err).to.be.a(ReferenceError);
}
});
This can look confusing at first. Like riding a bike, it just 'clicks' forever once it clicks.
examples from doc... ;)
because you rely on this context:
which is lost when the function is invoked by .throw
there’s no way for it to know what this is supposed to be
you have to use one of these options:
wrap the method or function call inside of another function
bind the context
// wrap the method or function call inside of another function
expect(function () { cat.meow(); }).to.throw(); // Function expression
expect(() => cat.meow()).to.throw(); // ES6 arrow function
// bind the context
expect(cat.meow.bind(cat)).to.throw(); // Bind
One other possible implementation, more cumbersome than the .bind() solution, but one that helps to make the point that expect() requires a function that provides a this context to the covered function, you can use a call(), e.g.,
expect(function() {model.get.call(model, 'z');}).to.throw('...');
I have found a nice way around it:
// The test, BDD style
it ("unsupported site", () => {
The.function(myFunc)
.with.arguments({url:"https://www.ebay.com/"})
.should.throw(/unsupported/);
});
// The function that does the magic: (lang:TypeScript)
export const The = {
'function': (func:Function) => ({
'with': ({
'arguments': function (...args:any) {
return () => func(...args);
}
})
})
};
It's much more readable then my old version:
it ("unsupported site", () => {
const args = {url:"https://www.ebay.com/"}; //Arrange
function check_unsupported_site() { myFunc(args) } //Act
check_unsupported_site.should.throw(/unsupported/) //Assert
});

Categories