docs: enterprise refactor plan with ralph specs
This commit is contained in:
143
tests/plugins/agents/PlaywrightAgent.test.ts
Normal file
143
tests/plugins/agents/PlaywrightAgent.test.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* PlaywrightAgent integration test.
|
||||
* Uses a base64 data: URL so no external server is needed.
|
||||
*/
|
||||
|
||||
import { PlaywrightAgent } from '../../../src/plugins/agents/PlaywrightAgent';
|
||||
import { NullLogger } from '../../../src/core/Logger';
|
||||
|
||||
const HTML_CONTENT = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>Test Page</title></head>
|
||||
<body>
|
||||
<a href="#section" id="nav-link">Go to section</a>
|
||||
<button id="submit-btn">Submit</button>
|
||||
<input type="text" id="name-input" name="name" />
|
||||
<input type="email" id="email-input" name="email" />
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
const TEST_URL = `data:text/html;base64,${Buffer.from(HTML_CONTENT).toString('base64')}`;
|
||||
|
||||
describe('PlaywrightAgent', () => {
|
||||
jest.setTimeout(30000);
|
||||
let agent: PlaywrightAgent;
|
||||
|
||||
beforeEach(() => {
|
||||
agent = new PlaywrightAgent({ seed: 42, headless: true, logger: new NullLogger() });
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await agent.close();
|
||||
});
|
||||
|
||||
it('launches and captures initial state', async () => {
|
||||
await agent.launch(TEST_URL);
|
||||
const state = await agent.captureState();
|
||||
|
||||
expect(state.id).toBeTruthy();
|
||||
expect(state.title).toBe('Test Page');
|
||||
expect(state.domSnapshot).toContain('submit-btn');
|
||||
expect(state.visitCount).toBe(0);
|
||||
expect(typeof state.timestamp).toBe('number');
|
||||
});
|
||||
|
||||
it('discovers clickable and fillable actions', async () => {
|
||||
await agent.launch(TEST_URL);
|
||||
const state = await agent.captureState();
|
||||
const actions = await agent.discoverActions(state);
|
||||
|
||||
expect(actions.length).toBeGreaterThan(0);
|
||||
|
||||
const clicks = actions.filter((a) => a.type === 'click');
|
||||
const fills = actions.filter((a) => a.type === 'fill');
|
||||
|
||||
expect(clicks.length).toBeGreaterThan(0);
|
||||
expect(fills.length).toBeGreaterThan(0);
|
||||
|
||||
// All actions must have required fields
|
||||
for (const action of actions) {
|
||||
expect(action.id).toBeTruthy();
|
||||
expect(action.seed).toBeDefined();
|
||||
expect(action.stateId).toBe(state.id);
|
||||
expect(action.timestamp).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('executes a click action and returns an observation', async () => {
|
||||
await agent.launch(TEST_URL);
|
||||
const state = await agent.captureState();
|
||||
const actions = await agent.discoverActions(state);
|
||||
const clickAction = actions.find((a) => a.type === 'click');
|
||||
|
||||
expect(clickAction).toBeDefined();
|
||||
const observation = await agent.executeAction(clickAction!);
|
||||
|
||||
expect(observation.id).toBeTruthy();
|
||||
expect(observation.actionId).toBe(clickAction!.id);
|
||||
expect(observation.newStateId).toBeTruthy();
|
||||
expect(Array.isArray(observation.httpResponses)).toBe(true);
|
||||
expect(Array.isArray(observation.consoleErrors)).toBe(true);
|
||||
expect(Array.isArray(observation.jsExceptions)).toBe(true);
|
||||
});
|
||||
|
||||
it('executes a fill action and returns an observation', async () => {
|
||||
await agent.launch(TEST_URL);
|
||||
const state = await agent.captureState();
|
||||
const actions = await agent.discoverActions(state);
|
||||
const fillAction = actions.find((a) => a.type === 'fill');
|
||||
|
||||
expect(fillAction).toBeDefined();
|
||||
const observation = await agent.executeAction(fillAction!);
|
||||
|
||||
expect(observation.actionId).toBe(fillAction!.id);
|
||||
expect(observation.jsExceptions).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('uses deterministic seed for action discovery (same seed = same order)', async () => {
|
||||
await agent.launch(TEST_URL);
|
||||
const state = await agent.captureState();
|
||||
const actions1 = await agent.discoverActions(state);
|
||||
|
||||
// Skip if no actions found (page didn't load elements)
|
||||
if (actions1.length === 0) return;
|
||||
|
||||
await agent.close();
|
||||
|
||||
const agent2 = new PlaywrightAgent({ seed: 42, headless: true, logger: new NullLogger() });
|
||||
await agent2.launch(TEST_URL);
|
||||
const state2 = await agent2.captureState();
|
||||
const actions2 = await agent2.discoverActions(state2);
|
||||
await agent2.close();
|
||||
|
||||
// Same seed → same seeds on actions in same order
|
||||
const seeds1 = actions1.map((a) => a.seed);
|
||||
const seeds2 = actions2.map((a) => a.seed);
|
||||
expect(seeds1).toEqual(seeds2);
|
||||
});
|
||||
|
||||
it('two instances with different seeds produce different action seeds', async () => {
|
||||
const agent2 = new PlaywrightAgent({ seed: 99, headless: true, logger: new NullLogger() });
|
||||
|
||||
await agent.launch(TEST_URL);
|
||||
await agent2.launch(TEST_URL);
|
||||
|
||||
const state1 = await agent.captureState();
|
||||
const state2 = await agent2.captureState();
|
||||
|
||||
const actions1 = await agent.discoverActions(state1);
|
||||
const actions2 = await agent2.discoverActions(state2);
|
||||
|
||||
await agent2.close();
|
||||
|
||||
// Only test if actions were found
|
||||
if (actions1.length === 0) {
|
||||
// If no actions, pass — the test validates seed differences when actions exist
|
||||
return;
|
||||
}
|
||||
|
||||
const seeds1 = actions1.map((a) => a.seed);
|
||||
const seeds2 = actions2.map((a) => a.seed);
|
||||
expect(seeds1).not.toEqual(seeds2);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user