Remote Screenshots mit Selenium und dem Robot Framework

5 Kommentare

Screenshots sind bei Oberflächentests sehr hilfreich, um schnell visuelles Feedback zu bekommen, warum ein Test fehlgeschlagen ist. Selenium bietet hierzu einige gute Möglichkeiten. Diese stoßen Momentan allerdings an viele Grenzen, von denen einige hier im Blogpost diskutiert und sogar gelöst werden sollen

Achtung (2010-02-15): Dieser Artikel bezieht sich auf die SeleniumLibrary 2.2. Die weiter unten vorgeschlagene Änderung hat mittlerweile Eingang in die SeleniumLibrary 2.3 gefunden (Release Notes). Vielen Dank an das Robot Framework Team!

Unsere bevorzugte Art und Weise, Selenium in die automatischen Fachtests zu integrieren ist das Robot Framework. Dadurch ist es möglich Tests über alle Schichten zu fahren, von der Datenbank bis hin zur UI. Und das sogar täglich (bzw. nächtlich) oder bei jedem commit. Hier begegnet uns auch schon das erste Problem. Unser CI-Server (Hudson) läuft unter Linux … und ohne XServer. D.h. der Selenium Server kann nicht auf dem Server laufen, von dem aus die automatischen Fachtests angestoßen werden. Ein weiterer Grund, den Selenium-Server woanders laufen zu lassen, ist auch dass es schwierig wird, unter Linux den Internet Explorer zum testen zu benutzen; trotz aller Sicherheitslücken und Wechselempfehlungen ein in Intranets immer noch ein häufig genutzter Browser.

Viele Screenshot-Funktionen des Selenium-Servers nehmen als Argument die Datei, in welche der Screenshot gespeichert werden soll; ungünstig, wenn man dann nur wieder über Umwege (scp, fileshares, …) an die Screenshots kommt. Gerade im Fehlerfall sollte die Analyse ja so einfach, wie möglich sein. Es gibt allerdings auch zwei Funktionen in Selenium, die den Screenshot als String (base64 encodiertes PNG) an den Client zurückgeben. Das scheint doch ein guter Hebel zu sein 🙂

java.lang.String captureEntirePageScreenshotToString(java.lang.String kwargs)
java.lang.String captureScreenshotToString()

Die bestehende SeleniumLibrary für das Robot Framework, stellt allerdings leider die beiden Selenium-Server-Methoden nicht als Keyword bereit, hier heißt es also noch manuell Hand anlegen, bis das offiziell supported wird (Issue 89). Dazu lädt man sich die aktuelle Source-Distribution der Robot SeleniumLibrary 2.2.2 herunter, und editiert die Datei __init__.py. Man verzeihe mir hoffentlich meine stümperhaften Python-Gehversuche:

import base64 
 
