Skip to content

UI TestEngine: pressButton/writeValue return status string (empty=OK)#5976

Merged
Grantim merged 1 commit into
masterfrom
fix-ui-disabled-status-return
Apr 24, 2026
Merged

UI TestEngine: pressButton/writeValue return status string (empty=OK)#5976
Grantim merged 1 commit into
masterfrom
fix-ui-disabled-status-return

Conversation

@Grantim
Copy link
Copy Markdown
Contributor

@Grantim Grantim commented Apr 24, 2026

Summary

PR #5961 conflated "widget is disabled" with "caller error" by turning the disabled case into an Expected failure. That broke the internal test_all_scens scenario runner — the Python binding's expectedValueOrThrow raised inside the GUI-thread callback and the process landed on std::terminate (exit 6). #5968 narrowed the blocked heuristic but left the silent-to-error semantic intact.

This PR cleanly separates the two axes:

  • Control::pressButton / Control::writeValue<T> now return Expected<std::string>. Empty string on success (click / write simulated). Non-empty string ("disabled[: <reason>]", same vocabulary as composeStatus) when the widget was drawn disabled — in that case the simulation is a silent no-op. unexpected(...) is reserved for hard errors only: path not found, wrong entry kind, value out of range / not in allowedValues.
  • The Python binding (mrviewerpy.uiPressButton / uiWriteValue) logs non-empty status via spdlog::warn and does not throw on disabled — pre-UI TestEngine: expose disabled/blocked state on entries #5961 silent contract restored.
  • The MCP handler translates non-empty status into a thrown std::runtime_error with the same "pressButton <path>: disabled: <reason>" wire-format as before, so LLM-facing behavior is unchanged.

readValue, listEntries, listAllEntries, and the TypedEntry::status field are untouched.

Test plan

  • Debug|x64 build clean locally.
  • MCP: ui.pressButton on a disabled entry (QuickAccess/Undo) → thrown error "pressButton QuickAccess/Undo: disabled: Nothing to undo." (wire format byte-identical to pre-fix).
  • MCP: ui.pressButton on an enabled entry → empty-JSON success.
  • MCP: ui.writeValueReal on an enabled float field → empty-JSON success.
  • MCP: ui.pressButton on a non-existent path → hard-error propagates through unexpected(...) as before.
  • Python path + the previously-failing test_all_scens scenario — left for CI.

🤖 Generated with Claude Code

Refactor `Control::pressButton` / `writeValue<T>` from `Expected<void>` to `Expected<std::string>` — empty on success, non-empty `"disabled[: <reason>]"` on silent no-op. Hard errors (path/type/range) still go through `unexpected`. Python binding logs the status and does not throw (restores pre-#5961 silent contract that `test_all_scens` relies on); MCP handler translates non-empty status into a thrown `runtime_error` so its error wire-format stays identical.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Grantim Grantim merged commit be20285 into master Apr 24, 2026
35 checks passed
@Grantim Grantim deleted the fix-ui-disabled-status-return branch April 24, 2026 14:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants