Full page mobile device screenshot via BrowserStack

Hi all,

Hoping some of the experts here can help me before I lose my last bit of sanity on this problem. It’s been driving me mad for a couple of weeks now.

Basically, I’ve created a tool to take full-page screenshots on a list of URLs on a number of desktop and device configs. Desktop, this all works swimmingly and I get full-page screenshots back with no issue.

The problem comes when trying to do this on mobile devices, due to the pixel ratio of the devices I’m running on.

My method takes the total height and width of the page, as well as the height and width of the viewport, and basically takes images of the viewport, scrolls where necessary, then at the end, stitches all this together in to a single image.

However, with the pixel ratio, which I’m retrieving via JavaScript, even when using a 3.0 pixel ratio on an iPhone 12 for example, the screenshots are never captured properly and don’t scroll correctly and/or don’t capture the entirety of the page. Widthwise it’s ok, it’s always height.

The code for this method is as follows:

public Image GetEntireScreenshot(ArrayList hiddenElementListAfterFirstViewport = null, bool isMobile = false)
            var pixelRatio = 1; 
            if (isMobile) pixelRatio = (int) (long) ((IJavaScriptExecutor) driver).ExecuteScript("return window.devicePixelRatio");

            // Size of page
            var totalWidth = Convert.ToInt32((int)(long)((IJavaScriptExecutor)driver).ExecuteScript("return document.body.offsetWidth") * pixelRatio);
            var totalHeight = Convert.ToInt32((int)(long)((IJavaScriptExecutor)driver).ExecuteScript("return  document.body.parentNode.scrollHeight") * pixelRatio);

            // Size of the viewport
            var viewportWidth = Convert.ToInt32((int)(long)((IJavaScriptExecutor)driver).ExecuteScript("return document.body.clientWidth") * pixelRatio);
            var viewportHeight = Convert.ToInt32((int)(long)((IJavaScriptExecutor)driver).ExecuteScript("return window.innerHeight") * pixelRatio);

            var screenshot = (ITakesScreenshot)driver;
            ((IJavaScriptExecutor)driver).ExecuteScript("window.scrollTo(0, 0)");

            if (hiddenElementListAfterFirstViewport != null)
                foreach (string hiddenElementXPath in hiddenElementListAfterFirstViewport)
                    IList<IWebElement> elements = driver.FindElements(By.XPath(hiddenElementXPath));
                    foreach (var element in elements)
                        ((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].style.visibility='hidden'", element);

            if (totalWidth <= viewportWidth && totalHeight <= viewportHeight) return ScreenshotToImage(screenshot.GetScreenshot());

            var rectangles = new List<Rectangle>();

            // Loop until the totalHeight is reached
            for (var y = 0; y < totalHeight; y += viewportHeight)
                var newHeight = viewportHeight;

                // Fix if the height of the element is too big
                if (y + viewportHeight > totalHeight) newHeight = totalHeight - y;

                // Loop until the totalWidth is reached
                for (var x = 0; x < totalWidth; x += viewportWidth)
                    var newWidth = viewportWidth;
                    // Fix if the Width of the Element is too big
                    if (x + viewportWidth > totalWidth) newWidth = totalWidth - x;

                    // Create and add the Rectangle
                    rectangles.Add(new Rectangle(x, y, newWidth, newHeight));

            var stitchedImage = new Bitmap(totalWidth, totalHeight);
            var previous = Rectangle.Empty;
            foreach (var rectangle in rectangles)
                // Calculate scrolling (if needed)
                if (previous != Rectangle.Empty) ((IJavaScriptExecutor) driver).ExecuteScript($"window.scrollBy({rectangle.Right - previous.Right}, {rectangle.Bottom - previous.Bottom})");

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

                // Copy the Image
                using (var graphics = Graphics.FromImage(stitchedImage))
                    graphics.DrawImage(ScreenshotToImage(screenshot.GetScreenshot()), rectangle, sourceRectangle, GraphicsUnit.Pixel);

                previous = rectangle;
            return stitchedImage;

I’m staggered by the fact that I can’t find any posts anywhere online with people who have had the same or similar issues. So either I’m over complicating it and it’s so trivial that it’s never required a post, or this is way harder than I think and people aren’t doing it for good reason

If anyone has any thoughts or ideas, they would be very much appreciated


Normally you want to ask about this kind of thing on the browserstack forums or on the QA stackoverflow https://sqa.stackexchange.com/ . I don’t know Browserstack at all, I only suspect it merely glues the Selenium stack more seamlessly to the management tools, but I’m seeing that your issue is not with Browserstack at all, but more a problem with screen size variations. It might be feeling like an “evil problem”, but let’s not go there yet.

So I’m a bit confused about your test “use-case” Toby. Are you taking screenshots for purposes of marketing, or for debugging test failures? And what kinds of test failures are you wanting to debug? If you are scrolling in your mobile app, it sounds like you really have a “non-mobile-friendly” app and are wanting to test it on a mobile. Which may explain why nobody has asked about this kind of thing in “google-land” very often.

If you are in fact doing this to make debugging a test easier (or for marketing), I suggest doing slightly overlapping screenshots and using a “stitching” library. I don’t assume things are ever trivial, generally it’s the hard things that never get discussed. As ever, don’t write code just because you can, because we know that in code we can do anything that we can imagine. Only write code if doing so gives you real tangible value.