I'm having an issue with automating the process of downloading a file from a website. The website has a Java button, that when clicked, triggers the download of an Excel file. I'm using the most recent build of Watin (v2.1).
I've managed to get Watin to log into the website, navigate to the appropriate page, change parameters on the page, and click the button to start the download.
However, when the download has completed, the IE9 download box appears, and nothing happens, until Watin timesout.
I'd appreciate any suggestions as I can't see any way of downloading a file, or getting it to save the file. Even if it passed 'Alt+S' to the page, that would save it. I've tried running it through WatinTestRecorder and that doesn't prompt for saving.
using (var browser = new IE(sLogin))
{
browser.AddDialogHandler(new OKDialogHandler());
browser.AddDialogHandler(new DialogHandlerHelper());
browser.AddDialogHandler(new ConfirmDialogHandler());
browser.AddDialogHandler(new ReturnDialogHandlerIe9());
browser.TextField(Find.ByName("txtUserID")).TypeText("username");
browser.TextField(Find.ByName("txtPassword")).TypeText("password");
browser.Button(Find.ByName("btnLogin")).Click();
browser.WaitForComplete();
browser.GoTo(targetUri);
browser.SelectList("ctl00_phFormContent_ucOptionParam0_lst").SelectByValue("4");
browser.Button(Find.ByName("ctl00$phFormButtonBar$btnRun")).Click();
browser.WaitForComplete();
//Some code to download the file here!
}
This should be supported since version 1.1.0.4000. The release notes for that version aren't online anymore (http://watin.org/documentation/), but I found it in Googles cache (http://svn6.assembla.com/svn/ci-samples/dotnet/watir/website/releasenotes-1-1-0-4000.html)
It should be something like:
using(IE ie = new IE(someUrlToGoTo))
{
FileDownloadHandler fileDownloadHandler = new FileDownloadHandler(fullFileName);
ie.AddDialogHandler(fileDownloadHandler);
ie.Link("startDownloadLinkId").Click();
fileDownloadHandler.WaitUntilFileDownloadDialogIsHandled(15);
fileDownloadHandler.WaitUntilDownloadCompleted(200);
}
EDIT:
After the comments below, this answer was accepted. So I'm assuming the following code works (which is taken from the link to SourceForge in my last comment, notice the ClickNoWait):
using(IE ie = new IE(someUrlToGoTo))
{
FileDownloadHandler fileDownloadHandler = new FileDownloadHandler(fullFileName);
ie.AddDialogHandler(fileDownloadHandler);
ie.Link("startDownloadLinkId").ClickNoWait();
fileDownloadHandler.WaitUntilFileDownloadDialogIsHandled(15);
fileDownloadHandler.WaitUntilDownloadCompleted(200);
}
The accepted answer did not work for me because IE 9 pops up a "Notification" you have to navigate before you can get to the actual Save As dialog (and WatiN cannot handle notifications automatically). I followed Borris Pavlov's link which did a good job showing the way. I cleaned up the code a bit that was posted there and this was the resulting file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WatiN.Core;
using WatiN.Core.Native.Windows;
using System.Threading;
using System.Windows.Automation;
namespace MyProject
{
public static class BrowserExtensionMethods
{
public static void DownloadIeFile(this IE browser,string saveAsFilename=null)
{
// see information here (http://msdn.microsoft.com/en-us/library/windows/desktop/ms633515(v=vs.85).aspx)
Window windowMain = new Window(NativeMethods.GetWindow(browser.hWnd, 5));
TreeWalker dialogElementTreeWalker = new TreeWalker(Condition.TrueCondition);
AutomationElement mainWindow = dialogElementTreeWalker.GetParent(AutomationElement.FromHandle(browser.hWnd));
Window windowDialog = new Window(NativeMethods.GetWindow(windowMain.Hwnd, 5));
// if doesn't work try to increase sleep interval or write your own waitUntill method
Thread.Sleep(1000);
windowDialog.SetActivate();
AutomationElementCollection dialogElements = AutomationElement.FromHandle(windowDialog.Hwnd).FindAll(TreeScope.Children, Condition.TrueCondition);
if (string.IsNullOrEmpty(saveAsFilename))
{
ClickSave(dialogElements);
}
else
{
ClickSaveAs(mainWindow, dialogElements,saveAsFilename);
}
}
private static void ClickSaveAs(AutomationElement mainWindow, AutomationElementCollection dialogElements,string filename)
{
foreach (AutomationElement element in dialogElements)
{
if (element.Current.Name.Equals("Save"))
{
AutomationElementCollection dialogSubElements = element.FindAll(TreeScope.Children, Automation.ControlViewCondition);
InvokePattern clickPatternForSaveDropdown = (InvokePattern)dialogSubElements[0].GetCurrentPattern(AutomationPattern.LookupById(10000));
clickPatternForSaveDropdown.Invoke();
Thread.Sleep(3000);
AutomationElementCollection dialogElementsInMainWindow = mainWindow.FindAll(TreeScope.Children, Condition.TrueCondition);
foreach (AutomationElement currentMainWindowDialogElement in dialogElementsInMainWindow)
{
if (currentMainWindowDialogElement.Current.LocalizedControlType == "menu")
{
// first array element 'Save', second array element 'Save as', third second array element 'Save and open'
InvokePattern clickMenu = (InvokePattern)currentMainWindowDialogElement.FindAll(TreeScope.Children, Condition.TrueCondition)[1].GetCurrentPattern(AutomationPattern.LookupById(10000));
clickMenu.Invoke();
Thread.Sleep(5000);
ControlSaveDialog(mainWindow, filename);
break;
}
}
}
}
}
private static void ClickSave(AutomationElementCollection dialogElements)
{
foreach (AutomationElement element in dialogElements)
{
// You can use "Save ", "Open", ''Cancel', or "Close" to find necessary button Or write your own enum
if (element.Current.Name.Equals("Save"))
{
// if doesn't work try to increase sleep interval or write your own waitUntil method
// WaitUntilButtonExsist(element,100);
Thread.Sleep(1000);
AutomationPattern[] automationPatterns = element.GetSupportedPatterns();
// replace this foreach if you need 'Save as' with code bellow
foreach (AutomationPattern currentPattern in automationPatterns)
{
// '10000' button click event id
if (currentPattern.Id == 10000)
{
InvokePattern click = (InvokePattern)element.GetCurrentPattern(currentPattern);
click.Invoke();
}
}
}
}
}
private static void ControlSaveDialog(AutomationElement mainWindow, string path)
{
//obtain the save as dialog
//*** must disable throwing of the NonComVisibleBaseClass "exception" for this to work in debug mode:
// 1. Navigate to Debug->Exceptions...
// 2. Expand "Managed Debugging Assistants"
// 3. Uncheck the NonComVisibleBaseClass Thrown option.
// 4. Click [Ok]
//***copied from http://social.msdn.microsoft.com/Forums/en-US/27c3bae8-41fe-4db4-8022-e27d333f714e/noncomvisiblebaseclass-was-detected?forum=Vsexpressvb
var saveAsDialog = mainWindow.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.NameProperty, "Save As"));
//var saveAsDialog = mainWindow.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.NameProperty, "[#] Save As [#]")); //needed if using sandboxie
//get the file name box
var saveAsText = saveAsDialog
.FindFirst(TreeScope.Descendants,
new AndCondition(
new PropertyCondition(AutomationElement.NameProperty, "File name:"),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit)))
.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern;
//fill the filename box
saveAsText.SetValue(path);
Thread.Sleep(1000);
//find the save button
var saveButton =
saveAsDialog.FindFirst(TreeScope.Descendants,
new AndCondition(
new PropertyCondition(AutomationElement.NameProperty, "Save"),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button)));
//invoke the button
var pattern = saveButton.GetCurrentPattern(InvokePattern.Pattern) as InvokePattern;
pattern.Invoke();
}
}
}
See the comment in the ControlSaveDialog method for info on how to make this work as a debug build (VS settings).
I actually only tested the "Save As" operation so I hope the other works (it seems that the original poster added the "Save As" as an afterthought so my guess is that he tested the "Save" operation more thoroughly).
To call this you can use some code like:
Link lastMp4Link = mp4Links[mp4Links.Count - 1]; //mp4Links is a WatiN.Core.LinkCollection
lastMp4Link.Click();
browser.DownloadIeFile(string.Format(#"c:\temp\myFile.blah")); //"browser" is a WatiN.Core.IE object
I just got word from the Watin-users mailing list that FileDownloadHandler is broken with IE9. There is no fix yet, however I uninstalled IE9 (roll back to IE8) and it has the old style dialog that is handled by WatiN.
Watin can't find download dialog and dialog buttons. It can resolve with this.
How to test file download with Watin / IE9?
See comments
After many revisions using WaitN, I ended up using Selenium. It gives you a ton more control over what is being processed and used and allows you to use more modern web browsers.
Related
I'm aware of selenium (but for some reason, the real page that has a complex HTML, I couldn't retrieve it well using DOM, so I eventually give up - I'm trying luck with FlaUI). To start out, I'm trying some simple task such as open a page and click on a button. But it isn't working. It didn't result in any error, the application seems to just hangs. What am I missing?
my code:
var process = Process.Start(#"C:\Program Files\Mozilla Firefox\firefox.exe",
"file:///C:/Users/jckj33/Desktop/button.html");
//process.WaitForInputIdle();
var app = FlaUI.Core.Application.Attach(process.Id);
Debug.WriteLine("wating...");
app.WaitWhileBusy();
Debug.WriteLine("waiting done!!");
Thread.Sleep(5000);
using (var automation = new UIA3Automation())
{
var window = app.GetMainWindow(automation);
var button = window
.FindFirstDescendant(cf => cf.ByControlType(FlaUI.Core.Definitions.ControlType.Button))
.AsButton();
button?.Invoke();
}
where button.html is:
<input type='button' value='Click me' onclick='alert("hello, world!")'>
I am newbie to C#. I need help to be able to login the webpage and read some data.
After googling, I tried to find below code and other resources but in all cases, I can only get the html source of the login page but not other pages source data.
I need to traverse to the homepage first.
Then, I need to traverse
to "Port Status" and read some useful data. To inform data is stored in the
frames. How can I read data from the frames ?
Adding more info
1) view-source:http://192.168.0.239/homepage.html, which calls script as shown below
getSubTree('Management');
2) The above call hits the content in java script file (http://192.168.0.239/frame.js)
case "Management":
str += OneNodeLink("lv1", "Switch Information", "/iss/specific/sysInfo.html?Gambit="+GAMBIT);
str += OneNodeLink("lv1", "Port Status", "/iss/specific/port_settings.html?Gambit="+GAMBIT);
document.getElementById("treeFrame").innerHTML = str;
3) The above code executes this file "view-source:http://192.168.0.239/iss/specific/port_settings.html?Gambit=pisfgagehesfhjikojngqcabdfkjeeffmpkhfckm" and gets "Port Status"
My requirement is to read the "Port Status" received which is from the frames data. Hope I am able to make it clear. Let me know if you need more info to help.
Link has screenshots and html source files : https://www.dropbox.com/sh/oml3tk75tf1lu5c/AADuGtbZci3gnyOQ2AE8IYwua?dl=0
Thanks a lot in advance
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace WebBrowserWithoutAForm
{
class Program
{
private static bool completed = false;
private static WebBrowser wb;
[STAThread]
static void Main(string[] args)
{
wb = new WebBrowser();
wb.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(wb_DocumentCompleted);
string postData = string.Format("LoginPassword={0}&login=Login", "password");
ASCIIEncoding enc = new ASCIIEncoding();
wb.Navigate("http://192.168.0.239", "", enc.GetBytes(postData), "Content-Type: application/x-www-form-urlencoded\r\n");
//wb.Navigate("http://192.168.0.239");
while (!completed)
{
Application.DoEvents();
Thread.Sleep(100);
}
Console.Write("\n\nDone with it!\n\n");
Console.ReadLine();
}
static void wb_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
//Console.WriteLine(wb.Document.Body.InnerHtml);
completed = true;
Thread.Sleep(1000);
//*******HERE I NEED TO TRAVERSE TO THE HOME PAGE AND GET ITS SOURCE ******
wb.Navigate("http://192.168.0.239/homepage.html");
Console.WriteLine(wb.DocumentText);
}
}
}
I suggest you use selenium - my sample code of using Selenium to login facebook
I'm not gonna code for you but you could copy some part of my code though.
For the login page, I would do a GetElementById on the input for the login, and then set the value attribute. Then for the login button, if it's actually a button, I would trigger the click() event, otherwise if it is a form, then I would do a submit.
For port Status, you would again need to know the html of the home page and do a GetElementById on it and then trigger a click() event.
To put some code to these words it would look something like this:
var portStatus = this.webBrowser1.Document.GetElementById("portStatus");
portStatus.InvokeMember("click");
//this should give you access to DOM of the first frame on the page.
//if you have more than 1 then you will need to know which one.
var frameDoc= this.webBrowser1.Document.Window.Frames[0].Document;
//or
var frameDoc= this.webBrowser1.Document.Window.Frames["iframeid"].Document;;
var login = this.webBrowser1.Document.GetElementById("Login");
login.SetAttribute("Value", "password");
var loginButton = this.webBrowser1.Document.GetElementById("LoginButton");
loginButton.InvokeMember("click");
//or if the login button is a form then submit the form
HtmlElement form = webBrowser1.Document.GetElementById("FormID");
if (form != null)
form.InvokeMember("submit");
Since you didn't provide the HTML of the homepage, The Id's used here would have to be replaced by the actual Ids of the page. And you would replace "password" with whatever your actual password is here.
I hope this helps, if you need some explanation let me know.
Also see this for working with frames
I'm running a Selenium test in C# to open a URL, log in using a supplied username & password, then navigate to a page containing downloadable reports. See my code below (note: website names and usernames/passwords are withheld):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenQA.Selenium;
using OpenQA.Selenium.IE;
using OpenQA.Selenium.Support;
using OpenQA.Selenium.Support.UI;
namespace SeleniumProject
{
class SeleniumTest
{
public static void Main(string[] args)
{
#region Constants
//User Account Information
const string username = "MyUsername";
const string password = "MyPassword";
//Initial Login Page URL and Elements
const string urlLogin = "MyURL";
const string usernameLoginName = "username";
const string passwordLoginName = "password";
const string submitLoginClassName = "btnAlign";
//Welcome Page Element
const string profileWelcomeClassName = "mstrLargeIconViewItemLink";
#endregion
int elementListIndex = 0;
IWebDriver driver = new InternetExplorerDriver();
driver.Manage().Window.Maximize();
driver.Navigate().GoToUrl(urlLogin);
driver.FindElement(By.Name(usernameLoginName)).SendKeys(username);
driver.FindElement(By.Name(passwordLoginName)).SendKeys(password);
driver.FindElement(By.ClassName(submitLoginClassName)).Click();
if (driver.Title == "Servicer Performance Profile Home. MicroStrategy")
{
try
{
driver.FindElement(By.ClassName(profileWelcomeClassName)).Click();
}
catch (NoSuchElementException ex)
{
//failed
}
}
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(15));
wait.Until(ExpectedConditions.VisibilityOfAllElementsLocatedBy(By.XPath("//img[contains(#src,'images/freddiemac/sppdash/navigation-drawer-1.png')]")));
IReadOnlyList<IWebElement> elementList = driver.FindElements(By.XPath("//img[contains(#src,'images/freddiemac/sppdash/navigation-drawer-1.png')]"));
string mainHandle = driver.CurrentWindowHandle;
foreach (var element in elementList)
{
if (element.Displayed && elementListIndex == 5)
{
element.Click();
driver.FindElement(By.XPath("//div[contains(.,'EDR Overview')]")).Click();
break;
}
else
{
elementListIndex++;
}
}
}
}
}
What's happening is whenever I execute that last Click() event that's within the if statement nested inside of the foreach loop, instead of the normal behavior of the link opening a new tab in the same IE, it's opening as a new window and reverting back to a prior page. Normally, whenever I log into this website manually and click this link, a new tab is opened that contains another download link inside of it; that's the page I'm trying to get to.
I have no idea why this new browser window is opening with a prior page instead of even the target page I'm requesting. Could this have something to do with Selenium & IE11 not getting along? Another idea is the current login session expiring somehow, but this is all being executed in less than 10 seconds, so I wouldn't assume this is the issue.
Does anyone have any ideas?
This issue has been resolved. After many, many failed attempts at changing IE settings for handling new tabs, executing JavaScript onclick() events programmatically (instead of using native browser click commands), opening and switching between empty tabs, trying right-click keyboard commands, etc., the issue came down to IE simply not being compatible with what I was trying to do. First attempt with Google Chrome proved to be successful. The website behaved normally and links that were supposed to trigger a new tab indeed triggered the new tab.
My advice to those of you new to Selenium webdriver testing, whether your language is C# or something else, is to avoid Internet Explorer at all costs. Even with IE 11, none of what I was wanting to do worked. Test using Chrome as your first choice. Maybe it will save you three working days of troubleshooting and debugging.
I don't want to use SetForegroundWindow(), sending keyboard keys or similar techniques, because that can cause issues (unexpected behaviour) in my software.
I have tried to find the title using Cheat Engine program (but haven't found anything useful as Google Chrome seems to work "upside-down").
So I went step ahead, using Process Hacker program I have realized that there is a parent (chrome.exe) process with a valid window handle to the current active tab and all other chrome processes are children of it a.k.a. background processes (with invalid window handle).
By browsing deeper into windows of chrome.exe (parent process), I have found the class name of the window handle being "Chrome_WidgetWin_1" and current active tab's title/text.
Here's a picture of Google Chrome's Task Manager.
I'm looking for a function in C# or C or C++ that will take an integer (process ID) and return a string (tab title/text).
static string GetChromeTabTitle(uint processId)
{
// Assuming I call this function with valid process identifier (PID).
// What do I do next, here??
}
The best way I have found is by using the System.Windows.Automation library. It allows interacting with an application (primarily for accessibility purposes), but you can use it for other purposes like getting Chrome tabs.
Note that this will only work when the Chrome windows is not minimized.
The process is not exactly simple, if you want you can look how I did it in my own project, though it's not something you can just copy it paste, you'll find what you need in the ChromeTabsFinder: https://github.com/christianrondeau/GoToWindow/blob/master/GoToWindow.Plugins.ExpandBrowsersTabs/Chrome/ChromeTabsFinder.cs
Here's the code (you'll need the automation librairies):
public IEnumerable<ITab> GetTabsOfWindow(IntPtr hWnd)
{
var cacheRequest = new CacheRequest();
cacheRequest.Add(AutomationElement.NameProperty);
cacheRequest.Add(AutomationElement.LocalizedControlTypeProperty);
cacheRequest.Add(SelectionItemPattern.Pattern);
cacheRequest.Add(SelectionItemPattern.SelectionContainerProperty);
cacheRequest.TreeScope = TreeScope.Element;
AutomationElement tabBarElement;
using (cacheRequest.Activate())
{
var chromeWindow = AutomationElement.FromHandle(hWnd);
var mainElement = chromeWindow.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Google Chrome"));
if (mainElement == null)
yield break;
tabBarElement = mainElement.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.LocalizedControlTypeProperty, "tab"));
}
if(tabBarElement == null)
yield break;
var tabElements = tabBarElement.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.LocalizedControlTypeProperty, "tab item"));
for (var tabIndex = 0; tabIndex < tabElements.Count; tabIndex++)
{
yield return "Tab: " + tabElements[tabIndex].Current.Name + ", Index: " + tabIndex + 1;
}
}
When IE opens a second browser window using onclick.window.open() I need to do some processing on the second window and then close the window.
I have tried the following recomendations found on stackoverflow regarding window handles:
string popupHandle = string.Empty;
ReadOnlyCollection<string> windowHandles = selObj.Driver.WindowHandles;
foreach (string handle in windowHandles)
{
if (handle != originalHandle)
{
popupHandle = handle;
break;
}
}
selObj.Driver.SwitchTo().Window((popupHandle));
selObj.Driver.Close();
selObj.Driver.SwitchTo().Window(originalHandle);
Before I execute this code I attempt to click on a control of the new window to change focus.
Webdriver cannot locate the control (I also tried to use the WebDriverWait class to wait for the second browse to fully load.
WebDriverWait also throws an exception after the wait seconds.
Upon attempting to execute the:
selObj.Driver.SwitchTo().Window((popupHandle));
I receive the following exception:
No response from server for url http://
I know this is C# and IE, wondering if anyone has found a work around for this?
Thanks
My trio C#+WebDriver+Ie works fine.
Yes - IE has to be focused.
Please, Try my code. Code works with IE, Chrome and Firefox
protected void SwitchToWindow(string name)
{
foreach (string item in _driver.WindowHandles)
{
if (_driver.SwitchTo().Window(item).Title.Contains(name))
{
_driver.SwitchTo().Window(item);
break;
}
}
}
Where _driver is selObj.Driver for your case.
A little bit late but can be effective :
var ieOptions = new InternetExplorerOptions
{
IntroduceInstabilityByIgnoringProtectedModeSettings = true
};
ieOptions.ForceCreateProcessApi = true;
ieOptions.BrowserCommandLineArguments = "-private";
driver = new InternetExplorerDriver(IE_DRIVER_PATH, ieOptions);
You have to set ForceCreateProcessApi at true. It's only with IE that you have to set this kind of options.
And if the program threw an other error like :
"This issue occurs when the following registry entry is set to 0:
HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\TabProcGrowth"
You will have to create a key in register (using regedit) and set this key
to 0 (Type: REG_DWORD and Value Type: Decimal).