File size: 3,472 Bytes
8e9d829
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// Which Chromium the browser-level tests should drive.
//
// `chromium.launch()` with no executablePath demands the EXACT build the
// installed Playwright expects, and fails with "Looks like Playwright was just
// installed or updated / npx playwright install" if the machine has any other
// revision. That is a real failure mode here rather than a hypothetical: this
// workspace ships a shared browser under PLAYWRIGHT_BROWSERS_PATH (rev 1208 at
// the time of writing) while `playwright@^1.62` asks for 1234, so every browser
// suite died before running a line of app code — and the message blames a missing
// install rather than a mismatch, which sends you off to download 115MB you
// already have.
//
// Order of preference:
//   1. PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH — an explicit answer always wins.
//   2. the newest chromium under PLAYWRIGHT_BROWSERS_PATH, whatever its revision.
//   3. undefined — let Playwright resolve its own download, which is what CI and
//      an ordinary dev machine want. The error, if any, stays Playwright's.
//
// Driving a revision Playwright wasn't built against is a deliberate trade: these
// suites use long-stable APIs (newPage, setContent, addScriptTag, keyboard,
// screenshots), and a browser that is one build off is worth far more than a
// suite nobody can run.
import fs from 'node:fs';
import path from 'node:path';

// Full chromium first: it can run headed as well as headless, where
// chrome-headless-shell cannot. Both are accepted — the shell alone is enough for
// the headless suites, and on some images it is all that was fetched.
const FAMILIES = [
  { dir: /^chromium-(\d+)$/, exe: ['chrome-linux64/chrome', 'chrome-linux/chrome', 'chrome-mac/Chromium.app/Contents/MacOS/Chromium'] },
  { dir: /^chromium_headless_shell-(\d+)$/, exe: ['chrome-headless-shell-linux64/chrome-headless-shell', 'chrome-headless-shell-linux/chrome-headless-shell'] },
];

export function chromiumExecutablePath() {
  const explicit = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH;
  if (explicit) return fs.existsSync(explicit) ? explicit : undefined;

  const root = process.env.PLAYWRIGHT_BROWSERS_PATH;
  if (!root || !fs.existsSync(root)) return undefined;

  let entries;
  try { entries = fs.readdirSync(root); } catch { return undefined; }
  for (const family of FAMILIES) {
    const found = entries
      .map((name) => ({ name, rev: Number((family.dir.exec(name) || [])[1]) }))
      .filter((e) => Number.isFinite(e.rev))
      .sort((a, b) => b.rev - a.rev);                 // newest revision present
    for (const { name } of found) {
      for (const rel of family.exe) {
        const p = path.join(root, name, rel);
        if (fs.existsSync(p)) return p;
      }
    }
  }
  return undefined;
}

/**
 * Launch options for a headless run. Spread into `chromium.launch({...})`.
 *
 * `--no-sandbox` because these suites run inside a container as a non-root user
 * with no user namespaces, where Chromium's sandbox cannot initialise and the
 * browser exits immediately. It is scoped to tests driving content this repo
 * generated itself.
 */
export function chromiumLaunchOptions(extra = {}) {
  const executablePath = chromiumExecutablePath();
  return {
    headless: true,
    ...(executablePath ? { executablePath } : {}),
    args: ['--no-sandbox', '--disable-dev-shm-usage', ...(extra.args || [])],
    ...Object.fromEntries(Object.entries(extra).filter(([k]) => k !== 'args')),
  };
}