diff --git a/internal/driver/chrome/driver_test.go b/internal/driver/chrome/driver_test.go
index a554bf0..1997544 100644
--- a/internal/driver/chrome/driver_test.go
+++ b/internal/driver/chrome/driver_test.go
@@ -2,10 +2,71 @@ package chrome
import (
"context"
+ "encoding/json"
"testing"
"time"
)
+// 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.