I’m using Selenium for some browser-controlling stuff and mostly use Chrome with the ChromeDriver. But in Version 2.x, ChromeDriver only takes screenshots for the visible area of the screen.

So I created a Script in C# which scrolls around the page, taking screenshots and then stitches it all together.

The code should be easy to port into other languages.

// Get the Total Size of the Document
int totalWidth = (int)EvalScript("return document.width");
int totalHeight = (int)EvalScript("return document.height");

// Get the Size of the Viewport
int viewportWidth = (int)EvalScript("return document.body.clientWidth");
int viewportHeight = (int)EvalScript("return document.body.clientHeight");

// Split the Screen in multiple Rectangles
List rectangles = new List();
// Loop until the Total Height is reached
for (int i = 0; i < totalHeight; i += viewportHeight)
{
	int newHeight = viewportHeight;
	// Fix if the Height of the Element is too big
	if (i + viewportHeight > totalHeight)
	{
		newHeight = totalHeight - i;
	}
	// Loop until the Total Width is reached
	for (int ii = 0; ii < totalWidth; ii += viewportWidth)
	{
		int newWidth = viewportWidth;
		// Fix if the Width of the Element is too big
		if (ii + viewportWidth > totalWidth)
		{
			newWidth = totalWidth - ii;
		}

		// Create and add the Rectangle
		Rectangle currRect = new Rectangle(ii, i, newWidth, newHeight);
		rectangles.Add(currRect);
	}
}

// Build the Image
var stitchedImage = new Bitmap(totalWidth, totalHeight);
// Get all Screenshots and stitch them together
Rectangle previous = Rectangle.Empty;
foreach (var rectangle in rectangles)
{
	// Calculate the Scrolling (if needed)
	if (previous != Rectangle.Empty)
	{
		int xDiff = rectangle.Right - previous.Right;
		int yDiff = rectangle.Bottom - previous.Bottom;
		// Scroll
		RunScript(String.Format("window.scrollBy({0}, {1})", xDiff, yDiff));
		System.Threading.Thread.Sleep(200);
	}

	// Take Screenshot
	var screenshot = ((ITakesScreenshot)_driver).GetScreenshot();

	// Build an Image out of the Screenshot
	Image screenshotImage;
	using (MemoryStream memStream = new MemoryStream(screenshot.AsByteArray))
	{
		screenshotImage = Image.FromStream(memStream);
	}

	// Calculate the Source Rectangle
	Rectangle sourceRectangle = new Rectangle(viewportWidth - rectangle.Width, viewportHeight - rectangle.Height, rectangle.Width, rectangle.Height);

	// Copy the Image
	using (Graphics g = Graphics.FromImage(stitchedImage))
	{
		g.DrawImage(screenshotImage, rectangle, sourceRectangle, GraphicsUnit.Pixel);
	}

	// Set the Previous Rectangle
	previous = rectangle;
}
// The full Screenshot is now in the Variable "stitchedImage"

The EvalScript just executes the Javascript and returns the Value, so it should look like:

public override T EvalScript(string script)
{
	IJavaScriptExecutor js = (IJavaScriptExecutor)_driver;
	return (T)js.ExecuteScript(script);
}

