C# - Selenium 整页截图

Bon*_*obo 1 c# selenium selenium-chromedriver selenium-webdriver

我想使用带有 Selenium 和 ChromeDriver 的 C# 截取整页屏幕截图。在这里:https : //stackoverflow.com/a/45201692/5400125我找到了一个如何在 Java 中执行此操作的示例。我试图在 C# 中实现这一点,但是在第一次调用 sendEvaluate 时加载页面后出现异常:

OpenQA.Selenium.WebDriverException: '没有这样的会话 (驱动程序信息: chromedriver=2.41.578737 (49da6702b16031c40d63e5618de03a32ff6c197e),platform=Windows NT 10.0.17134) x86_6

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我这样称呼它:

 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();
Run Code Online (Sandbox Code Playgroud)

我使用的是 Chrome 68 和 ChromeDriver 2.41

Jim*_*ans 8

这段代码对我来说很好用,在创建ChromeDriver. 请注意,下面的代码故意以非常非常冗长的风格编写,以便清楚地说明解决方案的每一部分。它可以很容易地写得更简洁,这取决于一个人的编码风格和健壮错误处理的要求。而且,在未来的版本中,将没有必要创建一个执行返回结果的 DevTools 命令的方法;这样的方法已经是 .NET 绑定的一部分。

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以将此代码与以下内容一起使用:

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");
Run Code Online (Sandbox Code Playgroud)