问题
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
回答1:
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;
}
}
You 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");
回答2:
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();
来源:https://stackoverflow.com/questions/52043197/c-sharp-selenium-full-page-screenshot