Skip to content

feat: expose feature flag state to AltTester tests - #9682

Merged
mikhail-dcl merged 3 commits into
devfrom
feat/alttester-feature-flags-probe
Aug 12, 2026
Merged

feat: expose feature flag state to AltTester tests#9682
mikhail-dcl merged 3 commits into
devfrom
feat/alttester-feature-flags-probe

Conversation

@mikhail-dcl

@mikhail-dcl mikhail-dcl commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

What does this PR change?

Adds AlttesterFeatureFlagsProbe — a static, ALTTESTER-gated entry point that lets the
AltTester UI suite read feature flag state from the running client.

The InWorld navbar test hard-codes six flag-gated sidebar buttons as always-present. When
alfa-marketplace-credits was turned off, the test started failing on every run — a correct
client, a wrong expectation. The suite can't fix that by fetching the flags document itself:
Unleash evaluates its hostname strategy off the referer header (a bare fetch returns 51 flags
instead of 114) and the resolved state also folds in app args and editor overrides. Reading the
client's own values removes that whole class of drift.

Four methods:

Method Returns
IsFlagEnabled(flagId) raw remote flag, e.g. alfa-marketplace-credits
IsFeatureEnabled(featureId) resolved FeatureId state — flag + app args + editor, what the UI gates on
GetFlagVariantJson(flagId) variant name/enabled/payload, for allowlist gating
GetStatusJson() all enabled flags and features, for failure diagnostics

IsFeatureEnabled throws on a name that isn't a FeatureId member, so a typo fails loudly
instead of reading as "off" and letting a test assert the wrong thing quietly. GetStatusJson
is the opposite — it never throws, because a test calls it when something has already gone wrong.

Sits in Explorer/Assets/DCL/FeatureFlags/, which compiles into DCL.Network, so it reaches
both FeatureFlagsConfiguration and FeaturesRegistry with no new assembly references. Gated
by ALTTESTER and stripped from release builds by CloudBuild.cs, same as
AlttesterSceneReadinessProbe.

Docs: docs/automation-testing.md gains a Static Probes section — it documented every other part
of the AltTester setup but not the CallStaticMethod hooks, so all three (scene readiness, feature
flags, PerfSampler) are now listed in one place, with docs/feature-flags.md pointing at it.

No production code paths change.

Test Instructions

EditMode tests cover the probe: DCL.FeatureFlags.Tests.AlttesterFeatureFlagsProbeShould
(raw flag reads, case-insensitive FeatureId parsing, throw-on-unknown-name, variant payload,
status shape, and the not-yet-initialized path).

make test-editmode TEST_FILTER=DCL.FeatureFlags.Tests.AlttesterFeatureFlagsProbeShould

Expected result: all tests pass.

Consumed from the automation suite via AltDriver.CallStaticMethod, assembly DCL.Network,
type DCL.FeatureFlags.AlttesterFeatureFlagsProbe:

AltDriver.CallStaticMethod<bool>(
    "DCL.FeatureFlags.AlttesterFeatureFlagsProbe", "IsFeatureEnabled",
    "DCL.Network", new object[] { "MarketplaceCredits" });

Requires an instrumented (non-release) build — release zips strip the ALTTESTER define.

Quality Checklist

  • Changes have been tested locally — EditMode tests authored but not executed locally (the Unity Editor held the project lock, so batchmode could not run); CI editmode covers them
  • Documentation has been updated (if required)
  • Performance impact has been considered
  • For SDK features: Test scene is included

AlttesterFeatureFlagsProbe reads FeatureFlagsConfiguration and FeaturesRegistry
through AltDriver.CallStaticMethod, so the UI suite can assert against the flags
the client gates on instead of re-fetching the remote document and re-deriving
Unleash evaluation. ALTTESTER-gated, like AlttesterSceneReadinessProbe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

🚦 CI Status

Build

Build skipped — no changes detected under Explorer/.

Lint

Lint in progress, come back later!

Tests

All Unity tests passed ✅

