Lesson 2: Map your first page
Describe the sign-up form as page objects — the app, its sections and their elements — and write a test that fills it in and checks what the server received.
Tutorials · Lesson 2 of 6 · about 20 minutes
git checkout lesson-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.
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.
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.
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.
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>().That | Checks 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.
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:
-
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. -
A wrong locator. Put the expected name back, and in
FormsPage.cschange"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.