隐藏

记录:c#中使用Selenium之二 整页截图

发布:2020/9/24 14:45:32作者:管理员 来源:本站 浏览次数:1102

1. 上文中介绍了怎么使用selenium及设置手机模式浏览,但发现只能截取屏幕部分图片,而之前网页端用webbrowser截图还可以,但手机端的话因为webbrowser是ie内核,导致页面样式杂乱。现改用selenium下面介绍整理的几种方式

    1.) selenium有默认的较为简单截图方式,只能截图屏幕区域,可满足简单的需求。


	
  1. //截图
  2. Screenshot screenShotFile = ((ITakesScreenshot)driver).GetScreenshot();
  3. string img_url = Environment.CurrentDirectory + @"\\test.jpg";
  4. screenShotFile.SaveAsFile(img_url, OpenQA.Selenium.ScreenshotImageFormat.Jpeg);

    2.) 创建继承ChromeDriver的类,执行js获取参数


	
  1. public class ChromeDriverEx : ChromeDriver
  2. {
  3. private const string SendChromeCommandWithResult = "sendChromeCommandWithResponse";
  4. private const string SendChromeCommandWithResultUrlTemplate = "/session/{sessionId}/chromium/send_command_and_get_result";
  5. public ChromeDriverEx(string chromeDriverDirectory, ChromeOptions options)
  6. : base(chromeDriverDirectory, options)
  7. {
  8. CommandInfo commandInfoToAdd = new CommandInfo(CommandInfo.PostCommand, SendChromeCommandWithResultUrlTemplate);
  9. this.CommandExecutor.CommandInfoRepository.TryAddCommand(SendChromeCommandWithResult, commandInfoToAdd);
  10. }
  11. public ChromeDriverEx(ChromeDriverService service, ChromeOptions options)
  12. : base(service, options)
  13. {
  14. CommandInfo commandInfoToAdd = new CommandInfo(CommandInfo.PostCommand, SendChromeCommandWithResultUrlTemplate);
  15. this.CommandExecutor.CommandInfoRepository.TryAddCommand(SendChromeCommandWithResult, commandInfoToAdd);
  16. }
  17. public Screenshot GetFullPageScreenshot()
  18. {
  19. string metricsScript = @"({
  20. width: Math.max(window.innerWidth,document.body.scrollWidth,document.documentElement.scrollWidth)|0,
  21. height: Math.max(window.innerHeight,document.body.scrollHeight,document.documentElement.scrollHeight)|0,
  22. deviceScaleFactor: window.devicePixelRatio || 1,
  23. mobile: typeof window.orientation !== 'undefined'
  24. })";
  25. Dictionary<string, object> metrics = this.EvaluateDevToolsScript(metricsScript);
  26. this.ExecuteChromeCommand("Emulation.setDeviceMetricsOverride", metrics);
  27. Dictionary<string, object> parameters = new Dictionary<string, object>();
  28. parameters["format"] = "png";
  29. parameters["fromSurface"] = true;
  30. object screenshotObject = this.ExecuteChromeCommandWithResult("Page.captureScreenshot", parameters);
  31. Dictionary<string, object> screenshotResult = screenshotObject as Dictionary<string, object>;
  32. string screenshotData = screenshotResult["data"] as string;
  33. this.ExecuteChromeCommand("Emulation.clearDeviceMetricsOverride", new Dictionary<string, object>());
  34. Screenshot screenshot = new Screenshot(screenshotData);
  35. return screenshot;
  36. }
  37. public object ExecuteChromeCommandWithResult(string commandName, Dictionary<string, object> commandParameters)
  38. {
  39. if (commandName == null)
  40. {
  41. throw new ArgumentNullException("commandName", "commandName must not be null");
  42. }
  43. Dictionary<string, object> parameters = new Dictionary<string, object>();
  44. parameters["cmd"] = commandName;
  45. parameters["params"] = commandParameters;
  46. Response response = this.Execute(SendChromeCommandWithResult, parameters);
  47. return response.Value;
  48. }
  49. private Dictionary<string, object> EvaluateDevToolsScript(string scriptToEvaluate)
  50. {
  51. Dictionary<string, object> parameters = new Dictionary<string, object>();
  52. parameters["returnByValue"] = true;
  53. parameters["expression"] = scriptToEvaluate;
  54. object evaluateResultObject = this.ExecuteChromeCommandWithResult("Runtime.evaluate", parameters);
  55. Dictionary<string, object> evaluateResultDictionary = evaluateResultObject as Dictionary<string, object>;
  56. Dictionary<string, object> evaluateResult = evaluateResultDictionary["result"] as Dictionary<string, object>;
  57. Dictionary<string, object> evaluateValue = evaluateResult["value"] as Dictionary<string, object>;
  58. return evaluateValue;
  59. }
  60. }

    此种调用方式为           


	
  1. //cdSvc参数为对象ChromeDriverService,也可直接将d://chromedriver/驱动所在地址作为参数传递
  2. ChromeDriverEx driver = new ChromeDriverEx(cdSvc, options);
  3. driver1.Url = "http://m.baidu.com";
  4. Screenshot screenshot = driver1.GetFullPageScreenshot();
  5. screenshot.SaveAsFile(Environment.CurrentDirectory + @"\FullPageScreenshot.png");

     3.) 在上面的方法的前提下,另一种GetFullPageScreenshot方式


	
  1. var filePath = Environment.CurrentDirectory + @"\FullPageScreenshot11.png";
  2. Dictionary<string, Object> metrics = new Dictionary<string, Object>();
  3. metrics["width"] = driver.ExecuteScript("return Math.max(window.innerWidth,document.body.scrollWidth,document.documentElement.scrollWidth)");
  4. metrics["height"] = driver.ExecuteScript("return Math.max(window.innerHeight,document.body.scrollHeight,document.documentElement.scrollHeight)");
  5. //返回当前显示设备的物理像素分辨率与 CSS 像素分辨率的比率
  6. metrics["deviceScaleFactor"] = driver.ExecuteScript("return window.devicePixelRatio");
  7. metrics["mobile"] = driver.ExecuteScript("return typeof window.orientation !== 'undefined'");
  8. driver.ExecuteChromeCommand("Emulation.setDeviceMetricsOverride", metrics);
  9. driver.GetScreenshot().SaveAsFile(filePath, ScreenshotImageFormat.Png);

 

