В примерах автоматизации расширений часто встречаются два флага:
--disable-extensions-except=/path/to/ext --load-extension=/path/to/ext
В более старых инструкциях Puppeteer или Playwright запускают поверх установленного Chrome и передают оба флага.
В стандартной сборке Google Chrome 150, которую тестировали здесь, браузер стартовал без ошибки в командной строке, но служба расширений проигнорировала оба флага. Автоматизация подключилась нормально, расширение отсутствовало, а проявилось это позже как тайм-аут селектора в этом harness.
Chrome записал причину отказа в одну строку, но только после включения логирования stderr.
Главный вывод — сравнение разных сборок браузера. Два следующих раздела — это именно заметки о harness: один про загрузки, запускаемые расширением, а второй — про этот путь установки через командную строку на file:// fixtures. Thunderbit сам является расширением Chrome, поэтому эта проблема для нас напрямую важна; при этом Thunderbit не был объектом теста.
Строка в логах
Добавьте --enable-logging=stderr и запустите стандартный Chrome с этими флагами:
Официальная ссылка: исходный код службы расширений Chromium.
WARNING:chrome/browser/extensions/extension_service.cc:442]
--disable-extensions-except is not allowed in Google Chrome, ignoring.
Если передать только --load-extension, появится собственное предупреждение из другой строки того же файла:
WARNING:chrome/browser/extensions/extension_service.cc:420]
--load-extension is not allowed in Google Chrome, ignoring.
Chrome for Testing при тех же самых флагах не выводит ни одного из этих предупреждений.
«Not allowed in Google Chrome.» Это предупреждение указывает на правило, завязанное именно на брендированную сборку, как наиболее вероятное объяснение отказа. Оно подтверждает, что стандартная сборка Google Chrome 150 проигнорировала флаг, и указывает место в коде, откуда пришло сообщение. Но оно само по себе не доказывает, что версия не имеет значения, и не раскрывает, по какому именно признаку сборка была отфильтрована.
Все, что ниже, — это проверка и последствия.
Проверка на уровне поведения
Минимальное MV3-расширение, собранное специально для этого теста, а не скачанное, использует content script, popup, обмен сообщениями туда-обратно, извлечение DOM и экспорт через chrome.downloads на локальном fixture с тремя товарами. Harness ведет шесть пронумерованных проверок: зарегистрирован ли service worker; содержит ли маркер расширения ID; совпадает ли маркер content script; видна ли кнопка в popup; возвращает ли popup три строки; содержит ли захваченный CSV ожидаемую строку. Отдельная проверка согласованности сравнивает независимые сигналы service worker и маркера на странице.
Во всех трех сборках использовались одни и те же явные флаги расширения, каталог расширения, HTTP fixture, headed persistent-context mode и свежий профиль для каждой ветки. Ветку со стандартным Chrome запускали через channel: 'chrome'; две успешные ветки использовали явные пути к исполняемым файлам. Строки версий считывались обратно через CDP.
| Сборка | Версия, сообщенная браузером | Service worker расширения | Content script внедрен | Полный прогон из 6 проверок |
|---|---|---|---|---|
| Chrome for Testing | Chrome/149.0.7827.55 | ✅ | ✅ | PASS 6/6 |
| Стандартный Google Chrome | Chrome/150.0.7871.187 | ❌ не зарегистрирован | ❌ | FAIL на шаге 0 |
| Chrome for Testing | Chrome/151.0.7922.10 | ✅ | ✅ | PASS 6/6 |
Сырые сводки: Chrome for Testing 149, стандартный Chrome 150, Chrome for Testing 151 и предупреждения stderr.
Chrome for Testing 149 включен специально: он старше, чем стандартная сборка, с которой его сравнивают. Если бы возможность исчезла из-за повышения версии, старый билд должен был бы оказаться в рабочей части шкалы, а сбойный — быть самым новым. Вместо этого сбойная сборка находится между двумя рабочими, а значит, простая линейная деградация по версии не подходит.
Одна только эта таблица не доказывает, что ограничение зависит именно от сборки, и важно объяснить почему. Единственная ячейка с ошибкой одновременно является и единственной ячейкой для stock, и единственной ячейкой для 150-й версии — в этом дизайне сборка и версия по-прежнему полностью смешаны. Три ветки исключают монотонное удаление; они не исключают сценарий «сломалось в 150-й, восстановилось в 151-й». Чтобы доказать это только по поведению, нужна ячейка, которую эта машина не может дать: Chrome for Testing 150 или брендированная сборка другой версии.
Предупреждение усиливает интерпретацию про branded-build, потому что прямо называет Google Chrome, а поведение в трех ветках исключает только простое монотонное удаление. Чтобы доказать механизм, независимый от версии, все равно нужен либо cross-build сравнение одной и той же версии, либо ссылка на исходники/конфигурацию.
Связанные по каждой сборке артефакты показывают сводку прогона для каждой ветки; в этой статье не публикуется матрица по каждому запуску для дополнительного числа запусков, поэтому это число не используется как независимое доказательство.
Одного сигнала недостаточно, чтобы сказать «не загрузилось»
Первая версия этого теста определяла, загрузилось ли расширение, по маркеру, который content script записывает в страницу, — и именно так же она решала, отработал ли сам content script. Один и тот же признак использовался как две разные метрики. Если маркер отсутствует, нельзя понять, «расширение вообще не загрузилось» это или «оно загрузилось, но content script не внедрился», а у этих случаев совершенно разные способы исправления.
MV3-расширения запускают фоновый service worker, а Playwright умеет обращаться к service worker напрямую. Это независимый сигнал: он вообще не касается страницы, поэтому его нельзя перепутать с инъекцией. Текущий тест читает оба сигнала и проверяет, что они совпадают.
Во всех трех прогонах они совпали. В стандартном Chrome service worker вообще ни разу не был зарегистрирован — это и есть сильная формулировка проблемы. В обеих сборках Chrome for Testing worker поднимался по адресу chrome-extension://<id>/background.js еще до открытия страницы.
Используйте Chrome for Testing — возможно, он у вас уже есть
Две успешные ветки использовали явные исполняемые файлы Chrome for Testing с версиями 149.0.7827.55 и 151.0.7922.10. В executablePath у Playwright нужно указывать закрепленный исполняемый файл, а не находить стандартный Chrome через channel: 'chrome'. npx playwright install chromium ставит Chromium, управляемый самим Playwright; он тоже может быть полезен для автоматизации, но это не тот же дистрибутив, что две ветки Chrome for Testing, и в этом сравнении не был четвертой веткой.
Официальная ссылка: анонс Chrome for Testing.
Связанный обзор: аудит разрешений расширения Chrome Scraper.
У такого подхода есть и долгосрочный плюс. Стандартный Chrome обновляется автоматически, и поэтому тест, который сегодня проходит, во вторник может сломаться по причине, которой нет в коммите. Chrome for Testing закреплен. Для всего, чей результат должен оставаться осмысленным и через три месяца, это важнее, чем удобство.
Заметка о harness: как ловили экспорт расширения
Для scraping-расширения экспорт — это и есть смысл всей работы: именно там теряются поля, ломаются кодировки и неправильно расплющиваются вложенные данные. Есть две вещи, которые не описаны в документации, и обе мешают.
| Что зафиксировал прогон экспорта | Значение |
|---|---|
playwright_download_event_fired | false |
suggestedFilename | null |
| Имя файла, которое запрашивает расширение | probe-export.csv |
| Имя файла, которое оказалось в каталоге | download.csv |
| Содержимое файла | заголовок и все три строки сохранились без потерь |
В этом harness на MV3/Playwright 1.56.0 chrome.downloads не вызвал событие загрузки Playwright. Когда расширение экспортировало CSV через API, waitForEvent('download') не срабатывал. Harness ловил файл, открывая CDP-сеанс, явно задавая поведение загрузки и читая выходной каталог:
const cdp = await ctx.newCDPSession(page);
await cdp.send('Browser.setDownloadBehavior', {
behavior: 'allow', downloadPath: DIR, eventsEnabled: true,
});
В этом же harness CDP-путь захвата не сохранил запрошенное имя файла. Заголовок и все три строки дошли без повреждений, но probe-export.csv появился как download.csv. Это наблюдаемое поведение для протестированных сборок браузера и конфигурации, а не документированная гарантия для любой загрузки через расширение. Проверяйте содержимое отдельно от имени файла.
Заметка о harness: поведение file:// для этого пути установки
Во время проверки этой части выяснилось, что одно широко повторяемое утверждение неверно, и оно уже успело попасть в ранний черновик этой статьи: будто content scripts не работают на страницах file://, потому что у расширений по умолчанию нет доступа к локальным файлам.
На обеих сборках Chrome for Testing измерение показало следующее: если загрузить расширение через командную строку и сразу перейти на file:///…/fixture/index.html, content script внедряется нормально. Маркер есть, ID расширения правильный, на обеих сборках. Расширения, загруженные из командной строки в распакованном виде, получают доступ к файлам; переключатель «разрешить доступ к файлам», который обычно вспоминают, относится к другому пути установки.
Публиковать fixtures по HTTP по-прежнему лучше, потому что file://-страница совсем не похожа на реальный target. Но это аргумент про реалистичность, а не техническое требование, и обычно приводимое объяснение здесь неверно.
Что это не доказывает
- Механизм ограничения мы понимаем только настолько, насколько это следует из строки предупреждения. Chrome говорит, что эти флаги в этой сборке не разрешены, и называет файл исходника. Но мы не смотрели в исходники, чтобы понять, это связано с конфигурацией сборки, политикой или чем-то еще.
- Это одна машина. macOS на arm64, одна стандартная патч-версия, две сборки Chrome for Testing. Chrome меняется достаточно быстро, поэтому это нужно перепроверять, а не цитировать как вечную истину.
- Это только путь
--load-extension. Упакованные установки.crx, загрузка в developer mode и enterprise allowlist policies не тестировались. Ничто здесь не поддерживает тезис «standard Chrome вообще не умеет запускать расширения». - Расширение — это специально сделанный stub. Он проверяет механику, которую использует любое scraping-расширение, но реальное расширение крупнее и может ломаться иначе, чем stub.
Что я в этом расследовании понял неправильно
Стоит сказать это прямо, потому что обе ошибки — из тех, что переживают ревью, если результат выглядит правдоподобно.
Первая версия текста утверждала, что сбой был тихим, что в логах нет ни одной строки и что механизм невозможно понять — будто любой, кто говорит, что знает причину, просто гадает. На самом деле достаточно было одного флага, и Chrome все это время печатал предупреждение с уровнем WARNING. Фраза «я не смог это найти» была оформлена как «это невозможно найти».
Вторая ошибка — это утверждение про file:// выше: оно было повторено из заметок и снабжено уверенным механизмом еще до теста. Один прогон его опроверг.
Паттерн в обоих случаях один и тот же. Правдоподобное утверждение, которое никто не оспорит, просто тащится дальше, потому что проверять кажется излишним. Исправление — не в большей осторожности формулировок, а в правиле о том, что можно утверждать: если утверждение нельзя привязать к конкретному прогону, оно не должно попадать в публикацию.
Команды, использованные в локальном harness
Версионные probe, extension stub, fixture и сырые сводки здесь приложены, но это пока не самостоятельный публичный пакет для воспроизведения. Точные источники загрузки Chrome for Testing 149 и 151 и их checksums в статье не зафиксированы, а пути к исполняемым файлам ниже были локальными входными данными. Прежде чем представлять полное сравнение как независимо воспроизводимое, опубликуйте источники браузеров и стабильный коммит репозитория.
Связанный обзор: обзор Playwright.
cd harness
npm install playwright@1.56.0
npx playwright install chromium # Chromium от Playwright; не две ветки CfT ниже
(cd fixture && python3 -m http.server 8731 &)
SP=$(pwd) OUT=cft-149.json LABEL=cft-149 EXE="<Chrome for Testing 149>" node probe_v2.mjs
SP=$(pwd) OUT=stock-150.json LABEL=stock-150 CHANNEL=chrome node probe_v2.mjs
SP=$(pwd) OUT=cft-151.json LABEL=cft-151 EXE="<Chrome for Testing 151>" node probe_v2.mjs
Один и тот же скрипт и одинаковые явные аргументы расширения использовались для всех трех веток, но способ получения исполняемого файла отличался: CHANNEL=chrome для стандартного Chrome и EXE для двух бинарников Chrome for Testing. Версия в каждом выходном файле читается из самого браузера, а не берется на веру из метки.
Для строки предупреждения harness не нужен:
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
--user-data-dir=/tmp/p --enable-logging=stderr \
--load-extension=/path/to/ext about:blank 2>&1 | grep "not allowed"
По состоянию на 2026-07-28.
Попробовать Thunderbit для извлечения веб-данных
Решение и оговорки
В протестированной матрице стандартный Google Chrome 150 отказал в работе с --disable-extensions-except плюс --load-extension, тогда как Chrome for Testing 149 и 151 загрузили расширение. Chrome записал отказ с уровнем WARNING: "--load-extension is not allowed in Google Chrome, ignoring." Формулировка поддерживает интерпретацию про брендированную сборку, а «бутерброд» из версий исключает простое монотонное удаление, но сборка и версия по-прежнему смешаны, если нет cross-build прогона одной и той же версии или доказательства из исходников/конфигурации.
Для закрепленного harness расширений используйте явно версионированный исполняемый файл браузера и проверяйте и service worker, и маркер content script. В этом окружении Playwright 1.56.0 экспорт расширения требовал захвата каталога через CDP и сохранялся под другим именем файла. Загруженный через командную строку stub также внедрялся в протестированный fixture file://; другие пути установки не тестировались.
Попробовать Thunderbit для извлечения веб-данных Get Started Free
Частые вопросы
Удален ли --load-extension из Chrome полностью?
Нет. Chrome for Testing 149.0.7827.55 и 151.0.7922.10 оба загрузили распакованный MV3 stub через этот флаг и успешно прошли все шесть проверок. Стандартный Google Chrome 150.0.7871.187 отказал в его использовании и записал "--load-extension is not allowed in Google Chrome, ignoring". Это поддерживает, но не доказывает, gating по бренду: поведение исключает простое монотонное удаление, а сценарий «сломалось в Chrome 150 и восстановилось в 151» все еще совместим с трехветочной схемой.
Почему я не вижу ошибку и как понять разницу между “не загрузилось вообще” и “загрузилось, но молча не сработало”?
Предупреждение не печатается при стандартной детализации логов. Запустите с --enable-logging=stderr, и оно появится сразу; без этого Chrome стартует нормально, но его служба расширений игнорирует флаги, а первым симптомом в harness становится тайм-аут селектора. Чтобы отличить «никогда не загружалось» от ошибки инъекции, используйте два независимых сигнала: background service worker MV3 и маркер content script в DOM целевой страницы. В протестированной ветке стандартного Chrome не появился ни один; в обеих ветках Chrome for Testing появились оба.
Что использовать для автоматизации расширений вместо этого?
В успешных ветках использовались закрепленные исполняемые файлы Chrome for Testing через executablePath. Chromium, управляемый Playwright, — еще один возможный бинарник для автоматизации, но в этом тесте он не был отдельной веткой и не должен описываться как тот же дистрибутив без проверки реального исполняемого файла.
Почему waitForEvent('download') никогда не срабатывает, когда расширение экспортирует файл?
В этом окружении MV3/Playwright 1.56.0 событие не срабатывало, и suggested filename не предоставлялся. Harness открывал CDP-сеанс, вызывал Browser.setDownloadBehavior с явным каталогом, а затем читал файл с диска. В протестированных прогонах байты CSV сохранились, но probe-export.csv пришел как download.csv; более широкие комбинации расширений и браузеров не тестировались.
О чем здесь не говорится — про file:// URL и про стандартный Chrome вообще?
Два ограничения, в противоположных направлениях. Content scripts действительно работают на file:// URL для расширений, загруженных из командной строки — это проверено на обеих сборках Chrome for Testing: скрипт внедрялся нормально, а ID расширения отображался корректно, так что распространенное утверждение, будто это не так из-за отсутствия доступа к локальным файлам по умолчанию, неверно именно для этого пути установки. Публиковать fixtures по HTTP все равно лучше, потому что это больше похоже на реальную цель, а не потому, что file:// блокирует инъекцию. С другой стороны: из всего этого не следует, что стандартный Chrome не умеет запускать расширения. Измерялся только путь командной строки --load-extension. Установка упакованного .crx, загрузка в developer mode и enterprise allowlist policies не тестировались, и никаких утверждений о них не делается. Речь идет только о той паре флагов, которую вам советуют использовать в учебниках по автоматизации.


