Where CDP fits
Every Chromium control library ultimately speaks CDP. Puppeteer and Playwright wrap it in idiomatic APIs and add their own features (auto-waiting, selector engines). For 95% of scraping you want the wrapper, not raw CDP. The exception: when you need to attach to a real user's Chrome (a profile with cookies, history, extensions installed) instead of launching a fresh headless instance. Raw CDP via the websocket endpoint is the cleanest path.
Detection considerations
Chrome exposes the CDP port only when launched with --remote-debugging-port. Some defensive scripts probe for this and flag the session — though it is a weak signal because the port is not visible to the page itself, only to the host. The stronger CDP-related signal is the presence of the Runtime.enable domain in the page context, which Puppeteer/Playwright enable by default. Stealth tools toggle these off when not needed.
When to use CDP directly
Three real cases: (1) attaching to an existing Chrome process with a real profile, (2) implementing custom request interception that Playwright's API does not expose, (3) building a stealth tool that needs to control which CDP domains are enabled. For everything else, Playwright or Puppeteer is a better default.
