Beliebte Suchanfragen
|
//

Taking Remote Screenshots With Selenium And The Robot Framework

10.2.2010 | 8 minutes of reading time

.Screenshot are incredibely helpful when doing user interface tests, to get fast feedback why a test has failed. Selenium provides several great opportunities to do exactly that. But, there are many problems related to that at the moment, from which some will discussed, even solved in the following post.

Update (2010-02-15): This post refers to SeleniumLibrary version 2.2. In the meantime, the proposals made to be included for remote screenshots have been included in version 2.3. (Release Notes ). Thanks to the Robot Framework Team!

Our preferred way to integrate Selenium into our automated acceptance tests is the Robot Framework. This enables us to drive tests through all layers of the application, from the database up to the UI. Since it’s automated, this happens daily (or better nightly) or even on every commit. And this is where the first problem occurs. Our CI-Server (Hudson) runs on Linux … but without an XServer. Thus, the selenium server cannot run on the same machine, from with the acceptance tests are triggered. Another reason to run the selenium server on a different host is, that it’s quite difficult to run the Internet Explorer on Linux. A browser that still in wide use, especially in corporate networks, despite the numerous security warnings and rendering issues.

Many screenshot methods that are offered by the selenium server, take the file to save the screenshot to as an argument. This makes it difficult to access that screenshots (scp, fileshares, …). You’ll waste precious time just to access the file, while the analysis and debugging should be as easy as possible. But there’s also two methods in Selenium , that return the Screenshot as String base64 encoded PNG). This looks like a promising path 🙂

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

However, the existing SeleniumLibrary for the Robot Framework does not expose those methods as keyword. So we have to help ourselves, until it will be officially supported (Issue 89 ). So please download the latest Source-Distribution of the Robot SeleniumLibrary 2.2.2 , and edit the file __init__.py. Please excuse my first, amateurish python coding trials:

