Custom function in testcafe - javascript

So, i am trying to create a custom function that will allow me to check if a field contains a number or a text, but for further test i will need to check more complex stuff, like if the sum of some table equals something, etc.
I can't find examples of custom functions for example:
function isNumber(n) {
let a = parseInt(n);
if (a > 0 || a < 0) {
return true
}
return false
}
test('Test example', async t => {
await t
.expect(isNumber(Selector('#thisNum').innerText)).ok('This is a number' );
});

The assertion message will only be displayed when the assertion fails (refer to the message parameter). For example,
await t
.expect(failingValue).ok('failingValue is not a number');
Would display something like the following on a failed test:
1) AssertionError: failingValue is not a number: expected false to be truthy
Therefore, I'd never expect to see the "This is a number" message displayed.
As for the function, I've experienced a couple of instances where the promise wasn't resolved yet, so try awaiting the number selector:
await t
.expect(isNumber(await Selector('#thisNum').innerText)).ok('This is a number');
Hope this helps.

Related

How to wait for Cypress then() command to finish before returning a value?

I'm trying to set a variable within a .then() command which is declared outside of it, and after the whole block finished (the .then()) I'm returning that value.
The problem is, when I return the value, the variable is undefined, but within the .then() block, the variable is loaded.
Here is the example code:
public getValueFromElement(): string {
cy.log("Obtaining the Value");
let myNumber: string; // Here I'm declaring my variable
cy.get(this.labelWithText).then(($element) => {
let originalLabelText: string = $element.text();
let splittedText: string[];
splittedText = originalLabelText.split(": ");
myNumber = splittedText[1]; // Here I'm assigning the value
cy.log("Inside the THEN" + myNumber); //This logs the number correctly
});
return myNumber; // But after I return it using the function, the value is `undefined`!
}
I'm assuming this could be related to the async / sync problem, as the return statement is being executed immediately when the function is called, and the promise created by the .then() is still running, but I don't know how to fix this.
Do you know how I can wait for the .then() to finish first before returning the value?
Thanks!!
You say "The problem is, when I return the value, the variable is undefined".
That's because the return myNumber line runs before the cy.get(this.labelWithText).then(($element) => { completes, because the command is running asynchronously.
You need to return the command itself, and also the derived myNumber is returned from inside the .then().
public getValueFromElement(): Chainable<string> { // cannot return the raw string
cy.log("Obtaining the Value");
return cy.get(this.labelWithText).then(($element) => {
...
const myNumber = splittedText[1];
cy.log("Inside the THEN " + myNumber)
return myNumber
})
}
Use it like this
getValueFromElement().then(myNumber => {
cy.log("Outside the function " + myNumber)
})
I've come to the conclusion that this works:
public async getTheNumber(): Promise<string> {
return new Promise((resolve, reject) => {
cy.log("Retrieving the number");
cy.get(this.selector).then(($element) => {
let myNumber = $element.text().split(": ")[1];
cy.log(`The Number is ${myNumber}`);
resolve(myNumber);
});
});
}
and when reading it from the test I'm doing this:
myNumberAtTestLevel = await myObject.getTheNumber();
Thing is that I've seen that I have to change my it() method to async in order for this to work.
However, I've come across this documentation of Cypress:
https://docs.cypress.io/api/utilities/promise#Syntax
I'm trying to implement the same using Cypress.Promises but I'm not able to.
Any ideas?
You can do it synchronously like this
public getValueFromElement(): string {
cy.log("Obtaining the Value");
const $element = Cypress.$(this.labelWithText)
const originalLabelText: string = $element.text()
const splitText = originalLabelText.split(": ")
const myNumber = splitText[1]
return myNumber
}
Here you sacrifice the retry options which are built into asynchronous commands.
Cypress says to use it only if you are sure the element exists already, which depends on the context of your text.
#MikhailBolotov indeed. This is how you'd handle that
cy.get("myOpenElementSelector").click() // async code
.then(() => { // must wrap sync code in then
const myNumber = getValueFromElement() // to ensure correct sequence
expect(+myNumber).to.eq(64)
})
#Mihi has the idomatic way, but it's sometimes difficult when composing page object methods.
This was an incorrect answer, but I'm keeping it here for education purposes in case someone else stumbles upon the same issue.
You can NOT use await like this:
public async getValueFromElement(): string {
cy.log("Obtaining the Value");
let myNumber: string; // Here I'm declaring my variable
let $element = await cy.get(this.labelWithText);
let originalLabelText: string = $element.text();
let splittedText: string[];
splittedText = originalLabelText.split(": ");
myNumber = splittedText[1];
return myNumber
}
But be aware that now this function being async, itself returns a promise.
Why?
Here's what the docs say:
If you're a modern JS programmer you might hear "asynchronous" and think: why can't I just use async/await instead of learning some proprietary API?
Cypress's APIs are built very differently from what you're likely used to: but these design patterns are incredibly intentional. We'll go into more detail later in this guide.

