diff --git a/internal/verifier/bindings.go b/internal/verifier/bindings.go index 0f678dc..57121d7 100644 --- a/internal/verifier/bindings.go +++ b/internal/verifier/bindings.go @@ -2,7 +2,6 @@ package verifier import ( "fmt" - "log/slog" "math/big" "time" @@ -91,7 +90,8 @@ func (v *Verifier) installRuntimeBindings() error { // installHost exposes globalThis.__sanderlingHost__ for the goja runtime entry. // The shared picker (pick.ts) draws against it: platform() drives the verb // matrix and press-key pool; seedHi/seedLo construct its Pcg; queryCandidates -// enumerates targets over the hierarchy tree; reportUnsupported logs once. +// enumerates targets over the hierarchy tree; reportUnsupported records the +// verb for the run report. func (v *Verifier) installHost() error { host := v.runtime.NewObject() if err := host.Set("platform", func(goja.FunctionCall) goja.Value { @@ -113,7 +113,7 @@ func (v *Verifier) installHost() error { return err } if err := host.Set("reportUnsupported", func(call goja.FunctionCall) goja.Value { - slog.Warn("verb unsupported on platform", "verb", call.Argument(0).String(), "platform", v.platform) + v.recordUnsupported(call.Argument(0).String()) return goja.Undefined() }); err != nil { return err @@ -121,6 +121,16 @@ func (v *Verifier) installHost() error { return v.runtime.GlobalObject().Set("__sanderlingHost__", host) } +// recordUnsupported notes a verb the picker requested that this platform +// cannot dispatch, deduped and in first-seen order. +func (v *Verifier) recordUnsupported(verb string) { + if verb == "" || v.unsupportedSeen[verb] { + return + } + v.unsupportedSeen[verb] = true + v.unsupported = append(v.unsupported, verb) +} + // bindQueryCandidates returns the host-enumerated targets for a verb as an // array of {x, y, selector, width, height}, in tree order. func (v *Verifier) bindQueryCandidates(call goja.FunctionCall) goja.Value { @@ -339,4 +349,3 @@ func (v *Verifier) extractSpecIndex(value goja.Value) (int, bool) { } return int(indexValue.ToInteger()), true } - diff --git a/internal/verifier/worker.go b/internal/verifier/worker.go index e5e8fcc..6127101 100644 --- a/internal/verifier/worker.go +++ b/internal/verifier/worker.go @@ -43,6 +43,18 @@ type Verifier struct { appPackage string platform string seed uint64 + + // unsupported collects verbs the picker requested but the platform cannot + // dispatch (reportUnsupported host callback), deduped and in first-seen + // order, so the runner can surface them in the run report. + unsupported []string + unsupportedSeen map[string]bool +} + +// UnsupportedVerbs returns the verbs the picker requested that this platform +// cannot dispatch, deduped and in first-seen order. +func (v *Verifier) UnsupportedVerbs() []string { + return v.unsupported } type Option func(*Verifier) @@ -71,12 +83,13 @@ func WithAppPackage(appPackage string) Option { func New(options ...Option) (*Verifier, error) { verifier := &Verifier{ - runtime: goja.New(), - properties: map[string]int{}, - evaluators: map[string]*ltl.Evaluator{}, - priorVerdicts: map[string]ltl.Verdict{}, - witnesses: map[string]Witness{}, - platform: "android", + runtime: goja.New(), + properties: map[string]int{}, + evaluators: map[string]*ltl.Evaluator{}, + priorVerdicts: map[string]ltl.Verdict{}, + witnesses: map[string]Witness{}, + platform: "android", + unsupportedSeen: map[string]bool{}, } for _, option := range options { option(verifier) @@ -602,10 +615,12 @@ func selectorForElement(tree *hierarchy.Tree, element *hierarchy.Element) string // candidatesForVerb enumerates the host-side targets a builtin verb may draw // from, in v.lastTree.Elements ORDER (the order is part of the picker's parity // contract). The filters are LIFTED from the old Go picker: -// taps/doubleTaps/longPresses: clickable + enabled + positive bounds -// typing: editable + enabled + positive bounds -// scrolls: scrollable attribute + positive bounds -// swipes: any in-scope element +// +// taps/doubleTaps/longPresses: clickable + enabled + positive bounds +// typing: editable + enabled + positive bounds +// scrolls: scrollable attribute + positive bounds +// swipes: any in-scope element +// // Every candidate carries the resolving selector so the runner can re-route by // id/text. Out-of-scope nodes (the soft keyboard, system UI) are always dropped. func (v *Verifier) candidatesForVerb(verb string) []candidate {