参考:https://stackoverflow.com/questions/52043197/c-sharp-selenium-full-page-screenshot


I'd like to take a full page screenshot using C# with Selenium and ChromeDriver. Here: https://stackoverflow.com/a/45201692/5400125 I found an example how to do it in Java. I am trying to achieve this in C#, but I get an exception after page is loaded on the first call to sendEvaluate:

OpenQA.Selenium.WebDriverException: 'no such session (Driver info: chromedriver=2.41.578737 (49da6702b16031c40d63e5618de03a32ff6c197e),platform=Windows NT 10.0.17134 x86_64)'

public class ChromeDriverEx : ChromeDriver
{
    public ChromeDriverEx(string chromeDriverDirectory, ChromeOptions options)
        : base(chromeDriverDirectory, options, RemoteWebDriver.DefaultCommandTimeout)
    {
        var addCmd = this.GetType().BaseType
            .GetMethod("AddCustomChromeCommand", BindingFlags.NonPublic | BindingFlags.Instance);
        addCmd.Invoke(this,
            new object[] {"sendCommand", "POST", "/session/:sessionId/chromium/send_command_and_get_result"});
    }

    public void GetFullScreenshot()
    {
        Object metrics = sendEvaluate(
            @"({" +
            "width: Math.max(window.innerWidth,document.body.scrollWidth,document.documentElement.scrollWidth)|0," +
            "height: Math.max(window.innerHeight,document.body.scrollHeight,document.documentElement.scrollHeight)|0," +
            "deviceScaleFactor: window.devicePixelRatio || 1," +
            "mobile: typeof window.orientation !== 'undefined'" +
            "})");
    }

    private object sendEvaluate(string script)
    {
        var response = sendCommand("Runtime.evaulate",
            new Dictionary<string, object> {{"returnByValue", true}, {"expression", script}});
        return response;
    }

    private object sendCommand(string cmd, object param)
    {
        var r = this.Execute("sendCommand", new Dictionary<string, object> {{"cmd", cmd}, {"params", param}});
        return r.Value;
    }
}



And I call it like this:

 var opts = new ChromeOptions();
 opts.AddAdditionalCapability("useAutomationExtension", false);
 opts.AddArgument("disable-infobars");
 var driver = new ChromeDriverEx(".", opts);
 driver.Navigate().GoToUrl("https://stackoverflow.com/questions");
 driver.GetFullScreenshot();

I'm using Chrome 68 and ChromeDriver 2.41

This code works fine for me, in creating a subclass of ChromeDriver. Note that the code below is purposely written in a very, very verbose style, so as to clearly illustrate every piece of the solution. It could easily be written more concisely, depending on one's coding style and requirement for robust error handling. Moreover, in a future release, it will be unnecessary to create a method for executing a DevTools command that returns a result; such a method will already be part of the .NET bindings.

public class ChromeDriverEx : ChromeDriver
{
    private const string SendChromeCommandWithResult = "sendChromeCommandWithResponse";
    private const string SendChromeCommandWithResultUrlTemplate = "/session/{sessionId}/chromium/send_command_and_get_result";

    public ChromeDriverEx(string chromeDriverDirectory, ChromeOptions options)
        : base(chromeDriverDirectory, options)
    {
        CommandInfo commandInfoToAdd = new CommandInfo(CommandInfo.PostCommand, SendChromeCommandWithResultUrlTemplate);
        this.CommandExecutor.CommandInfoRepository.TryAddCommand(SendChromeCommandWithResult, commandInfoToAdd);
    }

