Skip to main content

Site Mode

Most projects run more than one Kensa-producing test execution: unit tests, UI tests, integration tests, often in distinct sourcesets. Default Kensa output gives each its own kensa-output/ directory — three separate sites to open. Site mode aggregates them into one.

The aggregation groups rather than flat-merges: each sourceset's tests stay under their own root in the sidebar, with their own version, generated-at timestamp, and configuration. Switching between source roots is one click.

Disk layout

Default (single bundle, unchanged):

build/kensa-output/
├── index.html
├── kensa.js
├── logo.svg
├── configuration.json
├── indices.json
├── results/
└── tabs/

Site mode:

build/kensa-site/
├── index.html # shell — written by assembleKensaSite
├── kensa.js
├── logo.svg
├── manifest.json # written by assembleKensaSite
└── sources/
├── uiTest/
│ ├── configuration.json
│ ├── indices.json
│ ├── results/
│ └── tabs/
├── scenarioTest/
└── test/

Each sources/<id>/ directory is a complete data-only bundle — the IntelliJ plugin and the Kensa CLI single-bundle viewer both work pointed at it directly.

Manifest schema

manifest.json lists the sources the shell renders:

{
"schemaVersion": 1,
"kensaVersion": "0.8.0",
"sources": [
{ "id": "uiTest", "title": "UI Tests", "url": "sources/uiTest" },
{ "id": "scenarioTest", "title": "Scenario Tests", "url": "sources/scenarioTest" },
{ "id": "test", "title": "Acceptance Tests","url": "sources/test" }
]
}
  • url is resolved relative to manifest.json, so the same site works behind any host.
  • Unknown future fields must be ignored by older shells; schemaVersion only bumps for breaking changes.

Enabling site mode

Two lines in build.gradle.kts:

kensa {
outputSourceSets = setOf("uiTest", "scenarioTest", "test")
site = true
}

outputSourceSets (since plugin 0.9.4) lists the sourcesets whose Test tasks contribute per-source bundles to the site — it's what governs which sources appear. If any of those tests also use @RenderedValue / @ExpandableSentence, list those sourcesets in sourceSets too so the compiler plugin instruments them; the two properties are independent (see Compiler-plugin vs output sourcesets). On plugin ≤ 0.9.3 the single sourceSets property drove both.

Then run the tests and assemble:

./gradlew check assembleKensaSite

Open build/kensa-site/index.html. The sidebar shows one root per sourceset.

Partial runs

assembleKensaSite is partial-run friendly — running only some of the configured sourcesets produces a site with only those sources. Sourcesets that didn't run are warned about in the build output, not errored:

> Kensa source 'test' was expected but no bundle is present at build/kensa-site/sources/test;
omitting from manifest. Run its test task/execution to include it.

So ./gradlew uiTest assembleKensaSite is a valid dev-loop command — just produces a site with only the UI source.

Stale source cleanup

If you remove a sourceset from outputSourceSets, assembleKensaSite deletes its bundle from sources/ on the next run. No manual cleanup needed.

Source IDs and titles

The runtime contract uses two system properties:

PropertyEffect
kensa.source.idPer-source bundle directory name. Defaults to the sourceset name.
kensa.source.titleOverrides Configuration.titleText — the label shown for that source in the sidebar.

The Gradle plugin pre-populates kensa.source.id (with the sourceset name); the title flows from Configuration.titleText, which the runtime writes into the bundle's configuration.json and the assemble step reads back (or from the plugin's sourceTitles map). Override either via task.systemProperty on the relevant Test task — see per-source overrides.

Cross-build uniqueness in CI

If you publish per-build sites and want to point a remote loader (e.g. the Kensa desktop viewer) at multiple builds, override kensa.source.id to include the build number:

tasks.named<Test>("uiTest") {
systemProperty("kensa.source.id", "${System.getenv("BUILD_NUMBER") ?: "local"}-uiTest")
systemProperty("kensa.source.title", "Build #${System.getenv("BUILD_NUMBER") ?: "local"} — UI Tests")
}

Multi-module builds

Since plugin 0.9.3, site mode can aggregate sources across an entire Gradle multi-project build into one viewable site at the rootProject. No new plugin id, no enumeration of participating subprojects — apply the plugin where it's needed and turn site = true on at the root.

rootProject/build.gradle.kts
plugins {
id("dev.kensa.gradle-plugin")
}

kensa {
site = true
// Optional cross-module title overrides, keyed by namespaced id:
sourceTitles["web__test"] = "Web tests"
sourceTitles["api__test"] = "API tests"
sourceTitles["libs-billing__test"]= "Billing tests"
}
:web/build.gradle.kts (subproject — unchanged from single-project usage)
plugins {
id("dev.kensa.gradle-plugin")
}

kensa {
site = true
outputSourceSets = setOf("test", "uiTest")
// Optional contributor-local titles (override per source set name, not namespaced id):
sourceTitles["uiTest"] = "Web UI"
}

