feat: advance browser agent integration workflows

This commit is contained in:
go0p
2026-08-06 15:11:35 +08:00
committed by go0p
parent af5a4db694
commit 0c8e1c7b69
215 changed files with 35137 additions and 5442 deletions
@@ -0,0 +1,146 @@
import { describe, expect, it } from 'vitest';
import { createRecordingTraceRuntime } from './trace';
function environment() {
let active = true;
let recordingId: string | undefined = 'recording-1';
let captureValues = false;
let maxEntries = 3;
let parentEventId: string | undefined;
let uniqueSequence = 0;
let currentTime = 1_000;
const runtime = createRecordingTraceRuntime({
active: () => active,
recordingId: () => recordingId,
captureValues: () => captureValues,
maxEntries: () => maxEntries,
parentEventId: () => parentEventId,
unique: (prefix) => `${prefix}-${++uniqueSequence}`,
}, () => currentTime);
return {
runtime,
setActive(value: boolean) {
active = value;
},
setRecordingId(value: string | undefined) {
recordingId = value;
},
setCaptureValues(value: boolean) {
captureValues = value;
},
setMaxEntries(value: number) {
maxEntries = value;
},
setParentEventId(value: string | undefined) {
parentEventId = value;
},
advance(milliseconds: number) {
currentTime += milliseconds;
},
};
}
describe('recording trace runtime', () => {
it('does not create events or trace state while recording is inactive', () => {
const environmentState = environment();
environmentState.setActive(false);
expect(environmentState.runtime.record({ kind: 'interaction', operation: 'click' })).toBeUndefined();
expect(environmentState.runtime.observe(() => {
throw new Error('factory must not run');
})).toBeUndefined();
expect(environmentState.runtime.currentContext()).toBeUndefined();
expect(environmentState.runtime.snapshot(10)).toEqual({
count: 0,
droppedCount: 0,
sequence: 0,
events: [],
});
});
it('binds interaction context and preserves parent and sensitivity metadata', () => {
const environmentState = environment();
environmentState.setCaptureValues(true);
environmentState.setParentEventId('crypto-parent');
environmentState.runtime.bindContext({ traceId: 'trace-login', interactionId: 'interaction-submit' });
const event = environmentState.runtime.record({
kind: 'fetch',
operation: 'POST',
inputs: [],
outputs: [],
});
expect(event).toEqual(expect.objectContaining({
id: 'event-1',
recordingId: 'recording-1',
traceId: 'trace-login',
interactionId: 'interaction-submit',
parentEventId: 'crypto-parent',
sensitiveCaptured: true,
sequence: 1,
}));
});
it('reuses an active trace and creates a new trace after the idle window', () => {
const environmentState = environment();
const first = environmentState.runtime.record({ kind: 'transform', operation: 'first' });
environmentState.advance(4_999);
const second = environmentState.runtime.record({ kind: 'transform', operation: 'second' });
environmentState.advance(5_001);
const third = environmentState.runtime.record({ kind: 'transform', operation: 'third' });
expect(second?.traceId).toBe(first?.traceId);
expect(third?.traceId).not.toBe(first?.traceId);
});
it('keeps sequence monotonic while evicting bounded history', () => {
const environmentState = environment();
environmentState.setMaxEntries(2);
environmentState.runtime.record({ kind: 'interaction', operation: 'one' });
environmentState.runtime.record({ kind: 'interaction', operation: 'two' });
environmentState.runtime.record({ kind: 'interaction', operation: 'three' });
const snapshot = environmentState.runtime.snapshot(10);
expect(snapshot.count).toBe(2);
expect(snapshot.droppedCount).toBe(1);
expect(snapshot.sequence).toBe(3);
expect(snapshot.events.map((event) => event.operation)).toEqual(['two', 'three']);
});
it('isolates observer errors and supports reset and resumed sequence starts', () => {
const environmentState = environment();
environmentState.runtime.record({ kind: 'interaction', operation: 'before-reset' });
expect(environmentState.runtime.observe(() => {
throw new Error('broken observer');
})).toBeUndefined();
expect(environmentState.runtime.snapshot(10).droppedCount).toBe(1);
environmentState.runtime.reset(8);
environmentState.runtime.advanceSequenceStart(12);
const event = environmentState.runtime.record({ kind: 'navigation', operation: 'navigate' });
expect(event?.sequence).toBe(13);
expect(environmentState.runtime.snapshot(0)).toEqual({
count: 1,
droppedCount: 0,
sequence: 13,
events: [],
});
});
it('does not evaluate an observer factory without a recording identity', () => {
const environmentState = environment();
environmentState.setRecordingId(undefined);
let evaluated = false;
environmentState.runtime.observe(() => {
evaluated = true;
return { kind: 'interaction', operation: 'click' };
});
expect(evaluated).toBe(false);
expect(environmentState.runtime.snapshot(10).events).toEqual([]);
});
});