Skip to content

A page object pattern implementation for Selenium

License

Notifications You must be signed in to change notification settings

corker/NOpenPage

Repository files navigation

NOpenPage Build status NuGet Status Join the chat at https://gitter.im/corker/NOpenPage

A page object pattern implementation for Selenium.

As Martin Fowler explains in this article

When you write tests against a web page, you need to refer to elements within that web page in order to click links and determine what's displayed. However, if you write tests that manipulate the HTML elements directly your tests will be brittle to changes in the UI. A page object wraps an HTML page, or fragment, with an application-specific API, allowing you to manipulate page elements without digging around in the HTML.

Why?

There are some implementations for the page object pattern like Bumblebee or LoadableComponent in WebDriver.Support. Then why do you need another one? The motivation behind NOpenPage is to have a page object layer with a little learning curve and flexible enough to not to stand on your way.

What?

There are only three classes that you need to be aware of to start with NOpenPage:

  • Browser is a static class that is an entry point to page and control implementations;
  • Page is a base class for all page objects;
  • PageControl is a base class for all page controls.

How?

  1. Install a nuget package --
Install-Package NOpenPage
  1. Configure NOpenPage -- Here is a code from a sample project that can be found in this repository. The code shows how to configure NOpenPage with SpecFlow - BDD framework that implements Gherkin language for .NET environment.
namespace NOpenPage.Examples.SpecFlow.Steps
{
    [Binding]
    public class SharedSteps
    {
        private const string WebDriverKey = "WebDriver";

        static SharedSteps()
        {
            Browser.Configure(config =>
            {
                config
                    .WithWebDriverResolver(ResolveWebDriver)
                    .WithWebElementResolver(ResolveWebElement)
                    .WithWebElementResolver<SearchPanel>(ResolveSearchPanel);
            });
        }

        [BeforeScenario]
        public void BeforeScenario()
        {
            IWebDriver driver = new ChromeDriver();
            ScenarioContext.Current[WebDriverKey] = driver;
        }

        [AfterScenario]
        public void AfterScenario()
        {
            var driver = (IWebDriver) ScenarioContext.Current[WebDriverKey];
            driver.Quit();
        }

        private static IWebDriver ResolveWebDriver()
        {
            if (ScenarioContext.Current.ContainsKey(WebDriverKey))
            {
                return (IWebDriver) ScenarioContext.Current[WebDriverKey];
            }
            throw new InvalidOperationException("WebDriver not found");
        }

        private static IWebElement ResolveWebElement(ISearchContext context, WebElementProvider provider)
        {
            if (context == null) throw new ArgumentNullException(nameof(context));

            var wait = new DefaultWait<ISearchContext>(context)
            {
                Timeout = TimeSpan.FromMinutes(1),
                PollingInterval = TimeSpan.FromMilliseconds(500.0)
            };
            wait.IgnoreExceptionTypes(typeof (NotFoundException));
            return wait.Until(c => provider(c));
        }

        private static IWebElement ResolveSearchPanel(ISearchContext context, WebElementProvider provider)
        {
            if (context == null) throw new ArgumentNullException(nameof(context));

            var wait = new DefaultWait<ISearchContext>(context)
            {
                Timeout = TimeSpan.FromMinutes(10),
                PollingInterval = TimeSpan.FromMilliseconds(500.0)
            };
            wait.IgnoreExceptionTypes(typeof (NotFoundException));
            return wait.Until(c => provider(c));
        }
    }
}
  1. Use Browser class from tests -- Here is a code from a sample project that can be found in this repository. The code shows how to use NOpenPage with SpecFlow - BDD framework that implements Gherkin language for .NET environment.
namespace NOpenPage.Examples.SpecFlow.Steps
{
    [Binding]
    public class NuGetOrgSteps
    {
        [Given(@"I have opened a nuget home page")]
        public void GivenIHaveOpenedANugetHomePage()
        {
            Browser.Do(NuGetOrgPage.Navigate);
        }

        [When(@"I search for '(.*)'")]
        public void WhenISearchFor(string text)
        {
            Browser.On<NuGetOrgPage>().Search(text);
        }

        [Then(@"I can see (.*) packages")]
        public void ThenICanSeePackages(int count)
        {
            Browser.On<NuGetOrgPage>().AssertSearchResultsCountIs(count);
        }
    }
}

The package is fully covered with tests and successfully applied on real products.

Who am I?

My name is Michael Borisov. I'm interested in continuous delivery, distributed systems, CQRS, DDD, event sourcing, micro services architecture and everything related to the topic.

If you have any questions or comments regarding to the project please feel free to contact me on Twitter or LinkedIn

Happy coding!