Tutorials · Lesson 2 of 6 · about 20 minutes

Start from lesson 1. You need the project from the Quick start, with its test passing. If you do not have it, clone VAF-Tutorial and run git checkout lesson-1.
1

Look at the page

Open the sign-up form in Chrome, right-click the Full name field and choose Inspect. The field is an <input id="full-name"> inside <section id="registration">. Every field, button and message on the practice pages has a stable id like this one.

Fill the form in by hand and send it. The form disappears and a second part of the page, <section id="result">, shows what the server received. Your test will do the same.

2

Give the project a structure

A suite grows quickly, so separate the description of the application from the tests from the start. In Solution Explorer, add two folders to the project: PageObjects and Tests.

Move UnitTest1.cs into Tests and rename it to FirstTests.cs. Change its namespace to FirstVafTest.Tests and the class name to FirstTests; the test itself stays the same:

FirstVafTest/Tests/FirstTests.cs On GitHub
using NUnit.Framework;
using VisionAutomationFramework;
using VisionAutomationFramework.Browsers;
using VisionAutomationFramework.Extensions;

namespace FirstVafTest.Tests
{
    public class FirstTests
    {
        [Test]
        public void OpensThePracticeForm()
        {
            var browser = Testing.On<Chrome>();

            try
            {
                browser.LetsNavigateTo("https://www.visionautomationframework.com/practice/forms")
                    .Should().HaveTitle("Practice: forms — Vision Automation Framework");
            }
            finally
            {
                browser.Scope.Driver.Quit();
            }
        }
    }
}

The rename is not only tidiness: a class called Tests in the namespace FirstVafTest and the new namespace FirstVafTest.Tests would have the same name, and the project would stop compiling with error CS0101.

3

Map the application

Page objects describe the application in three levels. At the top is the application itself: one class that implements IPageContext, the root every section is found in. Add a class AppPage to PageObjects:

FirstVafTest/PageObjects/AppPage.cs On GitHub
using OpenQA.Selenium;
using VisionAutomationFramework.Core;

namespace FirstVafTest.PageObjects
{
    // The whole application: the root every section is found in. One is enough for the whole site,
    // because VAF looks for a section in whatever page the browser shows at that moment.
    public class AppPage : IPageContext
    {
        public By PageFindMechanism { get; set; } = By.TagName("body");
    }
}

Its locator is the body of the page, so one AppPage serves every page of the site. You will not need a second one.

4

Map the sections and their elements

Below the application are sections, parts of a screen (ISectionContext), and inside them elements, the fields, buttons and texts (IElementContext). Each class holds one locator and nothing else. Add FormsPage.cs to PageObjects:

FirstVafTest/PageObjects/FormsPage.cs On GitHub
using OpenQA.Selenium;
using VisionAutomationFramework.Core;

namespace FirstVafTest.PageObjects
{
    // https://www.visionautomationframework.com/practice/forms
    // A static class per screen only groups the sections of that screen.
    public static class FormsPage
    {
        // The sign-up form
        public class Registration : ISectionContext
        {
            public By SectionFindMechanism { get; set; } = By.Id("registration");

            public class FullName : IElementContext
            {
                public By ElementFindMechanism { get; set; } = By.Id("full-name");
            }

            public class Email : IElementContext
            {
                public By ElementFindMechanism { get; set; } = By.Id("email");
            }

            public class Password : IElementContext
            {
                public By ElementFindMechanism { get; set; } = By.Id("password");
            }

            public class Country : IElementContext
            {
                public By ElementFindMechanism { get; set; } = By.Id("country");
            }

            public class AcceptTerms : IElementContext
            {
                public By ElementFindMechanism { get; set; } = By.Id("accept-terms");
            }

            public class Submit : IElementContext
            {
                public By ElementFindMechanism { get; set; } = By.Id("submit-button");
            }
        }

        // What the server received, shown after a valid form is sent
        public class Result : ISectionContext
        {
            public By SectionFindMechanism { get; set; } = By.Id("result");

            public class Title : IElementContext
            {
                public By ElementFindMechanism { get; set; } = By.Id("result-title");
            }

            public class FullName : IElementContext
            {
                public By ElementFindMechanism { get; set; } = By.Id("result-full-name");
            }

            public class Email : IElementContext
            {
                public By ElementFindMechanism { get; set; } = By.Id("result-email");
            }

            public class Country : IElementContext
            {
                public By ElementFindMechanism { get; set; } = By.Id("result-country");
            }
        }
    }
}

