16

What is the best way to take screenshot of a web page? At the moment I just start an selenium instance of firefox and using winapi bring it to the front and make a screenshot. I ask similar question already.

There is two points:

  • Slowness.
  • If any window occurently gets higher than our web browser's window, this window will imprint in our screenshot.

Is there any method to take screenshot more 'programmly'?

Here is some code I use now:

class FirefoxDriverEx : FirefoxDriver
{
    public Process GetFirefoxProcess()
    {
        var fi = typeof(FirefoxBinary).GetField("process", BindingFlags.NonPublic | BindingFlags.Instance);
        return fi.GetValue(this.Binary) as Process;
    }
}

Here is the code illustrating process of taking screenshot itself:

using (FirefoxDriverEx driver = new FirefoxDriverEx())
{
    driver.Navigate().GoToUrl(url);

    var process = driver.GetFirefoxProcess();

    if (process != null)
    {
        var screenCapture = new ScreenCapture();
        Win.SetForegroundWindow(process.MainWindowHandle.ToInt32());
    }
}

Right now, I'm thinking about some manager that will control a queue of windows to take the screenshots from.

Question edit.

I'm not looking for a solution to just get screenshot 'in memory' and return it back to HTTP stream. So any ways to save screenshot and save it to file and then get it from there is very ambiguous for that purpose.

Question edit #2.

I forgot to mention. Needed screenshot should be made as it seen by user. So, screenshot should have browser window and a site inside of web browser window's bounds. I can't find any way to change mode of taking a screenshot in WebDriver of selenium. WebDriver just take screenshot of a page without any browser window.

Community
  • 1
  • 1
kseen
  • 359
  • 8
  • 56
  • 104
  • I'd be happy to give this another go, but I must admit that I am not sure what you need. Would you mind summing up what you are seeking again? Maybe just rewording it will help. – chrismead Aug 15 '12 at 21:24

5 Answers5

6

I'd recommend getScreenshotAs. It gets even the 'out of view' part of the screen.

Here is some sample code in gr0ovy.

import java.io.IOException
import java.net.URL
import java.nio.file.Path
import java.nio.file.Paths
import java.text.SimpleDateFormat

import org.openqa.selenium.Capabilities
import org.openqa.selenium.TakesScreenshot
import org.openqa.selenium.WebDriverException
import org.openqa.selenium.remote.CapabilityType
import org.openqa.selenium.remote.DriverCommand
import org.openqa.selenium.remote.RemoteWebDriver
import org.openqa.selenium.OutputType
import org.openqa.selenium.WebDriver



public class Selenium2Screenshot {
private WebDriver driver
private String browserType
private boolean skipScreenshots

public Selenium2Screenshot(WebDriver webDriver, String browserType, boolean skipScreenshots) {
    this.driver = webDriver
    this.browserType = browserType
    this.skipScreenshots = skipScreenshots
}
public void takeScreenshot(String filenameBase) {
    if (!skipScreenshots) {
        Date today
        String formattedDate
        SimpleDateFormat formatter
        Locale currentLocale
        File scrFile
        currentLocale = new Locale("en", "US")
        formatter = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss_SSS", currentLocale)
        today = new Date()
        formattedDate = formatter.format(today)
        String filename = getUiAutomationDir() + filenameBase + "_" + browserType + formattedDate + ".png"
        Log.logger.info("Screenshot filename = " + filename)

        try {
            scrFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE)
            JavaIO.copy(scrFile.getAbsolutePath(), filename)
        } catch (Exception e) {
            Log.logger.error(e.message, e)
        }
    } else {
        Log.logger.info("Skipped Screenshot")
    }
}
private String getUiAutomationDir()
{
    String workingDir = System.getProperty("user.dir")
    Path workingDirPath = Paths.get(workingDir)
    String returnString = workingDirPath.toString() + "\\"
    return returnString
}

}

Edited on 8/1/12:

Get application handle code. I am surely duplicating code that is on stackoverflow several times, but hopefully this is not the exact same code as in other posts :-)

public static IntPtr FindWindowByPartialCaption(String partialCaption)
    {
        var desktop = User32.GetDesktopWindow();
        var children = EnumerateWindows.GetChildWindows(desktop);
        foreach (var intPtr in children)
        {
            var current = GetText(intPtr);
            if (current.Contains(partialCaption))
                return intPtr;
        }
        return IntPtr.Zero;
    }

    [DllImport("user32.dll", EntryPoint = "GetDesktopWindow")]
    public static extern IntPtr GetDesktopWindow();

    [DllImport("user32.dll")]
    public static extern bool EnumChildWindows(IntPtr hWndParent, EnumWindowProc lpEnumFunc, IntPtr lParam);

    public delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);
    public static List<IntPtr> GetChildWindows(IntPtr parent)
    {
        return GetChildWindows(parent, false);
    }
    public static List<IntPtr> GetChildWindows(IntPtr parent, bool reverse)
    {
        List<IntPtr> result = new List<IntPtr>();
        GCHandle listHandle = GCHandle.Alloc(result);
        try
        {
            EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
            EnumChildWindows(parent, childProc, GCHandle.ToIntPtr(listHandle));
        }
        finally
        {
            if (listHandle.IsAllocated)
                listHandle.Free();
        }
        if (reverse)
        {
            List<IntPtr> resultList = result.Reverse<IntPtr>().ToList();
            return resultList;
        } 
        else
            return result;
    }

    private static bool EnumWindow(IntPtr handle, IntPtr pointer)
    {
        GCHandle gch = GCHandle.FromIntPtr(pointer);
        List<IntPtr> list = gch.Target as List<IntPtr>;
        if (list == null)
        {
            throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
        }
        list.Add(handle);
        //  You can modify this to check to see if you want to cancel the operation, then return a null here
        return true;
    }
}

