File indexing completed on 2024-05-05 17:43:16
0001 #!/usr/bin/env python3 0002 0003 # SPDX-FileCopyrightText: 2019 The GNOME Music developers 0004 # SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org> 0005 # SPDX-FileCopyrightText: 2023 Fushan Wen <qydwhotmail@gmail.com> 0006 # SPDX-License-Identifier: GPL-2.0-or-later 0007 0008 # pylint: disable=too-many-arguments 0009 0010 import threading 0011 import unittest 0012 from os import getcwd, getpid, path 0013 from typing import Any, Final 0014 0015 from appium import webdriver 0016 from appium.options.common.base import AppiumOptions 0017 from appium.webdriver.common.appiumby import AppiumBy 0018 from gi.repository import Gio, GLib 0019 from selenium.webdriver.support import expected_conditions as EC 0020 from selenium.webdriver.support.ui import WebDriverWait 0021 0022 WIDGET_ID: Final = "org.kde.plasma.mediacontroller" 0023 0024 0025 class Mpris2: 0026 """ 0027 MPRIS2 interface implemented in GDBus, since dbus-python does not support property 0028 """ 0029 0030 BASE_IFACE: Final = "org.mpris.MediaPlayer2" 0031 OBJECT_PATH: Final = "/org/mpris/MediaPlayer2" 0032 PLAYER_IFACE: Final = GLib.Variant('s', "org.mpris.MediaPlayer2.Player") 0033 APP_INTERFACE: Final = f"org.mpris.MediaPlayer2.appiumtest.instance{str(getpid())}" 0034 0035 __connection: Gio.DBusConnection 0036 0037 def __init__(self) -> None: 0038 self.metadata: dict[str, GLib.Variant] = { 0039 'mpris:trackid': GLib.Variant('o', "/appiumtest/1"), 0040 'xesam:url': GLib.Variant('s', "file://" + path.join(getcwd(), "resources/media1.mp3")), 0041 'mpris:length': GLib.Variant('x', 30 * 10e6), # ms 0042 'xesam:title': GLib.Variant('s', "Konqi ❤️️ Katie"), 0043 'xesam:album': GLib.Variant('s', "The Best of Konqi"), 0044 'xesam:artist': GLib.Variant('as', ["KDE Community", "La communauté KDE"]), 0045 "mpris:artUrl": GLib.Variant('s', "file://" + path.join(getcwd(), "resources/mediacontrollertests_art1.png")) 0046 } 0047 self.base_properties: dict[str, GLib.Variant] = { 0048 "CanQuit": GLib.Variant('b', True), 0049 "Fullscreen": GLib.Variant('b', False), 0050 "CanSetFullscreen": GLib.Variant('b', False), 0051 "CanRaise": GLib.Variant('b', True), 0052 "HasTrackList": GLib.Variant('b', True), 0053 "Identity": GLib.Variant('s', 'AppiumTest'), 0054 "DesktopEntry": GLib.Variant('s', 'appiumtest'), 0055 "SupportedUriSchemes": GLib.Variant('as', [ 0056 'file', 0057 ]), 0058 "SupportedMimeTypes": GLib.Variant('as', [ 0059 'application/ogg', 0060 ]), 0061 } 0062 self.player_properties: dict[str, GLib.Variant] = { 0063 'PlaybackStatus': GLib.Variant('s', "Stopped"), 0064 'LoopStatus': GLib.Variant('s', "None"), 0065 'Rate': GLib.Variant('d', 1.0), 0066 'Shuffle': GLib.Variant('b', False), 0067 'Metadata': GLib.Variant('a{sv}', self.metadata), 0068 'Position': GLib.Variant('x', 0), 0069 'MinimumRate': GLib.Variant('d', 1.0), 0070 'MaximumRate': GLib.Variant('d', 1.0), 0071 'Volume': GLib.Variant('d', 1.0), 0072 'CanGoNext': GLib.Variant('b', True), 0073 'CanGoPrevious': GLib.Variant('b', True), 0074 'CanPlay': GLib.Variant('b', True), 0075 'CanPause': GLib.Variant('b', True), 0076 'CanSeek': GLib.Variant('b', True), 0077 'CanControl': GLib.Variant('b', True), 0078 } 0079 0080 self.__owner_id: int = Gio.bus_own_name(Gio.BusType.SESSION, self.APP_INTERFACE, Gio.BusNameOwnerFlags.NONE, self.on_bus_acquired, self.on_name_acquired, self.on_name_lost) 0081 assert self.__owner_id > 0 0082 0083 def quit(self) -> None: 0084 self.stop(self.__connection, self.OBJECT_PATH) 0085 Gio.bus_unown_name(self.__owner_id) 0086 print("Player exit") 0087 0088 def on_bus_acquired(self, connection: Gio.DBusConnection, name: str, *args) -> None: 0089 """ 0090 Interface is ready, now register objects. 0091 """ 0092 self.__connection = connection 0093 0094 properties_introspection_xml: str = '\n'.join(open("../dataengines/mpris2/org.freedesktop.DBus.Properties.xml", encoding="utf-8").readlines()) 0095 introspection_data = Gio.DBusNodeInfo.new_for_xml(properties_introspection_xml) 0096 reg_id = connection.register_object(self.OBJECT_PATH, introspection_data.interfaces[0], self.properties_handle_method_call, None, None) 0097 0098 player_introspection_xml: str = '\n'.join(open("../dataengines/mpris2/org.mpris.MediaPlayer2.Player.xml", encoding="utf-8").readlines()) 0099 0100 introspection_data = Gio.DBusNodeInfo.new_for_xml(player_introspection_xml) 0101 reg_id = connection.register_object(self.OBJECT_PATH, introspection_data.interfaces[0], self.player_handle_method_call, self.player_handle_get_property, self.player_handle_set_property) 0102 assert reg_id != 0 0103 0104 interface_introspection_xml: str = '\n'.join(open("../dataengines/mpris2/org.mpris.MediaPlayer2.xml", encoding="utf-8").readlines()) 0105 introspection_data = Gio.DBusNodeInfo.new_for_xml(interface_introspection_xml) 0106 reg_id = connection.register_object(self.OBJECT_PATH, introspection_data.interfaces[0], self.interface_handle_method_call, self.interface_handle_get_property, self.interface_handle_set_property) 0107 assert reg_id != 0 0108 0109 def on_name_acquired(self, connection, name, *args) -> None: 0110 pass 0111 0112 def on_name_lost(self, connection, name, *args) -> None: 0113 pass 0114 0115 def properties_handle_method_call(self, connection: Gio.DBusConnection, sender: str, object_path: str, interface_name: str, method_name: str, parameters: GLib.Variant, invocation: Gio.DBusMethodInvocation) -> None: 0116 """ 0117 Handles method calls for org.freedesktop.DBus.Properties 0118 """ 0119 assert interface_name == "org.freedesktop.DBus.Properties", f"Wrong interface name {interface_name} from {sender}" 0120 0121 if method_name == "Get": 0122 ret: Any = None 0123 interface: str = parameters[0] 0124 property_name: str = parameters[1] 0125 if interface == self.BASE_IFACE: 0126 ret = self.base_properties[property_name] 0127 elif interface == self.PLAYER_IFACE.get_string(): 0128 ret = self.player_properties[property_name] 0129 else: 0130 assert False, f"Unknown interface {interface}" 0131 0132 print(f"Get: {interface} {property_name} {ret}") 0133 # https://bugzilla.gnome.org/show_bug.cgi?id=765603 0134 invocation.return_value(GLib.Variant('(v)', ret)) 0135 0136 elif method_name == "GetAll": 0137 interface: str = parameters[0] 0138 if interface == self.BASE_IFACE: 0139 ret = GLib.Variant('a{sv}', self.base_properties) 0140 elif interface == self.PLAYER_IFACE.get_string(): 0141 ret = GLib.Variant('a{sv}', self.player_properties) 0142 else: 0143 assert False, f"Unknown interface {interface}" 0144 0145 print(f"GetAll: {interface} {ret}") 0146 invocation.return_value(GLib.Variant.new_tuple(ret)) 0147 0148 elif method_name == "Set": 0149 interface: str = parameters[0] 0150 property_name: str = parameters[1] 0151 value: Any = parameters[2] 0152 0153 if interface != self.PLAYER_IFACE.get_string(): 0154 assert False, f"Wrong interface {interface}" 0155 0156 if property_name == "Rate": 0157 self.set_rate(value, connection, object_path) 0158 elif property_name == "LoopStatus": 0159 self.set_repeat(value, connection, object_path) 0160 elif property_name == "Shuffle": 0161 self.set_shuffle(value, connection, object_path) 0162 elif property_name == "Volume": 0163 self.set_volume(value, connection, object_path) 0164 else: 0165 assert False, f"Unknown property {property_name}" 0166 0167 print(f"Set: {interface} {property_name} {value}") 0168 0169 else: 0170 invocation.return_value(None) 0171 0172 def set_playing(self, playing: bool, connection: Gio.DBusConnection, object_path: str) -> None: 0173 """ 0174 Changes the playing state 0175 """ 0176 self.player_properties["PlaybackStatus"] = GLib.Variant('s', "Playing" if playing else "Paused") 0177 changed_properties = GLib.Variant('a{sv}', { 0178 "PlaybackStatus": self.player_properties["PlaybackStatus"], 0179 }) 0180 Gio.DBusConnection.emit_signal(connection, None, object_path, "org.freedesktop.DBus.Properties", "PropertiesChanged", GLib.Variant.new_tuple(self.PLAYER_IFACE, changed_properties, GLib.Variant('as', ()))) 0181 0182 def stop(self, connection: Gio.DBusConnection, object_path: str) -> None: 0183 """ 0184 Changes the playing state 0185 """ 0186 self.player_properties["PlaybackStatus"] = GLib.Variant('s', "Stopped") 0187 self.player_properties["Metadata"] = GLib.Variant('a{sv}', {}) 0188 changed_properties = GLib.Variant('a{sv}', { 0189 "PlaybackStatus": self.player_properties["PlaybackStatus"], 0190 "Metadata": self.player_properties["Metadata"], 0191 }) 0192 Gio.DBusConnection.emit_signal(connection, None, object_path, "org.freedesktop.DBus.Properties", "PropertiesChanged", GLib.Variant.new_tuple(self.PLAYER_IFACE, changed_properties, GLib.Variant('as', ()))) 0193 0194 def set_rate(self, rate: float, connection: Gio.DBusConnection, object_path: str) -> None: 0195 """ 0196 Changes the shuffle state 0197 """ 0198 assert isinstance(rate, float), f"argument is not a float but {type(rate)}" 0199 assert self.player_properties["MinimumRate"].get_double() <= rate <= self.player_properties["MaximumRate"].get_double(), f"Rate {rate} is out of bounds" 0200 self.player_properties["Rate"] = GLib.Variant('d', rate) 0201 changed_properties = GLib.Variant('a{sv}', { 0202 "Rate": self.player_properties["Rate"], 0203 }) 0204 Gio.DBusConnection.emit_signal(connection, None, object_path, "org.freedesktop.DBus.Properties", "PropertiesChanged", GLib.Variant.new_tuple(self.PLAYER_IFACE, changed_properties, GLib.Variant('as', ()))) 0205 0206 def set_shuffle(self, shuffle: bool, connection: Gio.DBusConnection, object_path: str) -> None: 0207 """ 0208 Changes the shuffle state 0209 """ 0210 assert isinstance(shuffle, bool), f"argument is not a boolean but {type(shuffle)}" 0211 self.player_properties["Shuffle"] = GLib.Variant('b', shuffle) 0212 changed_properties = GLib.Variant('a{sv}', { 0213 "Shuffle": self.player_properties["Shuffle"], 0214 }) 0215 Gio.DBusConnection.emit_signal(connection, None, object_path, "org.freedesktop.DBus.Properties", "PropertiesChanged", GLib.Variant.new_tuple(self.PLAYER_IFACE, changed_properties, GLib.Variant('as', ()))) 0216 0217 def set_repeat(self, repeat: str, connection: Gio.DBusConnection, object_path: str) -> None: 0218 """ 0219 Changes the loop state 0220 """ 0221 assert isinstance(repeat, str), f"argument is not a string but {type(repeat)}" 0222 self.player_properties["LoopStatus"] = GLib.Variant('s', repeat) 0223 changed_properties = GLib.Variant('a{sv}', { 0224 "LoopStatus": self.player_properties["LoopStatus"], 0225 }) 0226 Gio.DBusConnection.emit_signal(connection, None, object_path, "org.freedesktop.DBus.Properties", "PropertiesChanged", GLib.Variant.new_tuple(self.PLAYER_IFACE, changed_properties, GLib.Variant('as', ()))) 0227 0228 def set_volume(self, volume: float, connection: Gio.DBusConnection, object_path: str) -> None: 0229 """ 0230 Adjusts the volume 0231 """ 0232 assert isinstance(volume, float) and 0 <= volume <= 1, f"Invalid volume {volume} of type {type(volume)}" 0233 self.player_properties["Volume"] = GLib.Variant('d', volume) 0234 changed_properties = GLib.Variant('a{sv}', { 0235 "Volume": self.player_properties["Volume"], 0236 }) 0237 Gio.DBusConnection.emit_signal(connection, None, object_path, "org.freedesktop.DBus.Properties", "PropertiesChanged", GLib.Variant.new_tuple(self.PLAYER_IFACE, changed_properties, GLib.Variant('as', ()))) 0238 0239 def player_handle_method_call(self, connection: Gio.DBusConnection, sender: str, object_path: str, interface_name: str, method_name: str, parameters: GLib.Variant, invocation: Gio.DBusMethodInvocation) -> None: 0240 """ 0241 Handles method calls for org.mpris.MediaPlayer2.Player 0242 """ 0243 assert interface_name == "org.mpris.MediaPlayer2.Player", f"Wrong interface name {interface_name} from {sender}" 0244 0245 print(f"player_handle_method_call method_name: {method_name}") 0246 0247 if method_name == "Next": 0248 self.metadata = { 0249 'mpris:trackid': GLib.Variant('o', "/appiumtest/2"), 0250 'xesam:url': GLib.Variant('s', "file://" + path.join(getcwd(), "resources/media2.mp3")), 0251 'mpris:length': GLib.Variant('x', 90 * 10e6), # ms 0252 'xesam:title': GLib.Variant('s', "Konqi's Favorite"), 0253 'xesam:album': GLib.Variant('s', "The Best of Konqi"), 0254 'xesam:artist': GLib.Variant('as', ["KDE Community", "KDE 社区"]), 0255 "mpris:artUrl": GLib.Variant('s', "file://" + path.join(getcwd(), "resources/mediacontrollertests_art2.png")) 0256 } 0257 self.player_properties["Metadata"] = GLib.Variant('a{sv}', self.metadata) 0258 self.player_properties["CanGoPrevious"] = GLib.Variant('b', True) 0259 self.player_properties["CanGoNext"] = GLib.Variant('b', False) 0260 changed_properties = GLib.Variant('a{sv}', { 0261 'Metadata': self.player_properties["Metadata"], 0262 "CanGoPrevious": self.player_properties["CanGoPrevious"], 0263 "CanGoNext": self.player_properties["CanGoNext"], 0264 }) 0265 Gio.DBusConnection.emit_signal(connection, None, object_path, "org.freedesktop.DBus.Properties", "PropertiesChanged", GLib.Variant.new_tuple(self.PLAYER_IFACE, changed_properties, GLib.Variant('as', ()))) 0266 0267 elif method_name == "Previous": 0268 self.metadata = { 0269 'mpris:trackid': GLib.Variant('o', "/appiumtest/2"), 0270 'xesam:url': GLib.Variant('s', "file://" + path.join(getcwd(), "resources/media0.mp3")), 0271 'mpris:length': GLib.Variant('x', 60 * 10e6), # ms 0272 'xesam:title': GLib.Variant('s', "Katie's Favorite"), 0273 'xesam:album': GLib.Variant('s', "The Best of Konqi"), 0274 'xesam:artist': GLib.Variant('as', ["KDE Community"]), 0275 "mpris:artUrl": GLib.Variant('s', "file://" + path.join(getcwd(), "resources/mediacontrollertests_art0.png")) 0276 } 0277 self.player_properties["Metadata"] = GLib.Variant('a{sv}', self.metadata) 0278 self.player_properties["CanGoPrevious"] = GLib.Variant('b', False) 0279 self.player_properties["CanGoNext"] = GLib.Variant('b', True) 0280 changed_properties = GLib.Variant('a{sv}', { 0281 'Metadata': self.player_properties["Metadata"], 0282 "CanGoPrevious": self.player_properties["CanGoPrevious"], 0283 "CanGoNext": self.player_properties["CanGoNext"], 0284 }) 0285 Gio.DBusConnection.emit_signal(connection, None, object_path, "org.freedesktop.DBus.Properties", "PropertiesChanged", GLib.Variant.new_tuple(self.PLAYER_IFACE, changed_properties, GLib.Variant('as', ()))) 0286 0287 elif method_name == "Pause": 0288 self.set_playing(False, connection, object_path) 0289 0290 elif method_name == "PlayPause": 0291 self.set_playing(self.player_properties["PlaybackStatus"] != "Playing", connection, object_path) 0292 0293 elif method_name == "Stop": 0294 self.stop(connection, object_path) 0295 0296 elif method_name == "Play": 0297 self.set_playing(True, connection, object_path) 0298 0299 elif method_name == "Seek": 0300 offset: int = parameters[0] 0301 length: int = int(self.metadata["mpris:length"]) 0302 position: int = int(self.player_properties["Position"]) 0303 assert 0 <= position + offset <= length 0304 self.player_properties["Position"] = GLib.Variant('x', position + offset) 0305 changed_properties = GLib.Variant('a{sv}', { 0306 'Position': self.player_properties["Position"], 0307 }) 0308 Gio.DBusConnection.emit_signal(connection, None, object_path, self.PLAYER_IFACE.get_string(), "Seeked", GLib.Variant.new_tuple(self.player_properties["Position"])) 0309 Gio.DBusConnection.emit_signal(connection, None, object_path, "org.freedesktop.DBus.Properties", "PropertiesChanged", GLib.Variant.new_tuple(self.PLAYER_IFACE, changed_properties, GLib.Variant('as', ()))) 0310 0311 elif method_name == "SetPosition": 0312 assert parameters[0] == self.metadata["mpris:trackid"].get_string(), f"expected trackid: {parameters[0]}, actual trackid: {self.metadata['mpris:trackid'].get_string()}" 0313 self.player_properties["Position"] = GLib.Variant('x', parameters[1]) 0314 changed_properties = GLib.Variant('a{sv}', { 0315 'Position': self.player_properties["Position"], 0316 }) 0317 Gio.DBusConnection.emit_signal(connection, None, object_path, "org.freedesktop.DBus.Properties", "PropertiesChanged", GLib.Variant.new_tuple(self.PLAYER_IFACE, changed_properties, GLib.Variant('as', ()))) 0318 0319 elif method_name == "OpenUri": 0320 print("OpenUri") 0321 0322 else: 0323 # In case the interface adds new methods, fail here for easier discovery 0324 assert False, f"{method_name} does not exist" 0325 0326 def player_handle_get_property(self, connection: Gio.DBusConnection, sender: str, object_path: str, interface_name: str, value: Any): 0327 """ 0328 Handles properties for org.mpris.MediaPlayer2.Player 0329 """ 0330 assert interface_name == "org.mpris.MediaPlayer2.Player", f"Wrong interface name {interface_name} from {sender}" 0331 assert value in self.player_properties.keys(), f"{value} does not exist" 0332 0333 return self.player_properties[value] 0334 0335 def player_handle_set_property(self, connection: Gio.DBusConnection, sender: str, object_path: str, interface_name: str, key: str, value: Any) -> bool: 0336 """ 0337 Handles properties for org.mpris.MediaPlayer2.Player 0338 """ 0339 assert interface_name == "org.mpris.MediaPlayer2.Player", f"Wrong interface name {interface_name} from {sender}" 0340 assert key in self.player_properties.keys(), f"{key} does not exist" 0341 0342 print(f"player_handle_set_property key: {key}, value: {value}") 0343 0344 if key == "Rate": 0345 self.set_rate(value, connection, object_path) 0346 elif key == "LoopStatus": 0347 self.set_repeat(value, connection, object_path) 0348 elif key == "Shuffle": 0349 self.set_shuffle(value, connection, object_path) 0350 elif key == "Volume": 0351 self.set_volume(value, connection, object_path) 0352 else: 0353 assert False 0354 0355 # What is the correct thing to return here on success? It appears that 0356 # we need to return something other than None or what would be evaluated 0357 # to False for this call back to be successful. 0358 return True 0359 0360 def interface_handle_method_call(self, connection: Gio.DBusConnection, sender: str, object_path: str, interface_name: str, method_name: str, parameters: GLib.Variant, invocation: Gio.DBusMethodInvocation) -> None: 0361 """ 0362 Handles method calls for org.mpris.MediaPlayer2 0363 """ 0364 assert interface_name == "org.mpris.MediaPlayer2", f"Wrong interface name {interface_name} from {sender}" 0365 0366 if method_name == "Raise": 0367 print("Raise") 0368 elif method_name == "Quit": 0369 print("Quit") 0370 else: 0371 assert False, f"method f{method_name} does not exist" 0372 0373 def interface_handle_get_property(self, connection: Gio.DBusConnection, sender: str, object_path: str, interface_name: str, value: Any): 0374 """ 0375 Handles properties for org.mpris.MediaPlayer2 0376 """ 0377 assert interface_name == "org.mpris.MediaPlayer2", f"Wrong interface name {interface_name} from {sender}" 0378 assert value in self.base_properties.keys(), f"{value} does not exist" 0379 0380 return self.base_properties[value] 0381 0382 def interface_handle_set_property(self, connection: Gio.DBusConnection, sender: str, object_path: str, interface_name: str, key: str, value: Any) -> bool: 0383 """ 0384 Handles properties for org.mpris.MediaPlayer2 0385 """ 0386 assert interface_name == "org.mpris.MediaPlayer2", f"Wrong interface name {interface_name} from {sender}" 0387 assert key in self.base_properties.keys(), f"{key} does not exist" 0388 0389 return True 0390 0391 0392 class GlibMainloopThread(threading.Thread): 0393 """ 0394 Runs Glib main loop in another thread 0395 """ 0396 0397 def __init__(self) -> None: 0398 # Set up DBus loop 0399 self.loop = GLib.MainLoop() 0400 self.timer = threading.Timer(30, self.loop.quit) 0401 0402 # Create the thread 0403 super(GlibMainloopThread, self).__init__() 0404 0405 def run(self) -> None: 0406 """ 0407 Method to run the DBus main loop (on a thread) 0408 """ 0409 self.timer.start() 0410 self.loop.run() 0411 0412 def quit(self) -> None: 0413 self.timer.cancel() 0414 self.loop.quit() 0415 0416 0417 class MediaControllerTests(unittest.TestCase): 0418 """ 0419 Tests for the media controller widget 0420 """ 0421 0422 driver: webdriver.Remote 0423 mpris_interface: Mpris2 | None 0424 loopThread: GlibMainloopThread 0425 0426 @classmethod 0427 def setUpClass(cls) -> None: 0428 """ 0429 Opens the widget and initialize the webdriver 0430 """ 0431 options = AppiumOptions() 0432 options.set_capability("app", f"plasmawindowed -p org.kde.plasma.nano {WIDGET_ID}") 0433 options.set_capability("timeouts", {'implicit': 10000}) 0434 cls.driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', options=options) 0435 0436 cls.loopThread = GlibMainloopThread() 0437 cls.loopThread.start() 0438 0439 @classmethod 0440 def tearDownClass(cls) -> None: 0441 cls.loopThread.quit() 0442 0443 def setUp(self) -> None: 0444 self.mpris_interface = Mpris2() 0445 0446 def tearDown(self) -> None: 0447 self.mpris_interface.quit() 0448 self.mpris_interface = None 0449 self.driver.find_element(by=AppiumBy.NAME, value="No media playing") 0450 0451 def test_track(self) -> None: 0452 """ 0453 Tests the widget can show track metadata 0454 """ 0455 play_button = self.driver.find_element(by=AppiumBy.NAME, value="Play") 0456 previous_button = self.driver.find_element(by=AppiumBy.NAME, value="Previous Track") 0457 next_button = self.driver.find_element(by=AppiumBy.NAME, value="Next Track") 0458 shuffle_button = self.driver.find_element(by=AppiumBy.NAME, value="Shuffle") 0459 repeat_button = self.driver.find_element(by=AppiumBy.NAME, value="Repeat") 0460 0461 # Match song title, artist and album 0462 self.driver.find_element(by=AppiumBy.NAME, value=self.mpris_interface.metadata["xesam:title"].get_string()) # Title 0463 self.driver.find_element(by=AppiumBy.NAME, value=self.mpris_interface.metadata["xesam:album"].get_string()) # Album 0464 self.driver.find_element(by=AppiumBy.NAME, value="0:00") # Current position 0465 self.driver.find_element(by=AppiumBy.NAME, value="-5:00") # Remaining time 0466 self.driver.find_element(by=AppiumBy.NAME, value=', '.join(self.mpris_interface.metadata["xesam:artist"].unpack())) # Artists 0467 0468 # Now click the play button 0469 wait: WebDriverWait = WebDriverWait(self.driver, 5) 0470 play_button.click() 0471 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "Pause"))) 0472 play_button.click() 0473 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "Play"))) 0474 0475 # Now click the shuffle button 0476 shuffle_button.click() 0477 wait.until(lambda _: self.mpris_interface.player_properties["Shuffle"].get_boolean()) 0478 # Click again to disable shuffle 0479 shuffle_button.click() 0480 wait.until(lambda _: not self.mpris_interface.player_properties["Shuffle"].get_boolean()) 0481 0482 # Now click the repeat button 0483 repeat_button.click() 0484 wait.until(lambda _: self.mpris_interface.player_properties["LoopStatus"].get_string() == "Playlist") 0485 # Click again to switch to Track mode 0486 repeat_button.click() 0487 wait.until(lambda _: self.mpris_interface.player_properties["LoopStatus"].get_string() == "Track") 0488 # Click again to disable repeat 0489 repeat_button.click() 0490 wait.until(lambda _: self.mpris_interface.player_properties["LoopStatus"].get_string() == "None") 0491 0492 # Switch to the previous song, and match again 0493 previous_button.click() 0494 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "Katie's Favorite"))) 0495 self.assertFalse(previous_button.is_enabled()) 0496 self.assertTrue(next_button.is_enabled()) 0497 self.driver.find_element(by=AppiumBy.NAME, value=self.mpris_interface.metadata["xesam:title"].get_string()) 0498 self.driver.find_element(by=AppiumBy.NAME, value=self.mpris_interface.metadata["xesam:album"].get_string()) 0499 self.driver.find_element(by=AppiumBy.NAME, value="0:00") 0500 self.driver.find_element(by=AppiumBy.NAME, value="-10:00") 0501 self.driver.find_element(by=AppiumBy.NAME, value=', '.join(self.mpris_interface.metadata["xesam:artist"].unpack())) 0502 0503 # Switch to the next song, and match again 0504 next_button.click() 0505 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "Konqi's Favorite"))) 0506 self.assertTrue(previous_button.is_enabled()) 0507 self.assertFalse(next_button.is_enabled()) 0508 self.driver.find_element(by=AppiumBy.NAME, value=self.mpris_interface.metadata["xesam:title"].get_string()) 0509 self.driver.find_element(by=AppiumBy.NAME, value=self.mpris_interface.metadata["xesam:album"].get_string()) 0510 self.driver.find_element(by=AppiumBy.NAME, value="0:00") 0511 self.driver.find_element(by=AppiumBy.NAME, value="-15:00") 0512 self.driver.find_element(by=AppiumBy.NAME, value=', '.join(self.mpris_interface.metadata["xesam:artist"].unpack())) 0513 0514 0515 if __name__ == '__main__': 0516 unittest.main()