feat(automation): pure locator (id/path/predicate) with unit tests

This commit is contained in:
SoftFever
2026-06-03 01:38:13 +08:00
parent 02140d2a1e
commit ddd1967bff
4 changed files with 193 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
#include "Locator.hpp"
namespace Slic3r { namespace GUI { namespace Automation {
static void flatten_into(const UiNode& n, std::vector<const UiNode*>& out) {
out.push_back(&n);
for (const UiNode& c : n.children)
flatten_into(c, out);
}
std::vector<const UiNode*> flatten(const UiNode& root) {
std::vector<const UiNode*> out;
flatten_into(root, out);
return out;
}
static bool matches_predicate(const UiNode& n, const Target& t) {
if (t.backend && n.backend != *t.backend) return false;
if (t.name && !(n.id == *t.name || n.label == *t.name)) return false;
if (t.klass && n.klass != *t.klass) return false;
if (t.label && n.label != *t.label) return false;
if (t.value && !(n.has_value && n.value == *t.value)) return false;
return true;
}
std::vector<const UiNode*> find_matches(const UiNode& root, const Target& target) {
const auto all = flatten(root);
std::vector<const UiNode*> out;
// Resolution order: exact id -> exact path -> predicate.
if (target.id) {
for (const UiNode* n : all)
if (n->id == *target.id) out.push_back(n);
return out;
}
if (target.path) {
for (const UiNode* n : all)
if (n->path == *target.path) out.push_back(n);
return out;
}
if (target.empty())
return out; // nothing to match on
for (const UiNode* n : all)
if (matches_predicate(*n, target)) out.push_back(n);
return out;
}
bool evaluate_state(const UiNode* node, WaitState state,
const std::optional<std::string>& expected_value) {
if (node == nullptr)
return false;
switch (state) {
case WaitState::Exists: return true;
case WaitState::Visible: return node->visible;
case WaitState::Enabled: return node->enabled && node->visible;
case WaitState::Value:
return node->has_value && expected_value && node->value == *expected_value;
}
return false;
}
}}} // namespace

View File

@@ -0,0 +1,37 @@
#pragma once
#include "IUiBackend.hpp"
#include <optional>
#include <string>
#include <vector>
namespace Slic3r { namespace GUI { namespace Automation {
// A target specification. Resolution order: id -> path -> predicate
// (name OR class OR label OR value, all provided fields must match).
struct Target {
std::optional<std::string> id;
std::optional<std::string> path;
std::optional<std::string> name; // matches id OR label
std::optional<std::string> klass;
std::optional<std::string> label;
std::optional<std::string> value;
std::optional<BackendKind> backend;
bool empty() const {
return !id && !path && !name && !klass && !label && !value;
}
};
// Depth-first flatten of a tree into stable-ordered pointers (parents before children).
std::vector<const UiNode*> flatten(const UiNode& root);
// All nodes matching the target spec (resolution-order aware).
std::vector<const UiNode*> find_matches(const UiNode& root, const Target& target);
enum class WaitState { Exists, Visible, Enabled, Value };
// True if `node` satisfies the wait condition. A null node only satisfies a
// negative... here we keep it simple: null => false for all states.
bool evaluate_state(const UiNode* node, WaitState state,
const std::optional<std::string>& expected_value);
}}} // namespace