| name | denom-selection |
| description | EGM denomination selection and verification. Use when selecting denominations, handling forced denom popup, switching denominations during play, verifying denom changes via PaytableProvider AVV readback, iterating denom/bet combinations, or detecting DenomForcedSelectionPopUp. Covers: GetDenomButtonName(index), ChangeDenomInProgress, press-poll-repress retry pattern, AVV verification, OpenPopUps detection. stages: test-implementation, test-validation |
Denomination Selection
When to Use
- Selecting or switching denominations in any test
- Handling the forced denomination selection popup after credit insertion
- Verifying denomination changes took effect
- Iterating through denomination/bet-level combinations
- Detecting whether the denom popup is visible
Denomination Selection via Direct Buttons
- Mechanism:
ButtonNames.DenomButtons.GetDenomButtonName(index) where index is 1-based.
- Available denominations: Order corresponds to
ConfigurationStatusProvider.GetEnabledPaytablesAsync().
- Use case: Both for dismissing the forced denomination popup AND for switching denominations during active play.
Forced Denomination Selection Popup
Trigger
Credit insertion at zero balance on multi-denomination configurations. Also shown when selecting the change denom button. Forced the first time credits are entered for a session.
Detection
Read StatusDatabasePaths.PopUpStatus.OpenPopUps — a JsonArray of JsonObject elements. Each element has a "Name" property containing the popup identifier (e.g., "DenomForcedSelectionPopUp").
var openPopUps = await statusDatabaseFacade.GetStatusValueAsync<JsonNode>(
StatusDatabasePaths.PopUpStatus.OpenPopUps,
nameof(StatusDatabasePaths.PopUpStatus.OpenPopUps));
if (openPopUps is JsonArray array)
{
var hasDenomPopup = array.Any(item =>
{
var name = (item as JsonObject)?["Name"]?.GetValue<string>();
return string.Equals(name, "DenomForcedSelectionPopUp", StringComparison.Ordinal);
});
}
Dismissal
Press a denomination button — e.g., ButtonNames.DenomButtons.GetDenomButtonName(1).
State After Selection
StatusDatabasePaths.GameStatus.InActivePlayMode becomes true.
- While the popup is visible, game cycles cannot be initiated.
CRITICAL: Button Presses Can Be Swallowed
Button presses on the EGM can be silently swallowed by UI transitions, popups, or animation states. A Denom button press that occurs during a brief intermediate screen is simply ignored — the game stays at whatever denomination it was previously on. Code that presses a button and proceeds without verifying the state changed will silently test the wrong configuration.
Anti-Pattern Rule
State verification assertions for denomination changes must never be removed to resolve test failures. If a readback mismatch occurs, fix the mapping, timing, or retry logic — do not strip the assertion.
Press → Poll → Re-Press Retry Pattern
After pressing DenomN, the test MUST verify that the active paytable actually changed to the expected AVV identifier. Waiting only for ChangeDenomInProgress → false is insufficient — that flag indicates the denom-change animation finished, not that the correct denomination was selected (the press may have been swallowed entirely).
Canonical Code Template
private const int DenomVerifyPollIntervalMs = 200;
private const int DenomVerifyTimeoutMs = 5000;
private const int DenomChangeMaxRetries = 3;
private const int DenomChangePollIntervalMs = 500;
private const int DenomChangeTimeoutMs = 15000;
private async Task SelectAndVerifyDenomAsync(
int denomButtonIndex,
string expectedAvv)
{
for (var attempt = 1;
attempt <= DenomChangeMaxRetries;
attempt++)
{
await buttonsFacade.EmulateButtonPressByNameAsync(
ButtonNames.DenomButtons
.GetDenomButtonName(denomButtonIndex));
await WaitForConditionAsync(
async () =>
!await IsChangeDenomInProgressAsync(),
timeoutMs: DenomChangeTimeoutMs,
pollIntervalMs: DenomChangePollIntervalMs);
var matched = await WaitForConditionAsync(
async () =>
{
var active = await paytableProvider
.GetActivePaytableAsync();
return string.Equals(
active,
expectedAvv,
StringComparison.Ordinal);
},
timeoutMs: DenomVerifyTimeoutMs,
pollIntervalMs: DenomVerifyPollIntervalMs);
if (matched)
{
return;
}
var actual = await paytableProvider
.GetActivePaytableAsync();
await TestContext.Out.WriteLineAsync(
$"[Retry] Denom button {denomButtonIndex} "
+ $"attempt {attempt}: expected {expectedAvv}, "
+ $"got {actual}. Re-pressing...");
}
var finalAvv = await paytableProvider
.GetActivePaytableAsync();
Assert.Fail(
$"Denomination selection failed after "
+ $"{DenomChangeMaxRetries} attempts. "
+ $"Expected AVV={expectedAvv}, got {finalAvv}.");
}
Key Rules
- Use
PaytableProvider.GetActivePaytableAsync() (reads from SAE UTP module) as the authoritative readback — NOT ChangeDenomInProgress
ChangeDenomInProgress → false is a necessary precondition (wait for animation), but NOT sufficient proof the correct denom is active
- If the readback shows the wrong AVV after timeout, re-press the button and re-verify
- After exhausting retries, fail explicitly with both expected and actual AVV
- Log every retry attempt via
TestContext.Out.WriteLineAsync for diagnostics
Required Dependencies
Tests that iterate denominations MUST wire these dependencies in [SetUp]:
PaytableProvider — for GetActivePaytableAsync() (requires SaeFacade)
StatusDatabaseFacade — for ChangeDenomInProgress and OpenPopUps checks
ButtonsFacade — for EmulateButtonPressByNameAsync()
ConfigurationStatusProvider — for GetEnabledPaytablesAsync() (denomination list)