From 29a4d07db97737e1e53ddf62c69d4abbe896ba9f Mon Sep 17 00:00:00 2001 From: Danny Milosavljevic Date: Fri, 2 Jan 2026 23:54:25 +0100 Subject: [PATCH] gnu: Add node-playwright-core. * gnu/packages/playwright.scm: New file. (node-playwright-core): New variable. * gnu/local.mk (GNU_SYSTEM_MODULES): Add reference to file. Change-Id: Ie94128a2bbbd0fadbd8762dc99b802706aee3530 --- gnu/local.mk | 1 + gnu/packages/playwright.scm | 257 ++++++++++++++++++++++++++++++++++++ 2 files changed, 258 insertions(+) create mode 100644 gnu/packages/playwright.scm diff --git a/gnu/local.mk b/gnu/local.mk index e619ab688c..f114e326e5 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -553,6 +553,7 @@ GNU_SYSTEM_MODULES = \ %D%/packages/diagram.scm \ %D%/packages/pkg-config.scm \ %D%/packages/plan9.scm \ + %D%/packages/playwright.scm \ %D%/packages/plotutils.scm \ %D%/packages/poedit.scm \ %D%/packages/polkit.scm \ diff --git a/gnu/packages/playwright.scm b/gnu/packages/playwright.scm new file mode 100644 index 0000000000..c492674ae8 --- /dev/null +++ b/gnu/packages/playwright.scm @@ -0,0 +1,257 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2025 Danny Milosavljevic +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see . + +(define-module (gnu packages playwright) + #:use-module ((guix licenses) #:prefix license:) + #:use-module (guix packages) + #:use-module (guix gexp) + #:use-module (guix git-download) + #:use-module (guix build-system node) + #:use-module (guix utils) + #:use-module (gnu packages) + #:use-module (gnu packages chromium) + #:use-module (gnu packages node) + #:use-module (gnu packages node-xyz) + #:use-module (gnu packages python) + #:use-module (gnu packages web)) + +;; Playwright is a browser automation framework. Upstream provides patched +;; versions of Firefox and WebKit browsers, but those require building +;; custom browser forks. This package only supports Chromium-based browsers +;; (using the system ungoogled-chromium) since Chromium's DevTools Protocol +;; is sufficient for automation without patches. +;; +;; Firefox support would require Playwright's "Juggler" protocol patches. +;; WebKit support would require Playwright's WebKit automation patches. +;; Both are invasive changes to browser source code. +;; +;; In the future, WebDriver BiDi (a W3C standard) may allow Playwright to +;; work with unpatched browsers. + +(define-public node-playwright-core + (package + (name "node-playwright-core") + (version "1.50.0") + (source + (origin + (method git-fetch) + (uri (git-reference + (url "https://github.com/microsoft/playwright") + (commit (string-append "v" version)))) + (file-name (git-file-name name version)) + (sha256 + (base32 "0xymivpn2c4srbaqdix3qx2zcr6cx5zgs2a20drvi4dmkspn4jz6")) + (modules '((guix build utils))) + (snippet + '(delete-file-recursively "packages/playwright-core/bundles")))) + (build-system node-build-system) + (arguments + (list + #:tests? #f ; Tests require browser binaries. + #:phases + #~(modify-phases %standard-phases + (add-after 'unpack 'break-monorepo-structure + (lambda _ + ;; This is a monorepo with npm workspaces. Delete root package.json + ;; and lock files to prevent npm from reading workspace config. + (delete-file "package.json") + (for-each delete-file + (find-files "." "package-lock\\.json$")))) + (add-after 'break-monorepo-structure 'change-to-package-directory + (lambda _ + (chdir "packages/playwright-core"))) + (add-after 'unpack 'skip-browser-download + (lambda _ + (setenv "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD" "1"))) + (add-after 'unpack 'unbundle-dependencies + (lambda _ + (substitute* "packages/playwright-core/src/zipBundle.ts" + (("require\\('./zipBundleImpl'\\)\\.yazl") + "require('yazl')") + (("require\\('./zipBundleImpl'\\)\\.yauzl") + "require('yauzl')") + (("require\\('./zipBundleImpl'\\)\\.extract") + "require('extract-zip')")) + (substitute* "packages/playwright-core/src/utilsBundle.ts" + (("require\\('./utilsBundleImpl'\\)\\.colors") + "require('colors/safe')") + (("require\\('./utilsBundleImpl'\\)\\.debug") + "require('debug')") + (("require\\('./utilsBundleImpl'\\)\\.diff") + "require('diff')") + (("require\\('./utilsBundleImpl'\\)\\.dotenv") + "require('dotenv')") + (("require\\('./utilsBundleImpl'\\)\\.getProxyForUrl") + "require('proxy-from-env').getProxyForUrl") + (("require\\('./utilsBundleImpl'\\)\\.HttpsProxyAgent") + "require('https-proxy-agent').HttpsProxyAgent") + (("require\\('./utilsBundleImpl'\\)\\.jpegjs") + "require('jpeg-js')") + (("require\\('./utilsBundleImpl'\\)\\.lockfile") + "require('proper-lockfile')") + (("require\\('./utilsBundleImpl'\\)\\.mime") + "require('mime')") + (("require\\('./utilsBundleImpl'\\)\\.minimatch") + "require('minimatch')") + (("require\\('./utilsBundleImpl'\\)\\.open") + "require('open')") + (("require\\('./utilsBundleImpl'\\)\\.PNG") + "require('pngjs').PNG") + (("require\\('./utilsBundleImpl'\\)\\.program") + "require('commander').program") + (("require\\('./utilsBundleImpl'\\)\\.progress") + "require('progress')") + (("require\\('./utilsBundleImpl'\\)\\.SocksProxyAgent") + "require('socks-proxy-agent').SocksProxyAgent") + (("require\\('./utilsBundleImpl'\\)\\.yaml") + "require('yaml')") + (("require\\('./utilsBundleImpl'\\)\\.wsServer") + "require('ws').WebSocketServer") + (("require\\('./utilsBundleImpl'\\)\\.wsReceiver") + "require('ws').Receiver") + (("require\\('./utilsBundleImpl'\\)\\.wsSender") + "require('ws').Sender") + (("require\\('./utilsBundleImpl'\\)\\.ws\\b") + "require('ws')") + (("require\\('./utilsBundleImpl'\\)\\.StackUtils") + "require('stack-utils')")))) + (add-after 'unbundle-dependencies 'add-unbundled-dependencies + (lambda _ + ;; Add dependencies that were unbundled from utilsBundle/zipBundle. + ;; Must run before patch-dependencies which minifies package.json. + (modify-json + #:file "packages/playwright-core/package.json" + (lambda (pkg) + (acons "dependencies" + '(("colors" . "*") + ("commander" . "*") + ("debug" . "*") + ("diff" . "*") + ("dotenv" . "*") + ("extract-zip" . "*") + ("https-proxy-agent" . "*") + ("jpeg-js" . "*") + ("mime" . "*") + ("minimatch" . "*") + ("open" . "*") + ("pngjs" . "*") + ("progress" . "*") + ("proper-lockfile" . "*") + ("proxy-from-env" . "*") + ("socks-proxy-agent" . "*") + ("stack-utils" . "*") + ("ws" . "*") + ("yaml" . "*") + ("yazl" . "*") + ("yauzl" . "*")) + pkg))))) + (add-before 'build 'generate-injected-scripts + (lambda _ + ;; Run generate_injected.js to create src/generated/*.ts files. + (invoke "node" "../../utils/generate_injected.js"))) + (add-after 'generate-injected-scripts 'add-build-script + (lambda _ + ;; Upstream uses Babel, but Babel isn't packaged. Use esbuild. + ;; Compile TypeScript and copy non-TS files (JS, JSON, PNG). + (modify-json + (lambda (pkg) + (acons "scripts" + '(("build" . "find src -name '*.ts' | xargs esbuild --outdir=lib --format=cjs --platform=node && cp -r src/third_party lib/ && find src \\( -name '*.json' -o -name '*.png' \\) -exec sh -c 'mkdir -p lib/$(dirname ${0#src/}) && cp $0 lib/${0#src/}' {} \\;")) + pkg))))) + (add-before 'install 'set-cc + (lambda _ + (setenv "CC" #$(cc-for-target)))) + (add-after 'install 'wrap-playwright-cli + (lambda* (#:key inputs outputs #:allow-other-keys) + (let* ((out (assoc-ref outputs "out")) + (cli (string-append out "/lib/node_modules/playwright-core/cli.js"))) + (wrap-program cli + ;; Avoid vite dependency. + `("PW_CODEGEN_NO_INSPECTOR" = ("1")) + `("PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD" = ("1"))))))))) + (inputs + (list node-esbuild + ;; Dependencies (unbundled from utilsBundle). + node-colors + node-commander + node-debug + node-diff + node-dotenv + node-graceful-fs + node-https-proxy-agent + node-jpeg-js + node-mime + node-minimatch-3 + node-ms + node-open + node-pngjs + node-progress + node-proper-lockfile + node-proxy-from-env + node-retry + node-signal-exit + node-socks-proxy-agent + node-stack-utils + node-ws + node-yaml + ;; Dependencies (unbundled from zipBundle). + node-yazl + node-yauzl + node-extract-zip)) + (native-inputs + (list esbuild python)) + (native-search-paths + (list + (search-path-specification + (variable "PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH") + (file-type 'regular) + (separator #f) ;single entry + (files '("bin/chromium"))) + (search-path-specification + (variable "PWTEST_CLI_EXECUTABLE_PATH") + (file-type 'regular) + (separator #f) ;single entry + (files '("bin/chromium"))))) + (home-page "https://playwright.dev/") + (synopsis "Browser automation library (Chromium-only)") + (description + "Playwright is a framework for browser automation and end-to-end testing. +This package provides @code{playwright-core}, the library without bundled +browsers. + +@strong{Important}: This package only supports Chromium-based browsers. +Firefox and WebKit are not supported because Playwright requires custom-patched +versions of those browsers that are not packaged for Guix. + +To use Playwright with the system Chromium, either: +@enumerate +@item +Set the environment variable @env{PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH} to the +Chromium binary path, or +@item +Use the @code{executablePath} option in your Playwright code: +@example +const browser = await chromium.launch(@{ + executablePath: '/run/current-system/profile/bin/chromium' +@}); +@end example + +For the command line variant $code{playwright-core}, set the environment +variable $env{PWTEST_CLI_EXECUTABLE_PATH} to the Chromium binary path. +@end enumerate") + (license license:asl2.0)))