| name | bet-selection |
| description | EGM bet level selection using direct CustomBet buttons. Use when selecting bet levels, iterating bet/denom combinations, verifying bet changes, pressing bet buttons, or fixing code that uses BetIncrement/BetDecrement. Covers: GetBetButtonName(index), AvailableStakeCombinationIndices, SelectedStakeCombinationIndex, press-poll-repress retry pattern, index mapping rule, TotalBet diagnostic logging. CRITICAL: bet levels use direct selection buttons NOT increment/decrement. stages: test-implementation, test-validation |
Bet Level Selection
When to Use
- Selecting or changing bet levels in any test
- Iterating through bet/denomination combinations
- Verifying bet level changes took effect
- Fixing code that incorrectly uses
BetIncrement/BetDecrement
- Writing tests that play games at specific bet levels
CRITICAL RULE: Direct Selection Only
Bet levels are selected using direct selection buttons (CustomBet1, CustomBet2, ..., CustomBetN), NOT increment/decrement buttons.
await buttonsFacade.EmulateButtonPressByNameAsync(
ButtonNames.BetButtons.GetBetButtonName(index));
await buttonsFacade.EmulateButtonPressByNameAsync("BetIncrement");
await buttonsFacade.EmulateButtonPressByNameAsync("BetDecrement");
BetIncrement/BetDecrement exist only on PhysicalButtonIds.BetButtons (physical hardware IDs), not on ButtonNames.BetButtons (UTP button names). Using them causes CS0117 compilation errors.
Index Mapping Rule
CustomBetN (1-based button) selects the Nth entry in AvailableStakeCombinationIndices. The readback from SelectedStakeCombinationIndex equals AvailableStakeCombinationIndices[N-1], not N-1 itself.
Example: If AvailableStakeCombinationIndices is [0, 2, 5, 8]:
- Pressing
CustomBet3 sets SelectedStakeCombinationIndex to 5
- Pressing
CustomBet1 sets SelectedStakeCombinationIndex to 0
Compute the expected index before pressing:
var indices = await mptStatusProvider.GetAvailableStakeCombinationIndicesAsync();
var expectedIndex = indices[betButtonIndex - 1]!.GetValue<int>();
Available Bet Levels
- Query:
StatusDatabasePaths.MptStatus.AvailableStakeCombinationIndices — a JsonArray of integers. Count determines valid CustomBet buttons.
- Current bet level:
StatusDatabasePaths.MptStatus.SelectedStakeCombinationIndex — zero-based stake combination index.
- Precondition: Game must be in Active Play Mode (
InActivePlayMode is true) and not in a game cycle. Bet buttons are ignored during popups or game cycles.
Press → Poll → Re-Press Retry Pattern
After pressing CustomBetN, the test MUST verify that SelectedStakeCombinationIndex actually changed to the expected value. Button presses on the EGM can be silently swallowed by UI transitions, popups, or animation states.
Anti-Pattern Rule
State verification assertions for bet level changes must never be removed to resolve test failures during automated or manual fix iterations. If a readback mismatch occurs, fix the mapping, timing, or retry logic — do not strip the assertion. A test that plays games without verifying the active bet level is testing nothing.
Canonical Code Template
private const int BetChangePollIntervalMs = 200;
private const int BetChangeTimeoutMs = 5000;
private const int BetChangeMaxRetries = 3;
private async Task SelectAndVerifyBetLevelAsync(
int betButtonIndex,
int expectedStakeIndex)
{
for (var attempt = 1;
attempt <= BetChangeMaxRetries;
attempt++)
{
await buttonsFacade.EmulateButtonPressByNameAsync(
ButtonNames.BetButtons
.GetBetButtonName(betButtonIndex));
var matched = await WaitForConditionAsync(
async () =>
{
var current = await mptStatusProvider
.GetSelectedStakeCombinationIndexAsync();
return current == expectedStakeIndex;
},
timeoutMs: BetChangeTimeoutMs,
pollIntervalMs: BetChangePollIntervalMs);
if (matched)
{
return;
}
var actual = await mptStatusProvider
.GetSelectedStakeCombinationIndexAsync();
await TestContext.Out.WriteLineAsync(
$"[Retry] Bet button {betButtonIndex} attempt "
+ $"{attempt}: expected index "
+ $"{expectedStakeIndex}, got {actual}. "
+ "Re-pressing...");
}
var finalIndex = await mptStatusProvider
.GetSelectedStakeCombinationIndexAsync();
Assert.Fail(
$"Bet level selection failed after "
+ $"{BetChangeMaxRetries} attempts. "
+ $"Expected SelectedStakeCombinationIndex="
+ $"{expectedStakeIndex}, got {finalIndex}.");
}
Key Rules
- Compute the expected index BEFORE pressing:
var indices = await mptStatusProvider.GetAvailableStakeCombinationIndicesAsync(); var expectedIndex = indices[betButtonIndex - 1]!.GetValue<int>();
- Poll
SelectedStakeCombinationIndex in a bounded loop — do NOT use a fixed delay
- If the poll times out, re-press the button (the first press was swallowed) — do NOT skip or weaken the assertion
- After exhausting retries, fail explicitly with both expected and actual values
- Log every retry attempt via
TestContext.Out.WriteLineAsync for diagnostics
TotalBet Diagnostic Logging
When iterating bet/denom combinations, log StatusDatabaseFacade.GetCurrentTotalBetCreditsAsync() before each game start:
var totalBet = await statusDatabaseFacade
.GetCurrentTotalBetCreditsAsync();
await TestContext.Out.WriteLineAsync(
$" Playing denom {denomAvv} bet {betLevel}: "
+ $"TotalBet={totalBet}");
If consecutive bet levels produce identical TotalBet values, the button presses are not taking effect.
Required Dependencies
Tests that iterate bet levels MUST wire these dependencies in [SetUp]:
MptStatusProvider — for GetSelectedStakeCombinationIndexAsync() and GetAvailableStakeCombinationIndicesAsync()
StatusDatabaseFacade — for GetCurrentTotalBetCreditsAsync() (diagnostic logging)
ButtonsFacade — for EmulateButtonPressByNameAsync()
SelfPlay Warning
SelfPlayFacade.StartSingleGameAsync() uses the SelfPlay UTP module's internal automation, which overrides the currently selected bet level with its own configured wager. Any preceding CustomBet button press is ignored. When a test needs to play at a specific bet level, start the game by pressing ButtonNames.GameplayButtons.StartGame via ButtonsFacade.EmulateButtonPressByNameAsync() instead.