//go:build browser package chrome import ( "context" "encoding/json" "errors" "testing" "time" ) // TestActionMethods_HonorCallerCancellation confirms the DeviceDriver action // methods route through runCtx so a cancelled caller context aborts the CDP // round-trip instead of blocking on d.tabCtx. Without this a hung browser would // ignore step deadlines and Ctrl-C. func TestActionMethods_HonorCallerCancellation(t *testing.T) { d := New() defer d.Terminate(context.Background()) launchCtx, launchCancel := context.WithTimeout(context.Background(), 30*time.Second) defer launchCancel() if err := d.Launch(launchCtx, "data:text/html,", false, nil); err != nil { t.Fatalf("Launch: %v", err) } cancelled, cancel := context.WithCancel(context.Background()) cancel() actions := map[string]func() error{ "Tap": func() error { return d.Tap(cancelled, 1, 1) }, "Swipe": func() error { return d.Swipe(cancelled, 1, 1, 2, 2, 50*time.Millisecond) }, "LongPress": func() error { return d.LongPress(cancelled, 1, 1) }, "PressKey": func() error { return d.PressKey(cancelled, "enter") }, "InputText": func() error { return d.InputText(cancelled, "x") }, "EraseText": func() error { return d.EraseText(cancelled, 1) }, "TapSelector": func() error { return d.TapSelector(cancelled, "#go") }, } for name, action := range actions { t.Run(name, func(t *testing.T) { done := make(chan error, 1) go func() { done <- action() }() select { case err := <-done: if !errors.Is(err, context.Canceled) { t.Fatalf("want context.Canceled, got %v", err) } case <-time.After(5 * time.Second): t.Fatal("action ignored cancelled caller ctx and blocked") } }) } } // TestHierarchy_EditableFlag confirms the injected hierarchy script marks text // inputs, textareas, and contenteditable elements editable while leaving // buttons and non-text inputs alone. func TestHierarchy_EditableFlag(t *testing.T) { const html = `` + `` + `` + `` + `
x
` + `` + `` d := New() defer d.Terminate(context.Background()) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := d.Launch(ctx, "data:text/html,"+html, false, nil); err != nil { t.Fatalf("Launch: %v", err) } dump, err := d.Hierarchy(ctx) if err != nil { t.Fatalf("Hierarchy: %v", err) } type node struct { Attributes map[string]string `json:"attributes"` Children []node `json:"children"` Editable *bool `json:"editable"` } var root node if err := json.Unmarshal([]byte(dump), &root); err != nil { t.Fatalf("unmarshal hierarchy: %v", err) } editableByID := map[string]*bool{} var walk func(n node) walk = func(n node) { if id := n.Attributes["resource-id"]; id != "" { editableByID[id] = n.Editable } for _, c := range n.Children { walk(c) } } walk(root) isEditable := func(id string) bool { return editableByID[id] != nil && *editableByID[id] } for _, id := range []string{"name", "bio", "rich"} { if !isEditable(id) { t.Errorf("%q: editable = %v, want true", id, editableByID[id]) } } for _, id := range []string{"go", "chk"} { if isEditable(id) { t.Errorf("%q: editable = true, want false/absent", id) } } } // TestRunCtx_CallerCancelPropagates confirms that cancelling the caller's // context cancels the chromedp-bound context returned by runCtx. This is the // channel by which step deadlines and Ctrl-C reach in-flight CDP calls. func TestRunCtx_CallerCancelPropagates(t *testing.T) { tabCtx, tabCancel := context.WithCancel(context.Background()) defer tabCancel() d := &Driver{tabCtx: tabCtx} callerCtx, callerCancel := context.WithCancel(context.Background()) derived, cancel := d.runCtx(callerCtx) defer cancel() callerCancel() select { case <-derived.Done(): case <-time.After(time.Second): t.Fatal("derived ctx did not cancel after caller cancellation") } } // TestRunCtx_TabCancelPropagates confirms the inverse: tearing down the tab // also cancels any in-flight derived context. func TestRunCtx_TabCancelPropagates(t *testing.T) { tabCtx, tabCancel := context.WithCancel(context.Background()) d := &Driver{tabCtx: tabCtx} derived, cancel := d.runCtx(context.Background()) defer cancel() tabCancel() select { case <-derived.Done(): case <-time.After(time.Second): t.Fatal("derived ctx did not cancel after tab cancellation") } }