TESTS SUITE Result Passed Failed Skipped
EditMode ✅ Passed 24626 0 13
PlayMode ✅ Passed 236 0 5

Neither repo mentioned them, so the feature flag probe had nowhere to be
discovered from. Lists all three, and points feature-flags.md at it so the
next person reaches for the probe instead of re-fetching the document.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@decentraland-bot decentraland-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review — feat: expose feature flag state to AltTester tests

STEP 2 — Root-cause check ✅

Problem: AltTester UI tests hard-code flag-gated sidebar buttons as always-present. When alfa-marketplace-credits was turned off remotely, the test failed even though the client was correct — the test expectation was wrong.

Does the diff fix the cause? Yes. The root cause is that the test suite cannot access the client's resolved feature flag state (Unleash hostname strategy, app args, and editor overrides make external re-derivation unreliable). This probe exposes the client's actual internal state to the test framework. PASS — this is the correct fix.

STEP 3 — Design & integration ✅

MANDATORY OWNER SEARCH: AlttesterFeatureFlagsProbe is a stateless static class — it holds no fields, no collections, no subscriptions, and no persistent state across frames. It is a read-only accessor, not a lifecycle owner. There is nothing to create, destroy, subscribe, or tear down.

It reads from two existing [Singleton] instances:

  • FeatureFlagsConfiguration.Instance — created during app startup, lives for the app lifetime. Defined in Explorer/Assets/DCL/FeatureFlags/FeatureFlagsConfiguration.cs.
  • FeaturesRegistry.Instance — created during app startup, depends on FeatureFlagsConfiguration. Defined in Explorer/Assets/DCL/FeatureFlags/FeaturesRegistry.cs.

Neither singleton gains a new lifecycle responsibility from this probe. The probe simply reads their existing public API.

Pattern precedent: Follows the established AlttesterSceneReadinessProbe pattern (Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/AlttesterSceneReadinessProbe.cs) — also a static class gated by #if ALTTESTER, also exposing internal client state to AltTester. The new probe is actually simpler (no mutable state, vs. the scene probe's volatile ISceneFacade? field).

Assembly placement: Sits in Explorer/Assets/DCL/FeatureFlags/, which compiles into DCL.Network — the same assembly as FeatureFlagsConfiguration and FeaturesRegistry. No new assembly references required. Correct location.

Design checklist:

  • Duplicates a lifecycle already owned? ❌ No lifecycle.
  • Reconciles every frame? ❌ Called on-demand by AltTester.
  • Holds persistent state outside ECS? ❌ Stateless.
  • Reaches data through repeated intermediary? ❌ Reads canonical singletons directly.

TEARDOWN / CONSUMPTION TRACE: No subscriptions, event hookups, callbacks, connections, buffers, or measurements. Nothing to tear down.

PASS — design is sound.

STEP 4 — Member audit ✅

Method Purpose Consumers Verdict
IsFlagEnabled(string) Raw remote flag state via FeatureFlagsConfiguration AltTester suite Legitimate thin accessor for the AltTester API surface; not a redundant wrapper — it provides the string-based entry point AltTester needs
IsFeatureEnabled(string) Resolved FeatureId state via FeaturesRegistry (flag + app args + editor) AltTester suite Distinct from IsFlagEnabled — reads a different source with different semantics. String→enum parsing is necessary for AltTester's reflection-based CallStaticMethod
GetFlagVariantJson(string) Variant name + payload as JSON AltTester suite Exposes data neither boolean method provides (variant name, payload type/value for allowlist gating)
GetStatusJson() Diagnostic snapshot of all enabled flags/features AltTester suite on failure Deliberately never throws — called when something is already wrong. Justified for failure diagnostics
ParseFeatureId(string) (private) Case-insensitive FeatureId parse + validation IsFeatureEnabled only Clean extraction of parse+validate; throws ArgumentException on typos — design intent per PR description

No failure modes triggered: no single-use merges needed (each method has a distinct AltTester purpose), no absent≠false confusion, no re-derivation, no redundant guards.

STEP 5 — Line-level review ✅