http://www.pinvoke.net/ is also a great resource.

chrismead
  • 2,163
  • 3
  • 24
  • 36
  • I have tried to use this method getScreenshotAs but, unfortunately there is no such method in C# selenium reference. What I'm doing wrong? – kseen Jul 29 '12 at 15:41
  • Ah, I see. Looks like it is a bit different in c#. See if this works for you: http://www.jroller.com/bogie14/entry/selenium_2_screen_capture_c (It still works using the WebDriver interface & the ITakesScreenshot interface, so all browsers should be fine. The groovy version worked for me with IE, FF, & Chrome. I didn't have time to add in other browsers, but you may need to have use the window handle, like in here: http://stackoverflow.com/questions/1163761/c-sharp-capture-screenshot-of-active-window. I can post code to show how to get the browser handle, if you need. – chrismead Jul 29 '12 at 21:01
  • I would be very thankful you if you post code to get browser's handle. Thanks! – kseen Jul 31 '12 at 13:59
  • I'm so sorry, but your provided solution is not that thing I'm searching for. Please see my question update. – kseen Aug 06 '12 at 15:25
  • No sweat. There are other OutputType params that you can use with getScreenshotAs : http://selenium.googlecode.com/svn/trunk/docs/api/java/org/openqa/selenium/OutputType.html – chrismead Aug 06 '12 at 15:27
  • C# binding for Selenium is bit difference than the Java one. So C#'s `GetScreenshot()` method doesn't has arguments at all. And there is not `getScreenshotAs` method in that interface in C#. Please look back to your provided [link](http://www.jroller.com/bogie14/entry/selenium_2_screen_capture_c) – kseen Aug 06 '12 at 15:31
  • Have you checked what GetScreenshot() returns (what are the ScreenShot object attributes, etc)? Or if there are other methods besides, SaveAsFile. I currently don't have selenium in my project or I'd check on it. – chrismead Aug 06 '12 at 15:44
  • I already check that `Screenshot` class. `GetScreenshot()` does produces a screenshot, but it little different I need. Please look on my second question edit. – kseen Aug 06 '12 at 16:09
0

http://msdn.microsoft.com/en-us/library/windows/desktop/dd162869(v=vs.85).aspx

I personally love this API. Create a bitmap with width and height calculated from the returned rectangle of GetWindowRect API and for HDC parameter use (for example):

thebitmap.GetHdc()

You should be fine.

Edit: also check this.

Btw, you can take screenshot of any window you like, even if they fall back.(note that this will not work for minimized windows. However, if you really need, there are some way arounds for that too.)

gunakkoc
  • 1,069
  • 11
  • 30
0

If you're looking for a programmatic way to get a screenshot of the main window of a given process, here is a function that does it:

    public static Bitmap TakeScreenshot(Process process)
    {
        // may need a process Refresh before
        return TakeScreenshot(process.MainWindowHandle);
    }

    public static Bitmap TakeScreenshot(IntPtr handle)
    {
        RECT rc = new RECT();
        GetWindowRect(handle, ref rc);
        Bitmap bitmap = new Bitmap(rc.right - rc.left, rc.bottom - rc.top);
        using (Graphics graphics = Graphics.FromImage(bitmap))
        {
            PrintWindow(handle, graphics.GetHdc(), 0);
        }
        return bitmap;
    }

    [DllImport("user32.dll")]
    private static extern bool GetWindowRect(IntPtr hWnd, ref RECT rect);

    [DllImport("user32.dll")]
    private static extern bool PrintWindow(IntPtr hWnd, IntPtr hDC, int flags);

    [StructLayout(LayoutKind.Sequential)]
    private struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }

Unfortunately, on Aero-equipped OS (Vista/Win7/Win8) it will not capture the full transparent border. The usual transparent border will be blacked instead. Maybe it's enough for what you're trying to acomplish.

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
0

I've been using webshotcmd (the paid version is also command line) in a production app for years. It can be configured to wait for the page to load, to wait n seconds after page load, etc. It uses Internet Explorer and works on Windows. Starts pretty fast (in my experience, the msie activex has always been instant to load).

