Prompt step by step for the missing base image, ISO, npm deps and Chromium

Offer to build the base QCOW2, download the ISO, run npm ci and install
Chromium (from the npmmirror mirror by default). Let Playwright locate its
own Chromium so macOS works.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
guofangandClaude Sonnet 5.5 committed 2026-11-02 20:53:50 +08:00
1 parent b37d1e002c
commit 1476b4a3de
3 files changed
+77 -13

No files matched your search

+13
View File
@@ -31,6 +31,8 @@ set -euo pipefail
images_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" images_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
downloader="${images_dir}/download-images.sh" downloader="${images_dir}/download-images.sh"
# Path lookups below must not offer to run this script again.
export VM_IMAGE_NO_PROMPT=1
win_dir="${images_dir}/windows11" win_dir="${images_dir}/windows11"
usage() { usage() {
@@ -116,6 +118,17 @@ if ((!from_img)); then
|| die "install socat or nc (it presses a key at the boot prompt of the ISO)." || die "install socat or nc (it presses a key at the boot prompt of the ISO)."
fi fi
# Without the ISO, offer to download it (needs a terminal).
if [[ -z "${WIN11_VM_INSTALL_ISO:-}" ]] && ! "$downloader" --print-path windows11 --iso >/dev/null 2>&1; then
"$downloader" --print-path windows11 --iso >&2 || true
if { : </dev/tty >/dev/tty; } 2>/dev/null; then
printf 'Download the Windows 11 ISO now? [y/N] ' >/dev/tty
read -r answer </dev/tty || answer=
[[ "$answer" == [yY] || "$answer" == [yY][eE][sS] ]] || exit 1
"$downloader" windows11 || exit 1
fi
fi
output="${output:-$("$downloader" --print-path windows11)}" output="${output:-$("$downloader" --print-path windows11)}"
if [[ -e "$output" && $force -eq 0 ]]; then if [[ -e "$output" && $force -eq 0 ]]; then
die "${output} exists (it may be the backing file of existing VM disks); use --force or another OUTPUT." die "${output} exists (it may be the backing file of existing VM disks); use --force or another OUTPUT."
+52 -2
View File
@@ -156,6 +156,36 @@ The Windows ISO is found automatically in install-iso/ by matching
EOF EOF
} }
# Ask a yes/no question on the terminal; fails when there is none or the answer
# is not yes. stdout carries the printed path, so all dialogue goes to /dev/tty.
ask_yes_no() {
local answer
{ : </dev/tty >/dev/tty; } 2>/dev/null || return 1
printf '%s [y/N] ' "$1" >/dev/tty
read -r answer </dev/tty || return 1
[[ "$answer" == [yY] || "$answer" == [yY][eE][sS] ]]
}
# Print the base QCOW2 path. When the image does not exist and a terminal is
# attached, offer to run build-windows-base.sh, which in turn offers to download
# the ISO. The builder sets VM_IMAGE_NO_PROMPT so its own lookups never ask.
windows_base_path() {
local qcow2 builder="${images_dir}/build-windows-base.sh"
if qcow2="$(windows_qcow2_path 2>/dev/null)" && [[ -f "$qcow2" ]]; then
printf '%s\n' "$qcow2"
return 0
fi
windows_qcow2_path >/dev/null || true
if [[ -z "${VM_IMAGE_NO_PROMPT:-}" && -x "$builder" ]] && ask_yes_no "Build the Windows 11 base image now with build-windows-base.sh?"; then
# stdout carries the path, so the build's output goes to the terminal.
"$builder" >/dev/tty 2>&1 </dev/tty || return 1
windows_qcow2_path
return
fi
# Name the expected path if the ISO fixes it; the caller reports it missing.
windows_qcow2_path 2>/dev/null
}
print_image_path() { print_image_path() {
local target="$1" local target="$1"
local media="${2:-qcow2}" local media="${2:-qcow2}"
@@ -171,7 +201,7 @@ print_image_path() {
if [[ "$media" == iso ]]; then if [[ "$media" == iso ]]; then
windows_iso_path windows_iso_path
else else
windows_qcow2_path windows_base_path
fi fi
;; ;;
*) *)
@@ -416,7 +446,27 @@ download_windows_iso() {
printf 'Error: node is required to get the Windows 11 ISO URL.\n' >&2 printf 'Error: node is required to get the Windows 11 ISO URL.\n' >&2
exit 1 exit 1
fi fi
url="$(node "${images_dir}/nodejs/get-windows-iso-url.js" --arch "$windows_arch")" if [[ ! -d "${images_dir}/nodejs/node_modules/playwright-core" ]]; then
printf 'Error: the Node.js dependencies are not installed (npm ci has not been run in %s/nodejs).\n' \
"$images_dir" >&2
command -v npm >/dev/null 2>&1 || { printf 'Error: npm is required; install Node.js with npm.\n' >&2; exit 1; }
ask_yes_no "Run npm ci in ${images_dir}/nodejs now?" || exit 1
(cd "${images_dir}/nodejs" && npm ci) || exit 1
fi
local status=0
url="$(node "${images_dir}/nodejs/get-windows-iso-url.js" --arch "$windows_arch")" || status=$?
if ((status == 2)) && [[ -z "${CHROME_PATH:-}" ]]; then
# Exit status 2 means Chromium was not found (the script has said so).
ask_yes_no "Install Chromium with playwright-core now?" || exit 1
# Default to the npmmirror mirror (the official CDN is slow in China); set
# PLAYWRIGHT_DOWNLOAD_HOST to override it.
(cd "${images_dir}/nodejs" \
&& PLAYWRIGHT_DOWNLOAD_HOST="${PLAYWRIGHT_DOWNLOAD_HOST:-https://cdn.npmmirror.com/binaries/playwright}" \
npx playwright-core install chromium) || exit 1
url="$(node "${images_dir}/nodejs/get-windows-iso-url.js" --arch "$windows_arch")" || exit 1
elif ((status != 0)); then
exit "$status"
fi
[[ -n "$url" ]] || { printf 'Error: no Windows 11 ISO URL.\n' >&2; exit 1; } [[ -n "$url" ]] || { printf 'Error: no Windows 11 ISO URL.\n' >&2; exit 1; }
name="${url%%\?*}" name="${url%%\?*}"
name="${name##*/}" name="${name##*/}"
+12 -11
View File
@@ -5,12 +5,11 @@
// Usage: node get-windows-iso-url.js [--arch x64|arm64] [--lang NAME] // Usage: node get-windows-iso-url.js [--arch x64|arm64] [--lang NAME]
// Environment: // Environment:
// PLAYWRIGHT_CORE playwright-core directory (default: node_modules here) // PLAYWRIGHT_CORE playwright-core directory (default: node_modules here)
// CHROME_PATH Chromium executable (default: newest one in ~/.cache/ms-playwright) // CHROME_PATH Chromium executable (default: the one Playwright installed)
// The URL expires after about 24 hours and may be bound to the IP that // The URL expires after about 24 hours and may be bound to the IP that
// requested it. Only the URL goes to stdout; logs go to stderr. // requested it. Only the URL goes to stdout; logs go to stderr.
const fs = require('fs'); const fs = require('fs');
const os = require('os');
const path = require('path'); const path = require('path');
const args = process.argv.slice(2); const args = process.argv.slice(2);
@@ -28,8 +27,7 @@ Options:
Environment: Environment:
PLAYWRIGHT_CORE playwright-core directory (default: node_modules here) PLAYWRIGHT_CORE playwright-core directory (default: node_modules here)
CHROME_PATH Chromium executable (default: newest one in CHROME_PATH Chromium executable (default: the one Playwright installed)
~/.cache/ms-playwright)
Examples: Examples:
node get-windows-iso-url.js --arch x64 --lang "Chinese Simplified" node get-windows-iso-url.js --arch x64 --lang "Chinese Simplified"
@@ -60,17 +58,20 @@ function findPlaywright() {
process.exit(2); process.exit(2);
} }
function findChrome() { // CHROME_PATH wins; otherwise Playwright picks the Chromium it installed (in
if (process.env.CHROME_PATH) return process.env.CHROME_PATH; // its own per-OS cache, PLAYWRIGHT_BROWSERS_PATH if set).
const root = path.join(os.homedir(), '.cache/ms-playwright'); function launchOptions(chromium) {
const dirs = fs.existsSync(root) ? fs.readdirSync(root).filter(d => /^chromium-\d+$/.test(d)).sort() : []; if (process.env.CHROME_PATH) return { executablePath: process.env.CHROME_PATH, headless: true };
if (!dirs.length) { console.error('Chromium not found: set CHROME_PATH or run npx playwright-core install chromium'); process.exit(2); } if (!fs.existsSync(chromium.executablePath())) {
return path.join(root, dirs.pop(), 'chrome-linux64/chrome'); console.error('Chromium not found: set CHROME_PATH or run npx playwright-core install chromium');
process.exit(2);
}
return { headless: true };
} }
(async () => { (async () => {
const { chromium } = require(findPlaywright()); const { chromium } = require(findPlaywright());
const browser = await chromium.launch({ executablePath: findChrome(), headless: true }); const browser = await chromium.launch(launchOptions(chromium));
try { try {
const ctx = await browser.newContext({ const ctx = await browser.newContext({
locale: 'en-US', locale: 'en-US',