Testing promises in protractor - javascript

I am new to the angular/protractor world so this is a really basic question. According to this spec, we can write protractor test for a web element like below:
var foo = element(by.id('foo'));
expect(foo.getText()).toEqual('Inner text');
However, foo.getText() returns a promise type, not a string, how can 'expect' compare that returned promise object against another string? Is there any documentation that explains this usage?

Yes, the expect(), if used with Protractor, understands promises - it would resolve a promise before making an expectation making writing Protractor tests easier. This is actually done in a separate project which Protractor depends on - jasminewd2 which patches jasmine's expect() to resolve the promises and wraps jasmine's describe(), it() and other test control block functions to be executed inside the Control Flow.
Note that, it also supports promises on both sides of the assertion, you can do, for instance:
let elementText1 = $('.ng-scope p').getText();
let elementText2 = $('#transformedtext>h4').getText();
expect(elementText1).toEqual(elementText2);
As far as Protractor documentation goes, this is partially described here.

Related

Static/dynamic way to detect dangling promises

I know this problem has been dealt with so many times, but none of them seem to solve the issue of reliably detecting dangling promises (even those that resolve properly).
So I want to be able to figure out a way (whether at runtime or better at static time) to root out "dangling promises", especially of this class:
async function a() {
... do some async operation ...
}
async function b() {
a(); // forgot to call await on it
}
where I have accidentally forget to await on one function and some task that executes asynchronously doesn't get awaited on. Often times these types of bugs don't throw exceptions so I can't just use "unhandledRejection" and call it a day.
At this point after lots of desperate attempts I just need a way to detect this kinda faulty pattern at either (optimally) static/compile/lint time or at runtime. Runtime for me I think should work assuming I have good test coverage.
tl;dr basically, I'm searching for some code that would do something like the following:
Error: Potential dangling promise detected!
...
at b (/dangling.js:5:3)
For each dangling promise
My thought process
I first tried finding some static analysis library that would help to detect these things (theoretically it should be possible but I had no luck with finding such a thing). A while back I remember finding something in the depths of stackoverflow that talked about using some typescript checker to check for dangling promises, though now I can't find it anymore :( . Though at the time changing the entire codebase to typescript was a no-go. (Later I learned you can actually use tsc to typecheck javascript (given that you do some type annotation in comments))
Currently I was previously using node version 11, so I thought about using the node.js's async_hooks API and try to listen on events (apparently simply monkey patching the Promise constructor wouldn't work because node.js bypasses the Promise constructor when creating a Promise object to return from an async function). Using node v11, after a bit of code hackery here it seemed to work (though it's not very efficient cause it throws away a lot of promise optimization in the v8 engine, but it does the job). There was a small hiccup in this entire operation in that I had to still monkey patch the then/catch/finally functions of the Promise API to check if we are currently calling that function (somehow that worked with detecting some dangling promises).
Now enter node v12 (apparently I need this for certain other things that are breaking), and now that hackery (unsurprisingly) completely breaks. After scrutinizing the version diffs, seems like they optimized the await/async implementation. After narrowing down the reason, it seems like await no longer calls the then function of the promise and just directly does some native shenanigans (idk what really).
Now I'm actually kinda desperate for some solution (and maybe if Typescript has some way of type-checking these dangling promises, what was the option to enable this check? I know (tested) that tsc doesn't do this by default).
You are most likely looking for the no-floating-promises rule of typescript-eslint

When do I have to wait for a promise in Protractor?

I know there is similar questions on here about this, but I cannot make sense of them for the life of me.
Here's an example, where I need to click a button and check the url.
My initial thought is I would write it as
element(by.id('button')).click();
expect(browser.getCurrentUrl()).toContain('asyncisconfusing');
I know the expect handles its promise but what about the .click? Shouldn't I have to write it like this?
element(by.id('button')).click().then(() => {
expect(browser.getCurrentUrl()).toContain('asyncisconfusing')
})
Or is protractor/webdriver auto-magically doing this?
In theory, since Protractor maintains a queue of promises via Control Flow and works in sync with an AngularJS application under test, you should not resolve promises explicitly unless you need a real value for further processing. In other words, this should be the prefferred form:
element(by.id('button')).click();
expect(browser.getCurrentUrl()).toContain('asyncisconfusing');
In practice though, explicitly resolving click() promises, or adding explicit waits via browser.wait() helps to deal with occasional and random timing issues.
http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/promise.html
The first section talks about how the control flow is used to manage promises without having to chain together every single command.

Resolving promises in Protractor and Cucumber using Chai as Promised

