Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/core/test/interact.test.ts
1029 views
1
import { Helpers } from '@secret-agent/testing';
2
import { InteractionCommand } from '@secret-agent/interfaces/IInteractions';
3
import HumanEmulator from '@secret-agent/default-human-emulator';
4
import { getLogo, ITestKoaServer } from '@secret-agent/testing/helpers';
5
import Core, { Session } from '../index';
6
import ConnectionToClient from '../server/ConnectionToClient';
7
8
let koaServer: ITestKoaServer;
9
let connection: ConnectionToClient;
10
beforeAll(async () => {
11
connection = Core.addConnection();
12
await connection.connect();
13
Helpers.onClose(() => connection.disconnect(), true);
14
15
HumanEmulator.maxDelayBetweenInteractions = 0;
16
HumanEmulator.maxScrollDelayMillis = 0;
17
koaServer = await Helpers.runKoaServer();
18
});
19
afterAll(Helpers.afterAll, 30e3);
20
afterEach(Helpers.afterEach);
21
22
const humanEmulatorId = HumanEmulator.id;
23
24
describe('%s interaction tests', () => {
25
it('executes basic click command', async () => {
26
koaServer.get('/mouse', ctx => {
27
ctx.body = `
28
<body>
29
<button>Test</button>
30
<script>
31
document.querySelector('button').addEventListener('click', event => {
32
document.querySelector('button').classList.add('clicked');
33
});
34
</script>
35
</body>
36
`;
37
});
38
const meta = await connection.createSession({ humanEmulatorId });
39
const session = Session.get(meta.sessionId);
40
Helpers.needsClosing.push(session);
41
const tab = Session.getTab(meta);
42
await tab.goto(`${koaServer.baseUrl}/mouse`);
43
44
const spy = jest.spyOn(session.plugins, 'playInteractions');
45
await tab.interact([
46
{
47
command: InteractionCommand.click,
48
mousePosition: ['window', 'document', ['querySelector', 'button']],
49
},
50
]);
51
52
expect(spy).toHaveBeenCalledTimes(1);
53
const interactGroups = spy.mock.calls[0][0];
54
expect(interactGroups).toHaveLength(1);
55
expect(interactGroups[0]).toHaveLength(2);
56
expect(interactGroups[0][0].command).toBe('scroll');
57
58
const buttonClass = await tab.execJsPath([
59
'document',
60
['querySelector', 'button.clicked'],
61
'classList',
62
]);
63
expect(buttonClass.value).toStrictEqual({ 0: 'clicked' });
64
});
65
66
it('executes basic type command', async () => {
67
koaServer.get('/input', ctx => {
68
ctx.set('Content-Security-Policy', "script-src 'unsafe-eval'");
69
ctx.body = `
70
<body>
71
<input type="text" />
72
</body>
73
`;
74
});
75
const meta = await connection.createSession({ humanEmulatorId });
76
const session = Session.get(meta.sessionId);
77
Helpers.needsClosing.push(session);
78
const tab = Session.getTab(meta);
79
80
await tab.goto(`${koaServer.baseUrl}/input`);
81
await tab.execJsPath(['document', ['querySelector', 'input'], ['focus']]);
82
await tab.interact([
83
{
84
command: InteractionCommand.type,
85
keyboardCommands: [{ string: 'Hello world!' }],
86
},
87
]);
88
const inputValue = await tab.execJsPath(['document', ['querySelector', 'input'], 'value']);
89
expect(inputValue.value).toBe('Hello world!');
90
});
91
92
it('should be able to click elements off screen', async () => {
93
koaServer.get('/longpage', ctx => {
94
ctx.body = `
95
<body>
96
<div style="height:500px">
97
<button id="button-1" onclick="click1(event)">Test</button>
98
</div>
99
<div style="margin-top:1100px; flex: content; justify-content: center">
100
<button id="button-2" onclick="click2()" style="width: 30px; height: 20px;">Test 2</button>
101
</div>
102
<div style="margin-top:2161px; float:right;clear:both: width:20px; height: 20px;">
103
<button id="button-3" onclick="click3()">Test 3</button>
104
</div>
105
<script>
106
let lastClicked = '';
107
function click1(ev) {
108
lastClicked = 'click1' + (ev.isTrusted ? '' : '!!untrusted');
109
}
110
function click2() {
111
lastClicked = 'click2';
112
}
113
function click3() {
114
lastClicked = 'click3';
115
}
116
</script>
117
</body>
118
`;
119
});
120
121
const meta = await connection.createSession({
122
humanEmulatorId,
123
viewport: {
124
width: 1920,
125
height: 1080,
126
screenWidth: 1920,
127
screenHeight: 1080,
128
positionX: 0,
129
positionY: 0,
130
},
131
});
132
const session = Session.get(meta.sessionId);
133
Helpers.needsClosing.push(session);
134
const tab = Session.getTab(meta);
135
await tab.goto(`${koaServer.baseUrl}/longpage`);
136
137
const click = async (selector: string) => {
138
await tab.interact([
139
{
140
command: InteractionCommand.click,
141
mousePosition: ['window', 'document', ['querySelector', selector]],
142
},
143
]);
144
return await tab.getJsValue('lastClicked');
145
};
146
147
let lastClicked = await click('#button-1');
148
expect(lastClicked).toBe('click1');
149
150
lastClicked = await click('#button-2');
151
expect(lastClicked).toBe('click2');
152
153
lastClicked = await click('#button-3');
154
expect(lastClicked).toBe('click3');
155
156
lastClicked = await click('#button-1');
157
expect(lastClicked).toBe('click1');
158
}, 60e3);
159
160
it('should be able to click elements that move on load', async () => {
161
koaServer.get('/img.png', async ctx => {
162
ctx.set('Content-Type', 'image/png');
163
await new Promise(resolve => setTimeout(resolve, 50));
164
ctx.body = getLogo();
165
});
166
167
// test putting next to an image that will only space after it loads
168
koaServer.get('/move-on-load', ctx => {
169
ctx.body = `
170
<body>
171
<div style="height: 1800px"></div>
172
<div>
173
<img src="/img.png" />
174
<img src="/img.png?test=1" />
175
<img src="/img.png?test=3" />
176
<button onclick="clickit()" id="button-1">Click me</button>
177
</div>
178
<script>
179
180
let lastClicked = '';
181
function clickit() {
182
lastClicked = 'clickedit';
183
}
184
</script>
185
</body>
186
`;
187
});
188
189
const meta = await connection.createSession({ humanEmulatorId });
190
const session = Session.get(meta.sessionId);
191
Helpers.needsClosing.push(session);
192
193
const tab = Session.getTab(meta);
194
// @ts-ignore
195
const interactor = tab.mainFrameEnvironment.interactor;
196
// @ts-ignore
197
const originalFn = interactor.lookupBoundingRect.bind(interactor);
198
const lookupSpy = jest.spyOn(interactor, 'lookupBoundingRect');
199
lookupSpy.mockImplementationOnce(async mousePosition => {
200
const data = await originalFn(mousePosition);
201
data.y -= 500;
202
return data;
203
});
204
205
await tab.goto(`${koaServer.baseUrl}/move-on-load`);
206
await tab.interact([
207
{
208
command: InteractionCommand.click,
209
mousePosition: ['window', 'document', ['querySelector', '#button-1']],
210
},
211
]);
212
expect(lookupSpy.mock.calls.length).toBeGreaterThanOrEqual(2);
213
const lastClicked = await tab.getJsValue('lastClicked');
214
expect(lastClicked).toBe('clickedit');
215
});
216
217
it('should cancel pending interactions after a page clears', async () => {
218
koaServer.get('/redirect-on-move', ctx => {
219
ctx.body = `
220
<body>
221
<div style="height: 1000px"></div>
222
<div><button id="button-1">Click me</button></div>
223
<script>
224
document.addEventListener('mousemove', () => {
225
window.location = '${koaServer.baseUrl}/';
226
})
227
</script>
228
</body>
229
`;
230
});
231
const meta = await connection.createSession({ humanEmulatorId });
232
const session = Session.get(meta.sessionId);
233
Helpers.needsClosing.push(session);
234
const tab = Session.getTab(meta);
235
await tab.goto(`${koaServer.baseUrl}/redirect-on-move`);
236
await tab.waitForLoad('DomContentLoaded');
237
await tab.interact([
238
{
239
command: InteractionCommand.click,
240
mousePosition: ['window', 'document', ['querySelector', '#button-1']],
241
},
242
]);
243
244
await tab.waitForLocation('change');
245
const url = await tab.url;
246
expect(url).toBe(`${koaServer.baseUrl}/`);
247
});
248
});
249
250