import logging
import os
import subprocess
import sys
import time
import unittest
from typing import Final
from appium import webdriver
from appium.options.common.base import AppiumOptions
from appium.webdriver.common.appiumby import AppiumBy
from gi.repository import GLib
from selenium.webdriver.support.ui import WebDriverWait
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, "desktop"))
from desktoptest import name_has_owner, start_kactivitymanagerd
KDE_VERSION: Final = 6
KCM_ID: Final = "kcm_keys"
KGLOBALACCELD_PATH: Final = os.getenv("KGLOBALACCELD_PATH", "/usr/libexec/kglobalacceld")
class KCMTest(unittest.TestCase):
"""
Tests for kcm_keys
"""
driver: webdriver.Remote
kactivitymanagerd: subprocess.Popen | None = None
kglobalacceld: subprocess.Popen | None = None
@classmethod
def setUpClass(cls) -> None:
if not name_has_owner(None, "org.kde.kglobalaccel"):
cls.kglobalacceld = subprocess.Popen([KGLOBALACCELD_PATH], stdout=sys.stderr, stderr=sys.stderr)
cls.kactivitymanagerd = start_kactivitymanagerd()
options = AppiumOptions()
options.set_capability("app", f"kcmshell{KDE_VERSION} {KCM_ID}")
options.set_capability("environ", {
"LC_ALL": "en_US.UTF-8",
"QT_FATAL_WARNINGS": "1",
"QT_LOGGING_RULES": "qt.accessibility.atspi.warning=false;qt.qml.typeresolution.cycle.warning=false;qt.qpa.wayland.warning=false;kf.plasma.core.warning=false;kf.windowsystem.warning=false;kf.kirigami.platform.warning=false",
})
options.set_capability("timeouts", {'implicit': 10000})
cls.driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', options=options)
def tearDown(self) -> None:
"""
Take screenshot when the current test fails
"""
if not self._outcome.result.wasSuccessful():
subprocess.check_call(["import", "-window", "root", f"failed_test_shot_{KCM_ID}_#{self.id()}.png"])
@classmethod
def tearDownClass(cls) -> None:
"""
Make sure to terminate the driver again, lest it dangles.
"""
cls.driver.find_element(AppiumBy.XPATH, "//*[@name='Cancel' and contains(@accessibility-id, 'Button')]").click()
if cls.kactivitymanagerd is not None:
cls.kactivitymanagerd.kill()
if cls.kglobalacceld is not None:
subprocess.check_output([f"kquitapp{KDE_VERSION}", f"kglobalaccel"], stderr=sys.stderr)
cls.kglobalacceld.wait()
cls.driver.quit()
def test_0_open(self) -> None:
self.driver.find_element(AppiumBy.NAME, "Select an item from the list to view its shortcuts here")
self.driver.find_element(by=AppiumBy.XPATH, value="//heading[@name='Applications' and contains(@accessibility-id, 'ListSectionHeader')]")
self.driver.find_element(by=AppiumBy.XPATH, value="//heading[@name='Common Actions' and contains(@accessibility-id, 'ListSectionHeader')]")
def test_1_application_shortcuts(self) -> None:
"""
List some applications' shortcuts
"""
self.assertTrue(name_has_owner(None, "org.kde.kglobalaccel"))
self.driver.find_element(by=AppiumBy.XPATH, value="//list_item[@name='KRunner']").click()
self.driver.find_element(by=AppiumBy.NAME, value="Run command on clipboard contents:Alt+Shift+F2").click()
self.driver.find_element(by=AppiumBy.NAME, value="Default shortcut Alt+Shift+F2 is enabled.")
self.driver.find_element(by=AppiumBy.XPATH, value="//list_item[@name='Emoji Selector']").click()
self.driver.find_element(by=AppiumBy.NAME, value="Launch:")
self.driver.find_element(by=AppiumBy.NAME, value="Meta+.")
self.driver.find_element(by=AppiumBy.NAME, value="Meta+Ctrl+Alt+Shift+Space")
self.driver.find_element(by=AppiumBy.NAME, value="Add custom shortcut").click()
subprocess.check_call(["xdotool", "key", "Ctrl+0x002e"])
delete_button = self.driver.find_element(by=AppiumBy.NAME, value="Delete shortcut")
apply_button = self.driver.find_element(AppiumBy.XPATH, "//*[@name='Apply' and contains(@accessibility-id, 'Button')]")
apply_button.click()
wait = WebDriverWait(self.driver, 10)
wait.until(lambda _: "Ctrl+." in subprocess.check_output([f"kreadconfig{KDE_VERSION}", "--file", "kglobalshortcutsrc", "--group", "services", "--group", "org.kde.plasma.emojier.desktop", "--key", "_launch"]).decode(encoding="utf-8").strip())
delete_button.click()
apply_button.click()
wait.until_not(lambda _: "Ctrl+." in subprocess.check_output([f"kreadconfig{KDE_VERSION}", "--file", "kglobalshortcutsrc", "--group", "services", "--group", "org.kde.plasma.emojier.desktop", "--key", "_launch"]).decode(encoding="utf-8").strip())
def test_2_add_new(self) -> None:
self.assertTrue(name_has_owner(None, "org.kde.kglobalaccel"))
window_id = subprocess.check_output(["xdotool", "search", "Shortcuts"]).decode().strip()
subprocess.check_call(["xdotool", "windowmove", window_id, "0", "0"])
button_rect = self.driver.find_element(AppiumBy.NAME, "Add New").rect
scale = 1.0
if "KDECI_BUILD" not in os.environ:
xrdb_output = subprocess.check_output(["xrdb", "-query"]).decode().splitlines()
for l in xrdb_output:
if l.startswith("Xft.dpi"):
scale = int(l.removeprefix("Xft.dpi:").strip()) / 90
logging.info(f"button rect: {button_rect}")
subprocess.check_call(["xdotool", "mousemove", str(int(button_rect["x"] * scale + 10)), str(int(button_rect["y"] * scale + 10)), "click", "1"])
self.driver.find_element(AppiumBy.NAME, "Add new command or script").click()
command = "echo appiumtest > /dev/null"
self.driver.find_element(AppiumBy.NAME, "Command").send_keys(command)
time.sleep(1)
self.driver.find_element(AppiumBy.NAME, "Add").click()
self.driver.find_element(AppiumBy.NAME, f"{command}:")
self.driver.find_element(AppiumBy.NAME, "No default shortcuts")
self.driver.find_element(by=AppiumBy.NAME, value="Add custom shortcut").click()
subprocess.check_call(["xdotool", "key", "Ctrl+0x002e"])
delete_button = self.driver.find_element(by=AppiumBy.NAME, value="Delete shortcut")
apply_button = self.driver.find_element(AppiumBy.XPATH, "//*[@name='Apply' and contains(@accessibility-id, 'Button')]")
apply_button.click()
custom_command_desktop_file = "net.local.echo.desktop"
wait = WebDriverWait(self.driver, 10)
wait.until(lambda _: subprocess.check_output([f"kreadconfig{KDE_VERSION}", "--file", "kglobalshortcutsrc", "--group", "services", "--group", custom_command_desktop_file, "--key", "_launch"]).decode(encoding="utf-8").strip() == "Ctrl+.")
with open(os.path.join(GLib.get_user_data_dir(), "applications", custom_command_desktop_file), encoding="utf-8") as file_handler:
for line in file_handler:
if line.startswith("Exec="):
self.assertIn(command, line)
if line.startswith("X-KDE-GlobalAccel-CommandShortcut"):
self.assertIn("true", line)
delete_button.click()
apply_button.click()
wait.until(lambda _: subprocess.check_output([f"kreadconfig{KDE_VERSION}", "--file", "kglobalshortcutsrc", "--group", "services", "--group", custom_command_desktop_file, "--key", "_launch"]).decode(encoding="utf-8").strip() == "")
self.assertTrue(os.path.exists(os.path.join(GLib.get_user_data_dir(), "applications", custom_command_desktop_file)))
self.driver.find_element(by=AppiumBy.NAME, value="Remove all shortcuts for ").click()
self.driver.find_element(by=AppiumBy.NAME, value="Undo deletion").click()
self.driver.find_element(by=AppiumBy.NAME, value="Remove all shortcuts for ").click()
apply_button.click()
wait.until_not(lambda _: os.path.exists(os.path.join(GLib.get_user_data_dir(), "applications", custom_command_desktop_file)))
if __name__ == '__main__':
assert os.getenv("LC_ALL") == "en_US.UTF-8"
assert os.getenv("TEST_WITH_KWIN_WAYLAND") == "0"
logging.getLogger().setLevel(logging.INFO)
unittest.main()