Я использую настоящий Chrome с nodriver, используя копию одного из моих профилей пользователей Chrome. Обычно это работает. Но если я нажимаю Ctrl+C на выполнение сценария, следующий запуск часто приводит к нарушению визуального состояния в macOS:
Видимое окно Chrome остается нарисованным на странице новой вкладки (или на ранее показанной странице).
DOM, который я читаю через CDP, является правильной страницей навигации — document.title, document.URL, запросы элементов, клики, заполнение форм, снимки экрана (*) — все это отражает реальную страницу, по которой осуществляется навигация, и автоматизация продолжает работать комплексно.
Если я нажму на омнибокс, фактический пользовательский интерфейс страницы "выпадет" из панели поиска (реальная страница на мгновение станет видимой под раскрывающимся списком) или средство визуализации выйдет из строя и вкладка закроется.
(*) Page.captureScreenshot фиксирует реальную страницу, а не то, что видно на экране.
Ручной запуск Chrome с использованием того же профиля с последующим чистым выходом устраняет ошибку до следующего принудительного завершения сценария. Таким образом, неработающее состояние — это то, что нечистый выход оставляет в профиле или в каком-либо состоянии Chrome на уровне macOS.
Настройка
При каждом запуске я копирую настоящий каталог пользовательских данных Chrome в новый каталог tempfile.mkdtemp(...) и запускаю Chrome для копии с --profile-directory=Profile 4, поэтому исходный профиль никогда не записывается.
Минимальный воспроизводимый пример
import asyncio
import shutil
import tempfile
from pathlib import Path
import nodriver as nd
REAL_BASE = Path("/Users/me/Library/Application Support/Google/Chrome")
PROFILE = "Profile 4"
CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
def prepare_temp_profile() -> Path:
"""Copy the real profile to a fresh temp dir (matches what my real code does)."""
tmp = Path(tempfile.mkdtemp(prefix="repro-chrome-"))
if (REAL_BASE / "Local State").exists():
shutil.copy2(REAL_BASE / "Local State", tmp / "Local State")
shutil.copytree(
REAL_BASE / PROFILE,
tmp / PROFILE,
dirs_exist_ok=True,
ignore_dangling_symlinks=True,
ignore=shutil.ignore_patterns("RunningChromeVersion"),
)
return tmp
async def main():
user_data_dir = prepare_temp_profile()
config = nd.Config(
headless=False,
browser_executable_path=CHROME,
user_data_dir=str(user_data_dir),
browser_args=[
f"--profile-directory={PROFILE}",
"--window-size=1366,768",
],
)
browser = await nd.start(config=config)
tab = await browser.get("https://example.org")
title = await tab.evaluate("document.title")
url = await tab.evaluate("document.URL")
print("CDP says title =", title, "| url =", url)
# Keep window open so you can compare the painted pixels to the CDP truth.
await asyncio.sleep(120)
if __name__ == "__main__":
asyncio.run(main())
Воспроизведение
Запустите сценарий. Убедитесь, что в окне отображается example.org и напечатанный заголовок соответствует.
Нажмите Ctrl+C, пока окно открыто.
Запустите сценарий еще раз. Повторите шаги 2–3 несколько раз.
Примерно 30–60% запусков после уничтожения заканчиваются тем, что видимое окно зависает на устаревшем содержимом (NTP или ранее обработанной странице), в то время как CDP говорит title = Пример домена | url = https://example.org/ печатается правильно.
Вещи, которые я пробовал, которые не исправляют
Удаление большего количества файлов во время копирования временного каталога, поэтому ничего из предыдущего нечистого запуска не сохраняется. В ignore_patterns(...) я добавил:
Пропуск собственного окна запуска Chrome, чтобы первая навигация не была переключением средства рендеринга с вкладки NTP на целевой URL. Я запустил Chrome с --no-startup-window и попросил nodriver создать первую вкладку через CDP (
, который вызывает Target.createTarget под капотом).
Передача --disable-session-crashed-bubble (также передается по умолчанию nodriver).
Ни один из них не меняет поведение. Ошибка по-прежнему срабатывает стохастически после нажатия Ctrl+C.
Что я спрашиваю
Какое состояние Chrome/Chromium — помимо флагов выхода профиля JSON, больших двоичных объектов Sessions/, кэшей графического процессора и шейдеров, а также пути подкачки рендеринга — необходимо сбросить для восстановления композитора после нечистого выхода в macOS?
Существует ли флаг запуска Chrome, который отключает любое кэшированное состояние уровня macOS, которое Chrome повторно использует при запусках с одним и тем же каталогом пользовательских данных (например, что-либо в ~/Library/Saved Application State/com.google.Chrome.savedState/ или кэшах Core Animation/IOSurface)?
Есть ли команда CDP, которую я могу выполнить сразу после nd.start(), которая принудительно видимое окно для воссоздания поверхности компоновщика с нуля (что-то эквивалентное отсоединению + воссозданию, реальному изменению размера Browser.setWindowBounds, циклу Emulation.setDeviceMetricsOverride и т. д.)?
Или это известная ошибка Chromium, и в этом случае в чем проблема/обходной путь?
Любой указатель на фактическую первопричину будет очень признателен — каждый «очевидный» кандидат, о котором я могу думать (восстановление сеанса, кеш графического процессора, кеш шейдеров, пузырь сбоя, замена рендерера), не является источником.
Когда браузер находится в этом состоянии с ошибкой и я нажимаю на панель поиска, я вижу это:
Я использую настоящий Chrome с nodriver, используя копию одного из моих профилей пользователей Chrome. Обычно это работает. Но если я нажимаю Ctrl+C на выполнение сценария, следующий запуск часто приводит к нарушению визуального состояния в macOS: [list] [*]Видимое окно Chrome остается нарисованным на [b]странице новой вкладки[/b] (или на ранее показанной странице). [*]DOM, который я читаю через CDP, является [b]правильной[/b] страницей навигации — document.title, document.URL, запросы элементов, клики, заполнение форм, снимки экрана (*) — все это отражает реальную страницу, по которой осуществляется навигация, и автоматизация продолжает работать комплексно. [*]Если я нажму на омнибокс, фактический пользовательский интерфейс страницы [b]"выпадет" из панели поиска[/b] (реальная страница на мгновение станет видимой под раскрывающимся списком) или средство визуализации выйдет из строя и вкладка закроется. [/list] (*) Page.captureScreenshot фиксирует реальную страницу, а не то, что видно на экране. Ручной запуск Chrome с использованием того же профиля с последующим чистым выходом устраняет ошибку до следующего принудительного завершения сценария. Таким образом, неработающее состояние — это то, что нечистый выход оставляет в профиле или в каком-либо состоянии Chrome на уровне macOS. Настройка [list] [*]последняя версия macOS [*]Google Chrome (текущая стабильная версия) [*]Python 3.12 [*][code]nodriver[/code] (текущий) [/list] При каждом запуске я копирую настоящий каталог пользовательских данных Chrome в новый каталог tempfile.mkdtemp(...) и запускаю Chrome для копии с --profile-directory=Profile 4, поэтому исходный профиль никогда не записывается. Минимальный воспроизводимый пример [code]import asyncio import shutil import tempfile from pathlib import Path
def prepare_temp_profile() -> Path: """Copy the real profile to a fresh temp dir (matches what my real code does).""" tmp = Path(tempfile.mkdtemp(prefix="repro-chrome-"))
title = await tab.evaluate("document.title") url = await tab.evaluate("document.URL") print("CDP says title =", title, "| url =", url)
# Keep window open so you can compare the painted pixels to the CDP truth. await asyncio.sleep(120)
if __name__ == "__main__": asyncio.run(main()) [/code] Воспроизведение [list] [*]Запустите сценарий. Убедитесь, что в окне отображается example.org и напечатанный заголовок соответствует. [*]Нажмите Ctrl+C, пока окно открыто. [*]Запустите сценарий еще раз. Повторите шаги 2–3 несколько раз. [/list] Примерно [b]30–60%[/b] запусков после уничтожения заканчиваются тем, что видимое окно зависает на устаревшем содержимом (NTP или ранее обработанной странице), в то время как CDP говорит title = Пример домена | url = https://example.org/ печатается правильно. Вещи, которые я пробовал, которые [b]не[/b] исправляют [list] [*][b]Удаление большего количества файлов во время копирования временного каталога[/b], поэтому ничего из предыдущего нечистого запуска не сохраняется. В ignore_patterns(...) я добавил: [list] [code]Singleton*[/code] [*][code]Sessions[/code], текущий сеанс, текущие вкладки, последний сеанс, последние вкладки [*][code]GPUCache[/code], ShaderCache, GrShaderCache, DawnGraphiteCache, DawnWebGPUCache [*][code]Crashpad[/code], Crashpad Metrics-active.pma, Crashpad Metrics-spare.pma, Отчеты о сбоях [*][code]lockfile[/code] [/list]
[*][b]Перезапись скопированных настроек[/b], чтобы они выглядели как чистый предыдущий выход, чтобы Chrome не входил в аварийное восстановление: [code]prefs["profile"]["exit_type"] = "Normal" prefs["profile"]["exited_cleanly"] = True prefs["session"]["restore_on_startup"] = 5 # open NTP prefs["session"]["startup_urls"] = [] [/code]
[*][b]Перезапись скопированного локального состояния[/b] таким же образом: [code]ls["user_experience_metrics"]["stability"]["exited_cleanly"] = True ls["profile"]["info_cache"]["Profile 4"]["exited_cleanly"] = True [/code]
[*][b]Пропуск собственного окна запуска Chrome[/b], чтобы первая навигация не была переключением средства рендеринга с вкладки NTP на целевой URL. Я запустил Chrome с --no-startup-window и попросил nodriver создать первую вкладку через CDP ([code]browser.get(url, new_tab=True)[/code], который вызывает Target.createTarget под капотом).
[*][b]Передача --disable-session-crashed-bubble[/b] (также передается по умолчанию nodriver).
[/list] Ни один из них не меняет поведение. Ошибка по-прежнему срабатывает стохастически после нажатия Ctrl+C. Что я спрашиваю Какое состояние Chrome/Chromium — помимо флагов выхода профиля JSON, больших двоичных объектов Sessions/, кэшей графического процессора и шейдеров, а также пути подкачки рендеринга — необходимо сбросить для восстановления композитора после нечистого выхода в macOS? [list] [*]Существует ли флаг запуска Chrome, который отключает любое кэшированное состояние уровня macOS, которое Chrome повторно использует при запусках с одним и тем же каталогом пользовательских данных (например, что-либо в ~/Library/Saved Application State/com.google.Chrome.savedState/ или кэшах Core Animation/IOSurface)? [*]Есть ли команда CDP, которую я могу выполнить сразу после nd.start(), которая принудительно видимое окно для воссоздания поверхности компоновщика с нуля (что-то эквивалентное отсоединению + воссозданию, реальному изменению размера Browser.setWindowBounds, циклу Emulation.setDeviceMetricsOverride и т. д.)? [*]Или это известная ошибка Chromium, и в этом случае в чем проблема/обходной путь? [/list] Любой указатель на фактическую первопричину будет очень признателен — каждый «очевидный» кандидат, о котором я могу думать (восстановление сеанса, кеш графического процессора, кеш шейдеров, пузырь сбоя, замена рендерера), не является источником. Когда браузер находится в этом состоянии с ошибкой и я нажимаю на панель поиска, я вижу это: [img]https://i.sstatic.net/V0rldA2t.png[/img]