Issues with elements that have same By locator on multiple pages - c#

I have 5 pages (A, B, C, D, E). A, B, C and D each contain a button with Id 'Next' which will load the next page. Now, the problem is that sometimes (not always), the 'Next' button is not clicked on each page.
My code is supposed to do the following: we launch the browser and navigate to page A by URL. Then, we perform some logic on the page and then we click the 'Next' button. We arrive at page B and perform some logic on the page and click the 'Next' button. We arrive at page C, and immediately click the 'Next' button without doing anything else. On page D, we perform some logic and click the 'Next' button. (and so on...)
Now, the problem is that on page C, the 'Next' button is not always clicked, but it does not throw an error for FindElement. So it tries to perform the logic on page D and the web driver crashes because it's still on page C. How can I fix this problem? Notice that I do use a dynamic webdriverWait for the element to be present on the page, but this makes so difference because the same the locator it always the same (ID = 'Next'). Also, notice I don't return my PageObjects - I'm not sure whether this is absolutely required.
Any thoughts?
Here is my code:
public class Page
{
public Page()
{
PageFactory.InitElements(SeleniumTests.driver, this);
}
}
public static class SeleniumTests
{
public static IWebDriver driver { get; set; }
}
class Page_1 : Page
{
[FindsBy(How = How.Id, Using = "Next")]
public void Continue()
{
btnNext.SafeClick();
}
}
class Page_2 : Page
{
[FindsBy(How = How.Id, Using = "Next")]
public void Continue()
{
btnNext.SafeClick();
}
}
class Page_3 : Page
{
[FindsBy(How = How.Id, Using = "Next")]
public void Continue()
{
btnNext.SafeClick();
}
}
class Page_4 : Page
{
[FindsBy(How = How.Id, Using = "Next")]
public void Continue()
{
btnNext.SafeClick();
}
}
class Page_5 : Page
{
[FindsBy(How = How.Id, Using = "Next")]
public void Continue()
{
btnNext.SafeClick();
}
}
public static class SeleniumExtensionMethod
{
public static WebDriverWait wait = new WebDriverWait(SeleniumTests.driver, TimeSpan.FromSeconds(15));
public static void SafeClick(this IWebElement webElement, Double seconds = 15)
{
try
{
wait.Until(ExpectedConditions.ElementToBeClickable(webElement)).Click();
}
catch (System.Reflection.TargetInvocationException ex)
{
Console.WriteLine(ex.InnerException);
}
}
}
And finally:
public partial class Form1 : Form
{
Page_1 pageA = new Page_1();
pageA.PerformSomeLogic();
pageA.Continue();
Page_2 pageB = new Page_2();
pageB.PerformSomeLogic();
pageB.Continue();
Page_3 pageC = new Page_3();
// Don't do anything here, just continue.
pageC.Continue();
Page_4 pageD = new Page_4();
pageD.PerformSomeLogic();// -----> here is crashes, as the previous line 'pageC.Continue()' was not really executed, it seems as though the button was clicked 2 times on page B
pageD.Continue() ;
Page_5 pageE = new Page_5();
pageD.PerformSomeLogic();
pageE.Continue();
}
Edit: what I want, ideally is to do some kind of dynamic wait which actually would work in this case. I can also use Thread.Sleep(); and this solve my problem but it's a code smell and I want to avoid it.

You have 2 options:
make sure the page is loaded and you are on that page
You can add a wait.Until() for a unique element of the page.
get the selector for the button based on the parent page
Since you have Continue() in each page object you could use a css selector to identify the button based on the parent page or based on a unique section from the page that does not exists in the other ones, else what is the point on having the same method with the same selector in every page. You can find this easily by using FireBug. Simply navigate to the page, right click the element, click Inspect Element with FireBug, then right click the element and click 'Copy CSS Path'.
For example:
Lets say you have a div with id='pageC'.
You could use a css selector like #pageC #Next

