# tradein-browser — camoufox Playwright WS server (изолированный контейнер Firefox). # Backend подключается к этому контейнеру через playwright.firefox.connect. FROM python:3.12-slim ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 # Firefox runtime libs + ca-certificates для TLS RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ apt-get update && apt-get install -y --no-install-recommends \ libgtk-3-0 \ libasound2 \ libdbus-glib-1-2 \ libx11-xcb1 \ libxtst6 \ ca-certificates \ && useradd --create-home --uid 1000 app WORKDIR /app # camoufox[geoip] тянет playwright как зависимость RUN pip install --no-cache-dir "camoufox[geoip]" aiohttp # ── #915: null-guard для pageError.location в playwright 1.60.0 ───────────────── # Firefox/juggler-бэкенд иногда отдаёт uncaught JS-ошибку страницы (реклама / # cross-origin скрипты на avito) БЕЗ поля location. playwright-диспетчер при # доставке pageError-события читает `pageError.location.url` (coreBundle.js, # 2 места: BrowserContextDispatcher + trace event) без null-guard → # `TypeError: Cannot read properties of undefined (reading 'url')` ВНУТРИ # event-хэндлера node-драйвера → рвёт protocol-соединение → "Connection closed # while reading from the driver" / TargetClosedError → server.py отдаёт 500 и # делает полный relaunch браузера (дорого, тормозит sweep). Недетерминированно: # зависит от того, кинет ли конкретная загрузка locationless-ошибку. # Апгрейд playwright невозможен — camoufox 0.4.11 pinned под конкретный # Firefox+juggler. Чиним optional-chaining'ом прямо в pinned-бандле: # `pageError.location.X` → `pageError.location?.X` (no-op когда location есть; # отдаёт {url:undefined,...} когда нет → событие доставляется без краша). # Сборка падает (exit 1) если паттерн не найден — сигнал что bump playwright # сломал патч. RUN PW_BUNDLE="$(python -c 'import os,playwright;print(os.path.join(os.path.dirname(playwright.__file__),"driver","package","lib","coreBundle.js"))')" \ && sed -i -E 's/pageError\.location\.(url|lineNumber|columnNumber)/pageError.location?.\1/g' "$PW_BUNDLE" \ && N="$(grep -cF 'pageError.location?.url' "$PW_BUNDLE")" \ && echo "tradein-browser: patched pageError.location?.url sites: $N" \ && [ "$N" -ge 2 ] COPY server.py ./server.py # camoufox fetch качает (а) Firefox-сборку и (б) GeoLite2 mmdb (geoip=True). # Firefox идёт в $HOME/.cache → ENV HOME=/home/app кладёт его в кэш app-юзера, # куда рантайм его ищет (фикс #899). НО mmdb пишется в site-packages пакета # camoufox (root-owned после pip install) — под app это PermissionError на # GeoLite2-City.mmdb. Поэтому fetch выполняем под ROOT (пишет и mmdb, и — # благодаря ENV HOME — Firefox в /home/app/.cache), затем chown'им Firefox-кэш # на app. mmdb остаётся root-owned в site-packages (world-readable → app читает # на рантайме). Verified реальной сборкой образа 2026-05-31. ENV HOME=/home/app RUN python -m camoufox fetch && chown -R app:app /home/app/.cache USER app EXPOSE 3000 CMD ["python", "server.py"]