Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion packages/build/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"./extensions/prisma": "./src/extensions/prisma.ts",
"./extensions/audioWaveform": "./src/extensions/audioWaveform.ts",
"./extensions/typescript": "./src/extensions/typescript.ts",
"./extensions/puppeteer": "./src/extensions/puppeteer.ts"
"./extensions/puppeteer": "./src/extensions/puppeteer.ts",
"./extensions/playwright": "./src/extensions/playwright.ts"
},
"sourceDialects": [
"@triggerdotdev/source"
Expand Down Expand Up @@ -57,6 +58,9 @@
],
"extensions/puppeteer": [
"dist/commonjs/extensions/puppeteer.d.ts"
],
"extensions/playwright": [
"dist/commonjs/extensions/playwright.d.ts"
]
}
},
Expand Down Expand Up @@ -173,6 +177,17 @@
"types": "./dist/commonjs/extensions/puppeteer.d.ts",
"default": "./dist/commonjs/extensions/puppeteer.js"
}
},
"./extensions/playwright": {
"import": {
"@triggerdotdev/source": "./src/extensions/playwright.ts",
"types": "./dist/esm/extensions/playwright.d.ts",
"default": "./dist/esm/extensions/playwright.js"
},
"require": {
"types": "./dist/commonjs/extensions/playwright.d.ts",
"default": "./dist/commonjs/extensions/playwright.js"
}
}
},
"main": "./dist/commonjs/index.js",
Expand Down
304 changes: 304 additions & 0 deletions packages/build/src/extensions/playwright.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
import type { BuildContext, BuildExtension } from "@trigger.dev/core/v3/build";

type PlaywrightBrowser = "chromium" | "firefox" | "webkit";

interface PlaywrightExtensionOptions {
/**
* Browsers to install. Select only needed browsers to optimize build time and size.
* @default ["chromium"]
*/
browsers?: PlaywrightBrowser[];

/**
* Run the browsers in headless mode (Recommended)
* @default true
*/
headless?: boolean;
}

