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
Related
I am writing a selenium test using javascript in Jmeter. When I click on a link on the site, it opens in a new tab by default. The automated browser even switches to this new tab. But, it seems selenium is not switching to the new tab. When I print the inner HTML for the body element (obtained by xpath //body) after clicking on the link, I get back the source for the first tab, not the second.
When I try to wait for any element at all on the next page (waiting for //div[#id="my-div-on-second-page-only"], for example), I get a timeout saying the element was never located (long after I can see the page has finished loading).
I did happen upon this question, but it's for python, and I am also struggling to understand the accepted answer.
Update
My code for switching the tabs currently looks like this:
// Switch tabs
var tabs = WDS.browser.getWindowHandles();
var tab = WDS.browser.getWindowHandle();
WDS.log.info("Tabs: " + tabs + " current: " + tab); // Output below
// Assume always there are only two tabs
for (t in tabs)
{
WDS.log.info("Checking tab " + t);
if (t != tab)
{
WDS.log.info("Switching to " + t);
WDS.browser.switchTo().window(t);
break;
}
}
The output for the line marked with // Output below is:
Tabs: [CDwindow-A83928D86BA4F6F46C5D7F4B63B674A5, CDwindow-177CF406C98C28DF4AF5E7EC3228B896] current: CDwindow-A83928D86BA4F6F46C5D7F4B63B674A5
I am not even entering the for/in loop. I have tried switching using tabs[index], tabs.get(0);, and tabs.iterator.next();, but none have worked. I've been scouring the internet all day for some information on the data type returned by WDS.browser.getWindowHandles();, but just can't seem to find it.
Update 2
I ultimately switched to the Java interpreter. None of the proposed solutions, even the ones in javascript, worked. I suspect there is an issue with the javascript interpreter itself. I'm going to leave the question open in case anyone would like to offer a solution that is tested and works in the Jmeter webdriver extension for future knowledge.
Below is my working Java code. It uses Matias Dominguez's example, which makes the assumption that the last entry in tabs is the new tab. Although I find Mike Cook's solution seems to be the best in terms of a general solution, Matias' was good enough for my problem.
// Switch tabs
ArrayList tabs = new ArrayList(); // Jmeter interpreter uses <String> by default
tabs.addAll(WDS.browser.getWindowHandles());
String tab = WDS.browser.getWindowHandle();
WDS.log.info("Tabs: " + tabs + " current: " + tab + " Size of tabs: " + tabs.size());
WDS.browser.switchTo().window(tabs.get(tabs.size() - 1));
WDS.log.info("Tabs switched successfully");
I would load up a list with all the window handles before the click action. Then after clicking the new window would not be equal to any of the saved window handle values.
List<String> tabs = new ArrayList<>();
tabs.addAll(driver().getWindowHandles());
String newTab = null;
targetElement.click();
for (String currentWindow: driver().getWindowHandles()) {
if (!tabs.contains(currentWindow)) {
newTab = currentWindow;
break;
}
}
When you click a link and its open a new tab the driver still focus on the first tab, so you have to move to it:
List<String> tabs = new ArrayList<>();
tabs.addAll(driver().getWindowHandles());
driver().switchTo().window(tabs.size() - 1);
This get all windows handles and move to the last tab opened, then you can find(theElement).
You can do something like below.
var full_tabs = WDS.browser.getWindowHandles();
var x = full_tabs.toString();
var arr = x.split(',');
var newtab = arr[arr.length-1].split(']').toString().replace(',','').replace(' ','');
It is not best solution but it will work. You can combine it into 2 line.
You can switch between tabs calling the below method:
public void setMainWindow() {
try{
int numberOfWindow = driver.getWindowHandles().size();
boolean tmpIndex = true;
String mainWindowHandler = driver.getWindowHandle(); // Store the current window handle
if (numberOfWindow > 1) {
for(String winHandle : driver.getWindowHandles()){
driver.switchTo().window(winHandle); // Switch to new window opened
if (tmpIndex) {
mainWindowHandler = winHandle;
}
tmpIndex = false;
}
// Perform the necessary actions on new window
driver.close(); // Close the new window, if that window no more required
driver.switchTo().window(mainWindowHandler); // Switch back to original browser (first window)
}
} catch (Exception e) {}
}
// Continue with original browser (first window)...
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");
}
});
};
First of all, please don't recommend me to use the native iOS/Android objects, this is a test about custom controls and I need it to work.
I'm encountering a strange behavior while using a custom Navbar in Android or iOS. All seems to be right until I close a window directly (by code) in the custom navBar. The secont time I open another window with the navBar, the old objects (label, buttons, etc.) are still there.
I post an example:
First, from a main window I call the AddForm in the NavBar:
var ui = require('navigation');
var nav = ui.createNavigatorGroup();
Alloy.Globals.navBar = nav;
nav.open(winAddPill, {animated: true});
When user press ADD button (you can't see, is in bottom of a form), I autoclose the window in the nav, after save the data, with this code:
Alloy.Globals.navBar.close($.win);
If I do that, when now I call another window, e.g., show info (which has a DELETE button in right), the title label is mixed with previous window:
Alloy.Globals.navBar.open(winPill, {animated: true});
As you can see, all is mixed, this is what must be shown instead:
If I continue to open new windows, all is still mixing.
Any help to avoid this behavior? I was fighting with this problem 4 days and don't find the solution.
Finally, the custom navBar that I'm using:
exports.createNavigatorGroup = function() {
var me = {};
var navViews = []; // A stack of navigation bars
var navView;
function pushNavBar() {
navView = Ti.UI.createView({
top: 0,
height: 44,
backgroundColor: '#BBB'
});
navViews.push(navView);
};
function popNavBar() {
navViews.pop();
navView = navViews[navViews.length - 1];
};
// Make sure we always have a navView available to prepare
pushNavBar();
me.open = function(win) {
navView.add(Ti.UI.createLabel({
text: win.title,
color: 'black'
}));
navView.win = win;
win.add(navView);
win.navBarHidden = true;
win.open();
// Prepare for the next window
pushNavBar();
};
me.close = function(win) {
if (navViews.length > 1) {
// Close the window on this nav
popNavBar();
win.close();
}
};
return me;
};
Also I added a simple and runable project in GitHub with only 3 empty windows for testing. You can see the question here and the project is here.
I found the f***g problem!!!
I analized with detail the code and found it. All the problem resides in that a new navView is always prepared for config new objects, so, lets say, we have 2 windows opened, then we have 3 navViews. The number 3 is clear, nothing inside, just ready to setLeftButton before open new window with it.
But if we close the actual window, with the navView 2 with objects, the code makes "popNavView" that only remove the last (empty) navView 3 and SETS the actual navView 2 as ready to use... with all its actual objects inside.
The simple solution is... remove it after set, with this simple line inside popNavBar:
function popNavBar() {
navViews.pop();
navView = navViews[navViews.length - 1];
navView.removeAllChildren();
};
I will re-do the github code to let it available for others who wants to use a functional custom NavBar.
I have a website that opens a new window. I am trying to trigger onclose event on the child window (if the user closed the window the parent window will alert it).
I found a stackoverflow question regarding that problem at: How to run function of parent window when child window closes?
But, the answer also preforms action on the child window which I think I can't do because the child window opens a website not in my control (I can't edit its html).
I was thinking of using the following to trigger the on close event
var inFormOrLink;
$('a').live('click', function() { inFormOrLink = true; });
$('form').bind('submit', function() { inFormOrLink = true; });
$(window).bind("beforeunload", function() {
return inFormOrLink;
})
How do I apply this on the new tab/window?
This is not possible to do solely on the client-side.
In order to do this, you'll need to do the following:
Upon opening a tab, send an ajax call to a script that doesn't stop loading until it receives a second ajax call, this is usually done with a script that waits for an sql value to exist before outputting.
Upon closing the tab, send the second ajax call to that script so that it replies to the original ajax call.
Viola.
I'm not aware of a simpler way to do this.
Original window:
function waitForTabClose(windowID) {
$.post('waitfortabclose.php',{windowID:windowID},function(data) {
if (data == 1) {
// Do tab closed stuff
} else {
waitForTabClose(windowID);
}
});
}
waitfortabclose.php
$i=1;
$wID = $_POST['windowID'];
do {
if (file_get_contents($wID) == $wID) {
echo 1;exit;
}
set_time_limit(30);
sleep(5);
} while($i++ < 50);
echo 0;
New Tab
window.onclose = function() {
$.post('windowclosed.php',{windowID:windowID);
};
windowclosed.php
$wID = $_POST['windowID'];
file_put_contents($wID,$wID);
This is pseudocode, and hasn't been tested. The functionality can be written in a lot of ways, this is just how I'd do it.
Found a solution to what i were looking for. it was much easier then the above answer plus its actually works and what i were looking for.
var win = open('http://www.google.com');
if (win.closed) {
alert('Window closed! Hoorah!');
}
Thanks very much for whoever tried helping.
I'm having troubles with my flyout. What happens with my gadget is you double click a component and it will have a corresponding flyout window. If you double click that or any other visual component with a flyout, though, the flyout document is returned as null. I have no idea why this is, and if you make the flyout go away and reopen it or a new one it's ok. It's only when a flyout is already opened this happens. I'm looking for some ideas on why this is.
Double click code:
Blah.prototype.ondblclick = function()
{
var me = this.parent;
if (System.Gadget.Flyout.show)
{
// flyout is already shown, make sure it shows our stuff
System.Gadget.Flyout.file = FLYOUT_FILE;
onFlyoutShow();
}
else
{
System.Gadget.Flyout.file = FLYOUT_FILE;
System.Gadget.Flyout.onShow = onFlyoutShow;
System.Gadget.Flyout.show = true;
}
System.Gadget.Flyout.onHide = onFlyoutHide;
function onFlyoutShow()
{
me.flyoutOpen = true;
me.updateFlyout();
}
function onFlyoutHide()
{
me.flyoutOpen = false;
}
};
Executed code:
Blah.prototype.updateFlyout = function ()
{
var flyoutDoc = System.Gadget.Flyout.document;
//flyoutDoc is null at this point
var info = flyoutDoc.getElementById("info");
info.innerHTML = "info: " + this.information;
//Error thrown: 'null' is null or not an object
}
I don't know a lot about writing gadgets for windows 7, but to me it looks a lot like a timing issue. When the flyout is already there, you change the file property which tells it to load a new file. Without waiting you then call onFlyoutShow which tries to get the document and the document isn't loaded yet.
My first thought is: Doesn't the onShow event fire when you set the file? Probably doesn't or you wouldn't have the if, but worth verifying.
If that doesn't work, calling onFlyoutShow in a timeout. Start with a long timer, like 1000. And then shorten it, hopefully you can get down to 0: setTimeout(onFlyoutShow, 0);