I'm trying to implement the following test scenario:
perform a click on a logo on the page
assert there is a new browser window opened (tab in Chrome) and check the current URL
The problem is that the page opened in a new browser window is a non-angular page while the main page I'm performing the click in is an angular page.
Here is my first attempt:
it("should show logo", function () {
var logo = scope.page.logo;
expect(logo.isDisplayed()).toEqual(true);
// opens a new page on click
logo.click().then(function () {
browser.getAllWindowHandles().then(function (handles) {
browser.switchTo().window(handles[1]).then(function () {
expect(browser.getCurrentUrl()).toEqual('http://myurl.com/');
});
// switch back to the main window
browser.switchTo().window(handles[0]);
});
});
});
which fails with:
Error: Error while waiting for Protractor to sync with the page:
"angular could not be found on the window"
which is understandable.
My second attempt was to play around with ignoreSynchronization boolean flag:
browser.ignoreSynchronization = true;
logo.click().then(function () {
browser.getAllWindowHandles().then(function (handles) {
browser.switchTo().window(handles[1]).then(function () {
expect(browser.getCurrentUrl()).toEqual('http://myurl.com/');
});
// switch back to the main window
browser.switchTo().window(handles[0]);
});
This actually makes this particular test pass without any errors, but it affects every test executed after this one, because browser is a global object and is shared between tests - protractor no longer syncs with angular on a page which results into all sorts of errors.
How should I implement the test?
As a workaround, I can change the restartBrowserBetweenTests setting to true and change ignoreSynchronization value without any problems - but it slows down the tests dramatically.
You can set ignoreSynchronization to be false once your verification is done. However, note that ignoreSynchronization is synchronous while everything else (click/get/etc) is asynchronous, so you need to be careful with that. Probably the safest way is to set it to true in beforeEach and false in afterEach, and just test that single logo click in that test.
Another solution: I have used sleep since getWindowHandles was
returning only one window name(parent/angular window). Let me know if there
is a better way.
this.validateProductPageInfo = function (productTitle) {
browser.sleep(5000);
browser.getAllWindowHandles().then(function (handles) {
if (handles.length === 2) {
browser.switchTo().window(handles[1]).then(function () {
var prodTitle = by.xpath('//h1[#id="fw-pagetitle"]');
browser.driver.findElement(prodTitle).getText().then(function (text) {
expect(text).toBe(productTitle);
});
});
browser.switchTo().window(handles[0]);
} else {
console.log("Error in switching to non angular window");
}
});
};
Related
We are in process of implementing BDD approach and use protractor for testing. The application under test has both Angular and Non Angular pages. The login page is non angular and home page is angular. The script runs fine on Login page and when it lands to non angular nothing happens (no action performed).
What could be the issue?
StepDefinition.js
Given(/^User lands on Login$/, function () {
var appUrl = properties.get('appUrl');
return browser.driver.get('appUrl');
browser.ignoreSynchronization = true;
});
When(/^User enters Username and Password$/, function () {
xph.get('Username').sendKeys(username);
return xph.get('Password').sendKeys('password');;
});
When(/^User Clicks Submit$/, function(){
browser.executeScript("arguments[0].click();",xph.get('Login'))
//return browser.sleep(7000);
browser.ignoreSynchronization = false;
browser.waitForAngular();
});
Then(/^User successfully logs$/, function() {
var hString= xph.get('LogOut');
hString.getText().then(function(text){expect(text).to.equal('LogOut');});
});
Then(/^User clicks Create Account$/, function () {
browser.executeScript("arguments[0].click();",xph.get('CreateAcct'))
});
Two issue in your code, try again after fix them as following:
1) you put browser.ignoreSynchronization = true behindreturn, it make no sense.
Inside browser.get(), it will detect the opening page is angular, except to put browser.ignoreSynchronization = true before browser.get() to tell protractor the opening page is non angular page.
Given(/^User lands on Login$/, function () {
var appUrl = properties.get('appUrl');
browser.ignoreSynchronization = true;
return browser.driver.get('appUrl');
});
2) You have to return a promise like object for each step definition, otherwise the runner will pause at that step definition until timeout.
waitForAngular() equivalent to browser.ignoreSynchronization = false;, The former is introduced in protractor higher version, the later can work protractor lower and higher version.
Why you not use the Protractor API xph.get('Login').click(), but using Javascript DOM API to click the Submit button.
When(/^User Clicks Submit$/, function(){
return element(<locator of Submit button>).click().then(function(){
return browser.ignoreSynchronization = false;
})
});
I am currently testing a non Angular js website with protractor. My code is as follows :
describe("Login ", function () {
it("test", co.wrap(function* () {
browser.ignoreSynchronization = true;
yield browser.get("URL");
var elmOK = element(by.css('a[href="#partner"]'))
yield elmOK.click();
expect(browser.getCurrentUrl()).toContain('login');
}));
});
Test Scenario : My test opens the URL mentioned and selects the link with href=#partner. A login page which should pop up. But when I run the test and the link is clicked the login page doesn't popp up.
Please tell me what I am doing wrong?
Protractor appears synchronous because it waits for angular to be stable prior to moving to the next action. So if the above was an angular page, it would:
load the page, then Protractor would wait for angular (wait for the page to be stable)
tell selenium webdriver to find the element and click on it.
In a non-angular page, setting the ignore synchronization set to true in the right direction. What you need to do is to come up with your own sleep between getting the page and clicking the element. After you click on the element, you'll also need to wait for the next window to pop up, change focus to the next window to see if the url contains login.
browser.ignoreSynchronization = true;
browser.get("URL");
// let the page load
browser.sleep(2000);
element(by.css('a[href="#partner"]')).click().then(() => {
// may have to add sleep in for the page to load
browser.sleep(2000);
// switch to the next window for the popup
browser.driver.getAllWindowHandles().then((windowHandles) => {
let popupLoginHandle = windowHandles[1];
browser.driver.switchTo().window(popupLoginHandle).then(() => {
// check to see if the pop up has a url that contains 'login'
expect(browser.getCurrentUrl()).toContain('login');
});
});
});
Putting Hard coded waits is not an good practice.
If you are using jasmine for reporting , you may put the following code inside conf.js:
jasmineNodeOpts: {
defaultTimeoutInterval: 600000,
},
Made some changes based on help from engineering. Here is the final code I used for grabbing the new window handle:
localdriver = #driver
#driver.getAllWindowHandles()
.then (handles) ->
localdriver.switchTo().window(handles[1])
I'm currently running an automation stack that uses Selenium Webdriver, Mocha, Chai, and Grunt. I'm creating scripts in Coffeescript, but an answer to my question in Javascript would be perfectly fine.
What I'm trying to do:
Click button on main browser window
Switch driver to the second window that opens after button click
Perform actions in the second window
Close second window and return to the first.
I've scoured the internet looking for an answer on how to do this. Just started learning all this stuff a few months ago, and I'm still stumbling through creating stuff. I'm seeing a lot of Java and C+ examples, but not much on the Javascript side. Can anyone provide an example of how to set up the code for the above scenario using Selenium Webdriver and Javascript?
var parent = driver.getWindowHandle();
var windows = driver.getAllWindowHandles();
driver.switchTo().window(windows[1]);
// do some stuff
driver.close();
driver.switchTo().window(parent);
What you want is driver.getAllWindowHandles(), but because this returns a promise, make sure that you then use the handles inside of the then function
// select the newly opened window
driver.getAllWindowHandles().then(function gotWindowHandles(allhandles) {
driver.switchTo().window(allhandles[allhandles.length - 1]);
});
Whenever new tab opens, it takes some time to come up and render. In this situation, it is difficult to switch the tab because the tab is not opened yet and driver.getAllWindowHandles() will not give handler for that tab. I solved this problem in this way, I am assuming I have one opened tab and on some button click, I am opening new 2nd tab.
function openNewTab(driver) {
driver.wait(function () {
return driver.getAllWindowHandles().then(function (handles) {
var isHandleCount2 = (handles.length == 2);
if (isHandleCount2) {
driver.switchTo().window(handles[1]);
}
return isHandleCount2;
});
}).then(function () {
// Now do some stuff in new tab
var buttonElement = driver.wait(until.elementLocated(By.xpath("//td[*//span[text()='Click me']]")));
buttonElement.click();
});
}
This code will wait until the number of handles or tabs will not equal to 2.
#Jai Prak's answer is brilliant.What about the case of three tabs or more? The newest tab will always be the last Tab.
return await driver.wait(async function () {
return await driver.getAllWindowHandles().then(async function (handles) {
// var isHandleCount2 = (handles.length == 2);
if (handles.length > 1) {
return driver.switchTo().window(handles[handles.length - 1]);
}
return false;
});
}).then(function () {
// Now do some stuff in new tab
});
The above will apply except in cases you switch between Tabs.To move the next tab, get the current Tab's index -1
Case I try to test:
On Angular app page press button, that redirects you to some other site (not an Angular app).
it('should go to 3d party service when i click "auth" button' , function() {
browser.driver.sleep(3000);
element(by.id('files-services-icon')).click();
element(by.id('box-vendor-menu-item')).click();
browser.driver.sleep(2000);
expect( browser.driver.getLocationAbsUrl()).toContain('https://app.box.com/api/oauth2/authorize');
});
but I get:
UnknownError: unknown error: angular is not defined
How that can be achived? Thanks!
You need to do 2 things
Set browser.ignoreSynchronization = true; before trying to read the URL of the 3rd party page, so the browser doesn't wait for (and so require) Angular promises to resolve in the page (and set it to false afterwards);
Use browser.getCurrentUrl() as opposed to browser.getLocationAbsUrl(), as the former just uses the plain webdriver method of reading the URL, rather than accesing it via Angular.
The following should work:
it('should go to 3d party service when i click "auth" button' , function() {
element(by.id('files-services-icon')).click();
element(by.id('box-vendor-menu-item')).click();
browser.ignoreSynchronization = true;
expect(browser.getCurrentUrl()).toContain('https://app.box.com/api/oauth2/authorize');
browser.ignoreSynchronization = false;
});
You need to tweak ignoreSynchronization flag - set it to true before clicking on the link and set back to false in afterEach():
afterEach(function () {
browser.ignoreSynchronization = false;
});
it('should go to 3d party service when i click "auth" button' , function() {
element(by.id('files-services-icon')).click();
browser.ignoreSynchronization = true;
element(by.id('box-vendor-menu-item')).click();
expect(browser.getLocationAbsUrl()).toContain('https://app.box.com/api/oauth2/authorize');
});
See also:
Non-angular page opened after a click
You may also need to wait for URL to change, example here.
I am new to Protractor. I think I have this down when dealing with an Angular page, but can't figure it out for a non-Angular page. Any help would be appreciated.
describe('Search', function() {
it('should click Search button and wait for results', function() {
browser.driver.findElement(by.id('search')).click();
});
});
Testing non-angular pages with Protractor can be tricky regarding waiting for stuff.
I suggest you upgrade Protractor to latest (1.5.0 as of now), use a custom function waitReady() that browser.wait for elements ready and rewrite your test like below. Note you can put everything within 1 spec if you like so.
// TODO: use page objects
var searchBtnElm = $('#search'); // use element(by.id('search')) if you prefer
it('waits for the elements present and visible (non-angular)', function() {
expect(searchBtnElm.waitReady()).toBeTruthy();
});
it('should click Search button', function() {
searchBtnElm.click();
});
it('wait for more results', function() {
// keep using waitReady() before interacting with the elements
// and before performing expectations on them
});
More details of why waitReady here.
Note: remember to set ignore synchronization for testing a non-angular page:
browser.ignoreSynchronization = true;
You can set it before browser.get the non-angular page.
I've suggested setting a high implicit wait in the past, e.g.
browser.manage().timeouts().implicitlyWait(5000);
That hack allows to you avoid waitReady and keep using the standard
expect(searchBtnElm.isPresent()).toBeTruthy();
But has an ugly disadvantage when testing for elements NOT present, i.e. when testing for absent or non visible elements in which case it will wait 5 seconds (5000ms) in vane, e.g. when doing
expect(someNonExistingElm.isPresent()).toBeFalsy();
Figured this out. I simply added the code below, after the click method:
describe('Search', function() {
it('should click Search button and wait for results', function() {
browser.driver.findElement(by.id('search')).click();
dvr.wait(function() {
return dvr.isElementPresent(by.xpath(
'/html/body/div/div[4]/div/div[2]/div/div/div/span'));
}, 20000);
});
});
Another Neat approach is to use "Expected Conditions" inside browser.wait - something like this:
var EC = protractor.ExpectedConditions;
var search = element(by.id('search'))
browser.wait(EC.visibilityOf(search), 2000).then(function(){
search.click()
})
You can get more details here: https://angular.github.io/protractor/#/api?view=ExpectedConditions
In protractor there are two types terms for on the page. isPresent ask if the element is exists on the page. isDisplayed asks if the element is visible. If you are waiting for a page to load you need to wait for isDisplayed, but that will error if it is not present, so wait for isPresent first. I use a function to wait for an element.
function waitForElement(el, waitTimeoutMilliseconds){
return browser.wait(function() { return el.isPresent(); }, waitTimeoutMilliseconds)
.then(function(){
return browser.wait(function() { return el.isDisplayed(); }, waitTimeoutMilliseconds);
});
}
Then just call that function in your test.
describe('Search', function() {
it('should click Search button and wait for results', function() {
var el = element(by.id('search'));
waitForElement(el, 5000);
el.click();
});
});