mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
fix(driver): skip pre-erase for replace-on-input drivers
The web driver's InputText already replaces content via select-all, so the runner's unconditional EraseText was a redundant round-trip on every InputText. A new optional TextReplacer capability lets a driver assert replace semantics; the runner skips the erase when asserted.
This commit is contained in:
1 parent
6009adac48
commit
29ec13adcd
5 files changed
+61
No files matched your search
@@ -193,6 +193,12 @@ func (d *Driver) InputText(_ context.Context, text string) error {
|
||||
)
|
||||
}
|
||||
|
||||
// ReplacesTextOnInput reports that InputText replaces existing content via
|
||||
// select-all, so the runner skips its pre-erase.
|
||||
func (d *Driver) ReplacesTextOnInput() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// EraseText clears the focused field. InputText above already replaces via
|
||||
// select-all, so the character count is not needed to bound the deletion.
|
||||
func (d *Driver) EraseText(_ context.Context, _ int) error {
|
||||
|
||||
@@ -60,6 +60,16 @@ type ForegroundChecker interface {
|
||||
ForegroundApp(ctx context.Context) (string, error)
|
||||
}
|
||||
|
||||
// TextReplacer is the optional capability for drivers whose InputText already
|
||||
// replaces the field's content instead of appending to it. The runner must
|
||||
// skip its pre-erase for such drivers: the erase would be a redundant
|
||||
// round-trip on every InputText.
|
||||
type TextReplacer interface {
|
||||
// ReplacesTextOnInput reports whether InputText replaces existing
|
||||
// content, making the runner's pre-erase unnecessary.
|
||||
ReplacesTextOnInput() bool
|
||||
}
|
||||
|
||||
// FocusedWindowChecker is the optional capability for reporting which app owns
|
||||
// the focused (on-screen) window. The startup gate prefers it over
|
||||
// ForegroundChecker: the resumed-activity signal flips to a freshly launched
|
||||
|
||||
@@ -63,6 +63,10 @@ type Driver struct {
|
||||
MetricsData driver.Metrics
|
||||
Failures map[ActionKind]error
|
||||
|
||||
// ReplacesText makes the mock assert the TextReplacer capability, so
|
||||
// tests cover both the erase-before-type and replace-on-input paths.
|
||||
ReplacesText bool
|
||||
|
||||
// ForegroundResults is consumed one entry per ForegroundApp call (the
|
||||
// last entry repeats). Empty yields "", which disables the runner's
|
||||
// app-scope guard so tests that don't care are unaffected.
|
||||
@@ -212,6 +216,12 @@ func (d *Driver) InputText(ctx context.Context, text string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) ReplacesTextOnInput() bool {
|
||||
d.mutex.Lock()
|
||||
defer d.mutex.Unlock()
|
||||
return d.ReplacesText
|
||||
}
|
||||
|
||||
func (d *Driver) EraseText(ctx context.Context, characterCount int) error {
|
||||
if err := d.failure(ActionEraseText); err != nil {
|
||||
return err
|
||||
|
||||
@@ -512,11 +512,14 @@ func applyAction(ctx context.Context, drv driver.DeviceDriver, action verifier.A
|
||||
// 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.
|
||||
// Drivers whose InputText already replaces skip the erase entirely.
|
||||
if !inputReplacesText(drv) {
|
||||
if count := existingTextLength(action, tree); count > 0 {
|
||||
if err := drv.EraseText(ctx, count); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return drv.InputText(ctx, action.Text)
|
||||
case verifier.ActionKindSwipe:
|
||||
duration := time.Duration(action.DurationMillis) * time.Millisecond
|
||||
@@ -567,6 +570,13 @@ func collectLogs(ctx context.Context, drv driver.DeviceDriver, since time.Time)
|
||||
return result
|
||||
}
|
||||
|
||||
// inputReplacesText reports whether the driver's InputText replaces existing
|
||||
// content, making the runner's pre-erase redundant.
|
||||
func inputReplacesText(drv driver.DeviceDriver) bool {
|
||||
replacer, ok := drv.(driver.TextReplacer)
|
||||
return ok && replacer.ReplacesTextOnInput()
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
@@ -628,6 +628,31 @@ func TestApplyAction_InputTextWithoutTargetSkipsSettle(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestApplyAction_InputTextSkipsEraseForReplacingDriver pins that a driver
|
||||
// asserting the TextReplacer capability never pays the pre-erase round-trip:
|
||||
// its InputText already replaces the field's content.
|
||||
func TestApplyAction_InputTextSkipsEraseForReplacingDriver(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()
|
||||
driverMock.ReplacesText = true
|
||||
action := verifier.Action{Kind: verifier.ActionKindInputText, On: "id:username", Text: "alice"}
|
||||
|
||||
if err := applyAction(context.Background(), driverMock, action, tree, time.Millisecond); err != nil {
|
||||
t.Fatalf("applyAction: %v", err)
|
||||
}
|
||||
if containsAction(driverMock.Actions(), mockdriver.ActionEraseText, "") {
|
||||
t.Errorf("replacing driver must not be asked to erase: %v", driverMock.Actions())
|
||||
}
|
||||
if !containsAction(driverMock.Actions(), mockdriver.ActionInputText, "") {
|
||||
t.Errorf("expected InputText, got %v", driverMock.Actions())
|
||||
}
|
||||
}
|
||||
|
||||
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":[]}
|
||||
|
||||
Reference in new issue
Block a user