Ossa.QualityDashboard.Accessibility 1.1.0-beta.9

This is a prerelease version of Ossa.QualityDashboard.Accessibility.
dotnet add package Ossa.QualityDashboard.Accessibility --version 1.1.0-beta.9
                    
NuGet\Install-Package Ossa.QualityDashboard.Accessibility -Version 1.1.0-beta.9
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Ossa.QualityDashboard.Accessibility" Version="1.1.0-beta.9" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Ossa.QualityDashboard.Accessibility" Version="1.1.0-beta.9" />
                    
Directory.Packages.props
<PackageReference Include="Ossa.QualityDashboard.Accessibility" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Ossa.QualityDashboard.Accessibility --version 1.1.0-beta.9
                    
#r "nuget: Ossa.QualityDashboard.Accessibility, 1.1.0-beta.9"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Ossa.QualityDashboard.Accessibility@1.1.0-beta.9
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Ossa.QualityDashboard.Accessibility&version=1.1.0-beta.9&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Ossa.QualityDashboard.Accessibility&version=1.1.0-beta.9&prerelease
                    
Install as a Cake Tool

Quality Dashboard

Pulls code-quality stats from SonarQube, security findings from OWASP ZAP reports and WCAG results from your sites' Playwright integration tests, and consolidates them into a one-page dashboard (A4 landscape) for steering committee / QA documents.

  • Outputs: HTML (self-contained), PDF and Word (.docx), plus optional JSON of the underlying figures
  • Multiple projects, one page: one row per project with a traffic light (RAG), key metrics, a WCAG score and grade, coverage, issue, ZAP and WCAG sparklines, and trend direction
  • WCAG report per site: every page its tests visited with a score, grade and each failure, written as HTML straight after the test run, or as HTML, PDF or Word with quality-dashboard wcag (see WCAG). The dashboard itself carries only the WCAG summary, so it stays one page.
  • Trends come from SonarQube's analysis history, plus a local SQLite database of past ZAP and WCAG results, over a configurable window (default 90 days)
  • Installs as a .NET tool (quality-dashboard) from NuGet; designed for an Azure DevOps Windows agent

Install

Requires the .NET 8, 9 or 10 SDK (or later) on Windows x64 or Arm64. The package carries a build per framework and dotnet tool install picks the newest one your SDK supports; add --framework net8.0 to choose one explicitly.

dotnet tool install -g Ossa.QualityDashboard     # install
dotnet tool update -g Ossa.QualityDashboard      # upgrade
quality-dashboard --version

To pin it per repository instead, use a local tool manifest: dotnet new tool-manifest, then dotnet tool install Ossa.QualityDashboard and run it as dotnet quality-dashboard.

Quick start

# Preview the layout with built-in sample data (no SonarQube needed)
quality-dashboard --demo --out ./out

# Real run
$env:SONAR_TOKEN = '<your SonarQube user token>'
quality-dashboard --config dashboard.config.json --out ./out

Output: out/quality-dashboard.html, .pdf, .docx. From a clone of this repo, dotnet run --project src/QualityDashboard -f net10.0 -- <options> works the same without installing.

Command line

