Builds a test harness that runs the same suite under every relevant flag combination - picks the minimum cover (single flags + pairwise interactions where the team marks them, not the full 2^N cartesian product), wires an OpenFeature in-memory provider so the suite never hits the production flag service, runs each combination as its own labeled CI matrix shard, and emits a per-combination result matrix. Use when a feature behind a flag must be verified on AND off (release toggles + experiment toggles per Hodgson) and the team wants those runs deterministic and parallel.
79
99%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
A test that hits the production flag service is non-deterministic by definition - the answer depends on whoever toggled the flag last. And a test that asks "did we test the feature with the flag off?" needs both runs side by side.
This skill builds a harness that:
The skill's reference architecture targets OpenFeature because it standardizes the SDK across LaunchDarkly, Flagsmith, ConfigCat, self-hosted, etc. - the harness works identically against any provider (openfeature-overview).
If the team has only one or two flags and a flat "always on for
test" config works, this skill is overkill - set the test
environment's flag values once in setup and stop there.
fail-fast: false on the matrix so every failing
combination surfaces in one run, not just the first.Per feature-toggles, flags fall into four categories with different test needs:
| Category | Lifespan | Dynamism | Test combinations needed |
|---|---|---|---|
| Release toggle | Days - weeks | Static at deploy | OFF (current) and ON (new behavior). 2 runs. |
| Experiment toggle | Days - weeks | Per-request dynamic | One run per variant (A / B / control). |
| Ops toggle | Long-lived | Per-request dynamic | ON (normal) and OFF (degraded / kill). |
| Permissioning toggle | Years | Per-request dynamic | One run per relevant user cohort. |
For experiment and permissioning toggles, the harness simulates each cohort by seeding the EvaluationContext (feature-toggles).
Don't run all 2^N combinations. Author marks the interactions worth testing:
# tests/flag-matrix.yaml
flags:
new_checkout: { kind: release, test: [off, on] }
promo_codes: { kind: release, test: [off, on] }
ranking_experiment: { kind: experiment, variants: [control, treatment_a, treatment_b] }
payment_kill_switch: { kind: ops, test: [on, off] } # off = degraded
interactions:
# The author asserts these flag pairs interact; run their combinations explicitly.
- [new_checkout, promo_codes]
# Ranking experiment doesn't interact with checkout; don't bloat the matrix.The harness enumerates every flag's variants individually plus the listed interaction tuples, never the full 2^N product. See the Worked example for the run count this schema yields.
Wire the in-memory provider so the suite reads flag values from
FLAGS_JSON instead of the production service (Node shown; Python and
Java variants in the reference):
// tests/harness/flag-harness.ts
import { OpenFeature, InMemoryProvider } from '@openfeature/server-sdk';
export function withFlags(flags: Record<string, unknown>) {
return OpenFeature.setProviderAndWait(new InMemoryProvider(
Object.fromEntries(Object.entries(flags).map(([k, v]) => [k, {
defaultVariant: 'configured', variants: { configured: v }, disabled: false,
}])),
));
}
// beforeAll(() => withFlags(JSON.parse(process.env.FLAGS_JSON || '{}')));Generate one shard per combination, then feed the JSON to the CI matrix:
python scripts/gen-flag-matrix.py tests/flag-matrix.yaml # -> JSON array of {name, flags}gen-flag-matrix.py source, CI matrix YAML, result aggregation,
and the PR / nightly cadence split:
references/matrix-and-ci.md.A checkout team ships new_checkout (release) and promo_codes
(release) behind flags, runs a ranking_experiment (control /
treatment_a / treatment_b), and guards payments with a
payment_kill_switch (ops). They author tests/flag-matrix.yaml
exactly as in Step 1 and declare the one interaction that matters:
[new_checkout, promo_codes].
gen-flag-matrix.py enumerates 9 single-flag runs (2 + 2 + 3 + 2)
plus 4 interaction runs (new_checkout × promo_codes) = 13 shards,
not the 24 of the full 2^N product. Each shard boots the suite with
the in-memory provider pinned to that combination via FLAGS_JSON,
so no run touches the production flag service.
CI runs all 13 shards in parallel with fail-fast: false. The
aggregated matrix shows promo_codes=on red at checkout.spec.ts:42
and ranking_experiment=treatment_b red at cart.spec.ts:18, while
the baseline and every other combination pass. The team reads two
flag-specific failures in one glance instead of re-running to find
the second.
The full anti-pattern table (2^N blowup, hitting the prod provider,
asserting flag value instead of behavior, fail-fast: true, missing
baseline row) and the harness's limitations (no targeting rules,
author-declared interactions, per-request dynamism, matrix-size
bound) are in
references/anti-patterns-and-limits.md.
getBooleanValue / getStringValue
/ getNumberValue / getObjectValue signatures + default-value
behavior.testcontainers,
docker-compose-tests - the surrounding stack the harness drives
per combination.