1import base64 
2 
3...
4 
5    def _absnorm(self, path):
6        return os.path.normpath(os.path.abspath(path.replace('/', os.sep)))
7 
8    def _write_to_file(self, path, content, mode):
9        path = self._absnorm(path)
10        parent = os.path.dirname(path)
11        if not os.path.exists(parent):
12            os.makedirs(parent)
13        f = open(path, mode+'b')
14        f.write(content)
15        f.close()
16        return path
17 
18    def capture_remote_screenshot(self, path=None):
19        """Captures a screenshot and returns it as a string
20 
21        Given path must be relative to Robot Framework output directory,
22        otherwise the embedded image is not shown in the log file. If path is
23        not given, file with name similar to 'selenium-image-x.png' is created
24        directly under the output directory.
25        """
26 
27        # configure path
28        if path and os.path.isabs(path):
29            raise RuntimeError("Given path must be relative to Robot outpudir")
30        if not path:
31            path = self._namegen.next()
32        outdir = NAMESPACES.current.variables['${outputdir}']
33        fullpath = os.path.join(outdir, path)
34        if not os.path.exists(os.path.split(fullpath)[0]):
35            os.makedirs(os.path.split(fullpath)[0])
36 
37        # retrieve remote screenshot
38        self._info("Retrieving Screenshot")
39        screenshot = self._selenium.capture_entire_page_screenshot_to_string("background=#CCFFDD")
40        screenshot=base64.b64decode(screenshot)
41 
42        # save screenshot
43        self._info("Saving screenshot to file '%s'" % fullpath)
44        self._write_to_file(fullpath, screenshot, 'w')
45        self._html('
46<td></td>
47</tr>
48<tr>
49<td colspan="3"><a href="http://blog.codecentric.de/en/2010/02/taking-remote-screenshots-with-selenium-and-the-robot-framework/">'
50                   '<img src="http://blog.codecentric.de/en/2010/02/taking-remote-screenshots-with-selenium-and-the-robot-framework/" width="700px"></a></td>
51</tr>
52' % (path, path))

The functions _absnorm and _write_to_file I borrowed from the OperatingSystem Robot Library (and also slightly modified them). To install the modified Selenium Library, just execute “setup.py install”.

How can the new keyword be used in a Robot Testcase? I find it most helpful to get a browser screenshot after a failing test, to debug the problem quickly. You can use the testcase teardown for that, which invokes the keyword when the test failed:

Testing

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

Now, in every teardown it is checked if the test failed. If this is the case, the keyword “Take Screenshot” will be called. This checks for the correct browser, and only if its not the Internet Explorer, it calls the new Keyword “Capture Remote Screenshot”, which embeds the screenshot in the robot logfile:

In order to get screenshots working with internet explorer, a few hacks were necessary. At first I had to realize, that I have to call the correct selenium method, otherwise the screenshot is completely black (not only for IE, but for all Browsers). If you use the method selenium.capture_screenshot_to_string(), you screenshot will be black. It uses Java to take a screenshot from the entire screen (not only the browser content). This works with all browsers, unless the selenium server runs in the background. We are running the selenium server on a remote desktop, and in order to get non-black screenshots you have to say logged in. An alternative setup was using VNC, but this seemed also unpractical to us (via 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.

The last alternative is, to not capture the entire screen, but only the browser content. This has the advantage, that you will see the complete website, even if it is larger than the screen. This method uses javascript to get hold of the rendered browser content. Problem: Works perfectly with Firefox, but not at all with Internet Explorer. Not yet. This can be fixed — so I thought. The following turned out to be a dead end. If somebody knows how to fix it, I’d be very interested to hear about it.

WIN: Screenshots with IE

This is the only working method, that I could figure out to take Screenshots with the internet explorer. Run the selenium server in “singleWindow” mode. In order to do that, execute:

java -jar selenium-server.jar -singleWindow

Additionally, selenium has to use the browser profile “*iexploreproxy”, that’s why the testcase above filters for all other IE-profiles.

Note: Eventually, you still have to install the latest SnapsIE library (see below). I have not tested, if the above works when I deinstall it again.

Achtung (2010-02-15): SnapsIE 0.2 indeed has to be installed to make screenshots work with IE.

FAIL: Screenshots with IE

This did not work at all. As I said, if you know why, let me know:

  1. Download and install SnapsIE 0.2
  2. Unpack selenium-server-1.0.1.jar and
    • Replace core\lib\snapsie.js with the version from SnapsIE 0.2
    • Edit the file core\scripts\selenium-api.js according to the proposal by Elf in the Selenium-Forum. I extended the changes a little, so here’s the complete new method::
1Selenium.prototype.doCaptureEntirePageScreenshot = function(filename, kwargs) {
2    /**
3     * Saves the entire contents of the current window canvas to a PNG file.
4     * Contrast this with the captureScreenshot command, which captures the
5     * contents of the OS viewport (i.e. whatever is currently being displayed
6     * on the monitor), and is implemented in the RC only. Currently this only
7     * works in Firefox when running in chrome mode, and in IE non-HTA using
8     * the EXPERIMENTAL "Snapsie" utility. The Firefox implementation is mostly
9     * borrowed from the Screengrab! Firefox extension. Please see
10     * http://www.screengrab.org and http://snapsie.sourceforge.net/ for
11     * details.
12     *
13     * @param filename  the path to the file to persist the screenshot as. No
14     *                  filename extension will be appended by default.
15     *                  Directories will not be created if they do not exist,  
16     *                  and an exception will be thrown, possibly by native
17     *                  code.
18     * @param kwargs    a kwargs string that modifies the way the screenshot
19     *                  is captured. Example: "background=#CCFFDD" .
20     *                  Currently valid options:
21     *                  <dl>
22     *                   <dt>background</dt>
23     *                     <dd>the background CSS for the HTML document. This
24     *                     may be useful to set for capturing screenshots of
25     *                     less-than-ideal layouts, for example where absolute
26     *                     positioning causes the calculation of the canvas
27     *                     dimension to fail and a black background is exposed
28     *                     (possibly obscuring black text).</dd>
29     *                  </dl>
30     */
31    if (! browserVersion.isChrome &&
32        ! (browserVersion.isIE && ! browserVersion.isHTA)) {
33        throw new SeleniumError('captureEntirePageScreenshot is only '
34            + 'implemented for Firefox ("firefox" or "chrome", NOT '
35            + '"firefoxproxy") and IE non-HTA ("iexploreproxy", NOT "iexplore" '
36            + 'or "iehta"). The current browser isn\'t one of them!');
37    }
38    // do or do not ... there is no try
39 
40    if (browserVersion.isIE) {
41        // targeting snapsIE >= 0.2
42        function getFailureMessage(exceptionMessage) {
43            var msg = 'Snapsie failed: ';
44            if (exceptionMessage) {
45                if (exceptionMessage ==
46                    "Automation server can't create object") {
47                    msg += 'Is it installed? Does it have permission to run '
48                        + 'as an add-on? See http://snapsie.sourceforge.net/';
49                }
50                else {
51                    msg += exceptionMessage;
52                }
53            }
54            else {
55                msg += 'Undocumented error';
56            }
57            return msg;
58        }
59 
60        if (typeof(runOptions) != 'undefined' &&
61            runOptions.isMultiWindowMode() == false) {
62            // framed mode
63            try {
64                Snapsie.saveSnapshot(filename, 'selenium_myiframe');
65            }
66            catch (e) {
67                throw new SeleniumError(getFailureMessage(e.message));
68            }
69        }
70        else {
71            // multi-window mode
72            if (!this.snapsieSrc) {
73                // XXX - cache snapsie, and capture the screenshot as a
74                // callback. Definitely a hack, because we may be late taking
75                // the first screenshot, but saves us from polluting other code
76                // for now. I wish there were an easier way to get at the
77                // contents of a referenced script!
78                if (/.hta/.exec(snapsieUrl))
79                {
80                    snapsieUrl = "http://localhost:4444/selenium-server/Core/lib/snapsie.js";
81                }
82                var self = this;
83                new Ajax.Request(snapsieUrl, {
84                    method: 'get'
85                    , onSuccess: function(transport) {
86                        self.snapsieSrc = transport.responseText;
87                        self.doCaptureEntirePageScreenshot(filename, kwargs);
88                    }
89                });
90                return;
91            }
92 
93            // it's going into a string, so escape the backslashes
94            filename = filename.replace(/\\/g, '\\\\');
95 
96            // this is sort of hackish. We insert a script into the document,
97            // and remove it before anyone notices.
98            var doc = selenium.browserbot.getDocument();
99            var script = doc.createElement('script'); 
100            var scriptContent = this.snapsieSrc
101                + 'try {'
102                + '    Snapsie.saveSnapshot("' + filename + '");'
103                + '}'
104                + 'catch (e) {'
105                + '    document.getElementById("takeScreenshot").failure ='
106                + '        e.message;'
107                + '}';
108            script.id = 'takeScreenshot';
109            script.language = 'javascript';
110            script.text = scriptContent;
111            doc.body.appendChild(script);
112            script.parentNode.removeChild(script);
113            if (script.failure) {
114                throw new SeleniumError(getFailureMessage(script.failure));
115            }
116        }
117        return;
118    }
|

share post

//

More articles in this subject area

Discover exciting further topics and let the codecentric world inspire you.

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.