#!/usr/bin/env python3 # SPDX-License-Identifier: BSD-3-Clause # SPDX-FileCopyrightText: 2022-2023 Harald Sitter 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 selenium.common.exceptions import NoSuchElementException from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import WebDriverWait sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir, "desktop")) from desktoptest import name_has_owner, start_kactivitymanagerd WIDGET_ID: Final = "org.kde.plasma.kickoff" KDE_VERSION: Final = 6 class KickoffTests(unittest.TestCase): driver: webdriver.Remote kactivitymanagerd: subprocess.Popen | None = None @classmethod def setUpClass(cls) -> None: cls.kactivitymanagerd = start_kactivitymanagerd() options = AppiumOptions() options.set_capability("environ", { "LC_ALL": "en_US.UTF-8", }) options.set_capability("app", f"plasmawindowed -p org.kde.plasma.desktop {WIDGET_ID}") options.set_capability("timeouts", {'implicit': 10000}) cls.driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', options=options) @classmethod def tearDownClass(self) -> None: subprocess.check_call([f"kquitapp{KDE_VERSION}", "plasmawindowed"]) self.driver.quit() if self.kactivitymanagerd is not None: self.kactivitymanagerd.terminate() self.kactivitymanagerd.wait() def tearDown(self) -> None: """ Take screenshot when the current test fails """ if not self._outcome.result.wasSuccessful(): self.driver.get_screenshot_as_file(f"failed_test_shot_{WIDGET_ID}_#{self.id()}.png") print(self.driver.page_source, file=sys.stderr) def test_1_keyboard_navigation(self) -> None: focused_elements = self.driver.find_elements(by=AppiumBy.XPATH, value="//table_cell[contains(@states, 'focused')]") self.assertEqual(len(focused_elements), 1) first_favorite = focused_elements[0].id self.assertIn("KickoffGridDelegate", focused_elements[0].get_attribute('accessibility-id')) # Go right to second favorite ActionChains(self.driver).send_keys(Keys.RIGHT).perform() focused_elements = self.driver.find_elements(by=AppiumBy.XPATH, value="//table_cell[contains(@states, 'focused')]") self.assertEqual(len(focused_elements), 1) self.assertNotEqual(first_favorite, focused_elements[0].id) # Go left to first favorite again ActionChains(self.driver).send_keys(Keys.LEFT).perform() focused_elements = self.driver.find_elements(by=AppiumBy.XPATH, value="//table_cell[contains(@states, 'focused')]") self.assertEqual(len(focused_elements), 1) self.assertEqual(first_favorite, focused_elements[0].id) # Go further left to the category list ActionChains(self.driver).key_down(Keys.SHIFT).send_keys(Keys.TAB).key_up(Keys.ALT).perform() focused_elements = self.driver.find_elements(by=AppiumBy.XPATH, value="//list_item[contains(@states, 'focused')]") self.assertEqual(len(focused_elements), 1) self.assertNotEqual(first_favorite, focused_elements[0].id) favorites_category = focused_elements[0].id # Go down to the 'all apps' category ActionChains(self.driver).send_keys(Keys.DOWN).perform() focused_elements = self.driver.find_elements(by=AppiumBy.XPATH, value="//list_item[contains(@states, 'focused')]") self.assertEqual(len(focused_elements), 1) self.assertNotEqual(favorites_category, focused_elements[0].id) # Go right to the first all app. This must not be a grid delegate anymore (favorites are griddelegates) ActionChains(self.driver).send_keys(Keys.RIGHT).perform() focused_elements = self.driver.find_elements(by=AppiumBy.XPATH, value="//list_item[contains(@states, 'focused')]") self.assertEqual(len(focused_elements), 1) self.assertNotEqual(favorites_category, focused_elements[0].id) self.assertNotEqual(first_favorite, focused_elements[0].id) self.assertNotIn("KickoffGridDelegate", focused_elements[0].get_attribute('accessibility-id')) first_all_app = focused_elements[0].id # Go down to second all app ActionChains(self.driver).send_keys(Keys.DOWN).perform() focused_elements = self.driver.find_elements(by=AppiumBy.XPATH, value="//list_item[contains(@states, 'focused')]") self.assertEqual(len(focused_elements), 1) self.assertNotEqual(first_all_app, focused_elements[0].id) # Hitting Tab should get us to the "menu bar" at the bottom ActionChains(self.driver).send_keys(Keys.TAB).perform() focused_elements = self.driver.find_elements(by=AppiumBy.XPATH, value="//page_tab[contains(@states, 'focused') and @name='Applications']") self.assertEqual(len(focused_elements), 1) # Hitting Right should get us to the "Places" page ActionChains(self.driver).send_keys(Keys.RIGHT).perform() self.driver.find_element(AppiumBy.NAME, "Frequently Used") # Hitting Right again should move focus to the power action button ActionChains(self.driver).send_keys(Keys.RIGHT).perform() try: self.driver.find_element(by=AppiumBy.XPATH, value="//push_button_menu[contains(@states, 'focused') and @name='Session']") except NoSuchElementException: self.driver.find_element(by=AppiumBy.XPATH, value="//button[contains(@states, 'focused') and @name='Sleep']") def test_2_search_calculator(self) -> None: search_field = self.driver.find_element(by=AppiumBy.NAME, value="Search") search_field.send_keys("1") search_field.send_keys("2345+67890") element = self.driver.find_element(by=AppiumBy.CLASS_NAME, value="[list item | 80235]") search_field.clear() WebDriverWait(self.driver, 5).until_not(lambda _: element.is_displayed()) def test_3_search_app(self) -> None: # Emoji Selector is the only actual application we install from workspace :| search_field = self.driver.find_element(by=AppiumBy.NAME, value="Search") search_field.send_keys("Emoji Selector") element = self.driver.find_element(by=AppiumBy.CLASS_NAME, value="[list item | Emoji Selector]") element.click() WebDriverWait(self.driver, 5).until(lambda _: name_has_owner(None, "org.kde.plasma.emojier")) subprocess.check_call([f"kquitapp{KDE_VERSION}", "plasma.emojier"]) search_field.clear() WebDriverWait(self.driver, 5).until_not(lambda _: element.is_displayed()) if __name__ == '__main__': unittest.main()