fix(emex): ship runtime scripts/ in prod image and forward chromium executable path
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled

EMEX-decoded vehicles surfaced empty category pages on prod because two
runtime prerequisites were absent from the production image:

1. `scripts/emex-vin-scraper.js` was never copied — the build stage's
   `COPY . .` brings it in but the production stage only cherry-picks
   `apps/api/dist`, `drizzle`, and `start.sh`. Every EMEX leaf hit
   therefore failed with "Scraper file not found at: /app/scripts/…"
   and the category page rendered "Bu kategori icin parca bulunamadi."
2. `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium-browser` was
   exported in the Dockerfile but never reached `chromium.launch()` —
   that env var is consumed by `playwright install`, not at runtime.
   Playwright fell back to its bundled headless-shell cache path
   (`/root/.cache/ms-playwright/chromium_headless_shell-*/…`) which
   does not exist on the alpine image, so even with the scraper file
   present the browser pool init would have kept failing.

Fix:
- Dockerfile: `COPY --from=build /app/scripts ./scripts`.
- `emex.browser.ts` + `parts-catalogs-auth.service.ts`: read
  `process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` and pass it as
  `executablePath` to `chromium.launch()` when set.

Verified on prod container: `ls /app/scripts` → missing pre-fix; the
binary at `/usr/bin/chromium-browser` exists, so the env-var hand-off
will resolve cleanly once the new image lands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 08:45:18 +03:00
parent 17bd256979
commit a1e80c042f
3 changed files with 20 additions and 2 deletions

View File

@@ -165,6 +165,12 @@ export class EmexBrowserService implements OnModuleInit, OnModuleDestroy {
// Dynamic import — playwright is a devDependency
const { chromium } = await import("playwright");
// PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH only influences `playwright install`;
// chromium.launch() at runtime ignores it unless we forward it explicitly.
// Alpine images don't ship the bundled chromium-headless-shell that
// Playwright otherwise looks for under /root/.cache/ms-playwright/, so
// without this hand-off the production container fails with
// "Executable doesn't exist at …chrome-headless-shell".
const launchOptions: Record<string, unknown> = {
headless: true,
args: [
@@ -175,6 +181,8 @@ export class EmexBrowserService implements OnModuleInit, OnModuleDestroy {
"--disable-gpu",
],
};
const executablePath = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH;
if (executablePath) launchOptions.executablePath = executablePath;
if (this.useProxy) {
const port = this.randomProxyPort();

View File

@@ -615,7 +615,10 @@ export class PartsCatalogsAuthService implements OnModuleInit, OnModuleDestroy {
private async _doLaunch(): Promise<void> {
const { chromium } = await import("playwright");
this.browser = await chromium.launch({
// See emex.browser.ts:_doLaunch — PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH is
// a `playwright install` knob, not a launch knob, so forward it manually
// for Alpine production where the bundled headless shell isn't installed.
const launchOptions: Record<string, unknown> = {
headless: true,
args: [
"--no-sandbox",
@@ -624,7 +627,11 @@ export class PartsCatalogsAuthService implements OnModuleInit, OnModuleDestroy {
"--disable-accelerated-2d-canvas",
"--disable-gpu",
],
});
};
const executablePath = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH;
if (executablePath) launchOptions.executablePath = executablePath;
this.browser = await chromium.launch(launchOptions);
this.browser.on("disconnected", () => {
this.logger.warn("Browser disconnected — will relaunch on next request");