Protractor, do I have to use then() after protractor calls? - promise

Is it ok to write:
getFieldX().clear().sendKeys('abc');
or should I write:
getFieldX().clear().then( () => sendKeys('abc));
I'm totally confused about the Promise handling in protractor. clear() returns a promise, so I should use .then() afterwards, shouldn't I?
But I found examples with .then and some without.
Protractor itself has an example without .then():
https://www.protractortest.org/#/control-flow
Does Protractor has its own mechanism and resolves one after the other promise so there is no need for using .then() after a protractor call that returns a Promise?
And is the control flow of protractor only active in a spec?
When using .sendKeys() in a normal function I have to use .sendKeys().then(...) ?

This all depends on if you are using SELENIUM_PROMISE_MANAGER or not. As this is (has?) becoming deprecated, I would not use it. It should be set to false by default, but if you want to be sure you can add SELENIUM_PROMISE_MANAGER = false; to your conf file. The way that protractor has been moving is to use async/await, so your sendKeys function would look like:
let field = element(by.css('CSS.Selector'));
await field.clear();
await field.sendKeys('abc');
Because these are async functions, you will need to define your function properly, so a basic spec would look like:
describe('Demonstrating async/await',function(){
it('Should send some keys', async function(){
let field = element(by.css('CSS.Selector'));
await field.clear();
await field.sendKeys('abc');
});
});
The important difference there is that a function needs to be defined as async function(). As far as reading the code goes, await simply can be read as "Wait until promise resolved to move on". It does get a bit tedious and you feel like you write await before every line of code (you basically do), but I find it significantly better than .then() trees.

Related

Cypress: How to capture text from a selector on one page to use as text on another page

New cypress user here, I am aware that cypress does not handle variables like how testcafe and others do due to the asyn nature of it. Using the example given and what I could find I have this as an example:
cy.get('selector').invoke('text').as('text_needed')
cy.get('#text_needed')
const txtneeded = this.text_needed
cy.log(txtneeded)
This looks at a given selector, takes what it finds and uses it as text and set it as a variable usable at any time in the test and outputs it to the log. The plan is to use that text in a search filter in another page to find the item it references.
The problem is that it fails with Cannot read properties of undefined (reading 'text_needed')
Is this because the content of the selector is not assigned to text properly, the outer html is <a data-v-78d50a00="" data-v-3d3629a7="" href="#">PO90944</a> The PO90944 is what I want to capture.
Your help would be appreciated!
You cannot save an alias and access it via this.* in the same execution context (callback) because it's a synchronous operation and your alias is not yet resolved at this time.
This is a correct way to go:
cy.get('selector').invoke('text').as('text_needed')
cy.get('#text_needed').then(txtneeded => {
cy.log(txtneeded)
})
First, make sure to define it as traditional function, not as an arrow function as this context doesn't work as you'd expect there, more info here.
Next, typically in a single test you should use .then() callback to perform additional actions on elements, and use aliases when you need to share context between hooks or different tests, so please consider the following:
// using aliases together with this within the single test won't work
cy.get(<selector>).invoke('text').as('text_needed')
cy.get('#text_needed').should('contain', 'PO90944') // works fine
cy.log(this.text_needed) // undefined
// this will work as expected
cy.get(<selector>).invoke('text').then(($el) => {
cy.wrap($el).should('contain', 'PO90944'); // works fine
cy.log($el) // works fine
});
Setting alias in beforeEach hook for example, would let you access this.text_needed in your tests without problems.
Everything nicely explained here.
Edit based on comments:
it('Some test', function() {
cy.visit('www.example.com');
cy.get('h1').invoke('text').as('someVar');
});
it('Some other test', function() {
cy.visit('www.example.com');
cy.log('I expect "Example Domain" here: ' + this.someVar);
});
And here's the output from cypress runner:

Accessing aliases in Cypress with "this"

I'm trying to share values between my before and beforeEach hooks using aliases. It currently works if my value is a string but when the value is an object, the alias is only defined in the first test, every test after that this.user is undefined in my beforeEach hook. How can I share a value which is an object between tests?
This is my code:
before(function() {
const email = `test+${uuidv4()}#example.com`;
cy
.register(email)
.its("body.data.user")
.as("user");
});
beforeEach(function() {
console.log("this.user", this.user); // This is undefined in every test except the first
});
The alias is undefined in every test except the first because aliases are cleared down after each test.
Aliased variables are accessed via cy.get('#user') syntax. Some commands are inherently asynchronous, so using a wrapper to access the variable ensures it is resolved before being used.
See documentation Variables and Aliases and get.
There does not seem to be a way to explicitly preserve an alias, as the is with cookies
Cypress.Cookies.preserveOnce(names...)
but this recipe for preserving fixtures shows a way to preserve global variables by reinstating them in a beforeEach()
let city
let country
before(() => {
// load fixtures just once, need to store in
// closure variables because Mocha context is cleared
// before each test
cy.fixture('city').then((c) => {
city = c
})
cy.fixture('country').then((c) => {
country = c
})
})
beforeEach(() => {
// we can put data back into the empty Mocha context before each test
// by the time this callback executes, "before" hook has finished
cy.wrap(city).as('city')
cy.wrap(country).as('country')
})
If you want to access a global user value, you might try something like
let user;
before(function() {
const email = `test+${uuidv4()}#example.com`;
cy
.register(email)
.its("body.data.user")
.then(result => user = result);
});
beforeEach(function() {
console.log("global user", user);
cy.wrap(user).as('user'); // set as alias
});
it('first', () => {
cy.get('#user').then(val => {
console.log('first', val) // user alias is valid
})
})
it('second', () => {
cy.get('#user').then(val => {
console.log('second', val) // user alias is valid
})
})
Replace
console.log("global user", this.user);
with
cy.log(this.user);
and it should work as expected.
The reason for this is the asynchronous nature of cypress commands. Think of it as a two-step process: All the cypress commands are not doing what you think, when they run. They just build up a chain of commands. This chain is executed as the test later on.
This is obviously not the case for other commands like console.log(). This command is executed when preparing the test.
This is explained in great detail in the cypress documentation:
But I felt it very hard to get my head around this. You have to get used to it.
One rule of thumb: Almost every command in your test should be a cypress command.
So just use cy.log instead of console.log
If you must use console.log you can do it like this:
cy.visit("/).then(() => console.log(this.user))
this way the console.log is chained. Or if you do not have a subject to chain off, build your own custom command like this:
Cypress.Commands.add("console", (message) => console.log(message))
cy.console(this.user)
Another mistake with using this in cypress is using arrow functions. If you do, you don't have access to the this you are expecting. See Avoiding the use of this in the cypress docs.
TL;DR: If you want an aliased user object available in each of your tests, you must define it in a beforeEach hook not a before hook.
Cypress performs a lot of cleanup between tests and this includes clearing all aliases. According to the Sharing Contexts section of Variables and Aliases: "Aliases and properties are automatically cleaned up after each test." The result you are seeing (your alias is cleaned after the first test and subsequently undefined) is thus expected behavior.
I cannot determine what register does in the original post, but it seems your intention is to save the overhead of performing API calls repeatedly in a beforeEach hook. It is definitely easiest to put everything you want in the beforeEach hook and ignore the overhead (also, pure API calls with no UI interaction will not incur much penalty).
If you really need to avoid repetition, this should not be accomplished through regular variables due to potential timing problems with Cypress' custom chainables. This is an anti-pattern they publish. The best way to do this would be:
Create a fixture file with static user data that you will use to conduct the test. (Remove the uuidv4.)
For the set of tests that need your user data, call register in a before hook using the fixture data. This will create the data in the system under test.
Use a beforeEach hook to load the fixture data and alias it for each of your tests. Now, the static data you need is accessible with no API calls and it is guaranteed to be in the system properly thanks to the before hook.
Run your tests using the alias.
Clean up the data in an after hook (since your user no longer has a random email, you need to add this step).
If you need to do the above for the whole test suite, put your before and after hooks in the support file to make them global.

How can you mix Parse.Promise with other forms of Promise, e.g. Bluebird / ES6 native Promises?

I want to run some of my Parse Cloud Code on Heroku (Node) to kick start the migration process out of Parse's hosted Cloud Code.
One of the differences between the two environments is the fact that Parse SDK you use is different too. On node, I found that if you do a
var Parse require('parse/node').Parse;
... then Parse.Cloud.httpRequest is actually missing.
I was looking for Node alternatives for performing http requests and found request. But... it doesn't really fit in well with Parse Promises:
parsePromise1()
.then(parsePromise2) // okay
.then(requestPromise) // CUSTOM HTTP REQUEST!
.done(parsePromise3) // this Parse promise always succeeds, and occurs even before the request is fully completed.
I was wondering if anybody had found better ways of converting out of Parse Promises? Thanks.
You can use the request-promise package that wraps request with promises support or a library like bluebird that exposes a method that converts callback APIs to promises.
I recommend that you migrate from Parse promises which are suboptimal to ES2015 native promises (or, more powerful alternatives like bluebird). With those you could:
Promise.resolve(someParsePromise).then(() => // convert
return someParsePromiseFn()// return some Parse promise, that works
}).then(res => {
// more async work ...
});
And avoid .done altogether.
Its not ideal at all, but you can use this kind of wrapper from native Bluebird to native promises:
const Promise = require('bluebird')
function handle () {
return new Promise((resolve, reject) => {
handleWithNativePromise(...arguments)
.then(resolve)
.catch(reject)
})
}

Jasmine 2.0 rc* waits is not defined

Just upgraded to jasmine 2.0 rc5 from 1.3 and now all my tests that used waits() are broken because the waits() and waitsFor() function are undefined. I can't seem to find any reference to that anywhere online, is anyone aware of what is the new API to replace wait() ?
Well, the usage syntax for asynchronous calls changed.
You can easily see the differences between the two versions in its documentations:
Jasmine 1.3 Asynchronous support uses waitsFor() and run() functions.
According to Jasmine 2.0 Asynchronous support, these functions has been wiped out from the library. However, Jasmine 2.0 adds async support to the primitive beforeEach(), afterEach() and it() functions. The callback functions passed to these functions now can take an argument that indicates if the spec can or can't run.
Then, when you reach the necessary conditions to run your test (whenever your async job is complete), you simply call done(). And all the magic happens ;)
From the documentation:
describe("Asynchronous specs", function() {
var value;
beforeEach(function(done) {
setTimeout(function() {
value = 0;
done();
}, 1);
});
it("should support async execution of test preparation and expectations", function(done) {
value++;
expect(value).toBeGreaterThan(0);
done();
});
});
The it() spec above will run only after the setTimeout() call, because done() is called there. Note the it() callback takes an argument (done).
Use jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000
Please refer the below URL for Jasmine Documentation...
http://jasmine.github.io/2.0/introduction.html
Section is "Asynchronous Support" in the documentation.

How can I pass a local variable from function to event listener function in JavaScript?

Good day!
I began writing my own basic JavaScript library for personal use and distribution a few days ago, but I am having trouble with one of the methods, specifically bind().
Within the method itself, this refers to the library, the object.
I went to Google and found function.call(), but it didn't work out the way I planned it--it just executed the function.
If you take a look at another method, each(), you'll see that it uses call() to pass values.
I also tried the following:
f.arguments[0]=this;
My console throws an error, saying it cannot read '0' of "undefined".
I would like to be able to pass this (referencing the library--NOT THE WINDOW) to use it in the event listener.
You can see it starting at line 195 of the JavaScript of this JSFiddle.
Here it is as well:
bind:function(e,f){
if(e.indexOf("on")==0){
e=e.replace("on","");
}
if(typeof f==='function'){
/*Right now, 'this' refers to the library
How can I pass the library to the upcoming eventListener?
*/
//f=f(this); doesn't work
//f.call(this); //doesn't work
//this.target refers to the HTMLElement Object itself, which we are adding the eventListener to
//the outcome I'm looking for is something like this:
/*$('h3').which(0).bind(function({
this.css("color:red");
});*/
//(which() defines which H3 element we're dealing with
//bind is to add an event listener
this.target.addEventListener(e,f,false)
}
return this;
},
Thank you so much for your help, contributors!
If, as per your comments, you don't want to use .bind(), rather than directly passing f to addEventListener() you could pass another function that in turn calls f with .call() or .apply():
if(typeof f==='function'){
var _this = this;
this.target.addEventListener(e,function(event){
f.call(_this, event);
},false)
}
Doing it this way also lets your library do any extra event admin, e.g., pre-processing on the event object to normalise properties that are different for different browsers.
So in this particular case you actually want to call JavaScript's built in bind method that all functions have.
f = f.bind(this);
f will be a new function with it's this argument set to whatever you passed into it.
Replace f=f(this); with f.apply(this);
Look at underscore code, here:
https://github.com/jashkenas/underscore/blob/master/underscore.js#L596

Resources