Configuration
precept.json beside the test binary, overlaid in turn by precept.{environment}.json and PRECEPT_* environment variables — each one winning over the last:
{
"MaxParallelism": 8,
"ParallelScope": "Class",
"RunTimeoutMinutes": 180,
"Web": { "BaseUrl": "https://staging.example.com", "Browser": "chromium", "Headless": true },
"Api": { "BaseUrl": "https://api.staging.example.com" },
"ConnectionStrings": { "Default": "Host=localhost;Database=app" }
}
Comments and trailing commas are allowed in these files; .NET's JSON configuration provider skips them.
Every setting in these files can also be set from the environment — Web:Headless is
PRECEPT_WEB__HEADLESS — which is how a pipeline overrides a run and how a credential stays out of
source control. Environment variables is the complete
list, with each default.
Precept.TestPlatform copies precept.json and every precept.*.json beside it to the output directory, so adding one of these files is all there is to it — no <None Update> to write. Set <EnablePreceptSettingsCopy>false</EnablePreceptSettingsCopy> to take that over yourself; a file the project already gives CopyToOutputDirectory is left alone either way.
You do not have to write the first one. A project that builds with no precept*.json in it gets a commented precept.json from the package — created and copied to the output directory in that same build, then yours to edit and commit. The file is only ever written when the project has none at all, so an existing one is never touched and a suite that keeps nothing but an overlay is left alone. Deleting the file, though, is not remembered: the next build writes it again unless the project says otherwise.
<PropertyGroup>
<EnablePreceptSettingsScaffold>false</EnablePreceptSettingsScaffold>
</PropertyGroup>
Completion in an editor#
Precept publishes a JSON schema for these files, generated from the settings classes themselves, so every key, its type, its default and its documentation are the ones this version of Precept actually reads. Name it at the top of the file and an editor completes the sections, offers the values an enum accepts, and shows each setting's description as you type:
{
"$schema": "https://autom3tion.github.io/precept-docs/precept.schema.json",
"Web": { "Browser": "chromium" }
}
The templates and the scaffolded precept.json already carry that line, and it belongs in the
precept.{environment}.json overlays too — they bind the same shape. Precept itself reads nothing
from it; $schema is a key like any other that nothing is bound to.
Sections and keys the schema does not name are allowed everywhere, at the top level and inside any block. A suite's own settings are as legitimate as Precept's, so they complete nothing and warn about nothing. Two things follow from that, both worth knowing:
Completion is case-sensitive, and binding is not.
"headless"binds exactly as"Headless"does, but the schema will not have suggested it and will not describe it. Nothing is underlined — it is simply a key the editor has nothing to say about.Comments come from the editor, not from the schema. Precept allows
//comments and trailing commas in these files, and so does .NET's JSON configuration provider, but an editor that readsprecept.jsonas strict JSON flags them anyway. Tell it otherwise once:{ "files.associations": { "precept*.json": "jsonc" } }
The schema is precept.schema.json, and it is regenerated on every release, so
a settings key added by a new version is offered as soon as the project is on it.
Every setting is the same information as a file you can read
top to bottom: one precept.json with every key at its default, generated from the same place.
Where a section comes from#
One file, but not one owner. The runner owns the top level — MaxParallelism, ParallelScope,
RunTimeoutMinutes, DefaultTimeoutMilliseconds, DefaultRetries, ArtifactDirectory, Environment,
IsContinuousIntegration — plus Filter, Assertions, Reporting and ConnectionStrings. Those
are on PreceptSettings, the object startup is handed.
Every other section belongs to the package that reads it, and is registered by that package's
AddPrecept… call: Web by Precept.Web, Api by Precept.Api, Grpc by Precept.Grpc,
TestData by Precept.TestData, Files by Precept.Files, and each Reporting:<Name> subsection by its reporter. A browser
setting is then versioned with the browser module, and a project that never installed one is never
offered its settings. Reqnroll is the one section with no registration call: Precept.Reqnroll's
plugins read it themselves, at build time and as the first scenario starts — see
configuring Reqnroll.
This shows up in two places, and nowhere else:
The section is read only if the module is registered. A
Webblock in a project that never callsAddPreceptWeb()is read by nothing. Given a project,precept_explain_modulesays so for each module, since configuration that does nothing looks identical to configuration that works.Overriding one in code is an argument to the registration, not an assignment on
PreceptSettings— see startup:services.AddPreceptWeb(web => web.BaseUrl = stub.BaseUrl);
The file itself is unaffected: sections sit at the top level of precept.json whoever owns them,
and are overlaid and overridden by environment variables all the same way.
Your own settings#
Precept does not try to model everything a suite might need to configure — auth, tenants, feature flags, a service address. Put those in the same file under a section of your own, and bind them onto a class you write:
{
"Api": { "BaseUrl": "https://api.staging.example.com" },
"Auth": { "TokenEndpoint": "https://id.example.com/token", "ClientId": "test-runner" }
}
public sealed class AuthSettings
{
public string? TokenEndpoint { get; set; }
public string? ClientId { get; set; }
public string? ClientSecret { get; set; }
public int TimeoutMilliseconds { get; set; } = 10_000;
}
public sealed class Startup : IPreceptStartup
{
public void ConfigureServices(IServiceCollection services, PreceptSettings settings)
{
services.AddPreceptSettings<AuthSettings>();
services.AddSingleton<ITokenProvider, TokenProvider>();
}
}
AuthSettings is then a singleton like any other, injected into step definitions, page objects and fixtures:
[Binding]
public class AuthSteps(AuthSettings auth, ITokenProvider tokens) { }
The section name defaults to the type's name without a Precept prefix or a Settings/Options suffix — AuthSettings reads "Auth". Pass one explicitly for anything else, including nested sections:
services.AddPreceptSettings<AuthSettings>("Tenants:Primary");
services.AddPreceptSettings<AuthSettings>(configure: a => a.ClientSecret = Vault.Read("client-secret"));
Custom sections get the same treatment as the built-in ones: overlaid by precept.{environment}.json, then overridden by environment variables — PRECEPT_AUTH__CLIENTSECRET sets Auth:ClientSecret, which is how a secret stays out of source control. Properties absent from configuration keep their defaults, so a section is optional. A configure callback runs after binding and wins.
To read a section without registering it — to branch on it while wiring services, say — use settings.GetSection<T>(). The raw IConfiguration is on settings.Configuration if you need it.