mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
fix(runner): erase existing field text before InputText
InputText appended on native platforms, so repeated draws grew fields without bound. The folio fuzz run wedged on the add-account screen: each draw concatenated another name until the 40-character validation error became permanent. Replace semantics also makes retried typing idempotent. The web driver already replaced via select-all; native now matches.
This commit is contained in:
1 parent
41511a2a32
commit
89e9601e43
2 files changed
+69
No files matched your search
@@ -497,6 +497,15 @@ func applyAction(ctx context.Context, drv driver.DeviceDriver, action verifier.A
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// InputText replaces the field's content: erase what the target
|
||||||
|
// holds before typing. Appending instead lets repeated draws grow
|
||||||
|
// the field without bound (e.g. into a max-length validation error
|
||||||
|
// the fuzzer can never escape) and makes retried typing land twice.
|
||||||
|
if count := existingTextLength(action, tree); count > 0 {
|
||||||
|
if err := drv.EraseText(ctx, count); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
return drv.InputText(ctx, action.Text)
|
return drv.InputText(ctx, action.Text)
|
||||||
case verifier.ActionKindSwipe:
|
case verifier.ActionKindSwipe:
|
||||||
duration := time.Duration(action.DurationMillis) * time.Millisecond
|
duration := time.Duration(action.DurationMillis) * time.Millisecond
|
||||||
@@ -547,6 +556,20 @@ func collectLogs(ctx context.Context, drv driver.DeviceDriver, since time.Time)
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// existingTextLength returns the character count of the InputText target's
|
||||||
|
// current text, so the runner can erase it before typing. Zero when the
|
||||||
|
// target cannot be resolved or holds no text.
|
||||||
|
func existingTextLength(action verifier.Action, tree *hierarchy.Tree) int {
|
||||||
|
if action.On == "" || tree == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
element := tree.Find(action.On)
|
||||||
|
if element == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return len([]rune(element.Text))
|
||||||
|
}
|
||||||
|
|
||||||
func resolveCoordinates(action verifier.Action, tree *hierarchy.Tree) (int, int, bool) {
|
func resolveCoordinates(action verifier.Action, tree *hierarchy.Tree) (int, int, bool) {
|
||||||
// When On is empty, X/Y are authoritative (web V8 path emits coordinates
|
// When On is empty, X/Y are authoritative (web V8 path emits coordinates
|
||||||
// directly from getBoundingClientRect; the runtime nullifies unresolved
|
// directly from getBoundingClientRect; the runtime nullifies unresolved
|
||||||
|
|||||||
@@ -370,6 +370,52 @@ func TestRunner_LogsWaitForIdleDriverErrors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestApplyAction_InputTextErasesExistingTextBeforeTyping(t *testing.T) {
|
||||||
|
tree, err := hierarchy.Parse(`{"attributes":{"resource-id":"root","bounds":"[0,0,1080,2340]"},"children":[
|
||||||
|
{"attributes":{"resource-id":"username","text":"stale-value","bounds":"[10,10,500,100]"},"children":[]}
|
||||||
|
]}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse: %v", err)
|
||||||
|
}
|
||||||
|
driverMock := mockdriver.New()
|
||||||
|
action := verifier.Action{Kind: verifier.ActionKindInputText, On: "id:username", Text: "alice"}
|
||||||
|
|
||||||
|
if err := applyAction(context.Background(), driverMock, action, tree); err != nil {
|
||||||
|
t.Fatalf("applyAction: %v", err)
|
||||||
|
}
|
||||||
|
actions := driverMock.Actions()
|
||||||
|
if len(actions) != 3 {
|
||||||
|
t.Fatalf("want tap, erase, input; got %v", actions)
|
||||||
|
}
|
||||||
|
if actions[0].Kind != mockdriver.ActionTap {
|
||||||
|
t.Errorf("first action = %v, want tap", actions[0].Kind)
|
||||||
|
}
|
||||||
|
if actions[1].Kind != mockdriver.ActionEraseText || actions[1].CharacterCount != len("stale-value") {
|
||||||
|
t.Errorf("second action = %+v, want erase_text of %d characters", actions[1], len("stale-value"))
|
||||||
|
}
|
||||||
|
if actions[2].Kind != mockdriver.ActionInputText || actions[2].Text != "alice" {
|
||||||
|
t.Errorf("third action = %+v, want input_text alice", actions[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplyAction_InputTextSkipsEraseWhenTargetEmpty(t *testing.T) {
|
||||||
|
tree, err := hierarchy.Parse(`{"attributes":{"resource-id":"root","bounds":"[0,0,1080,2340]"},"children":[
|
||||||
|
{"attributes":{"resource-id":"username","bounds":"[10,10,500,100]"},"children":[]}
|
||||||
|
]}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse: %v", err)
|
||||||
|
}
|
||||||
|
driverMock := mockdriver.New()
|
||||||
|
action := verifier.Action{Kind: verifier.ActionKindInputText, On: "id:username", Text: "alice"}
|
||||||
|
|
||||||
|
if err := applyAction(context.Background(), driverMock, action, tree); err != nil {
|
||||||
|
t.Fatalf("applyAction: %v", err)
|
||||||
|
}
|
||||||
|
if containsAction(driverMock.Actions(), mockdriver.ActionEraseText, "") {
|
||||||
|
t.Errorf("empty field must not be erased: %v", driverMock.Actions())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestApplyAction_InputTextSurfacesFocusTapError(t *testing.T) {
|
func TestApplyAction_InputTextSurfacesFocusTapError(t *testing.T) {
|
||||||
t.Run("selector focus tap fails", func(t *testing.T) {
|
t.Run("selector focus tap fails", func(t *testing.T) {
|
||||||
driverMock := mockdriver.New()
|
driverMock := mockdriver.New()
|
||||||
|
|||||||
Reference in new issue
Block a user