Option Description
--config <file> Dashboard config (JSON). Required unless --demo.
--out <dir> Output folder (default ./out).
--format <list> Any of html,pdf,docx,json (default html,pdf,docx).
--file-name <name> Output file name without extension (default quality-dashboard).
--zap-root <dir> (alias --reports-root) Folder that relative zapReport and accessibilityReport paths resolve against (default: the config file's folder).
--browser-path <exe> Edge/Chrome used for PDF output. Default: auto-detect, or the QD_BROWSER_PATH env var.
--history-db <file> SQLite file holding ZAP and WCAG history. Default: config historyDb, else %APPDATA%\QualityDashboard\history.db.
--no-history Don't read or write the history database.
--demo Use built-in sample data (never touches the history database).
--version Print the tool version.

For a site's WCAG report (every page and its failures), use quality-dashboard wcag --report <wcag-report.json>: see WCAG report command.

Exit code is 0 on success and 1 for invalid arguments/config, when no project could be read, or when an output format failed. If one project's data can't be read, it still appears on the dashboard with the error shown, and in Azure DevOps a pipeline warning is raised.

Configuration

Start from the sample dashboard.config.json in the repo root (comments are allowed).

Key Meaning
title, subtitle Page heading.
sonar.url SonarQube Server URL, or https://sonarcloud.io.
sonar.tokenEnv Name of the env var holding the token (default SONAR_TOKEN). The token never goes in the file.
historyDays Trend window in days (default 90).
historyDb Optional path to the ZAP history database. Environment variables are expanded, e.g. %APPDATA%\\QualityDashboard\\history.db.
projects[] name (display), sonarKey, optional branch, optional zapReport (path to ZAP JSON), optional accessibilityReport (path to a WCAG report, see WCAG), optional id (stable history key, see below). Each project needs at least one of sonarKey / zapReport / accessibilityReport.
thresholds minCoverage (%), maxZapHigh, maxZapMedium, minAccessibilityScore (default 90), maxAccessibilityCritical (default 0), maxAccessibilitySerious (default 0): drive the traffic lights.

The SonarQube token needs Browse permission on each project. Branch analysis (branch) requires SonarQube Developer Edition or higher.

ZAP reports

Use the traditional JSON report:

  • Docker scripts: zap-baseline.py -t https://app -J portal-api.json (or zap-full-scan.py / zap-api-scan.py)
  • Automation Framework: a report job with template: traditional-json

Counts on the dashboard are distinct alert types per risk level (High / Medium / Low), matching the ZAP report summary. The "Key security findings" panel lists the top High/Medium alerts with the number of affected URLs.

ZAP history database

SonarQube keeps its own history, but a ZAP report is a one-off file. So each run stores the ZAP results in a SQLite database, and the dashboard draws a ZAP H+M trend (distinct High + Medium alert types per scan) from it.

  • Location: %APPDATA%\QualityDashboard\history.db by default. Override it with historyDb in config or --history-db. The folder and schema are created on first run.
  • Deduplication: each report is identified by a SHA-256 of its contents, so re-running the dashboard against the same ZAP report doesn't add a duplicate point.
  • Scan date: taken from the report's @generated timestamp, or the time it was recorded if that's missing.
  • Project key: history is stored against the project's id, falling back to sonarKey, then name. Set id if you expect to rename a project.
  • Tables: zap_scan (one row per scan, with counts by risk), zap_alert (top alerts per scan) and accessibility_scan (one row per WCAG report: site score and failures by impact). You can query them directly with any SQLite tool. Databases from version 1.0 are upgraded in place.
  • Resilience: if the database can't be opened, the dashboard is still produced without ZAP and WCAG trends, and a warning is raised.

A trend line needs at least two recorded scans in the window; until then the column shows "Building history".

Where to keep it in Azure DevOps. %APPDATA% belongs to whichever account runs the agent:

  • Microsoft-hosted agents start from a clean VM every run, so %APPDATA% doesn't survive. The pipeline's historyAsArtifact option (on by default) downloads history.db from the previous run's quality-history artifact and publishes the updated copy at the end.
  • Self-hosted agents keep %APPDATA% between runs (for an agent running as a service, that's the service account's profile, e.g. C:\Windows\ServiceProfiles\NetworkService\AppData\Roaming). Turn historyAsArtifact off, or point historyDb at a fixed local folder such as D:\QualityDashboard\history.db.
  • Avoid putting the database on a network share: SQLite file locking is unreliable over SMB.

WCAG

WCAG checks run inside each site's existing Playwright integration tests, so they see the pages exactly as the tests do: signed in, as each role, with dialogs open. The Ossa.QualityDashboard.Accessibility NuGet library runs axe-core on any Playwright IPage and writes one wcag-report.json per site. There are two outputs:

  • The WCAG report (one per site): every page and its failures. The scanner writes it as wcag-report.html next to the JSON as soon as the tests finish. For PDF or Word, run quality-dashboard wcag on the JSON.
  • The one-page dashboard reads the JSON like a ZAP report: it scores it, applies traffic-light and trend rules, keeps history, and shows a WCAG score, grade and trend per project. The page-by-page detail stays in the WCAG report.

Both are rendered by the same shared code, so the scores and layout always match.

The library works with xUnit, NUnit or MSTest (it only needs an IPage) and with any Playwright browser: Microsoft Edge (msedge channel, no browser download needed on Windows), Chrome, Chromium, Firefox or WebKit.

Scanning from integration tests

dotnet add package Ossa.QualityDashboard.Accessibility

NUnit with Microsoft.Playwright.NUnit: one auditor for the whole run, and one extra line in each test that reaches a page worth checking.

[SetUpFixture]
public class WcagSetup
{
    public static AccessibilityAuditor Auditor = null!;

    [OneTimeSetUp]
    public void Start() => Auditor = new(new AccessibilityOptions
    {
        SiteName = "Customer Portal",
        Standard = WcagStandard.Wcag22AA,   // per site: Wcag2A, Wcag2AA, Wcag21A, Wcag21AA, Wcag22AA (default), Wcag22AAA
    });

    [OneTimeTearDown]
    public Task Stop() => Auditor.WriteReportAsync();
}

public class OrderTests : PageTest
{
    [Test]
    public async Task Orders_list()
    {
        await Page.GotoAsync("/orders");                    // already signed in (see below)
        await WcagSetup.Auditor.ScanAsync(Page, "Orders list");
        // ... the test's own assertions
    }
}

To run NUnit tests in Edge, add a .runsettings (see tests/QualityDashboard.Accessibility.NUnitSmoke/edge.runsettings):

<RunSettings>
  <Playwright>
    <BrowserName>chromium</BrowserName>
    <LaunchOptions><Channel>msedge</Channel></LaunchOptions>
  </Playwright>
</RunSettings>

xUnit: hold the auditor in an assembly or collection fixture (IAsyncLifetime) and call WriteReportAsync() from DisposeAsync(). With plain Microsoft.Playwright, launch Edge with playwright.Chromium.LaunchAsync(new() { Channel = "msedge" }).

Signed-in pages. The auditor scans whatever page it's given, so reuse your tests' existing login. A common pattern: sign in once per role in the fixture, save await context.StorageStateAsync(new() { Path = "admin.json" }), then create each test's context with new BrowserNewContextOptions { StorageStatePath = "admin.json" }. Name scans per role or state when it matters, e.g. "Orders list (admin)" or "Checkout – address dialog".

Options (AccessibilityOptions):

Option Meaning
SiteName Shown on the dashboard.
Standard WCAG version and level to check (default WCAG 2.2 AA). Each level includes those below it.
OutputPath Report file. Default: the QD_WCAG_REPORT env var, else TestResults/wcag-report.json under the test run's working folder.
WriteHtml Also write the readable WCAG report next to it (wcag-report.html). Default true.
IncludeBestPractices Also run axe best-practice rules. Their failures are listed but never scored.
DisabledRules axe rule ids to skip.
ExcludeSelectors CSS selectors to leave out, e.g. third-party widgets you can't change.
FailOnImpact Make ScanAsync throw AccessibilityViolationException (failing the test) on a failure at this impact or worse, e.g. WcagImpact.Critical. The result is still recorded.

Each ScanAsync call records one page result, keyed by name and browser. Scanning the same name in the same browser again replaces the earlier result. The query string, fragment and any credentials are stripped from URLs, so tokens never reach the report.

After the run, open TestResults/wcag-report.html for the results. To include the site on the dashboard, point its project at the JSON:

{ "name": "Customer Portal", "sonarKey": "portal-web", "accessibilityReport": "reports/customer-portal/wcag-report.json" }

WCAG report command

quality-dashboard wcag renders a WCAG report from the JSON in any format, e.g. for a QA document:

dotnet test
quality-dashboard wcag --report TestResults/wcag-report.json --out ./out --format html,pdf,docx
Option Description
--report <file> The wcag-report.json from the test run. Required.
--out <dir> Output folder (default ./out).
--format <list> Any of html,pdf,docx,json (default html,pdf,docx). PDF is printed from the HTML by Edge or Chrome.
--file-name <name> Output file name without extension (default: the report's name).
--title <text> Heading (default <site> · WCAG report).
--browser-path <exe> Edge/Chrome used for PDF output, as for the dashboard.

The report has the site score and counts, the pages worst first, the top failing rules, then a section per page listing each failure: impact, rule and WCAG success criteria, what to fix, the number of failing elements, and the first few elements (CSS selector and axe's explanation). Best-practice findings are shown but not scored. Exit code is 0 on success, 1 if the report can't be read or an output fails.

Scores and grades

Rule
Page score (0–100) 100 minus, for each failing rule, its impact weight × (1 + log10(failing elements)). Weights: critical 10, serious 5, moderate 2, minor 1. So one serious contrast failure on 1 element costs 5, and on 10 elements costs 10.
Grade A ≥ 95 with no serious or critical failures; B ≥ 85; C ≥ 70; D ≥ 50; E below 50. Any critical failure caps the grade at D.
Site score The mean of the page scores. A page scanned in several browsers counts once, at its worst result. The site grade uses the same bands, capped at D if any page has a critical failure.
Counts Critical / Serious / Moderate / Minor count failing rules per page, summed over pages. "Distinct failing rules" counts each rule once, so a layout bug on every page shows up as one fix.
Not scored axe best-practice rules, and "needs review" results that axe can't decide automatically. These are listed for manual checking.

The dashboard re-scores every report with these rules rather than trusting stored numbers, so reports from older library versions are scored the same way. Automated checks can't catch every WCAG issue, so treat the score as a floor and keep doing manual and assistive-technology testing.

Licences. The dashboard tool stays MIT-only: it just reads the JSON report. The scanner library depends on Deque.AxeCore.Playwright (MIT), which bundles axe-core (MPL-2.0, used unmodified). MPL-2.0 is file-level copyleft and puts no obligations on your own code.

How the traffic light and trend are worked out

Status Rule
Red (action needed) SonarQube quality gate failed, ZAP High alerts > maxZapHigh, or critical WCAG failures > maxAccessibilityCritical.
Amber (at risk) Quality gate warning, coverage < minCoverage (or not reported), ZAP Medium alerts > maxZapMedium, serious WCAG failures > maxAccessibilitySerious, WCAG score < minAccessibilityScore, trend worsening, or a configured data source couldn't be read.
Green (on track) None of the above.
No data No SonarQube, ZAP or WCAG data available.

Trend compares the first and last data point in the window for four signals:

  • Coverage (SonarQube) counts as changed if it moved by ≥ 0.5 percentage points.
  • Total issues (SonarQube bugs + vulnerabilities + code smells) count as changed if they moved by more than 2% (and at least 1).
  • ZAP High + Medium alert types (history database) count as changed if they moved by 1 or more.
  • WCAG site score (history database) counts as changed if it moved by 1 point or more.
  • More good signals (coverage up, issues down, fewer ZAP alerts, higher WCAG score) than bad → Improving; more bad than good → Worsening; otherwise Stable. No signal with two or more points in the window → No history.

These rules are printed in the dashboard footer so readers know what each colour means.

Azure DevOps

pipelines/azure-pipelines.yml is a ready-to-use pipeline (weekly schedule + manual runs) on windows-latest:

  1. Create a variable group quality-dashboard with a secret SonarToken.
  2. Edit and commit dashboard.config.json in the repo root (the pipeline's default configPath), or point configPath at your own copy.
  3. ZAP reports either live in the repo next to the config, or come from the pipeline that runs the scans: set the zapPipeline parameter, and that pipeline's zap-reports artifact is downloaded and used as --zap-root. In that case, set each zapReport to the file's path inside the artifact (e.g. portal-api.json).
  4. Choose toolSource: source (default) builds the tool from this repo; nuget installs the published Ossa.QualityDashboard tool instead (optionally pinned with the template's toolVersion).
  5. Run it. The dashboard appears as the quality-dashboard artifact on the run.

To add the dashboard to an existing pipeline (in any repo, once the tool is on NuGet), use the step template:

steps:
- template: pipelines/templates/quality-dashboard.yml
  parameters:
    configPath: dashboard.config.json
    sonarToken: $(SonarToken)
    toolSource: nuget
    zapRoot: $(Pipeline.Workspace)/zap   # optional

WCAG reports in a pipeline. In the site's test job, set QD_WCAG_REPORT to a folder you publish (e.g. $(Build.ArtifactStagingDirectory)/reports/customer-portal/wcag-report.json) and publish that folder as an artifact. In a later job of the same run, pass the artifact name to the template's reportsArtifact parameter: it's downloaded and used as the reports root, so accessibilityReport paths are relative to the artifact (e.g. customer-portal/wcag-report.json). The published folder already holds wcag-report.html; for PDF or Word, add a step running quality-dashboard wcag --report <json> --out <folder> in either job and publish its output.

Publishing the tool to NuGet

pipelines/publish-tool.yml builds, tests and packs the tool on every Git Flow branch, and publishes releases and prereleases to nuget.org.

Branching and versions. The repo follows Git Flow. Versions come from git history via the GitTools extension (gitversion-setup@4 / gitversion-execute@4) and GitVersion.yml; no tags are needed.

Branch Version Published to nuget.org
main 1.1.0, then the version of each merged release/hotfix branch (1.2.0, 1.2.1 …) Yes (stable)
release/X.Y.Z X.Y.Z-beta.N (N rises with each commit) Yes (prerelease)
hotfix/X.Y.Z X.Y.Z-beta.N Yes (prerelease)
develop next minor as alpha, e.g. 1.3.0-alpha.N No (pipeline artifact only)
feature/*, pull requests e.g. 1.3.0-my-feature.1 No (pipeline artifact only)

To release, branch release/1.2.0 from develop, stabilise it (each push publishes 1.2.0-beta.N), then merge it into main (publishes 1.2.0) and back into develop. Hotfixes work the same way from main with hotfix/1.2.1. The run's build number is set to the version. Don't commit to main directly: a direct commit keeps the previous version, so its push is skipped as a duplicate.

Install a prerelease with dotnet tool install -g Ossa.QualityDashboard --prerelease (or --version 1.2.0-beta.3).

  • Setup:

    1. Install the GitTools extension in the Azure DevOps organisation.
    2. Create a nuget.org API key that can push Ossa.QualityDashboard*, and add it as the secret pipeline variable NuGetApiKey.
    3. Create a develop branch from main if you don't have one.
  • GitHub Actions: if the repo is on GitHub, .github/workflows/publish-tool.yml does the same job: the same GitVersion calculation (GitTools gitversion/setup and execute actions), the same branches publishing, and the same artifacts. A build job on windows-latest tests and packs, and a separate publish job pushes to nuget.org through the nuget environment.

    The workflow uses nuget.org Trusted Publishing, so no long-lived API key is stored. GitHub issues a short-lived OIDC token, and the NuGet/login action exchanges it for a one-hour nuget.org key just before the push. To set it up:

    1. On nuget.org, go to your username → Trusted Publishing and add a policy with the GitHub repository owner and repository, Workflow File publish-tool.yml (file name only) and Environment nuget. Give it a scope that allows pushing new packages and new versions of Ossa.QualityDashboard*.
    2. In GitHub, add a secret NUGET_USER with the nuget.org username (profile name, not email) that owns the policy.
    3. Optionally, add required reviewers to the nuget environment so each publish needs approval.

    For a private repository, the policy starts out active for 7 days only, and becomes permanent after the first successful publish. Use either the Azure DevOps pipeline or this workflow for publishing, not both.

  • Check the version locally: dotnet tool restore, then dotnet gitversion /showvariable FullSemVer. GitVersion is pinned in dotnet-tools.json to match the pipeline.

  • Two packages, one version: Ossa.QualityDashboard (the tool) and Ossa.QualityDashboard.Accessibility (the WCAG scanner library for site test projects) are packed and published together. The tests drive the Microsoft Edge that's preinstalled on windows-latest, so no Playwright browser download is needed.

  • Package layout: the Ossa.QualityDashboard tool package is multi-targeted to net8.0, net9.0 and net10.0. Each framework has its own tools/<tfm>/any folder and dependency group, and NuGet picks the nearest framework at install time. Native libraries (SkiaSharp, HarfBuzz, SQLite) use NuGet's runtimes/<rid>/native layout, trimmed to win-x64 and win-arm64, and the .NET host loads the right one at run time. Change TargetFrameworks and ToolPackRuntimes in the csproj to adjust either list.

Build a package locally with dotnet pack src/QualityDashboard -c Release (output in nupkg/), and install it with dotnet tool install -g Ossa.QualityDashboard --add-source ./nupkg.

Agent requirements: Windows, plus Microsoft Edge or Google Chrome for PDF output (both are on Microsoft-hosted windows-latest; Edge ships with Windows 10/11 and Windows Server 2022+). The template installs the .NET 10 SDK itself. ZAP's Docker images need Linux containers, so run the scan itself on a Linux job/pipeline and publish the JSON reports as an artifact.

Self-contained executable

dotnet publish src/QualityDashboard -c Release -f net10.0 -r win-x64 -o ./publish
./publish/QualityDashboard.exe --demo

This gives a single QualityDashboard.exe that needs no .NET install, which is handy for agents without internet access or the .NET SDK.

Development

dotnet test

The WCAG scanner tests launch Microsoft Edge through Playwright. To use another browser, set WCAG_TEST_BROWSER to chrome, chromium, firefox or webkit (the last three need pwsh tests/QualityDashboard.Accessibility.Tests/bin/Debug/net10.0/playwright.ps1 install <browser> first).

Path Purpose
src/QualityDashboard/Sonar SonarQube Web API client (qualitygates/project_status, measures/component, measures/search_history).
src/QualityDashboard/Zap ZAP traditional-JSON parser.
src/QualityDashboard/Accessibility WCAG report parser (AccessibilityReportParser).
src/QualityDashboard.Accessibility The Ossa.QualityDashboard.Accessibility scanner library (AccessibilityAuditor).
src/Shared Compiled into both the tool and the library, so both give identical results: WCAG scoring rules and report schema (WcagScoring.cs), the site/page summary (WcagSummary.cs) and the WCAG HTML report (WcagReportHtml.cs). The summary records are public in the tool (for its JSON output) and internal in the library.
src/QualityDashboard/WcagCommand.cs The quality-dashboard wcag command.
src/QualityDashboard/Model Data collection and the RAG / trend rules (DashboardBuilder).
src/QualityDashboard/History SQLite store of ZAP scan and WCAG results (HistoryStore).
src/QualityDashboard/Render HTML (template in Templates/dashboard.html), PDF (headless Edge prints the HTML) and DOCX (OpenXML) renderers.
src/QualityDashboard/Demo Sample data for --demo.

The page is designed to fit about 8 projects on one A4 page. With more, the table carries onto a second page (the header row repeats). WCAG detail by page is in the separate WCAG report, not on the dashboard.

Third-party packages: the tool uses DocumentFormat.OpenXml (MIT), Microsoft.Data.Sqlite (MIT) and ScottPlot (MIT). The scanner library uses Microsoft.Playwright (MIT) and Deque.AxeCore.Playwright (MIT), which bundles axe-core (MPL-2.0). No commercially licensed components.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.0-beta.9 0 11/14/2026