Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .changeset/playwright-extension-dry-run-1-58.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@trigger.dev/build": patch
---

The `playwright` build extension now works with Playwright 1.58 and later. 1.58 changed the `playwright install --dry-run` output, which made deploy image builds fail while downloading the browsers.
39 changes: 39 additions & 0 deletions packages/build/src/extensions/playwright.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { describe, expect, it } from "vitest";
import { dryRunHeaderPattern } from "./playwright.js";

// Real `playwright install --dry-run` headers, before and after the 1.58 format change.
const HEADERS = {
"1.57": {
chromium: "browser: chromium version 143.0.7499.4",
"chromium-headless-shell": "browser: chromium-headless-shell version 143.0.7499.4",
firefox: "browser: firefox version 144.0.2",
webkit: "browser: webkit version 26.0",
},
"1.62": {
chromium: "Chrome for Testing 151.0.7922.34 (playwright chromium v1234)",
"chromium-headless-shell":
"Chrome Headless Shell 151.0.7922.34 (playwright chromium-headless-shell v1234)",
firefox: "Firefox 153.0 (playwright firefox v1538)",
webkit: "WebKit 26.5 (playwright webkit v2336)",
},
} as const;

const browsers = Object.keys(HEADERS["1.57"]) as Array<keyof (typeof HEADERS)["1.57"]>;

describe("playwright extension dry-run header pattern", () => {
it.each(browsers)("selects the %s block in both output formats", (browser) => {
const pattern = new RegExp(dryRunHeaderPattern(browser));

expect(pattern.test(HEADERS["1.57"][browser])).toBe(true);
expect(pattern.test(HEADERS["1.62"][browser])).toBe(true);
});

it.each(browsers)("does not select another browser's block for %s", (browser) => {
const pattern = new RegExp(dryRunHeaderPattern(browser));

for (const other of browsers.filter((b) => b !== browser)) {
expect(pattern.test(HEADERS["1.57"][other])).toBe(false);
expect(pattern.test(HEADERS["1.62"][other])).toBe(false);
}
});
});
17 changes: 16 additions & 1 deletion packages/build/src/extensions/playwright.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,19 @@ export function playwright(options: PlaywrightExtensionOptions = {}) {
return new PlaywrightExtension(options);
}

/**
* Extended regex selecting a browser's block header in `playwright install --dry-run` output.
*
* Playwright < 1.58 prints `browser: <name> version <v>`; 1.58+ prints
* `<Product> <v> (playwright <name> v<build>)`. The trailing space / `v` keep
* `chromium` from matching the `chromium-headless-shell` block.
*
* @internal
*/
export function dryRunHeaderPattern(browser: string): string {
return `browser: ${browser} |\\(playwright ${browser} v`;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shipwright · CRITICAL

The new regex pattern 'browser: ${browser} |(playwright ${browser} v' is built by interpolating the browser name directly into a regex string without escaping regex metacharacters

Impact: The new regex pattern 'browser: ${browser} |(playwright ${browser} v' is built by interpolating the browser name directly into a regex string without escaping regex metacharacters. If a browser name ever contains a regex special character (e.g., a dot, plus, or parenthesis), the generated RegExp will either fail to compile or match unintended text, breaking the deploy image build. The existing code already interpol…

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.


/**
* Background:
*
Expand Down Expand Up @@ -317,7 +330,9 @@ class PlaywrightExtension implements BuildExtension {

Array.from(browsersToInstall).forEach((browser) => {
instructions.push(
`RUN grep -A5 -m1 "browser: ${browser}" /tmp/browser-info.txt > /tmp/${browser}-info.txt`,
// Only the two lines after the header (install location, download url)
// are read, so the window stops there.
`RUN grep -A2 -m1 -E "${dryRunHeaderPattern(browser)}" /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") && \
Expand Down
Loading