microsoft / WinAppDriver

Windows Application Driver
MIT License
3.64k stars 1.4k forks source link

sendKeys applies QWERTY-Style on German Keyboard Setting #446

Open ap0yuv opened 6 years ago

ap0yuv commented 6 years ago

OS: win10 Language Setting: DEU (German)

Given the following script:

const wd = require("wd");
const serverConfig = {
    host: 'localhost',
    port: 4723,
    path: '/'
};

const driver = wd.promiseChainRemote(serverConfig);

const desiredCapabilities = {
    app: 'C:\\Windows\\System32\\notepad.exe',     
    platformName: 'Windows',
    deviceName: 'windowsPC'
};

driver
    .init(desiredCapabilities)
    .elementByClassName("Edit")
    .sendKeys('zora driving a yacht')
    .fin( () => { return driver.quit(); });

Output to Notepad is: yora driving a zacht

If i take a look into the Server-Log the Request is recieved correctly:

==========================================
POST /session/2787FC29-6B61-41AE-B4AF-DE6BC7DFC8C2/element/42.2425972/value HTTP/1.1
Accept: application/json
Connection: keep-alive
Content-Length: 31
Content-Type: application/json; charset=UTF-8
Host: localhost:4723
User-Agent: admc/wd/1.6.2

{"value":["zora driving a yacht"]}
HTTP/1.1 200 OK
Content-Length: 63
Content-Type: application/json

{"sessionId":"2787FC29-6B61-41AE-B4AF-DE6BC7DFC8C2","status":0}

==========================================

Possible Workaround: Switching Language/Keyboard in Windows to English and the text is typed to Notepad correctly.

Regards, ap0yuv

Daynvheur commented 5 years ago

Same in French keyboard layout : @"%TEMP%\NotepadTestFile" outputs "%TEMP%*NotepqdTestFile".

Server-Log is correct :

==========================================
POST /session/F93DAB3D-95B3-483B-8B21-1541C9A36B45/element/42.265638/value HTTP/1.1
Accept: application/json, image/png
Content-Length: 37
Content-Type: application/json;charset=utf-8
Host: 127.0.0.1:4723

{"value":["%TEMP%\\NotepadTestFile"]}
HTTP/1.1 200 OK
Content-Length: 63
Content-Type: application/json

{"sessionId":"F93DAB3D-95B3-483B-8B21-1541C9A36B45","status":0}

Same workaround : force switching to English keyboard layout when running the tests. (And back to the actual layout aftewards : there is no way to produce slash characters with a French AZERTY kerboard otherway.)

fdahlberg commented 5 years ago

Can you force the switching to English Keyboard as part of your test script? I've been trying for a while but failed. I've created a desktop session and sent WIndows(command) + space key to change keyboard layout. But I'm not sure how to figure out what it has been changed to...

My code looks like this at the moment...

            DesiredCapabilities desktopAppCapabilities = new DesiredCapabilities();
            desktopAppCapabilities.SetCapability("app", "Root");
            var DesktopSession = new WindowsDriver<WindowsElement>(new Uri(WindowsApplicationDriverUrl), desktopAppCapabilities);

            Console.WriteLine("Making sure keyboard is english-us");
            var keyboardIsUS = false;
            var keyboardChangeCounter = 0;
            while (!keyboardIsUS && keyboardChangeCounter < 5)
            {
                Console.WriteLine("Trying to change keyboard by sending window + space");
                DesktopSession.Keyboard.SendKeys(Keys.Command + Keys.Space + Keys.Command);
                Console.WriteLine("System keyboard layouts: " + string.Join(",", KeyboardLayouts.GetSystemKeyboardLayouts().Select(kl => kl.KeyboardName)));
                var currentKeyboardLayout = KeyboardLayouts.GetProcessKeyboardLayout();
                Console.WriteLine("Current Keyboard: " + currentKeyboardLayout.KeyboardName);
                keyboardIsUS = currentKeyboardLayout.KeyboardId == 1033;
                keyboardChangeCounter++;
            }
