Take screenshots of websites for different screen sizes






4.95/5 (26 votes)
How to take screenshots of websites and emulate different devices and screen sizes in C#.
Introduction
A common requirement for modern applications is to take a screenshot of a website after a user has entered an URL. There's 2 principal use cases:
- the client wants to take a screenshot and save the image for later comparison (think web testing or approval of content before publication)
- the client wants to show the website image next to a link (think portfolio)
At first this may sound like a simple requirement, but given that nowadays we have multiple devices (desktops, tablets, phones...) and that a well-respected (i.e. responsive) website will look different on each of them, there is an additional level of complexity.
I've therefore decided to write a C# library to answer this problem. I've published the source code on GitHub and you can also download and install the NuGet package Web.Screenshot from nuget.org.
In this article, I'm first going to go through the library API, so you know how to use it. Then, I'll go through the code to explain its implementation.
Using Web.Screenshot NuGet package
Installing Web.Screenshot NuGet package
To fastest way to see what Web.Screenshot has to offer is to create a new Console project in Visual Studio. Click on File > New > Project and then select Console Application:
Then, right click on References and click on Manage NuGet Packages...
Search for Web.Screenshot and select the package:
Then click Install.
Once the installation is completed, you should see a new assembly listed in references called: Alx.Web.Screenshot.
Using Web.Screenshot API
The main class is a static class called Screenshot
which you can find in Alx.Web
namespace. It offers 2 main methods (with some overrides):
Take()
: takes a screenshot of a website and return anImage
resultSave()
: saves the website screenshot as an image file on the disk
Here are the overrides available:
public static Image Take(string url, Devices device = Devices.Desktop)
public static Image Take(string url, Size size)
public static void Save(string url, string path, ImageFormat format, Devices device = Devices.Desktop)
public static void Save(string url, string path, ImageFormat format, Size size)
The Image
, Size
and ImageFormat
classes are the built-in .NET classes available from System.Drawing
.
Devices
is an enum
defined in Alx.Web
and its values are: PhonePortrait, PhoneLandscape, TabletPortrait, TabletLandscape, Desktop
.
Let's write some code now. In programme.cs, update the main method to the following code:
static void Main(string[] args)
{
Console.WriteLine("Application started");
const string url = "http://www.bbc.co.uk/news";
var device = Devices.Desktop;
var path = String.Format(@"C:\Temp\website-{0}.png", device);
Alx.Web.Screenshot.Save(url, path, ImageFormat.Png, device);
Console.WriteLine("Saved " + path);
device = Devices.TabletLandscape;
path = String.Format(@"C:\Temp\website-{0}.png", device);
Alx.Web.Screenshot.Save(url, path, ImageFormat.Png, device);
Console.WriteLine("Saved " + path);
device = Devices.PhonePortrait;
path = String.Format(@"C:\Temp\website-{0}.png", device);
Alx.Web.Screenshot.Save(url, path, ImageFormat.Png, device);
Console.WriteLine("Saved " + path);
Console.WriteLine("Press [Enter] to exit...");
Console.ReadLine();
}
The code is pretty much self-explanatory: it will save 3 screenshots of the BBC news website in 3 different formats: Desktop, Tablet (landscape) and Phone (portrait). The files will be saved in C:\Temp
, but you may want to use a different folder. Here I save the files using the PNG format, but you could use JPG or any other image format defined in System.Drawing
.
Here's the front page for today (21st Aug 2015):
Desktop
Tablet
Phone
Using Web.Screenshot
, it should now be easy to take screenshots of any websites.
If you are interested in how Web.Screenshot is actually implemented, then read on. Web.Screenshot
's source code is also publicly available on GitHub, so feel free to contribute and/or branch to add more functionality.
Web.Screenshot implementation
Now, let's look at Web.Screenshot
source code. As already mentionned, you can download Web.Screenshot code from GitHub.
Let's start with the easy one: Devices.cs
public enum Devices
{
[Size(375, 667)] // Screen size for iPhone 6
PhonePortrait,
[Size(667, 375)] // Screen size for iPhone 6
PhoneLandscape,
[Size(600, 960)] // Screen size for Google Nexus 7
TabletPortrait,
[Size(960, 600)] // Screen size for Google Nexus 7
TabletLandscape,
[Size(1920, 1080)] // Screen size for Dell XPS12
Desktop
}
Quite self-explanatory again: there's 5 values, for 3 different devices, some of them in 2 different positions (landscape and portrait).
You will notice the Size
attribute which specifies the width and the height of the screen for each value.
Here is the code for SizeAttribute.cs:
public class SizeAttribute : Attribute
{
public int Width { get; set; }
public int Height { get; set; }
public Size Size
{
get { return new Size(Width, Height); }
}
public SizeAttribute(int width, int height)
{
Width = width;
Height = height;
}
}
I've created an extension method to read Enum
's attributes. It's in EnumExtensions.cs:
public static class EnumHelper
{
public static T GetAttribute<T>(this Enum enumVal) where T : Attribute
{
var type = enumVal.GetType();
var memInfo = type.GetMember(enumVal.ToString());
var attributes = memInfo[0].GetCustomAttributes(typeof(T), false);
return (attributes.Length > 0) ? (T)attributes[0] : null;
}
}
And the static Screenshot
class has this method to read the screen size from the Enum
value:
public static Size GetSize(Devices device)
{
var attribute = device.GetAttribute<SizeAttribute>();
if (attribute == null)
{
throw new DeviceSizeException(device);
}
return attribute.Size;
}
Now that we've got that in place, let's take a look at the Take()
methods. Remember, the Take()
methods takes the website screenshot and return a System.Drawing.Image
object. The code is also in Screenshot.cs:
public static Image Take(string url, Devices device = Devices.Desktop)
{
var size = GetSize(device);
return Take(url, size);
}
public static Image Take(string url, Size size)
{
var result = Capture(url, size);
return result;
}
Nothing too complicate so far. Both methods will end up calling Capture()
with 2 arguments: the website url and the device screen's size. Now, let's take a look at the Capture()
method, this is where the fun begins:
private static Image Capture(string url, Size size)
{
Image result = new Bitmap(size.Width, size.Height);
var thread = new Thread(() =>
{
using (var browser = new WebBrowser())
{
browser.ScrollBarsEnabled = false;
browser.AllowNavigation = true;
browser.Navigate(url);
browser.Width = size.Width;
browser.Height = size.Height;
browser.ScriptErrorsSuppressed = true;
browser.DocumentCompleted += (sender,args) => DocumentCompleted(sender, args, ref result);
while (browser.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
return result;
}
First thing first: WebBrowser
controls cannot be instantiated if the current thread is not in a
single-threaded apartment. To be sure to run in a single-threaded apartment, we create a new thread and we set the apartment state to ApartmentState.STA
.
We set a few properties on the browser control:
- to disable scroll bars
- to set the width and the height
- to suppress scripts errors
We call Navigate(url)
to navigate to the requested URL.
We set the DocumentCompleted
event, which will be fired when the page is loaded and the document is ready.
Then we enter the while
loop and process events until the browser state is set to Complete
.
Finally, we start the thread (thread.Start()
), we wait for it to complete (thread.Join()
) and we return the result (of type Image
). The thread will complete when the browser state is set to Complete
. The DocumentCompleted event would be fired just before. Here's the code for the event handler:
private static void DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e, ref Image image)
{
var browser = sender as WebBrowser;
if (browser == null) throw new Exception("Sender should be browser");
if (browser.Document == null) throw new Exception("Document is missing");
if (browser.Document.Body == null) throw new Exception("Body is missing");
using (var bitmap = new Bitmap(browser.Width, browser.Height))
{
browser.DrawToBitmap(bitmap, new Rectangle(0, 0, browser.Width, browser.Height));
image = (Image)bitmap.Clone();
}
}
First, we perform a few sanity checks, then we create a new Bitmap
of the same size as the web browser. We call browser.DrawToBitmap()
to copy the content of the browser as an image into the bitmap
variable. Then, we set the image
variable (which was passed by reference) with a clone of the bitmap (because bitmap will be disposed after we exit the using block, and therefore wouldn't be available to the calling method).
That's about it. Thanks to the WebControl
.NET class, just a few lines of code were sufficient to write this library.
Now that we've managed to take a screenshot of a website and stored it into an Image
object, we can take a look at the Save()
methods:
public static void Save(string url, string path, ImageFormat format, Devices device = Devices.Desktop)
{
var size = GetSize(device);
Save(url, path, format, size);
}
public static void Save(string url, string path, ImageFormat format, Size size)
{
var image = Take(url, size);
using (var stream = new MemoryStream())
{
image.Save(stream, format);
var bytes = stream.ToArray();
File.WriteAllBytes(path, bytes);
}
}
The Save()
methods simply call the Take()
method and saves the Image
object to the disk in the requested image format.
Points of Interest
If you too need to take screenshots of websites, then I hope you'll find this library useful. I hope you'll also find the possibility to take screenshots for different devices screen sizes interesting.
From a code point of view, and depending on your experience with the .NET framework, you may find the following points interesting:
- The use of
WebControl
outside of a Windows Forms Application - The creation of a new thread in a single-threaded apartment
- The use of custom attribute on
Enum
values - An example of extension methods
- How to save an
Image
object as an image file on the disk
Web.Screenshot
is available as a Nuget package from nuget.org.
Web.Screenshot
source code is available on GitHub. Feel free to branch and contribute to add more features.
Also, feel free to submit any issues and/or suggestions.
Alternative Solutions
Selenium WebDriver
Another approach I've investigated before creating this library is to use Selenium.WebDriver.
Selenium.WebDriver
offers a lot more than simply taking screenshots of websites. Once the page is loaded, it can navigate to another URL by simulating a click on a link. It can also read content of the page, as well as set content, which can be very useful for completing forms. Also, it can emulate different browsers by creating different drivers, for instance Firefox driver or Chrome driver.
PhantomJS
PhantomJS is another library which I've investigated. PhantomJS
is a headless browser and its functionality are very similar to those of Selenium.WebDriver. PhantomJS can be scripted with JavaScript. One of its down side though is its heavy weight: 47 MB.
So why not using them?
The reason why I've decided to create Web.Screenshot
is that I've found both Selenium.WebDriver and PhantomJS to be unreliable when it comes to internal website (intranet) using Windows Authentication with Kerboros. In fact, from my reading and from my understanding I believe PhantomJS does not support Windows Authentication at all (the authentication header is missing from the request, which results in access denied error messages).
If you've managed to get PhantomJS working with Windows Authentication (Kerboros), please let me know as I'd like to understand what setting was missing.
Having said that, I've used both Selenium.WebDriver and PhantomJS for testing on other projects and they worked like a charm!
History
- [22/08/2015]: first version