Archiv für den Monat August 2013

Forms.WebBrowser als “Headless Browser” um JavaScript auszuführen


Leider habe ich keinen Headless Browser (Browser ohne GUI) für C# finden können der auch das Ausführen von einfachem, JavaScript unterstützt um z.B. einfache DOM Manipulationen nach dem Laden des Dokuments durchzuführen. Daher habe ich am Ende auf das Control “System.Windows.Forms.WebBrowser” zurückgegriffen.

Aber leichter gesagt als getan, denn die größten Probleme hatte ich hier meinen HTML Text der aus einem String kommt im Browser zu laden.

using WebBrowser = System.Windows.Forms.WebBrowser;

//Unseren Browser initialisieren 
WebBrowser browser = new WebBrowser();
//Unser HTML Dokument laden welches wir im Browser manipulieren wollen
string html = “dein html Dokument <html…”;

//Erst die "Blank" Seite laden
browser.Navigate("about:blank");
//Dann kann man das Dokument auch in unseren Browser laden
browser.Document.Write(html);
//Im HTML Dokument gibt es eine Funktion die "Onload" heißt, diese wird hier aufgerufen
//und diese manipuliert den DOM entsprechend
browser.Document.InvokeScript("OnLoad");
//so kann der Manipulierte DOM entsprechend abgerufen werden.
var manipulatedDom = browser.Document.GetElementsByTagName("html")[0].InnerHtml;

Wie man sieht kann man mit dem Forms.WebBrowser Element ebenfalls einfaches JavaScript ausführen. Leider weiß ich nicht wie es sich verhält wenn man Altert Boxen oder ähnliches verwendet, aber für einfache DOM Manipulationen reicht es auf jeden fall aus, bei mir war jeglicher JavaScript Code direkt im HTML Dokument eingebettet.

Wenn jemand einen guten Headless Browser für .NET kennt der auch mit JavaScript umgehen kann, dann würde ich mich natürlich über einen entsprechenden Link freuen.

Advertisements

Performance “verbessern” von Coded-Ui Tests für Webseiten


Die Möglichkeit auch Weboberflächen automatisiert zu Testen mit Coded-Ui, ist sehr einfach zu realisieren. Denn die Coded-Ui Oberflächentests über den IE sind ähnlich leicht umzusetzen wie Oberflächentests mit Coded-Ui für eine WPF Anwendung. Nur leider ist die Geschwindigkeit wenn man Daten aus der Oberfläche über das “BrowserWindow” Objekt abfragt sehr langsam.

Wenn man z.B. eine Tabelle mit Werten hat, die verglichen werden sollen, dann bleibt einem mit den Coded-Ui Objekten nichts anderes übrig, wie die Tabelle zu ermitteln und dann in der Tabelle z.B. über ein foreach row die einzelnen Zeilen bzw. Zellen abzufragen:

//Tabelle wurde vorher über die Coded-Ui Oberfläche direkt als Element eingebunden
HtmlTable statisticTable = UiStatisticsSummaryTbl();
//z.B. den  Inhalt aus einer Zelle Ermitteln um diesen später zu vergleichen
string text = statisticTable.GetCell(1, 1).InnerText;

Wenn man hier die Tabelle immer erneut oder auch nur einmalig nach bestimmten Werten durchsuchen muss, kann dies eine Beträchtliche Zeit in Anspruch nehmen.

Um dieses Problem zu umgehen, nutze ich für aufwendige inhaltliche Oberflächenüberprüfungen das HtmlAgilityPack und lasse mir den HTML Code des kompletten Browser Fensters zurückgeben und parse/überprüfe die Oberflächenwerte mit der Hilfe von XPath Ausdrücken.

Ermitteln des Aktuellen Browserfensterinhalts:

 /// <summary>
 /// Den aktuellen Browserfensterinhalt als HTMLDocument des Agility Packs zurückgeben.
 /// </summary>
 public static HtmlAgilityPack.HtmlDocument GetHtmlDocument(BrowserWindow window)
 {
     HtmlDocument document = new HtmlDocument(window);
     string text = (string)document.GetProperty("OuterHtml");

     if (!string.IsNullOrEmpty(text))
     {
         HtmlAgilityPack.HtmlDocument doc= new HtmlAgilityPack.HtmlDocument();
         doc.LoadHtml(text);
         return doc;
      }
            return null;
 }

Die Abfragen direkt über das HTML Dokument selbst dauern dann nur noch einen Bruchteil der Zeit wie die Coded-Ui Abfrage eines einzelnen Elements gedauert hat.

//Unser HTML ermitteln aus dem aktuellen Testbrowserfenster
HtmlAgilityPack.HtmlDocument doc = GetHtmlDocument(browserwindow);
HtmlAgilityPack.HtmlDocument node = new HtmlAgilityPack.HtmlDocument();
//Unsere Tabelle anhand der ID ermitteln
node.LoadHtml(doc.GetElementbyId("DefaultStatTSummaryTblId").InnerHtml);
//Alle Rows unserer Tabelle ermitteln (Achtung die Header tr werden auch mit ermittelt)
HtmlAgilityPack.HtmlNodeCollection rows = node.DocumentNode.SelectNodes("//tr");
//den passenden Inhalt ermitteln aus der passenden Zelle
string text = rows[1].SelectNodes(".//td")[1].InnerText;

Mit dieser Methode kann man natürlich nicht alle Oberflächentests durchführen, aber wenn es sich um aufwendige Inhaltsabfragen der Oberfläche handelt, dann kann man damit einiges an Zeit einsparen.