3

If you run the following code, then at each iteration of the cycle, the browser will bring up on the front and get focus.

public class Program
{
  private static void Main()
  {
    var driver = new ChromeDriver();
    driver.Navigate().GoToUrl("https://i.imgur.com/cdA7SBB.jpg");
    for (int i = 0; i < 100; i++)
    {
      var ss = ((ITakesScreenshot)driver).GetScreenshot();
      ss.SaveAsFile("D:/imgs/i.jpg");
    }
  }
}

The question is: why does this happen and can it be turned off? headless mod does not fit.

It seems that this always happens when Selenium needs to save / read the file or start the process.

Koen Meijer
  • 811
  • 11
  • 19
Anton Komyshan
  • 1,377
  • 10
  • 21

3 Answers3

7

To take a screenshot, chromedriver activates the window. It's by design and there's no option to avoid it even though it's technically possible. For the relevant sources have a look at window_commands.cc.

You could however avoid the effect by moving the window off-screen:

driver.Manage().Window.Position = new Point(-32000, -32000);

or by launching the browser off-screen:

var options = new ChromeOptions();
options.AddArgument("--window-position=-32000,-32000");

UPDATE

You can avoid the activation by taking the screenshot directly via the devtool API. Here's a class to override GetScreenshot:

using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Remote;
using JObject = System.Collections.Generic.Dictionary<string, object>;


class ChromeDriverEx : ChromeDriver
{
    public ChromeDriverEx(ChromeOptions options = null) 
        : base(options ?? new ChromeOptions()) {
        var repo = base.CommandExecutor.CommandInfoRepository;
        repo.TryAddCommand("send", new CommandInfo("POST", "/session/{sessionId}/chromium/send_command_and_get_result"));
    }

    public new Screenshot GetScreenshot() {
        object response = Send("Page.captureScreenshot", new JObject {{"format", "png"}, {"fromSurface", true}});
        string base64 = (string)((JObject)response)["data"];
        return new Screenshot(base64);
    }

    protected object Send(string cmd, JObject args) {
        return this.Execute("send",  new JObject {{"cmd", cmd}, {"params", args}}).Value;
    }
}

usage:

var driver = new ChromeDriverEx();
driver.Url = "https://stackoverflow.com";
driver.GetScreenshot().SaveAsFile("/tmp/screenshot.png");
driver.Quit();
Florent B.
  • 41,537
  • 7
  • 86
  • 101
  • Thx for answer. This solution hides the obsessive browser window, but does not solve the problem of the browser's stealing of focus. – Anton Komyshan Jan 17 '18 at 17:05
  • 1
    You won't be able to deactivate to focus out of the box since the driver doesn't provide the option. You have two options. Either rebuild chromedriver with a modified `window_commands.cc.` or extend `ChromeDriver` to directly take a screenshot via the devtools protocol. You'll find an example with Java/Python [here](https://stackoverflow.com/questions/45199076/take-full-page-screen-shot-in-chrome-with-selenium/45201692#45201692). – Florent B. Jan 17 '18 at 17:19
  • do you know why chromedriver developers made "grip focus" solution by design, now i can't understand why chrome driver need focus for take screenshot? – Anton Komyshan Jan 17 '18 at 17:24
  • 1
    The page could change its content when the focus is lost. Keep in mind that Selenium was mainly designed for testing. So taking a screenshot of a non activated window doesn't really make sense. – Florent B. Jan 17 '18 at 17:32
  • Brilliant answer, this is exactly what I need. Thank you so much. – Anton Komyshan Jan 21 '18 at 16:20
  • @FlorentB. can you please update the code for Selenium 4? – pwrngr Aug 13 '22 at 03:27
1

When you invoke Navigate().GoToUrl("url") method through your Automation script, it is expected that your script will be interacting with some of the elements on the webpage. So for Selenium to interact with those elements, Selenium needs focus. Hence opening up the browser, bring up on the front and getting the focus is the default phenomenon implemented through Navigate().GoToUrl("url").

Now Default Mode or Headless Mode is controlled by the ChromeOption/FirefoxOptions class which is passed as an argument while initializing the WebDriver instance and will call Navigate().GoToUrl("url"). So, Navigate().GoToUrl("url") would have no impact how the WebDriver instance is controlling the Mode of Operation i.e. Default Mode or Headless Mode.

Now when you try to invoke the method from ITakesScreenshot Interface i.e. ITakesScreenshot.GetScreenshot Method which is defined as :

Gets a Screenshot object representing the image of the page on the screen.

In case of WebDriver instance which extends ITakesScreenshot, makes the best effort depending on the browser to return the following in order of preference:

  1. Entire page
  2. Current window
  3. Visible portion of the current frame
  4. The screenshot of the entire display containing the browser

There may be some instances when the browser looses the focus. In that case you can use IJavascriptExecutor to regain the focus as follows :

((IJavascriptExecutor) driver).executeScript("window.focus();");
undetected Selenium
  • 183,867
  • 41
  • 278
  • 352
0

I was struggling with an issue when generic GetScreenshot() in parallel testing was causing browser to lose focus. Some elements were being removed from DOM and my tests were failing. I've come up with a working solution for Edge and Chrome 100+ with Selenium 4.1:

public Screenshot GetScreenshot()
    {
        IHasCommandExecutor executor = webDriverInstance as IHasCommandExecutor;
        var sessionId = ((WebDriver)webDriverInstance).SessionId;
        var command = new HttpCommandInfo(HttpCommandInfo.PostCommand, $"/session/{sessionId}/chromium/send_command_and_get_result");
        executor.CommandExecutor.TryAddCommand("Send", command);

        var response = Send(executor, "Page.captureScreenshot", new JObject { { "format", "png" }, { "fromSurface", true } });
        var base64 = ((Dictionary<string, object>)response.Value)["data"];
        return new Screenshot(base64.ToString());
    }

    private Response Send(IHasCommandExecutor executor, string cmd, JObject args)
    {         
        var json = new JObject { { "cmd", cmd }, { "params", args } }; 
        var command = new Command("Send", json.ToString());
        return executor.CommandExecutor.Execute(command);
    }
pwrngr
  • 148
  • 8