C Sharp - test and automate website with Webbrowser without extra libraries (Selenium, Watin)

If you want to test or automate something on any webpage you can do it with .Net built-in component WebBrowser. Simple scenarios - you don't need extra libraries (Selenium, Watin)


1) Search something on Bing

a) in Visual Studio add new Windows Form Application in C sharp


b) double click on WebBrowser in toolbox









c) double click DocumentCompleted in WebBrowser properties to add event




Source code:

public partial class Form1 : Form
    {
        bool linksReady = false;
        public Form1()
        {
            InitializeComponent();
            webBrowser1.Navigate("bing.com");
        }

        private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            if (linksReady)
                return;

            Search("awesome blog");
            DisplayResults();

        }

        private void DisplayResults()
        {
            var links = webBrowser1.Document.GetElementsByTagName("li").OfType<HtmlElement>().Where(x => x.GetAttribute("className") == "b_algo").ToList();
            if (links.Count > 0)
            {
                linksReady = true;
                string text = string.Join(Environment.NewLine, links.Select(x => x.GetElementsByTagName("a")[0].GetAttribute("href")));
                MessageBox.Show(text);
            }
        }

        private void Search(string keyword)
        {
            var searchTb = webBrowser1.Document.GetElementById("sb_form_q");
            if (searchTb != null)
            {
                searchTb.SetAttribute("value", keyword);
                var searchBtn = webBrowser1.Document.GetElementById("sb_form_go");
                searchBtn.InvokeMember("click");
            }
        }
    }

I think it is quite simple. It puts the keyword in search textbox, then presses search button and reads results from li.b_algo a. Firebug or chrome developer tools are useful here to check ids and classes of these elements.

Result:




2) Validate page http://producent-karniszy.pl/

a) read categories from left menu
b) read products from each category
c) open each product page and change and check pricess


public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            GoToPage("http://producent-karniszy.pl", mainPage_DocumentCompleted);
        }

        private void GoToPage(string url, WebBrowserDocumentCompletedEventHandler handler)
        {
            WebBrowser webBrowser = new WebBrowser();
            webBrowser.Tag = handler;
            webBrowser.Size = new Size(1200, 800);
            webBrowser.ScriptErrorsSuppressed = true;
            webBrowser.CausesValidation = false;
            webBrowser.DocumentCompleted += handler;
            webBrowser.Navigate(url);
        }

        void mainPage_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            WebBrowser webBrowser = sender as WebBrowser;
            webBrowser.DocumentCompleted -= webBrowser.Tag as WebBrowserDocumentCompletedEventHandler;
            List<HtmlElement> links = webBrowser.Document.GetElementsByTagName("a").OfType<HtmlElement>().ToList();
            List<String> filteredLinks = links.Where(x => x.InnerText != null && (x.InnerText.Contains("Pojedyncze") || x.InnerText.Contains("Podwójne"))).Select(x => x.GetAttribute("href")).Distinct().ToList();
            loadCategoriesPages(filteredLinks); 
        }

        void loadCategoriesPages(List<string> links)
        {
            foreach (string link in links)
            {
                GoToPage(link, category_DocumentCompleted);
            }
        }

        void category_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            WebBrowser webBrowser = sender as WebBrowser;
            webBrowser.DocumentCompleted -= webBrowser.Tag as WebBrowserDocumentCompletedEventHandler;

            List<HtmlElement> links = webBrowser.Document.GetElementsByTagName("a").OfType<HtmlElement>().ToList();

            List<String> filteredLinks = links.Select(x => x.GetAttribute("href")).Where(x => x.Contains("karnisz-metalowy")).Distinct().ToList();
            List<String> randomLinks = filteredLinks.OrderBy(x => Guid.NewGuid()).Take(6).ToList(); // random
            loadProductsPages(randomLinks); 
            webBrowser.Navigate("");
            webBrowser.Dispose();
        }

        void loadProductsPages(List<string> links)
        {
            foreach (string link in links)
            {
                GoToPage(link, product_DocumentCompleted);
            }
        }

        void product_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            WebBrowser webBrowser = sender as WebBrowser;
            webBrowser.DocumentCompleted -= webBrowser.Tag as WebBrowserDocumentCompletedEventHandler;

            decimal price1 = getPrice(webBrowser);
            ExecuteJs(webBrowser, "$('#1').val('19').change();");
            decimal price2 = getPrice(webBrowser);
            ExecuteJs(webBrowser, "$('#3').val('34').change();");
            decimal price3 = getPrice(webBrowser);
            ExecuteJs(webBrowser, "$('#1').val('20').change();");
            decimal price4 = getPrice(webBrowser);
            if (price1 >= price2 || price2 != price3 || price3 >= price4)
            {
                tbOutput.Text += string.Format("invalid prices {1} {2} {3} {4} - {0}", webBrowser.Url, price1, price2, price3, price4) + Environment.NewLine;
            }
            else
            {
                tbOutput.Text += string.Format("valid prices {1} {2} {3} {4} - {0} ", webBrowser.Url, price1, price2, price3, price4) + Environment.NewLine;
            }
            webBrowser.Navigate("");
            webBrowser.Dispose();
        }

        private void ExecuteJs(WebBrowser webBrowser, string js)
        {
            HtmlDocument doc = webBrowser.Document;
            HtmlElement head = doc.GetElementsByTagName("head")[0];
            HtmlElement s = doc.CreateElement("script");
            s.SetAttribute("text", "function customFunction() { " + js + " }");
            head.AppendChild(s);
            webBrowser.Document.InvokeScript("customFunction");
        }

        private decimal getPrice(WebBrowser webBrowser)
        {
            string value = "";
            try
            {
                value = webBrowser.Document.GetElementById("changeprice").InnerText;
                return decimal.Parse(value.Replace("PLN", "").Replace(".", ","), System.Globalization.NumberStyles.Currency);
            }
            catch (Exception ex)
            {
                tbOutput.Text += string.Format("invalid price {1} {2} - {0}", webBrowser.Url, value, ex.Message + Environment.NewLine);
                return 0;
            }
        } 
 
 }

It is little more complicated. It creates WebBrowser for each page. I change dropdown value with jquery ($('#1').val('19').change()) - this is the simplest solution for me.

Result





Important things about WebBrowser
-  DocumentCompleted can be called many times for one page for example if there are iframes.  You can add bool flag like in my first example.

-  even if DocumentCompleted is called, it doesn't mean full page is loaded. For example javascript which add elements dynamically. You can add Timer and check if your element is already available.
- There is no method getElementByClassName but you can do:
webBrowser1.Document.GetElementsByTagName("li").OfType<HtmlElement>().Where(x => x.GetAttribute("className") == "b_algo")
- call webBrowser.Dispose() if you load many pages or you will have OutOfMemory Error