2025, Dec 09 03:00

How to Load a Chromium Extension in Automated Sessions with NoDriver on macOS ARM64 (Python)

Fix missing Chromium extensions in automation: load via --load-extension, skip --incognito, or reuse a profile with --user-data-dir on macOS ARM64 using NoDriver

Running Chromium under automation often behaves differently from a manual launch. A common surprise: an extension that stays enabled when you open the browser yourself is missing when you start Chromium from a Python script. If your session also opens in incognito, the gap becomes even more visible. Below is a concise walkthrough of why this happens and how to start Chromium with a specific extension loaded when using NoDriver on macOS 64 ARM.

Reproducing the issue

The following asynchronous Python snippet starts Chromium with a set of flags that unintentionally prevent extensions from loading and forces an incognito session. The result is a tab without your extension.

async def process_account(login_blob):
    browser_session = None
    provided_secret = None

    secret_phrase = None
    if ".com:" in login_blob:
        login_blob, secret_phrase = login_blob.split(":", 1)
        provided_secret = secret_phrase

    try:
        selected_proxy = None
        if ENABLE_PROXIES and proxy_pool:
            selected_proxy = random.choice(proxy_pool)
        else:
            print(f"{RED}Not using proxy {CLR}")

        win_w = random.randint(1000, 1500)
        win_h = random.randint(750, 950)
        pos_x = random.randint(0, 500)
        pos_y = random.randint(0, 500)

        opts = [
            f"--window-size={win_w},{win_h}",
            f"--window-position={pos_x},{pos_y}",
            "--disable-sync",
            "--no-first-run",
            "--no-default-browser-check",
            "--disable-backgrounding-occluded-windows",
            "--disable-renderer-backgrounding",
            "--disable-background-timer-throttling",
            "--disable-breakpad",
            "--disable-extensions",
            "--incognito",
            "--disable-dev-shm-usage",
        ]

        if selected_proxy:
            host, port, usern, passw = split_proxy_dsn(selected_proxy)
            proxy_auth = [usern, passw]
            proxy_url = f"http://{host}:{port}"
            opts.append(f"--proxy-server={proxy_url}")

        browser_session = await nd.start(
            browser_executable_path=CHROME_BIN,
            headless=HEADLESS_ON,
            stealth=True,
            browser_args=opts
        )

        bootstrap_tab = await browser_session.get("draft:,")
        await apply_proxy_auth(proxy_auth[0], proxy_auth[1], bootstrap_tab)

        page = await browser_session.get("https://www.target.com/")

        await page.evaluate(
            """
            () => {
                localStorage.clear();
                sessionStorage.clear();
            }
            """
        )

What’s actually going on

There are three interacting pieces. First, incognito mode typically disables extensions by default. Second, headless or automated Chrome sessions typically do not load extensions in incognito sessions unless you explicitly allow it. Third, the flag --disable-extensions shuts off every extension unconditionally. Together, these flags ensure your extension never shows up when you launch Chromium programmatically.

The fix

The practical path is to stop disabling extensions, avoid forcing incognito if you want an extension active, and explicitly load the extension from disk. If you want to reuse a profile where the extension is already configured, you can also point Chromium at that profile directory.

async def process_account(login_blob):
    browser_session = None
    provided_secret = None

    secret_phrase = None
    if ".com:" in login_blob:
        login_blob, secret_phrase = login_blob.split(":", 1)
        provided_secret = secret_phrase

    try:
        selected_proxy = None
        if ENABLE_PROXIES and proxy_pool:
            selected_proxy = random.choice(proxy_pool)
        else:
            print(f"{RED}Not using proxy {CLR}")

        win_w = random.randint(1000, 1500)
        win_h = random.randint(750, 950)
        pos_x = random.randint(0, 500)
        pos_y = random.randint(0, 500)

        ext_path = "/Users/<user>/Library/Application Support/Chromium/Default/Extensions/pgojnojmmhpofjgdmaebadhbocahppod/1.16.0_0"
        # Optionally, reuse an existing profile with extensions configured:
        # profile_dir = "/Users/<user>/Library/Application Support/Chromium"

        opts = [
            f"--window-size={win_w},{win_h}",
            f"--window-position={pos_x},{pos_y}",
            "--disable-sync",
            "--no-first-run",
            "--no-default-browser-check",
            "--disable-backgrounding-occluded-windows",
            "--disable-renderer-backgrounding",
            "--disable-background-timer-throttling",
            "--disable-breakpad",
            "--disable-dev-shm-usage",
            f"--load-extension={ext_path}",
            # f"--user-data-dir={profile_dir}",
        ]

        if selected_proxy:
            host, port, usern, passw = split_proxy_dsn(selected_proxy)
            proxy_auth = [usern, passw]
            proxy_url = f"http://{host}:{port}"
            opts.append(f"--proxy-server={proxy_url}")

        browser_session = await nd.start(
            browser_executable_path=CHROME_BIN,
            headless=HEADLESS_ON,
            stealth=True,
            browser_args=opts
        )

        bootstrap_tab = await browser_session.get("draft:,")
        await apply_proxy_auth(proxy_auth[0], proxy_auth[1], bootstrap_tab)

        page = await browser_session.get("https://www.target.com/")

        await page.evaluate(
            """
            () => {
                localStorage.clear();
                sessionStorage.clear();
            }
            """
        )

The key changes are straightforward. Do not pass --disable-extensions. Do not force --incognito if you expect the extension to be active. Provide the on-disk extension path using --load-extension. On macOS, a path can look like /Users/<user>/Library/Application Support/Chromium/Default/Extensions/pgojnojmmhpofjgdmaebadhbocahppod/1.16.0_0. If you prefer to reuse an existing profile where the extension is already installed and toggled on, point Chromium to that profile via --user-data-dir.

Why this matters

When you launch Chromium through automation, command-line switches fully dictate the runtime. UI choices made during a manual session, including enabling an extension in incognito, can be overridden or ignored depending on which flags are present. Understanding how --incognito, --disable-extensions and --load-extension interact prevents chasing side effects and ensures your scripted runs behave like your manual runs.

Wrap-up

If an extension is missing in an automated session, start by removing --disable-extensions, avoid forcing incognito when you need an extension, and load the extension explicitly from its folder with --load-extension. If persistence is important, use --user-data-dir to reuse a known profile. With these adjustments, Chromium started by NoDriver will mirror your manual setup and keep the extension available when your script opens a tab.