Selenium can no longer click a button after page refresh - c#

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 .

Related

Multiple binding to RelayCommand in WPF MVVM Light

I have started working with WPF MVVM Light and now I'am trying to navigate between pages.
In the MainWindow I have added a "BackButton"
<Button Command='{Binding Main.GoBack, Mode=OneWay}' />
which is binding to MainViewModel method "RelayCommand GoBack".
private RelayCommand _goBack;
public RelayCommand GoBack
{
get
{
return _goBack
?? (_goBack = new RelayCommand(
() =>
_navigationService.GoBack();
}));
}
}
Why is this button changing view only once? If I want to click it secound time
it doesn't work (nothing happend). If I change page for another by another button its starting work again and againg only for once.
Part of implementation of FrameNavigationService:
public FrameNavigationService()
{
_pagesByKey = new Dictionary<string, Uri>();
_historic = new List<string>();
}
public void GoBack()
{
if (_historic.Count > 1)
{
_historic.RemoveAt(_historic.Count - 1);
NavigateTo(_historic.Last(), null);
}
}
public void NavigateTo(string pageKey)
{
NavigateTo(pageKey, null);
}
public virtual void NavigateTo(string pageKey, object parameter)
{
lock (_pagesByKey)
{
if (!_pagesByKey.ContainsKey(pageKey))
{
throw new ArgumentException(string.Format("No such page: {0} ", pageKey), "pageKey");
}
var frame = GetDescendantFromName(Application.Current.MainWindow, "MainFrame") as Frame;
if (frame != null)
{
frame.Source = _pagesByKey[pageKey];
}
Parameter = parameter;
_historic.Add(pageKey);
CurrentPageKey = pageKey;
}
}
What can I do to handle this? May be I should do it tottaly differently?
You should possibly not be doing goback at all.
Unless you really want to use the journal, using a frame and pages is a bad idea. It's a rare requirement to go back to the last view in desktop apps. What with them not being a web browser.
Maybe you have that requirement though.
If you have a frame then you have it's journal and you can just call goback on the frame's navigationservice.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.navigation.navigationservice.goback?view=netframework-4.8
You set keepalive on pages.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.page.keepalive?view=netframework-4.8
You wrote that code and it seems to be largely reproducing navigationservice functionality. From what you've shown us.
As it is.
Use type rather than a magic string as the key. A type is checked at compile time, a magic string is not and you can make mistakes.
Have you explored this issue at all? I think maybe this is one of those times that telling someone what they did wrong isn't really helping as much as telling them how they ought to diagnose.
Debugging is a key skill for any developer.
You have the code running in front of you.
Put break points in, step through and examine what is happening.
When you navigate, what ends up in _historic?
When you goback, what happens exactly?
When you click the goback that second time what path does it go down and what state is causing that.
Make sure you are using RelayCommand in GalaSoft.MvvmLight.CommandWpf,not at GalaSoft.MvvmLight.Command.RelayCommand

Issues with elements that have same By locator on multiple pages

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();

Reload Data every time I navigate to Page