4 Comments

  1. Lavender aroma

    Hi,
    Your post’s so meaningful with me…But

    There is a funtion :

    // Take Screenshot
    var screenshot = ((ITakesScreenshot)_driver).GetScreenshot();
    

    with this function “Selenium is unable to take screenshots when dialog boxes are present.”

    Unfortunately, My Webpage usually use alert to show error message! Now i want to take the screenshots when the alert is showed! How can i do that?

  2. S.Roshanth

    Here is the modification of the method which is work with chrome driver.
    use this method with driver and method call like this :
    private IWebDriver _driver = new ChromeDriver(CHROME_DRIVER_PATH);
    screenshot.SaveAsFile(saveFileName, ImageFormat.Jpeg);

    public Bitmap GetEntereScreenshot()
    {
    	Bitmap stitchedImage = null;
    	try
    	{
    		long totalwidth1 = (long)((IJavaScriptExecutor)_driver).ExecuteScript(“return document.body.offsetWidth”);//documentElement.scrollWidth”);
    		long totalHeight1 = (long)((IJavaScriptExecutor)_driver).ExecuteScript(“return  document.body.parentNode.scrollHeight”);
    		int totalWidth = (int)totalwidth1;
    		int totalHeight = (int)totalHeight1;
    		// Get the Size of the Viewport
    		long viewportWidth1 = (long)((IJavaScriptExecutor)_driver).ExecuteScript(“return document.body.clientWidth”);//documentElement.scrollWidth”);
    		long viewportHeight1 = (long)((IJavaScriptExecutor)_driver).ExecuteScript(“return window.innerHeight”);//documentElement.scrollWidth”);
    		int viewportWidth = (int)viewportWidth1;
    		int viewportHeight = (int)viewportHeight1;
    		// Split the Screen in multiple Rectangles
    		List rectangles = new List();
    		// Loop until the Total Height is reached
    		for (int i = 0; i  totalHeight)
    			{
    				newHeight = totalHeight – i;
    			}
    			// Loop until the Total Width is reached
    			for (int ii = 0; ii  totalWidth)
    				{
    					newWidth = totalWidth – ii;
    				}
    				// Create and add the Rectangle
    				Rectangle currRect = new Rectangle(ii, i, newWidth, newHeight);
    				rectangles.Add(currRect);
    			}
    		}
    		// Build the Image
    		stitchedImage = new Bitmap(totalWidth, totalHeight);
    		// Get all Screenshots and stitch them together
    		Rectangle previous = Rectangle.Empty;
    		foreach (var rectangle in rectangles)
    		{
    			// Calculate the Scrolling (if needed)
    			if (previous != Rectangle.Empty)
    			{
    				int xDiff = rectangle.Right – previous.Right;
    				int yDiff = rectangle.Bottom – previous.Bottom;
    				// Scroll
    				//selenium.RunScript(String.Format(“window.scrollBy({0}, {1})”, xDiff, yDiff));
    				((IJavaScriptExecutor)_driver).ExecuteScript(String.Format(“window.scrollBy({0}, {1})”, xDiff, yDiff));
    				System.Threading.Thread.Sleep(200);
    			}
    			// Take Screenshot
    			var screenshot = ((ITakesScreenshot)_driver).GetScreenshot();
    			// Build an Image out of the Screenshot
    			Image screenshotImage;
    			using (MemoryStream memStream = new MemoryStream(screenshot.AsByteArray))
    			{
    				screenshotImage = Image.FromStream(memStream);
    			}
    			// Calculate the Source Rectangle
    			Rectangle sourceRectangle = new Rectangle(viewportWidth – rectangle.Width, viewportHeight – rectangle.Height, rectangle.Width, rectangle.Height);
    			// Copy the Image
    			using (Graphics g = Graphics.FromImage(stitchedImage))
    			{
    				g.DrawImage(screenshotImage, rectangle, sourceRectangle, GraphicsUnit.Pixel);
    			}
    			// Set the Previous Rectangle
    			previous = rectangle;
    		}
    	}
    	catch (Exception ex)
    	{
    		// handle
    	}
    	return stitchedImage;
    }
    

  3. @S.Roshanth
    There seems to be a problem while pasting your code, some parts are missing. But I think the main changes are that the total width/height and vierport width/height should look like the following:

    // Get the Total Size of the Document
    int totalWidth = (int)EvalScript("return document.body.clientWidth");
    int totalHeight = (int)EvalScript("return document.body.clientHeight");
    
    // Get the Size of the Viewport
    int viewportWidth = (int)EvalScript("return document.documentElement.clientWidth");
    int viewportHeight = (int)EvalScript("return document.documentElement.clientHeight");
    
  4. Sim

    Hi Roemer,

    This is a wonderful post, quite useful as ITakesScreenshot method seems to render only the visible port with some versions of Chrome and Safari.
    I am quite new to C# and struggling with implementing this Code, can you please make this code work for a Sample Site (Say, Yahoo and share the sample working Code) along with the Main Method and Arguments used to Call this method.

    Thanking You
    Sim Rogers

Leave a Reply

Your email address will not be published.

*