# `CDPEx.Chrome`
[🔗](https://github.com/patrols/cdp_ex/blob/v0.9.0/lib/cdp_ex/chrome.ex#L1)

Launches, discovers, and stops the headless Chrome OS process.

`launch/1` opens Chrome via a `Port` with `--remote-debugging-port=0`, reads
the chosen DevTools WebSocket URL from Chrome's stderr (falling back to the
`DevToolsActivePort` file), and returns a handle. `stop/1` kills the process
and removes the temp profile.

The argument and discovery helpers (`build_args/2`, `default_args/2`,
`resolve_binary/1`) are pure and unit-testable without launching anything.

## Launch options

  * `:headless` — run headless (default `true`); `false` drops `--headless`
  * `:chrome_binary` — path to the Chrome/Chromium executable
  * `:window_size` — `{width, height}` (default `{1280, 1024}`)
  * `:user_data_dir` — profile dir; a fresh temp dir is created (and removed on
    stop) when omitted. A caller-supplied dir is left in place.
  * `:extra_args` — extra flags appended to the defaults
  * `:args` — full flag list that **replaces** the defaults entirely
  * `:launch_timeout` — ms to wait for the DevTools endpoint (default
    `15_000`). A *ceiling*, not a fixed wait: readiness is polled and returns
    as soon as Chrome is reachable, so a generous value is free. Raise it for
    slow cold-start hosts (e.g. headless Chrome in a constrained container).

## Default flags

Defaults are deliberately neutral (stability + headless), not scraping-tuned.
Anti-bot flags (spoofed user-agent, `--disable-web-security`,
`--disable-blink-features=AutomationControlled`, …) are **not** included — add
them via `:extra_args` if you need them.

> #### Sandbox {: .warning}
>
> The defaults include `--no-sandbox` / `--disable-setuid-sandbox` so Chrome
> starts in the common container/CI setup (running as root), where the sandbox
> can't initialize. That is a security reduction when visiting untrusted pages —
> to keep the sandbox, run as a non-root user and override the flag list via
> `:args` (omitting the two sandbox flags).

# `handle`

```elixir
@type handle() :: %{
  port: port(),
  os_pid: non_neg_integer() | nil,
  debug_url: String.t(),
  user_data_dir: String.t(),
  owns_data_dir: boolean()
}
```

# `launch_error`

```elixir
@type launch_error() ::
  {:chrome_not_found, String.t()}
  | {:debug_url_not_found, String.t()}
  | {:devtools_file_malformed, String.t()}
  | {:chrome_exited, integer(), String.t()}
```

Error reasons from `launch/1`. Precisely specced (not `term()`) so Dialyzer flags
drift at the source.

# `build_args`

```elixir
@spec build_args(
  String.t(),
  keyword()
) :: [String.t()]
```

Builds the full Chrome argument list for a profile dir.

Returns `opts[:args]` verbatim when given (full override); otherwise the
neutral defaults plus `opts[:extra_args]`.

# `default_args`

```elixir
@spec default_args(
  String.t(),
  keyword()
) :: [String.t()]
```

The neutral default flag list (stability + headless), with `--user-data-dir`,
`--window-size`, and the conditional `--headless` applied from `opts`.

# `launch`

```elixir
@spec launch(keyword()) :: {:ok, handle()} | {:error, launch_error()}
```

Launches headless Chrome and returns `{:ok, handle}` once its DevTools
endpoint is reachable, or `{:error, reason}`.

The `Port` is owned by the calling process, which therefore receives the
`{port, {:exit_status, _}}` message if Chrome dies.

# `resolve_binary`

```elixir
@spec resolve_binary(keyword()) :: String.t()
```

Resolves the Chrome binary path from (in order) the `:chrome_binary` option,
the `CDP_EX_CHROME_BINARY` env var, the `CHROME_BINARY` env var, then an
OS-specific default.

# `stop`

```elixir
@spec stop(handle()) :: :ok
```

Stops Chrome: kills the OS process, closes the port, and removes the temp
profile dir (only when `cdp_ex` created it). Idempotent and crash-safe — it is
the cleanup run from `CDPEx.Browser`'s `terminate/2` callback.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