shatulsky commented 5 years ago

Same behavior with russian keyboard. Sending "QWERTY" keys with Russian Keyboard layout actually type - "ЙЦУКЕН". So it actually simulate keyboard key pressing with current Keyboard layout instead of just typing actual text. I can by handled by switching keyboard layout right in test but it is not greather solution. Would be greate if it will be fixed by winappdriver devs.

bhaas commented 5 years ago

I have a workaround on Windows 10 for this problem, but I hope this will be solved soon:

You can set a keyboard shortcut for the keyboard layout in Windows Settings -> Time & language -> Region & language -> Additional date, time, & regional settings -> Change input methods -> Advanced settings -> Change language bar hot keys.

I set US keyboard layout to ctrl+0 and my local keyboard layout to ctrl+1 and in my testcode I am using the shortcuts to switch the keyboard layout ...

// switch to US keyboard layout Actions switchKeyboardLayoutActions = new Actions(session); switchKeyboardLayoutActions.SendKeys(Keys.Control + "0" + Keys.Control); switchKeyboardLayoutActions.Build(); switchKeyboardLayoutActions.Perform();

// your send keys actions ...

// switch back keyboard layout switchKeyboardLayoutActions = new Actions(session); switchKeyboardLayoutActions.SendKeys(Keys.Control + "1" + Keys.Control); switchKeyboardLayoutActions.Build(); switchKeyboardLayoutActions.Perform();

andrisi commented 4 years ago

Another workaround is to enable per application keyboard layouts in Windows and then somehow switch the target app's - I almost there. See: https://www.tenforums.com/tutorials/102999-turn-off-use-different-keyboard-layout-each-app-window.html

xiler8 commented 4 years ago

Just ran into this issue on a Swedish keyboard on Win 10. SendKeys("-") turns into a + sign in my UWP app. And this becomes quite cumbersome since I don't want to switch keyboard every time I run the tests, even if it is automated in the test.

@hassanuz, is this a bug from my point of view. I am new to WinAppDriver and Appium, is this a bug in WinAppDriver? Or Appium?

andrisi commented 3 years ago

Let's ask @DHowett kindly :-) Basically remember that the rest of the world uses non-us keyboards. SendKeys ought to send the charachters we ask for, not translated through they local keyboard.

shatulsky commented 3 years ago

Here is my workaround. Let me know if I missed smth. Instead of using WinAPI, you can actually type 1 symbol and verify the language by it. E. g. - type q, get current text box text, verify if the last symbol is q, then - current language is English, if the last symbol is й or - Russian, etc (do not forget to remove the symbol after verify :) ). You can use my code as a base and just replace GetCurrentKeyboardLayout realization.

public void Input(string text)
        {
            CultureInfo textCulture = GetTextCulture(text);

            if (!GetCurrentKeyboardLayout().Equals(textCulture))
            {
                SetKeyboardLayout(textCulture);
            }

            Logger.Log.Debug($"Inputting '{text}' to {this} with {textCulture.Name} culture");
            GetElement().SendKeys(text);
        }

        private static CultureInfo GetTextCulture(string input)
        {
            if (Cultures.IsTextOnEnglish(input))
            {
                return Cultures.En;
            }

            if (Cultures.IsTextOnRussian(input))
            {
                return Cultures.Ru;
            }

            return CultureInfo.CurrentCulture;
        }

