mirror of
https://github.com/lackeyjb/playwright-skill.git
synced 2026-09-14 16:39:27 +08:00
ca6b7e7ec8
* feat: modernize agent skill for v5
* fix: address review findings for v5 refresh
- Restore $SKILL_DIR path resolution in SKILL.md with explicit export
- Fix -e stdout/stderr truncation at 64KB pipe buffer (flush both before exit)
- Spawn child in caller's cwd so relative paths resolve against the project
- Re-raise signals so shells see interrupt status (SIGINT -> 130)
- Escalate to SIGKILL after 2s when child traps SIGINT/SIGTERM
- handleCookieBanner: .filter({ visible: true }).first() avoids hidden matches
- launchBrowser: --no-sandbox only for Chromium as root, merged with caller args
- takeScreenshot: skip mkdirSync when custom path given
- detectDevServers: numeric sort for customPorts of varying digit counts
- PW_HEADLESS='' now falls through to visible default
- SLOW_MO non-numeric input maps to 0 instead of NaN
- CI: npm ci, smoke via run.js, validate path corrected
- Tests: 4 new executor cases + stderr flush + cwd preservation + smoke cookie banner
110 lines
4.3 KiB
JavaScript
110 lines
4.3 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const { spawnSync } = require('node:child_process');
|
|
|
|
const skillDir = path.resolve(__dirname, '../skills/playwright-skill');
|
|
|
|
test('file scripts can load skill helpers', () => {
|
|
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'playwright-skill-'));
|
|
const script = path.join(directory, 'helper-check.js');
|
|
fs.writeFileSync(script, "const helpers = require(`${process.env.PW_SKILL_DIR}/lib/helpers`); console.log(typeof helpers.detectDevServers);");
|
|
|
|
const result = spawnSync(process.execPath, [path.join(skillDir, 'run.js'), script], {
|
|
cwd: skillDir,
|
|
encoding: 'utf8',
|
|
});
|
|
|
|
assert.equal(result.status, 0, result.stderr);
|
|
assert.match(result.stdout, /function/);
|
|
fs.rmSync(directory, { recursive: true, force: true });
|
|
});
|
|
|
|
test('throwing scripts propagate a non-zero exit code', () => {
|
|
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'playwright-skill-'));
|
|
const script = path.join(directory, 'throw-check.js');
|
|
fs.writeFileSync(script, "throw new Error('boom');");
|
|
|
|
const result = spawnSync(process.execPath, [path.join(skillDir, 'run.js'), script], { encoding: 'utf8' });
|
|
|
|
assert.notEqual(result.status, 0);
|
|
assert.match(result.stderr, /boom/);
|
|
fs.rmSync(directory, { recursive: true, force: true });
|
|
});
|
|
|
|
test('missing scripts fail with a non-zero exit code', () => {
|
|
const result = spawnSync(process.execPath, [path.join(skillDir, 'run.js'), '/nonexistent/missing.js'], { encoding: 'utf8' });
|
|
|
|
assert.notEqual(result.status, 0);
|
|
assert.match(result.stderr, /Script not found/);
|
|
});
|
|
|
|
test('inline execution exits even when handles stay open', () => {
|
|
const result = spawnSync(process.execPath, [path.join(skillDir, 'run.js'), '-e', "setInterval(() => {}, 1000); console.log('inline-ok');"], {
|
|
encoding: 'utf8',
|
|
timeout: 15000,
|
|
});
|
|
|
|
assert.equal(result.status, 0, result.stderr);
|
|
assert.match(result.stdout, /inline-ok/);
|
|
});
|
|
|
|
test('inline execution flushes output larger than the pipe buffer', () => {
|
|
const size = 200000;
|
|
const result = spawnSync(process.execPath, [path.join(skillDir, 'run.js'), '-e', `console.log('x'.repeat(${size}));`], {
|
|
encoding: 'utf8',
|
|
timeout: 15000,
|
|
maxBuffer: 10 * 1024 * 1024,
|
|
});
|
|
|
|
assert.equal(result.status, 0, result.stderr);
|
|
assert.equal(result.stdout.length, size + 1);
|
|
});
|
|
|
|
test('inline execution flushes stderr larger than the pipe buffer', () => {
|
|
const size = 200000;
|
|
const result = spawnSync(process.execPath, [path.join(skillDir, 'run.js'), '-e', `console.error('y'.repeat(${size}));`], {
|
|
encoding: 'utf8',
|
|
timeout: 15000,
|
|
maxBuffer: 10 * 1024 * 1024,
|
|
});
|
|
|
|
assert.equal(result.status, 0);
|
|
assert.equal(result.stderr.length, size + 1);
|
|
});
|
|
|
|
test('scripts keep the caller working directory', () => {
|
|
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'playwright-skill-'));
|
|
const script = path.join(directory, 'cwd-check.js');
|
|
fs.writeFileSync(script, 'console.log(process.cwd());');
|
|
|
|
const result = spawnSync(process.execPath, [path.join(skillDir, 'run.js'), script], {
|
|
cwd: directory,
|
|
encoding: 'utf8',
|
|
});
|
|
|
|
assert.equal(result.status, 0, result.stderr);
|
|
assert.equal(result.stdout.trim(), fs.realpathSync(directory));
|
|
fs.rmSync(directory, { recursive: true, force: true });
|
|
});
|
|
|
|
test('PW_SCRIPT_DIR preserves scripts and avoids collisions', () => {
|
|
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'playwright-skill-'));
|
|
const saveDir = path.join(directory, 'saved');
|
|
const script = path.join(directory, 'save-me.js');
|
|
fs.writeFileSync(script, "console.log('saved');");
|
|
|
|
const runArgs = { encoding: 'utf8', env: { ...process.env, PW_SCRIPT_DIR: saveDir } };
|
|
const first = spawnSync(process.execPath, [path.join(skillDir, 'run.js'), script], runArgs);
|
|
const second = spawnSync(process.execPath, [path.join(skillDir, 'run.js'), script], runArgs);
|
|
|
|
assert.equal(first.status, 0, first.stderr);
|
|
assert.equal(second.status, 0, second.stderr);
|
|
const saved = fs.readdirSync(saveDir);
|
|
assert.ok(saved.includes('save-me.js'), saved.join(', '));
|
|
assert.equal(saved.filter(name => name.startsWith('save-me')).length, 2, saved.join(', '));
|
|
fs.rmSync(directory, { recursive: true, force: true });
|
|
});
|