Lately a colleague and I have had some disagreements on the "right" way to implement Cucumber step definitions using Protractor and Chai as Promised. Our contention comes from a mutual lack of understanding of precisely what is going with promise resolution in the context of Cucumber.
We're testing against an AngularJS application, so resolving promises and asynchronous behavior is a necessary evil. The biggest problem we've had is forcing synchronous test behavior and getting Cucumber to wait on promises between step definitions. In some cases, we've observed situations such that Cucumber seems to plow straight through step definitions before Webdriver even executes them. Our solutions to this problem vary...
Consider the hypothetical scenario:
Scenario: When a user logs in, they should see search form
Given a user exists in the system
When the user logs into the application
Then the search form should be displayed
Most of the confusion originates in the Then step. In this example the definition should assert that all fields for the search form exist on the page, meaning multiple isPresent() checks.
From the documentation and examples I was able to find, I felt the assertion should look something like this:
this.Then(/the search form should be displayed/, function(next) {
expect(element(by.model('searchTerms')).isPresent()).to.eventually.be.true;
expect(element(by.model('optionsBox')).isPresent()).to.eventually.be.true;
expect(element(by.button('Run Search')).isPresent()).to.eventually.be.true.and.notify(next);
});
However, my colleague contends that in order to satisfy promise resolution, you need to chain your expects with then() like this:
this.Then(/the search form should be displayed/, function(next) {
element(by.model('searchTerms')).isPresent().then(function(result) {
expect(result).to.be.true;
}).then(function() {
element(by.model('optionsBox')).isPresent().then(function(result) {
expect(result).to.be.true;
}).then(function() {
element(by.button('Run Search')).isPresent().then(function(result) {
expect(result).to.be.true;
next;
});
});
});
});
The latter feels really wrong to me, but I don't really know if the former is correct either. The way I understand eventually() is that it works similarly to then(), in that it waits for the promise to resolve before moving on. I would expect the former example to wait on each expect() call in order, and then call next() through notify() in the final expect() to signal to cucumber to move on to the next step.
To add even more confusion, I've observed other colleagues write their expects like this:
expect(some_element).to.eventually.be.true.then(function() {
expect(some_other_element).to.eventually.be.true.then(function() {
expect(third_element).to.eventually.be.true.then(function() {
next();
});
});
});
So the questions I think I'm alluding to are:
Is any of the above even kinda right?
What does eventually() really do? Does it force synchronous behavior like then()?
What does and.notify(next) really do? Is it different from calling next() inside of a then()?
Is there a best practices guide out there that we haven't found yet that gives more clarity on any of this?
Many thanks in advance.
Your feeling was correct, your colleague was wrong (though it was a reasonable mistake!). Protractor automatically waits for one WebDriver command to resolve before running a second. So in your second code block, element(by.button('Run Search')).isPresent() will not resolve until both element(by.model('optionsBox')).isPresent() and element(by.model('searchTerms')).isPresent() are done.
eventually resolves promises. An explanation is here: https://stackoverflow.com/a/30790425/1432449
I do not believe it's different from putting next() inside of then()
I do not believe there is a best practices guide. Cucumber is not a core focus of the Protractor team, and support for it is largely provided by the community on github. If you or someone you know would like to write a best practices guide, we (the protractor team) would welcome a PR!
What works for me is this - The function below searches for something that will always equal true - in the case the existence of a html tag. I call this function at the end of each test, passing in the callback
function callbackWhenDone(callback) {
browser.wait(EC.presenceOf(element(by.css('html'))))
.then(function () {callback();})
}
This is the usage in a simple test:
this.Given(/^I go on "([^"]*)"$/, function (arg1, callback) {
browser.get('/' + arg1);
callbackWhenDone(callback);
});
A bit of a hack I know but it gets the job done and looks pretty clean when used everywhere

Protractor and promises

A lot of the functions exposed by Protractor return promises.
Do I need to structure my Jasmine tests using Protractor using things like async tests (with the done parameter), and .then, or does Protractor provide some magic to do this for me?
WebDriverJS takes care of that with the control-flow. Protractor adds the modification of Jasmine's expect to keep the thens at bay. It's best explained here.
Yes, there is some magic that protractor performs in order to wait for each promise to resolve.
The best description of the process is in the protractor documentation: How It Works.
This means we don't have to structure the tests as async using a done. We can simply assert using expect (in Jasmine) and everything should work.

Why does Protractor sometimes returns a promise and sometimes returns a value?

In the official documentation you can find the following code:
var history = element.all(by.repeater('result in memory'));
expect(history.count()).toEqual(2);
But you can also find examples using promises
element.all(by.repeater('app in userApps')).count().then(function(count) {
console.log(count);
});
So why does Protractor sometimes returns a promise and sometimes it returns a value?
history.count() does return a promise, but Protractor adapts Jasmine expect to understand promises.
https://github.com/angular/protractor/blob/master/docs/control-flow.md
It always returns a promise, it's just that expect is patched to handle them by adding them to the control-flow so that they get executed and resolved in the proper order.
the protractor folks "patched" jasmine to be promise aware. that is,
the expect statement does duck-typing -- if it is a promise, it waits
for it to resolve and executes the underlying assertion. if it is any
other type, it executes the assertion as it would in any other jasmine
world.
source

Categories