Technorati Tags:,,
In addition to my automated unit, system and integration tests for ASP.NET projects, I like to give my customers something pretty that they can look at and visually see that the web site is behaving properly.
I use the Gallio test runner to produce a pretty HTML report, and WatiN (Web Application Testing In.NET) to test the UI and create screenshots.
I have a couple of issues with WatiN’s “CaptureWebPageToFile” method, though:
- It blew up the first (and only) time I tried it, possibly because…
- It scrolls down to capture the entire web page (I tried it on a very long page), and I usually don’t need that
Also, sometimes I don’t need a picture of the whole browser window - I just want a picture of the element that I'm testing (for example, proving that a button has the correct caption).
I wrote a WatiN screenshot saver helper class with these methods:
- SaveBrowserWindowScreenshot(Watin.Core.IE ie) / SaveBrowserWindowScreenshot(Watin.Core.Element element)
- saves a screenshot of the browser window
- SaveBrowserWindowScreenshotWithHighlight(Watin.Core.Element element)
- saves a screenshot of the browser window, with the specified element scrolled into view and highlighted
- SaveElementScreenshot(Watin.Core.Element element)
- saves a picture of only the specified element
The element highlighting improves on the built-in WatiN method (which just gives the element a yellow background, and makes the element pretty much unreadable when you have a light foreground color) by adding the ability to specify a HighlightCssClassName that points to a style in your site’s stylesheet.
This code is specifically for testing with Internet Explorer (‘cause that’s what I have to test with at work), but you’re welcome to take it and do with it what you want…
using System; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Threading; using SHDocVw; using WatiN.Core; using mshtml;
namespace BrianSchroer.TestHelpers { public static class WatinScreenshotSaver { public static void SaveBrowserWindowScreenshotWithHighlight (Element element, string screenshotName) { HighlightElement(element, true);
SaveBrowserWindowScreenshot(element, screenshotName);
HighlightElement(element, false); }
public static void SaveBrowserWindowScreenshotWithHighlight(Element element) { HighlightElement(element, true);
SaveBrowserWindowScreenshot(element);
HighlightElement(element, false); }
public static void SaveBrowserWindowScreenshot(Element element, string screenshotName) { SaveScreenshot(GetIe(element), screenshotName, SaveBitmapForCallbackArgs); }
public static void SaveBrowserWindowScreenshot(Element element) { SaveScreenshot(GetIe(element), null, SaveBitmapForCallbackArgs); }
public static void SaveBrowserWindowScreenshot(IE ie, string screenshotName) { SaveScreenshot(ie, screenshotName, SaveBitmapForCallbackArgs); }
public static void SaveBrowserWindowScreenshot(IE ie) { SaveScreenshot(ie, null, SaveBitmapForCallbackArgs); }
public static void SaveElementScreenshot(Element element, string screenshotName) { // TODO: Figure out how to get browser window "chrome" size and not have to go to full screen: var iex = (InternetExplorerClass) GetIe(element).InternetExplorer; bool fullScreen = iex.FullScreen; if (!fullScreen) iex.FullScreen = true;
ScrollIntoView(element);
SaveScreenshot(GetIe(element), screenshotName, args => SaveElementBitmapForCallbackArgs(element, args));
iex.FullScreen = fullScreen; }
public static void SaveElementScreenshot(Element element) { SaveElementScreenshot(element, null); }
private static void SaveScreenshot(IE browser, string screenshotName, Action<ScreenshotCallbackArgs> screenshotCallback) { string fileName = string.Format("{0:000}{1}{2}.jpg", ++_screenshotCount, (string.IsNullOrEmpty(screenshotName))? "": " ", screenshotName);
string path = Path.Combine(ScreenshotDirectoryName, fileName);
Console.WriteLine; // Gallio HTML-encodes the following display, but I have a utility program to // remove the "HTML===" and "===HTML" and un-encode the rest to show images in the Gallio report: Console.WriteLine("HTML===
<img src=\"{1}\" />
MakeBrowserWindowTopmost(browser);
try { var args = new ScreenshotCallbackArgs { InternetExplorerClass = (InternetExplorerClass)browser.InternetExplorer, ScreenshotPath = path };
Thread.Sleep(100);
screenshotCallback(args); } catch (Exception ex) { Console.WriteLine(ex.Message); } }
public static void HighlightElement(Element element, bool doHighlight) { if (!element.Exists) return;
if (string.IsNullOrEmpty(HighlightCssClassName)) { element.Highlight(doHighlight); return; }
string jsRef = element.GetJavascriptElementReference; if (string.IsNullOrEmpty(jsRef)) return;
var sb = new StringBuilder("try { ");
sb.AppendFormat(" {0}.scrollIntoView(false);", jsRef);
string format = (doHighlight) ? "{0}.className += ' {1}'" : "{0}.className = {0}.className.replace(' {1}', '')";
sb.AppendFormat(" " + format + ";", jsRef, HighlightCssClassName);
sb.Append("} catch(e) {}");
string script = sb.ToString;
GetIe(element).RunScript(script); }
public static void ScrollIntoView(Element element) { string jsRef = element.GetJavascriptElementReference; if (string.IsNullOrEmpty(jsRef)) return;
var sb = new StringBuilder("try { "); sb.AppendFormat(" {0}.scrollIntoView(false);", jsRef); sb.Append("} catch(e) {}");
string script = sb.ToString;
GetIe(element).RunScript(script); }
public static void MakeBrowserWindowTopmost(IE ie) { ie.BringToFront; SetWindowPos(ie.hWnd, HWND_TOPMOST, 0, 0, 0, 0, TOPMOST_FLAGS); }
public static string HighlightCssClassName { get; set; }
private static int _screenshotCount; private static string _screenshotDirectoryName; public static string ScreenshotDirectoryName { get { if (_screenshotDirectoryName == null) { var asm = Assembly.GetAssembly(typeof(WatinScreenshotSaver)); var uri = new Uri(asm.CodeBase);
var fileInfo = new FileInfo(uri.LocalPath); string directoryName = fileInfo.DirectoryName;
_screenshotDirectoryName = Path.Combine( directoryName, string.Format("Screenshots_{0:yyyyMMddHHmm}", DateTime.Now));
Console.WriteLine("Screenshot folder: {0}", _screenshotDirectoryName);
Directory.CreateDirectory(_screenshotDirectoryName); }
return _screenshotDirectoryName; }
set { _screenshotDirectoryName = value; _screenshotCount = 0; } }
[DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); private const UInt32 SWP_NOSIZE = 0x0001; private const UInt32 SWP_NOMOVE = 0x0002; private const UInt32 TOPMOST_FLAGS = SWP_NOMOVE | SWP_NOSIZE;
private static IE GetIe(Element element) { if (element == null) return null;
var container = element.DomContainer;
while (container as IE == null) container = container.DomContainer;
return (IE)container; }
private static void SaveBitmapForCallbackArgs(ScreenshotCallbackArgs args) { InternetExplorerClass iex = args.InternetExplorerClass;
SaveBitmap(args.ScreenshotPath, iex.Left, iex.Top, iex.Width, iex.Height); }
private static void SaveElementBitmapForCallbackArgs(Element element, ScreenshotCallbackArgs args) { InternetExplorerClass iex = args.InternetExplorerClass;
Rectangle bounds = GetElementBounds(element);
SaveBitmap(args.ScreenshotPath, iex.Left + bounds.Left, iex.Top + bounds.Top, bounds.Width, bounds.Height); }
///
int left = elem.offsetLeft; int top = elem.offsetTop;
for (IHTMLElement parent = elem.offsetParent; parent!= null; parent = parent.offsetParent) { left += parent.offsetLeft; top += parent.offsetTop; }
return new Rectangle(left, top, elem.offsetWidth, elem.offsetHeight); }
private static void SaveBitmap(string path, int left, int top, int width, int height) { using (var bitmap = new Bitmap(width, height)) { using (Graphics g = Graphics.FromImage(bitmap)) { g.CopyFromScreen( new Point(left, top), Point.Empty, new Size(width, height) ); }
bitmap.Save(path, ImageFormat.Jpeg); } }
private class ScreenshotCallbackArgs { public InternetExplorerClass InternetExplorerClass { get; set; } public string ScreenshotPath { get; set; } } } }
