In my last post I introduced BDD with SpecFlow. I created a feature file and got to a point where I now needed to hook up my BDD tests with browser integration. To do this, I’m going to use Coypu and the Selenium headless browser. See my previous post for download links.
First, let’s configure Coypu to use the Selenium headless browser:
[Binding] public class CoypuInitializer { [BeforeTestRun] public static void BeforeTestRun() { //I'm using the Dev Studio cassini server; Configuration.AppHost = "localhost"; Configuration.Port = 64567; ConfigureForHeadlessBrowser(); } private static void ConfigureForHeadlessBrowser() { Configuration.Driver = typeof(SeleniumHtmlUnitWebDriver); } [AfterScenario] public static void AfterScenario() { Coypu.Browser.EndSession(); } }
public class SeleniumHtmlUnitWebDriver : SeleniumWebDriver { public SeleniumHtmlUnitWebDriver() : base(new RemoteWebDriver(DesiredCapabilities.HtmlUnit())) {} }
You will also need to add DLL references to Coypu.dll and WebDriver.dll (which is in the Coypu zip).
You can use this class to make other global configuration settings such as the Timeout, or the time Coypu waits between retries when looking for a HTML element. Full details can be found at the GitHub Coypu website, or the README.md file that comes with the Coypu binaries.
Now we are ready to implement the first feature step. As a reminder, the feature file is:
Scenario: Display Pi using Taylor series and Bucknall's BigNumber to many decimal places Given I visit the Pi.NET website And I have selected the 'Bucknall Big Number' algorithm And I have entered 500 decimal places When I press Go Then Pi should be displayed to the correct number of decimal places And Calculation statistics should be displayed
Let’s implement the “Given I visit the Pi.NET website” step:
[Given(@"I visit the Pi\.NET website")] public void GivenIVisitThePiWebsite() { Browser.Session.Visit(string.Format("/")); }
Using Coypu, I’m going to open the website specified in the CoypuInitializer class and visit the root of that website.
I now go to my MVC app and implement just enough to get that step passing.
Once I’ve done that, running the tests now gives:
Given I visit the Pi.NET website -> done: PiNetSteps.GivenIVisitThePiWebsite() (15.4s) And I have selected the 'Bucknall Big Number' algorithm -> pending: PiNetSteps.GivenIHaveSelectedTheBucknallBigNumberAlgorithm("Bucknall Big Number")
i.e. – the first step has completed and the test has stopped at the next pending step. We’ve successfully hit the website.
I proceed like this, step by step, until I end up with the following steps file. I’ve added comments to explain the Coypu features I am using:
[Binding] public class PiNetSteps:Steps { private string numberOfDecimalPlaces; [Given(@"I have selected the '(.+)' algorithm")] public void GivenIHaveSelectedAnAlgorithm(string algorithm) { //pass on this call to the generic radio button method (later in this class) Given(string.Format("I select the radio button '{0}'", algorithm)); } [Given(@"I have entered (.+) decimal places")] public void GivenIHaveEnteredTheNumberOfRequiredDecimalPlaces(string numberOfDecimalPlaces) { this.numberOfDecimalPlaces = numberOfDecimalPlaces; //store for later use in an assert //populate a text box Browser.Session.FillIn("NumberOfDecimalPlaces").With(numberOfDecimalPlaces); } [When(@"I press Go")] public void WhenIPressGo() { //click a link. This fires an AJAX post to controller action //(I wanted to test JS capabilities of Selenium headless browser) Browser.Session.ClickLink("Calculate"); } [Then(@"Pi should be displayed to the correct number of decimal places")] public void ThenPiShouldBeDisplayedToTheRequestedNumberOfDecimalPlaces() { //Check page HasContent I'm after - "Number of iterations" is displayed by AJAX call result if (Browser.Session.HasContent("Number of iterations:")) { //check Pi is calculated to correct number of places string piToNumberOfRequiredDecimalPlaces = Pi.Get(Convert.ToInt32(numberOfDecimalPlaces)); Element piElement = Browser.Session.FindField("pi"); //find HTML element string calculatedPi = piElement.Text; Assert.That(piToNumberOfRequiredDecimalPlaces, Is.EqualTo(calculatedPi)); } else { Assert.Fail("Could not find PI"); } } [Then(@"Calculation statistics should be displayed")] public void ThenCalculationStatisticsShouldBeDisplayed() { //you can use actions/funcs with different retry timeouts Assert.IsTrue(Browser.Session.WithIndividualTimeout(TimeSpan.FromMilliseconds(1), () => Browser.Session.HasContent("Number of iterations: ")), "Number of Iterations taken not found"); Assert.IsTrue(Browser.Session.WithIndividualTimeout(TimeSpan.FromMilliseconds(1), () => Browser.Session.HasContent("Number of decimal places: " + numberOfDecimalPlaces)), "Number of Decimal places calculated not found"); Assert.IsTrue(Browser.Session.WithIndividualTimeout(TimeSpan.FromMilliseconds(1), () => Browser.Session.HasContent("Elapsed Milliseconds: ")), "Time taken not found"); } [Given(@"I select the radio button '(.+)'")] public void SelectARadioButton(string radioButtonValue) { //select a radio button by value Browser.Session.Choose(radioButtonValue); } [Given(@"I visit the Pi.NET website")] public void GivenIVisitThePiPage() { //visit a web page at site specified in Coypu initialization. Browser.Session.Visit(string.Format("/")); } }
I’ve used several SpecFlow and Coypu features in the class above. There are many more that you can read about at the GitHub Coypu website, or the README.md file that comes with the Coypu binaries. The documentation also shows how to use a real browser, rather than the Selenium headless one.