Your code is overly complex for the task you are attempting to complete.
First get your IWebDriver:
IWebDriver driver = new FirefoxDriver();
You should be using an implicit wait in this scenario, lets start with our timeout set to 15 seconds. This will ensure that driver.FindElements() calls will search for 15 seconds before timing out. If the element is found before the 15 seconds is up, it will stop the wait at that moment.
driver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(15));
Since your next button has the same id on each of the pages, we can use By.Id("Next") to find the next button on each page.
driver.FindElement(By.Id("Next"));
Putting it all together:
IWebDriver driver = new FirefoxDriver();
driver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(15));
for(int page = 1; page < 5; page++) //Iterate through all pages, click next when applicable
{
driver.FindElement(By.Id("Next"));
}
or
IWebDriver driver = new FirefoxDriver();
driver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(15));
if(driver.FindElement(By.Id("Next")).Enabled) //Ensure button is clickable before proceeding
{
driver.FindElement(By.Id("Next"));
}

My guess is that your script is going fast enough that it is clicking the Next button a second time on Page 2 before Page 3 gets a chance to load. One way around this is to wait for the Next button to be stale. The basic logic would be
1. Click the Next button
2. Wait for the Next button to be stale
The code might look something like
public void Continue()
{
btnNext.SafeClick();
btnNext.WaitForStale();
}
where .WaitForStale() is just
wait.until(ExpectedConditions.stalenessOf(btnNext));
Also, unless you've really simplified your Page Objects, there doesn't seem to be a reason to have Page_1 through Page_5. If they all have the same functionality, they should be in a single page and you would just reinstantiate the page each time you move to the next page, e.g.
public partial class Form1 : Form
{
Page_1 pageA = new Page_1();
pageA.PerformSomeLogic();
pageA.Continue();
pageB = new Page_1(); <-- this can be Page_1() or better yet, renamed to ContentPage or something that better represents the type of page rather than the page number.
pageB.PerformSomeLogic();
pageB.Continue();

Related

Selenium can no longer click a button after page refresh

I had already looked at StaleElementReference Exception in PageFactory before setting this question and none of the answers there seemed to resolve my issue.
I am having problems resolving the error message
OpenQA.Selenium.StaleElementReferenceException : stale element
reference: element is not attached to the page document.
I know exactly what is causing the issue but cant find a way to resolve it.
Code below but as an overview.
I go to the Account Page
Update Address Fields and click Update Address button,
Refresh the Browser,
Confirm the Address has been updated.
I then Clean up by reentering the original Address Data. However when I try to click the Update Address button it is showing as no longer attached to the page (although it is visible). I am using the Page Factory Model as my framework. I understand that to resolve the error I need to find the element again but I can't work out how to do this. Whatever method I use I need to be able to apply it across my whole framework as I have a gut feeling it is going to crop up repeatedly on the site I am testing.
My Code
test throwing the error.
[Test]
public void Change_Account_Address()
{
Page.headerView.ClickOnLogin();
Page.loginPage.EnterUserNameandPasword(_testName);
Page.accountPage.ConfirmAtAccountPage(_testName);
Page.accountPage.UpdateAccountAddress(_testName);
Browsers.Refresh();
Page.accountPage.ConfirmAddressisUpdated(_testName);
Page.accountPage.UpdateAccountAddress("ResetAccountAddress"); - Error occurs on this step.
Page.accountPage.LogOut();
}
Account page button that is causing the error
[FindsBy(How = How.CssSelector, Using = "#myAccountForm > div:nth-child(3) > button")]
[CacheLookup]
private IWebElement UpdateDetails { get; set; }
Using that button in the test
public void UpdateAccountAddress(string testName)
{
var testData = ExcelDataAccess.GetTestData(testName);
AddressLine1.EnterText(testData.AddressLine1, "AddressLine1");
AddressLine2.EnterText(testData.AddressLine2, "AddressLine2");
AddressLine3.EnterText(testData.AddressLine3, "AddressLine3");
City.EnterText(testData.City, "City");
AccountPostcode.EnterText(testData.AccountPostcode, "AccountPostcode");
UpdateDetails.ClickOnIt("UpdateButton");
}
Click on it Extension
public static void ClickOnIt(this IWebElement element, string elementName)
{
element.Click();
Console.WriteLine("Clicked on " + elementName);
}
Finally my Page Class
public class Page
{
private static T GetPage<T>() where T : new()
{
var page = new T();
PageFactory.InitElements(Browsers.getDriver, page);
return page;
}
public static AccountPage accountPage
{
get
{
return GetPage<AccountPage>();
}
}
}
Try this basic stuff
public static void ClickOnIt(this IWebElement element, string elementName)
{
Thread.sleep(3000); // sleep for 3 seconds
element.Click();
Console.WriteLine("Clicked on " + elementName);
}
Then use Expicit wait instead of sleep method as it is not recommended .

How to wait until selenium completes its navigation?

Ok here my code and but it immediately executes
private static ChromeDriver mainDriver;
mainDriver.Navigate().GoToUrl(srFetchUrl);
string srPageSource = mainDriver.PageSource;
I have to get the source code after the page is actually navigated to new page and page is loaded
You can try this method, this will wait until page loads completely and you can add your expected time to page load.
public void E_WaitForPageLoad() throws Exception
{
JavascriptExecutor js = (JavascriptExecutor)driver;
//This loop will rotate for 100 times to check If page Is ready after every 1 second.
//You can replace your if you wants to Increase or decrease wait time.
int waittime;
waittime = 60;
for (int i=0; i<waittime; i++)
{
try
{
Thread.sleep(1000);
}catch (InterruptedException e) {}
//To check page ready state.
if (js.executeScript("return document.readyState").toString().equals("complete"))
{
//System.out.println("Wait for Page Load : "+js.executeScript("return document.readyState").toString());
break;
}
}
System.out.println("\nWeb-Page Loaded.");
}
Thank You,
Ed D, India.
Specify , implicit or explicit wait till the element in the page is loaded.
refer this link for C# wait syntax

Selenium WaitDriver doesn't wait for element to be clickable

When I click on a button it displays me a form with another buttons and I want to click on one of them. Here is a video with this (really short one), please watch http://screencast.com/t/zyliSemW1s1
So I click on button "Buy Tickets" simply like that:
button.Click();
And then I wait for the next button to be clickable.
I use the next code:
WebDriverWait wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(15));
IWebElement element = wait.Until(ExpectedConditions.ElementToBeClickable(myButton));
After that I click on button that I waited for:
element.Click();
And I get error: Element is not clickable at that point.
As I know, the method ExpectedConditions.ElementToBeClickable() waits for 2 conditions: element is visible and element is enabled.
When I use Thread.Sleep(3000) before clicking on the second button the code works and button is clickable.
I saw similar issue and the solution was to wait for handlers of this button:Selenium Wait doesn't wait for Element to be Clickable
But what to do if I don't know what handles it? I think it handles by jQuery and I use the next code to wait till it stops executing:
var ajaxIsComplete = (bool)
((IJavaScriptExecutor)Driver).ExecuteScript("return jQuery.active == 0");
If it returns "false" I wait and check again.
But it still doesn't work.
So for now my flow goes like that:
I click on button "Buy Tickets"
I wait till jQuery stops executing
I wait till element is clickable using ExpectedConditions.ElementToBeClickable() method
I click on the element and it returns me an error that it is not clickable.
Please guys tell me what is wrong in my flow and how to manage it correct.
Update:
I'm adding HTML code of buttons:
I click to this one:
<button class="btn btn-warning play-now" name="button" type="submit">Buy Tickets</button>
And wait for this one:
<img alt="Credit Card" class="merchant" src="https://numgames-production.s3.amazonaws.com/uploads/merchant/image/21/CC_Offline.png?AWSAccessKeyId=AKIAJ2Q64HPERGHAJJUA&Expires=1470984765&Signature=Qj%2BFSQ3ElctkY6KTMfzp%2FedPjPo%3D">
Denis,
As mentioned in comments to OP, here are a few little extension methods that may help your quest:
public static void WaitForAjax(this IWebDriver driver, int timeoutSecs = 10, bool throwException = false)
{
for (var i = 0; i < (timeoutSecs*10); i++)
{
var javaScriptExecutor = driver as IJavaScriptExecutor;
var ajaxIsComplete = javaScriptExecutor != null && (bool)javaScriptExecutor.ExecuteScript("return jQuery.active == 0");
if (ajaxIsComplete) return;
Thread.Sleep(100);
}
if (throwException)
{
throw new Exception("WebDriver timed out waiting for AJAX call to complete");
}
}
public static bool ElementExists(this IWebDriver driver, By condition)
{
return ElementExists(driver, condition, new TimeSpan());
}
public static bool ElementExists(this IWebDriver driver, By condition, TimeSpan timeSpan)
{
bool isElementPresent = false;
if (timeSpan == default(TimeSpan))
{
timeSpan = TimeSpan.FromMilliseconds(15000);
}
var driverWait = new WebDriverWait(driver, (TimeSpan)timeSpan);
driverWait.IgnoreExceptionTypes(typeof(WebDriverTimeoutException));
isElementPresent = driverWait.Until(x => x.FindElements(condition).Any());
return isElementPresent;
}
public static IWebElement FindElementAfterWait(this IWebDriver driver, By condition, int fromSeconds = 90)
{
bool isElementPresent = false;
IWebElement singleElement = null;
var driverWait = new WebDriverWait(driver, TimeSpan.FromSeconds(fromSeconds));
driverWait.IgnoreExceptionTypes(typeof(WebDriverTimeoutException));
try
{
isElementPresent = driverWait.Until(ExpectedConditions.ElementExists(condition)) != null;
if (isElementPresent)
{
singleElement = driver.FindElement(condition);
}
}
catch
{
// log any errors
}
return singleElement;
}
usages:
bool elementExists = _driver.ElementExists(By.Id("submitButton"));
var submitButton = _driver.FindElementAfterWait(By.Id("submitButton"));
submitButton.Click();
_driver.WaitForAjax();
// then do other code stuff...
Hopefully, a combo of these may get you out of the fix.
This is a typical problem of working with any asynchronous (AJAX) page.
You don't need to use any "magic" methods like sleeps, jquery.active and expected conditions.
Web pages are usually built that way that user would see when operation ends - like some message appears, or some button become enabled. I believe in your case something like this also happens after you click "Buy Tickets" - you need to notice this and wait for this in your code.
That wait needs to be performed using Explicit wait for your specific case. This is the only reliable way to manage asynchronous pages (including elements not yet clickable).
You can see more detailed overview in my blog post - Approaches to handling AJAX in Web Automation (Selenium)
Instead of using ElementToBeClickable, try using presenceOfElementLocated. I think your expected element is not present on DOM, so try using presenceOfElementLocated first. Once it is present on DOM, then use ElementToBeClickable.
// public void test()
{
IWebDriver driver = new ChromeDriver();
ClickSaveButton(driver,"MyButton",10); //Wait for 10 seconds
}
//Customized wait block
public void ClickSaveButton(IWebDriver driver,String ElementID = "" int TimeOut)
{
Console.Error.WriteLine("Waiting....");
try
{
driver.FindElement(By.Id(ElementID)).Click();
}
catch (Exception exception)
{
Thread.Sleep(1000);
if (TimeOut > 0) ClickSaveButton(driver, TimeOut - 1);
}
}
I was facing this problem and checking if the element is clickable, visible, located (or their combinations) etc was not enough. Selenium was still not waiting and trying to click on the element.
The only solution I found for my case was a bad practice, but functional as a workaround. I try to get the element inside a Loop with Try/Catch as found here in Falgun Cont response:
StackExchange - How to wait for element to be clickable in WebDriver with C#