I have this Windows Phone Page where I load data through the standard ViewModel scope.
public Profile()
{
InitializeComponent();
App.PersonalizedViewModel.favorites.Clear();
DataContext = App.PersonalizedViewModel;
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
if (!App.PersonalizedViewModel.IsDataLoaded)
{
App.PersonalizedViewModel.LoadData();
}
}
This works fine. However when I navigate to this page from some other page the data is still the same. I mean the LoadData() method should recheck updated data right? Please suggest.
EDIT:
My PersonalizedViewModelClass:
public class PersonalizationViewModel: INotifyPropertyChanged
{
public PersonalizationViewModel()
{
this.favorites = new ObservableCollection<ItemViewModel>();
this.Bar = new ObservableCollection<Bars>();
}
public ObservableCollection<ItemViewModel> favorites { get; private set; }
public ObservableCollection<Bars> Bar { get; private set; }
private string _sampleProperty = "Sample Runtime Property Value";
public string SampleProperty
{
get
{
return _sampleProperty;
}
set
{
if (value != _sampleProperty)
{
_sampleProperty = value;
NotifyPropertyChanged("SampleProperty");
}
}
}
public bool IsDataLoaded
{
get;
private set;
}
/// <summary>
/// Creates and adds a few ItemViewModel objects into the Items collection.
/// </summary>
public async void LoadData()
{
favorites.Clear();
try
{
var query = ParseObject.GetQuery("Favorite")
.WhereEqualTo("user", ParseUser.CurrentUser.Username);
IEnumerable<ParseObject> results = await query.FindAsync();
this.favorites.Clear();
foreach (ParseObject result in results)
{
string venue = result.Get<string>("venue");
string address = result.Get<string>("address");
string likes = result.Get<string>("likes");
string price = result.Get<string>("price");
string contact = result.Get<string>("contact");
this.favorites.Add(new ItemViewModel { LineOne=venue, LineTwo=address, LineThree=likes, Rating="", Hours="", Contact=contact, Price=price, Latitude="", Longitude="" });
}
if (favorites.Count == 0)
{
// emailPanorama.DefaultItem = emailPanorama.Items[1];
MessageBox.Show("You do not have any saved cafes. Long press a cafe in main menu to save it.");
}
}
catch (Exception exc)
{
MessageBox.Show("Data could not be fetched!", "Error", MessageBoxButton.OK);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Implementation of PersonalizedViewModel:
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
await App.PersonalizedViewModel.LoadData();
user_tb.Text = ParseUser.CurrentUser.Username;
if (NavigationContext.QueryString.ContainsKey("item"))
{
var index = NavigationContext.QueryString["item"];
var indexParsed = int.Parse(index);
mypivot.SelectedIndex = indexParsed;
}
if (NavigationService.BackStack.Any())
{
var length = NavigationService.BackStack.Count() - 1;
var i = 0;
while (i < length)
{
NavigationService.RemoveBackEntry();
i++;
}
}
}
I don't see the problem, however, I think you need to narrow in on the problem.
First off, you are calling LoadData from 2 places. 1 from MainPage_Load and 1 from OnNavigatedTo. In MainPage_Load it is conditional and in OnNavigatedTo it is always being called. I suggest that you get to a single path through the code instead of 2 so that you don't get different experiences. I personally recommend (without knowing all the details) that you call load data from OnNavigatedTo instead of MainPage_Load. If you want to do it conditionally that is fine but if you are loading the data from memory, it really is unnecessary as you won't improve performance anymore than a few milliseconds. Also, if you are not loading from memory, you may not want to load it conditionally because the underlying data may have changed. In either case, the choice to load data or not should be moved out of the view and into the data layer (but that is for another post).
Once you have a single path chosen (i.e. calling LoadData from MainPage_Load or OnNavigatedTo) you should use your debugger. Put a break point in LoadData method and if it is being called appropriately, then your problem is more specific than your posted question. Here are some questions to think about (you may want to start from the last question and work your way backward)
Questions:
Is LoadData being called appropriately?
Does ParseObject have the correct data?
Is the ParseUser...UserName set properly?
Is the foreach being executed the proper # of times (i.e. does the result of your query have the right # of items?)
Couple Code Tips completely unrelated to this problem:
Single Path through code. Don't call LoadData from more than one place.
Don't call favorites.clear() twice in the same method. (it is called twice in LoadData)
Consistent naming. favorites is lowercase but Bar is upper case.
User proper data types. On your ItemViewModel you have Hours, Latitude, and Longitude. You have them as strings. These clearly are not strings. Also, you should not set them to empty. Empty means they have been set to a value. Emtpy is a valid value. Null means not set. To keep your objects clean and accurate you want to be accurate in how you set things and then deal appropriately with the impact. If you really really want them to be initialized to empty strings, then at least do it in the constructor of ItemViewModel so that every caller doesn't have to know how to initialize every property. I guarantee this is leading to buggy code if you continue using this practice.
Please take the comments as constructive criticism not criticism. I know many people don't like to hear these things but the teams I lead write bugs until they start following these types of guidelines.
Good luck,
Tom
Instead of defining this
App.PersonalizedViewModel.favorites.Clear();
DataContext = App.PersonalizedViewModel;
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
into constructor i.e. Profile I would suggest remove this code from Constructor and add it into your OnNavigatedTo. so the data will load after navigation
Your OnNavigatedTo Method looks like follows
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
App.PersonalizedViewModel.favorites.Clear();
DataContext = App.PersonalizedViewModel;
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}
Might be your problem will solve.
Edit
Try this query
var results = (from find in ParseObject.GetQuery("Favorite").WhereEqualTo("user", ParseUser.CurrentUser.Username) select find);
Tried this:
var query = from favorite in ParseObject.GetQuery("Favorite")
where favorite.Get<string>("user") == ParseUser.CurrentUser.Username
select favorite;
IEnumerable<ParseObject> results = await query.FindAsync();
I had a similar Problem.All u want to do here is generate a new instance of the Page.U can do this in two Ways.
One Way is by forcing a GUID along with Page Navigation URI that will create a New Instance of the Page and your Load Data() will work.
NavigationService.Navigate(new Uri(String.Format("/MainPage.xaml?item={0}", Guid.NewGuid().ToString()), UriKind.RelativeOrAbsolute));
The Second Way to implement that Part of your Page in a User Control .Like create a User Control for Load Data() and put it in constructor.It will generate a new Instance everytime you load the Page.
If the problem persists in the front end,you can try this.
1.have you mentioned the below attribute in your xaml page?
<UserControl Loaded="MainPage_Loaded">
So that every time the page loads the data will get loaded on to the page.
2.The data must exist, if you have no problem in the code behind as it is a WPF application and not a web page.
Hope you find it useful.
Two changes required..
Remove the this.Loaded from OnNavigatedTo. That may not be required.
Second move the LoadData to OnNavigatedTo method
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
App.PersonalizedViewModel.favorites.Clear();
DataContext = App.PersonalizedViewModel;
// this.Loaded += new RoutedEventHandler(MainPage_Loaded);
if (!App.PersonalizedViewModel.IsDataLoaded)
{
App.PersonalizedViewModel.LoadData();
}
}
For the purpose of debugging, you can remove the line if (!App.PersonalizedViewModel.IsDataLoaded) and try.

