From e449a0b618a4aadc5604a0e006442f7a3dc6e4d4 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Wed, 3 Jun 2026 01:42:38 +0800 Subject: [PATCH] feat(automation): resolve_unique + wait-state evaluation --- src/slic3r/GUI/Automation/Locator.cpp | 6 ++++++ src/slic3r/GUI/Automation/Locator.hpp | 4 ++++ tests/automation/test_locator.cpp | 31 +++++++++++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/src/slic3r/GUI/Automation/Locator.cpp b/src/slic3r/GUI/Automation/Locator.cpp index 690facff6c..629c6c5c4e 100644 --- a/src/slic3r/GUI/Automation/Locator.cpp +++ b/src/slic3r/GUI/Automation/Locator.cpp @@ -45,6 +45,12 @@ std::vector find_matches(const UiNode& root, const Target& target return out; } +const UiNode* resolve_unique(const UiNode& root, const Target& target, int& match_count) { + const auto m = find_matches(root, target); + match_count = static_cast(m.size()); + return m.size() == 1 ? m.front() : nullptr; +} + bool evaluate_state(const UiNode* node, WaitState state, const std::optional& expected_value) { if (node == nullptr) diff --git a/src/slic3r/GUI/Automation/Locator.hpp b/src/slic3r/GUI/Automation/Locator.hpp index 16b6b2714f..27b40a5647 100644 --- a/src/slic3r/GUI/Automation/Locator.hpp +++ b/src/slic3r/GUI/Automation/Locator.hpp @@ -27,6 +27,10 @@ std::vector flatten(const UiNode& root); // All nodes matching the target spec (resolution-order aware). std::vector find_matches(const UiNode& root, const Target& target); +// Resolve to exactly one node for actions. Returns the node on a unique match; +// returns nullptr otherwise and sets match_count (0 = not found, >1 = ambiguous). +const UiNode* resolve_unique(const UiNode& root, const Target& target, int& match_count); + enum class WaitState { Exists, Visible, Enabled, Value }; // True if `node` satisfies the wait condition. A null node only satisfies a diff --git a/tests/automation/test_locator.cpp b/tests/automation/test_locator.cpp index b219828a19..f4168d35a6 100644 --- a/tests/automation/test_locator.cpp +++ b/tests/automation/test_locator.cpp @@ -90,3 +90,34 @@ TEST_CASE("find_matches not found returns empty", "[automation][locator]") { Target t; t.id = "nope"; CHECK(find_matches(tree, t).empty()); } + +TEST_CASE("resolve_unique success / not-found / ambiguous", + "[automation][locator]") { + const auto tree = make_tree(); + int count = -1; + + Target ok; ok.id = "btn_slice"; + CHECK(resolve_unique(tree, ok, count) != nullptr); + CHECK(count == 1); + + Target missing; missing.id = "nope"; + CHECK(resolve_unique(tree, missing, count) == nullptr); + CHECK(count == 0); + + Target ambiguous; ambiguous.label = "Export"; + CHECK(resolve_unique(tree, ambiguous, count) == nullptr); + CHECK(count == 2); +} + +TEST_CASE("evaluate_state covers exists/visible/enabled/value", + "[automation][locator]") { + UiNode n; n.visible = true; n.enabled = false; + n.has_value = true; n.value = "PLA"; + + CHECK(evaluate_state(&n, WaitState::Exists, std::nullopt)); + CHECK(evaluate_state(&n, WaitState::Visible, std::nullopt)); + CHECK_FALSE(evaluate_state(&n, WaitState::Enabled, std::nullopt)); // disabled + CHECK(evaluate_state(&n, WaitState::Value, std::string("PLA"))); + CHECK_FALSE(evaluate_state(&n, WaitState::Value, std::string("ABS"))); + CHECK_FALSE(evaluate_state(nullptr, WaitState::Exists, std::nullopt)); +}