Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/core/test/replay.test.ts
1029 views
1
import Core, { Session } from '@secret-agent/core';
2
import { Helpers } from '@secret-agent/testing';
3
import { InteractionCommand } from '@secret-agent/interfaces/IInteractions';
4
import * as WebSocket from 'ws';
5
import { ITestKoaServer } from '@secret-agent/testing/helpers';
6
import { createPromise } from '@secret-agent/commons/utils';
7
import Output from '@secret-agent/client/lib/Output';
8
import ReplayOutput from '@secret-agent/replay/backend/api/ReplayOutput';
9
import ObjectObserver from '@secret-agent/client/lib/ObjectObserver';
10
import ICommandWithResult from '../interfaces/ICommandWithResult';
11
import { IDomChangeRecord } from '../models/DomChangesTable';
12
13
let koaServer: ITestKoaServer;
14
beforeAll(async () => {
15
koaServer = await Helpers.runKoaServer();
16
});
17
afterAll(Helpers.afterAll);
18
afterEach(Helpers.afterEach);
19
20
describe('basic Replay API tests', () => {
21
it('should be able to subscribe to changes', async () => {
22
koaServer.get('/test1', ctx => {
23
ctx.body = `<body>
24
<h1>This is page 1</h1>
25
<input type="text" name="anything" value="none">
26
<a href="/test2">Click me</a>
27
</body>
28
`;
29
});
30
koaServer.get('/test2', ctx => {
31
ctx.body = `<body>
32
<h1>This is page 2</h1>
33
</body>
34
`;
35
});
36
await Core.start();
37
const connection = Core.addConnection();
38
Helpers.onClose(() => connection.disconnect());
39
const meta = await connection.createSession();
40
41
const api = new WebSocket(`${await Core.server.address}/replay`, {
42
headers: {
43
'data-location': meta.sessionsDataLocation,
44
'session-id': meta.sessionId,
45
},
46
});
47
const commandMap: { [id: string]: ICommandWithResult } = {};
48
const paintMap: {
49
[timestamp: number]: IDomChangeRecord[];
50
} = {};
51
const gotCommandsPromise = createPromise();
52
api.on('message', async message => {
53
const { event, data } = JSON.parse(message.toString());
54
55
if (event === 'commands') {
56
for (const command of data) {
57
commandMap[command.id] = command;
58
}
59
if (Object.keys(commandMap).length >= 8) {
60
gotCommandsPromise.resolve();
61
}
62
} else if (event === 'dom-changes') {
63
for (const change of data) {
64
if (!paintMap[change.timestamp]) paintMap[change.timestamp] = [];
65
paintMap[change.timestamp].push(change);
66
}
67
}
68
});
69
const tab = Session.getTab(meta);
70
await tab.goto(`${koaServer.baseUrl}/test1`);
71
await tab.waitForLoad('PaintingStable');
72
await new Promise(resolve => setTimeout(resolve, 100));
73
await tab.interact([
74
{
75
command: InteractionCommand.type,
76
keyboardCommands: [{ string: 'test' }],
77
mousePosition: ['window', 'document', ['querySelector', 'input']],
78
},
79
]);
80
await tab.waitForElement(['document', ['querySelector', 'a']]);
81
await tab.interact([
82
{
83
command: InteractionCommand.click,
84
mousePosition: ['window', 'document', ['querySelector', 'a']],
85
},
86
]);
87
await tab.waitForMillis(100);
88
await tab.waitForLoad('PaintingStable');
89
const location = await tab.execJsPath(['location', 'href']);
90
expect(location.value).toBe(`${koaServer.baseUrl}/test2`);
91
92
await gotCommandsPromise.promise;
93
94
const commands = Object.values(commandMap);
95
const firstCommand = commands[0];
96
expect(firstCommand.name).toBe('goto');
97
expect(commands[1].label).toBe('waitForLoad("PaintingStable")');
98
99
const paintEvents = Object.values(paintMap);
100
expect(paintEvents[0]).toHaveLength(14);
101
102
await Core.shutdown(true);
103
if (api.readyState === WebSocket.OPEN) api.terminate();
104
}, 20e3);
105
106
it('should be able to rebuild an output', async () => {
107
const observable = new ObjectObserver(new Output());
108
109
const clientOutput = observable.proxy;
110
const replayOutput = new ReplayOutput();
111
let id = 0;
112
observable.onChanges = changes => {
113
const changesToRecord = changes.map(change => ({
114
type: change.type,
115
value: JSON.stringify(change.value),
116
path: JSON.stringify(change.path),
117
lastCommandId: id,
118
timestamp: new Date().getTime(),
119
}));
120
replayOutput.onOutput(changesToRecord);
121
};
122
123
clientOutput.test = 1;
124
expect(replayOutput.getLatestOutput(id).output).toEqual(clientOutput.toJSON());
125
126
id += 1;
127
clientOutput.sub = { nested: true, str: 'test', num: 1 };
128
expect(replayOutput.getLatestOutput(id).output).toEqual(clientOutput.toJSON());
129
130
id += 1;
131
delete clientOutput.sub.num;
132
expect(replayOutput.getLatestOutput(id).output).toEqual(clientOutput.toJSON());
133
134
id += 1;
135
delete clientOutput.sub;
136
delete clientOutput.test;
137
expect(replayOutput.getLatestOutput(id).output).toEqual(clientOutput.toJSON());
138
139
id += 1;
140
clientOutput.array = [{ test: 1 }, { test: 2 }, { test: 3 }];
141
expect(replayOutput.getLatestOutput(id).output).toEqual(clientOutput.toJSON());
142
143
id += 1;
144
clientOutput.array.splice(1, 1);
145
expect(replayOutput.getLatestOutput(id).output).toEqual(clientOutput.toJSON());
146
147
id += 1;
148
clientOutput.array.push({ test: 0 });
149
clientOutput.array.sort((a, b) => {
150
return a.test - b.test;
151
});
152
expect(replayOutput.getLatestOutput(id).output).toEqual(clientOutput.toJSON());
153
});
154
});
155
156