/**
* This list is from the official playwright registry.
*
* @see https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/registry/nativeDeps.ts
*/
const debian12Deps = {
tools: [
"xvfb",
"fonts-noto-color-emoji",
"fonts-unifont",
"libfontconfig1",
"libfreetype6",
"xfonts-scalable",
"fonts-liberation",
"fonts-ipafont-gothic",
"fonts-wqy-zenhei",
"fonts-tlwg-loma-otf",
"fonts-freefont-ttf",
],
chromium: [
"libasound2",
"libatk-bridge2.0-0",
"libatk1.0-0",
"libatspi2.0-0",
"libcairo2",
"libcups2",
"libdbus-1-3",
"libdrm2",
"libgbm1",
"libglib2.0-0",
"libnspr4",
"libnss3",
"libpango-1.0-0",
"libx11-6",
"libxcb1",
"libxcomposite1",
"libxdamage1",
"libxext6",
"libxfixes3",
"libxkbcommon0",
"libxrandr2",
],
firefox: [
"libasound2",
"libatk1.0-0",
"libcairo-gobject2",
"libcairo2",
"libdbus-1-3",
"libdbus-glib-1-2",
"libfontconfig1",
"libfreetype6",
"libgdk-pixbuf-2.0-0",
"libglib2.0-0",
"libgtk-3-0",
"libharfbuzz0b",
"libpango-1.0-0",
"libpangocairo-1.0-0",
"libx11-6",
"libx11-xcb1",
"libxcb-shm0",
"libxcb1",
"libxcomposite1",
"libxcursor1",
"libxdamage1",
"libxext6",
"libxfixes3",
"libxi6",
"libxrandr2",
"libxrender1",
"libxtst6",
],
webkit: [
"libsoup-3.0-0",
"gstreamer1.0-libav",
"gstreamer1.0-plugins-bad",
"gstreamer1.0-plugins-base",
"gstreamer1.0-plugins-good",
"libatk-bridge2.0-0",
"libatk1.0-0",
"libcairo2",
"libdbus-1-3",
"libdrm2",
"libegl1",
"libenchant-2-2",
"libepoxy0",
"libevdev2",
"libfontconfig1",
"libfreetype6",
"libgbm1",
"libgdk-pixbuf-2.0-0",
"libgles2",
"libglib2.0-0",
"libglx0",
"libgstreamer-gl1.0-0",
"libgstreamer-plugins-base1.0-0",
"libgstreamer1.0-0",
"libgtk-4-1",
"libgudev-1.0-0",
"libharfbuzz-icu0",
"libharfbuzz0b",
"libhyphen0",
"libicu72",
"libjpeg62-turbo",
"liblcms2-2",
"libmanette-0.2-0",
"libnotify4",
"libopengl0",
"libopenjp2-7",
"libopus0",
"libpango-1.0-0",
"libpng16-16",
"libproxy1v5",
"libsecret-1-0",
"libwayland-client0",
"libwayland-egl1",
"libwayland-server0",
"libwebp7",
"libwebpdemux2",
"libwoff1",
"libx11-6",
"libxcomposite1",
"libxdamage1",
"libxkbcommon0",
"libxml2",
"libxslt1.1",
"libatomic1",
"libevent-2.1-7",
"libavif15",
],
lib2package: {
"libavif.so.15": "libavif15",
"libsoup-3.0.so.0": "libsoup-3.0-0",
"libasound.so.2": "libasound2",
"libatk-1.0.so.0": "libatk1.0-0",
"libatk-bridge-2.0.so.0": "libatk-bridge2.0-0",
"libatspi.so.0": "libatspi2.0-0",
"libcairo.so.2": "libcairo2",
"libcups.so.2": "libcups2",
"libdbus-1.so.3": "libdbus-1-3",
"libdrm.so.2": "libdrm2",
"libgbm.so.1": "libgbm1",
"libgio-2.0.so.0": "libglib2.0-0",
"libglib-2.0.so.0": "libglib2.0-0",
"libgobject-2.0.so.0": "libglib2.0-0",
"libnspr4.so": "libnspr4",
"libnss3.so": "libnss3",
"libnssutil3.so": "libnss3",
"libpango-1.0.so.0": "libpango-1.0-0",
"libsmime3.so": "libnss3",
"libX11.so.6": "libx11-6",
"libxcb.so.1": "libxcb1",
"libXcomposite.so.1": "libxcomposite1",
"libXdamage.so.1": "libxdamage1",
"libXext.so.6": "libxext6",
"libXfixes.so.3": "libxfixes3",
"libxkbcommon.so.0": "libxkbcommon0",
"libXrandr.so.2": "libxrandr2",
"libgtk-4.so.1": "libgtk-4-1",
},
};

/**
* Creates a Playwright extension for trigger.dev
* @param options Configuration options
*/
export function playwright(options: PlaywrightExtensionOptions = {}) {
return new PlaywrightExtension(options);
}