private void SetKeyboardLayout(CultureInfo targetCulture)
        {
            Logger.Log.Debug($"Switching keyboard layout to {targetCulture}");

            CultureInfo initCulture = GetCurrentKeyboardLayout();
            Logger.Log.Debug($"Current keyboard layout is {initCulture}");

            if (initCulture.Equals(targetCulture))
            {
                return;
            }

            while (true)
            {
                CultureInfo cultureBeforeChange = GetCurrentKeyboardLayout();

                new Actions(GetDriver())
                    .SendKeys(Keys.LeftAlt + Keys.LeftShift + Keys.LeftShift + Keys.LeftAlt)
                    .Perform();

                CultureInfo currentCulture = null;
                Wait.HasCondition(
                    c =>
                    {
                        currentCulture = GetCurrentKeyboardLayout();
                        return !cultureBeforeChange.Equals(currentCulture);
                    },
                    WaitTimeouts.TenSeconds,
                    "Keyboard layout change");

                Logger.Log.Debug($"Current keyboard layout is {initCulture}");

                if (currentCulture.Equals(initCulture))
                {
                    throw new ArgumentException($"Keyboard layout cannot be changed to {targetCulture.EnglishName}");
                }

                if (currentCulture.Equals(targetCulture))
                {
                    break;
                }
            }
        }

        private static CultureInfo GetCurrentKeyboardLayout()
        {
            return GetCurrentWindowKeyboardLayout();
        }

        public static CultureInfo GetCurrentWindowKeyboardLayout()
        {
            uint foregroundProcess = GetWindowThreadProcessId(GetForegroundWindow(), out IntPtr _);
            int keyboardLayout = GetKeyboardLayout(foregroundProcess).ToInt32() & 0xFFFF;
            return new CultureInfo(keyboardLayout);
        }

        [DllImport("user32.dll")]
        private static extern IntPtr GetForegroundWindow();

        [DllImport("user32.dll")]
        private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out IntPtr process);

        [DllImport("user32.dll")]
        private static extern IntPtr GetKeyboardLayout(uint thread);
public static class Cultures
    {
        /// <summary>
        ///     Gets instance of en-US <see cref="CultureInfo" />.
        /// </summary>
        public static readonly CultureInfo En = new CultureInfo("en-US");

        /// <summary>
        ///     Gets instance of ru-RU <see cref="CultureInfo" />.
        /// </summary>
        public static readonly CultureInfo Ru = new CultureInfo("ru-RU");

        /// <summary>
        ///     Gets a value indicating whether specified <see cref="string" /> is on English.
        /// </summary>
        /// <param name="input">String to determine the language.</param>
        /// <returns><see langword="true" /> if string is on English; otherwise, <see langword="false" />.</returns>
        public static bool IsTextOnEnglish(string input)
        {
            return Regex.Match(input, @"^[a-zA-Z\d \-\!\$\%\^\&\*\(\)\\_\+\|\~\=\`\{\}\[\]\:\""\;\'\<\>\?\,\.\/\#\@]*$")
                .Success;
        }

        /// <summary>
        ///     Gets a value indicating whether specified <see cref="string" /> is on Russian.
        /// </summary>
        /// <param name="input">String to determine the language.</param>
        /// <returns><see langword="true" /> if string is on Russian; otherwise, <see langword="false" />.</returns>
        public static bool IsTextOnRussian(string input)
        {
            return Regex.Match(input, @"^[а-яА-Я\d \-\!\$\%\^\&\*\(\)\\_\+\|\~\=\`\{\}\[\]\:\""\;\'\<\>\?\,\.\/\#\@]*$")
                .Success;
        }
    }
kbartashevich commented 3 years ago

Just got that one with a German keyboard... It was reeeally confusing to see expected values having "z" replaced by "y" 😃

sweiguny commented 3 years ago

Any news on this? WinAppDriver still emulates the wrong Keyboard Layout.

Kusigeri commented 3 years ago

Same behavior with Hungarian keyboard. Swaps the characters z and y.

keeganrowe commented 3 years ago

Same issue in Switzerland like everywhere else. I'm sending an ISO format time stamp and it's being changed.

2021-05-20T06:59:00+02:00 becomes 2021'05'20T06:59:00+02:00

Which no longer a valid ISO timestamp.

Are there workarounds to send the ASCII character codes or something instead?

raphaelAlma commented 1 month ago

a colleague find a pretty good workaround, Use the Clipboard and past it into the element using keys (you still have to be sure of what is V)
look like that, and it works

System.Windows.Forms.Clipboard.SetText("mytext");    
windowsElement.SendKeys(Keys.LeftControl + "v");