I am migrating my selenium scripts to PageFactory and currently stuck with find if an element exist on the page
My current implementation is
public bool IsElementExist(string element)
{
try
{
Driver.FindElement(By.XPath(element));
return true;
}
catch (NoSuchElementException)
{
return false;
}
}
My page has multiple items on its basket and if I need to delete all/some of the item. Everytime I delete the page refreshes, but the Pagefactory FindsBy is not getting refreshed and retains the very first value. Any assistance please.
When you have multiple elements matching a given locator, the findElement() method simply picks the first one and returns the corresponding WebElement. This could be confusing sometimes, because you might interact with a different element unintentionally.
A slightly better implementation of your function would be as below (example in Java but works similarly in C#):
public Boolean objectExists(By by) {
int numberOfMatches = driver.findElements(by).size();
if(numberOfMatches == 1) {
return true;
}
else {
// 0 matches OR more than 1 match
return false;
}
}
Not sure if this is directly related to your issue, but worth a try.
Below I am performing a wait but it provides an example of how you can pass an IWebElement to find an object and perform an action. The method you provided is going to expect you to provide the XPath as a string rather than the name of an already identified element on the page.
// Wait Until Object is Clickable
public static void WaitUntilClickable(IWebElement elementLocator, int timeout)
{
try
{
WebDriverWait waitForElement = new WebDriverWait(DriverUtil.driver, TimeSpan.FromSeconds(10));
waitForElement.Until(ExpectedConditions.ElementToBeClickable(elementLocator));
}
catch (NoSuchElementException)
{
Console.WriteLine("Element with locator: '" + elementLocator + "' was not found in current context page.");
throw;
}
}
This lives in a BaseUtil.cs that I call from MyPageFunctionality.cs like this:
BaseUtil.WaitUntilClickable(btnLogin, 10);
Would this work?
//call the method and pass the xpath. You can also create css, id etc.
page.WaitForElementNoLongerDisplayed_byXpath("//div[contains(#class, 'my-error-message')]");
public static void WaitForElementNoLongerDisplayed_byXpath(string elementXpath)
{
try
{
_wait.Until(driver => driver.FindElements(By.XPath(elementXpath)).Count == 0);
}
catch (Exception)
{
LogFunctions.WriteError("Element is still displayed and should not be");
TakeScreenshot("elementStillShown");
throw;
}
}
Related
I'm very new to Selenium so I'm looking for some insight on how to achieve this.
Essentially, I want my driver to wait for one of multiple elements to be visible. This is because the website can produce mixed results when using automation, so I want to treat this like it's "error handling" (if that makes sense).
Basically, if element a is visible first, do this. However, if element b is visible first, do that.
How would I be able to achieve this using the Selenium webdriver for C#? I read that combining the elements' XPath value by using "|" worked, however when I tried it with my application, I had no luck.
Any answers are greatly appreciated! Thank you in advance!
To me this is kind of tough because your elements could be displayed within a split second of each other. Wonder if you could put this in a case and break when you get one?
So what I was thinking was seeing if the element was present first.
public static bool IsElementPresent_byXpath(string xpath)
{
bool result;
try { result = Driver.FindElement(By.XPath(xpath)).Displayed; }
catch (NoSuchElementException) { return false; }
catch (StaleElementReferenceException) { return false; }
return result;
}
So if you do it this way, then you can write something like the below but my concern is that if the page is not changing, ele1, ele2 and ele3 could be invoked. So this code would need to be changed.
bool ele1 = Browser.IsElementPresent_byXpath("//[#id='1']");
bool ele2 = Browser.IsElementPresent_byXpath("//[#id='2']");
bool ele3 = Browser.IsElementPresent_byXpath("//[#id='3']");
if (ele1)
{
//do this
}
else if (ele2 || ele1 == false)
{
//do this
}
else if (ele3 || ele1 == false || ele2 == false)
{
//do this
}
I would go with Wait.Until, so that I can combine the benefits of waiting with set timeout for an event, and returning me matching locator, so I can base my logic on the result.
This is java code, but C# has equivalents.
Assuming I am waiting for either firstBy, secondBy or thirdBy:
By locatorOfElementThatIsPresent = new WebDriverWait(webDriver, timeoutInSeconds).<By>until(driver -> {
// findElements does not throw when element not found, so I skip try-catch
if (driver.findElements(firstBy).size() > 0) return firstBy;
if (driver.findElements(secondBy).size() > 0) return secondBy;
if (driver.findElemetns(thirdBy).size() > 0) return thirdBy;
return null; // neither found, so the method will be retried until timeout
});
I am trying to get the block of a CurrentPage (if it a specific page) in Root.Master.cs, but I am unable to find the way, how to do it.
I have tried to load block with its ID (a static number), but Reviewer says you need to find another way.
public MapMode CurrentMapMode
{
get
{
if (!(CurrentPage is EstateDisplayPage))
return MapService.GetMapMode(Request.QueryString, CurrentPage as ISiteWithMap);
var block = ServiceLocator.Current.GetInstance<IContentLoader>()
.Get<IContent>(new ContentReference(10861)); //MapBlock ID: 10861
return MapService.GetMapMode(Request.QueryString, block as ISiteWithMap);
}
}
I need to find another way to finding block, where I dont need to pass the static number (ID) to ContentRefrence. Thanks in advance
That is what I have done to get the Blocks of Specific Page on Root.Master.cs.
public MapMode CurrentMapMode
{
get
{
if (!(CurrentPage is EstateDisplayPage))
return MapService.GetMapMode(Request.QueryString, CurrentPage as ISiteWithMap);
var page = CurrentPage as EstateDisplayPage;
foreach (var item in page.ContentArea.Items)
{
var block = ServiceLocator.Current.GetInstance<IContentLoader>()
.Get<IContent>(item.GetContent().ContentLink);
if (block is ISiteWithMap)
{
return MapService.GetMapMode(Request.QueryString, block as ISiteWithMap);
}
}
return MapMode.Google;
}
}
I am trying to find an element by XPath, and if it is not found then execute the function again. It seems to be getting stuck on the finding element function, and I'm not sure why. I have this working on another site with the same method. Here are my attempts:
Attempt 1:
while (url == "https://drygoods.phish.com/dept/posters-prints-and-paper-goods")
try
{
driver[task].FindElement(By.XPath($"//img[contains(#alt, '{Keyword}')]")).Click();
}
catch
{
Thread.Sleep(1000);
DryGoodsFindProductKeyword(Keyword, task);
}
Attempt 2:
if (driver[task].Url != "https://drygoods.phish.com/dept/posters-prints-and-paper-goods")
{
driver[task].FindElement(By.XPath("//div[2]/div[2]/div/button")).Click();
driver[task].Url = "https://drygoods.phish.com/cart/";
//SolveCaptcha(task);
driver[task].FindElement(By.Id("GoToCheckout")).Click();
MessageBox.Show("Click Checkout");
Thread.Sleep(5000);
}
else
{
driver[task].FindElement(By.XPath($"//img[contains(#alt, '{Keyword}')]")).Click();
WebDriverWait wait = new WebDriverWait(driver[task], TimeSpan.FromSeconds(5));
wait.Until(ExpectedConditions.UrlContains("product"));
Thread.Sleep(1000);
DryGoodsFindProductKeyword(Keyword, task);
Here is my full method as well:
public void DryGoodsFindProductKeyword(string Keyword, int task)
{
String url = driver[task].Url;
driver[task].Url = "https://drygoods.phish.com/dept/posters-prints-and-paper-goods";
if (driver[task].Url != "https://drygoods.phish.com/dept/posters-prints-and-paper-goods")
{
driver[task].FindElement(By.XPath("//div[2]/div[2]/div/button")).Click();
driver[task].Url = "https://drygoods.phish.com/cart/";
//SolveCaptcha(task);
driver[task].FindElement(By.Id("GoToCheckout")).Click();
MessageBox.Show("Click Checkout");
Thread.Sleep(5000);
}
else
{
driver[task].FindElement(By.XPath($"//img[contains(#alt, '{Keyword}')]")).Click();
WebDriverWait wait = new WebDriverWait(driver[task], TimeSpan.FromSeconds(5));
wait.Until(ExpectedConditions.UrlContains("product"));
Thread.Sleep(1000);
DryGoodsFindProductKeyword(Keyword, task);
}
}
Thank you in advance for the help! Please let me know if I can add anymore information.
To make sure the page is loaded instead of just your URL changing you can use
wait.Until(d => ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState").Equals("complete"));
Maybe your element isn't displayed yet or it isn't enabled. They're both properties on your IWebElement. You could use this to see if it's clickable:
wait.Until(ExpectedConditions.ElementToBeClickable(byLocatorGoesHere)).Click();
If it's slightly out of view you may need to use Javascript to click it (or just scroll it into view).
I have a logger that I am adding to my project. Right now for every method I'm going to have to write Logger.DebugLog("Starting Method") at the start of each method and Logger.DebugLog("Completed Method")
this logger - when Debug enabled - allows me to track exactly what methods were called on this run so that if there is an issue I can see how far it got before breaking making it easy to debug. Assume that the method name and line are being captured - my goal here is I do not want to add those two lines on every one of the +100 public or private methods
namespace myProject
{
public class myClass
{
public bool MyPublicMethod(string Message = "someRandomMessage")
{
try
{
myPrivateMethod(1);
writeToLog(Message);
return true;
}
catch(){
return false;
}
}
private bool myPrivateMethod(int passedNumber)
{
try
{
writeToLog(passedNumber);
return true;
}
catch(){
return false;
}
}
}
}
my log file should look like this:
04:00:00 - Starting Method:MyPublicMethod
04:00:00 - Starting Method:myPrivateMethod
04:00:01 - 1
04:00:01 - Completed Method:myPrivateMethod
04:00:02 - someRandomMessage
04:00:02 - Completed Method:MyPublicMethod
What I am forced to do now which looks cluttered is:
namespace myProject
{
public class myClass
{
public bool MyPublicMethod(string Message = "someRandomMessage")
{
try
{
writeToLog("Starting Method");
myPrivateMethod(1);
writeToLog(Message);
writeToLog("Completed Method");
return true;
}
catch(){
return false;
}
}
private bool myPrivateMethod(int passedNumber)
{
try
{
writeToLog("Starting Method");
writeToLog(passedNumber);
writeToLog("Completed Method");
return true;
}
catch(){
return false;
}
}
}
}
is this even possible in .NET or do I have to explicitly list that logging method if I want to use it?
4/6/18 Edit: It is possible - see AOP. Heres a pretty decent article on it http://www.dotnetcurry.com/patterns-practices/1305/aspect-oriented-programming-aop-csharp-using-solid
Here is a good summary of exactly what I was looking for:
Consider the following code:
public class DocumentSource : IDocumentSource
{
//..
public Document[] GetDocuments(string format)
{
try
{
using (var context = CreateEFContext())
{
var documents =
context
.Documents
.Where(c => c.Name.EndsWith("." + format))
.ToArray();
logger.LogSuccess(
"Obtained " + documents.Length + " documents of type " + format +
Environment.NewLine +
"Connection String: " + connectionString);
return documents;
}
}
catch (Exception ex)
{
logger.LogError(
"Error obtaining documents of type " + format +
Environment.NewLine +
"Connection String: " + connectionString, ex);
throw;
}
}
//..
}
Here is how the method would look like without logging:
public Document[] GetDocuments(string format)
{
using (var context = CreateEFContext())
{
return
context
.Documents
.Where(c => c.Name.EndsWith("." + format))
.ToArray();
}
}
Clearly, the logging code has made the original method less readable. It has tangled the real method code with logging code.
This is also a violation of the Single Responsibility Principle.
Also, we expect to find the same logging pattern in many methods all over the code base. Basically, we expect to find the following pattern:
try
{
//Do something here
logger.LogSuccess(…
//..
}
catch (Exception ex)
{
logger.LogError(…
throw;
}
There are some Fody Add-ins that will allow you to add this kind of code into your compiled output at compile-time rather than having to write it yourself.
For example, MethodDecorator allows you to define a specific attribute, and any method you decorate with that attribute will call specific methods prior to entering and leaving the method.
I should note that for a project of any reasonable size, logging the entry and exit for every single method is going to produce more log messages than anyone can reasonably expect to read. I'd suggest you be judicious in which methods add log messages, and what information you include in those log messages.
The vast majority of the time a more useful strategy is to use guard statements to test your assumptions along the way, throwing an exception the moment anything is out of place, and then wrapping exceptions with more useful information (via InnerException) as they go up the call chain, then finally logging the results of those exceptions at the top level of your application. That way, you only produce log messages when something appears to go in a way you don't expect, and the log message in that case has all the information you're likely to need.
I'm not sure this is exactly what you're looking for, but it might point you in the right direction. For demonstration purposes I'm logging to the Console, but you could log to a file instead.
You could create a method that takes in another method to execute, which executes the function and returns the value, and which wraps the beginning and ending of the method call with writes to your log file:
// For methods that return a value
private static TResult LogMethod<TResult>(string displayName, Func<TResult> method)
{
Console.WriteLine($"{DateTime.Now} - Starting method: {displayName}");
TResult result = method();
Console.WriteLine($"{DateTime.Now} - Completed method: {displayName}");
return result;
}
// For void methods
private static void LogMethod(string displayName, Action method)
{
Console.WriteLine($"{DateTime.Now} - Starting method: {displayName}");
method();
Console.WriteLine($"{DateTime.Now} - Completed method: {displayName}");
}
As an example of how to use this, let's say we have the following methods:
private static int GetNumberFromUser(string prompt)
{
int result;
do
{
Console.Write(prompt);
} while (!int.TryParse(Console.ReadLine(), out result));
return result;
}
private static int AddNumbers(int first, int second)
{
return first + second;
}
private static void Tell(string message)
{
Console.WriteLine(message);
}
Now, instead of calling these methods directly, we can call them through our LogMethod function:
private static void Main()
{
var firstNumber = LogMethod("GetNumber",
() => GetNumberFromUser("Enter first number: "));
var secondNumber = LogMethod("GetNumber",
() => GetNumberFromUser("Enter second number: "));
var result = LogMethod("AddNumber",
() => AddNumbers(firstNumber, secondNumber));
LogMethod("Tell", () => Tell($"{firstNumber} + {secondNumber} = {result}"));
GetKeyFromUser("\nDone!! Press any key to exit...");
}
Output
In the selenium IDE, there is a verify command. When I exported the command into c# I found that verify is basically an assert in a try catch statement and the error is added into a string.
In my code, I want to use the functionality of the verify command but I do not want to use a try and catch statement for every single assert.
Does anyone have a way to do this?
edit:
public static void AssertVerticalAlignment(CustomWebDriver webDriver, IElement elementA, IElement elementB, double tolerance = 0)
{
try
{
Assert.AreEqual(elementA.Location.X, elementB.Location.X, tolerance);
}
catch (Exception exception)
{
LogHelper.LogException(exception.Message, exception.StackTrace, webDriver.GetScreenshot());
throw;
}
}
What I want to do is add a message in the assert. It should say nameOfElementA is not aligned with nameOfElementB. But I don't want to give elementA and elementB a name property.
This is how I call the method: AssertVerticalAlignment(webdriver, homepage.NameInput, homepage.AgeInput)
Homepage is an object and NameInput is part of Homepage. NameInput is of type IElement, which is basically same as IWebElement, but it cannot interact with html, ie. it doesn't the ability to click, etc.
So I want the message to say NameInput is not aligned with AgeInput
You are essentially asking for a way to do "soft assertions". The way the IDE does it is correct. After all, that's what "soft assertions" are. If something fails a particular assertion, you want it to carry on regardless. That is what the IDE is doing, by catching that exception (notice that in it's code it's only catching the AssertionException).
In order to help avoid messy code, the best you can do is create your own verify methods. Sometimes you don't even need to catch exceptions. For instance take this basic verifyElementIsPresent method:
private class SoftVerifier
{
private StringBuilder verificationErrors;
public SoftVerifier()
{
verificationErrors = new StringBuilder();
}
public void VerifyElementIsPresent(IWebElement element)
{
try
{
Assert.IsTrue(element.Displayed);
}
catch (AssertionException)
{
verificationErrors.Append("Element was not displayed");
}
}
}
Why do you need the exception at all?
private class SoftVerifier
{
private StringBuilder verificationErrors;
public SoftVerifier()
{
verificationErrors = new StringBuilder();
}
public void VerifyElementIsPresent(IWebElement element)
{
if (!element.Displayed)
{
verificationErrors.Append("Element was not displayed");
}
}
}
The sort answer is there are some ways you can make it a little less messy, but overall, no, there isn't much you can do about it.