-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(browser): support clipboard api userEvent.copy, cut, paste
#6769
base: main
Are you sure you want to change the base?
Changes from all commits
c6ea761
30c458f
1d477ab
a298b25
ab76c71
212dac1
9506828
cb717ab
7da03e8
d4620bd
24fd7dd
390457f
f6cfc27
1f12f7c
d14bebc
745a95a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -74,7 +74,7 @@ export const keyboardCleanup: UserEventCommand<(state: KeyboardState) => Promise | |
|
||
// fallback to insertText for non US key | ||
// https://github.com/microsoft/playwright/blob/50775698ae13642742f2a1e8983d1d686d7f192d/packages/playwright-core/src/server/input.ts#L95 | ||
const VALID_KEYS = new Set(['Escape', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'Backquote', '`', '~', 'Digit1', '1', '!', 'Digit2', '2', '@', 'Digit3', '3', '#', 'Digit4', '4', '$', 'Digit5', '5', '%', 'Digit6', '6', '^', 'Digit7', '7', '&', 'Digit8', '8', '*', 'Digit9', '9', '(', 'Digit0', '0', ')', 'Minus', '-', '_', 'Equal', '=', '+', 'Backslash', '\\', '|', 'Backspace', 'Tab', 'KeyQ', 'q', 'Q', 'KeyW', 'w', 'W', 'KeyE', 'e', 'E', 'KeyR', 'r', 'R', 'KeyT', 't', 'T', 'KeyY', 'y', 'Y', 'KeyU', 'u', 'U', 'KeyI', 'i', 'I', 'KeyO', 'o', 'O', 'KeyP', 'p', 'P', 'BracketLeft', '[', '{', 'BracketRight', ']', '}', 'CapsLock', 'KeyA', 'a', 'A', 'KeyS', 's', 'S', 'KeyD', 'd', 'D', 'KeyF', 'f', 'F', 'KeyG', 'g', 'G', 'KeyH', 'h', 'H', 'KeyJ', 'j', 'J', 'KeyK', 'k', 'K', 'KeyL', 'l', 'L', 'Semicolon', ';', ':', 'Quote', '\'', '"', 'Enter', '\n', '\r', 'ShiftLeft', 'Shift', 'KeyZ', 'z', 'Z', 'KeyX', 'x', 'X', 'KeyC', 'c', 'C', 'KeyV', 'v', 'V', 'KeyB', 'b', 'B', 'KeyN', 'n', 'N', 'KeyM', 'm', 'M', 'Comma', ',', '<', 'Period', '.', '>', 'Slash', '/', '?', 'ShiftRight', 'ControlLeft', 'Control', 'MetaLeft', 'Meta', 'AltLeft', 'Alt', 'Space', ' ', 'AltRight', 'AltGraph', 'MetaRight', 'ContextMenu', 'ControlRight', 'PrintScreen', 'ScrollLock', 'Pause', 'PageUp', 'PageDown', 'Insert', 'Delete', 'Home', 'End', 'ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown', 'NumLock', 'NumpadDivide', 'NumpadMultiply', 'NumpadSubtract', 'Numpad7', 'Numpad8', 'Numpad9', 'Numpad4', 'Numpad5', 'Numpad6', 'NumpadAdd', 'Numpad1', 'Numpad2', 'Numpad3', 'Numpad0', 'NumpadDecimal', 'NumpadEnter']) | ||
const VALID_KEYS = new Set(['Escape', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'Backquote', '`', '~', 'Digit1', '1', '!', 'Digit2', '2', '@', 'Digit3', '3', '#', 'Digit4', '4', '$', 'Digit5', '5', '%', 'Digit6', '6', '^', 'Digit7', '7', '&', 'Digit8', '8', '*', 'Digit9', '9', '(', 'Digit0', '0', ')', 'Minus', '-', '_', 'Equal', '=', '+', 'Backslash', '\\', '|', 'Backspace', 'Tab', 'KeyQ', 'q', 'Q', 'KeyW', 'w', 'W', 'KeyE', 'e', 'E', 'KeyR', 'r', 'R', 'KeyT', 't', 'T', 'KeyY', 'y', 'Y', 'KeyU', 'u', 'U', 'KeyI', 'i', 'I', 'KeyO', 'o', 'O', 'KeyP', 'p', 'P', 'BracketLeft', '[', '{', 'BracketRight', ']', '}', 'CapsLock', 'KeyA', 'a', 'A', 'KeyS', 's', 'S', 'KeyD', 'd', 'D', 'KeyF', 'f', 'F', 'KeyG', 'g', 'G', 'KeyH', 'h', 'H', 'KeyJ', 'j', 'J', 'KeyK', 'k', 'K', 'KeyL', 'l', 'L', 'Semicolon', ';', ':', 'Quote', '\'', '"', 'Enter', '\n', '\r', 'ShiftLeft', 'Shift', 'KeyZ', 'z', 'Z', 'KeyX', 'x', 'X', 'KeyC', 'c', 'C', 'KeyV', 'v', 'V', 'KeyB', 'b', 'B', 'KeyN', 'n', 'N', 'KeyM', 'm', 'M', 'Comma', ',', '<', 'Period', '.', '>', 'Slash', '/', '?', 'ShiftRight', 'ControlLeft', 'Control', 'MetaLeft', 'Meta', 'AltLeft', 'Alt', 'Space', ' ', 'AltRight', 'AltGraph', 'MetaRight', 'ContextMenu', 'ControlRight', 'PrintScreen', 'ScrollLock', 'Pause', 'PageUp', 'PageDown', 'Insert', 'Delete', 'Home', 'End', 'ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown', 'NumLock', 'NumpadDivide', 'NumpadMultiply', 'NumpadSubtract', 'Numpad7', 'Numpad8', 'Numpad9', 'Numpad4', 'Numpad5', 'Numpad6', 'NumpadAdd', 'Numpad1', 'Numpad2', 'Numpad3', 'Numpad0', 'NumpadDecimal', 'NumpadEnter', 'ControlOrMeta']) | ||
|
||
export async function keyboardImplementation( | ||
pressed: Set<string>, | ||
|
@@ -144,8 +144,7 @@ export async function keyboardImplementation( | |
|
||
for (const { releasePrevious, releaseSelf, repeat, keyDef } of actions) { | ||
let key = keyDef.key! | ||
const code = 'location' in keyDef ? keyDef.key! : keyDef.code! | ||
const special = Key[code as 'Shift'] | ||
Comment on lines
-147
to
-148
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed this as it wasn't working when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The difference here is The same is for |
||
const special = Key[key as 'Shift'] | ||
|
||
if (special) { | ||
key = special | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { expect, test } from 'vitest'; | ||
import { page, userEvent } from '@vitest/browser/context'; | ||
|
||
test('clipboard', async () => { | ||
// make it smaller since webdriverio fails when scaled | ||
page.viewport(300, 300) | ||
|
||
document.body.innerHTML = ` | ||
<input placeholder="first" /> | ||
<input placeholder="second" /> | ||
<input placeholder="third" /> | ||
`; | ||
|
||
// write first "hello" and copy to clipboard | ||
await userEvent.click(page.getByPlaceholder('first')); | ||
await userEvent.keyboard('hello'); | ||
await userEvent.dblClick(page.getByPlaceholder('first')); | ||
await userEvent.copy(); | ||
|
||
// paste into second | ||
await userEvent.click(page.getByPlaceholder('second')); | ||
await userEvent.paste(); | ||
|
||
// append first "world" and cut | ||
await userEvent.click(page.getByPlaceholder('first')); | ||
await userEvent.keyboard('world'); | ||
await userEvent.dblClick(page.getByPlaceholder('first')); | ||
await userEvent.cut(); | ||
|
||
// paste it to third | ||
await userEvent.click(page.getByPlaceholder('third')); | ||
await userEvent.paste(); | ||
|
||
expect([ | ||
(page.getByPlaceholder('first').element() as any).value, | ||
(page.getByPlaceholder('second').element() as any).value, | ||
(page.getByPlaceholder('third').element() as any).value, | ||
]).toMatchInlineSnapshot(` | ||
[ | ||
"", | ||
"hello", | ||
"helloworld", | ||
] | ||
`) | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am confused why this is required here. All sent characters are from https://github.com/testing-library/user-event/blob/main/src/keyboard/keyMap.ts shouldn't we do this check in
keyboard
?We probably need a test for every key 👀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parseKeyDef
doesn't strictly check what's inside{xxx}
and, for example,{ControlOrMeta}
ends up withkeyDef: { key: 'ControlOrMeta', code: 'Unknown' }
. Then we only sendkeyDef.key
to the provider, so it's working.We should probably do something with this key translation layer, but what do you suggest for this specific
ControlOrMeta
etc... specifically? Is this about consistency between providers (like supportingControlOrMeta
both for playwright and webdriverio)?