Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/plugins/default-human-emulator/test/emulator.test.ts
1031 views
1
import { IInteractionStep, InteractionCommand } from '@secret-agent/interfaces/IInteractions';
2
import IViewport from '@secret-agent/interfaces/IViewport';
3
import Log from '@secret-agent/commons/Logger';
4
import { IBoundLog } from '@secret-agent/interfaces/ILog';
5
import ICorePluginCreateOptions from '@secret-agent/interfaces/ICorePluginCreateOptions';
6
import HumanEmulator, { isVisible } from '../index';
7
8
const { log } = Log(module);
9
10
beforeAll(() => {
11
HumanEmulator.maxDelayBetweenInteractions = 0;
12
HumanEmulator.maxScrollDelayMillis = 0;
13
});
14
15
describe('typing', () => {
16
test('should spread out characters based on a wpm range', async () => {
17
HumanEmulator.wordsPerMinuteRange = [34, 34];
18
const humanEmulator = new HumanEmulator({ logger: log as IBoundLog } as ICorePluginCreateOptions);
19
const groups = [
20
[
21
{
22
command: InteractionCommand.type,
23
keyboardCommands: [{ string: 'Test typing sentence' }],
24
},
25
],
26
];
27
// @ts-ignore
28
const millisPerCharacter = humanEmulator.calculateMillisPerChar(groups);
29
expect(millisPerCharacter).toBe(353);
30
31
let count = 0;
32
let totalMillis = 0;
33
await humanEmulator.playInteractions(
34
groups,
35
async interactionStep => {
36
expect(interactionStep.keyboardKeyupDelay).toBeGreaterThanOrEqual(10);
37
expect(interactionStep.keyboardKeyupDelay).toBeLessThanOrEqual(60);
38
39
expect(interactionStep.keyboardDelayBetween).toBeGreaterThanOrEqual(353 - 60 - 353 / 2);
40
totalMillis += interactionStep.keyboardDelayBetween + interactionStep.keyboardKeyupDelay;
41
count += 1;
42
return null;
43
},
44
null,
45
);
46
const chars = 'Test typing sentence'.length;
47
expect(count).toBe(chars);
48
const charsPerSecond = totalMillis / 1000 / chars;
49
const charsPerMinute = 60 / charsPerSecond;
50
const wpm = Math.round(charsPerMinute / 5);
51
// should be close to 34 wpm
52
expect(Math.abs(34 - wpm)).toBeLessThanOrEqual(10);
53
});
54
});
55
56
describe('deltaToVisible', () => {
57
test('should calculate deltas correctly', async () => {
58
expect(isVisible(20, 50, 100)).toBe(true);
59
// 85 + 25 is 110. Need to scroll +10
60
expect(isVisible(85, 50, 100)).toBe(false);
61
// -30 + 25 is -5. Need to scroll up -5
62
expect(isVisible(-30, 50, 100)).toBe(false);
63
});
64
65
test('should work to find center coordinates', async () => {
66
expect(isVisible(20, 100, 100)).toBe(true);
67
expect(isVisible(85, 100, 100)).toBe(false);
68
expect(isVisible(150, 100, 100)).toBe(false);
69
expect(isVisible(-30, 100, 100)).toBe(true);
70
expect(isVisible(-51, 100, 100)).toBe(false);
71
});
72
});
73
74
describe('move', () => {
75
test('should break a move into a series of moves', async () => {
76
const humanEmulator = new HumanEmulator({ logger: log as IBoundLog } as ICorePluginCreateOptions);
77
const commands = [];
78
// @ts-ignore
79
await humanEmulator.scroll(
80
{
81
command: 'move',
82
mousePosition: [['document', 'querySelector', 'x']],
83
},
84
async step => {
85
commands.push(step);
86
},
87
{
88
mousePosition: { x: 25, y: 25 },
89
viewport: {
90
height: 600,
91
width: 800,
92
} as IViewport,
93
async lookupBoundingRect() {
94
return {
95
elementTag: 'div',
96
height: 10,
97
width: 100,
98
x: 0,
99
y: 800,
100
};
101
},
102
scrollOffset: Promise.resolve({ x: 0, y: 0 }),
103
logger: log,
104
createMouseupTrigger() {
105
return Promise.resolve({
106
didTrigger: () => Promise.resolve({ didClickLocation: true } as any),
107
});
108
},
109
createMouseoverTrigger() {
110
return Promise.resolve({
111
didTrigger: () => Promise.resolve(true),
112
});
113
},
114
},
115
);
116
117
expect(commands.length).toBeGreaterThan(2);
118
});
119
});
120
121
describe('scroll', () => {
122
test('should break a scroll into a curve', async () => {
123
const humanEmulator = new HumanEmulator({ logger: log as IBoundLog } as ICorePluginCreateOptions);
124
const commands = [];
125
// @ts-ignore
126
await humanEmulator.scroll(
127
{
128
command: 'scroll',
129
mousePosition: [['document', 'querySelector', 'x']],
130
},
131
async step => {
132
commands.push(step);
133
},
134
{
135
mousePosition: { x: 25, y: 25 },
136
viewport: {
137
height: 600,
138
width: 800,
139
} as IViewport,
140
async lookupBoundingRect() {
141
return {
142
elementTag: 'div',
143
height: 10,
144
width: 100,
145
x: 0,
146
y: 800,
147
};
148
},
149
scrollOffset: Promise.resolve({ x: 0, y: 0 }),
150
logger: log,
151
createMouseupTrigger() {
152
return Promise.resolve({
153
didTrigger: () => Promise.resolve({ didClickLocation: true } as any),
154
});
155
},
156
createMouseoverTrigger() {
157
return Promise.resolve({
158
didTrigger: () => Promise.resolve(true),
159
});
160
},
161
},
162
);
163
164
expect(commands.length).toBeGreaterThan(1);
165
});
166
167
test('should not scroll if over half in screen', async () => {
168
const humanEmulator = new HumanEmulator({ logger: log as IBoundLog } as ICorePluginCreateOptions);
169
const commands = [];
170
// @ts-ignore
171
await humanEmulator.scroll(
172
{
173
command: 'scroll',
174
mousePosition: [['document', 'querySelector', 'x']],
175
},
176
async step => {
177
commands.push(step);
178
},
179
{
180
mousePosition: { x: 25, y: 25 },
181
viewport: {
182
height: 600,
183
width: 800,
184
} as IViewport,
185
async lookupBoundingRect() {
186
return {
187
elementTag: 'div',
188
height: 200,
189
width: 100,
190
x: 50,
191
y: 499,
192
};
193
},
194
scrollOffset: Promise.resolve({ x: 0, y: 0 }),
195
logger: log,
196
createMouseupTrigger() {
197
return Promise.resolve({
198
didTrigger: () => Promise.resolve({ didClickLocation: true } as any),
199
});
200
},
201
createMouseoverTrigger() {
202
return Promise.resolve({
203
didTrigger: () => Promise.resolve(true),
204
});
205
},
206
},
207
);
208
209
expect(commands).toHaveLength(0);
210
});
211
212
test('should not exceed max pixels per scroll', async () => {
213
const humanEmulator = new HumanEmulator({ logger: log as IBoundLog } as ICorePluginCreateOptions);
214
const commands: IInteractionStep[] = [];
215
// @ts-ignore
216
await humanEmulator.scroll(
217
{
218
command: 'scroll',
219
mousePosition: [['document', 'querySelector', 'x']],
220
},
221
async step => {
222
commands.push(step);
223
},
224
{
225
mousePosition: { x: 25, y: 25 },
226
viewport: {
227
height: 600,
228
width: 800,
229
} as IViewport,
230
async lookupBoundingRect() {
231
return {
232
elementTag: 'div',
233
height: 200,
234
width: 100,
235
x: 50,
236
y: 50000,
237
};
238
},
239
scrollOffset: Promise.resolve({ x: 0, y: 0 }),
240
logger: log,
241
createMouseupTrigger() {
242
return Promise.resolve({
243
didTrigger: () => Promise.resolve({ didClickLocation: true } as any),
244
});
245
},
246
createMouseoverTrigger() {
247
return Promise.resolve({
248
didTrigger: () => Promise.resolve(true),
249
});
250
},
251
},
252
);
253
254
expect(commands.length).toBeGreaterThan(2);
255
256
const scrolls = commands.filter(x => x.command === 'scroll');
257
for (let i = 0; i < scrolls.length; i += 1) {
258
const current = scrolls[i];
259
const next = scrolls[i + 1];
260
if (current && next) {
261
const diff = Math.round(
262
Math.abs((next.mousePosition[1] as number) - (current.mousePosition[1] as number)),
263
);
264
expect(diff).toBeLessThanOrEqual(500);
265
}
266
}
267
});
268
});
269
270