3.0.0-beta.2 is a preview (published 28 September 2026). The stable release is still 2.1.0, and the Quick start and Guide describe it. NuGet offers the beta only when you ask for prereleases, so existing projects do not pick it up by accident. This beta is tested with Chrome and Firefox on Windows, and with a large existing suite. If something does not work, let me know.

What is new

  • .NET 10 alongside .NET Framework 4.7.2, with the same API.
  • Any test framework: NUnit 3 and 4, MSTest and xUnit. A failed check throws VafAssertionException, which every framework reports as a failed test. The MSTest IsolatedContext workaround is no longer needed.
  • Only Selenium as a dependency (Selenium.WebDriver and Selenium.Support 4.45 or any newer 4.x). VAF no longer brings NUnit, Newtonsoft.Json or RestSharp into your project.
  • Settings from environment variables or app.config (see below).
  • LetsSaveScreenshotTo(directory) saves a screenshot wherever you want and returns its path, so any test framework can attach it.
  • Testing.On(browser) for a browser you create with settings — a remote grid, extra arguments — next to Testing.On<Chrome>() (example below).
  • AndAlso() also after a check, so several checks on one section stay in one chain.
  • One HTML report per test (VAF_Log_<test>_<date>_<time>.html) instead of one per minute, and a failed element or section check shows its full message in the report.

A new project

With 3.0, the NUnit Test Project template of Visual Studio 2026 (or dotnet new nunit with the .NET 10 SDK) works as it is: it creates a .NET 10 project with NUnit 4, and there is no need to switch to .NET Framework and NUnit 3 as the 2.1 Quick start describes. Create the project, then add the package with prereleases included, from the command line:

dotnet add package VisionAutomationFramework --prerelease

or in Manage NuGet Packages with Include prerelease ticked. The project file then looks like this:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
    <PackageReference Include="NUnit" Version="4.2.2" />
    <PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
    <PackageReference Include="VisionAutomationFramework" Version="3.0.0-beta.2" />
  </ItemGroup>

</Project>

For .NET Framework, set <TargetFramework>net472</TargetFramework> (or newer 4.x); everything else stays. .NET 8 and 9 are not supported: the build stops with error VAF0001, which says so. The first test is the same as in 2.1:

using NUnit.Framework;
using VisionAutomationFramework;
using VisionAutomationFramework.Browsers;
using VisionAutomationFramework.Extensions;
namespace FirstVafTest
{
    public class Tests
    {
        [Test]
        public void OpensExampleDomain()
        {
            var browser = Testing.On<Chrome>();

            try
            {
                browser.LetsNavigateTo("https://example.com/")
                    .Should().HaveTitle("Example Domain");
            }
            finally
            {
                browser.Scope.Driver.Quit();
            }
        }
    }
}

When the title is wrong, the test fails with the same message as before: "Title on active tab isn't correctly populated", Expected: "…", But was: "…". NUnit labels the result Error rather than Failed, because the exception is VAF's own and not NUnit's.

Upgrading from 2.x

Update VisionAutomationFramework to 3.0.0-beta.2 (with Include prerelease) and rebuild. Most suites compile unchanged: page objects, shared steps and the fluent chains are the same. Check these points:

In 2.xIn 3.0
VAF brought NUnit, Newtonsoft.Json and RestSharp into your project Only Selenium. If your tests use NUnit, Newtonsoft.Json or RestSharp themselves, reference them in your project. The Quick start project already references NUnit.
A failed check threw NUnit's AssertionException It throws VisionAutomationFramework.VafAssertionException. Change any catch (AssertionException).
LetsTakeScreenshot(TestContext tc) with the old MSTest v1 TestContext LetsSaveScreenshotTo(TestContext.TestResultsDirectory), then attach the returned file — example below.
MSTest: new TestExecutionContext.IsolatedContext() in [TestInitialize] Not needed; remove it (and the NUnit package, if nothing else uses it).
BrowserStack: Testing.On<T>(browserStackOptions), BrowserStackRemoteWebDriver, IOSSafari, MobileRemoteDriver Removed (obsolete since 2.1). For a remote grid, implement IBrowserDefinition, return a Selenium RemoteWebDriver and start it with Testing.On(new YourBrowser(…)) — example below.
HTML report VAF_Log_<hh-mm MM-dd-yyyy>.html, one per minute VAF_Log_<test name>_<yyyy-MM-dd_HH-mm-ss>.html, one per test. Change anything that looks for the old name.
Mobile types: MobileAppActions, AppScope, MobileApp and the non-generic ButtonActions Removed. There was no way to start a mobile session with them.
InternetExplorer, WinFormApplication Removed: Internet Explorer is retired, and WinFormApplication was never implemented.
WebElementAssertionMethods without a namespace In VisionAutomationFramework.Extensions.

Screenshots with MSTest

[TestClass]
public class CheckoutTests
{
    public TestContext TestContext { get; set; }

    [TestMethod]
    public void OpensExampleDomain()
    {
        var browser = Testing.On<Chrome>();

        try
        {
            browser.LetsNavigateTo("https://example.com/")
                .Should().HaveTitle("Example Domain");
        }
        finally
        {
            string path = browser.LetsSaveScreenshotTo(TestContext.TestResultsDirectory);
            if (path != null)
            {
                TestContext.AddResultFile(path);
            }

            browser.Scope.Driver.Quit();
        }
    }
}

Under NUnit, LetsTakeScreenshot() still saves the file in the test directory and attaches it to the result, as in 2.x.

A browser with settings

Testing.On<Chrome>() needs a browser class without constructor parameters. For a remote grid or extra arguments, create the browser yourself and pass it in:

public class GridChrome : IBrowserDefinition
{
    private readonly IWebDriver _driver;

    public GridChrome(string gridUrl, params string[] arguments)
    {
        var options = new OpenQA.Selenium.Chrome.ChromeOptions();
        options.AddArguments(arguments);
        _driver = new OpenQA.Selenium.Remote.RemoteWebDriver(new System.Uri(gridUrl), options);
    }

    public IWebDriver GetDriver() => _driver;
}

// In a test:
var browser = Testing.On(new GridChrome("http://grid.example.com:4444/wd/hub", "--window-size=1920,1080"));

Catching a failed check

try
{
    App.Should().HaveSection<LoginForm>()
        .That.Should().HaveElement<LoginForm.Error>()
            .That.AsLabelText().Should().HaveValue("Invalid password");
}
catch (VafAssertionException e)   // 2.x: NUnit.Framework.AssertionException
{
    Assert.Fail("The login error is wrong: " + e.Message);
}

Settings

Each setting is read from the environment variable VAF_<name> first, then from appSettings in app.config. On .NET 10 app.config often does not reach the test run, so environment variables are the reliable way there, and in CI.

SettingDefaultMeaning
ElementWaitTimeout10seconds to wait for an element
LoaderWaitTimeout60seconds to wait for a loader to disappear
DownloadDirectoryc:\mydownloads on Windowswhere Chrome and Firefox save downloads

Known limits of this beta

  • Tested with Chrome and Firefox on Windows. Linux and macOS on .NET 10 should work but are not tested yet.
  • LetsSelectOptionFromRightClickMenu drives the browser's own context menu with real key presses: Windows only, and not in tests that run in parallel. When the browser is not the foreground window, it now fails instead of typing into another window. Firefox started from a test run usually does not get the foreground, so with Firefox this method mostly refuses; with Chrome it works.
  • On .NET Framework, Windows limits a file path to 260 characters. A screenshot in a deeply nested results folder is then not saved: LetsSaveScreenshotTo returns null and writes the reason to the VAF log.