Other than the above, I would recommend something based on a Webkit libray, it would be so much smaller than Firefox, and would start very fast (wkhtmltoimage is for now only available on Linux, but when it will be available for Windows, I would go for it - also command line). Right now just google for webkit screenshot (the huge number of available screenshotters using webkit makes me believe using that DLL would be easy to port to C#).

Edit: Considering your 2nd edit, take a look at Chrome Screen Capture source. To try it, the extension is available in the store/extension gallery.

oxygen
  • 5,891
  • 6
  • 37
  • 69
0

I was able to accomplish this by copying the window (piece by piece) into a bitmap that is set to the size of the ScrollRectangle for my webBrowser control. While it is certainly not the most elegant way of achieving this goal, I wanted to share the code in case anyone might be able to use it. Once I had something that was mostly working, I was then able to add some args, and I can now execute this utility from the command line:

Executable_Path URL Filename

    /// <summary>
    /// This method is called to start the process of copying the webpage to the bitmap
    /// this should be called after the page has fully loaded (use DocumentCompleted event to determine
    /// if the page has completed loading if calling from the command line.)
    /// </summary>
    private void copyWebpageToImage()
    {
        //these two vars will house the current position in the bmp file (starting at 0,0)
        int currXPosition = 0;
        int currYPosition = 0;

        //we need to set the height and width of our bitmap to the scrollrectangle of the webbrowser document object
        int width = webBrowser1.Document.Body.ScrollRectangle.Width;
        int height = webBrowser1.Document.Body.ScrollRectangle.Height;
        //instantiate the bitmap
        bm = new Bitmap(wd, ht);

        //Instantiate our graphics object
        Graphics gfx = Graphics.FromImage((Image)bm);

        //this point is used throughout the process, and helps to determine where the form is at on the screen
        Point formPoint = Form1.ActiveForm.Location;
        formPoint.X = formPoint.X + webBrowser1.Location.X;
        formPoint.Y = formPoint.Y + webBrowser1.Location.Y;
        formPoint.X = formPoint.X + 8; //offsets for my form (may be different for yours)
        formPoint.Y = formPoint.Y + 33; //offsets for my form

        //begin our recursive call that will stop when it reaches the end of the page
        copyEverythingToBitmap(bm, currXPosition, currYPosition, formPoint, gfx);

    }

    private void copyEverythingToBitmap(Bitmap bm, int currXPosition, int currYPosition, Point formPoint, Graphics gfx)
    {
        //check to see if currXPosition and currYPosition are both 0, if so we just began, call the zero copy method
        if (currXPosition == 0 && currYPosition == 0)
        {
            performZeroCopy(bm, currXPosition, currYPosition, formPoint, gfx);
        }
        //if the current x position is less than the total width of the scrollrectangle - the width of the webbrowser,
        //then we need to scroll the window, and copy the contents, y stays the same
        else if (currXPosition < bm.Width - webBrowser1.Width)
        {
            AlterXPosition(bm, ref currXPosition, ref currYPosition, ref formPoint, gfx);
        }
        //if we are no longer at the zero, zero, and we cannot increase the x position anymore,
        //then we need to scroll the window down and copy the contents, x is reset back to zero
        else if(currYPosition < bm.Height - webBrowser1.Height)
        {
            currYPosition = currYPosition + webBrowser1.Height - 20;
            currXPosition = 0;
            performZeroCopy(bm, currXPosition, currYPosition, formPoint, gfx);
        }
    }

    /// <summary>
    /// The name of this method is slightly misleading.  It inherently means that X is zero.
    /// </summary>
    private void performZeroCopy(Bitmap bm, int currXPosition, int currYPosition, Point formPoint, Graphics gfx)
    {
        webBrowser1.Document.Window.ScrollTo(currXPosition, currYPosition);
        gfx.CopyFromScreen(formPoint, new Point(currXPosition, currYPosition), new Size(webBrowser1.Width - 20, webBrowser1.Height - 20));

        if (currXPosition < bm.Width - webBrowser1.Width)
        {
            AlterXPosition(bm, ref currXPosition, ref currYPosition, ref formPoint, gfx);
        }
        else if(currYPosition < bm.Height - webBrowser1.Height)
        {
            currYPosition = currYPosition + webBrowser1.Height - 20;
            currXPosition = 0;
            performZeroCopy(bm, currXPosition, currYPosition, formPoint, gfx);
        }
    }

    private void AlterXPosition(Bitmap bm, ref int currXPosition, ref int currYPosition, ref Point formPoint, Graphics gfx)
    {
        currXPosition = currXPosition + webBrowser1.Width - 20;
        webBrowser1.Document.Window.ScrollTo(bm.Width - currXPosition, currYPosition);

        gfx.CopyFromScreen(formPoint, new Point(bm.Width - currXPosition - 3, currYPosition), new Size(webBrowser1.Width - 20, webBrowser1.Height - 20));

        if (currXPosition + webBrowser1.Width < bm.Width)
        {
            //we still have not traversed the full width of the page, call to alterxposition again...
        }
        else
        {
            copyEverythingToBitmap(bm, currXPosition, currYPosition, formPoint, gfx);
        }
    }

    private void saveImageToFile(string p)
    {
        bm.Tag = DateTime.Now;
        bm.Save(p, ImageFormat.Jpeg);
    }
John Bartels
  • 2,583
  • 3
  • 19
  • 26