mirror of
https://github.com/garrytan/gstack.git
synced 2026-08-05 13:58:37 +02:00
feat: QA restructure, browser ref staleness, eval efficiency metrics (v0.4.0) (#83)
* feat: browser ref staleness detection via async count() validation resolveRef() now checks element count to detect stale refs after page mutations (e.g. SPA navigation). RefEntry stores role+name metadata for better diagnostics. 3 new snapshot tests for staleness detection. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: qa-only skill, qa fix loop, plan-to-QA artifact flow Add /qa-only (report-only, Edit tool blocked), restructure /qa with find-fix-verify cycle, add {{QA_METHODOLOGY}} DRY placeholder for shared methodology. /plan-eng-review now writes test-plan artifacts to ~/.gstack/projects/<slug>/ for QA consumption. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: eval efficiency metrics — turns, duration, commentary across all surfaces Add generateCommentary() for natural-language delta interpretation, per-test turns/duration in comparison and summary output, judgePassed unit tests, 3 new E2E tests (qa-only, qa fix loop, plan artifact). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: bump version and changelog (v0.4.0) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: update ARCHITECTURE, BROWSER, CONTRIBUTING, README for v0.4.0 - ARCHITECTURE: add ref staleness detection section, update RefEntry type - BROWSER: add ref staleness paragraph to snapshot system docs - CONTRIBUTING: update eval tool descriptions with commentary feature - README: fix missing qa-only in project-local uninstall command Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add user-facing benefit descriptions to v0.4.0 changelog Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
bb46ca6b21
commit
f3ee0ee28a
@@ -201,6 +201,55 @@ describe('Ref invalidation', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// ─── Ref Staleness Detection ────────────────────────────────────
|
||||
|
||||
describe('Ref staleness detection', () => {
|
||||
test('ref metadata stores role and name', async () => {
|
||||
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
||||
await handleMetaCommand('snapshot', ['-i'], bm, shutdown);
|
||||
// Refs should exist with metadata
|
||||
expect(bm.getRefCount()).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('stale ref after DOM removal gives descriptive error', async () => {
|
||||
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
||||
const snap = await handleMetaCommand('snapshot', ['-i'], bm, shutdown);
|
||||
// Find a button ref
|
||||
const buttonLine = snap.split('\n').find(l => l.includes('[button]') && l.includes('"Submit"'));
|
||||
expect(buttonLine).toBeDefined();
|
||||
const refMatch = buttonLine!.match(/@(e\d+)/);
|
||||
expect(refMatch).toBeDefined();
|
||||
const ref = `@${refMatch![1]}`;
|
||||
|
||||
// Remove the button from DOM (simulates SPA re-render)
|
||||
await handleReadCommand('js', ['document.querySelector("button[type=submit]").remove()'], bm);
|
||||
|
||||
// Try to click — should get descriptive staleness error
|
||||
try {
|
||||
await handleWriteCommand('click', [ref], bm);
|
||||
expect(true).toBe(false); // Should not reach here
|
||||
} catch (err: any) {
|
||||
expect(err.message).toContain('stale');
|
||||
expect(err.message).toContain('button');
|
||||
expect(err.message).toContain('Submit');
|
||||
expect(err.message).toContain('snapshot');
|
||||
}
|
||||
});
|
||||
|
||||
test('valid ref still resolves normally after staleness check', async () => {
|
||||
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
||||
const snap = await handleMetaCommand('snapshot', ['-i'], bm, shutdown);
|
||||
const linkLine = snap.split('\n').find(l => l.includes('[link]'));
|
||||
expect(linkLine).toBeDefined();
|
||||
const refMatch = linkLine!.match(/@(e\d+)/);
|
||||
const ref = `@${refMatch![1]}`;
|
||||
// Should work normally — element still exists
|
||||
const result = await handleWriteCommand('hover', [ref], bm);
|
||||
expect(result).toContain('Hovered');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Snapshot Diffing ──────────────────────────────────────────
|
||||
|
||||
describe('Snapshot diff', () => {
|
||||
|
||||
Reference in New Issue
Block a user