Pass A — Blocking issues: None found.

  1. Code quality / CLAUDE.md: Enum.GetValues(typeof(FeatureId)) in GetStatusJson() boxes each enum value and allocates an array; AllEnabledFlags (existing code) uses LINQ. Both are acceptable — GetStatusJson() is a diagnostics method called only on test failure, never per-frame.
  2. Bugs / runtime errors: IsFlagEnabled, IsFeatureEnabled, and GetFlagVariantJson will throw if singletons aren't initialized. This is by design — AltTester tests run after client initialization; GetStatusJson() guards with try/catch for the pre-initialization diagnostic case. Consistent with how AlttesterSceneReadinessProbe methods behave.
  3. Security: All code gated by #if ALTTESTER, stripped from release builds by CloudBuild.cs. No secrets, no auth bypass, no injection vectors.
  4. Performance: Not a hot path — called by AltTester framework, not per-frame.
  5. Error handling: GetStatusJson() has proper try/catch for the uninitialized case. Other methods are intentionally fail-fast.
  6. Unused imports: All imports consumed.
  7. Resource leaks: None — no subscriptions, events, handles, or disposables.
    8–11. No issues.

Pass B — Design, encapsulation & resource smells: None found.

  • Naming follows the Alttester*Probe convention.
  • XML doc comments describe behavior accurately (especially the "never throws" contract on GetStatusJson()).
  • No magic values, no encapsulation violations, no nullable annotation issues.
  • ParseFeatureId correctly guards against numeric string parsing with Enum.IsDefined after TryParse.

Security review: No security issues found. All code is test instrumentation gated by #if ALTTESTER and stripped from release builds. No hardcoded secrets, no user input from untrusted sources (AltTester calls come from the test harness), no sensitive data exposure in production.

STEP 5 — Test coverage ✅

The test file AlttesterFeatureFlagsProbeShould.cs covers:

  • Raw flag reads (enabled, disabled, absent) — 4 TestCases
  • Case-insensitive FeatureId parsing — 2 TestCases
  • Registry vs flag distinction (ReadFeatureStateFromRegistryNotFromFlag)
  • Throw on unknown name (including empty string and numeric) — 3 TestCases
  • Variant payload deserialization
  • Absent variant handling
  • Status snapshot shape with enabled flags/features
  • Not-yet-initialized path (singletons not set)

Tests follow AAA pattern, use NUnit + NSubstitute, and properly Reset()/Initialize() singletons in SetUp/TearDown.

STEP 6 — Complexity: SIMPLE

4 files (2 source + 2 .meta), ~280 lines total, no ECS/async/lifecycle changes. Straightforward read-only accessor with comprehensive tests.

STEP 7 — QA: NO

All code gated by #if ALTTESTER, stripped from release builds. No production code paths change. No user-facing behavior affected.

STEP 8 — Warnings

None. Main scene not modified.

STEP 9 — Verdict

Clean, well-structured PR that follows established patterns (AlttesterSceneReadinessProbe), has excellent test coverage, correct assembly placement, and no production impact. No findings.

REVIEW_RESULT: PASS ✅
COMPLEXITY: SIMPLE
COMPLEXITY_REASON: Stateless test-instrumentation probe in FeatureFlags — no ECS, async, lifecycle, or runtime changes
QA_REQUIRED: NO

Reviewed by Jarvis 🤖 · Requested by decentraland-bot via GitHub

@pravusjif pravusjif left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@mikhail-dcl mikhail-dcl added the no QA needed Used to tag pull requests that does not require QA validation label Aug 12, 2026
@mikhail-dcl
mikhail-dcl enabled auto-merge (squash) August 12, 2026 10:30
@mikhail-dcl
mikhail-dcl disabled auto-merge August 12, 2026 10:30
@mikhail-dcl
mikhail-dcl merged commit 9aa190d into dev Aug 12, 2026
34 of 39 checks passed
@mikhail-dcl
mikhail-dcl deleted the feat/alttester-feature-flags-probe branch August 12, 2026 10:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no QA needed Used to tag pull requests that does not require QA validation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants