mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 11:07:10 +00:00
refactor(verifier): drop the superseded AllCandidates enumeration
This commit is contained in:
1 parent
17a0cd5fe5
commit
98483926a6
2 files changed
+1
-144
No files matched your search
@@ -128,51 +128,6 @@ type ActionCandidate struct {
|
|||||||
// prob is the internal accumulated selection probability, summed across
|
// prob is the internal accumulated selection probability, summed across
|
||||||
// dedup, then rounded into Weight. Not exposed in the prompt directly.
|
// dedup, then rounded into Weight. Not exposed in the prompt directly.
|
||||||
prob float64
|
prob float64
|
||||||
|
|
||||||
// The following are retained by the legacy AllCandidates enumeration.
|
|
||||||
Verb string
|
|
||||||
X, Y int
|
|
||||||
Width, Height int
|
|
||||||
Selector string
|
|
||||||
}
|
|
||||||
|
|
||||||
// llmVerbs lists the verbs AllCandidates enumerates, in the order they are
|
|
||||||
// emitted per element. Mirrors verbAccepts; no new filtering logic.
|
|
||||||
var llmVerbs = []string{"taps", "doubleTaps", "longPresses", "typing", "scrolls", "swipes"}
|
|
||||||
|
|
||||||
// AllCandidates flattens the per-verb candidate enumeration into one indexed
|
|
||||||
// list the LLM backend chooses from. It reuses scopedElements/verbAccepts/
|
|
||||||
// selectorForElement exactly as the seeded picker does, walking the tree once
|
|
||||||
// and emitting an entry for every (in-scope element, applicable verb) pair.
|
|
||||||
func (v *Verifier) AllCandidates() []ActionCandidate {
|
|
||||||
if v.lastTree == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
scope := v.scopedElements()
|
|
||||||
var result []ActionCandidate
|
|
||||||
for _, element := range v.lastTree.Elements {
|
|
||||||
if !scope[element] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, verb := range llmVerbs {
|
|
||||||
if !verbAccepts(verb, element) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
x, y := element.Bounds.Center()
|
|
||||||
result = append(result, ActionCandidate{
|
|
||||||
Index: len(result),
|
|
||||||
Verb: verb,
|
|
||||||
Kind: verbActionKind(verb),
|
|
||||||
Label: candidateLabel(element),
|
|
||||||
X: x,
|
|
||||||
Y: y,
|
|
||||||
Width: element.Bounds.Width(),
|
|
||||||
Height: element.Bounds.Height(),
|
|
||||||
Selector: selectorForElement(v.lastTree, element),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// verbActionKind maps a picker verb to the action kind it dispatches.
|
// verbActionKind maps a picker verb to the action kind it dispatches.
|
||||||
@@ -195,21 +150,6 @@ func verbActionKind(verb string) ActionKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// candidateLabel builds a short target description, preferring the most
|
|
||||||
// human-meaningful field available.
|
|
||||||
func candidateLabel(element *hierarchy.Element) string {
|
|
||||||
switch {
|
|
||||||
case element.Text != "":
|
|
||||||
return element.Text
|
|
||||||
case element.Description != "":
|
|
||||||
return element.Description
|
|
||||||
case element.ResourceID != "":
|
|
||||||
return element.ResourceID
|
|
||||||
default:
|
|
||||||
return element.Class
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// maxLabelRunes caps a visible-text label so joined descendant text stays short
|
// maxLabelRunes caps a visible-text label so joined descendant text stays short
|
||||||
// enough to render on one numbered line.
|
// enough to render on one numbered line.
|
||||||
const maxLabelRunes = 40
|
const maxLabelRunes = 40
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package verifier
|
package verifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -248,86 +247,6 @@ func count(candidates []ActionCandidate, description string) int {
|
|||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
// candidateTreeJSON is a small screen with one clickable button, one editable
|
|
||||||
// field, and one scrollable list. Every node has positive bounds, so each is
|
|
||||||
// additionally a swipe origin.
|
|
||||||
const candidateTreeJSON = `{
|
|
||||||
"attributes": {"bounds": "[0,0,1080,2400]"},
|
|
||||||
"children": [
|
|
||||||
{"attributes": {"resource-id": "LoginSubmit", "text": "Sign in", "bounds": "[0,100,1080,200]"}, "clickable": true, "enabled": true, "children": []},
|
|
||||||
{"attributes": {"resource-id": "EmailField", "class": "EditText", "bounds": "[0,300,1080,400]"}, "enabled": true, "children": []},
|
|
||||||
{"attributes": {"resource-id": "List", "scrollable": "true", "bounds": "[0,500,1080,2000]"}, "children": []}
|
|
||||||
]
|
|
||||||
}`
|
|
||||||
|
|
||||||
func TestAllCandidatesUnionsVerbsWithIndicesAndLabels(t *testing.T) {
|
|
||||||
tree, err := hierarchy.Parse(candidateTreeJSON)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
v := &Verifier{lastTree: tree}
|
|
||||||
candidates := v.AllCandidates()
|
|
||||||
|
|
||||||
// Indices are dense and ordered.
|
|
||||||
for i, candidate := range candidates {
|
|
||||||
if candidate.Index != i {
|
|
||||||
t.Errorf("candidate %d has Index %d", i, candidate.Index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect verbs per label to assert the union without pinning swipe count.
|
|
||||||
byLabel := map[string][]string{}
|
|
||||||
for _, candidate := range candidates {
|
|
||||||
byLabel[candidate.Label] = append(byLabel[candidate.Label], candidate.Verb)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The clickable button is tap/doubleTap/longPress + swipe; its label is the
|
|
||||||
// visible text, not the resource-id.
|
|
||||||
submit := byLabel["Sign in"]
|
|
||||||
if !contains(submit, "taps") || !contains(submit, "doubleTaps") || !contains(submit, "longPresses") {
|
|
||||||
t.Errorf("Sign in verbs = %v, want tap family", submit)
|
|
||||||
}
|
|
||||||
if !contains(submit, "swipes") {
|
|
||||||
t.Errorf("Sign in verbs = %v, want swipes (positive bounds)", submit)
|
|
||||||
}
|
|
||||||
if contains(submit, "typing") {
|
|
||||||
t.Errorf("Sign in should not be typeable, got %v", submit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The EditText is typeable (and a swipe origin); its label falls back to the
|
|
||||||
// resource-id since it has no text.
|
|
||||||
email := byLabel["EmailField"]
|
|
||||||
if !contains(email, "typing") {
|
|
||||||
t.Errorf("EmailField verbs = %v, want typing", email)
|
|
||||||
}
|
|
||||||
if contains(email, "taps") {
|
|
||||||
t.Errorf("EmailField is not clickable, got %v", email)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The scrollable list yields a scroll candidate.
|
|
||||||
list := byLabel["List"]
|
|
||||||
if !contains(list, "scrolls") {
|
|
||||||
t.Errorf("List verbs = %v, want scrolls", list)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kinds map verbs to action kinds.
|
|
||||||
for _, candidate := range candidates {
|
|
||||||
if candidate.Verb == "typing" && candidate.Kind != ActionKindInputText {
|
|
||||||
t.Errorf("typing candidate kind = %q, want InputText", candidate.Kind)
|
|
||||||
}
|
|
||||||
if candidate.Verb == "taps" && candidate.Kind != ActionKindTap {
|
|
||||||
t.Errorf("taps candidate kind = %q, want Tap", candidate.Kind)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAllCandidatesNilTree(t *testing.T) {
|
|
||||||
v := &Verifier{}
|
|
||||||
if got := v.AllCandidates(); got != nil {
|
|
||||||
t.Errorf("AllCandidates with no tree = %v, want nil", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLLMConfigDetectsMarker(t *testing.T) {
|
func TestLLMConfigDetectsMarker(t *testing.T) {
|
||||||
v := newLoadedVerifier(t, `globalThis.generator = { kind: "llm", config: { model: "vendor/model" } };`)
|
v := newLoadedVerifier(t, `globalThis.generator = { kind: "llm", config: { model: "vendor/model" } };`)
|
||||||
config, ok := v.LLMConfig()
|
config, ok := v.LLMConfig()
|
||||||
@@ -390,6 +309,4 @@ func newLoadedVerifier(t *testing.T, source string) *Verifier {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
func contains(items []string, want string) bool {
|
|
||||||
return slices.Contains(items, want)
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user