/**
* Background:
*
* Running `npx playwright install --with-deps` normally will install the browsers and the dependencies.
* However, this is not possible in a build context, because we don't have sudo access.
*
* So we need to install the dependencies manually and then download and install the browsers.
* This has a few challenges:
* 1. We don't want to download all browsers, only the ones we need with it's dependencies
* The less dependencies we have to install, the faster the build, and the smaller the image.
* 2. We need to know where to download the browsers from
* while we can hardcode the download url it might change over time (as it has in the past)
* so we need to download the browser info first and then parse the output to get the download url.
*
* Note: While this looks like we are downloading & installing a lot of stuff, it's actually not that bad
* since running `npx playwright install --with-deps` will result in the same amount of downloads.
*/
class PlaywrightExtension implements BuildExtension {
public readonly name = "PlaywrightExtension";
private readonly options: Required<PlaywrightExtensionOptions>;

constructor({ browsers = ["chromium"], headless = true }: PlaywrightExtensionOptions = {}) {
if (browsers && browsers.length === 0) {
throw new Error("At least one browser must be specified");
}
this.options = { browsers, headless };
}

onBuildComplete(context: BuildContext) {
if (context.target === "dev") return;

context.logger.debug(
`Adding ${this.name} to the build with browsers: ${this.options.browsers.join(", ")}`
);

const instructions: string[] = [
// Base dependencies, we need these to download the browsers
`RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
unzip \
jq \
grep \
sed \
npm \
&& apt-get clean && rm -rf /var/lib/apt/lists/*`,

// Install Playwright globally
`RUN npm install -g playwright`,
];

const deps = [...debian12Deps.tools, ...Object.values(debian12Deps.lib2package)];
if (this.options.browsers.includes("chromium")) deps.push(...debian12Deps.chromium);
if (this.options.browsers.includes("firefox")) deps.push(...debian12Deps.firefox);
if (this.options.browsers.includes("webkit")) deps.push(...debian12Deps.webkit);

instructions.push(
`RUN apt-get update && apt-get install -y --no-install-recommends ${deps.join(" ")} \
&& apt-get clean && rm -rf /var/lib/apt/lists/*`
);

// Setup directory for playwright browsers
instructions.push(`RUN mkdir -p /ms-playwright`);

/**
* `npx playwright install --dry-run` prints the download urls for the browsers.
* We save this output to a file and then parse it to get the download urls for the browsers.
*/
instructions.push(`RUN npx playwright install --dry-run > /tmp/browser-info.txt`);
this.options.browsers.forEach((browser) => {
const browserType = browser === "chromium" ? "chromium-headless-shell" : browser;
instructions.push(
`RUN grep -A5 "browser: ${browserType}" /tmp/browser-info.txt > /tmp/${browser}-info.txt`,

`RUN INSTALL_DIR=$(grep "Install location:" /tmp/${browser}-info.txt | cut -d':' -f2- | xargs) && \
DIR_NAME=$(basename "$INSTALL_DIR") && \
MS_DIR="/ms-playwright/$DIR_NAME" && \
mkdir -p "$MS_DIR"`,

`RUN DOWNLOAD_URL=$(grep "Download url:" /tmp/${browser}-info.txt | cut -d':' -f2- | xargs | sed "s/mac-arm64/linux/g" | sed "s/mac-15-arm64/ubuntu-20.04/g") && \
echo "Downloading ${browser} from $DOWNLOAD_URL" && \
curl -L -o /tmp/${browser}.zip "$DOWNLOAD_URL" && \
unzip -q /tmp/${browser}.zip -d "/ms-playwright/$(basename $(grep "Install location:" /tmp/${browser}-info.txt | cut -d':' -f2- | xargs))" && \
chmod -R +x "/ms-playwright/$(basename $(grep "Install location:" /tmp/${browser}-info.txt | cut -d':' -f2- | xargs))" && \
rm /tmp/${browser}.zip`
);
});

// Environment variables
const envVars: Record<string, string> = {
PLAYWRIGHT_BROWSERS_PATH: "/ms-playwright", // where playwright will find the browsers
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1", // we already downloaded the browsers
PLAYWRIGHT_SKIP_BROWSER_VALIDATION: "1", // we already downloaded the browsers
};

if (!this.options.headless) {
instructions.push(
`RUN echo '#!/bin/sh' > /usr/local/bin/xvfb-exec`,
`RUN echo 'Xvfb :99 -screen 0 1024x768x24 &' >> /usr/local/bin/xvfb-exec`,
`RUN echo 'exec "$@"' >> /usr/local/bin/xvfb-exec`,
`RUN chmod +x /usr/local/bin/xvfb-exec`
);

envVars.DISPLAY = ":99"; // Virtual display for the browsers
}

context.addLayer({
id: "playwright",
image: {
instructions,
},
deploy: {
env: envVars,
override: true,
},
});
}
}
19 changes: 19 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions references/v3-catalog/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"msw": "^2.2.1",
"openai": "^4.47.0",
"pg": "^8.11.5",
"playwright": "^1.50.1",
"puppeteer": "^23.4.0",
"react": "19.0.0-rc.0",
"react-email": "^3.0.1",
Expand Down
Loading