diff --git a/.github/badges/coverage.svg b/.github/badges/coverage.svg
index 12876e6..9029937 100644
--- a/.github/badges/coverage.svg
+++ b/.github/badges/coverage.svg
@@ -15,7 +15,7 @@
coverage
coverage
- 78%
- 78%
+ 76%
+ 76%
diff --git a/docs/assets/swiftkey_setup_1.png b/docs/assets/swiftkey_setup_1.png
new file mode 100644
index 0000000..30e3aa4
Binary files /dev/null and b/docs/assets/swiftkey_setup_1.png differ
diff --git a/docs/assets/swiftkey_setup_2.png b/docs/assets/swiftkey_setup_2.png
new file mode 100644
index 0000000..0e3d98a
Binary files /dev/null and b/docs/assets/swiftkey_setup_2.png differ
diff --git a/docs/assets/swiftkey_setup_3.png b/docs/assets/swiftkey_setup_3.png
new file mode 100644
index 0000000..2cda789
Binary files /dev/null and b/docs/assets/swiftkey_setup_3.png differ
diff --git a/docs/assets/swiftkey_setup_4.png b/docs/assets/swiftkey_setup_4.png
new file mode 100644
index 0000000..60fffa4
Binary files /dev/null and b/docs/assets/swiftkey_setup_4.png differ
diff --git a/docs/emu_setup.md b/docs/emu_setup.md
index 2fbafbc..8c6c70d 100644
--- a/docs/emu_setup.md
+++ b/docs/emu_setup.md
@@ -103,6 +103,40 @@ Make sure to disable the clipboard :
!!! failure "Layout"
For now, the only layout supported is `english US`. Make sure this is the layout GBoard is using.
+### Preparing Swiftkey
+
+Swiftkey keyboard isn't installed on the emulator by default : you need to install it first.
+
+!!! note
+ If you want to run the tests in parallel on several emulators, you need to repeat these steps for each emulator.
+
+Start the emulator, then go to Google, and paste [this link](https://play.google.com/store/apps/details?id=com.touchtype.swiftkey&hl=en_US&gl=US) to install Swiftkey.
+
+!!! tip
+ If the clipboard isn't shared with the emulator, open a terminal and run :
+
+ ```bash
+ adb shell input text "https://play.google.com/store/apps/details?id=com.touchtype.swiftkey&hl=en_US&gl=US"
+ ```
+
+Install the keyboard on your emulator :
+
+![](assets/swiftkey_setup_1.png){ width="300" }
+
+Open the app, follow the instructions to activate the keyboard.
+
+---
+
+By default, Swiftkey has the clipboard enabled, and it may interfere with the layout detection. You can disable the clipboard. First, access the clipboard settings :
+
+![](assets/swiftkey_setup_2.png){ width="300" }
+
+![](assets/swiftkey_setup_3.png){ width="300" }
+
+And disable the clipboard suggestions :
+
+![](assets/swiftkey_setup_4.png){ width="300" }
+
## Setting up iOS emulator
### Creating the emulator
diff --git a/kebbie/cmd.py b/kebbie/cmd.py
index 111c415..4ea72cf 100644
--- a/kebbie/cmd.py
+++ b/kebbie/cmd.py
@@ -28,7 +28,7 @@ def instantiate_correctors(
Returns:
The list of created Correctors.
"""
- if keyboard in ["gboard", "tappa"]:
+ if keyboard in ["gboard", "tappa", "swiftkey"]:
# Android keyboards
return [
EmulatorCorrector(
@@ -68,7 +68,7 @@ def common_args(parser: argparse.ArgumentParser):
dest="keyboard",
type=str,
required=True,
- choices=["gboard", "ios", "kbkitpro", "kbkitoss", "tappa", "fleksy"],
+ choices=["gboard", "ios", "kbkitpro", "kbkitoss", "tappa", "fleksy", "swiftkey"],
help="Which keyboard, to be tested, is currently installed on the emulator.",
)
diff --git a/kebbie/emulator.py b/kebbie/emulator.py
index 33701dd..e696993 100644
--- a/kebbie/emulator.py
+++ b/kebbie/emulator.py
@@ -32,6 +32,12 @@
FLEKSY = "fleksy"
KBKITPRO = "kbkitpro"
KBKITOSS = "kbkitoss"
+SWIFTKEY = "swiftkey"
+KEYBOARD_PACKAGE = {
+ GBOARD: "com.google.android.inputmethod.latin",
+ SWIFTKEY: "com.touchtype.swiftkey",
+ TAPPA: "com.tappa.keyboard",
+}
ANDROID_CAPABILITIES = {
"platformName": "android",
"automationName": "UiAutomator2",
@@ -98,10 +104,13 @@
"Keyboard Type - emojis": "smiley",
"Search": "enter",
"return": "enter",
+ "Enter": "enter",
"Symbol keyboard": "numbers",
"Symbols": "numbers",
+ "Symbols and numbers": "numbers",
"Keyboard Type - numeric": "numbers",
"Voice input": "mic",
+ ",, alternatives available, Voice typing, long press to activate": "mic",
"Close features menu": "magic",
"Open features menu": "magic",
"underline": "_",
@@ -123,7 +132,35 @@
"Digit keyboard": "numbers",
"More symbols": "shift",
"Keyboard Type - symbolic": "shift",
+ "Double tap for uppercase": "shift",
+ "Double tap for caps lock": "shift",
+ "capital Q": "Q",
+ "capital W": "W",
+ "capital E": "E",
+ "capital R": "R",
+ "capital T": "T",
+ "capital Y": "Y",
+ "capital U": "U",
+ "capital I": "I",
"Capital I": "I",
+ "capital O": "O",
+ "capital P": "P",
+ "capital A": "A",
+ "capital S": "S",
+ "capital D": "D",
+ "capital F": "F",
+ "capital G": "G",
+ "capital H": "H",
+ "capital J": "J",
+ "capital K": "K",
+ "capital L": "L",
+ "capital Z": "Z",
+ "capital X": "X",
+ "capital C": "C",
+ "capital V": "V",
+ "capital B": "B",
+ "capital N": "N",
+ "capital M": "M",
}
FLEKSY_LAYOUT = {
"keyboard_frame": [0, 517, 393, 266], # Only the keyboard frame is defined as absolute position
@@ -252,7 +289,7 @@ class Emulator:
ValueError: Error raised if the given platform doesn't exist.
"""
- def __init__(
+ def __init__( # noqa: C901
self,
platform: str,
keyboard: str,
@@ -293,6 +330,10 @@ def __init__(
self.last_char_is_space = False
self.last_char_is_eos = False
+ # Set the keyboard as default
+ if self.platform == ANDROID:
+ self.select_keyboard(keyboard)
+
# Get the right layout
if self.keyboard == GBOARD:
self.detected = GboardLayoutDetector(self.driver, self._tap)
@@ -312,10 +353,13 @@ def __init__(
elif self.keyboard == KBKITOSS:
self.detected = KbkitossLayoutDetector(self.driver, self._tap)
self.layout = self.detected.layout
+ elif self.keyboard == SWIFTKEY:
+ self.detected = SwiftkeyLayoutDetector(self.driver, self._tap)
+ self.layout = self.detected.layout
else:
raise ValueError(
- f"Unknown keyboard : {self.keyboard}. Please specify `{GBOARD}`, `{TAPPA}`, `{FLEKSY}`, `{KBKITPRO}`, "
- f"`{KBKITOSS}` or `{IOS}`."
+ f"Unknown keyboard : {self.keyboard}. Please specify `{GBOARD}`, `{TAPPA}`, `{FLEKSY}`, "
+ f"`{SWIFTKEY}`, `{KBKITPRO}`, `{KBKITOSS}` or `{IOS}`."
)
self.typing_field.clear()
@@ -352,6 +396,33 @@ def get_android_devices() -> List[str]:
devices = [d.split()[0] for d in devices if not (d.startswith("List of devices attached") or len(d) == 0)]
return devices
+ def select_keyboard(self, keyboard):
+ """Searches the IME of the desired keyboard and selects it, only for Android.
+
+ Args:
+ keyboard (str): Keyboard to search.
+ """
+ if keyboard not in KEYBOARD_PACKAGE:
+ print(
+ f"Warning ! {keyboard}'s IME isn't provided (in `KEYBOARD_PACKAGE`), can't automatically select the "
+ "keyboard."
+ )
+ return
+
+ ime_list = subprocess.check_output(["adb", "shell", "ime", "list", "-s"], universal_newlines=True)
+ ime_name = None
+ for ime in ime_list.strip().split("\n"):
+ if KEYBOARD_PACKAGE[keyboard] in ime:
+ ime_name = ime
+ break
+ if ime_name:
+ subprocess.run(
+ ["adb", "shell", "settings", "put", "secure", "show_ime_with_hard_keyboard", "1"],
+ stdout=subprocess.PIPE,
+ )
+ subprocess.run(["adb", "shell", "ime", "enable", ime_name], stdout=subprocess.PIPE)
+ subprocess.run(["adb", "shell", "ime", "set", ime_name], stdout=subprocess.PIPE)
+
def get_ios_devices() -> List[Tuple[str, str]]:
"""Static method that uses the `xcrun simctl` command to retrieve the
list of booted devices.
@@ -453,6 +524,9 @@ def type_characters(self, characters: str): # noqa: C901
if self.kb_is_upper:
# If the keyboard is in uppercase mode, change it to lowercase
self._tap(self.layout["uppercase"]["shift"])
+ if self.keyboard == SWIFTKEY:
+ # Swiftkey needs double tap, otherwise we are capslocking
+ self._tap(self.layout["uppercase"]["shift"])
self._tap(self.layout["lowercase"][c])
elif c in self.layout["uppercase"]:
# The character is an uppercase character
@@ -470,9 +544,9 @@ def type_characters(self, characters: str): # noqa: C901
self._tap(self.layout["lowercase"]["numbers"])
self._tap(self.layout["numbers"][c])
- if c != "'" or self.keyboard == GBOARD:
+ if c != "'" or self.keyboard in [GBOARD, SWIFTKEY]:
# For some reason, when `'` is typed, the keyboard automatically goes back
- # to lowercase, so no need to re-tap the button (unless the keyboard is GBoard).
+ # to lowercase, so no need to re-tap the button (unless the keyboard is GBoard / Swiftkey).
# In all other cases, switch back to letters keyboard
self._tap(self.layout["numbers"]["letters"])
else:
@@ -866,7 +940,7 @@ class GboardLayoutDetector(LayoutDetector):
def __init__(self, *args, **kwargs):
super().__init__(
*args,
- xpath_root="./*/*[@package='com.google.android.inputmethod.latin']",
+ xpath_root=f"./*/*[@package='{KEYBOARD_PACKAGE[GBOARD]}']",
xpath_keys=".//*[@resource-id][@content-desc]",
**kwargs,
)
@@ -1009,6 +1083,40 @@ def get_suggestions(self) -> List[str]:
return suggestions
+class SwiftkeyLayoutDetector(LayoutDetector):
+ """Layout detector for the Swiftkey keyboard. See `LayoutDetector` for more
+ information.
+ """
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(
+ *args,
+ xpath_root=f"./*/*[@package='{KEYBOARD_PACKAGE[SWIFTKEY]}']",
+ xpath_keys=".//*[@class='android.view.View'][@content-desc]",
+ **kwargs,
+ )
+
+ def get_suggestions(self) -> List[str]:
+ """Method to retrieve the keyboard suggestions from the XML tree.
+
+ Returns:
+ List of suggestions from the keyboard.
+ """
+ suggestions = []
+
+ # Get the raw content as text, weed out useless elements
+ for data in self.driver.page_source.split(" List[str]:
suggestions = []
# Get the raw content as text, weed out useless elements
- section = self.driver.page_source.split("com.tappa.keyboard:id/toolbar")[1].split(
+ section = self.driver.page_source.split(f"{KEYBOARD_PACKAGE[TAPPA]}:id/toolbar")[1].split(
""
)[0]
diff --git a/tests/test_emulator.py b/tests/test_emulator.py
index 4b15bef..eda5d62 100644
--- a/tests/test_emulator.py
+++ b/tests/test_emulator.py
@@ -23,11 +23,13 @@ class SubprocessResult:
def test_get_android_devices(monkeypatch):
def android_subprocess(*args, **kwargs):
return SubprocessResult(
- DummyStdout("""List of devices attached
+ DummyStdout(
+ """List of devices attached
emulator-5554 device
emulator-5558 device
-""")
+"""
+ )
)
monkeypatch.setattr(subprocess, "run", android_subprocess)
@@ -42,7 +44,8 @@ def android_subprocess(*args, **kwargs):
def test_get_ios_devices(monkeypatch):
def ios_subprocess(*args, **kwargs):
return SubprocessResult(
- DummyStdout("""== Devices ==
+ DummyStdout(
+ """== Devices ==
-- iOS 14.4 --
iPhone 12 mini (8A192CB8-A72C-4BBA-9A98-2476E66ABEF8) (Shutdown) (unavailable)
iPhone 12 (C0E1F6AB-FDA5-4953-BB22-7CDB09D3B303) (Shutdown) (unavailable)
@@ -71,7 +74,8 @@ def ios_subprocess(*args, **kwargs):
iPad mini (6th generation) (EC7F8EF2-D5A6-437B-9531-E2DBE924FB5A) (Shutdown) (unavailable)
iPad Pro (11-inch) (4th generation) (49F56D2F-5722-41CD-9C11-D4979084DA3E) (Shutdown) (unavailable)
iPad Pro (12.9-inch) (6th generation) (08E5485D-D293-4B7B-9831-F29D55EDA053) (Shutdown) (unavailable)
-""")
+"""
+ )
)
monkeypatch.setattr(subprocess, "run", ios_subprocess)