...
 
    def _absnorm(self, path):
        return os.path.normpath(os.path.abspath(path.replace('/', os.sep)))
 
    def _write_to_file(self, path, content, mode):
        path = self._absnorm(path)
        parent = os.path.dirname(path)
        if not os.path.exists(parent):
            os.makedirs(parent)
        f = open(path, mode+'b')
        f.write(content)
        f.close()
        return path
 
    def capture_remote_screenshot(self, path=None):
        """Captures a screenshot and returns it as a string
 
        Given path must be relative to Robot Framework output directory,
        otherwise the embedded image is not shown in the log file. If path is
        not given, file with name similar to 'selenium-image-x.png' is created
        directly under the output directory.
        """
 
        # configure path
        if path and os.path.isabs(path):
            raise RuntimeError("Given path must be relative to Robot outpudir")
        if not path:
            path = self._namegen.next()
        outdir = NAMESPACES.current.variables['${outputdir}']
        fullpath = os.path.join(outdir, path)
        if not os.path.exists(os.path.split(fullpath)[0]):
            os.makedirs(os.path.split(fullpath)[0])
 
        # retrieve remote screenshot
        self._info("Retrieving Screenshot")
        screenshot = self._selenium.capture_entire_page_screenshot_to_string("background=#CCFFDD")
        screenshot=base64.b64decode(screenshot)
 
        # save screenshot
        self._info("Saving screenshot to file '%s'" % fullpath)
        self._write_to_file(fullpath, screenshot, 'w')
        self._html('
<td></td>
</tr>
<tr>
<td colspan="3"><a href="http://blog.codecentric.de/2010/02/remote-screenshots-mit-selenium-und-dem-robot-framework/">'                   '</a></td>
</tr>
' % (path, path))

Die Funktionen _absnorm und _write_to_file habe ich mir aus der OperatingSystem Robot Library geborgt (und etwas modifiziert). Um die modifizierte Selenium Library zu installieren reicht ein aufruf von „setup.py install“ auf.

Wie kann man nun das neue Keyword am besten in seinen Robot Testcases benutzen? Ich finde es sehr hilfreich bei einem fehlschlagenden Test einen Screenshot vom Browser zu machen, um schnell sehen zu können, was das Problem ist. Dazu kann das Teardown des Testcases genutzt werden, das im Falle eines Fehlschlags das neue Keyword aufruft:

Testing

SettingValue
LibrarySeleniumLibrary10localhost4444
LibraryOperatingSystem
VariableValue
${BROWSER}ff
Test CaseActionArguments
TestSelenium PASSOpen Browserhttp://www.google.de${BROWSER}
[Teardown]Selenium Teardown
TestSelenium FAILOpen Browserhttp://www.google.de${BROWSER}
Page Should ContainAAAAAAAAAAAAA-IMPOSSIBLETEXT
[Teardown]Selenium Teardown
KeywordActionArguments
SeleniumTeardownRun Keyword If Test FailedTake Screenshot
Close Browser
Take ScreenshotRun Keyword If‚${BROWSER}‘ != ‚*iexplore‘ and ‚${BROWSER}‘ != *’ie‘ and ‚${BROWSER}‘ != ‚*internetexplorer‘ and ‚${BROWSER}‘ != ‚*iehta‘Capture Remote Screenshot

So wird in jedem Teardown geprüft ob der Test fehlgeschlagen ist. Ist dies der Fall wird das Keyword „Take Screenshot“ aufgerufen. Diesen prüft noch den Browser, und nur wenn dieser nicht der Internet Explorer ist, wird das neue Keyword „Capture Remote Screenshot“ aufgerufen, welchen den Screenshot dann auch sofort ins Logfile einbettet:

Um die Screenshots auch noch mit dem IE hinzubekommen waren ein paar Klimmzüge notwendig. Zuerst muss man feststellen, dass man die richtige Methode in selenium aufrufen muss, ansonsten bekommt man nämlich nur einen komplett schwarzen Screenshot. Benutzt man die Methode selenium.capture_screenshot_to_string() bleibt der Screenshot schwarz. Diese Methode benutzt Java um vom kompletten Bildschirm einen Screenshot zu machen (also nicht nur vom Browserinhalt). Das funktioniert zwar auch mit dem Internet Explorer, allerdings überhaupt nicht, wenn der Selenium Server in Hintergrund läuft. Bei uns läuft der Selenium Server auf einem Remote Desktop, und um von ganzen Bildschirm nicht-schwarze Screenshots zu bekommen muss man eingeloggt sein – unpraktisch. Eine Alternative war ein Setup über VNC, was uns aber nicht weniger unpraktisch erschien (von stackoverflow):

What we do is launch everything from under the context of a VNC session. On Windows, configure VNC to launch a session upon startup. Then make sure the user auto-logs in. Then place a .bat file in Program Files->Startup that launches Selenium RC. It’s kind of a pain, but it’s the most reliable way I’ve found for ensuring that Selenium RC starts in an environment that supports screenshots, launching IE, interacting with native events, etc.

Die Alternative ist, nicht den gesamten Bildschirm zu fotografieren, sondern nur den Browserinhalt. Dies hat auch den Vorteil, dass man die komplette Webseite sieht, wenn sie größer als der Bildschirm ist 😉 Diese Methode benutzt Javascript um an den gerenderten Browserinhalt zu kommen. Nun das Problem: Funktioniert wunderbar mit dem Firefox, aber überhaupt nicht mit dem Internet Explorer. Noch nicht. Hier ist was zu tun ist — so dachte ich. Das folgende hat sich als Sackgasse entpuppt, falls da jemand mehr Erfolg hat, als ich wäre ich sehr daran interessiert, wie.

WIN: Screenshots mit dem IE

Die für mich einzige funktionierende Methode, um auch mit dem IE Screenshots zu machen, ist den Selenium Server im „singleWindow“ Modus laufen zu lassen. Dazu startet man diesen folgendermaßen:

java -jar selenium-server.jar -singleWindow

Zudem muss Selenium als Browserprofil „*iexploreproxy“ benutzen, deshalb filtert der Testcase oben auch alle anderen IE-Profile aus.

Achtung: Vielleicht muss man trotzdem die neueste Snapsie-Bibliothek installieren (siehe unten). Ob das funktioniert, wenn ich diese wieder deinstalliere habe ich jetzt nicht getestet.

Achtung (2010-02-15): SnapsIE 0.2 muss auf jeden Fall installiert werden, damit Screenshots auch mit dem IE funktionieren!

FAIL: Screenshots mit dem IE

Das hier hat leider nicht funktioniert. Wie gesagt, wenn jemand weiß warum, bitte melden 🙂

  1. SnapsIE 0.2 herunterladen und installieren
  2. Das selenium-server-1.0.1.jar aus packen und
    • das core\lib\snapsie.js mit der version von SnapsIE 0.2 ersetzen
    • Die Datei core\scripts\selenium-api.js nach dem Vorschlag von Elf aus dem Selenium-Forum abändern. Ich hab die Änderung noch etwas erweitert, deshalb hier die kompette Methode:
Selenium.prototype.doCaptureEntirePageScreenshot = function(filename, kwargs) {
    /**
     * Saves the entire contents of the current window canvas to a PNG file.
     * Contrast this with the captureScreenshot command, which captures the
     * contents of the OS viewport (i.e. whatever is currently being displayed
     * on the monitor), and is implemented in the RC only. Currently this only
     * works in Firefox when running in chrome mode, and in IE non-HTA using
     * the EXPERIMENTAL "Snapsie" utility. The Firefox implementation is mostly
     * borrowed from the Screengrab! Firefox extension. Please see
     * http://www.screengrab.org and http://snapsie.sourceforge.net/ for
     * details.
     *
     * @param filename  the path to the file to persist the screenshot as. No
     *                  filename extension will be appended by default.
     *                  Directories will not be created if they do not exist,  
     *                  and an exception will be thrown, possibly by native
     *                  code.
     * @param kwargs    a kwargs string that modifies the way the screenshot
     *                  is captured. Example: "background=#CCFFDD" .
     *                  Currently valid options:
     *                  <dl>
     *                   <dt>background</dt>
     *                     <dd>the background CSS for the HTML document. This
     *                     may be useful to set for capturing screenshots of
     *                     less-than-ideal layouts, for example where absolute
     *                     positioning causes the calculation of the canvas
     *                     dimension to fail and a black background is exposed
     *                     (possibly obscuring black text).</dd>
     *                  </dl>
     */
    if (! browserVersion.isChrome &&
        ! (browserVersion.isIE && ! browserVersion.isHTA)) {
        throw new SeleniumError('captureEntirePageScreenshot is only '
            + 'implemented for Firefox ("firefox" or "chrome", NOT '
            + '"firefoxproxy") and IE non-HTA ("iexploreproxy", NOT "iexplore" '
            + 'or "iehta"). The current browser isn\'t one of them!');
    }
    // do or do not ... there is no try
 
    if (browserVersion.isIE) {
        // targeting snapsIE >= 0.2
        function getFailureMessage(exceptionMessage) {
            var msg = 'Snapsie failed: ';
            if (exceptionMessage) {
                if (exceptionMessage ==
                    "Automation server can't create object") {
                    msg += 'Is it installed? Does it have permission to run '
                        + 'as an add-on? See http://snapsie.sourceforge.net/';
                }
                else {
                    msg += exceptionMessage;
                }
            }
            else {
                msg += 'Undocumented error';
            }
            return msg;
        }
 
        if (typeof(runOptions) != 'undefined' &&
            runOptions.isMultiWindowMode() == false) {
            // framed mode
            try {
                Snapsie.saveSnapshot(filename, 'selenium_myiframe');
            }
            catch (e) {
                throw new SeleniumError(getFailureMessage(e.message));
            }
        }
        else {
            // multi-window mode
            if (!this.snapsieSrc) {
                // XXX - cache snapsie, and capture the screenshot as a
                // callback. Definitely a hack, because we may be late taking
                // the first screenshot, but saves us from polluting other code
                // for now. I wish there were an easier way to get at the
                // contents of a referenced script!
                if (/.hta/.exec(snapsieUrl))
                {
                    snapsieUrl = "http://localhost:4444/selenium-server/Core/lib/snapsie.js";
                }
                var self = this;
                new Ajax.Request(snapsieUrl, {
                    method: 'get'
                    , onSuccess: function(transport) {
                        self.snapsieSrc = transport.responseText;
                        self.doCaptureEntirePageScreenshot(filename, kwargs);
                    }
                });
                return;
            }
 
            // it's going into a string, so escape the backslashes
            filename = filename.replace(/\\/g, '\\\\');
 
            // this is sort of hackish. We insert a script into the document,
            // and remove it before anyone notices.
            var doc = selenium.browserbot.getDocument();
            var script = doc.createElement('script'); 
            var scriptContent = this.snapsieSrc
				+ 'try {'
                + '    Snapsie.saveSnapshot("' + filename + '");'
                + '}'
                + 'catch (e) {'
                + '    document.getElementById("takeScreenshot").failure ='
                + '        e.message;'
                + '}';
            script.id = 'takeScreenshot';
            script.language = 'javascript';
            script.text = scriptContent;
            doc.body.appendChild(script);
            script.parentNode.removeChild(script);
            if (script.failure) {
                throw new SeleniumError(getFailureMessage(script.failure));
            }
        }
        return;
    }
Andreas Ebbert-Karroum

Andreas Ebbert-Karroum ist Agile Principal Consultant bei codecentric und Product Owner von CenterDevice.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentare

  • Pekka Klärck

    Your problem with non-Firefox browsers was due to the use of capture_entire_page_screenshot_to_string instead of capture_screenshot_to_string. The latter works with all browsers because, AFAIK, it uses JVM functionality to take the screenshot of the entire desktop.

    We’ve now enhanced the original Capture Screenshot keyword to work regardless is the Selenium Server running on the local or remote host [1]. We also decided to implement a separate Capture Page Screenshot [2] but it only works on Firefox due to problems in Selenium you already noticed. All this new functionality will be included in SeleniumLibrary 2.3 [3] that is released on Monday.

    [1] http://code.google.com/p/robotframework-seleniumlibrary/issues/detail?id=89
    [2] http://code.google.com/p/robotframework-seleniumlibrary/issues/detail?id=93
    [3] http://code.google.com/p/robotframework-seleniumlibrary/issues/list?can=1&q=target%3D2.3

  • Andreas Ebbert-Karroum

    Hi Pekka,

    thanks for including this in the upcoming release! I appreciate that!

    The Problem with „capture_screenshot_to_string“ is that you have to be logged in to windows while the selenium server is running. This is a problem for us, since we only connect to the machine via Windows Remote Desktop. As soon as you log out, you will get black screenshots – but you will get black screenshots for all browsers 🙂

  • Amir

    Hallo,
    eine Frage dazu : sind Sie auf das Problem gestoßen, wo z.B. in einer Virtuellen Machine (VM) mit Windows Installation, das Erstellen von Screenshots in Selenium garnicht funktionier, d.h. man bekommt nur schwarze Screenshots zu sehen ???? Falls ja, wie haben Sie es gelöst???

    Also hier ein detailliertes Szenario:
    1) Ich habe Zugriff auf einer Windows-Machine über Remote-Desktop

    2) Ich starte einen Selenium-Junit-Test und schließe die Remote-Desktop-Verbindung.Der Test läuft in der Remote-Machine und dieser Test erstellt zwischenzeitlich paar Screenshots

    3) Nach einer Weile baue ich wieder eine Remote-Desktop-Verbindung auf werte die Screenshots aus => Ergebnis alle gemachten Screenshots sind schwarz

  • Andreas Ebbert-Karroum

    Hallo,

    ja, wie oben beschrieben, haben wir das gleiche Problem:

    Benutzt man die Methode selenium.capture_screenshot_to_string() bleibt der Screenshot schwarz. Diese Methode benutzt Java um vom kompletten Bildschirm einen Screenshot zu machen (also nicht nur vom Browserinhalt). Das funktioniert zwar auch mit dem Internet Explorer, allerdings überhaupt nicht, wenn der Selenium Server in Hintergrund läuft. Bei uns läuft der Selenium Server auf einem Remote Desktop, und um von ganzen Bildschirm nicht-schwarze Screenshots zu bekommen muss man eingeloggt sein – unpraktisch. Eine Alternative war ein Setup über VNC, was uns aber nicht weniger unpraktisch erschien (von stackoverflow):

    Also am besten die Selenium Methode „capture PAGE screenshot“ verwenden, die funktioniert auch auf Remote-Desktops. Da aber auch nur mit dem Firefox zuverlässig, für den IE muss noch das erwähnte Werkzeug SnapsIE installiert werden.

    Andreas

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.