Bug 1328726 - Add web-platform wdspec tests for key actions. r=ato, r=jgraham, a=test-only
authorMaja Frydrychowicz <mjzffr@gmail.com>
Tue, 24 Jan 2017 16:59:04 -0500
changeset 378813 48a5f47f26c51dd7d462b63ff27f6f7a107e68bd
parent 378812 448d84ab7edad36ca80a510cce7abdb9d788845b
child 378814 d3deaae90a747264db55e7dc024b59dec3858110
push id1419
push userjlund@mozilla.com
push dateMon, 10 Apr 2017 20:44:07 +0000
treeherdermozilla-release@5e6801b73ef6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersato, jgraham, test-only
bugs1328726
milestone53.0a2
Bug 1328726 - Add web-platform wdspec tests for key actions. r=ato, r=jgraham, a=test-only MozReview-Commit-ID: LkfDqHujfOl
testing/web-platform/meta/MANIFEST.json
testing/web-platform/tests/test_keys_wdspec.html
testing/web-platform/tests/webdriver/actions.py
testing/web-platform/tests/webdriver/support/__init__.py
testing/web-platform/tests/webdriver/support/keys.py
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -12706,16 +12706,21 @@
      {}
     ]
    ],
    "./server-side.md": [
     [
      {}
     ]
    ],
+   "./test_keys_wdspec.html": [
+    [
+     {}
+    ]
+   ],
    "2dcontext/2x2.png": [
     [
      {}
     ]
    ],
    "2dcontext/best-practices/.gitkeep": [
     [
      {}
@@ -61366,16 +61371,26 @@
      {}
     ]
    ],
    "webdriver/README.md": [
     [
      {}
     ]
    ],