Why do all pages open in the default browser instead of inside the form?

What I Expect to Happen
The webpage opens inside the form.
What Really Happens
The webpage opens in the default browser (in this case Chrome).
The Code
The Navigate(string) snippet is copied directly from MSDN.
using System;
using System.Windows.Forms;
namespace BrowserFrame
{
public partial class BrowserForm: Form
{
public BrowserForm()
{
InitializeComponent();
Navigate("http://www.stackoverflow.com");
}
private void Navigate(String address)
{
if (String.IsNullOrEmpty(address)) return;
if (address.Equals("about:blank")) return;
if (!address.StartsWith("http://") &&
!address.StartsWith("https://"))
{
address = "http://" + address;
}
try
{
webBrowser1.Navigate(new Uri(address));
}
catch (System.UriFormatException)
{
return;
}
}
}
}
What I Did So Far
Changing the default browser (e.g. IE, Firefox) opens the page in the default browser.
Using webBrowser1.Navigate(new Uri("http://www.stackoverflow.com")); directly does the same thing.
Tried calling Navigate from other events (e.g. OnLoad, MouseClick); same result.
Update
Turns out this happens to all web-based UI controls. (Posted a question on superuser.)
I don't understand why it would open in an external browser, as it doesn't happen to me, but you can try this and see if it makes any difference.
Have you tried:
webBrowser1.Navigate(address); without using an Uri?
It works fine for me, and it doesn't need http:// or https://.
I guess they automated that in the class constructor for strings.
Or perhaps you could try changing to this simple version:
public BrowserForm()
{
InitializeComponent();
webBrowser1.Navigate("http://www.stackoverflow.com");
}
Just to see if it helps.

Managed BHOs not instantiated using Protected Mode

I am writing a BHO for IE using C#. The code I'm concerned with is this:
public class BHO : IObjectWithSite, IOleCommandTarget
{
...
public BHO()
{
MessageBox.Show("Constructor called");
}
public int SetSite(object site)
{
MessageBox.Show("SetSite called!");
if( site != null )
{
_webBrowser = (WebBrowser) site;
_webBrowser.NavigateComplete2 += OnNavigateComplete2;
}
else
{
_webBrowser.NavigateComplete2 -= OnNavigateComplete2;
_webBrowser = null;
}
return 0;
}
private void OnNavigateComplete2(object pDisp, ref object URL)
{
MessageBox.Show("OnNavigateComplete2 called");
}
When IE is run with Protected Mode off, everything works fine. However, if Protected Mode is turned on, NavigateCompleted2() is called, but SetSite() and the constructor are never called (!?!). However, if I create a menu item which calls a method in the BHO class, or open a new tab, everything is correctly called.
Does anyone know why it doesn't work when I open a new IE window?
The full source listing can be found here.
Someone on MSDN answered my question: the constructor and method were still being called, but for some reason the MessageBoxes don't show when I open a new window in Protected Mode until the page is loaded. Variables weren't being set due to a different problem - the constructor was instantiating an object which was silently failing.
I now need help with a different (very much related) problem.

Categories