Run ./gradlew test:assembleKensaSite is automatically finalized by every contributing Test task across modules, and <rootDir>/build/kensa-site/ ends up with one aggregated manifest:

<rootDir>/build/kensa-site/
├── index.html
├── kensa.js
├── logo.svg
├── manifest.json
└── sources/
├── api__test/
├── libs-billing__test/
├── web__test/
└── web__uiTest/

Role discovery

The plugin decides each project's role automatically once every project has been evaluated:

Project contextRole
Is rootProject, site = true, ≥1 subproject also applies the plugin with site = trueAggregator
Not root, site = true, root is AggregatorContributor
site = true with no aggregator on root (single-project, or root doesn't apply the plugin)Standalone
site = false (default)(no site)

Subprojects that don't apply dev.kensa.gradle-plugin simply never register — they're not part of the site. Subprojects with site = false likewise don't contribute, even if they apply the plugin.

Namespaced source ids

Each contributor's sources are identified by <slug>__<sourceSet> in the aggregated manifest, where slug strips the leading : from the project path and replaces inner : with -:

Project pathSource setNamespaced id
:webtestweb__test
:webuiTestweb__uiTest
:libs:billingtestlibs-billing__test
: (root's own)test<rootProjectName>__test

__ is reserved as the separator — a contributor source-set name (or contributor sourceTitles key) containing __ is rejected at configuration time.

Title precedence

Aggregator mode adds one more rung at the top of the existing title precedence:

  1. Root kensa.sourceTitles["<slug>__<sourceSet>"] — namespaced key, wins everything.
  2. Contributor kensa.sourceTitles["<sourceSet>"] — bare source-set name, carried into the aggregator from the registering subproject.
  3. Per-source configuration.json#titleText (from Kensa.konfigure { titleText = ... } or the runtime default).
  4. The namespaced id itself.

Why build-declared titles win over code-side titleText. The aggregated site is a curated whole: only the build — and ultimately the root — sees every source across every module and can label them coherently without collisions. A platform/CI owner often needs to relabel a source whose module code they don't own, so the build keeps final say. Code-side titleText remains the fallback (rung 3) for sources the build doesn't name.

This means a source with both a code-side titleText and a build sourceTitles entry uses the build value — the code-side one is discarded. To avoid silent surprise, :assembleKensaSite warns when a build title overrides a different, non-default code-side title; set the title in one place to clear it.

kensa-core version consistency

Each contributor's resolved kensa.kensaCoreVersion is captured at registration. :assembleKensaSite fails fast if any contributor's version differs from the aggregator's, naming the divergent modules:

Kensa site: kensa-core version mismatch. Aggregator: 0.8.3. :legacy reports 0.8.0.
Align kensa.kensaCoreVersion across modules, or override kensa.kensaCoreVersion on the root.

This prevents shipping a site whose shell (resolved on the root) doesn't match a contributor's bundle format.

Partial runs

./gradlew :web:test writes only web__test's bundle and finalizes the root :assembleKensaSite. The aggregated manifest lists every source that's present on disk — other modules' previously-produced bundles stay and remain listed. If you removed a subproject from settings.gradle.kts between runs, its sources/<id>/ directory is pruned on the next assembly (same stale-cleanup semantics as single-project mode, applied across the aggregate set).

Single-project unchanged

A single-project build (no participating subprojects) falls through to the Standalone path — byte-identical output to pre-0.9.3 site mode. No namespaced ids, no new task names, no manifest schema change.

Maven

The Maven plugin is single-module today — each assemble-site mojo execution produces its own per-module site. Reactor-level aggregation is a future feature.

CI / hosted use

The site is fully self-contained and uses relative URLs in the manifest, so it works behind any static host without extra configuration:

  1. CI runs ./gradlew check assembleKensaSite.
  2. CI publishes build/kensa-site/ as a build artifact.
  3. The CI report tab points at kensa-site/index.html.

Works zero-config in TeamCity's report-tab feature, GitHub Pages, Jenkins HTML Publisher, etc.

Browser caching of manifest.json

Static hosts often serve everything with long Cache-Control: max-age=.... The Kensa shell defeats this for the manifest by fetching with cache: 'no-store', so the source list always reflects the latest build. Per-source data (configuration.json, indices.json, results/*.json) inherit your host's cache policy — if you publish per-build sites under stable URLs and want viewers to always see fresh data, set Cache-Control: no-store on the artifact server, or add per-build URL prefixes (e.g. /<build-id>/kensa-site/...).

What's NOT in site mode

  • The single-bundle layout is unchanged when site = false. Default Kensa workflow is untouched.
  • Cross-build aggregation (e.g. "compare last 5 nightly UI runs") is remote-source territory for the Kensa desktop viewer, not a CI artifact concern.
  • Per-task kensa { … } Gradle DSL extension (set source-specific titles inside the DSL rather than via systemProperty) is on the roadmap.