+   "webdriver/support/__init__.py": [
+    [
+     {}
+    ]
+   ],
+   "webdriver/support/keys.py": [
+    [
+     {}
+    ]
+   ],
    "webgl/OWNERS": [
     [
      {}
     ]
    ],
    "webgl/common.js": [
     [
      {}
@@ -126570,16 +126585,22 @@
    "workers/semantics/xhr/006.html": [
     [
      "/workers/semantics/xhr/006.html",
      {}
     ]
    ]
   },
   "wdspec": {
+   "webdriver/actions.py": [
+    [
+     "/webdriver/actions.py",
+     {}
+    ]
+   ],
    "webdriver/contexts.py": [
     [
      "/webdriver/contexts.py",
      {}
     ]
    ],
    "webdriver/navigation.py": [
     [
@@ -126657,16 +126678,20 @@
   "./serve.py": [
    "0efa39b1f26f86d73f2fce4f9e46003d62057b41",
    "support"
   ],
   "./server-side.md": [
    "c51b17fbac2a2e3121dc74f7badbd2873ce92f61",
    "support"
   ],
+  "./test_keys_wdspec.html": [
+   "4f7487e013de733271eba9f783de863155552c29",
+   "support"
+  ],
   "2dcontext/2x2.png": [
    "c67d3f646e86413722833d2308a9bfc793a916bf",
    "support"
   ],
   "2dcontext/best-practices/.gitkeep": [
    "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "support"
   ],
@@ -201213,28 +201238,40 @@
   "webdriver/OWNERS": [
    "ed6ae435828699abe5d1399c6e9bacc7bae7474f",
    "support"
   ],
   "webdriver/README.md": [
    "a4611303723fa7225d7667a1e4fe2495451b824f",
    "support"
   ],
+  "webdriver/actions.py": [
+   "3a857859e17b7965cec7fb1ba2368177f316a395",
+   "wdspec"
+  ],
   "webdriver/contexts.py": [
    "cef7ae3987fa61d0b17c616e35c6066ce1e4af83",
    "wdspec"
   ],
   "webdriver/interface.html": [
    "d783d0dd370f58b264ef238d8da5cd8601dc3c7f",
    "testharness"
   ],
   "webdriver/navigation.py": [
    "2216ea3b518ec6b1beef54ce2580b5e62c2841a0",
    "wdspec"
   ],
+  "webdriver/support/__init__.py": [
+   "da39a3ee5e6b4b0d3255bfef95601890afd80709",
+   "support"
+  ],
+  "webdriver/support/keys.py": [
+   "85bfdbd3203166c2b84616cc1936f6dd98f8de3b",
+   "support"
+  ],
   "webgl/OWNERS": [
    "34855ff53b22857e0085833768f19f7304e6a75e",
    "support"
   ],
   "webgl/bufferSubData.html": [
    "526612470551a0eb157b310c587d50080087808d",
    "testharness"
   ],
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/test_keys_wdspec.html
@@ -0,0 +1,55 @@
+<!doctype html>
+<meta charset=utf-8>
+
+<head>
+    <title>Test Keys</title>
+    <script>
+        var allEvents = {events: []};
+        function displayMessage(message) {
+            document.getElementById("events").innerHTML = "<p>" + message + "</p>";
+        }
+
+        function appendMessage(message) {
+            document.getElementById("events").innerHTML += "<p>" + message + "</p>";
+        }
+
+
+        function recordEvent(event) {
+          allEvents.events.push({
+            "code": event.code,
+            "key": event.key,
+            "which": event.which,
+            "location": event.location,
+            "ctrl": event.ctrlKey,
+            "meta": event.metaKey,
+            "shift": event.shiftKey,
+            "repeat": event.repeat,
+            "type": event.type
+          });
+          appendMessage(`${event.type}(code:${event.code}, key:${event.key}, which:${event.which})`);
+        }
+
+        function resetEvents() {
+            allEvents.events.length = 0;
+            displayMessage('');
+        }
+
+    </script>
+</head>
+<body>
+  <div>
+    <h2>KeyReporter</h2>
+    <input type="text" id="keys" size="80"
+           onkeyup="recordEvent(event)"
+           onkeypress="recordEvent(event)"
+           onkeydown="recordEvent(event)">
+  </div>
+  <div id="resultContainer" style="width:300;height:60">
+    <h2>Events</h2>
+    <div id="events"></div>
+  </div>
+
+</body>
+</html>
+
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/webdriver/actions.py
@@ -0,0 +1,231 @@
+import pytest
+
+from support.keys import Keys
+
+
+def get_events(session):
+    """Return list of key events recorded in the test_keys_page fixture."""
+    return session.execute_script("return allEvents.events;") or []
+
+
+def get_keys(input):
+    """Get printable characters entered into |input|.
+
+    :param input: HTML input element.
+    """
+    rv = input.property("value")
+    if rv is None:
+        return ""
+    else:
+        return rv
+
+
+def filter_dict(source, d):
+    """Filter |source| dict to only contain same keys as |d| dict.
+
+    :param source: dictionary to filter.
+    :param d: dictionary whose keys determine the filtering.
+    """
+    return {k: source[k] for k in d.keys()}
+
+
+@pytest.fixture
+def key_reporter(session, test_keys_page, request):
+    """ Represents focused input element on |test_keys_page| """
+    input = session.find.css("#keys", all=False)
+    input.click()
+    return input
+
+
+@pytest.fixture
+def test_keys_page(session, server):
+    session.url = server.where_is("test_keys_wdspec.html")
+
+
+@pytest.fixture
+def key_chain(session):
+    return session.actions.sequence("key", "keyboard_id")
+
+@pytest.fixture(autouse=True)
+def release_actions(session, request):
+    # release all actions after each test
+    # equivalent to a teardown_function, but with access to session fixture
+    request.addfinalizer(session.actions.release)
+
+def test_no_actions_send_no_events(session, key_reporter, key_chain):
+    key_chain.perform()
+    assert len(get_keys(key_reporter)) == 0
+    assert len(get_events(session)) == 0
+
+
+def test_lone_keyup_sends_no_events(session, key_reporter, key_chain):
+    key_chain.key_up("a").perform()
+    assert len(get_keys(key_reporter)) == 0
+    assert len(get_events(session)) == 0
+    session.actions.release()
+    assert len(get_keys(key_reporter)) == 0
+    assert len(get_events(session)) == 0
+
+
+# TODO - the harness bails with TIMEOUT before all these subtests complete
+# The timeout is per file, so move to separate file with longer timeout?
+# Need a way to set timeouts in py files (since can't do html meta)
+# @pytest.mark.parametrize("name,expected", ALL_EVENTS.items())
+# def test_webdriver_special_key_sends_keydown(session,
+#                                              key_reporter,
+#                                              key_chain,
+#                                              name,
+#                                              expected):
+#     key_chain.key_down(getattr(Keys, name)).perform()
+#     # only interested in keydown
+#     first_event = get_events(session)[0]
+#     # make a copy so we throw out irrelevant keys and compare to events
+#     expected = dict(expected)
+#     del expected["value"]
+#     # check and remove keys that aren't in expected
+#     assert first_event["type"] == "keydown"
+#     assert first_event["repeat"] == False
+#     first_event = filter_dict(first_event, expected)
+#     assert first_event == expected
+#     # check that printable character was recorded in input field
+#     if len(expected["key"]) == 1:
+#         assert get_keys(key_reporter) == expected["key"]
+
+
+@pytest.mark.parametrize("value,code", [
+    (u"a", "KeyA",),
+    ("a", "KeyA",),
+    (u"\"", "Quote"),
+    (u",", "Comma"),
+    (u"\u00E0", ""),
+    (u"\u0416", ""),
+    (u"@", "Digit2"),
+    (u"\uF6C2", ""),  # PUA
+])
+def test_single_printable_key_sends_correct_events(session,
+                                                   key_reporter,
+                                                   key_chain,
+                                                   value,
+                                                   code):
+    key_chain \
+        .key_down(value) \
+        .key_up(value) \
+        .perform()
+    expected = [
+        {"code": code, "key": value, "type": "keydown"},
+        {"code": code, "key": value, "type": "keypress"},
+        {"code": code, "key": value, "type": "keyup"},
+    ]
+    events = [filter_dict(e, expected[0]) for e in get_events(session)]
+    assert events == expected
+    assert get_keys(key_reporter) == value
+
+
+@pytest.mark.parametrize("value,code,key", [
+    (u"\uE050", "ShiftRight", "Shift"),
+    (u"\uE053", "OSRight", "Meta"),
+    (Keys.CONTROL, "ControlLeft", "Control"),
+])
+def test_single_modifier_key_sends_correct_events(session,
+                                                  key_reporter,
+                                                  key_chain,
+                                                  value,
+                                                  code,
+                                                  key):
+    key_chain \
+        .key_down(value) \
+        .key_up(value) \
+        .perform()
+    all_events = get_events(session)
+    expected = [
+        {"code": code, "key": key, "type": "keydown"},
+        {"code": code, "key": key, "type": "keyup"},
+    ]
+    events = [filter_dict(e, expected[0]) for e in all_events]
+    assert events == expected
+    assert len(get_keys(key_reporter)) == 0
+
+
+@pytest.mark.parametrize("value,code,key", [
+    (Keys.ESCAPE, "Escape", "Escape"),
+    (Keys.RIGHT, "ArrowRight", "ArrowRight"),
+])
+def test_single_nonprintable_key_sends_events(session,
+                                              key_reporter,
+                                              key_chain,
+                                              value,
+                                              code,
+                                              key):
+    key_chain \
+        .key_down(value) \
+        .key_up(value) \
+        .perform()
+    expected = [
+        {"code": code, "key": key, "type": "keydown"},
+        {"code": code, "key": key, "type": "keypress"},
+        {"code": code, "key": key, "type": "keyup"},
+    ]
+    events = [filter_dict(e, expected[0]) for e in get_events(session)]
+    if len(events) == 2:
+        # most browsers don't send a keypress for non-printable keys
+        assert events == [expected[0], expected[2]]
+    else:
+        assert events == expected
+    assert len(get_keys(key_reporter)) == 0
+
+
+def test_sequence_of_keydown_printable_keys_sends_events(session,
+                                                         key_reporter,
+                                                         key_chain):
+    key_chain \
+        .key_down("a") \
+        .key_down("b") \
+        .perform()
+    expected = [
+        {"code": "KeyA", "key": "a", "type": "keydown"},
+        {"code": "KeyA", "key": "a", "type": "keypress"},
+        {"code": "KeyB", "key": "b", "type": "keydown"},
+        {"code": "KeyB", "key": "b", "type": "keypress"},
+    ]
+    events = [filter_dict(e, expected[0]) for e in get_events(session)]
+    assert events == expected
+    assert get_keys(key_reporter) == "ab"
+
+
+def test_release_char_sequence_sends_keyup_events_in_reverse(session,
+                                                             key_reporter,
+                                                             key_chain):
+    key_chain \
+        .key_down("a") \
+        .key_down("b") \
+        .perform()
+    # reset so we only see the release events
+    session.execute_script("resetEvents();")
+    session.actions.release()
+    expected = [
+        {"code": "KeyB", "key": "b", "type": "keyup"},
+        {"code": "KeyA", "key": "a", "type": "keyup"},
+    ]
+    events = [filter_dict(e, expected[0]) for e in get_events(session)]
+    assert events == expected
+
+
+def test_sequence_of_keydown_character_keys(session, key_reporter, key_chain):
+    key_chain.send_keys("ef").perform()
+    expected = [
+        {"code": "KeyE", "key": "e", "type": "keydown"},
+        {"code": "KeyE", "key": "e", "type": "keypress"},
+        {"code": "KeyE", "key": "e", "type": "keyup"},
+        {"code": "KeyF", "key": "f", "type": "keydown"},
+        {"code": "KeyF", "key": "f", "type": "keypress"},
+        {"code": "KeyF", "key": "f", "type": "keyup"},
+    ]
+    events = [filter_dict(e, expected[0]) for e in get_events(session)]
+    assert events == expected
+    assert get_keys(key_reporter) == "ef"
+
+
+def test_release_no_actions_sends_no_events(session, key_reporter, key_chain):
+    session.actions.release()
+    assert len(get_keys(key_reporter)) == 0
+    assert len(get_events(session)) == 0
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/webdriver/support/keys.py
@@ -0,0 +1,812 @@
+# Licensed to the Software Freedom Conservancy (SFC) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The SFC licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""
+The Keys implementation.
+"""
+
+from inspect import getmembers
+
+
+class Keys(object):
+    """
+    Set of special keys codes.
+
+    See also https://w3c.github.io/webdriver/webdriver-spec.html#h-keyboard-actions
+    """
+
+    NULL = u"\ue000"
+    CANCEL = u"\ue001"  # ^break
+    HELP = u"\ue002"
+    BACKSPACE = u"\ue003"
+    TAB = u"\ue004"
+    CLEAR = u"\ue005"
+    RETURN = u"\ue006"
+    ENTER = u"\ue007"
+    SHIFT = u"\ue008"
+    CONTROL = u"\ue009"
+    ALT = u"\ue00a"
+    PAUSE = u"\ue00b"
+    ESCAPE = u"\ue00c"
+    SPACE = u"\ue00d"
+    PAGE_UP = u"\ue00e"
+    PAGE_DOWN = u"\ue00f"
+    END = u"\ue010"
+    HOME = u"\ue011"
+    LEFT = u"\ue012"
+    UP = u"\ue013"
+    RIGHT = u"\ue014"
+    DOWN = u"\ue015"
+    INSERT = u"\ue016"
+    DELETE = u"\ue017"
+    SEMICOLON = u"\ue018"
+    EQUALS = u"\ue019"
+
+    NUMPAD0 = u"\ue01a"  # number pad keys
+    NUMPAD1 = u"\ue01b"
+    NUMPAD2 = u"\ue01c"
+    NUMPAD3 = u"\ue01d"
+    NUMPAD4 = u"\ue01e"
+    NUMPAD5 = u"\ue01f"
+    NUMPAD6 = u"\ue020"
+    NUMPAD7 = u"\ue021"
+    NUMPAD8 = u"\ue022"
+    NUMPAD9 = u"\ue023"
+    MULTIPLY = u"\ue024"
+    ADD = u"\ue025"
+    SEPARATOR = u"\ue026"
+    SUBTRACT = u"\ue027"
+    DECIMAL = u"\ue028"
+    DIVIDE = u"\ue029"
+
+    F1 = u"\ue031"  # function  keys
+    F2 = u"\ue032"
+    F3 = u"\ue033"
+    F4 = u"\ue034"
+    F5 = u"\ue035"
+    F6 = u"\ue036"
+    F7 = u"\ue037"
+    F8 = u"\ue038"
+    F9 = u"\ue039"
+    F10 = u"\ue03a"
+    F11 = u"\ue03b"
+    F12 = u"\ue03c"
+
+    META = u"\ue03d"
+
+    # More keys from webdriver spec
+    ZENKAKUHANKAKU = u"\uE040"
+    R_SHIFT = u"\uE050"
+    R_CONTROL = u"\uE051"
+    R_ALT = u"\uE052"
+    R_META = u"\uE053"
+    R_PAGEUP = u"\uE054"
+    R_PAGEDOWN = u"\uE055"
+    R_END = u"\uE056"
+    R_HOME = u"\uE057"
+    R_ARROWLEFT = u"\uE058"
+    R_ARROWUP = u"\uE059"
+    R_ARROWRIGHT = u"\uE05A"
+    R_ARROWDOWN = u"\uE05B"
+    R_INSERT = u"\uE05C"
+    R_DELETE = u"\uE05D"
+
+
+ALL_KEYS = getmembers(Keys, lambda x: type(x) == unicode)
+
+ALL_EVENTS = {
+    "ADD": {
+        "code": "",
+        "ctrl": False,
+        "key": "+",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue025",
+        "which": 0,
+    },
+    "ALT": {
+        "code": "AltLeft",
+        "ctrl": False,
+        "key": "Alt",
+        "location": 1,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue00a",
+        "which": 0,
+    },
+    "BACKSPACE": {
+        "code": "Backspace",
+        "ctrl": False,
+        "key": "Backspace",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue003",
+        "which": 0,
+    },
+    "CANCEL": {
+        "code": "",
+        "ctrl": False,
+        "key": "Cancel",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue001",
+        "which": 0,
+    },
+    "CLEAR": {
+        "code": "",
+        "ctrl": False,
+        "key": "Clear",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue005",
+        "which": 0,
+    },
+    "CONTROL": {
+        "code": "ControlLeft",
+        "ctrl": True,
+        "key": "Control",
+        "location": 1,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue009",
+        "which": 0,
+    },
+    "DECIMAL": {
+        "code": "NumpadDecimal",
+        "ctrl": False,
+        "key": ".",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue028",
+        "which": 0,
+    },
+    "DELETE": {
+        "code": "Delete",
+        "ctrl": False,
+        "key": "Delete",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue017",
+        "which": 0,
+    },
+    "DIVIDE": {
+        "code": "NumpadDivide",
+        "ctrl": False,
+        "key": "/",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue029",
+        "which": 0,
+    },
+    "DOWN": {
+        "code": "ArrowDown",
+        "ctrl": False,
+        "key": "ArrowDown",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue015",
+        "which": 0,
+    },
+    "END": {
+        "code": "End",
+        "ctrl": False,
+        "key": "End",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue010",
+        "which": 0,
+    },
+    "ENTER": {
+        "code": "NumpadEnter",
+        "ctrl": False,
+        "key": "Enter",
+        "location": 1,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue007",
+        "which": 0,
+    },
+    "EQUALS": {
+        "code": "",
+        "ctrl": False,
+        "key": "=",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue019",
+        "which": 0,
+    },
+    "ESCAPE": {
+        "code": "Escape",
+        "ctrl": False,
+        "key": "Escape",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue00c",
+        "which": 0,
+    },
+    "F1": {
+        "code": "F1",
+        "ctrl": False,
+        "key": "F1",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue031",
+        "which": 0,
+    },
+    "F10": {
+        "code": "F10",
+        "ctrl": False,
+        "key": "F10",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue03a",
+        "which": 0,
+    },
+    "F11": {
+        "code": "F11",
+        "ctrl": False,
+        "key": "F11",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue03b",
+        "which": 0,
+    },
+    "F12": {
+        "code": "F12",
+        "ctrl": False,
+        "key": "F12",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue03c",
+        "which": 0,
+    },
+    "F2": {
+        "code": "F2",
+        "ctrl": False,
+        "key": "F2",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue032",
+        "which": 0,
+    },
+    "F3": {
+        "code": "F3",
+        "ctrl": False,
+        "key": "F3",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue033",
+        "which": 0,
+    },
+    "F4": {
+        "code": "F4",
+        "ctrl": False,
+        "key": "F4",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue034",
+        "which": 0,
+    },
+    "F5": {
+        "code": "F5",
+        "ctrl": False,
+        "key": "F5",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue035",
+        "which": 0,
+    },
+    "F6": {
+        "code": "F6",
+        "ctrl": False,
+        "key": "F6",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue036",
+        "which": 0,
+    },
+    "F7": {
+        "code": "F7",
+        "ctrl": False,
+        "key": "F7",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue037",
+        "which": 0,
+    },
+    "F8": {
+        "code": "F8",
+        "ctrl": False,
+        "key": "F8",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue038",
+        "which": 0,
+    },
+    "F9": {
+        "code": "F9",
+        "ctrl": False,
+        "key": "F9",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue039",
+        "which": 0,
+    },
+    "HELP": {
+        "code": "Help",
+        "ctrl": False,
+        "key": "Help",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue002",
+        "which": 0,
+    },
+    "HOME": {
+        "code": "Home",
+        "ctrl": False,
+        "key": "Home",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue011",
+        "which": 0,
+    },
+    "INSERT": {
+        "code": "Insert",
+        "ctrl": False,
+        "key": "Insert",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue016",
+        "which": 0,
+    },
+    "LEFT": {
+        "code": "ArrowLeft",
+        "ctrl": False,
+        "key": "ArrowLeft",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue012",
+        "which": 0,
+    },
+    "META": {
+        "code": "OSLeft",
+        "ctrl": False,
+        "key": "Meta",
+        "location": 1,
+        "meta": True,
+        "shift": False,
+        "value": u"\ue03d",
+        "which": 0,
+    },
+    "MULTIPLY": {
+        "code": "NumpadMultiply",
+        "ctrl": False,
+        "key": "*",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue024",
+        "which": 0,
+    },
+    "NULL": {
+        "code": "",
+        "ctrl": False,
+        "key": "Unidentified",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue000",
+        "which": 0,
+    },
+    "NUMPAD0": {
+        "code": "Numpad0",
+        "ctrl": False,
+        "key": "0",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue01a",
+        "which": 0,
+    },
+    "NUMPAD1": {
+        "code": "Numpad1",
+        "ctrl": False,
+        "key": "1",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue01b",
+        "which": 0,
+    },
+    "NUMPAD2": {
+        "code": "Numpad2",
+        "ctrl": False,
+        "key": "2",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue01c",
+        "which": 0,
+    },
+    "NUMPAD3": {
+        "code": "Numpad3",
+        "ctrl": False,
+        "key": "3",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue01d",
+        "which": 0,
+    },
+    "NUMPAD4": {
+        "code": "PageDown",
+        "ctrl": False,
+        "key": "4",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue01e",
+        "which": 0,
+    },
+    "NUMPAD5": {
+        "code": "PageUp",
+        "ctrl": False,
+        "key": "5",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue01f",
+        "which": 0,
+    },
+    "NUMPAD6": {
+        "code": "Numpad6",
+        "ctrl": False,
+        "key": "6",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue020",
+        "which": 0,
+    },
+    "NUMPAD7": {
+        "code": "Numpad7",
+        "ctrl": False,
+        "key": "7",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue021",
+        "which": 0,
+    },
+    "NUMPAD8": {
+        "code": "Numpad8",
+        "ctrl": False,
+        "key": "8",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue022",
+        "which": 0,
+    },
+    "NUMPAD9": {
+        "code": "Numpad9",
+        "ctrl": False,
+        "key": "9",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue023",
+        "which": 0,
+    },
+    "PAGE_DOWN": {
+        "code": "",
+        "ctrl": False,
+        "key": "PageDown",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue00f",
+        "which": 0,
+    },
+    "PAGE_UP": {
+        "code": "",
+        "ctrl": False,
+        "key": "PageUp",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue00e",
+        "which": 0,
+    },
+    "PAUSE": {
+        "code": "",
+        "ctrl": False,
+        "key": "Pause",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue00b",
+        "which": 0,
+    },
+    "RETURN": {
+        "code": "Enter",
+        "ctrl": False,
+        "key": "Return",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue006",
+        "which": 0,
+    },
+    "RIGHT": {
+        "code": "ArrowRight",
+        "ctrl": False,
+        "key": "ArrowRight",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue014",
+        "which": 0,
+    },
+    "R_ALT": {
+        "code": "AltRight",
+        "ctrl": False,
+        "key": "Alt",
+        "location": 2,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue052",
+        "which": 0,
+    },
+    "R_ARROWDOWN": {
+        "code": "Numpad2",
+        "ctrl": False,
+        "key": "ArrowDown",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue05b",
+        "which": 0,
+    },
+    "R_ARROWLEFT": {
+        "code": "Numpad4",
+        "ctrl": False,
+        "key": "ArrowLeft",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue058",
+        "which": 0,
+    },
+    "R_ARROWRIGHT": {
+        "code": "Numpad6",
+        "ctrl": False,
+        "key": "ArrowRight",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue05a",
+        "which": 0,
+    },
+    "R_ARROWUP": {
+        "code": "Numpad8",
+        "ctrl": False,
+        "key": "ArrowUp",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue059",
+        "which": 0,
+    },
+    "R_CONTROL": {
+        "code": "ControlRight",
+        "ctrl": True,
+        "key": "Control",
+        "location": 2,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue051",
+        "which": 0,
+    },
+    "R_DELETE": {
+        "code": "NumpadDecimal",
+        "ctrl": False,
+        "key": "Delete",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue05d",
+        "which": 0,
+    },
+    "R_END": {
+        "code": "Numpad1",
+        "ctrl": False,
+        "key": "End",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue056",
+        "which": 0,
+    },
+    "R_HOME": {
+        "code": "Numpad7",
+        "ctrl": False,
+        "key": "Home",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue057",
+        "which": 0,
+    },
+    "R_INSERT": {
+        "code": "Numpad0",
+        "ctrl": False,
+        "key": "Insert",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue05c",
+        "which": 0,
+    },
+    "R_META": {
+        "code": "OSRight",
+        "ctrl": False,
+        "key": "Meta",
+        "location": 2,
+        "meta": True,
+        "shift": False,
+        "value": u"\ue053",
+        "which": 0,
+    },
+    "R_PAGEDOWN": {
+        "code": "Numpad3",
+        "ctrl": False,
+        "key": "PageDown",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue055",
+        "which": 0,
+    },
+    "R_PAGEUP": {
+        "code": "Numpad9",
+        "ctrl": False,
+        "key": "PageUp",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue054",
+        "which": 0,
+    },
+    "R_SHIFT": {
+        "code": "ShiftRight",
+        "ctrl": False,
+        "key": "Shift",
+        "location": 2,
+        "meta": False,
+        "shift": True,
+        "value": u"\ue050",
+        "which": 0,
+    },
+    "SEMICOLON": {
+        "code": "",
+        "ctrl": False,
+        "key": ";",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue018",
+        "which": 0,
+    },
+    "SEPARATOR": {
+        "code": "NumpadSubtract",
+        "ctrl": False,
+        "key": ",",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue026",
+        "which": 0,
+    },
+    "SHIFT": {
+        "code": "ShiftLeft",
+        "ctrl": False,
+        "key": "Shift",
+        "location": 1,
+        "meta": False,
+        "shift": True,
+        "value": u"\ue008",
+        "which": 0,
+    },
+    "SPACE": {
+        "code": "Space",
+        "ctrl": False,
+        "key": " ",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue00d",
+        "which": 0,
+    },
+    "SUBTRACT": {
+        "code": "",
+        "ctrl": False,
+        "key": "-",
+        "location": 3,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue027",
+        "which": 0,
+    },
+    "TAB": {
+        "code": "Tab",
+        "ctrl": False,
+        "key": "Tab",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue004",
+        "which": 0,
+    },
+    "UP": {
+        "code": "ArrowUp",
+        "ctrl": False,
+        "key": "ArrowUp",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue013",
+        "which": 0,
+    },
+    "ZENKAKUHANKAKU": {
+        "code": "",
+        "ctrl": False,
+        "key": "ZenkakuHankaku",
+        "location": 0,
+        "meta": False,
+        "shift": False,
+        "value": u"\ue040",
+        "which": 0,
+    }
+}