Lesson 6: Waiting and reports
Let VAF wait for a spinner and for late content, and get an HTML report and a screenshot when a test fails.
Tutorials · Lesson 6 of 6 · about 25 minutes
git checkout lesson-5 in VAF-Tutorial.
Real applications are slow in places: a list loads behind a spinner, a message appears a moment after a click. A test that checks too early fails although nothing is wrong. This lesson shows how VAF waits — without a single sleep in your tests — and how a failed test tells you what happened.
Look at the page
Open loading and waiting. Click Load results: a spinner turns for one and a half seconds, then three results appear and the counter goes from 0 to 1. Click Show the message: the message appears two seconds later.
Map it
Add LoadingPage.cs to PageObjects:
FirstVafTest/PageObjects/LoadingPage.cs
On GitHub
using OpenQA.Selenium;
using VisionAutomationFramework.Core;
namespace FirstVafTest.PageObjects
{
// https://www.visionautomationframework.com/practice/loading
public static class LoadingPage
{
// Shown while the results load. Not in a section: VAF looks for the loader in the whole page.
public class Spinner : IElementContext
{
public By ElementFindMechanism { get; set; } = By.Id("loading-spinner");
}
// Results that come from the server one and a half seconds after the click
public class SpinnerDemo : ISectionContext
{
public By SectionFindMechanism { get; set; } = By.Id("spinner-demo");
public class Load : IElementContext
{
public By ElementFindMechanism { get; set; } = By.Id("load-results");
}
// How many times the results were loaded: "0" until the first load is done
public class LoadCount : IElementContext
{
public By ElementFindMechanism { get; set; } = By.Id("load-count");
}
public class FirstResult : IElementContext
{
public By ElementFindMechanism { get; set; } = By.Id("result-1");
}
}
// A message that appears two seconds after the click
public class LateDemo : ISectionContext
{
public By SectionFindMechanism { get; set; } = By.Id("late-demo");
public class Show : IElementContext
{
public By ElementFindMechanism { get; set; } = By.Id("show-later");
}
public class Message : IElementContext
{
public By ElementFindMechanism { get; set; } = By.Id("late-message");
}
}
}
}
The spinner is an element of its own, outside any section: you will hand it to VAF as the loader, and VAF looks for a loader in the whole page.
Two kinds of waiting
Add WaitingTests.cs to Tests:
FirstVafTest/Tests/WaitingTests.cs
On GitHub
using FirstVafTest.Common;
using FirstVafTest.PageObjects;
using NUnit.Framework;
using VisionAutomationFramework;
using VisionAutomationFramework.Extensions;
namespace FirstVafTest.Tests
{
// No sleeps in these tests: VAF waits for elements and for the loader by itself
public class WaitingTests : TestBase
{
[SetUp]
public void Open() => SharedContext.LetsOpen("/practice/loading");
[Test]
public void ResultsAreReadAfterTheSpinnerIsGone()
{
// From now on, VAF waits for the spinner to disappear before every element it looks for
SharedContext.Browser.LetsUseLoader<LoadingPage.Spinner>();
SharedContext.App.Should().HaveSection<LoadingPage.SpinnerDemo>()
.That.Should().HaveElement<LoadingPage.SpinnerDemo.Load>()
.That.AsButton().LetsClick()
.AndAlso().Should().HaveElement<LoadingPage.SpinnerDemo.LoadCount>()
.That.AsCounterField().Should().HaveValue("1")
.AndAlso().Should().HaveElement<LoadingPage.SpinnerDemo.FirstResult>()
.That.AsLabelText().Should().HaveValue("Alpha (load 1)");
}
// The same check without the loader comes too early: the counter still shows "0"
[Test]
public void WithoutTheLoaderTheCheckIsTooEarly()
{
SharedContext.App.Should().HaveSection<LoadingPage.SpinnerDemo>()
.That.Should().HaveElement<LoadingPage.SpinnerDemo.Load>()
.That.AsButton().LetsClick();
Assert.Throws<VafAssertionException>(() =>
SharedContext.App.Should().HaveSection<LoadingPage.SpinnerDemo>()
.That.Should().HaveElement<LoadingPage.SpinnerDemo.LoadCount>()
.That.AsCounterField().Should().HaveValue("1"));
}
[Test]
public void LateMessageIsWaitedFor()
{
// The message appears two seconds after the click; HaveElement waits for it
SharedContext.App.Should().HaveSection<LoadingPage.LateDemo>()
.That.Should().HaveElement<LoadingPage.LateDemo.Show>()
.That.AsButton().LetsClick()
.AndAlso().Should().HaveElement<LoadingPage.LateDemo.Message>()
.That.AsLabelText().Should().HaveValue("Here I am, two seconds later.");
}
}
}
-
An element that is not there yet —
HaveElementandHaveSectionwait until it is present and visible, up to 10 seconds by default.LateMessageIsWaitedForneeds nothing more. -
An element that is there, but not finished — the counter shows
0from the start.HaveElementfinds it at once, and the check reads it before the results arrive.WithoutTheLoaderTheCheckIsTooEarlyshows it: the check fails, as the test expects. -
The loader —
LetsUseLoader<LoadingPage.Spinner>()tells VAF to wait for the spinner to disappear before it looks for any element. Then the same check gets1. The loader is set for one browser: a new browser starts without it. If your application uses the same spinner everywhere, set it once inSharedContext.LetsStartTheBrowser.
Timeouts
VAF waits up to 10 seconds for an element and up to 60 seconds for the loader. To change them, set the
environment variables VAF_ElementWaitTimeout and VAF_LoaderWaitTimeout, in
seconds — in PowerShell before dotnet test:
$env:VAF_ElementWaitTimeout = "20"
dotnet test
In GitHub Actions or another pipeline, set them in the job's environment. VAF also reads them from the
appSettings of an app.config, but on .NET 10 the test host often does not load
the test project's app.config; environment variables work everywhere.
A report for every test, a screenshot for every failure
VAF writes every action and check into a log while the test runs. Let TestBase turn it into an
HTML page after each test, and take a screenshot when the test has failed. Replace
TestBase.cs in Common:
FirstVafTest/Common/TestBase.cs
On GitHub
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using VisionAutomationFramework;
namespace FirstVafTest.Common
{
// The base of every test class: the browser starts before each test and closes after it.
// A test class adds only its own [SetUp], usually one that opens its page; NUnit runs this
// [SetUp] first, then the one of the test class.
public abstract class TestBase
{
[SetUp]
public void StartTheBrowser() => SharedContext.LetsStartTheBrowser();
[TearDown]
public void ReportAndClose()
{
try
{
// A failed test leaves a screenshot of the page as it was, attached to the result
if (TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Failed)
{
SharedContext.Browser?.LetsTakeScreenshot();
}
// Every test leaves an HTML report of what VAF did, step by step
Testing.ConvertLogToHtml(TestContext.CurrentContext.WorkDirectory,
TestContext.CurrentContext.Test.Name);
}
finally
{
// Close the browser even when the report cannot be written
SharedContext.LetsCloseTheBrowser();
}
}
}
}
Every test class gets this without a change, because they all derive from TestBase. The report,
VAF_Log_<test>_<time>.html, is written to the test output folder
(bin\Debug\net10.0). The screenshot goes there too, and NUnit attaches it to the result of the
failed test, so you can open it from Test Explorer.
Run it, then break it
Run all tests: twenty pass. In ResultsAreReadAfterTheSpinnerIsGone, expect
"Beta (load 1)" and run it again. It fails; open the output folder:
- the HTML report lists every step the test took — the click, the wait for the spinner, each check — and marks the one that failed, with the expected and the actual value;
- the screenshot shows the page at that moment: the results are there, the first one is Alpha.
What you have now
A suite with page objects, shared steps and tests, which waits for slow pages, reports every run and shows you each failure — the same structure scales to a real application. Where to go from here:
- Point it at your own application: one
AppPage, then a page object per screen, then the steps. - VAF-Demo — the same structure for every practice page, and longer scenarios made only of steps.
- Practice pages — tables, windows, alerts and more, to try on your own.
- Guide — the reference for everything you used here.
Compare with the reference code
The project as it is at the end of this lesson is tag
lesson-6 of
VAF-Tutorial. If your test does not pass,
compare your files with it, or get it with git checkout lesson-6.