protractor e2e testing Returning Promises from utility steps - angularjs

I'm fairly new to protractor and promises in general. I've had a look around, and although there's many posts out there about returning promises, or the results from queued actions none of them make much sense to me, so i'm after a fairly easily described answer to what I hope is a simple question!
I am trying to write some protractor tests for my angularjs website.
I am using bootstrap and angular mainly, no other third party libraries, other than the occasional angular add-on such as toaster, and bootstrap modal.
I have several 'arrangement steps' before I get to the assertion part of my test. Let's say :
a) Person logs in
b) Person accesses options form ( which may or may
not be displayed already on the screen depending on some external
factors, but if it's not present they can open it with a button press
).
c) Person performs an action on the options form.
d) assert that the text box on the form now contains the correct value.
I can get my test to pass quite easily when the form is already on the screen, but the bit that's getting me stuck is step b) where I need to check first if the form is active and click a button if it's not pefore proceeding to step c.
I've tried to return the promise from isDisplayed like so :
//
// Is the user settings form active at the mo?
//
function _isUserSettingsFormActive()
{
var result = element(by.id(logoutFormID)).isDisplayed;
return result;
}
But if I call .then on _isUserSettingsFormActive() I get the following error :
[31mTypeError: undefined is not a function[0m
However if I output the results of _isUserSettinsFormActive() I see the below, so I know it's returning something :
function () {
return self.elementArrayFinder_[fnName].
apply(self.elementArrayFinder_, arguments).toElementFinder_();
}
All I want to do is check if an item exists and act on that before performing my assert.
It needs to be in a function, as this code will be used in many places throughout my test suit. It's not the 'expect' itself, more a step that may or may not need an action to set up the browser for my test to pass.

isDisplayed is a function, so it should be called like that:
function _isUserSettingsFormActive()
{
var result = element(by.id(logoutFormID)).isDisplayed();
return result;
}

Protractor does not work like the Java or C# bindings of Selenium would (it's funner but more work to achieve what would be simple actions in Java or C#). It would be safer to return a count promise if the options form is also not in the DOM but if it is in the DOM and just hidden you could use isDisplayed(). I wrote two examples below for both situation including clicking the button depending on the condition.
Option 1 (Not present in DOM and not displayed):
function _isUserSettingsFormActive() {
//$$('#logoutFormId') is the equivalent of element.all(by.id('logoutFormId'))
$$('#logoutFormId').count().then(function(num){
if(num < 1) {
element(by.id('openLogoutButton').click();
}
});
};
OR
Option 2 (Present in DOM but not displayed):
function _isUserSettingsFormActive() {
//$('#logoutFormId') is the equivalent of element(by.id('logoutFormId'))
$('#logoutFormId').isDisplayed().then(function(visible){
if(!visible) {
element(by.id('openLogoutButton').click();
}
});
};

Related

Non-angular page opened with click - angular not defined using ignoreSynchronization or waiting for Angular without

After a lot of research, and tinkering, I can't seem to actually get my Protractor test to do anything else other than have an Angular related error, even though I am using browser to avoid Angular being detected at all.
The test involves an Angular app, opening a dropdown in it, and clicking on the link for the console; the console opens a non-Angular admin page in a separate window.
So based on the many informative SO posts I found, I first used this...
browser.driver.getAllWindowHandles().then(function(handles) {
browser.driver.switchTo().window(handles[1]).then(function() {
//expect for new window here
});
});
Which appeared to work, as I could get to the window through repl pretty easily.
The issue is when either of the following were added...
browser.driver.getAllWindowHandles().then(function(handles) {
browser.driver.switchTo().window(handles[1]).then(function() {
expect(browser.getLocationAbsUrl()).toContain('/console/login.jsp');
expect(browser.driver.findElement(By.css('th.login')).getText()).toEqual('Login');
});
});
One expect check the URL and the other checks for the header element on the page, which is a table header. When I run this, I get the following:
Error while waiting for Protractor to sync with the page: "angular could not be found on the window"
When I decide to use browser.ignoreSynchronization = true, both in the function, or in a beforeEach, with or without a following afterEach setting it to false, I get the following:
JavascriptError: angular is not defined
I can't seem to get any "useful" errors to help me debug it, and trying it in repl does not help, as I get the same issue.
To be comprehensive, trying my URL expect without getting the second window will give me the root, and the other will fail.
Just doing one or the other will cause the same problem.
Changing to regular syntax (element(by.css...)) does not change things.
So much for my first question...
It appears that my use of browser.getLocationAbsUrl() is meant to be used for an Angular page, and was causing my issue...
Essentially, even though I believed I was using pure Webdriver calls, that call still required Angular on the page to work...
As stated in another post, the use of browser.driver.getCurrentUrl() is a non-Angular call using Webdriver, and fixed the problem. Thus, the final code is the following...
browser.sleep(1000); //to wait for the page to load
browser.driver.getAllWindowHandles().then(function(handles) {
browser.driver.switchTo().window(handles[1]).then(function() {
expect(browser.driver.getCurrentUrl()).toContain('/console/login.jsp');
expect(browser.driver.findElement(By.css('th.login')).getText()).toEqual('Login');
});
});
This works without setting ignoreSynchronization, BTW.
I realized it would probably be something relatively simple to fix it, just didn't expect I'd get it that quickly (I intended on submitting the question last night, but posted it this morning instead).
In any case, I hope this will at least be a good reference for anyone else facing the same issue.
Seems like getLocationAbsUrl is angular abs url.
Try using the native driver getCurrentUrl instead.
-- expect(browser.getLocationAbsUrl()).toContain('/console/login.jsp');
++ expect(browser.driver.getCurrentUrl() ...

Call translation service from a callback registered in an app.config section

I'm relatively new to AngularJS and the problem I'm facing is one of those "I want to inject a Service into an app.config" type of scenarios, which I realise cannot be done. (I'm comfortable with the different between Service and Provider, and why a Service cannot be injected into a .config.)
What I am trying to accomplish is to use angular-schema-form together with angular-translate such that field titles in generated forms are translated.
There is an issue where someone asks how to do this, and the advice given is to take advantage of angular-schema-form's postProcess, which is a property of the Provider. This callback gives you the form object before it is rendered, giving you the opportunity to manipulate it with user code. Therefore translation could be done within here.
The postProcess method is called on the Provider, so it is done within an app.config:
app.config(function(schemaFormProvider, $translateProvider) {
schemaFormProvider.postProcess(function(form){
// within here I can inspect the form object, find all
// properties whose key is "title", and then perform
// language translation on their values.
So, that is apparently the place where I have an opportunity to manipulate control titles and so on.
Over to the angular-translate library, for me to 'manually' translate strings, I can use the $translate service. This provides both synchronous and asynchronous methods to translate a given key string. The synchronous one is $translate.instant(key).
To glue these two together, what I have tried so far (which does work) is to create a 'bridge' method like this:
var app = angular.module('myApplicationName', ['schemaForm', 'pascalprecht.translate']);
....
app.config(function(schemaFormProvider, $translateProvider) {
schemaFormProvider.postProcess(function(form){
// ... code here which iterates over properties
// and finds all control titles ...
key = app.myTranslate(key);
// ....
}
....
});
app.myTranslate = function (key) {
var service = angular.injector(['ng', 'myApplicationName']).get("$translate");
return service.instant(key);
}
This does work, but it seems ugly and unsafe (as presumably there's no guarantee $translate is ready when the callback is first invoked) and the calls to angular.injector(['ng', 'myApplicationName']).get... are presumably expensive.
Is there a better way, or is this the only way I'm going to get it done, considering the constraints of the libraries I'm working with?
I have also considered an alternative approach altogether, which would be to instead perform the translations on the schema or form objects before they are processed by angular-schema-form. This could be done from within Controllers, eliminating the problem of accessing the $translate service. I may end up going down that route, but it would still be nice to understand the best solution for the above scenario.

Testing redirection using regex.test() instead of Jasmine expect

I'm testing the redirection after the sign-up of an AngularJs app.
After clicking the registration button i call a function to check if the url matches with targetRegex.
The first code block use Jasmine expect but the i get the error: timeout: timed out after 30000 msec waiting for spec to complete
return browser.wait(function() {
return browser.driver.getCurrentUrl().then(function(url) {
return expect(url).toContain(targetRegex);
});
});
Meanwhile the following code seems working well:
return browser.wait(function() {
return browser.driver.getCurrentUrl().then(function(url) {
return targetRegex.test(url); // look for a match of the regex /profile/ in the 'url'
});
});
Is anybody able to explain me why please?
From the Jasmine docs: The 'toContain' matcher is for finding an item in an Array.
For matching via a regular expression, I think you want to use toMatch instead, as in expect(url).toMatch(targetRegex);.
However, I'm not sure if you want to use that toMatch method after return. It doesn't return a simple boolean result. If you look at the source code for that method, you'll see that it returns an object with a compare method. Judging from the source code for other methods in the matchers subdirectory, apparently that's a standard pattern used internally by the Jasmine library.
So, bottom line, you might be better off sticking with the return targetRegex.test(url); approach instead, since that returns a boolean.

AngularJS: Accessing $scope objects in e2e tests

I am building a Math tutoring application and would like to test my UI using angular's e2e testing suite.
Currently I am working on a Fraction page that generates a random fraction, displays a series of shaded and unshaded boxes and asks the user to input the fraction formed by the shading.
Using an e2e test, I would like to test how the UI responds to both correct and incorrect input; however, since the fraction is randomized on page load, I do not know what 'correct' input is from inside the test.
The easiest way for me to get the correct answers to input would be to gain access to the Fraction object, located at $scope.problemObject for the controller, and call its API functions .getNumerator() and .getDenominator(). However, I have not found a way to get to this object from within my tests.
Relevant lines from my controller are:
$scope.problemObject = Fraction.random();
// This produces an object with two relevant
// functions (getNumerator() & getDenominator())
What I Have Tried
binding()
Initially I thought binding() would do what I needed, however all calls to binding('problemObject') or binding('problemObject.getNumerator()' and the like issue an error saying that the binding cannot be found. I suspect that this is because $scope.problemObject and the return value of $scope.problemObject.getNumerator() are not directly bound to the UI.
angular.element().scope()
Executing angular.element('#problem').scope().problemObject from the console on the page that I am testing works perfectly; however, trying the same line from within my test issues the following error: 'selectors not implemented'.
I have also tried a few variations:
element('#problem').scope().problemObject: Error: 'Object # has no method 'scope''
angular.element(element('#problem')).scope().problemObject: Error: 'Cannot read property 'problemObject' of undefined'
I guess 'element' in e2e test and 'angular.element' are different objects.
You may want to try reading the value from the view.
if it is input field.
var value = element('#problem').val();
Otherwise, something like:
var value = element('#problem').text();
(Looking into scope object from e2e is kind of cheating in my opinion.)
Edit
I totally misunderstood the question and construct of the web page.
Sorry for the confusion.
What it has to validate is the input fields against numbers of the shaded and non-shaded boxes ('td' elems in this example).
var total = element('td').count()
, fraction = element('td.shaded').count();
Idea is same, it is trying to get the numbers from the view, not from $scope.
Turns out the problem lies in the scope being stored in jQuery's data. Since jQuery stores the data in a hashtable as $.cache global, once we are outside of the frame that the test webpage is running in, we no longer have access to them. The way I solved it is by accessing the jQuery inside the iframe's window (conveniently given in the $window parameter).
Below is what I come up with to access the scope. You can do scope('#myElement', 'foo.bar') to query $scope.foo.bar.
angular.scenario.dsl('scope', function() {
return function(selector, entry) {
return this.addFutureAction('find scope variable for \'' + selector + '\'',
function($window, $document, done) {
var $ = $window.$; // jQuery inside the iframe
var elem = $(selector);
if (!elem.length) {
return done('No element matched \'' + selector + '\'.');
}
var entries = entry.split('.');
var prop = elem.scope();
for (var i in entries) {
prop = prop[entries[i]];
}
done(null, prop);
});
};
});

GetProperty problem

I have an ASP.net website and inside its .aspx page there is a javascript function
and from my silverlight project , i want to get a value of property in the javascript funcion
i used "eval" to evaluate the function and GetProperty to return the value i want
the problem is GetProperty work only if i call the function for the second time
but never return in the first call
javascript code:
function RETURNIMAGE() {
var x = { value: document.getElementById("ImageContainer").value };
return x; }
c# code:
string getImage = "document.getElementById('myIFrame').contentWindow.RETURNIMAGE ();";
ScriptObject imgObject = HtmlPage.Window.Eval(getImage) as ScriptObject;
var img = imgObject.GetProperty("value");
any help please?
Since it works the second time I strongly suspect that the first time the IFrame is still loading its contents. Both Silverlight and the Browser will be getting on with their various activities asynchronously from each other.
Here is something that might help to halt the code until the page loads:
A hidden object/property can be put in the frame, but make sure it is after the property to be fetched.
Then a 'while' loop can be inserted in the c# code to check that this hidden property exists (the loop breaks only when the property value is loaded correctly), then put the rest of your code after the while loop.
This solution may not be optimum, but may be used to check if it is a loading problem or not.

Resources