diff --git a/src/PlaywrightNative.Tests/PageKeyboardTests.cs b/src/PlaywrightNative.Tests/PageKeyboardTests.cs index d413cbe8..04c8fe40 100644 --- a/src/PlaywrightNative.Tests/PageKeyboardTests.cs +++ b/src/PlaywrightNative.Tests/PageKeyboardTests.cs @@ -336,6 +336,30 @@ await page.EvaluateAsync("document.querySelector('textarea').value").Con Is.EqualTo("嗨a")); } + [PlaywrightTest("page-keyboard.spec.ts", "should not type text with Alt modifier")] + [Test] + [Timeout(30_000)] + public async Task ShouldNotTypeTextWithAltModifier() + { + await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); + IPage page = await NewPageAsync(browser).ConfigureAwait(false); + await page.SetContentAsync("
").ConfigureAwait(false); + foreach (ILocator locator in new[] { page.Locator("input"), page.Locator("textarea"), page.Locator("div") }) + { + await locator.PressAsync("Alt+KeyA").ConfigureAwait(false); + await locator.PressAsync("Alt+Digit1").ConfigureAwait(false); + await locator.PressAsync("Alt+Shift+KeyA").ConfigureAwait(false); + await page.Keyboard.DownAsync("Alt").ConfigureAwait(false); + await page.Keyboard.PressAsync("b").ConfigureAwait(false); + await page.Keyboard.UpAsync("Alt").ConfigureAwait(false); + await locator.PressAsync("c").ConfigureAwait(false); + } + + await Assertions.Expect(page.Locator("input")).ToHaveValueAsync("c").ConfigureAwait(false); + await Assertions.Expect(page.Locator("textarea")).ToHaveValueAsync("c").ConfigureAwait(false); + await Assertions.Expect(page.Locator("div")).ToHaveTextAsync("c").ConfigureAwait(false); + } + [PlaywrightTest("page-keyboard.spec.ts", "insertText should only emit input event")] [Test] [Timeout(30_000)] @@ -707,6 +731,57 @@ public async Task ShouldSpecifyLocation() Assert.That(await lastEvent.EvaluateAsync("e => e.code").ConfigureAwait(false), Is.EqualTo("NumpadSubtract")); } + [PlaywrightTest("page-keyboard.spec.ts", "should specify numpad location on keyup")] + [Test] + [Timeout(30_000)] + public async Task ShouldSpecifyNumpadLocationOnKeyup() + { + await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); + IPage page = await NewPageAsync(browser).ConfigureAwait(false); + await page.GoToAsync(Prefix + "/input/keyboard.html").ConfigureAwait(false); + await page.Keyboard.PressAsync("NumpadSubtract").ConfigureAwait(false); + Assert.That( + await GetResultAsync(page).ConfigureAwait(false), + Is.EqualTo(string.Join( + "\n", + "Keydown: - NumpadSubtract NUMPAD []", + "Keypress: - NumpadSubtract NUMPAD 45 []", + "Keyup: - NumpadSubtract NUMPAD []"))); + } + + [PlaywrightTest("page-keyboard.spec.ts", "should press NumpadDecimal as Delete")] + [Test] + [Timeout(30_000)] + public async Task ShouldPressNumpadDecimalAsDelete() + { + await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); + IPage page = await NewPageAsync(browser).ConfigureAwait(false); + await page.SetContentAsync("").ConfigureAwait(false); + IJSHandle lastEvent = await CaptureLastKeydownAsync(page).ConfigureAwait(false); + ILocator textarea = page.Locator("textarea"); + + await textarea.PressAsync("NumpadDecimal").ConfigureAwait(false); + Assert.That(await lastEvent.EvaluateAsync("e => e.key").ConfigureAwait(false), Is.EqualTo("Delete")); + Assert.That(await lastEvent.EvaluateAsync("e => e.code").ConfigureAwait(false), Is.EqualTo("NumpadDecimal")); + Assert.That(await lastEvent.EvaluateAsync("e => e.location").ConfigureAwait(false), Is.EqualTo(3)); + await Assertions.Expect(textarea).ToHaveValueAsync(string.Empty).ConfigureAwait(false); + } + + [PlaywrightTest("page-keyboard.spec.ts", "should type numpad digits with Shift")] + [Test] + [Timeout(30_000)] + public async Task ShouldTypeNumpadDigitsWithShift() + { + await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); + IPage page = await NewPageAsync(browser).ConfigureAwait(false); + await page.SetContentAsync("").ConfigureAwait(false); + ILocator textarea = page.Locator("textarea"); + await textarea.PressAsync("Shift+Numpad1").ConfigureAwait(false); + await textarea.PressAsync("Shift+NumpadDecimal").ConfigureAwait(false); + await textarea.PressAsync("Shift+Numpad9").ConfigureAwait(false); + await Assertions.Expect(textarea).ToHaveValueAsync("1.9").ConfigureAwait(false); + } + [PlaywrightTest("page-keyboard.spec.ts", "should press Enter")] [Test] [Timeout(30_000)] diff --git a/src/PlaywrightNative/Chromium/CRRawKeyboard.cs b/src/PlaywrightNative/Chromium/CRRawKeyboard.cs index e622d84a..62b56702 100644 --- a/src/PlaywrightNative/Chromium/CRRawKeyboard.cs +++ b/src/PlaywrightNative/Chromium/CRRawKeyboard.cs @@ -81,6 +81,7 @@ public Task KeyUpAsync(IReadOnlyCollection modifiers, In windowsVirtualKeyCode = key.KeyCodeWithoutLocation == 0 ? key.KeyCode : key.KeyCodeWithoutLocation, code = key.Code, location = key.Location, + isKeypad = key.Location == 3, }); } diff --git a/src/PlaywrightNative/Input/USKeyboardLayout.cs b/src/PlaywrightNative/Input/USKeyboardLayout.cs index 1e3cd03d..128a1942 100644 --- a/src/PlaywrightNative/Input/USKeyboardLayout.cs +++ b/src/PlaywrightNative/Input/USKeyboardLayout.cs @@ -175,8 +175,24 @@ private static Dictionary BuildLayout() AddKey(map, "MediaTrackPrevious", 177, "MediaTrackPrevious", string.Empty); AddKey(map, "MediaPlayPause", 179, "MediaPlayPause", string.Empty); + // Numpad. Digit and decimal keys act as navigation keys unless Shift is held. + AddKey(map, "NumLock", 144, "NumLock", string.Empty); + AddKey(map, "NumpadDivide", 111, "/", "/", location: 3); + AddKey(map, "NumpadMultiply", 106, "*", "*", location: 3); + AddKey(map, "NumpadSubtract", 109, "-", "-", location: 3); + AddNumpad(map, "Numpad7", 36, 103, "Home", "7"); + AddNumpad(map, "Numpad8", 38, 104, "ArrowUp", "8"); + AddNumpad(map, "Numpad9", 33, 105, "PageUp", "9"); + AddNumpad(map, "Numpad4", 37, 100, "ArrowLeft", "4"); + AddNumpad(map, "Numpad5", 12, 101, "Clear", "5"); + AddNumpad(map, "Numpad6", 39, 102, "ArrowRight", "6"); + AddKey(map, "NumpadAdd", 107, "+", "+", location: 3); + AddNumpad(map, "Numpad1", 35, 97, "End", "1"); + AddNumpad(map, "Numpad2", 40, 98, "ArrowDown", "2"); + AddNumpad(map, "Numpad3", 34, 99, "PageDown", "3"); + AddNumpad(map, "Numpad0", 45, 96, "Insert", "0"); + AddNumpad(map, "NumpadDecimal", 46, 110, "Delete", "."); AddKey(map, "NumpadEnter", 13, "Enter", "\r", location: 3); - AddKey(map, "NumpadSubtract", 109, "-", string.Empty, location: 3); // Modifier keys — both location variants share keyCodeWithoutLocation. map["ShiftLeft"] = new KeyDefinition { KeyCode = 160, KeyCodeWithoutLocation = 16, Code = "ShiftLeft", Key = "Shift", Location = 1 }; @@ -214,6 +230,30 @@ private static KeyDefinition AddKey(Dictionary map, strin return def; } + private static void AddNumpad(Dictionary map, string code, int keyCode, int shiftKeyCode, string key, string shiftKey) + { + KeyDefinition shifted = new() + { + KeyCode = shiftKeyCode, + KeyCodeWithoutLocation = shiftKeyCode, + Code = code, + Key = shiftKey, + Text = shiftKey, + Location = 3, + }; + + map[code] = new KeyDefinition + { + KeyCode = keyCode, + KeyCodeWithoutLocation = keyCode, + Code = code, + Key = key, + Text = string.Empty, + Location = 3, + Shifted = shifted, + }; + } + private static void AddPrintable(Dictionary map, string code, int keyCode, string key, string shiftedKey) { KeyDefinition shifted = new()