The elements are nested in their section, so FormsPage.Registration.FullName and FormsPage.Result.FullName are two different elements, and the name says where each one is. The static class FormsPage only groups the sections of one screen.

An element is looked for inside its section: By.Id("full-name") is searched for within #registration, not in the whole page.

5

Write the test

Add RegistrationTests.cs to Tests. It opens the form, fills it in, sends it and checks the result, using only the page objects:

FirstVafTest/Tests/RegistrationTests.cs On GitHub
using FirstVafTest.PageObjects;
using NUnit.Framework;
using VisionAutomationFramework;
using VisionAutomationFramework.Browsers;
using VisionAutomationFramework.Extensions;

namespace FirstVafTest.Tests
{
    public class RegistrationTests
    {
        [Test]
        public void ValidRegistrationIsShownBack()
        {
            var browser = Testing.On<Chrome>();

            try
            {
                // The page context: every section is found from here
                var app = browser.LetsNavigateTo("https://www.visionautomationframework.com/practice/forms")
                    .Should().DisplayPage<AppPage>().That;

                // Fill in the form and send it
                app.Should().HaveSection<FormsPage.Registration>()
                    .That.Should().HaveElement<FormsPage.Registration.FullName>()
                        .That.AsTextInputField().LetsInsert("Ada Lovelace")
                    .AndAlso().Should().HaveElement<FormsPage.Registration.Email>()
                        .That.AsTextInputField().LetsInsert("ada@example.com")
                    .AndAlso().Should().HaveElement<FormsPage.Registration.Password>()
                        .That.AsTextInputField().LetsInsert("analytical")
                    .AndAlso().Should().HaveElement<FormsPage.Registration.Country>()
                        .That.AsSelectBox().LetsSelect("Croatia")
                    .AndAlso().Should().HaveElement<FormsPage.Registration.AcceptTerms>()
                        .That.AsCheckBox().LetsClick()
                    .AndAlso().Should().HaveElement<FormsPage.Registration.Submit>()
                        .That.AsButton().LetsClick();

                // Check what the server received
                app.Should().HaveSection<FormsPage.Result>()
                    .That.Should().HaveElement<FormsPage.Result.Title>()
                        .That.AsLabelText().Should().HaveValue("Registration received")
                    .AndAlso().Should().HaveElement<FormsPage.Result.FullName>()
                        .That.AsLabelText().Should().HaveValue("Ada Lovelace")
                    .AndAlso().Should().HaveElement<FormsPage.Result.Email>()
                        .That.AsLabelText().Should().HaveValue("ada@example.com")
                    .AndAlso().Should().HaveElement<FormsPage.Result.Country>()
                        .That.AsLabelText().Should().HaveValue("Croatia");
            }
            finally
            {
                browser.Scope.Driver.Quit();
            }
        }
    }
}

How to read the chain:

DisplayPage<AppPage>().ThatChecks that the page is there and gives you the page context, app.
Should().HaveSection<…>()Waits for the section and checks that it is there.
.That.Should().HaveElement<…>()Moves into the section and waits for an element in it.
.That.AsTextInputField()Says what kind of control the element is; that decides which actions and checks follow.
.LetsInsert(…), .LetsClick()Actions.
.AndAlso()Steps back to the section, so the chain can go on with its next element.
.Should().HaveValue(…)A check: the test fails when the value is different.

There is no waiting in the test. The result section appears only after the server has answered; HaveSection<FormsPage.Result>() waits for it, up to 10 seconds by default.

6

Run it, then break it

Open Test → Test Explorer and click Run All. Both tests turn green. Your project now looks like this:

FirstVafTest
├── PageObjects
│   ├── AppPage.cs
│   └── FormsPage.cs
├── Tests
│   ├── FirstTests.cs
│   └── RegistrationTests.cs
└── FirstVafTest.csproj

Now break the test twice, to see how VAF tells you what went wrong:

  1. A wrong value. In the test, expect "Ada Byron" as the full name of the result. The test fails as soon as it gets there, and the message names the element (FormsPage → Result → FullName), the expected and the actual value.
  2. A wrong locator. Put the expected name back, and in FormsPage.cs change "full-name" to "fullname". VAF now looks for the field for 10 seconds, then fails because the element is not present.

Fix the locator in FormsPage.cs and the test passes again. That is the point of page objects: a locator lives in one place, so when the page changes, you change one line, and every test that uses the element works again.

Compare with the reference code

The project as it is at the end of this lesson is tag lesson-2 of VAF-Tutorial. If your test does not pass, compare your files with it, or get it with git checkout lesson-2.