Node.js: Why is my expected value not returned in 'catch' block of 'try-catch' clause?

I have a Node.js project which tests the functionality of a website. It utilizes Webdriver.io v4 and Mocha/Chai.
I've created a function that checks if an element exists on a page with a timeout of 1 minute. If the element exists, it should return true. If it does not, it should return false.
I use the same function to test if an element is NOT present on the page. In this case, I am expecting the function to return false. However, instead of returning false, the function throws a Timeout error and returns neither true or false. This is strange because I've included a return false statement in the catch block of my try-catch clause.
In this project, when a function fails, I will get a message such as expected false to equal true or expected undefined to equal true. In this case, I get the message Timeout of 60000ms exceeded. Try to reduce the run time or increase your timeout for test specs (http://webdriver.io/guide/testrunner/timeouts.html); if returning a Promise, ensure it resolves.
Yes, I am expecting the element.waitForExist() to throw a Timeout error, but this error should be handled in the catch block by returning false. The program does display the error log as expected by the console.log(ex) line, but does not return false.
Why is my function not returning false in this case? What would be the best/easiest way to return the correct value? Thank you!
This is my function:
checkElementExists: {
value: function (element) {
try {
element.waitForExist();
if (element.isExisting()) {
return true;
} else {
return false;
}
} catch (ex) {
console.log(ex);
return false;
}
}
}
Expected: If element exists on page, the function returns true. If the element does not exist on the page, the function returns false.
Actual: If element exists on page, the function returns true. If the element does not exist on the page, a Timeout error is thrown but neither true or false is returned.
If you still have the problem of value not being returned, please try the below approach. I am not sure why catch fails to return, but could you please try the below:
checkElementExists: {
value: function (element) {
let val = false;
try {
element.waitForExist();
if (element.isExisting()) {
val = true;
}
} catch (ex) {
console.log(ex);
}
return val;
}
}

Confusing flow error when dealing with multiple promises and callbacks

I'm adding flow to a project, and I've run into an error I can't figure out. Here's some code that produces the same error:
type user = {
id: string,
};
const thing = function(): Promise<user> {
return new Promise(resolve => {
var p1 = new Promise(innerResolve => {
getData(data => {
innerResolve(data);
});
});
Promise.all([p1]).then(result => {
resolve(result[0]);
});
});
};
const getData = function(callback) {
setTimeout(() => {
callback({});
}, 1000);
};
Link to this code on Try Flow, where you can read the error.
In my application, the getData function goes out to a database to get the object. And the type of what it returns in the callback can only be this specific:
{
[key: string]: string,
}
So it could be called with an empty object, like in the example. If you replace that line with:
callback({id: 'working'})
there are no type errors. But I can't do that in my application.
Another strange thing is that I can remove this whole block:
Promise.all([p1]).then(result => {
resolve(result[0]);
});
and that also makes it pass the flow test.
And the one thing I'd think would fix this issue -- refinements -- don't resolve the issue.
if (typeof data === 'object' && typeof data.id === 'string') {
innerResolve(data);
} else {
throw new Error('bad db response');
}
I haven't been able to figure out any way to get flow to accept this code, without changing something that I can't actually change (like replacing the database code with a simple return).
Can anyone explain the error I'm seeing, and why I'm triggering it?
you are explicitly setting the return type of thing function to Promise<user>, and that's why you get the error since your callback passes the parameter {} which is NOT a user
I don't know what your specific requirement is or why you are using a timeout but if the return type can be something that is NOT user, meaning any you can make the return type: you can set the return type to Promise<user|any>
You explicitly set the return type, so you have to return that return type.
indeed, an empty object is not of type user. Instead of returning an empty object when you have no match, is to return an empty array, and make your thing function return an array.
This changes the semantics: thing returns a promise of an empty array when there is no match, or an array with one element when there is.
const thing = function(): Promise<user[]> {
... and the callback call should pass an array with zero or one elements:
callback([]);
Your sample code could also be reduced to this, but I assume you have other code that made you write it like you did:
type user = {
id: string,
};
const thing = function(): Promise<user[]> {
return new Promise(getData);
};
const getData = function(callback) {
setTimeout(() => {
callback([]);
}, 1000);
};
On Try Flow

Test a rejection with Chai as promised

I want to test a function returning a promise.
In this particular test, the promise is expected to be rejected with an Error object containing the classical message field (in this test, it is expected to equal "my error message") and a custom field I added named code, which is a string (like "EACCESS", "ERIGHT", etc, in this test it is expected to equal "EFOO")
I want to use chai-as-promised for that.
return expect(foo()).to.eventually.be.rejectedWith("my error message");
This assertion is working but now I would like to test the code field too.
How to do that?
If you're using Chai-As-Promised (as you say you are), then it allows for chaining off of rejectedWith - and it sets the chain assertion object to be the error object - meaning anything after rejectedWith() is now going to assert on the Error. This lets you do cool things like:
return expect(foo()).to.eventually
.be.rejectedWith("my error message")
.and.be.an.instanceOf(Error)
.and.have.property('code', 'EFOO');
Some of the chai methods also chain, so you can use that to make some quite deeply nested assertions about the error:
return expect(foo()).to.eventually
.be.rejectedWith("my error message")
.and.have.property('stack')
.that.includes('myfile.js:30')
Having version 5.1.0 of ChaiAsPromised, solution from Keithamus did not work for me - rejectedWith did not gave me the error object to assert, but "rejected" did:
return expect(foo())
.to.be.rejected
.and.be.an.instanceOf(Error)
.and.have.property('code', 'EFOO');
For asserting multiple properties
return expect(foo())
.to.be.rejected
.then(function(error) {
expect(error).to.have.property('name', 'my error message');
expect(error).to.have.property('code', 'EFOO');
});
#Markko Paas's solution didn't work for me until I added 'eventually', or else rejected value is always {} empty object.
return expect(foo())
.to.eventually.be.rejected
.and.be.an.instanceOf(Error)
.and.have.property('code', 'EFOO');
You can perform complex tests on errors using rejected.then:
it('throws a complex error', function () {
return expect(foo()).to.eventually.be.rejected.then((error) => {
expect(error.code).to.equal('expected code');
// other tests
// alternatively,
expect (error).to.eql({
foo: 'foo',
bar: 'bar
});
});
});
In my case, since I was using chai-as-promised in an async function, all I had to do is add an await statement before expect(promise).to.be.rejectedWith(errorMessage), e.g:
it('should reject', async () => {
await expect(promise).to.be.rejectedWith(errorMessage);
// ^^^^^
});
Chai-As-Promised did not work for me, because it does not throw if you expect something to be rejected and it does not reject.
Then I used the following, which IMO is also quite expressive:
//...
await $radioButton.click();
const executed = await(async () => {
try {
await tools.waitUntil(() => {
return consoleMessages.length === 2;
}, 1000); // 1000 is the timeout in milliseconds. waitUntil() rejects if it does timeout.
return true;
} catch (error) {
return false;
}
})();
chai.assert.strictEqual(executed, false);

Jasmine - How to test errors?

THE SITUATION:
Hello guys. I am learning jasmine to test my angular app.
I have create a basic function that does multiply two numbers.
If the parameters given are not a number, the function throw an error.
I then made two very basic tests.
The first to check if the function properly multiply the numbers.
The second to check if the function properly throw an error if a string is given as parameter.
The first test pass, the second not. And i don't understand why.
THE CODE:
The function:
function Multiply( num1, num2 )
{
var result;
if (isNaN(num1) || isNaN(num2))
{
throw new Error("not a number");
}
else
{
result = num1 * num2;
return result;
}
}
The spec:
describe('The function', function ()
{
it('properly multiply two numbers', function ()
{
result = Multiply(10, 5);
expect(result).toEqual(50);
});
it('throw an error if a parameter is not a number', function ()
{
result = Multiply(10, 'aaaa');
expect(result).toThrow(new Error("not a number"));
});
});
THE OUTPUT:
2 specs, 1 failure
Spec List | Failures
The function throw an error if a parameter is not a number
Error: not a number
Error: not a number
at Multiply (http://localhost/jasmine_test/src/app.js:8:9)
If i understand properly Jasmine. Both test should pass, because in the second case the function throw the error as we expected.
THE QUESTION:
How can i test if a function properly throw an error?
EDIT:
I am trying this new code, but is still not working:
describe('The function', function ()
{
it('throw an error if a parameter is not a number', function ()
{
expect(function() { Multiply(10, 'aaaa') }).toThrowError(new Error("not a number"));
});
});
OUTPUT:
2 specs, 1 failure
Spec List | Failures
The function throw an error if a parameter is not a number
Error: Expected is not an Error, string, or RegExp.
If I understand correctly you need to pass a function into the expect(...) call.
The code you have here:
expect(result).toThrow(new Error("not a number"));
Is checking the result of Multiply, which when it works is fine, but like I said .toThrow() expects a function, I'd use an anonymous function instead, see below:
expect( function(){ Multiply(10, 'aaaa'); } ).toThrow(new Error("not a number"));
EDIT: Did a quick search and this blog post is a very detailed explanation of what I am trying to say.
You need to put the code you expect to throw an error into a function:
expect(function () {
Multiply(10, 'aaaa');
}).toThrow(Error, 'not a number');
Otherwise, when you run your assertions, the error has already been thrown outside the scope. You can see available syntax for error matching in jasmine docs
The above methods are exactly true for exceptions handled inside the methods.
When you've to test the services you can use mocking mechanism to do that.
Use the advantage of NgModule providers section to create the mocks.
In describe() block,
providers: [{ provide: APIService, useValue: { api: { filterTaskDetails: () => throwError('Error') }}}]
throwError to be imported from rxjs.
In expect() block test it as,
spyOn(component['toaster'], 'showError');
/** add respected it() blocks**/
expect(component['toaster'].showError).toHaveBeenCalledTimes(1);

Categories

Resources