C# and the disappearing PageFactory – My next steps in Selenium testing

C# and the disappearing PageFactory – My next steps in Selenium testing

This entry is part 1 of 5 in the series Building a .Net Core PageFactory Framework

Being an Open Source project maintainer is a thankless task

Ask Jim Evans, the maintainer of the C# (aka dotnet) language bindings for Selenium Webdriver as well as as the Internet Explorer driver that I am sure just about everyone who has ever written a Selenium test has used. Until I started my new job at Altitude Angel 16 days ago I had spent very little time worrying about the C# language bindings as I have been working in Java. As someone keen to learn however, I have been following him for some time and trying to keep up to date with developments in the wider Selenium world. As I have mentioned previously, my mentor was a dotnet specialist and I haven’t been afraid to check out the dotnet code for ideas of how to do things in Java.

March 11th (3/11) was a fine day to choose for the release of Selenium 3.11 aka ‘Selenium for Workgroups.’ (Yes really – Firing up the standalone server even announced itself as such.) I am quite sure that everyone expected that homage to ‘Windows for Workgroups 3.11’ to be the most significant event of the release, but it didn’t quite work out that way. In the dotnet release notes a couple of items were announced as obsolete:

v3.11.0
=======
* Added support for intercepted element clicks. This adds a new exception,
`ElementClickInterceptedException` to indicate when an element is obscured
by another element. This allows users to distinguish between different
causes of elements not being clickable.
* Marked .NET ExpectedConditions obsolete. Using the ExpectedConditions
class provides no benefit over directly using lambda functions (anonymous
methods) directly in one’s code. Since the community appears to believe
that an “official” repository of wait conditions is desireable, the
existing code has been migrated to a new repository under a new organization
on GitHub (https://github.com/DotNetSeleniumTools/DotNetSeleniumExtras).
It is hoped that this will encourage a volunteer from the community to take
ownership of this code. Users should update their references and migrate
their code to use `SeleniumExtras.ExpectedConditions`. This implementation
will be removed from the .NET language bindings in a future release
* Marked .NET PageFactory obsolete. The .NET implementation of PageFactory
is deeply flawed. Additionally, using the PageFactory provides no benefit
over other methods of Page Object creation in .NET. This is true for code
verbosity as well, which is often the reason cited for wanting to use the
PageFactory in .NET. The existing code has been migrated to a new repository
under a new organization on GitHub
(https://github.com/DotNetSeleniumTools/DotNetSeleniumExtras). It is hoped
that this will encourage a volunteer from the community to take ownership
of this code. Users should update their references and migrate their code
to use `SeleniumExtras.PageFactory`. The implementation will be removed
* Updated .NET Actions class to change element offsets for W3C mouse move.
When the Actions class performs a mouse move that includes a reference to
an element and an x-y offset, the offsets are calculated differently
depending on whether the remote end adheres to the W3C WebDriver
Specification. In the W3C case, the offsets are calculated from the center
of the element. This differs from the legacy behavior, which calculates
offsets from the top-left corner of the element. This change uses the
language bindings to allow the user to optionally choose which origin to
use, and by default calculates all offsets from the upper-left, if not
specified. This normalizes the mouse move behavior across all types of
remote ends.

It is fair to say that this has not been entirely popular with the community that use the C# PageFactory. It would appear that poor Jim has been taking a lot of really undeserved flak for focusing his limited time on maintaining things that are of high value to the testing community.

Why this makes perfect sense

(Standard ‘I’m not an expert’ disclaimer here – My understanding of the C# language is very basic, and some of my beliefs about it and history may be incorrect. Please feel free to correct me!)

Instead of starting with why people were upset about this, lets explain the reasoning. I certainly haven’t been around long enough to know, but I suspect that in both of these cases, they started out as ports of the Java code to C#. As is usually the case with ports, it followed the same approaches to make something that resembled its Java implementation. Of course with most ports, eventually someone looks at it and decides that it doesn’t really work in the way that it would do if it had been written in that language initially.

ExpectedConditions

The ability to use lambdas to define the condition to end a wait in Java have been there for some time. However it is worth pointing out that Lambdas have only been properly supported in Java since version 8 in 2014. It made sense therefore to to define a way to define the conditions to exit a wait. I suspect that there are still many Java developers out there that have yet to get to grips with lambdas and I still remember the pain that it caused me.

C# on the other hand has had support for lambdas since November 2007. Any C# developer should be entirely familiar with using them. They provide the ability to generate far more complex conditions and so their use really should be a ‘no-brainer.’

PageFactory

Its only now that I have started to write Selenium tests in C# that I understand why the implementation was described as deeply flawed. The reason for dropping support now is due to it using proxies that have been deprecated from dotnet, but the reality is that they probably should never have existed in the first place. Let me explain…

When I was trying to get my defined rootElement PageBlocks working in Java I had problems at first with trying to instantiate the Page Objects when the Page was not displayed in the browser. You need to have lazy instantiation and as far as I am aware, you need the proxies as used in the Java PageFactory in order to do this. This is not however true in C# as properties allow you to define a call that is not made on instantiation. This is explained very clearly by Jim Evans in the issue where the deprecation of the .net proxies was raised. Whilst Liraz Shay was correct that this is more verbose, I totally understand that using complex proxies and reflection is an unjustifiable maintenance problem (and probably slower performing too.)

So why were people upset?

In short,

  1. This requires maintenance of existing code when upgrading to Selenium 3.11+. It is not a great deal right now (search and replace a couple of using calls and import another reference package) but may be more problematic if the need arises to support new or different .net runtimes.
  2. People were asked to step up and take ownership of this if they still wanted it to be maintained.

That’s it really. To the best of my knowledge, no one has yet stepped up to take ownership. If I felt that it was worth the effort I would have had a go at fixing it, but the reasons outlined above seem valid. Perhaps I can find a means of reducing the boilerplate code involved, but not at the expense of needless complication.

So how will that work out for me writing Selenium tests in C#

As it happens, the tests that we have already, have not been written using the PageFactory. I will be developing something like my Java code, but no initialising the pages is required. I will be using ‘PageObjects’ similar to those described by LirazShay in the issue I mentioned above. The difference is that I use (as I did in Java) an additional abstraction layer.

  • Pages and Blocks perform actions on elements (e.g. Click() or SendKeys()) in which case their return type is void, or they return data from the Page. They do not assert but will perform waits where it is appropriate to do so.
  • Steps hold the state of the view by composition (Usually ONE Page and potentially many Blocks) and may chain together multiple Page/Block calls to perform a more complex step. In Java I have been returning the appropriate steps class to provide a fluent interface for my Test classes. So far, I am less convinced about the use of this in C#.

So for a Page:

    public class FooBarPage
    {
        private readonly IWebDriver driver;
        
        public FooBarPage(IWebDriver driver) {
            this.driver = driver;
        }

        private IWebElement FooElement
        {
            get
            {
                return this.driver.FindElement(By.Id("foo"));
            }
        }

        private IWebElement BarElement
        {
            get
            {
                return this.driver.FindElement(By.Id("bar"));
            }
        }

        public void ClickFoo()
        {
            FooElement.Click();
            return this;
        }
        
        public string getBarText() 
        {
            return BarElement.Text;
        }
    }

and for the corresponding Steps class

    public class FooBarSteps
    {
        private FooBarPage fooBarPage;

        public FooBarSteps(FooBarPage fooBarPage)
        {
            this.fooBarPage = fooBarPage
        }

        public FooBarSteps AssertBarText(string barText)
        {
            fooBarPage.GetBarText()
                .Should()
                .Be("bar");
            return this;
        }
        
        public void clickFoo() 
        {
            fooBarPage.ClickFoo()
        }
    }

More next time when I’ll try to explain a Java developer’s first impressions on switching to C# and share some more sample code.

Progress made:

  • A new job working in C#. A lot to take on board.
  • A basic outline of an effective way to code Page Objects in C# without the obsolete PageFactory implementation.
  • Some tidying up and updates on my Java code. (It is in the repository if you want to check it out.)

Lessons learnt:

  • C# the language is similar to Java, but the subtle differences are not so subtle.
  • Switching from IntelliJ IDEA to Visual Studio 2017 is a far bigger shock than dealing with the language change.
  • Cycling 6km to/from the railway station is so much nicer than driving the whole way to work.
  • Dealing with delayed and cancelled trains is not so fun.
  • Get the toughest lock you can if leaving your bike at a UK railway station. Had mine stolen on day 7 parked there (along with most of the bikes it was parked alongside it.) 🙁

Edit: 3 October 2018

To Be Continued….

I am finally ready to start on my .net project. Follow my progress in this series.

Edit: 27 June 2019

That didn’t work out quite as planned….

Of course in order to write a PageFactory I need to generate WebDriver instances first. The series Launching WebDrivers in .NET Core the easy way now contains the posts describing my path to completing that. They were originally in this series:

Now to start again on a PageFactory…. Coming soon

Series NavigationC# PageFactory – Why I don’t use properties. >>
Comments are closed.