Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/puppet/test/Keyboard.test.ts
1030 views
1
import { IKeyboardKey } from '@secret-agent/interfaces/IKeyboardLayoutUS';
2
import Log from '@secret-agent/commons/Logger';
3
import IPuppetContext from '@secret-agent/interfaces/IPuppetContext';
4
import CorePlugins from '@secret-agent/core/lib/CorePlugins';
5
import { IBoundLog } from '@secret-agent/interfaces/ILog';
6
import Core from '@secret-agent/core';
7
import { TestServer } from './server';
8
import { createTestPage, ITestPage } from './TestPage';
9
import Puppet from '../index';
10
import CustomBrowserEmulator from './_CustomBrowserEmulator';
11
12
const { log } = Log(module);
13
const browserEmulatorId = CustomBrowserEmulator.id;
14
const MAC = process.platform === 'darwin';
15
16
describe('Keyboard', () => {
17
let server: TestServer;
18
let page: ITestPage;
19
let puppet: Puppet;
20
let context: IPuppetContext;
21
22
beforeAll(async () => {
23
Core.use(CustomBrowserEmulator);
24
server = await TestServer.create(0);
25
puppet = new Puppet(CustomBrowserEmulator.selectBrowserMeta().browserEngine);
26
await puppet.start();
27
const plugins = new CorePlugins({ browserEmulatorId }, log as IBoundLog);
28
context = await puppet.newContext(plugins, log);
29
});
30
31
afterEach(async () => {
32
await page.close();
33
});
34
35
beforeEach(async () => {
36
page = createTestPage(await context.newPage());
37
server.reset();
38
});
39
40
afterAll(async () => {
41
await server.stop();
42
await context.close();
43
await puppet.close();
44
});
45
46
it('should type into a textarea', async () => {
47
await page.evaluate(`(() => {
48
const textarea = document.createElement('textarea');
49
document.body.appendChild(textarea);
50
})()`);
51
await page.click('textarea');
52
const text = 'Hello world. I am the text that was typed!';
53
await page.type(text);
54
expect(await page.evaluate(`(() => document.querySelector('textarea').value)()`)).toBe(text);
55
});
56
57
it('should move with the arrow keys', async () => {
58
await page.goto(`${server.baseUrl}/input/textarea.html`);
59
await page.click('textarea');
60
await page.type('Hello World!');
61
expect(await page.evaluate(`document.querySelector('textarea').value`)).toBe('Hello World!');
62
for (let i = 0; i < 'World!'.length; i += 1) await page.keyboard.press('ArrowLeft');
63
await page.type('inserted ');
64
expect(await page.evaluate(`document.querySelector('textarea').value`)).toBe(
65
'Hello inserted World!',
66
);
67
await page.keyboard.down('Shift');
68
for (let i = 0; i < 'inserted '.length; i += 1) await page.keyboard.press('ArrowLeft');
69
await page.keyboard.up('Shift');
70
await page.keyboard.press('Backspace');
71
expect(await page.evaluate(`document.querySelector('textarea').value`)).toBe('Hello World!');
72
});
73
74
it('should send a character with type', async () => {
75
await page.goto(`${server.baseUrl}/input/textarea.html`);
76
await page.click('textarea');
77
await page.keyboard.sendCharacter('å—Ļ');
78
expect(await page.evaluate(`document.querySelector('textarea').value`)).toBe('å—Ļ');
79
await page.evaluate(`window.addEventListener('keydown', e => e.preventDefault(), true)`);
80
await page.keyboard.sendCharacter('a');
81
expect(await page.evaluate(`document.querySelector('textarea').value`)).toBe('å—Ļa');
82
});
83
84
it('should report shiftKey', async () => {
85
await page.goto(`${server.baseUrl}/input/keyboard.html`);
86
const keyboard = page.keyboard;
87
const codeForKey = new Map<IKeyboardKey, number>([
88
['Shift', 16],
89
['Alt', 18],
90
['Control', 17],
91
]);
92
for (const [modifierKey, modifierCode] of codeForKey) {
93
await keyboard.down(modifierKey);
94
expect(await page.evaluate('getResult()')).toBe(
95
`Keydown: ${modifierKey} ${modifierKey}Left ${modifierCode} [${modifierKey}]`,
96
);
97
await keyboard.down('!');
98
// Shift+! will generate a keypress
99
if (modifierKey === 'Shift')
100
expect(await page.evaluate('getResult()')).toBe(
101
`Keydown: ! Digit1 49 [${modifierKey}]\nKeypress: ! Digit1 33 33 [${modifierKey}]`,
102
);
103
else expect(await page.evaluate('getResult()')).toBe(`Keydown: ! Digit1 49 [${modifierKey}]`);
104
105
await keyboard.up('!');
106
expect(await page.evaluate('getResult()')).toBe(`Keyup: ! Digit1 49 [${modifierKey}]`);
107
await keyboard.up(modifierKey);
108
expect(await page.evaluate('getResult()')).toBe(
109
`Keyup: ${modifierKey} ${modifierKey}Left ${modifierCode} []`,
110
);
111
}
112
});
113
114
it('should report multiple modifiers', async () => {
115
await page.goto(`${server.baseUrl}/input/keyboard.html`);
116
const keyboard = page.keyboard;
117
await keyboard.down('Control');
118
expect(await page.evaluate('getResult()')).toBe('Keydown: Control ControlLeft 17 [Control]');
119
await keyboard.down('Alt');
120
expect(await page.evaluate('getResult()')).toBe('Keydown: Alt AltLeft 18 [Alt Control]');
121
await keyboard.down(';');
122
expect(await page.evaluate('getResult()')).toBe('Keydown: ; Semicolon 186 [Alt Control]');
123
await keyboard.up(';');
124
expect(await page.evaluate('getResult()')).toBe('Keyup: ; Semicolon 186 [Alt Control]');
125
await keyboard.up('Control');
126
expect(await page.evaluate('getResult()')).toBe('Keyup: Control ControlLeft 17 [Alt]');
127
await keyboard.up('Alt');
128
expect(await page.evaluate('getResult()')).toBe('Keyup: Alt AltLeft 18 []');
129
});
130
131
it('should send proper codes while typing', async () => {
132
await page.goto(`${server.baseUrl}/input/keyboard.html`);
133
await page.type('!');
134
expect(await page.evaluate('getResult()')).toBe(
135
['Keydown: ! Digit1 49 []', 'Keypress: ! Digit1 33 33 []', 'Keyup: ! Digit1 49 []'].join(
136
'\n',
137
),
138
);
139
await page.type('^');
140
expect(await page.evaluate('getResult()')).toBe(
141
['Keydown: ^ Digit6 54 []', 'Keypress: ^ Digit6 94 94 []', 'Keyup: ^ Digit6 54 []'].join(
142
'\n',
143
),
144
);
145
});
146
147
it('should send proper codes while typing with shift', async () => {
148
await page.goto(`${server.baseUrl}/input/keyboard.html`);
149
const keyboard = page.keyboard;
150
await keyboard.down('Shift');
151
await page.type('~');
152
expect(await page.evaluate('getResult()')).toBe(
153
[
154
'Keydown: Shift ShiftLeft 16 [Shift]',
155
'Keydown: ~ Backquote 192 [Shift]', // 192 is ` keyCode
156
'Keypress: ~ Backquote 126 126 [Shift]', // 126 is ~ charCode
157
'Keyup: ~ Backquote 192 [Shift]',
158
].join('\n'),
159
);
160
await keyboard.up('Shift');
161
});
162
163
it('should not type canceled events', async () => {
164
await page.goto(`${server.baseUrl}/input/textarea.html`);
165
await page.click('textarea');
166
await page.evaluate(`(() => {
167
window.addEventListener('keydown', event => {
168
event.stopPropagation();
169
event.stopImmediatePropagation();
170
if (event.key === 'l')
171
event.preventDefault();
172
if (event.key === 'o')
173
event.preventDefault();
174
}, false);
175
})()`);
176
await page.type('Hello World!');
177
expect(await page.evaluate(`document.querySelector('textarea').value`)).toBe('He Wrd!');
178
});
179
180
it('should specify repeat property', async () => {
181
await page.goto(`${server.baseUrl}/input/textarea.html`);
182
await page.click('textarea');
183
await page.keyboard.down('a');
184
185
expect((await captureLastKeydown(page)).repeat).toBe(false);
186
187
await page.keyboard.press('a');
188
expect((await captureLastKeydown(page)).repeat).toBe(true);
189
190
await page.keyboard.down('b');
191
expect((await captureLastKeydown(page)).repeat).toBe(false);
192
193
await page.keyboard.down('b');
194
expect((await captureLastKeydown(page)).repeat).toBe(true);
195
196
await page.keyboard.up('a');
197
await page.keyboard.down('a');
198
199
expect((await captureLastKeydown(page)).repeat).toBe(false);
200
});
201
202
it('should type all kinds of characters', async () => {
203
await page.goto(`${server.baseUrl}/input/textarea.html`);
204
await page.click('textarea');
205
const text = 'This text goes onto two lines.\nThis character is å—Ļ.';
206
await page.type(text);
207
expect(await page.evaluate(`document.querySelector('textarea').value`)).toBe(text);
208
});
209
210
it('should specify location', async () => {
211
await page.goto(`${server.baseUrl}/input/textarea.html`);
212
await captureLastKeydown(page); // install
213
await page.click(`textarea`);
214
215
await page.keyboard.press('Digit5');
216
expect((await captureLastKeydown(page)).location).toBe(0);
217
218
await page.keyboard.press('ControlLeft');
219
expect((await captureLastKeydown(page)).location).toBe(1);
220
221
await page.keyboard.press('ControlRight');
222
expect((await captureLastKeydown(page)).location).toBe(2);
223
224
await page.keyboard.press('NumpadSubtract');
225
expect((await captureLastKeydown(page)).location).toBe(3);
226
});
227
228
it('should press Enter', async () => {
229
await page.setContent('<textarea></textarea>');
230
await captureLastKeydown(page);
231
await testEnterKey('Enter', 'Enter', 'Enter');
232
await testEnterKey('NumpadEnter', 'Enter', 'NumpadEnter');
233
await testEnterKey('\n', 'Enter', 'Enter');
234
await testEnterKey('\r', 'Enter', 'Enter');
235
236
async function testEnterKey(key, expectedKey, expectedCode) {
237
await page.click('textarea');
238
await page.keyboard.press(key);
239
240
const lastEvent = await captureLastKeydown(page);
241
expect(lastEvent.key).toBe(expectedKey); // had the wrong key
242
expect(lastEvent.code).toBe(expectedCode); // had the wrong code
243
const value = await page.evaluate(`document.querySelector('textarea').value`);
244
expect(value).toBe('\n'); // failed to create a newline
245
await page.evaluate(`document.querySelector('textarea').value = ''`);
246
}
247
});
248
249
it('should throw on unknown keys', async () => {
250
// @ts-ignore-error
251
let error = await page.keyboard.press('NotARealKey').catch(e => e);
252
expect(error.message).toBe('Unknown key: "NotARealKey"');
253
254
// @ts-ignore-error
255
error = await page.keyboard.press('Ņ‘').catch(e => e);
256
expect(error && error.message).toBe('Unknown key: "Ņ‘"');
257
258
// @ts-ignore-error
259
error = await page.keyboard.press('😊').catch(e => e);
260
expect(error && error.message).toBe('Unknown key: "😊"');
261
});
262
263
it('should type emoji', async () => {
264
await page.goto(`${server.baseUrl}/input/textarea.html`);
265
await page.click('textarea');
266
await page.type('ðŸ‘đ Tokyo street Japan ðŸ‡ŊðŸ‡ĩ');
267
expect(await page.evaluate(`document.querySelector('textarea').value`)).toBe(
268
'ðŸ‘đ Tokyo street Japan ðŸ‡ŊðŸ‡ĩ',
269
);
270
});
271
272
it('should type emoji into an iframe', async () => {
273
await page.goto(server.emptyPage);
274
await page.attachFrame('emoji-test', `${server.baseUrl}/input/textarea.html`);
275
const textArea =
276
'document.querySelector("#emoji-test").contentWindow.document.body.querySelector("textarea")';
277
await page.evaluate(`(() => {
278
279
const rect = ${textArea}.focus()
280
})()`);
281
await page.type('ðŸ‘đ Tokyo street Japan ðŸ‡ŊðŸ‡ĩ');
282
283
expect(await page.evaluate(`${textArea}.value`)).toBe('ðŸ‘đ Tokyo street Japan ðŸ‡ŊðŸ‡ĩ');
284
});
285
286
// playwright test that we didn't copy the logic for - would need to add "mac editing shortcuts"
287
// eslint-disable-next-line jest/no-disabled-tests
288
it.skip('should handle selectAll', async () => {
289
await page.goto(`${server.baseUrl}/input/textarea.html`);
290
await page.click('textarea');
291
await page.type('some text');
292
const modifier = MAC ? 'Meta' : 'Control';
293
await page.keyboard.down(modifier);
294
await page.keyboard.press('a');
295
await page.keyboard.up(modifier);
296
await page.keyboard.press('Backspace');
297
expect(await page.evaluate(`document.querySelector('textarea').value`)).toBe('');
298
});
299
300
it('should be able to prevent selectAll', async () => {
301
await page.goto(`${server.baseUrl}/input/textarea.html`);
302
await page.click('textarea');
303
await page.type('some text');
304
await page.evaluate(`(() => {
305
document.querySelector('textarea').addEventListener(
306
'keydown',
307
event => {
308
if (event.key === 'a' && (event.metaKey || event.ctrlKey)) event.preventDefault();
309
},
310
false,
311
);
312
})()`);
313
const modifier = MAC ? 'Meta' : 'Control';
314
await page.keyboard.down(modifier);
315
await page.keyboard.press('a');
316
await page.keyboard.up(modifier);
317
await page.keyboard.press('Backspace');
318
expect(await page.evaluate(`document.querySelector('textarea').value`)).toBe('some tex');
319
});
320
321
it('should press the meta key', async () => {
322
await captureLastKeydown(page);
323
await page.keyboard.press('Meta');
324
const lastEvent = await captureLastKeydown(page);
325
const { key, code, metaKey } = lastEvent;
326
// if (options.FIREFOX && !MAC) expect(key).toBe('OS');
327
// else
328
expect(key).toBe('Meta');
329
330
// if (options.FIREFOX) expect(code).toBe('OSLeft');
331
// else
332
expect(code).toBe('MetaLeft');
333
334
// if (options.FIREFOX && !MAC) expect(metaKey).toBe(false);
335
// else
336
expect(metaKey).toBe(true);
337
});
338
339
it('should work after a cross origin navigation', async () => {
340
await page.goto(`${server.baseUrl}/empty.html`);
341
await page.goto(`${server.crossProcessBaseUrl}/empty.html`);
342
await captureLastKeydown(page);
343
await page.keyboard.press('a');
344
const lastEvent = await captureLastKeydown(page);
345
expect(lastEvent.key).toBe('a');
346
});
347
});
348
349
async function captureLastKeydown(page: ITestPage): Promise<any> {
350
return await page.evaluate(`(() => {
351
if (window.lastEvent) return window.lastEvent;
352
const lastEvent = {
353
repeat: false,
354
location: -1,
355
code: '',
356
key: '',
357
metaKey: false,
358
keyIdentifier: 'unsupported',
359
};
360
window.lastEvent = lastEvent;
361
document.addEventListener(
362
'keydown',
363
e => {
364
lastEvent.repeat = e.repeat;
365
lastEvent.location = e.location;
366
lastEvent.key = e.key;
367
lastEvent.code = e.code;
368
lastEvent.metaKey = e.metaKey;
369
// keyIdentifier only exists in WebKit, and isn't in TypeScript's lib.
370
lastEvent.keyIdentifier = 'keyIdentifier' in e && e.keyIdentifier;
371
},
372
true,
373
);
374
return lastEvent;
375
})()`);
376
}
377
378