public static IWebElement FindElement(ExpectedConditions expectedConditions, By by, int timeoutInSeconds)
{
DefaultWait<IWebDriver> wait = new DefaultWait<IWebDriver>(driver);
wait.Timeout = TimeSpan.FromSeconds(timeoutInSeconds);
wait.PollingInterval = TimeSpan.FromMilliseconds(10000);
wait.IgnoreExceptionTypes(typeof(NoSuchElementException));
IWebElement element =
wait.Until<IWebElement>(ExpectedConditions.ElementIsVisible(by));
}
My questions:
How to put this expectedConditions instead of what is currently in my method ?
I try to change:
IWebElement element =
wait.Until<IWebElement>(ExpectedConditions.ElementIsVisible(by));
with this:
IWebElement element =
wait.Until<IWebElement>(expectedConditions(by));
And received this error:
Method name expected.
The Until method requires a predicate as first argument.
A predicate is a function that is called at a regular interval until it returns something different from null or false.
So in your case you need to make it return a predicate and not a IWebElement:
public static Func<IWebDriver, IWebElement> MyCondition(By locator) {
return (driver) => {
try {
var ele = driver.FindElement(locator);
return ele.Displayed ? ele : null;
} catch (StaleElementReferenceException){
return null;
}
};
}
// usage
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
IWebElement element1 = wait.Until(MyCondition(By.Id("...")));
Which is equal to :
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
IWebElement element = wait.Until(ExpectedConditions.ElementIsVisible(By.Id("...")));
element.Click();
You could also use a lambda expression
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
IWebElement element = wait.Until((driver) => {
try {
var ele = driver.FindElement(By.Id("..."));
return ele.Displayed ? ele : null;
} catch (StaleElementReferenceException){
return null;
}
});
element.Click();
Or an extension method:
public static IWebElement WaitElementVisible(this IWebDriver driver, By by, int timeout = 10) {
return new WebDriverWait(driver, TimeSpan.FromSeconds(timeout)).Until((drv) => {
try {
var ele = drv.FindElement(by);
return ele.Displayed ? ele : null;
} catch (StaleElementReferenceException){
return null;
} catch (NotFoundException){
return null;
}
});
}
// usage
IWebElement element = driver.WaitElementVisible(By.Id("..."));
element.Click();
As you see, there is many ways to wait for an element is a specific state.
Related
Given I need wait for one textbox appears, do something, and later I will need wait another Textbox.
I want to know if this code is correct:
var waitForTextBox = new WebDriverWait(driver, TimeSpan.FromSeconds(30));
waitForTextBox.Until(ExpectedConditions.ElementIsVisible(By.Id("txtFirstName"))).SendKeys("John");
waitForTextBox.Until(ExpectedConditions.ElementIsVisible(By.Id("txtLastName"))).SendKeys("Skeet");
or if instead of reusing waitForTextBox, I will need do like this:
var waitForFirstName = new WebDriverWait(driver, TimeSpan.FromSeconds(30));
waitForFirstName.Until(ExpectedConditions.ElementIsVisible(By.Id("txtFirstName"))).SendKeys("John");
var waitForLastName = new WebDriverWait(driver, TimeSpan.FromSeconds(30));
waitForLastName.Until(ExpectedConditions.ElementIsVisible(By.Id("txtLastName"))).SendKeys("Skeet");
Reusing the WebDriverWait objects for multiple "waits" is best. The only time you need a different WebDriverWait object is if you need two different timeout periods. Otherwise the only state that a WebDriverWait keeps is the driver and how long it waits.
Ok, I found the source code for the WebDriverWait and Until method, and I find there is no problem reusing this object:
public virtual TResult Until<TResult>(Func<T, TResult> condition, CancellationToken token)
{
if (condition == null)
{
throw new ArgumentNullException("condition", "condition cannot be null");
}
var resultType = typeof(TResult);
if ((resultType.IsValueType && resultType != typeof(bool)) || !typeof(object).IsAssignableFrom(resultType))
{
throw new ArgumentException("Can only wait on an object or boolean response, tried to use type: " + resultType.ToString(), "condition");
}
Exception lastException = null;
var endTime = this.clock.LaterBy(this.timeout);
while (true)
{
token.ThrowIfCancellationRequested();
try
{
var result = condition(this.input);
if (resultType == typeof(bool))
{
var boolResult = result as bool?;
if (boolResult.HasValue && boolResult.Value)
{
return result;
}
}
else
{
if (result != null)
{
return result;
}
}
}
catch (Exception ex)
{
if (!this.IsIgnoredException(ex))
{
throw;
}
lastException = ex;
}
// Check the timeout after evaluating the function to ensure conditions
// with a zero timeout can succeed.
if (!this.clock.IsNowBefore(endTime))
{
string timeoutMessage = string.Format(CultureInfo.InvariantCulture, "Timed out after {0} seconds", this.timeout.TotalSeconds);
if (!string.IsNullOrEmpty(this.message))
{
timeoutMessage += ": " + this.message;
}
this.ThrowTimeoutException(timeoutMessage, lastException);
}
Thread.Sleep(this.sleepInterval);
}
}
I will mark the previous answer as correct, just putting here for a more detailed and added authoritative source also.
URL: http://bcmprod.brill.com/rsuite-cms/
I am trying to automate downloading of manuscripts from the client site above. I am using selenium phantomjs in C#.
I have the user credentials. But the elements that make up the login form (e.g. username, password) are not existing in the page source, but are existing when you inspect those elements in the browser.
These are the xpaths I'm using to locate them from "Inspect element" (IDs are dynamically assigned that's why I didn't use them):
string xpathUsername = ".//input[#name='user']";
string xpathPassword = ".//input[#name='pass']";
string xpathBtnLogin = ".//button[#type='submit'][#aria-label='Log In']";
How can I successfully login when the source returned by the driver has no login elements thus cannot be found, but yet existing when I inspect them in the browser?
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.IO;
using OpenQA.Selenium;
using OpenQA.Selenium.PhantomJS;
using OpenQA.Selenium.Support.UI;
using WebCrawler.Helper;
namespace WebCrawler.Webcodes
{
class RSuite : IWebcode
{
List<string> errors = new List<string>();
string xpathUsername = ".//input[#name='user']";
string xpathPassword = ".//input[#name='pass']";
string xpathBtnLogin = ".//button[#type='submit'][#aria-label='Log In']";
public RSuite()
{ }
public List<Record> GetRecords()
{
Console.WriteLine(string.Format("Crawling: {0}", Config.Url));
List<Record> recordList = new List<Record>();
try
{
PhantomJSDriverService service = PhantomJSDriverService.CreateDefaultService();
service.HideCommandPromptWindow = true;
using (IWebDriver driver = new PhantomJSDriver(service))
{
driver.Navigate().GoToUrl(Config.Url);
Console.WriteLine("\nChecking elements availability ...");
// code exception here: I couldn't get all these elements
IWebElement username = Element("User ID", GetElement(driver, xpathUsername));
IWebElement password = Element("Password", GetElement(driver, xpathPassword));
IWebElement btnlogin = Element("Login Button", GetElement(driver, xpathBtnLogin));
// input credentials
Console.WriteLine("\nAttempting to login ...");
if (username != null && password != null && btnlogin != null)
{
username.Clear();
username.SendKeys(Config.Username);
password.Clear();
password.SendKeys(Config.Password);
// is button clicked & loaded a new page? (If true, login is successful)
if (IsPageLoaded(driver, btnlogin))
{
Console.WriteLine("Logged in successfully.");
// do some action
// download files
}
else
{ ErrorHandler("Login failed."); }
}
else
{ ErrorHandler("Login failed."); }
}
// release
service.Dispose();
}
catch (Exception err)
{ ErrorHandler(err.GetBaseException().ToString()); }
// generate report for caught errors, if any
if (errors.Count() > 0)
Config.ErrorReport(this.GetType().Name.Trim().ToUpper(), string.Join("\n\n", errors));
return recordList;
}
private IWebElement GetElement(IWebDriver driver, string xPath)
{
IWebElement element = null;
try
{
// wait for elements to load
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(60));
Func<IWebDriver, IWebElement> waitForElement = new Func<IWebDriver, IWebElement>((IWebDriver d) =>
{
element = d.FindElement(By.XPath(xPath));
if (element != null)
{ return element; }
return null;
});
return wait.Until(waitForElement);
}
catch (Exception err)
{
ErrorHandler(err.GetBaseException().ToString());
return null;
}
}
private IWebElement Element(string label, IWebElement element)
{
if (element != null)
{ Console.WriteLine(string.Format("{0}: Yes", label)); }
else
{ Console.WriteLine(string.Format("{0}: No", label)); }
return element;
}
private bool IsPageLoaded(IWebDriver driver, IWebElement element)
{
try
{
// page redirected? Or is the element still attached to the DOM?
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(60));
element.Click();
return wait.Until(ExpectedConditions.StalenessOf(element));
}
catch (Exception err)
{
ErrorHandler(err.GetBaseException().ToString());
return false;
}
}
private void ErrorHandler(string error)
{
Console.WriteLine(error);
errors.Add(error);
}
}
}
As per your question the url http://bcmprod.brill.com/rsuite-cms/ is based on Ember.js so you have to induce WebDriverWait inconjunction with ExpectedConditions method ElementToBeClickable() while you look out for the elements.
The Locator Strategies to identify the desired fileds are as follows:
User ID:
string username = "//input[#class='ember-view ember-text-field finput text-field component' and #name='user']";
Password:
string password = "//input[#class='ember-view ember-text-field finput text-field component' and #name='pass']";
Log In:
string loginbtn = "//label[#class='ui-button-text']";
I know I can wait for an element to exist by doing this:
var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(5));
var element = wait.Until(d => d.FindElement(By.Id("foo-status")));
If it doesn't exist within 5 seconds, element is null.
How do I get Selenium to wait for the element to have a certain text value?
(I have answered my own question, but would be keen for any better answers or improvements.)
An easier solution for your usecase to wait for the element to have a certain text can be implemented with the help of ExpectedConditions in-conjunction with the method TextToBePresentInElement as follows :
wait = new WebDriverWait(driver, TimeSpan.FromSeconds(30));
var expectedText = "good";
var element = wait.Until(ExpectedConditions.TextToBePresentInElement(By.Id("foo-status"), expectedText));
The trick is to use a filter method that returns null if the text does not match.
public static IWebElement ElementTextFilter(IWebElement webElement, string text) {
if (webElement == null)
return null;
return webElement.Text.Equals(text, StringComparison.OrdinalIgnoreCase)
? webElement : null;
}
// ...
var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(5));
var expectedText = "good";
var element = wait.Until(d =>
ElementTextFilter(d.FindElement(By.Id("foo-status")), expectedText)
);
Can easily turn this into an extension method which is a bit cleaner.
public static class WebElementExtensions {
public static IWebElement WithText(this IWebElement webElement, string text) {
if (webElement == null)
return null;
return webElement.Text.Equals(text, StringComparison.OrdinalIgnoreCase)
? webElement : null;
}
// ...
var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(5));
var expectedText = "good";
var element = wait.Until(d => d.FindElement(By.Id("foo-status")).WithText(expectedText));
I am currently using Selenium WebDriverWait to wait for things to happend where I don't need the IWebDriver functionality. My code looks like this:
public static T WaitForNotNull<T>(this IWebDriver driver, Func<T> func)
{
var result = default(T);
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
wait.Until(d => (result = func()) != null);
return result;
}
public static void WaitForNull<T>(this IWebDriver driver, Func<T> func)
{
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
wait.Until(d => func() == null);
}
Is there a similar construct in .Net which I can use instead of WebDriverWait?
The answer is
No
There is no such thing in the .NET Framework, you will have to write such a method yourself.
This is the original Until implementation (source). You can improve it IMO.
And if you don't want to block the calling thread (in case of UI thread), you can easily use the async\await pattern
public TResult Until<TResult>(Func<T, TResult> condition)
{
if (condition == null)
{
throw new ArgumentNullException("condition", "condition cannot be null");
}
var resultType = typeof(TResult);
if ((resultType.IsValueType && resultType != typeof(bool)) || !typeof(object).IsAssignableFrom(resultType))
{
throw new ArgumentException("Can only wait on an object or boolean response, tried to use type: " + resultType.ToString(), "condition");
}
Exception lastException = null;
var endTime = this.clock.LaterBy(this.timeout);
while (true)
{
try
{
var result = condition(this.input);
if (resultType == typeof(bool))
{
var boolResult = result as bool?;
if (boolResult.HasValue && boolResult.Value)
{
return result;
}
}
else
{
if (result != null)
{
return result;
}
}
}
catch (Exception ex)
{
if (!this.IsIgnoredException(ex))
{
throw;
}
lastException = ex;
}
// Check the timeout after evaluating the function to ensure conditions
// with a zero timeout can succeed.
if (!this.clock.IsNowBefore(endTime))
{
string timeoutMessage = string.Format(CultureInfo.InvariantCulture, "Timed out after {0} seconds", this.timeout.TotalSeconds);
if (!string.IsNullOrEmpty(this.message))
{
timeoutMessage += ": " + this.message;
}
this.ThrowTimeoutException(timeoutMessage, lastException);
}
Thread.Sleep(this.sleepInterval);
}
}
I use Selenium with Phantomjs, and want to get the page content after the page fully loaded.
I tried http://docs.seleniumhq.org/docs/04_webdriver_advanced.jsp but it seems not working with phantomjs
Explicit wait:
using (IWebDriver driver = new PhantomJSDriver())
{
IWait<IWebDriver> wait = new OpenQA.Selenium.Support.UI.WebDriverWait(driver, TimeSpan.FromSeconds(30.00));
wait.Until(driver1 => ((IJavaScriptExecutor)driver).ExecuteScript("return document.readyState").Equals("complete"));
driver.Navigate().GoToUrl(url);
content = driver.PageSource;
driver.Quit();
}
Another test:
using (IWebDriver driver = new PhantomJSDriver())
{
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
driver.Url = url;
IWebElement myDynamicElement = wait.Until<IWebElement>((d) =>
{
return d.FindElement(By.Id("footer")); // failed because it's not yet loaded full content
});
content = driver.PageSource;
}
Or implicit wait:
using (IWebDriver driver = new PhantomJSDriver())
{
driver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(30));
driver.Navigate().GoToUrl(url);
content = driver.PageSource;
driver.Quit();
}
The content is still lacking. The only way is to put Thread.Sleep(waitTime); which is not a good solution for this.
Thanks.
For your "Explicit wait:" option, I think the correct sequence should be:
1) Navigate to target url by:
driver.Navigate().GoToUrl(url);
2) Wait until the target url fully loaded by
IWait<IWebDriver> wait = new OpenQA.Selenium.Support.UI.WebDriverWait(driver, TimeSpan.FromSeconds(30.00));
wait.Until(driver1 => ((IJavaScriptExecutor)driver).ExecuteScript("return document.readyState").Equals("complete"));
In this way next line will wait page fully loaded before read PageSource.
I have created a extension method. In this method you can put your condition.
public static bool WaitUntil(this IWebDriver driver, Func<IWebDriver, bool> expression, int timeOutSeconds = 10)
{
TimeSpan timeSpent = new TimeSpan();
bool result = false;
while (timeSpent.TotalSeconds < timeOutSeconds)
{
result = expression.Invoke(driver);
if (result == true)
{
break;
}
Thread.Sleep(timeSleepingSpan);
timeSpent = timeSpent.Add(new TimeSpan(0, 0, 0, 0, timeWaitingSpan));
}
return result;
}
Like
driver.WaitUntil(d => d.Url.Equals("https://www.meusite.com/"));
Try something like this:
try (
ExpectedConditions.presenceOfElementLocatedBy
ExpectedConditions.visibilityOfElementLocatedBy
) catch error if both conditions are not met