    public Screenshot GetFullPageScreenshot()
    {
        // Evaluate this only to get the object that the
        // Emulation.setDeviceMetricsOverride command will expect.
        // Note that we can use the already existing ExecuteChromeCommand
        // method to set and clear the device metrics, because there's no
        // return value that we care about.
        string metricsScript = @"({
width: Math.max(window.innerWidth,document.body.scrollWidth,document.documentElement.scrollWidth)|0,
height: Math.max(window.innerHeight,document.body.scrollHeight,document.documentElement.scrollHeight)|0,
deviceScaleFactor: window.devicePixelRatio || 1,
mobile: typeof window.orientation !== 'undefined'
})";
        Dictionary<string, object> metrics = this.EvaluateDevToolsScript(metricsScript);
        this.ExecuteChromeCommand("Emulation.setDeviceMetricsOverride", metrics);

        Dictionary<string, object> parameters = new Dictionary<string, object>();
        parameters["format"] = "png";
        parameters["fromSurface"] = true;
        object screenshotObject = this.ExecuteChromeCommandWithResult("Page.captureScreenshot", parameters);
        Dictionary<string, object> screenshotResult = screenshotObject as Dictionary<string, object>;
        string screenshotData = screenshotResult["data"] as string;

        this.ExecuteChromeCommand("Emulation.clearDeviceMetricsOverride", new Dictionary<string, object>());

        Screenshot screenshot = new Screenshot(screenshotData);
        return screenshot;
    }

    public object ExecuteChromeCommandWithResult(string commandName, Dictionary<string, object> commandParameters)
    {
        if (commandName == null)
        {
            throw new ArgumentNullException("commandName", "commandName must not be null");
        }

        Dictionary<string, object> parameters = new Dictionary<string, object>();
        parameters["cmd"] = commandName;
        parameters["params"] = commandParameters;
        Response response = this.Execute(SendChromeCommandWithResult, parameters);
        return response.Value;
    }

    private Dictionary<string, object> EvaluateDevToolsScript(string scriptToEvaluate)
    {
        // This code is predicated on knowing the structure of the returned
        // object as the result. In this case, we know that the object returned
        // has a "result" property which contains the actual value of the evaluated
        // script, and we expect the value of that "result" property to be an object
        // with a "value" property. Moreover, we are assuming the result will be
        // an "object" type (which translates to a C# Dictionary<string, object>).
        Dictionary<string, object> parameters = new Dictionary<string, object>();
        parameters["returnByValue"] = true;
        parameters["expression"] = scriptToEvaluate;
        object evaluateResultObject = this.ExecuteChromeCommandWithResult("Runtime.evaluate", parameters);
        Dictionary<string, object> evaluateResultDictionary = evaluateResultObject as Dictionary<string, object>;
        Dictionary<string, object> evaluateResult = evaluateResultDictionary["result"] as Dictionary<string, object>;

        // If we wanted to make this actually robust, we'd check the "type" property
        // of the result object before blindly casting to a dictionary.
        Dictionary<string, object> evaluateValue = evaluateResult["value"] as Dictionary<string, object>;
        return evaluateValue;
    }
}

ou would use this code with something like the following:

ChromeOptions options = new ChromeOptions();
ChromeDriverEx driver = new ChromeDriverEx(@"C:\path\to\directory\of\chromedriver", options);
driver.Url = "https://stackoverflow.com/questions";

Screenshot screenshot = driver.GetFullPageScreenshot();
screenshot.SaveAsFile(@"C:\desired\screenshot\path\FullPageScreenshot.png");




Here is my sample of get Full Screen ScreenShot:

            string _currentPath = Path.GetDirectoryName(Assembly.GetAssembly(typeof(One of your objects)).Location) + @"\Attachs\";
            var filePath = _currentPath + sSName;

            if (!Directory.Exists(_currentPath))
                Directory.CreateDirectory(_currentPath);

            Dictionary<string, Object> metrics = new Dictionary<string, Object>();
            metrics["width"] = _driver.ExecuteScript("return Math.max(window.innerWidth,document.body.scrollWidth,document.documentElement.scrollWidth)");
            metrics["height"] = _driver.ExecuteScript("return Math.max(window.innerHeight,document.body.scrollHeight,document.documentElement.scrollHeight)");
            metrics["deviceScaleFactor"] = (double)_driver.ExecuteScript("return window.devicePixelRatio");
            metrics["mobile"] = _driver.ExecuteScript("return typeof window.orientation !== 'undefined'");
            _driver.ExecuteChromeCommand("Emulation.setDeviceMetricsOverride", metrics);

            _driver.GetScreenshot().SaveAsFile(filePath, ScreenshotImageFormat.Png);

            _driver.ExecuteChromeCommand("Emulation.clearDeviceMetricsOverride", new Dictionary<string, Object>());
            _driver.Close();