← Files Device ItARCHIVED FILE
tests/setup.test.mjs
7.56 KB · Nov 6, 2026 · 18:17 UTC
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import { mkdirSync, writeFileSync } from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { dependencyState, setup, runCommand } from '../scripts/setup.mjs';
async function fixture(t, installed = true) {
const directory = await fs.mkdtemp(path.join(os.tmpdir(), 'device-it-setup-test-'));
t.after(() => fs.rm(directory, { recursive: true, force: true }));
await fs.writeFile(path.join(directory, 'package.json'), JSON.stringify({ dependencies: { fixture: '1.0.0' } }));
await fs.writeFile(path.join(directory, 'package-lock.json'), JSON.stringify({ packages: { '': { dependencies: { fixture: '1.0.0' } }, 'node_modules/fixture': { version: '1.0.0' }, 'node_modules/optional-fixture': { version: '1.0.0', optional: true } } }));
const install = (version = '1.0.0') => {
mkdirSync(path.join(directory, 'node_modules/fixture'), { recursive: true });
writeFileSync(path.join(directory, 'node_modules/fixture/package.json'), JSON.stringify({ version }));
};
if (installed) install();
const commands = [];
const run = (command, args, cwd, mutation) => {
commands.push({ command, args, cwd, mutation });
if (command === 'npm' && args[0] === '--version') return { status: 0, stdout: '10.9.4\n' };
if (command === 'npm' && args[0] === 'ci') install();
return { status: 0, stdout: '' };
};
return { directory, run, commands, writePackage: install };
}
const ready = async () => ({ ok: true, phase: 'browser', browserClosed: true, version: 'fixture' });
test('installation disables browser cache deletion without changing parent environment', async t => {
const f = await fixture(t);
const output = path.join(f.directory, 'child-cache-setting.txt');
const before = process.env.PLAYWRIGHT_SKIP_BROWSER_GC;
const result = runCommand(process.execPath, ['-e', "require('node:fs').writeFileSync(process.argv[1], process.env.PLAYWRIGHT_SKIP_BROWSER_GC)", output], f.directory, true);
assert.equal(result.status, 0);
assert.equal(await fs.readFile(output, 'utf8'), '1');
assert.equal(process.env.PLAYWRIGHT_SKIP_BROWSER_GC, before);
});
test('readiness checks full lock versions and tolerates absent optional packages', async t => {
const f = await fixture(t);
assert.deepEqual(await dependencyState(f.directory), { ok: true, checkedPackages: 2, issues: [] });
f.writePackage('2.0.0');
const state = await dependencyState(f.directory);
assert.equal(state.ok, false);
assert.match(state.issues[0], /expected 1.0.0, found 2.0.0/);
});
test('lock mismatch and node_modules symlink refuse installation', async t => {
const f = await fixture(t, false);
await fs.writeFile(path.join(f.directory, 'package.json'), JSON.stringify({ dependencies: { fixture: '2.0.0' } }));
let result = await setup({ ...f, install: true, probe: ready });
assert.equal(result.ok, false);
assert.match(result.error, /Package and lock disagree/);
assert.equal(result.actions.length, 0);
await fs.writeFile(path.join(f.directory, 'package.json'), JSON.stringify({ dependencies: { fixture: '1.0.0' } }));
await fs.symlink(os.tmpdir(), path.join(f.directory, 'node_modules'), 'dir');
result = await setup({ ...f, install: true, probe: ready });
assert.match(result.error, /node_modules is a symlink/);
assert.equal(result.actions.length, 0);
});
test('old Node and missing npm give prerequisites without downloads', async t => {
const f = await fixture(t);
const old = await setup({ ...f, install: true, nodeVersion: '20.18.0', probe: ready });
assert.equal(old.ok, false);
assert.match(old.nextSteps[0], /Node.js 22/);
assert.equal(f.commands.length, 0);
const missing = await setup({ ...f, install: true, run: () => ({ status: null, error: { code: 'ENOENT' } }), probe: ready });
assert.equal(missing.ok, false);
assert.equal(missing.runtime.npm, 'unavailable');
assert.equal(missing.actions.length, 0);
});
test('preflight missing dependencies is read-only', async t => {
const f = await fixture(t, false);
const result = await setup({ ...f, probe: async () => { assert.fail('Browser must not run with missing dependencies'); } });
assert.equal(result.ok, false);
assert.match(result.nextSteps[0], /run.mjs setup/);
assert.equal(f.commands.length, 1);
await assert.rejects(fs.stat(path.join(f.directory, 'node_modules')), { code: 'ENOENT' });
});
test('first setup installs once and second setup reuses ready dependencies/browser', async t => {
const f = await fixture(t, false);
const first = await setup({ ...f, install: true, probe: ready });
assert.equal(first.ok, true);
assert.equal(first.actions.length, 1);
assert.deepEqual(f.commands[1].args, ['ci', '--ignore-scripts', '--no-audit', '--no-fund', '--include=dev', '--include=optional']);
assert.equal(f.commands[1].cwd, f.directory);
assert.equal(f.commands[1].mutation, true);
const second = await setup({ ...f, install: true, probe: ready });
assert.equal(second.ok, true);
assert.deepEqual(second.actions, []);
assert.equal(f.commands.filter(x => x.mutation).length, 1);
});
test('matching package versions with broken imports are repaired once', async t => {
const f = await fixture(t);
let probes = 0;
const result = await setup({ ...f, install: true, probe: async () => ++probes === 1 ? { ok: false, phase: 'dependencies', missing: false, error: 'Missing module file' } : ready() });
assert.equal(result.ok, true);
assert.equal(probes, 2);
assert.equal(result.actions.length, 1);
});
test('missing Chromium downloads only from pinned local CLI and is rechecked', async t => {
const f = await fixture(t);
let probes = 0;
const result = await setup({ ...f, install: true, probe: async () => ++probes === 1 ? { ok: false, phase: 'browser', missing: true, error: "Executable doesn't exist at fixture" } : ready() });
assert.equal(result.ok, true);
assert.equal(probes, 2);
assert.equal(result.actions.length, 1);
assert.deepEqual(f.commands[1].args, [path.join(f.directory, 'node_modules/playwright/cli.js'), 'install', 'chromium']);
});
test('missing browser preflight and OS launch failure never install', async t => {
const f = await fixture(t);
const missing = await setup({ ...f, probe: async () => ({ ok: false, phase: 'browser', missing: true, error: 'missing executable' }) });
assert.equal(missing.ok, false);
assert.match(missing.nextSteps[0], /setup/);
const broken = await setup({ ...f, install: true, probe: async () => ({ ok: false, phase: 'browser', missing: false, error: 'Missing OS library fixture' }) });
assert.equal(broken.ok, false);
assert.equal(broken.browser.error, 'Missing OS library fixture');
assert.match(broken.nextSteps[0], /never installs OS packages/);
assert.deepEqual(broken.actions, []);
});
test('failed dependency/browser install stops with truthful non-ready result', async t => {
const f = await fixture(t, false);
const failureRun = (command, args, cwd, mutation) => mutation ? { status: 1 } : f.run(command, args, cwd, mutation);
const failed = await setup({ ...f, install: true, run: failureRun, probe: async () => { assert.fail('Failed install must stop before browser'); } });
assert.equal(failed.ok, false);
assert.match(failed.error, /dependency installation failed/);
assert.equal(failed.actions.length, 1);
f.writePackage();
const browserFailed = await setup({ ...f, install: true, run: failureRun, probe: async () => ({ ok: false, phase: 'browser', missing: true }) });
assert.equal(browserFailed.ok, false);
assert.match(browserFailed.error, /Chromium download failed/);
assert.equal(browserFailed.actions.length, 1);
});
SHA-256: 44deaf886cb23f6983afbe750f352aca67374692fadb8893852bf9139eff1421