1/** 2 * Copyright 2018 Google Inc. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17import utils from './utils.js'; 18import os from 'os'; 19import expect from 'expect'; 20import { 21 getTestState, 22 setupTestBrowserHooks, 23 setupTestPageAndContextHooks, 24 itFailsFirefox, 25} from './mocha-utils'; // eslint-disable-line import/extensions 26import { KeyInput } from '../lib/cjs/puppeteer/common/USKeyboardLayout.js'; 27 28describe('Keyboard', function () { 29 setupTestBrowserHooks(); 30 setupTestPageAndContextHooks(); 31 32 it('should type into a textarea', async () => { 33 const { page } = getTestState(); 34 35 await page.evaluate(() => { 36 const textarea = document.createElement('textarea'); 37 document.body.appendChild(textarea); 38 textarea.focus(); 39 }); 40 const text = 'Hello world. I am the text that was typed!'; 41 await page.keyboard.type(text); 42 expect( 43 await page.evaluate(() => document.querySelector('textarea').value) 44 ).toBe(text); 45 }); 46 it('should press the metaKey', async () => { 47 const { page, isFirefox } = getTestState(); 48 49 await page.evaluate(() => { 50 (window as any).keyPromise = new Promise((resolve) => 51 document.addEventListener('keydown', (event) => resolve(event.key)) 52 ); 53 }); 54 await page.keyboard.press('Meta'); 55 expect(await page.evaluate('keyPromise')).toBe( 56 isFirefox && os.platform() !== 'darwin' ? 'OS' : 'Meta' 57 ); 58 }); 59 it('should move with the arrow keys', async () => { 60 const { page, server } = getTestState(); 61 62 await page.goto(server.PREFIX + '/input/textarea.html'); 63 await page.type('textarea', 'Hello World!'); 64 expect( 65 await page.evaluate(() => document.querySelector('textarea').value) 66 ).toBe('Hello World!'); 67 for (let i = 0; i < 'World!'.length; i++) page.keyboard.press('ArrowLeft'); 68 await page.keyboard.type('inserted '); 69 expect( 70 await page.evaluate(() => document.querySelector('textarea').value) 71 ).toBe('Hello inserted World!'); 72 page.keyboard.down('Shift'); 73 for (let i = 0; i < 'inserted '.length; i++) 74 page.keyboard.press('ArrowLeft'); 75 page.keyboard.up('Shift'); 76 await page.keyboard.press('Backspace'); 77 expect( 78 await page.evaluate(() => document.querySelector('textarea').value) 79 ).toBe('Hello World!'); 80 }); 81 it('should send a character with ElementHandle.press', async () => { 82 const { page, server } = getTestState(); 83 84 await page.goto(server.PREFIX + '/input/textarea.html'); 85 const textarea = await page.$('textarea'); 86 await textarea.press('a'); 87 expect( 88 await page.evaluate(() => document.querySelector('textarea').value) 89 ).toBe('a'); 90 91 await page.evaluate(() => 92 window.addEventListener('keydown', (e) => e.preventDefault(), true) 93 ); 94 95 await textarea.press('b'); 96 expect( 97 await page.evaluate(() => document.querySelector('textarea').value) 98 ).toBe('a'); 99 }); 100 it( 101 'ElementHandle.press should support |text| option', 102 async () => { 103 const { page, server } = getTestState(); 104 105 await page.goto(server.PREFIX + '/input/textarea.html'); 106 const textarea = await page.$('textarea'); 107 await textarea.press('a', { text: 'ё' }); 108 expect( 109 await page.evaluate(() => document.querySelector('textarea').value) 110 ).toBe('ё'); 111 } 112 ); 113 it('should send a character with sendCharacter', async () => { 114 const { page, server } = getTestState(); 115 116 await page.goto(server.PREFIX + '/input/textarea.html'); 117 await page.focus('textarea'); 118 await page.keyboard.sendCharacter('嗨'); 119 expect( 120 await page.evaluate(() => document.querySelector('textarea').value) 121 ).toBe('嗨'); 122 await page.evaluate(() => 123 window.addEventListener('keydown', (e) => e.preventDefault(), true) 124 ); 125 await page.keyboard.sendCharacter('a'); 126 expect( 127 await page.evaluate(() => document.querySelector('textarea').value) 128 ).toBe('嗨a'); 129 }); 130 it('should report shiftKey', async () => { 131 const { page, server } = getTestState(); 132 133 await page.goto(server.PREFIX + '/input/keyboard.html'); 134 const keyboard = page.keyboard; 135 const codeForKey = new Map<KeyInput, number>([ 136 ['Shift', 16], 137 ['Alt', 18], 138 ['Control', 17], 139 ]); 140 for (const [modifierKey, modifierCode] of codeForKey) { 141 await keyboard.down(modifierKey); 142 expect(await page.evaluate(() => globalThis.getResult())).toBe( 143 'Keydown: ' + 144 modifierKey + 145 ' ' + 146 modifierKey + 147 'Left ' + 148 modifierCode + 149 ' [' + 150 modifierKey + 151 ']' 152 ); 153 await keyboard.down('!'); 154 // Shift+! will generate a keypress 155 if (modifierKey === 'Shift') 156 expect(await page.evaluate(() => globalThis.getResult())).toBe( 157 'Keydown: ! Digit1 49 [' + 158 modifierKey + 159 ']\nKeypress: ! Digit1 33 33 [' + 160 modifierKey + 161 ']' 162 ); 163 else 164 expect(await page.evaluate(() => globalThis.getResult())).toBe( 165 'Keydown: ! Digit1 49 [' + modifierKey + ']' 166 ); 167 168 await keyboard.up('!'); 169 expect(await page.evaluate(() => globalThis.getResult())).toBe( 170 'Keyup: ! Digit1 49 [' + modifierKey + ']' 171 ); 172 await keyboard.up(modifierKey); 173 expect(await page.evaluate(() => globalThis.getResult())).toBe( 174 'Keyup: ' + 175 modifierKey + 176 ' ' + 177 modifierKey + 178 'Left ' + 179 modifierCode + 180 ' []' 181 ); 182 } 183 }); 184 it('should report multiple modifiers', async () => { 185 const { page, server } = getTestState(); 186 187 await page.goto(server.PREFIX + '/input/keyboard.html'); 188 const keyboard = page.keyboard; 189 await keyboard.down('Control'); 190 expect(await page.evaluate(() => globalThis.getResult())).toBe( 191 'Keydown: Control ControlLeft 17 [Control]' 192 ); 193 await keyboard.down('Alt'); 194 expect(await page.evaluate(() => globalThis.getResult())).toBe( 195 'Keydown: Alt AltLeft 18 [Alt Control]' 196 ); 197 await keyboard.down(';'); 198 expect(await page.evaluate(() => globalThis.getResult())).toBe( 199 'Keydown: ; Semicolon 186 [Alt Control]' 200 ); 201 await keyboard.up(';'); 202 expect(await page.evaluate(() => globalThis.getResult())).toBe( 203 'Keyup: ; Semicolon 186 [Alt Control]' 204 ); 205 await keyboard.up('Control'); 206 expect(await page.evaluate(() => globalThis.getResult())).toBe( 207 'Keyup: Control ControlLeft 17 [Alt]' 208 ); 209 await keyboard.up('Alt'); 210 expect(await page.evaluate(() => globalThis.getResult())).toBe( 211 'Keyup: Alt AltLeft 18 []' 212 ); 213 }); 214 it('should send proper codes while typing', async () => { 215 const { page, server } = getTestState(); 216 217 await page.goto(server.PREFIX + '/input/keyboard.html'); 218 await page.keyboard.type('!'); 219 expect(await page.evaluate(() => globalThis.getResult())).toBe( 220 [ 221 'Keydown: ! Digit1 49 []', 222 'Keypress: ! Digit1 33 33 []', 223 'Keyup: ! Digit1 49 []', 224 ].join('\n') 225 ); 226 await page.keyboard.type('^'); 227 expect(await page.evaluate(() => globalThis.getResult())).toBe( 228 [ 229 'Keydown: ^ Digit6 54 []', 230 'Keypress: ^ Digit6 94 94 []', 231 'Keyup: ^ Digit6 54 []', 232 ].join('\n') 233 ); 234 }); 235 it('should send proper codes while typing with shift', async () => { 236 const { page, server } = getTestState(); 237 238 await page.goto(server.PREFIX + '/input/keyboard.html'); 239 const keyboard = page.keyboard; 240 await keyboard.down('Shift'); 241 await page.keyboard.type('~'); 242 expect(await page.evaluate(() => globalThis.getResult())).toBe( 243 [ 244 'Keydown: Shift ShiftLeft 16 [Shift]', 245 'Keydown: ~ Backquote 192 [Shift]', // 192 is ` keyCode 246 'Keypress: ~ Backquote 126 126 [Shift]', // 126 is ~ charCode 247 'Keyup: ~ Backquote 192 [Shift]', 248 ].join('\n') 249 ); 250 await keyboard.up('Shift'); 251 }); 252 it('should not type canceled events', async () => { 253 const { page, server } = getTestState(); 254 255 await page.goto(server.PREFIX + '/input/textarea.html'); 256 await page.focus('textarea'); 257 await page.evaluate(() => { 258 window.addEventListener( 259 'keydown', 260 (event) => { 261 event.stopPropagation(); 262 event.stopImmediatePropagation(); 263 if (event.key === 'l') event.preventDefault(); 264 if (event.key === 'o') event.preventDefault(); 265 }, 266 false 267 ); 268 }); 269 await page.keyboard.type('Hello World!'); 270 expect(await page.evaluate(() => globalThis.textarea.value)).toBe( 271 'He Wrd!' 272 ); 273 }); 274 it('should specify repeat property', async () => { 275 const { page, server } = getTestState(); 276 277 await page.goto(server.PREFIX + '/input/textarea.html'); 278 await page.focus('textarea'); 279 await page.evaluate(() => 280 document 281 .querySelector('textarea') 282 .addEventListener('keydown', (e) => (globalThis.lastEvent = e), true) 283 ); 284 await page.keyboard.down('a'); 285 expect(await page.evaluate(() => globalThis.lastEvent.repeat)).toBe(false); 286 await page.keyboard.press('a'); 287 expect(await page.evaluate(() => globalThis.lastEvent.repeat)).toBe(true); 288 289 await page.keyboard.down('b'); 290 expect(await page.evaluate(() => globalThis.lastEvent.repeat)).toBe(false); 291 await page.keyboard.down('b'); 292 expect(await page.evaluate(() => globalThis.lastEvent.repeat)).toBe(true); 293 294 await page.keyboard.up('a'); 295 await page.keyboard.down('a'); 296 expect(await page.evaluate(() => globalThis.lastEvent.repeat)).toBe(false); 297 }); 298 it('should type all kinds of characters', async () => { 299 const { page, server } = getTestState(); 300 301 await page.goto(server.PREFIX + '/input/textarea.html'); 302 await page.focus('textarea'); 303 const text = 'This text goes onto two lines.\nThis character is 嗨.'; 304 await page.keyboard.type(text); 305 expect(await page.evaluate('result')).toBe(text); 306 }); 307 it('should specify location', async () => { 308 const { page, server } = getTestState(); 309 310 await page.goto(server.PREFIX + '/input/textarea.html'); 311 await page.evaluate(() => { 312 window.addEventListener( 313 'keydown', 314 (event) => (globalThis.keyLocation = event.location), 315 true 316 ); 317 }); 318 const textarea = await page.$('textarea'); 319 320 await textarea.press('Digit5'); 321 expect(await page.evaluate('keyLocation')).toBe(0); 322 323 await textarea.press('ControlLeft'); 324 expect(await page.evaluate('keyLocation')).toBe(1); 325 326 await textarea.press('ControlRight'); 327 expect(await page.evaluate('keyLocation')).toBe(2); 328 329 await textarea.press('NumpadSubtract'); 330 expect(await page.evaluate('keyLocation')).toBe(3); 331 }); 332 it('should throw on unknown keys', async () => { 333 const { page } = getTestState(); 334 335 let error = await page.keyboard 336 // @ts-expect-error bad input 337 .press('NotARealKey') 338 .catch((error_) => error_); 339 expect(error.message).toBe('Unknown key: "NotARealKey"'); 340 341 // @ts-expect-error bad input 342 error = await page.keyboard.press('ё').catch((error_) => error_); 343 expect(error && error.message).toBe('Unknown key: "ё"'); 344 345 // @ts-expect-error bad input 346 error = await page.keyboard.press('').catch((error_) => error_); 347 expect(error && error.message).toBe('Unknown key: ""'); 348 }); 349 it('should type emoji', async () => { 350 const { page, server } = getTestState(); 351 352 await page.goto(server.PREFIX + '/input/textarea.html'); 353 await page.type('textarea', ' Tokyo street Japan '); 354 expect( 355 await page.$eval( 356 'textarea', 357 (textarea: HTMLInputElement) => textarea.value 358 ) 359 ).toBe(' Tokyo street Japan '); 360 }); 361 it('should type emoji into an iframe', async () => { 362 const { page, server } = getTestState(); 363 364 await page.goto(server.EMPTY_PAGE); 365 await utils.attachFrame( 366 page, 367 'emoji-test', 368 server.PREFIX + '/input/textarea.html' 369 ); 370 const frame = page.frames()[1]; 371 const textarea = await frame.$('textarea'); 372 await textarea.type(' Tokyo street Japan '); 373 expect( 374 await frame.$eval( 375 'textarea', 376 (textarea: HTMLInputElement) => textarea.value 377 ) 378 ).toBe(' Tokyo street Japan '); 379 }); 380 it('should press the meta key', async () => { 381 const { page, isFirefox } = getTestState(); 382 383 await page.evaluate(() => { 384 globalThis.result = null; 385 document.addEventListener('keydown', (event) => { 386 globalThis.result = [event.key, event.code, event.metaKey]; 387 }); 388 }); 389 await page.keyboard.press('Meta'); 390 // Have to do this because we lose a lot of type info when evaluating a 391 // string not a function. This is why functions are recommended rather than 392 // using strings (although we'll leave this test so we have coverage of both 393 // approaches.) 394 const [key, code, metaKey] = (await page.evaluate('result')) as [ 395 string, 396 string, 397 boolean 398 ]; 399 if (isFirefox && os.platform() !== 'darwin') expect(key).toBe('OS'); 400 else expect(key).toBe('Meta'); 401 402 if (isFirefox) expect(code).toBe('OSLeft'); 403 else expect(code).toBe('MetaLeft'); 404 405 if (isFirefox && os.platform() !== 'darwin') expect(metaKey).toBe(false); 406 else expect(metaKey).toBe(true); 407 }); 408}); 409