I am trying to figure out how to make this basic test pass consistently.
describe('home page', function () {
beforeEach(function () {
browser.driver.get('https://localhost:0000/xxxx/');
});
it('should have a title', function () {
var userName = browser.driver.findElement(by.id('userid_inputtext')).sendKeys('userNameXX');
var passWord = browser.driver.findElement(By.id("password_inputtext")).sendKeys("passWordXX");
var login = browser.driver.findElement(By.id('sign_in_button'));
login.click();
browser.driver.getCurrentUrl();
browser.driver.getTitle().then(function (title) {
expect(title).toEqual('Webpage Title');
});
});
});
The login in page is not Angular but after login it loads the Angular app. Right now my test is passing some of the time. The problem is that sometimes it picks up the title of the login page and sometimes it picks up the title of the home page(I want it to consistently test the title of the home page after login).
I am have played around with using a promise and browser.wait a little bit but have not really nailed this down. Any advice would be great!
Couple of things I could think of - most of the methods in protractor API are asynchronous and return promises.
In your login page once you login.click(), your homePage takes some time to load and therefore following async methods like - browser.getCurrentUrl & browser.getTitle are called first. This happens inconsistently as you pointed out.
Also you should assign variables to element locators and then perform actions on them! You should use Page Objects to store your locators. Please refer the Official Protractor Style Guide
browser.getCurrentUrl also returns a promise you have to resolve it as well.
To solve this issue you should use browser.wait in the right way:
describe('home page', function () {
beforeEach(function () {
browser.driver.get('https://localhost:0000/xxxx/');
});
it('should have a title', function () {
var userName = browser.driver.findElement(by.id('userid_inputtext'));
userName.clear();
userName.sendKeys('userNameXX');
var passWord = browser.driver.findElement(By.id("password_inputtext"));
passWord.clear();
passWord.sendKeys("passWordXX");
var login = browser.driver.findElement(By.id('sign_in_button'));
browser.driver.wait(login.click(),5000,'homePage should be loaded within 5 secs'); // would throw an error if the page takes more than 5 secs to load
// you could also use `browser.driver.sleep(500)` but not advisable as sleeps slow the tests down!
browser.driver.getCurrentUrl().then(function (url) {
expect(url).toEqual('homePage url');
});
browser.driver.getTitle().then(function (title) {
expect(title).toEqual('Webpage Title');
});
});
});
Also since your login page is non-angular, you could write your test in the right way which is to use browser.ignoreSynchronization and using the protractor api methods in a cleaner way.
describe('home page', function () {
beforeEach(function () {
browser.get('https://localhost:0000/xxxx/');
});
it('should have a title', function () {
browser.ignoreSynchronization = true; // set it to true for non-angular pages(loginPage)
var userName = element(by.id('userid_inputtext'));
userName.clear();
userName.sendKeys('userNameXX');
var passWord = element(By.id("password_inputtext"));
passWord.clear();
passWord.sendKeys("passWordXX");
var login = element(By.id('sign_in_button'));
browser.wait(login.click(),5000,'homePage should be loaded within 5 secs');
browser.ignoreSynchronization = false; // set it to false for angular pages(homePage)
browser.getCurrentUrl().then(function (url) {
expect(url).toEqual('homePage url');
});
browser.getTitle().then(function (title) {
expect(title).toEqual('Webpage Title');
});
});
});
Notice there is no need of accessing the browser.driver object, you can directly use protractor's methods!
Related
In my web app, I have a page where users can copy books from their library.
On the website, when a user clicks the copy button, the app executes this bit of Backbone.js code:
clonebook: function () {
var self = this;
this.book.clone().then((r) => {
self.model.collection.add(r);
});
},
In My SQL Server database, book looks like this:
bookId, bookTitle, authorId, libraryId, rowOrderNumber
The problem is, if the user tries to clone multiple books really fast by hitting the copy button, the rowOrderNumber can get out of whack.
Is there a way to tell the Backbone clone method to wait until the database or server has completed a clone process before going on to the next one?
Thanks!
I didn't use backbone, but
clonebook: function () {
var self = this;
self.loading = true
this.book.clone().then((r) => {
self.model.collection.add(r).then(() => {
self.loading = false
});
});
},
Not you have to use this loading somehow to disable the button
The most common UX pattern for this is to disable the button when the process starts, and enable when finished.
clonebook: function () {
var self = this;
// disable clone button
this.book.clone().then((r) => {
self.model.collection.add(r);
// enable clone button
});
},
We need to use for loop after login to a web page and perform multiple tests inside the for block on the page. My ideal test scenario should be like the snippet below. We have a table that have buttons on each of the row, and we will navigate to the next page for that particular button and validate data. currently we plugged all expects and assertions in a single IT block, but that is not a good solution. We need to split the sections of tests in different IT block.
require('..\\waitAbsent.js');
require("../node_modules/jasmine-expect/index.js");
var EC = protractor.ExpectedConditions;
describe('Student Enrollment Page Content Validation', function() {
beforeAll(function () {
browser.driver.manage().window().maximize();
browser.get(globalVariables.loginMain);
globalVariables.Email_Input_box.sendKeys(globalVariables.Demo_User);
globalVariables.Password_Input_Box.sendKeys(globalVariables.Demo_PWD);
globalVariables.Submit_Button.click();
browser.wait(EC.invisibilityOf(globalVariables.Submit_Button), 25000, 'submit button is not disappearing yet');
});
async function Row_Numbers() {
const RowCount = (globalVariables.tableData_Dashboard.all(by.tagName("tr")).count()).then(function(RC){
return RC;
});
}
for (var i = 1; i<Row_Numbers(); i++){
function tableData(n){
var row_1 = globalVariables.tableData_Dashboard.all(by.tagName("tr")).get(n);
// get cell values
var cells = row_1.all(by.tagName("td"));
it ('should return the data fo the first cell', function(){
var Student_ID = cells.get(0).getText().then(function (SID) {
console.log(SID);
return SID;
});
expect(Student_ID.toEqual('Something'));
});
it ("should show the button in this row", async function(){
const Button = globalVariables['Edit_Button_' + n];
// console.log(Button)
expect(await Button.isDisplayed());
Button.click();
// do some thing
});
}tableData(i)
}
});
When we run the test using this script, we get the following error:
E/launcher - Error while waiting for Protractor to sync with the page: "both angularJS testability and angular testability are undefined. This could be either because this is a non-angular page or because your test involve
s client-side navigation, which can interfere with Protractor's bootstrapping. See https://github.com/angular/protractor/issues/2643 for details"
How can I use the for loop to achieve our goal?
I have a couple of comments about this.
Firstly, Your angular error is because the Row_number function you declare is outside of any it block and therefore running before your beforeAll has run.
Next there should be no need for your tableData function as it appears replacing it's parameter n with the i counter from the loop would have the same effect.
Finally if your code needs to go across multiple pages to achieve these tests it will likely be much better to use a data-driven approach and write separate data files for each test. Do the values of these tables row change or would they be consistent?
Update:
This approach may look something like this but I have not tested this.
beforeAll(function () {
browser.driver.manage().window().maximize();
browser.get(globalVariables.loginMain);
globalVariables.Email_Input_box.sendKeys(globalVariables.Demo_User);
globalVariables.Password_Input_Box.sendKeys(globalVariables.Demo_PWD);
globalVariables.Submit_Button.click();
browser.wait(EC.invisibilityOf(globalVariables.Submit_Button), 25000, 'submit button is not disappearing yet');
});
it('test it', async () => {
globalVariables.tableData_Dashboard.all(by.tagName("tr")).forEach((row) => {
var cells = row.all(by.tagName("td"));
var Student_ID = cells.get(0).getText().then(function (SID) {
console.log(SID);
return SID;
});
expect(Student_ID.toEqual('Something'), 'should return the data fo the first cell');
const Button = globalVariables['Edit_Button_' + n];
// console.log(Button)
expect(Button.isDisplayed(), 'should show the button in this row').toBe(true);
Button.click();
// do some thing
});
})
I'm trying to redirect back to the previous page using $timeout and $window.history.back(). So when form is submitted a message will show saying (thank you... bla bla) all good from here, but when redirecting back the $timeout doesn't seems to kick in.
<div class="thankyou" data-ng-show="vm.hideEnquiryForm">
<h3><i class="fa fa-envelope fa-x2"></i> Thank you for enquiring, we will get back to you as soon as possible.</h3>
Return
</div>
vm.submitForm = function () {
enquireService.postEnquireForm(vm)
.then(function (response) {
//$location.path(vm.returnUrl);
//console.log($location.path() + ' '+ vm.returnUrl);
if (!vm.hideEnquiryForm) {
vm.hideEnquiryForm = true;
}
$timeout(function () {
$window.history.back;
}, 3000);
})
}
The are many way to do what you want...
The best way, in my opinion, could be using the interface exposed by your router (assuming that you are using a router)...
this is a useful post based on top of UI.Router Angular - ui-router get previous state, so, you need just to go to the previous state using $state.go;
If you cannot do something like that, the only way is using the real history object...
vm.submitForm = function () {
var returnUrl;
enquireService
.postEnquireForm(vm)
.then(function (response) {
var returnUrl = response.returnUrl;
if (!vm.hideEnquiryForm) {
vm.hideEnquiryForm = true;
}
return $timeout(window.history.back.bind(window), 3000);
});
};
I'm using the intern.js library with Chai and BDD to test my javascript application. I have the following code:
// Login as admin
bdd.before(function() {
indexPage = new IndexPage(this.remote, adminUsername, adminPass);
});
bdd.it('should turn a user to an input box', function () {
return indexPage.login(baseUrl)
.clearLocalStorage()
.get(baseUrl + '#/details')
.findAllByCssSelector('.user-filter')
.findByName('user')
.clearValue()
.click().pressKeys(['Functional Test', '\uE015', '\uE006'])
.end()
.findByXpath('//td[#class="grid-column-user"]/span')
.click()
.end()
.findByXpath('//td[#class="grid-column-user"]/input')
.then(function (elem) {
assert.lengthOf(elem, 1, "Yay");
})
.end();
});
bdd.it('should get the error state class when incorrect input is added', function () {
return indexPage.login(baseUrl)
.clearLocalStorage()
.get(baseUrl + '#/details')
.findAllByCssSelector('.user-filter')
.findByName('user')
.clearValue()
.click().pressKeys(['Functional Tes', '\uE015', '\uE006'])
.end()
.findByXpath('//td[#class="grid-column-user"]/span')
.click()
.pressKeys(['adsf', '\uE006'])
.end()
.findByXpath('//td[#class="grid-column-user"]/input[#class="user-error"]')
.then(function (elem) {
assert.lengthOf(elem, 1, "User should be input");
})
.end();
});
So I want to extrapolate out a lot of the logic that is duplicated between the tests. It seems like the following code could be in the before block:
bdd.before(function() {
indexPage = new IndexPage(this.remote, adminUsername, adminPass);
testUser = indexPage.login(baseUrl)
.clearLocalStorage()
.get(baseUrl + '#/details')
.findAllByCssSelector('.user-filter')
.findByName('user')
.clearValue()
.click().pressKeys(['Functional Test', '\uE015', '\uE006'])
});
bdd.it('should get the error state class when incorrect input is added', function () {
return testUser.end()
.findByXpath('//td[#class="grid-column-user"]/span')
.click()
.pressKeys(['adsf', '\uE006'])
.end()
.findByXpath('//td[#class="grid-column-user"]/input[#class="user-error"]')
.then(function (elem) {
assert.lengthOf(elem, 1, "User should be input");
})
.end();
});
When I put this code into the before block and store it as a variable, the behavior of the code doesn't run as it did when it was all in one long chained call and not in the before block. I'm not sure what I'm doing wrong here as I've tried multiple different iterations on what I've extrapolated out.
Thanks!
In your original code, you're resetting the page state for each test by logging in to a new session and clearing local storage. In your new code, you're only doing that once at the beginning of the suite, so all of the tests within the suite are going to run in the same session on the test page.
To replicate the behavior of your original tests, use a beforeEach rather than a before.
I have a login-dialog using a angular-strap modal, which gets invoked by:
scope.authModal = $modal({
template: '/components/login/login.html',
show: false,
scope: scope,
backdrop: 'static'
});
(that code is inside the link function of a login-directive.)
Now, my protractor code looks like this:
it('should perform login properly', function () {
browser.manage().deleteAllCookies();
element(by.model('login.username')).sendKeys('xy123');
element(by.model('login.password')).sendKeys('abz89');
element(by.binding("guiText.loginButton")).click();
browser.waitForAngular();
expect(element(by.id('login.username')).isPresent()).to.eventually.equal(false);
});
In another test above the element(by.id('login.username')).isPresent() has been proved to equal true when the login-dialog is visible.
The problem is, I'm getting Error: timeout of 10000ms exceeded with that test. In the browser I can see, that the credentials are typed in correctly and the button is being clicked. The login modal disappeas and then nothing happens and the browser is eventually running in to that timeout exception after waiting 10 seconds.
I had the same problem and I did below to solve this.
Write this function in your helper file and call this to click on login button in your code. Try to access the button by Id and then pass the id in this function, if not id then update the function as per your need
var clickAndWait= function (btnId) {
var returnVal = false;
browser.wait(function () {
if (!returnVal) {
element(by.id(btnId)).click().then(function () {
returnVal = true;
});
}
return returnVal;
}, 30000);
};