.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
Setting Value Library SeleniumLibrary 10 localhost 4444 Library OperatingSystem
Variable Value ${BROWSER} ff
Test Case Action Arguments TestSelenium PASS Open Browser http://www.google.de ${BROWSER} [Teardown] Selenium Teardown TestSelenium FAIL Open Browser http://www.google.de ${BROWSER} Page Should Contain AAAAAAAAAAAAA-IMPOSSIBLETEXT [Teardown] Selenium Teardown
Keyword Action Arguments SeleniumTeardown Run Keyword If Test Failed Take 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:
- Download and install SnapsIE 0.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 }
More articles
fromAndreas Ebbert-Karroum
Your job at codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
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.
Blog author
Andreas Ebbert-Karroum
Agile Principal Consultant
Do you still have questions? Just send me a message.
Do you still have questions? Just send me a message.