Migrating from undetected-chromedriver
Clearcote is driven over CDP, not WebDriver, so the move is from Selenium to Playwright. With no ChromeDriver there is no driver to patch or keep in step with the browser. The same steps work for selenium-stealth.
The change
import undetected_chromedriver as uc
from selenium.webdriver.common.by import By
from clearcote import launch
options = uc.ChromeOptions()
options.add_argument("--proxy-server=http://proxy.example:8080")
driver = uc.Chrome(options=options, headless=True)
browser = launch(
fingerprint="acct-1", # one seed per account: the same device every run
proxy={"server": "http://proxy.example:8080"},
geoip=True, # timezone, language and WebRTC follow the exit IP
humanize=True, # human mouse paths, typing and scrolling
)
page = browser.new_page()
driver.get("https://example.com")
driver.find_element(By.CSS_SELECTOR, "a").click()
print(driver.title)
driver.quit()
page.goto("https://example.com")
page.locator("a").first.click()
print(page.title())
browser.close()launch() downloads a verified Clearcote build on first use, starts it and returns a Playwright Browser.
Selenium to Playwright, call by call
| Selenium | Playwright |
|---|---|
driver.get(url) | page.goto(url) |
driver.find_element(By.CSS_SELECTOR, s) | page.locator(s) |
driver.find_element(By.XPATH, x) | page.locator("xpath=" + x) |
el.click(), el.send_keys("x") | loc.click(), loc.fill("x") or loc.press_sequentially("x") |
el.text, el.get_attribute("href") | loc.inner_text(), loc.get_attribute("href") |
WebDriverWait(driver, 10).until(...) | Usually nothing: actions wait for the element. Otherwise loc.wait_for() |
driver.execute_script(js) | page.evaluate(js) |
driver.get_cookies(), add_cookie | page.context.cookies(), add_cookies |
driver.save_screenshot(path) | page.screenshot(path=path) |
driver.switch_to.frame(f) | page.frame_locator(s) |
driver.switch_to.window(h) | Each tab is its own page; context.pages lists them |
Options
| Before | With Clearcote |
|---|---|
stealth(driver, vendor=..., webgl_vendor=..., renderer=..., platform=...) | Remove it. The persona supplies these values, and they agree with the GPU, the fonts and the user agent |
| a proxy extension for a login | proxy={"server", "username", "password"} on launch(); needs the latest build (below) |
--user-agent, --lang, --window-size | Remove them, or use accept_language and timezone; geoip=True sets both from the proxy |
user_data_dir= | launch_persistent_context(user_data_dir, ...) |
version_main=, driver patching | Nothing to patch: there is no ChromeDriver |
A large Selenium suite?
Selenium and WebDriver are not supported. ChromeDriver can attach to a browser started with serve() through debugger_address, but the driver then brings back the WebDriver surface this move is meant to remove (see How detection works). Use it only while you port, and move the important flows first.
from urllib.parse import urlparse
from clearcote import serve
from selenium import webdriver
with serve(fingerprint="acct-1") as srv: # unsupported: for the transition only
options = webdriver.ChromeOptions()
options.debugger_address = urlparse(srv.cdp_url).netloc
driver = webdriver.Chrome(options=options)
driver.get("https://example.com")
driver.quit()Check the switch
clearcote info prints the build the SDK resolves and a launch test. Without a licence key that is the open build (BSD-3, no account); a proxy with a username and password needs the latest build, free with a GitHub sign-in (clearcote login). Then open the audit in the new setup and compare. Comparisons: undetected-chromedriver and selenium-stealth.