Where do I put my object when using asp

I'm relatively new to C# and VS, and currently having a play with ASP.NET, but I seem to be getting very muddled on where I should place my objects that I need to use within my webforms. As a very basic example, how could I add a counter button? With the code below, when you click the button nothing changes and the textbox just shows The count is 2'. I think this is because the page is reloading each time and therefore the Counter object gets 're' instantiated. So how do I avoid that?
Default.aspx
...
<asp:Button ID="bttnCounter" runat="server" Text="Click Me" OnClick="ButtonClick"/>
...
Default.aspx.cs
public partial class _Default : Page
{
Counter counter = new Counter();
protected void Page_Load(object sender, EventArgs e)
{
bttnCounter.Click += new EventHandler(this.ButtonClick);
}
public void ButtonClick(Object sender, EventArgs e)
{
counter.CountUp();
output.Text = "The count is " + counter.CurrentCount;
}
}
Counter.cs
public class Counter
{
public int CurrentCount { get; private set; }
public Counter()
{
CurrentCount = 0;
}
public void CountUp()
{
CurrentCount++;
}
}
I may have just completely mis understood this, but when I was using WinForms, I would include the object within the form code.
You should save it in the pages ViewState, since that is the only persistent user-based storage that isn't session bound (you can open the same page multiple times).
this.ViewState["someVar"] = yourObject;
Retrieve it later:
YourClass yourObject = (YourClass)this.ViewState["someVar"];
You are correct, the page is loading each time and thus resets your counter to zero each time you click the button.
there are a number of approaches to solve this, the simplest is perhaps to use the Session["counter"] object to store the counter and reload it on page load.
However, as you are new I would suggest you abandon this style of asp.net and instead learn the new MVC version
this has a different approach which avoids many of the page lifecycle problems of asp.net (webforms) although you will still need to store the counter either on the server, or in the page response to the user so it can be sent back in the query string, cookie or whatever

