Sunday, March 11, 2018

Deprecating Parts of Selenium's .NET Bindings

Note: This blog post should be considered a work-in-progress until this note is removed.

With the release of 3.11.0 of the Selenium .NET bindings, a few things in the support library (WebDriver.Support.dll) have been marked with the Obsolete attribute. This will come as something of a surprise for some users when they update to that version. In particular, the .NET implementation of the PageFactory and the ExpectedConditions class used with WebDriverWait have been deprecated. It's understandable that there would be some consternation about suddenly seeing compile warnings mentioning removing components in a future release of Selenium, particularly if one's own code makes use of those components. Why would the .NET bindings' maintainers do this?

First, one must consider the original intent of the WebDriver.Support.dll assembly. When originally created, it was originally designed to showcase some of the things that would be possible to create based on the WebDriver API. It was not intended that large numbers of users would plug those examples directly into production code.

Second, the .NET implementation of these constructs was created mostly because some users asked, "Java has it, so why doesn't .NET?" Rather than blindly copying the Java implementations as was done, it would have been better to think about what actually makes sense when using C#. In other words, "C# isn't Java, and therefore the things that work best for Java may not be entirely appropriate for C#."

In the case of the .NET PageFactory, the implementation was problematic and cumbersome, as well as not nearly flexible enough for the myriad ways people wanted to create Page Objects. Additionally, when .NET Core 2.0 was released, the classes upon which the .NET PageFactory relied were not included .NET Core 2.0. This meant that to get the PageFactory working under .NET Core, the project either had to take on a new dependency, mangle the code with conditional compile directives, or leave it unsupported in .NET Core. The first approach is a non-starter for the Selenium project's .NET bindings, the reasons for which should be a subject of its own blog post. The second approach made the code nearly impossible to properly maintain.

Furthermore, with respect to the PageFactory in particular, there is no benefit to be gained by identifying elements via an attribute over doing it directly in runtime code. Claims that the PageFactory made Page Object creation and maintenance less verbose simply do not hold up under close scrutiny.

With respect to ExpectedConditions, again, this was an addition that was created in .NET solely because "Java has it." At the time the ExpectedConditions class in Java was created, the syntax for creating a lambda function (or something that acted like one) was particularly arcane and difficult to understand. In that case, a helper class made lots of sense for the Java bindings. However, C# isn't Java. In C#, the syntax for creating lambda functions ("anonymous methods" in the language of Microsoft's documentation) has been well understood by C# developers for many years, and is a standard tool in their arsenal.

In this case, the question of code verbosity does have some merit, but since wait conditions are rarely one-size-fits-all, it would be a much cleaner approach for users to develop their own conditions class that has the wait conditions they're interested in. This, however, is something users have an aversion to. Additionally, the thought of a "standard" collection of implementations of specific wait conditions seems to be a good idea on its face, but there is a great deal of variation on the way users want any given condition to work. Having a collection of wait conditions might be a good thing, but the Selenium project is not the place for it.

So that people would still have access to the existing implementations, a new organization has been set up on Github. The code for these two pieces of the support library have been migrated there, and the first binary artifacts have been distributed concurrent with Selenium 3.11. People can move over to the ported implementations with minimal change (usually just a namespace change) to their own code. The new repo is awaiting someone from the community who feels strongly about maintaining these types of libraries.

8 comments:

  1. While I can see your point, I still think that proxied elements might be useful in some cases.
    You cannot pass element object, defined as property, as an argument, for example (preserving it's 'laziness').
    It is possible to make proxy to re-execute action on element in case of staleElement exception, another example.
    (I realize it is possible using other ways, but anyway)

    I'm willing to maintain that library, unless you find a better candidate.

    Have a nice day!

    ReplyDelete
  2. "Furthermore, with respect to the PageFactory in particular, there is no benefit to be gained by identifying elements via an attribute over doing it directly in runtime code"

    My opinion is at the opposite of yours on this point. But that's because I have made an overlay to Selenium where I NEVER identify an element directly.

    Here is a sample of my PageObjects :
    public List WebElementsMonitor(bool readData)
    {
    var lst = new List();
    var enableMonitor = WebElementValueHelper.DefineCheckbox(this.CheckboxEnableMonitoring, readData);
    lst.Add(enableMonitor);
    if ((bool) enableMonitor.Value)
    {
    lst.DefineInput(this.InputMonitoringIpAddress, readData);
    lst.DefineInput(this.InputSnmpReadCommunity, readData);
    lst.DefineInput(this.InputSnmpWriteCommunity, readData);
    }
    return lst;
    }

    My vision of testing with Selenium was that my PageObject class must define all atomic elements of my page (with the proxy IWebElement) and the global intelligence of my page will be defined in this class (my function WebElementsMonitor for example.
    In this method, I use the IWebElement to define a WebElementValue (which contains IWebElement, the value, the type of my element (DropDown, Text, ...), index (if I'm in an array) and other stuff...
    When I want to manipulate values in this page, I can use my static classes WebElementsHelpers.SetData/CheckData/GetData. The intelligence stills always the same and thoses methods fill my WebElementValue with the values I wanted in my page.
    I wrote methods like to simplify the coding and the loging.

    With my architecture and my vision, the PageFactory is a real gift. Write some scenarios is really simple, I always understand everything my code tried to do (especially when I run 20 parallel task of Selenium on the same project...)
    I decree that I NEVER NEVER NEVER use identification outside the PageFactory (except for some case with single page applications)

    ReplyDelete
  3. For ExpectedConditions you say;

    "the syntax for creating lambda functions ("anonymous methods" in the language of Microsoft's documentation) has been well understood by C# developers for many years, and is a standard tool in their arsenal."

    I'm relatively new to C# so how would you implement something like the current way

    Wait.Until(ElementToBeClickable(Element))

    using lambda?

    I currently locate elements using expression bodies like

    private IWebElement elementname => Wait.Until(ExpectedConditions.ElementToBeClickable(Driver.FindElementById("elementId")));

    I'm just not sure what to use once expected conditions gets removed.

    ReplyDelete
    Replies
    1. Wait.Until(_driver =>
      {
      return _driver.FindElement(By.Id("elementId")).Enabled;
      });

      That return statement can be modified to return any complex conditional boolean possible.

      For example, Click on Login, then Wait.Until either a) validation message appears, b) error message appears, c) user page is returned, d) admin page is returned, e) server error page is returned, etc.

      Or you can make it as simple as waiting for an element to be visible and enabled before clicking.

      Delete
    2. Well the code :
      Wait.Until(_driver =>
      {
      return _driver.FindElement(By.Id("elementId")).Enabled;
      });
      Does not work. It does not wait for the element to become clickable.

      Delete
  4. Do we have new classes to replacing PageFactory in next?

    ReplyDelete
    Replies
    1. Not provided by the Selenium project. If you'd like to continue using the PageFactory as currently coded, you'll need to migrate to the DotNetSeleniumExtras version of the PageFactory.

      Delete
    2. SeleniumExtras.PageObjects.PageFactory is not available either? DotNetSeleniumExtras github says the repository is moved to Selenium.Support. Selenium Github repository says it is covered in DotNetSeleniumExtras. This goes in round robin and stalemate. So i there a short cut in Selenium C# the way it is used in other Selenium languages?

      Delete