mirror of
https://github.com/proffesor-for-testing/agentic-qe.git
synced 2026-09-19 08:45:47 +08:00
3eb9ccc461
M1 visual-diff threshold: documented "max difference fraction" convention matching Playwright maxDiffPixelRatio (was inconsistent in docs/code). M2 fixture server bind: default to 127.0.0.1 instead of 0.0.0.0; QE_BROWSER_FIXTURE_HOST=0.0.0.0 opt-in for explicit external access. Codespaces auto-forwards 0.0.0.0 ports — this was an unintentional open server every time the eval ran. M3 path-traversal guard: replaced startsWith() with path.relative() + '..'/absolute check. The old guard false-passed sibling-prefix dirs (/foo vs /foo2) and was fragile on Windows mixed separators. M4 ANSI escape injection: added sanitizeSnippet() that strips C0 controls (0x00-0x1F except \t\n) and DEL (0x7F) from every check-injection finding snippet. Prevents `cat findings.json` from triggering terminal control sequences embedded in malicious page content. M5 parseArgs --key=value: split on first '=' so --threshold=0.05 works alongside --threshold 0.05. Only the first '=' is split so URL/base64 values survive intact. M6 batch.js pre-validation: added validateAllSteps() that walks every step's required fields BEFORE the first vibium call. Step 17 typo now aborts before steps 1-16 execute their side effects. M7 intent-score bare-x regex: anchored \bx\b in close_dialog scorer so "fix", "exit", "extra", "sixteen" no longer match. Unicode × and ✕ unchanged. M8 check-injection --exclude-selector: new flag drops subtrees from the scan. Page text is read from a CLONE of <body> with matching elements removed — live page unchanged. Lets docs sites scan their own pages without self-flagging documentation about prompt injection. M9 09-assets vibium catch: wrapped raw error with actionable recovery guidance (`npm install -g vibium`, then re-run `aqe init`). Hidden bug found and fixed during M2/M3 testing: fixtures/serve-skills.js existed but had never been run because the repo root is ESM and the file uses CommonJS require(). Added scoped fixtures/package.json with "type": "commonjs" matching the existing scripts/package.json pattern. Tests: 86 unit tests across 7 files (was 50). All pass. Smoke test: 9/9 still passing against real Vibium v26.3.18. Mirrored .claude/skills/qe-browser → assets/skills/qe-browser. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
113 lines
4.1 KiB
TypeScript
113 lines
4.1 KiB
TypeScript
/**
|
|
* qe-browser: batch.js unit tests
|
|
*
|
|
* Tests the M6 pre-validation pass and the dispatch contract. The actual
|
|
* vibium calls are not exercised here — those go through the smoke test.
|
|
*/
|
|
|
|
import { describe, it, expect } from 'vitest';
|
|
import { createRequire } from 'module';
|
|
|
|
const require = createRequire(import.meta.url);
|
|
const batch = require('../../../.claude/skills/qe-browser/scripts/batch.js');
|
|
|
|
describe('qe-browser batch', () => {
|
|
describe('VALID_ACTIONS', () => {
|
|
it('exposes the documented action whitelist', () => {
|
|
expect(batch.VALID_ACTIONS.has('go')).toBe(true);
|
|
expect(batch.VALID_ACTIONS.has('click')).toBe(true);
|
|
expect(batch.VALID_ACTIONS.has('fill')).toBe(true);
|
|
expect(batch.VALID_ACTIONS.has('assert')).toBe(true);
|
|
expect(batch.VALID_ACTIONS.has('typo_action')).toBe(false);
|
|
});
|
|
});
|
|
|
|
// M6 regression — devil's-advocate finding:
|
|
// batch.js originally validated each step lazily during dispatch, so a
|
|
// typo in step 17 surfaced AFTER steps 1-16 had already executed.
|
|
// validateAllSteps() now walks every step's required fields BEFORE the
|
|
// first vibium call.
|
|
describe('M6: validateStep / validateAllSteps', () => {
|
|
it('passes a well-formed go step', () => {
|
|
expect(batch.validateStep({ action: 'go', url: 'https://example.com' }, 0)).toBeNull();
|
|
});
|
|
|
|
it('flags missing url on go', () => {
|
|
const err = batch.validateStep({ action: 'go' }, 0);
|
|
expect(err).toMatch(/missing "url"/);
|
|
expect(err).toContain('step 0');
|
|
});
|
|
|
|
it('flags an unknown action', () => {
|
|
const err = batch.validateStep({ action: 'clikc', selector: '#x' }, 3);
|
|
expect(err).toMatch(/unknown action "clikc"/);
|
|
expect(err).toContain('step 3');
|
|
});
|
|
|
|
it('flags click without ref or selector', () => {
|
|
const err = batch.validateStep({ action: 'click' }, 1);
|
|
expect(err).toMatch(/missing "ref" or "selector"/);
|
|
});
|
|
|
|
it('accepts click with ref', () => {
|
|
expect(batch.validateStep({ action: 'click', ref: '@e1' }, 0)).toBeNull();
|
|
});
|
|
|
|
it('accepts click with selector', () => {
|
|
expect(batch.validateStep({ action: 'click', selector: 'button' }, 0)).toBeNull();
|
|
});
|
|
|
|
it('flags fill without text (even when target is set)', () => {
|
|
const err = batch.validateStep({ action: 'fill', selector: '#email' }, 2);
|
|
expect(err).toMatch(/"text" must be a string/);
|
|
});
|
|
|
|
it('accepts fill with text=""', () => {
|
|
// Empty string is a legitimate value (clear the input).
|
|
expect(
|
|
batch.validateStep({ action: 'fill', selector: '#email', text: '' }, 0)
|
|
).toBeNull();
|
|
});
|
|
|
|
it('flags assert without checks array', () => {
|
|
const err = batch.validateStep({ action: 'assert' }, 5);
|
|
expect(err).toMatch(/"checks" must be an array/);
|
|
});
|
|
|
|
it('accepts assert with empty checks array', () => {
|
|
// No checks is a no-op but not an error — keep dispatch consistent.
|
|
expect(batch.validateStep({ action: 'assert', checks: [] }, 0)).toBeNull();
|
|
});
|
|
|
|
it('flags non-object steps', () => {
|
|
const err = batch.validateStep(null, 0);
|
|
expect(err).toMatch(/must be an object/);
|
|
});
|
|
|
|
it('validateAllSteps returns errors from EVERY bad step', () => {
|
|
const steps = [
|
|
{ action: 'go', url: 'https://example.com' },
|
|
{ action: 'click' }, // missing target
|
|
{ action: 'unknown_op' }, // unknown action
|
|
{ action: 'fill', selector: '#x' }, // missing text
|
|
];
|
|
const errors = batch.validateAllSteps(steps);
|
|
expect(errors.length).toBe(3);
|
|
expect(errors[0]).toContain('step 1');
|
|
expect(errors[1]).toContain('step 2');
|
|
expect(errors[2]).toContain('step 3');
|
|
});
|
|
|
|
it('validateAllSteps returns [] for an all-valid plan', () => {
|
|
const steps = [
|
|
{ action: 'go', url: 'https://example.com' },
|
|
{ action: 'fill', selector: '#email', text: 'a@b.c' },
|
|
{ action: 'click', ref: '@e1' },
|
|
{ action: 'wait_url', pattern: '/dashboard' },
|
|
{ action: 'assert', checks: [{ kind: 'no_console_errors' }] },
|
|
];
|
|
expect(batch.validateAllSteps(steps)).toEqual([]);
|
|
});
|
|
});
|
|
});
|