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.
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();
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:
Entire page
Current window
Visible portion of the current frame
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();");
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);
}
Related
I am using the latest selenium in a .Net test using Browserless.io. I'm trying to scroll down the page slowly and allow the images to load.
When I view this page manually in my browser, you can see the images are lazy loaded in.
https://www.reuters.com/business/
I have created the following script to scroll down the page
[Fact]
public void Get_Lazy_Load_Images_Via_Chrome_Selenium()
{
IWebDriver driver;
var options = new ChromeOptions();
// Set launch args similar to puppeteer's for best performance
options.AddArgument("--disable-background-timer-throttling");
options.AddArgument("--disable-backgrounding-occluded-windows");
options.AddArgument("--disable-breakpad");
options.AddArgument("--disable-component-extensions-with-background-pages");
options.AddArgument("--disable-dev-shm-usage");
options.AddArgument("--disable-extensions");
options.AddArgument("--disable-features=TranslateUI,BlinkGenPropertyTrees");
options.AddArgument("--disable-ipc-flooding-protection");
options.AddArgument("--disable-renderer-backgrounding");
options.AddArgument("--enable-features=NetworkService,NetworkServiceInProcess");
options.AddArgument("--force-color-profile=srgb");
options.AddArgument("--hide-scrollbars");
options.AddArgument("--metrics-recording-only");
options.AddArgument("--mute-audio");
options.AddArgument("--headless");
options.AddArgument("--no-sandbox");
// Note we set our token here, with `true` as a third arg
options.AddAdditionalOption("browserless:token", "MYAPIKEY");
driver = new RemoteWebDriver(
new Uri("https://chrome.browserless.io/webdriver"), options.ToCapabilities()
);
driver.Navigate().GoToUrl("https://www.reuters.com/business/");
ScrollToBottom(driver);
Assert.NotNull(driver.Title);
driver.Quit();
}
private static void ScrollToBottom(IWebDriver driver)
{
long scrollHeight = 0;
do
{
var js = (IJavaScriptExecutor) driver;
var newScrollHeight = (long) js.ExecuteScript("window.scrollTo(0, document.body.scrollHeight); return document.body.scrollHeight;");
if(newScrollHeight == scrollHeight)
{
break;
}
scrollHeight = newScrollHeight;
Thread.Sleep(400);
} while (true);
}
However, no matter how much I Thread.Sleep the images just do not load? Any ideas where I am going wrong?
I tried to reproduce your scenario and came up with this simple solution
var collection = wait.Until(d => d.FindElements(By.XPath("//li[#class = 'story-collection__story__LeZ29 story-collection__default__G33_I']")));
foreach (var item in collection)
{
js.ExecuteScript("arguments[0].scrollIntoView({block: \"center\", inline: \"center\"});", item);
Console.WriteLine(item.FindElement(By.XPath(".//img")).GetAttribute("alt"));
}
Using the following test method (simplified here for the purpose of the question, please ignore the design issues as this isn't the actual code)
[TestClass]
public class LoginTests
{
protected static WindowsDriver<WindowsElement> session;
[TestMethod]
public void Login_WithValidCredentials_Logsin()
{
if (session == null)
{
var options = new AppiumOptions();
options.AddAdditionalCapability("app", #"D:\myApp.exe");
options.AddAdditionalCapability("deviceName", "WindowsPC");
session = new WindowsDriver<WindowsElement>(new Uri(WindowsApplicationDriverUrl), options);
Assert.IsNotNull(session);
session.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(1.5);
}
//Arrange
string username = "username";
var txtUsername = session.FindElementByName("txtUsername");
//Act
//To be developed
//Assert
Assert.AreEqual(username, txtUsername.Text);
}
}
I can't find any element in the UI, the app launches correctly when the test is ran (having WinAppDriver running in the background) but no matter how I try to find the elements on the screen I always get the same error message
"error":"no such element"
"message":"An element could not be located on the page using the given search parameters."
Update 1
Based on the first comment below, I used inspect.exe to confirm the name of the element on the screen that I'm trying to get and nop, I can't find it in the test.
I want to run a parallel execution of a crawler using Selenium ChromeDriver.
If I use the same instance of ChromeDriver in the ForEach loop I run into problems.
When trying to access attributes of a HTML document I get the exception:
OpenQA.Selenium.StaleElementReferenceException: stale element reference: element is not attached to the page document
probably because another thread modifies the state of the instance before the current thread can read it.
Here is what I have now:
public class ChromeCrawler : IDisposable
{
private readonly ChromeDriver _driver;
public ChromeCrawler()
{
var chromeOptions = new ChromeOptions();
chromeOptions.AddArguments("headless");
_driver = new ChromeDriver(chromeOptions);
}
public string GetHTML(string url)
{
_driver.Navigate().GoToUrl(url);
var html = _driver.FindElementsByTagName("html");
var content = html.First().GetAttribute("innerHTML"); //<----- Here I get the exception
return content;
}
....
}
var crawler = new ChromeCrawler();
//Execution
Parallel.ForEach(pages_list, page_url =>
{
var html = crawler.GetHTML(page_url );
.....
});
Is there a way to create a new instance of ChromeCrawler for each thread of the Parallel.ForEach?
Store your drivers in a list in a separate class, intended to keep track of all the drivers currently executing in parallel. In my case, I have a class called BrowserController, which tracks the current driver instances and handles both creation and removal of the drivers. It uses a function like this to add a new driver:
public Dictionary<string, RemoteWebDriver> Drivers;
public RemoteWebDriver AddDriver(string testName, string url, ICapabilities capabilities)
{
var driver = new ThreadLocal<RemoteWebDriver>(() =>
{
return new RemoteWebDriver(new Uri(url), capabilities);
}).Value;
Drivers.Add(testName, driver);
TestBase.StaticLogInfo($"Added driver for test: {testName}");
return Drivers[testName];
}
I'm using Selenium with c#.
Selenium usually can automatically scroll down to the bottom of a web page to find elements but I having issues with a certain page which can increase in size.
Can anyone suggest code that will scroll down to the bottom of the page once it grows in size?
Try using javascript as described in this question
IJavaScriptExecutor js = (IJavaScriptExecutor)driver;
js.ExecuteScript("window.scrollTo(0, document.body.scrollHeight);");
I know it's an old one, but it may be of someone's help. I came out with the following C# code:
private void ScrollToBottom(IWebDriver driver)
{
long scrollHeight = 0;
do
{
IJavaScriptExecutor js = (IJavaScriptExecutor) driver;
var newScrollHeight = (long) js.ExecuteScript("window.scrollTo(0, document.body.scrollHeight); return document.body.scrollHeight;");
if(newScrollHeight == scrollHeight)
{
break;
}
else
{
scrollHeight = newScrollHeight;
Thread.Sleep(400);
}
} while (true);
}
An example in C# using .Net 4.5 and Selenium WebDriver 2.45
Just change the _url variable to point to your website and run.
I used the ChromeDriver but it should work with the other drivers as well.
using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
namespace SeleniumScrollTest {
internal static class Program {
// Declare Selenium Web Driver
private static IWebDriver _chromeDriver;
private static String _url;
private static void Main(string[] args) {
// Instantiate URL
_url = #"http://my.website.com/LazyLoadContent";
// Instantiate Web Driver as ChromeDriver and set initial URL
_chromeDriver = new ChromeDriver {Url = _url};
// Instruct the WebDriver to wait X seconds for elements to load
_chromeDriver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(15));
// Instantiate JavaScript Executor using the web driver
var jse = (IJavaScriptExecutor) _chromeDriver;
// The minified JavaScript to execute
const string script =
"var timeId=setInterval(function(){window.scrollY<document.body.scrollHeight-window.screen.availHeight?window.scrollTo(0,document.body.scrollHeight):(clearInterval(timeId),window.scrollTo(0,0))},500);";
// Start Scrolling
jse.ExecuteScript(script);
// Wait for user input
Console.ReadKey();
// Close the browser instance
_chromeDriver.Close();
// Close the ChromeDriver Server
_chromeDriver.Quit();
}
}
}
If you've already a moderate understanding of Selenium and C#, the important bit is really the JavaScript.
-Sourced from Cybermaxs, here
var timeId = setInterval(function () {
if (window.scrollY !== document.body.scrollHeight)
window.scrollTo(0, document.body.scrollHeight);
else
clearInterval(timeId);
}, 500);
The 500 above is the interval at which it will attempt scroll (in microseconds), adjust this as necessary. [1000 microseconds = 1 second]
Am sorry I don't work with c# but guess the logic would remain the same for any language. If it is a lazy load of the page then you can use Actions class to perform sending pagedown key option. If you get message like more items to load or no more items then you can identify this element. Put the page down option inside a while loop which performs page down until the condition is satisfied. This way you can completely load all the content of the page. Let me know if you need more help.
I need to detect when the url in the browser changes whether it was because of click on a link, a form post or I changed the url in code.
I need it because I'm creating an object to represent the page and I need to dispose recreate it when the url changes.
Here is what I have tried so far:
private string _pageUrl;
protected T _page = default(T);
protected T Page
{
get
{
if (_page == null || UrlHasChanged())
{
_page = GetPage<T>();
SetPageUrl();
}
return _page;
}
}
private bool UrlHasChanged()
{
var driver = Session.GetDriver();
return driver.Url != _pageUrl;
}
public void SetPageUrl()
{
_pageUrl = Session.GetDriver().Url;
}
This works in most cases but it fails when the test goes forward a page and then goes back to the initial page.
I need a way to detect when the url changes so I can reset the _page field.
I'm a Java developer, so, I search in the C# documentation what looked similar to the Java API. I think you should use the EventFiringWebDriver :
EventFiringWebDriver firingDriver = new EventFiringWebDriver(driver);
firingDriver.NavigatingBack += new EventHandler<WebDriverNavigationEventArgs>(...);
firingDriver.NavigatedBack += new EventHandler<WebDriverNavigationEventArgs>(...);
firingDriver.NavigatingForward += new EventHandler<WebDriverNavigationEventArgs>(...);
firingDriver.NavigatedForward += new EventHandler<WebDriverNavigationEventArgs>(...);
I looked at the unit tests and I found this one that may be useful for you :
http://selenium.googlecode.com/svn/trunk/dotnet/test/WebDriver.Support.Tests/Events/EventFiringWebDriverTest.cs