How to scroll a web page in Coded UI Test with VS 2012?

I'm recording Coded UI Tests with VS 2012, which shall test the functions of a web application.
After I loaded the web page, I click on a button to start p.e. a job application.
After the next page has loaded on the same site, my problem begins.
The entry controls are at the end of the web site.
To take a look and input data into the entry controls, I must scroll down.
The recording produced the following method in the UIMap.
Designer.cs:
public void Scrollen()
{
#region Variable Declarations
Playback.PlaybackSettings.WaitForReadyLevel = WaitForReadyLevel.AllThreads;
this.UIGoogleMozillaFirefoxWindow.UIItemPropertyPage.UIBewerbungDemoFirmaDocument.WaitForControlExist();
this.UIGoogleMozillaFirefoxWindow.UIItemPropertyPage.UIBewerbungDemoFirmaDocument.WaitForControlReady();
Playback.PlaybackSettings.WaitForReadyLevel = WaitForReadyLevel.UIThreadOnly;
WinControl uIBewerbungDemoFirmaDocument = this.UIGoogleMozillaFirefoxWindow.UIItemPropertyPage.UIBewerbungDemoFirmaDocument;
#endregion
// Click "Job application" document
Point pt = new Point(1390, 553);
int count = 0;
while (!uIBewerbungDemoFirmaDocument.TryGetClickablePoint(out pt) && count < 20)
{
count++;
System.Threading.Thread.Sleep(10);
if (count == 20)
Console.WriteLine("ClickablePoint not found");
}
Mouse.Click(uIBewerbungDemoFirmaDocument, new Point(1390, 553));
Mouse.MoveScrollWheel(10);
}
As You can see, I tried WaitForControlExist, WaitForControlReady, TryGetClickablePoint and the method MoveScrollWheel.
But neither Mouse.Click nor Mouse.MoveScrollWheel are working.
And in the next method, where I click into the first of the entry fields, I get a message at execution time, that the click event produces an error, because the control is hidden (because it's down below on the website, out of visible range).
After several tests this is making me crazy.
Any idea what has gone wrong and how can I scroll down the web site, so my entry controls are in visible range?
You can try Control.EnsureClickable(). Or you can use below mentioned function to scroll the page until the control is not clickable.
public static void ScrollAndClick(HtmlControl Control)
{
bool isClickable = false;
if (Control.TryFind())
{
while (!isClickable)
{
try
{
Control.EnsureClickable();
Mouse.Click(Control);
isClickable = true;
}
catch (FailedToPerformActionOnHiddenControlException)
{
Mouse.MoveScrollWheel(-1);
throw;
}
}
}
else
{
throw new AssertInconclusiveException("Control Not Found");
}
}
You can also add condition related to timeout to make sure it don't go to infinite loop.
Let me know if you are having issue with this at your end.

Categories