I'm starting out on protractor testing of my sails.js / AngularJS app. Simplified, I have a following kind of ng-repeat in my HTML:
<div ng-repeat="book in books">
book.name
</div>
My test clicks a button that sends a $http POST call to the server, creating another book, and upon success, adds another book to $scope.books. The problem is that the test fails since the book hasn't been created yet when the test checks for its existence. I know my locator/filter works since when running the test again (= the book exists before sending the call) the test succeeds.
Test:
element.all(by.buttonText('Save')).
filter(function(elem) {return elem.isDisplayed();}).
first().
click();
browser.sleep(500); // even this doesn't work :(
element.all(by.repeater('book in books')).filter(function(elem, index) {
return elem.getText().then(function(text) {
return text === "nameOfBook";
});
})
.then(function(books) {
expect(books[0].isPresent()).toEqual(true);
});
So far the answers I've run into seem to suggest that protractor should automatically wait for the $http call to be finished before continuing, but in my case it doesn't seem to be so.
I even tried sleep times of 4000ms or so in which I can already see the new element in the repeater, but the test still sees the books[0] as undefined. The works when running the test again so the problem should not be in the filter but somewhere else.
Any insights on the matter?
You can explicitly wait for the new book name to be present in the DOM:
var EC = protractor.ExpectedConditions;
var elm = element(by.xpath("//div[. = 'nameOfBook']"));
browser.wait(EC.presenceOf(elm), 10000);
You can also wait for the count of elements in a repeater to increase:
var countOfElementsToBe = function(elms, expectedValue) {
return elms.count().then(function (actualValue) {
return expectedValue === actualValue;
});
};
var books = element.all(by.repeater('book in books'));
books.count().then(function (booksCount) {
browser.wait(countOfElementsToBe(books, booksCount + 1), 10000);
});
(not tested)
Related
This is my protractor test:
it("should check email validity", function(){
var resetButton = element(by.id('reset-button'));
element(by.model('Contact.email')).sendKeys('nick');
element.all(by.css('.form-control-error')).each(function (elem, index) {
if (index===1) {
expect(elem.isPresent()).toBe(true);
element(by.model('Contact.email')).sendKeys('#gmail.com').then(
function(){
expect(elem.isPresent()).toBe(false);
}
)
}
});
});
Behind that code there is a form with some input texts. The second one includes the email.form-control-erroris an error message which appears whenever the email format is not correct. The first time expect(elem.isPresent()).toBe(true);passes the test, the second time it does not, even if the error message disappears from the UI. It seems that Protractor does not perceive the fast change; however, it should because it is inside a promise. Do you have any explanation for that?
You should make things more reliable by adding a wait for the element to become not present ("stale") after sending the keys:
element(by.model('Contact.email')).sendKeys('#gmail.com');
var EC = protractor.ExpectedConditions;
browser.wait(EC.stalenessOf(elem), 5000);
expect(elem.isPresent()).toBe(false);
I am using React for render and Jest/Jasmine for test. I have test written using old Jest/Jasmine waitsFor and runs but these are gone now in Jasmine 2 and I am not sure how to replace with new done asyncs.
In my code React renders a small page about a user. That page has an AJAX call to fetch user posts. I want to test that user posts have come back nice, and waitsFor was very, very good at this: wait until user has some post, then continue.
I looked online at lots of people talking about using AJAX calls inside Jest test which is not what I want. My Jest test has no idea about AJAX call being made, so I need a way to wait until results come back.
Here is my current code with waitsFor and runs:
it('loads user post', () => {
var page = TestUtils.renderIntoDocument(
<UserPage params={{user: 'fizzbuzz', 'pass': 'xxx'}} />
);
waitsFor(() => {
return page.state.posts.length > 0;
}, "post loaded", 10000);
runs(() => {
var posts = TestUtils.scryRenderedDOMComponentsWithClass(page, 'post');
expect(posts.length).toEqual(10);
});
});
How can I delete the waitsFor and runs and replace with Jasmine 2.0 code that works? All Jest test knows is that page.state.posts.length must be greater than 0 before expecting anything.
You should refactor this test into two unit tests that will provide a more rigorous testing of your code. It would make the tests more independent of one another and help identify errors in a more refined scope. These won't be exact as I do not know what your code is like, but here's something along the lines I would expect to see: -
it('generates the expected properties for a page', function () {
var page = TestUtils.renderIntoDocument(
<UserPage params={{user: 'fizzbuzz', 'pass': 'xxx'}} />
);
expect(page.someProperty).toBeDefined();
expect(page.user).toEqual('fizzbuzz');
});
it('generates the correct number of posts from a given page object', function () {
var fakePage = {
// put your fake mock data here that TestUtils expects
};
var posts = TestUtils.scryRenderedDOMComponentsWithClass(fakePage, 'post');
expect(posts.length).toEqual(10);
});
I am not too sure what is happening in your renderIntoDocument function so the top test may be a little broken... It looks like there is either too much going on inside the function, or you need to test the calls that function is making instead. If you elaborate on what it does I'll edit the answer.
I'm a newbie trying to not rely so much or at all on using ptor.sleep() calls, especially after the click below. The line below never gets the value (they all return Nan)unless I include the ptor.sleep(1000); call after the click() below.
I've made various attempts to make the array elem to resolve before the results of the list after clicking, wrapping the click in the function, etc, but nothing I've tried works without the sleep calls. Already read up on protractor control flow.
devCountString = parseInt(arr[i]);
Thanks for any insights, maybe something obvious I've missed so that I can remove the ptor.sleep() calls.
my spec:
describe('\n == patch List suite results == \n', function() {
// login already was done in config files, onPrepare function.
var ptor, noFilterCount;
// needed here if we turn ptor.ignoreSynchronization = false;
beforeEach(function() {
ptor = protractor.getInstance();
ptor.ignoreSynchronization = true;
browser.get('https://my.abc.com:3000/fixes');
ptor.sleep(1200);
}); //end beforeEach()
it('11 - verify filter fewer', function() {
var sevStringElm, sevString;
var applicableCount;
ptor.ignoreSynchronization = false;
ptor.sleep(500);
sevStringElm = element(by.css("input.form-control.bf-spinner"));
sevStringElm.clear();
ptor.sleep(500);
sevStringElm.sendKeys( '8' );
ptor.sleep(500);
// click on the "fewer" spinner, wrap the click to wrap the .
var fewerPromise = element(by.css("span.bf-spinner-toggle:nth-child(2)")).click();
ptor.sleep(1000);
// now get the list of clickable elements in each device card. by title
var applicableDevicesElm = element.all(by.css("[title$='Applicable\ Devices']"));
applicableDevicesElm.getText().then(function(arr) {
console.log("arr.length= "+arr.length);
for (var i = 0; i < arr.length; i++) {
devCountString = parseInt(arr[i]);
expect(devCountString).toBeLessThan( 9 );
};
});
});
Everytime an action goes to the webdriver, Protractor will put that into the flow queue as shown in the documentation. As a result, when you get to inspect your elements after the click, the queue should have resolved the dependencies and have your state ready for the finder. In any case, even if you don't want to have the implicit wrapping that Protractor does on its actions (which are always asyc), you can put a .then(function(){}) after the click and put the post click logic in that anonymous calback function.
On a side note, you should have to use ptor anymore. Use browser instead that mixes in the protractor instance capabilities. Example: browser.sleep(1000)
In a test spec, I need to click a button on a web page, and wait for the new page completely loaded.
emailEl.sendKeys('jack');
passwordEl.sendKeys('123pwd');
btnLoginEl.click();
// ...Here need to wait for page complete... How?
ptor.waitForAngular();
expect(ptor.getCurrentUrl()).toEqual(url + 'abc#/efg');
Depending on what you want to do, you can try:
browser.waitForAngular();
or
btnLoginEl.click().then(function() {
// do some stuff
});
to solve the promise. It would be better if you can do that in the beforeEach.
NB: I noticed that the expect() waits for the promise inside (i.e. getCurrentUrl) to be solved before comparing.
I just had a look at the source - Protractor is waiting for Angular only in a few cases (like when element.all is invoked, or setting / getting location).
So Protractor won't wait for Angular to stabilise after every command.
Also, it looks like sometimes in my tests I had a race between Angular digest cycle and click event, so sometimes I have to do:
elm.click();
browser.driver.sleep(1000);
browser.waitForAngular();
using sleep to wait for execution to enter AngularJS context (triggered by click event).
You don't need to wait. Protractor automatically waits for angular to be ready and then it executes the next step in the control flow.
With Protractor, you can use the following approach
var EC = protractor.ExpectedConditions;
// Wait for new page url to contain newPageName
browser.wait(EC.urlContains('newPageName'), 10000);
So your code will look something like,
emailEl.sendKeys('jack');
passwordEl.sendKeys('123pwd');
btnLoginEl.click();
var EC = protractor.ExpectedConditions;
// Wait for new page url to contain efg
ptor.wait(EC.urlContains('efg'), 10000);
expect(ptor.getCurrentUrl()).toEqual(url + 'abc#/efg');
Note: This may not mean that new page has finished loading and DOM is ready. The subsequent 'expect()' statement will ensure Protractor waits for DOM to be available for test.
Reference: Protractor ExpectedConditions
In this case, you can used:
Page Object:
waitForURLContain(urlExpected: string, timeout: number) {
try {
const condition = browser.ExpectedConditions;
browser.wait(condition.urlContains(urlExpected), timeout);
} catch (e) {
console.error('URL not contain text.', e);
};
}
Page Test:
page.waitForURLContain('abc#/efg', 30000);
I typically just add something to the control flow, i.e.:
it('should navigate to the logfile page when attempting ' +
'to access the user login page, after logging in', function() {
userLoginPage.login(true);
userLoginPage.get();
logfilePage.expectLogfilePage();
});
logfilePage:
function login() {
element(by.buttonText('Login')).click();
// Adding this to the control flow will ensure the resulting page is loaded before moving on
browser.getLocationAbsUrl();
}
Use this I think it's better
*isAngularSite(false);*
browser.get(crmUrl);
login.username.sendKeys(username);
login.password.sendKeys(password);
login.submit.click();
*isAngularSite(true);*
For you to use this setting of isAngularSite should put this in your protractor.conf.js here:
global.isAngularSite = function(flag) {
browser.ignoreSynchronization = !flag;
};
to wait until the click itself is complete (ie to resolve the Promise), use await keyword
it('test case 1', async () => {
await login.submit.click();
})
This will stop the command queue until the click (sendKeys, sleep or any other command) is finished
If you're lucky and you're on angular page that is built well and doesn't have micro and macro tasks pending then Protractor should wait by itself until the page is ready. But sometimes you need to handle waiting yourself, for example when logging in through a page that is not Angular (read how to find out if page has pending tasks and how to work with non angular pages)
In the case you're handling the waiting manually, browser.wait is the way to go. Just pass a function to it that would have a condition which to wait for. For example wait until there is no loading animation on the page
let $animation = $$('.loading');
await browser.wait(
async () => (await animation.count()) === 0, // function; if returns true it stops waiting; can wait for anything in the world if you get creative with it
5000, // timeout
`message on timeout`
);
Make sure to use await
you can do something like this
emailEl.sendKeys('jack');
passwordEl.sendKeys('123pwd');
btnLoginEl.click().then(function(){
browser.wait(5000);
});
browser.waitForAngular();
btnLoginEl.click().then(function() { Do Something });
to solve the promise.
I can run this code in Android app (using PhoneGap adn jQuery Mobile) but not on desktop browsers.
It gives me a syntax error in firebug for this line =
var TicketList = eval("(" + ajax.responseText + ")");
Here is the code
// JScript source code
// ran on body load
function doJsStuff()
{
var ajax = AJAX();
ajax.onreadystatechange = function () {
if (ajax.readyState == 4) {
var TicketList = eval("(" + ajax.responseText + ")");
if (TicketList.ListCount > 0) {
document.getElementById("opencount").innerHTML = TicketList.ListCount +" Open Tickets";
for (Ticket in TicketList.Tickets) {
// add stuff to DOM
//AddTicketToList(TicketList.Tickets[Ticket]);
}
}
else {
document.getElementById("opencount").innerHTML = "All Tickets Reviewed";
DisplayNoresults();
}
}
}
ajax.open("GET", "http://website.com/ListTicketsRequest.ashx?PageNumber=1&PageSize=1&Status=Open", true);
ajax.send(null);
//document.addEventListener("deviceready", onDeviceReady, false);
//event to check for PhoneGap
//$('ul').listview('refresh');
$('#mtickets').page();
//showVars();
}
function AJAX()
{
var xmlHttp;
try
{
xmlHttp = new XMLHttpRequest();
}
catch (e)
{
}
return xmlHttp;
}
**TicketList is a variable in the JSon that comes across like this=
{"Tickets" : [{"TicketID": "1054","Category": "N/A","SubmittedUserID": "bob.thebuilder","ShortDescription": "test question QID:16668","CreationDate": "2/16/2011 12:24:19 PM","TicketStatus": "Open","LongDescription": "Something is wrong with this question I know I hve the right answer but it keeps telling me I'm wrong"},{"TicketID": "1053","Category": "Mission Support","SubmittedUserID": "dave","ShortDescription": "Make courseware revisions","CreationDate": "2/16/2011 9:34:48 AM","TicketStatus": "Open","LongDescription": "Find help tickets generated by users for possible courseware update."}], "PageCount": "6", "ListCount": "11"}
Note about PhoneGap If you are trying to include phoengap functions in a place where the code may also be executed on in a browser make sure you only add the phone gap function with on "deviceready" or your browser will not render. Example:
function onload(){
//event to check for PhoneGap
document.addEventListener("deviceready", onDeviceReady, true);
}
...
function onDeviceReady()
{
// Now PhoneGap API ready
vibrate(90); // vib to ack pg ready
$("a").click(function(event){
vibrate(30); // add 30 sec vib to all links
});
}
My immediate response would be to use jQuery's getJSON method, since you're aready using jQuery. jQuery's AJAX provides a much broader base of browser compatibility. Also, every time you use eval(), a small baby somewhere cries.
var url = "http://website.com/ListTicketsRequest.ashx?PageNumber=1&PageSize=1&Status=Open";
$.getJSON(url ,function(TicketList){
if (TicketList.ListCount > 0) {
$("#opencount").html(TicketList.ListCount +" Open Tickets");
for (Ticket in TicketList.Tickets) {
...
}
} else {
$("#opencount").html("All Tickets Reviewed");
DisplayNoresults();
}
});
If this still doesn't work for you, ensure that the JSON being returned is valid. But please stick to this method, and don't use eval!!
SIMPLIFIED UPDATE
var url = "http://website.com/ListTicketsRequest.ashx?PageNumber=1&PageSize=1&Status=Open";
$.getJSON(url ,function(AnyNameYouWant){
alert(AnyNameYouWant.ListCount + " Open Tickets");
});
UPDATE USING 'DATA'
If your url becomes too long, you might begin to encounter problems. It is suggested to pass the url data via the data argument.
var url = "http://website.com/ListTicketsRequest.ashx";
var data = "PageNumber=1&PageSize=1&Status=Open";
$.getJSON(url, data, function(AnyNameYouWant){
alert(AnyNameYouWant.ListCount + " Open Tickets");
});
Looking at your code, it seems likely to me that the syntax error isn't in the code you posted, but instead is contained in the JSON object you're evaluating in ajax.responseText. Take a look at the data being returned by the AJAX request. Is it valid Javascript? Does the page you're calling return something different to desktop browsers vs mobile? Is there an error message where the JSON code should be?
Another possibility: Is your app running on website.com? If not, Firefox is probably blocking the XMLHttpRequest from functioning properly. Firefox 3 and below block cross-site AJAX requests. Firefox 3.5 seems to allow some exceptions.