From 22aad53f25d67f1a1e80b30f2610e37fe1989c9b Mon Sep 17 00:00:00 2001 From: Stephen Heron Date: Thu, 12 Feb 2026 15:18:42 +0000 Subject: [PATCH] Add ROD_CHROME_ARGS support for passing extra flags to Chrome Add a new ROD_CHROME_ARGS environment variable that allows users to pass additional command-line flags to the Chrome/Chromium process launched by rodney. Flags can be specified with or without leading dashes and support key=value syntax (e.g. "disable-features=HttpsUpgrades --no-sandbox"). This is useful for working around browser-level behavior such as automatic HTTPS upgrades that can break navigation to local HTTP dev servers. Changes: - Add parseChromeArgs() and applyChromeArgs() to parse space-separated Chrome flags and apply them to the rod launcher - Read ROD_CHROME_ARGS in cmdStart and pass parsed flags to the launcher - Add unit tests for flag parsing (valid flags, empty input, invalid input) - Document ROD_CHROME_ARGS in README.md and help.txt Co-Authored-By: Claude Opus 4.6 --- README.md | 1 + help.txt | 5 ++++ main.go | 70 ++++++++++++++++++++++++++++++++++++++++++++++------ main_test.go | 34 +++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2a802b3..a4eea70 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,7 @@ rodney stop | Environment Variable | Default | Description | |---|---|---| | `ROD_CHROME_BIN` | `/usr/bin/google-chrome` | Path to Chrome/Chromium binary | +| `ROD_CHROME_ARGS` | (none) | Extra Chrome flags as space-separated args (for example `disable-features=HttpsUpgrades`) | | `ROD_TIMEOUT` | `30` | Default timeout in seconds for element queries | | `HTTPS_PROXY` / `HTTP_PROXY` | (none) | Authenticated proxy auto-detected on start | diff --git a/help.txt b/help.txt index 5db3f95..7d54908 100644 --- a/help.txt +++ b/help.txt @@ -61,3 +61,8 @@ Accessibility: Options: --version Print version and exit --help, -h, help Show this help message + +Environment: + ROD_CHROME_BIN Path to Chrome/Chromium binary + ROD_CHROME_ARGS Extra Chrome flags (space-separated) + ROD_TIMEOUT Default element query timeout in seconds diff --git a/main.go b/main.go index a8f1b6c..a493df5 100644 --- a/main.go +++ b/main.go @@ -20,6 +20,7 @@ import ( "github.com/go-rod/rod" "github.com/go-rod/rod/lib/launcher" + "github.com/go-rod/rod/lib/launcher/flags" "github.com/go-rod/rod/lib/proto" ) @@ -30,12 +31,12 @@ var version = "dev" // State persisted between CLI invocations type State struct { - DebugURL string `json:"debug_url"` - ChromePID int `json:"chrome_pid"` - ActivePage int `json:"active_page"` // index into pages list - DataDir string `json:"data_dir"` - ProxyPID int `json:"proxy_pid,omitempty"` // PID of auth proxy helper - ProxyPort int `json:"proxy_port,omitempty"` // local port of auth proxy + DebugURL string `json:"debug_url"` + ChromePID int `json:"chrome_pid"` + ActivePage int `json:"active_page"` // index into pages list + DataDir string `json:"data_dir"` + ProxyPID int `json:"proxy_pid,omitempty"` // PID of auth proxy helper + ProxyPort int `json:"proxy_port,omitempty"` // local port of auth proxy } func stateDir() string { @@ -226,6 +227,55 @@ func init() { } } +type chromeArg struct { + name string + value string + hasValue bool +} + +func parseChromeArgs(raw string) ([]chromeArg, error) { + fields := strings.Fields(raw) + parsed := make([]chromeArg, 0, len(fields)) + + for _, field := range fields { + arg := strings.TrimLeft(field, "-") + if arg == "" { + return nil, fmt.Errorf("invalid flag %q", field) + } + + parts := strings.SplitN(arg, "=", 2) + if parts[0] == "" { + return nil, fmt.Errorf("missing flag name in %q", field) + } + + entry := chromeArg{name: parts[0]} + if len(parts) == 2 { + entry.value = parts[1] + entry.hasValue = true + } + parsed = append(parsed, entry) + } + + return parsed, nil +} + +func applyChromeArgs(l *launcher.Launcher, raw string) error { + args, err := parseChromeArgs(raw) + if err != nil { + return err + } + + for _, arg := range args { + if arg.hasValue { + l.Set(flags.Flag(arg.name), arg.value) + continue + } + l.Set(flags.Flag(arg.name)) + } + + return nil +} + // withPage loads state, connects, and returns the active page. // Caller should NOT close the browser (we just disconnect). func withPage() (*State, *rod.Browser, *rod.Page) { @@ -274,6 +324,12 @@ func cmdStart(args []string) { l = l.Bin(bin) } + if chromeArgs := os.Getenv("ROD_CHROME_ARGS"); chromeArgs != "" { + if err := applyChromeArgs(l, chromeArgs); err != nil { + fatal("invalid ROD_CHROME_ARGS: %v", err) + } + } + // Detect authenticated proxy and launch helper if needed var proxyPID, proxyPort int if server, user, pass, needed := detectProxy(); needed { @@ -1348,7 +1404,7 @@ func queryAXNodes(page *rod.Page, name, role string) ([]*proto.AccessibilityAXNo } result, err := proto.AccessibilityQueryAXTree{ - BackendNodeID: doc.Root.BackendNodeID, + BackendNodeID: doc.Root.BackendNodeID, AccessibleName: name, Role: role, }.Call(page) diff --git a/main_test.go b/main_test.go index 0596b2e..e8e4538 100644 --- a/main_test.go +++ b/main_test.go @@ -7,6 +7,7 @@ import ( "net/http" "net/http/httptest" "os" + "reflect" "strings" "testing" "time" @@ -157,6 +158,39 @@ func navigateTo(t *testing.T, path string) *rod.Page { return page } +func TestParseChromeArgs_ParsesFlagsAndValues(t *testing.T) { + args, err := parseChromeArgs("disable-features=HttpsUpgrades --ignore-certificate-errors") + if err != nil { + t.Fatalf("parseChromeArgs returned error: %v", err) + } + + want := []chromeArg{ + {name: "disable-features", value: "HttpsUpgrades", hasValue: true}, + {name: "ignore-certificate-errors", hasValue: false}, + } + + if !reflect.DeepEqual(args, want) { + t.Fatalf("unexpected parse result\nwant: %#v\ngot: %#v", want, args) + } +} + +func TestParseChromeArgs_HandlesEmptyInput(t *testing.T) { + args, err := parseChromeArgs("") + if err != nil { + t.Fatalf("parseChromeArgs returned error: %v", err) + } + if len(args) != 0 { + t.Fatalf("expected empty args, got: %#v", args) + } +} + +func TestParseChromeArgs_RejectsInvalidFlag(t *testing.T) { + _, err := parseChromeArgs("--") + if err == nil { + t.Fatal("expected parseChromeArgs to fail for invalid flag") + } +} + // ===================== // ax-tree tests (RED) // =====================