File indexing completed on 2024-05-12 09:39:31
0001 #!/usr/bin/env python3 0002 0003 # SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org> 0004 # SPDX-FileCopyrightText: 2023 Fushan Wen <qydwhotmail@gmail.com> 0005 # SPDX-License-Identifier: GPL-2.0-or-later 0006 0007 # pylint: disable=too-many-arguments 0008 0009 import json 0010 import subprocess 0011 import unittest 0012 from os import getcwd, path 0013 from tempfile import NamedTemporaryFile 0014 from time import sleep 0015 from typing import Final 0016 0017 import gi 0018 from appium import webdriver 0019 from appium.options.common.base import AppiumOptions 0020 from appium.webdriver.common.appiumby import AppiumBy 0021 from selenium.webdriver.common.actions.action_builder import ActionBuilder 0022 from selenium.webdriver.common.actions.interaction import POINTER_TOUCH 0023 from selenium.webdriver.common.actions.pointer_input import PointerInput 0024 from selenium.webdriver.remote.webelement import WebElement 0025 from selenium.webdriver.support import expected_conditions as EC 0026 from selenium.webdriver.support.ui import WebDriverWait 0027 from utils.GLibMainLoopThread import GLibMainLoopThread 0028 from utils.mediaplayer import (InvalidMpris2, Mpris2, read_base_properties, read_player_metadata, read_player_properties) 0029 0030 gi.require_version('Gtk', '4.0') 0031 from gi.repository import Gtk, Gio, GLib 0032 0033 WIDGET_ID: Final = "org.kde.plasma.mediacontroller" 0034 0035 0036 class MediaControllerTests(unittest.TestCase): 0037 """ 0038 Tests for the media controller widget 0039 """ 0040 0041 driver: webdriver.Remote 0042 loop_thread: GLibMainLoopThread 0043 mpris_interface: Mpris2 | None 0044 player_b: subprocess.Popen | None = None 0045 player_browser: subprocess.Popen | None = None 0046 player_plasma_browser_integration: subprocess.Popen | None = None 0047 0048 @classmethod 0049 def setUpClass(cls) -> None: 0050 """ 0051 Opens the widget and initialize the webdriver 0052 """ 0053 cls.loop_thread = GLibMainLoopThread() 0054 cls.loop_thread.start() 0055 0056 options = AppiumOptions() 0057 options.set_capability("app", f"plasmawindowed -p org.kde.plasma.nano {WIDGET_ID}") 0058 options.set_capability("environ", { 0059 "QT_FATAL_WARNINGS": "1", 0060 "QT_LOGGING_RULES": "qt.accessibility.atspi.warning=false;kf.plasma.core.warning=false;kf.windowsystem.warning=false;kf.kirigami.platform.warning=false", 0061 }) 0062 options.set_capability("timeouts", {'implicit': 10000}) 0063 cls.driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', options=options) 0064 0065 def setUp(self) -> None: 0066 json_path: str = path.join(getcwd(), "resources/player_a.json") 0067 with open(json_path, "r", encoding="utf-8") as f: 0068 json_dict: dict[str, list | dict] = json.load(f) 0069 metadata: list[dict[str, GLib.Variant]] = read_player_metadata(json_dict) 0070 base_properties: dict[str, GLib.Variant] = read_base_properties(json_dict) 0071 current_index: int = 1 0072 player_properties: dict[str, GLib.Variant] = read_player_properties(json_dict, metadata[current_index]) 0073 0074 self.mpris_interface = Mpris2(metadata, base_properties, player_properties, current_index) 0075 assert self.mpris_interface.registered_event.wait(10) 0076 0077 def tearDown(self) -> None: 0078 if not self._outcome.result.wasSuccessful(): 0079 self.driver.get_screenshot_as_file(f"failed_test_shot_{WIDGET_ID}_#{self.id()}.png") 0080 if self.mpris_interface is not None: 0081 self.mpris_interface.quit() 0082 self.mpris_interface = None 0083 WebDriverWait(self.driver, 5, 0.2).until(EC.presence_of_element_located((AppiumBy.NAME, "No media playing"))) 0084 0085 @classmethod 0086 def tearDownClass(cls) -> None: 0087 """ 0088 Make sure to terminate the driver again, lest it dangles. 0089 """ 0090 cls.loop_thread.quit() 0091 cls.driver.quit() 0092 0093 def test_track(self) -> None: 0094 """ 0095 Tests the widget can show track metadata 0096 """ 0097 assert self.mpris_interface 0098 play_button = self.driver.find_element(by=AppiumBy.NAME, value="Play") 0099 previous_button = self.driver.find_element(by=AppiumBy.NAME, value="Previous Track") 0100 next_button = self.driver.find_element(by=AppiumBy.NAME, value="Next Track") 0101 shuffle_button = self.driver.find_element(by=AppiumBy.NAME, value="Shuffle") 0102 repeat_button = self.driver.find_element(by=AppiumBy.NAME, value="Repeat") 0103 0104 # Match song title, artist and album 0105 wait: WebDriverWait = WebDriverWait(self.driver, 5) 0106 wait.until(EC.presence_of_element_located((AppiumBy.NAME, self.mpris_interface.metadata[self.mpris_interface.current_index]["xesam:title"].get_string()))) # Title 0107 wait.until(EC.presence_of_element_located((AppiumBy.NAME, self.mpris_interface.metadata[self.mpris_interface.current_index]["xesam:album"].get_string()))) # Album 0108 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "0:00"))) # Current position 0109 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "-5:00"))) # Remaining time 0110 wait.until(EC.presence_of_element_located((AppiumBy.NAME, ', '.join(self.mpris_interface.metadata[self.mpris_interface.current_index]["xesam:artist"].unpack())))) # Artists 0111 0112 # Now click the play button 0113 play_button.click() 0114 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "Pause"))) 0115 play_button.click() 0116 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "Play"))) 0117 0118 # Now click the shuffle button 0119 shuffle_button.click() 0120 wait.until(lambda _: self.mpris_interface.player_properties["Shuffle"].get_boolean()) 0121 # Click again to disable shuffle 0122 shuffle_button.click() 0123 wait.until(lambda _: not self.mpris_interface.player_properties["Shuffle"].get_boolean()) 0124 0125 # Now click the repeat button 0126 repeat_button.click() 0127 wait.until(lambda _: self.mpris_interface.player_properties["LoopStatus"].get_string() == "Playlist") 0128 # Click again to switch to Track mode 0129 repeat_button.click() 0130 wait.until(lambda _: self.mpris_interface.player_properties["LoopStatus"].get_string() == "Track") 0131 # Click again to disable repeat 0132 repeat_button.click() 0133 wait.until(lambda _: self.mpris_interface.player_properties["LoopStatus"].get_string() == "None") 0134 0135 # Switch to the previous song, and match again 0136 previous_button.click() 0137 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "Katie's Favorite"))) 0138 self.assertFalse(previous_button.is_enabled()) 0139 self.assertTrue(next_button.is_enabled()) 0140 self.driver.find_element(by=AppiumBy.NAME, value=self.mpris_interface.metadata[self.mpris_interface.current_index]["xesam:title"].get_string()) 0141 self.driver.find_element(by=AppiumBy.NAME, value=self.mpris_interface.metadata[self.mpris_interface.current_index]["xesam:album"].get_string()) 0142 self.driver.find_element(by=AppiumBy.NAME, value="0:00") 0143 self.driver.find_element(by=AppiumBy.NAME, value="-10:00") 0144 self.driver.find_element(by=AppiumBy.NAME, value=', '.join(self.mpris_interface.metadata[self.mpris_interface.current_index]["xesam:artist"].unpack())) 0145 0146 # Switch to the next song (need to click twice), and match again 0147 next_button.click() 0148 next_button.click() 0149 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "Konqi's Favorite"))) 0150 self.assertTrue(previous_button.is_enabled()) 0151 self.assertFalse(next_button.is_enabled()) 0152 self.driver.find_element(by=AppiumBy.NAME, value=self.mpris_interface.metadata[self.mpris_interface.current_index]["xesam:title"].get_string()) 0153 self.driver.find_element(by=AppiumBy.NAME, value=self.mpris_interface.metadata[self.mpris_interface.current_index]["xesam:album"].get_string()) 0154 self.driver.find_element(by=AppiumBy.NAME, value="0:00") 0155 self.driver.find_element(by=AppiumBy.NAME, value="-15:00") 0156 self.driver.find_element(by=AppiumBy.NAME, value=', '.join(self.mpris_interface.metadata[self.mpris_interface.current_index]["xesam:artist"].unpack())) 0157 0158 def test_touch_gestures(self) -> None: 0159 """ 0160 Tests touch gestures like swipe up/down/left/right to adjust volume/progress 0161 @see https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/2438 0162 """ 0163 assert self.mpris_interface 0164 wait: WebDriverWait = WebDriverWait(self.driver, 5) 0165 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "0:00"))) # Current position 0166 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "-5:00"))) # Remaining time 0167 0168 # Center point of the screen 0169 geometry = Gtk.Window().get_display().get_monitors()[0].get_geometry() 0170 center_pos_x: int = int(geometry.width / 2) 0171 center_pos_y: int = int(geometry.height / 2) 0172 self.assertGreater(center_pos_x, 1) 0173 self.assertGreater(center_pos_y, 1) 0174 0175 # Touch the window, and wait a moment to make sure the widget is ready 0176 input_source = PointerInput(POINTER_TOUCH, "finger") 0177 action = ActionBuilder(self.driver, mouse=input_source, duration=500) 0178 action.pointer_action.move_to_location(center_pos_x, center_pos_y).click().pause(1) 0179 action.perform() 0180 0181 # Swipe right -> Position++ 0182 input_source = PointerInput(POINTER_TOUCH, "finger") 0183 action = ActionBuilder(self.driver, mouse=input_source, duration=500) 0184 action.pointer_action.move_to_location(center_pos_x, center_pos_y).pointer_down().move_to_location(center_pos_x * 2, center_pos_y).pointer_up() 0185 action.perform() 0186 self.mpris_interface.connection.flush_sync(None) 0187 wait.until(lambda _: self.mpris_interface.player_properties["Position"].get_int64() > 0) 0188 0189 # Swipe left -> Position-- 0190 old_position: int = self.mpris_interface.player_properties["Position"].get_int64() 0191 input_source = PointerInput(POINTER_TOUCH, "finger") 0192 action = ActionBuilder(self.driver, mouse=input_source, duration=500) 0193 action.pointer_action.move_to_location(center_pos_x, center_pos_y).pointer_down().move_to_location(0, center_pos_y).pointer_up().pause(1) 0194 action.perform() 0195 self.mpris_interface.connection.flush_sync(None) 0196 wait.until(lambda _: self.mpris_interface.player_properties["Position"].get_int64() < old_position) 0197 0198 # Swipe down: Volume-- 0199 input_source = PointerInput(POINTER_TOUCH, "finger") 0200 action = ActionBuilder(self.driver, mouse=input_source, duration=500) 0201 action.pointer_action.move_to_location(center_pos_x, center_pos_y).pointer_down().move_to_location(center_pos_x, center_pos_y * 2).pointer_up().pause(1) 0202 action.perform() 0203 self.mpris_interface.connection.flush_sync(None) 0204 wait.until(lambda _: self.mpris_interface.player_properties["Volume"].get_double() < 1.0) 0205 0206 # Swipe up: Volume++ 0207 old_volume: float = self.mpris_interface.player_properties["Volume"].get_double() 0208 input_source = PointerInput(POINTER_TOUCH, "finger") 0209 action = ActionBuilder(self.driver, mouse=input_source, duration=500) 0210 action.pointer_action.move_to_location(center_pos_x, center_pos_y).pointer_down().move_to_location(center_pos_x, 0).pointer_up().pause(1) 0211 action.perform() 0212 self.mpris_interface.connection.flush_sync(None) 0213 wait.until(lambda _: self.mpris_interface.player_properties["Volume"].get_double() > old_volume) 0214 0215 # Swipe down and then swipe right, only volume should change 0216 old_volume = self.mpris_interface.player_properties["Volume"].get_double() 0217 old_position = self.mpris_interface.player_properties["Position"].get_int64() 0218 input_source = PointerInput(POINTER_TOUCH, "finger") 0219 action = ActionBuilder(self.driver, mouse=input_source, duration=500) 0220 action.pointer_action.move_to_location(center_pos_x, center_pos_y).pointer_down().move_to_location(center_pos_x, center_pos_y * 2).pause(0.5).move_to_location(center_pos_x * 2, center_pos_y * 2).pointer_up().pause(1) 0221 action.perform() 0222 self.mpris_interface.connection.flush_sync(None) 0223 wait.until(lambda _: self.mpris_interface.player_properties["Volume"].get_double() < old_volume) 0224 self.assertEqual(old_position, self.mpris_interface.player_properties["Position"].get_int64()) 0225 0226 # Swipe right and then swipe up, only position should change 0227 old_volume = self.mpris_interface.player_properties["Volume"].get_double() 0228 old_position = self.mpris_interface.player_properties["Position"].get_int64() 0229 input_source = PointerInput(POINTER_TOUCH, "finger") 0230 action = ActionBuilder(self.driver, mouse=input_source, duration=500) 0231 action.pointer_action.move_to_location(center_pos_x, center_pos_y).pointer_down().move_to_location(center_pos_x * 2, center_pos_y).pause(0.5).move_to_location(center_pos_x * 2, 0).pointer_up().pause(1) 0232 action.perform() 0233 self.mpris_interface.connection.flush_sync(None) 0234 wait.until(lambda _: self.mpris_interface.player_properties["Position"].get_int64() > old_position) 0235 self.assertAlmostEqual(old_volume, self.mpris_interface.player_properties["Volume"].get_double()) 0236 0237 def _cleanup_multiplexer(self) -> None: 0238 if self.player_b: 0239 self.player_b.kill() 0240 self.player_b.wait() 0241 self.player_b = None 0242 0243 def test_multiplexer(self) -> None: 0244 """ 0245 The multiplexer should be hidden when there is only 1 player, and shows information from the active/playing player if there is one 0246 """ 0247 self.addCleanup(self._cleanup_multiplexer) 0248 0249 # Wait until the first player is ready 0250 wait: WebDriverWait = WebDriverWait(self.driver, 3) 0251 wait.until(EC.presence_of_element_located((AppiumBy.NAME, self.mpris_interface.metadata[self.mpris_interface.current_index]["xesam:title"].get_string()))) 0252 0253 # Start Player B, Total 2 players 0254 player_b_json_path: str = path.join(getcwd(), "resources/player_b.json") 0255 self.player_b = subprocess.Popen(("python3", path.join(getcwd(), "utils/mediaplayer.py"), player_b_json_path)) 0256 player_selector: WebElement = wait.until(EC.visibility_of_element_located((AppiumBy.ACCESSIBILITY_ID, "playerSelector"))) 0257 0258 # Find player tabs based on "Identity" 0259 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "AppiumTest"))) 0260 0261 # Make sure the current index does not change after a new player appears 0262 self.driver.find_element(by=AppiumBy.NAME, value=self.mpris_interface.metadata[self.mpris_interface.current_index]["xesam:title"].get_string()) # Title 0263 0264 # Switch to Player B 0265 self.driver.find_element(by=AppiumBy.NAME, value="Audacious").click() 0266 with open(player_b_json_path, "r", encoding="utf-8") as f: 0267 player_b_metadata = read_player_metadata(json.load(f)) 0268 wait.until(EC.presence_of_element_located((AppiumBy.NAME, player_b_metadata[0]["xesam:title"].get_string()))) 0269 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "Play"))) 0270 wait.until(EC.presence_of_element_located((AppiumBy.NAME, player_b_metadata[0]["xesam:album"].get_string()))) 0271 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "0:00"))) 0272 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "-15:00"))) 0273 wait.until(EC.presence_of_element_located((AppiumBy.NAME, ', '.join(player_b_metadata[0]["xesam:artist"].unpack())))) 0274 wait.until_not(EC.element_to_be_clickable((AppiumBy.NAME, "Next Track"))) 0275 wait.until_not(EC.element_to_be_clickable((AppiumBy.NAME, "Previous Track"))) 0276 0277 # Switch to Multiplexer 0278 # A Paused, B Paused -> A (first added) 0279 self.driver.find_element(by=AppiumBy.NAME, value="Choose player automatically").click() 0280 wait.until(EC.presence_of_element_located((AppiumBy.NAME, self.mpris_interface.metadata[self.mpris_interface.current_index]["xesam:title"].get_string()))) 0281 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "Play"))) 0282 wait.until(EC.element_to_be_clickable((AppiumBy.NAME, "Next Track"))) 0283 wait.until(EC.element_to_be_clickable((AppiumBy.NAME, "Previous Track"))) 0284 0285 # A Paused, B Playing -> B 0286 # Doc: https://lazka.github.io/pgi-docs/Gio-2.0/classes/DBusConnection.html 0287 session_bus: Gio.DBusConnection = Gio.bus_get_sync(Gio.BusType.SESSION) 0288 session_bus.call(f"org.mpris.MediaPlayer2.appiumtest.instance{str(self.player_b.pid)}", Mpris2.OBJECT_PATH, Mpris2.PLAYER_IFACE.get_string(), "Play", None, None, Gio.DBusSendMessageFlags.NONE, 1000) 0289 wait.until(EC.presence_of_element_located((AppiumBy.NAME, player_b_metadata[0]["xesam:title"].get_string()))) 0290 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "Pause"))) 0291 wait.until_not(EC.element_to_be_clickable((AppiumBy.NAME, "Next Track"))) 0292 wait.until_not(EC.element_to_be_clickable((AppiumBy.NAME, "Previous Track"))) 0293 0294 # Pause B -> Still B 0295 session_bus.call(f"org.mpris.MediaPlayer2.appiumtest.instance{str(self.player_b.pid)}", Mpris2.OBJECT_PATH, Mpris2.PLAYER_IFACE.get_string(), "Pause", None, None, Gio.DBusSendMessageFlags.NONE, 1000) 0296 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "Play"))) 0297 wait.until(EC.presence_of_element_located((AppiumBy.NAME, player_b_metadata[0]["xesam:title"].get_string()))) 0298 0299 # A Playing, B Paused -> A 0300 session_bus.call(self.mpris_interface.APP_INTERFACE, Mpris2.OBJECT_PATH, Mpris2.PLAYER_IFACE.get_string(), "Play", None, None, Gio.DBusSendMessageFlags.NONE, 1000) 0301 wait.until(EC.presence_of_element_located((AppiumBy.NAME, self.mpris_interface.metadata[self.mpris_interface.current_index]["xesam:title"].get_string()))) 0302 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "Pause"))) 0303 wait.until(EC.element_to_be_clickable((AppiumBy.NAME, "Next Track"))) 0304 wait.until(EC.element_to_be_clickable((AppiumBy.NAME, "Previous Track"))) 0305 0306 # A Playing, B Playing -> Still A 0307 session_bus.call(f"org.mpris.MediaPlayer2.appiumtest.instance{str(self.player_b.pid)}", Mpris2.OBJECT_PATH, Mpris2.PLAYER_IFACE.get_string(), "Play", None, None, Gio.DBusSendMessageFlags.NONE, 1000) 0308 sleep(1) 0309 self.driver.find_element(by=AppiumBy.NAME, value=self.mpris_interface.metadata[self.mpris_interface.current_index]["xesam:title"].get_string()) # Title 0310 0311 # A Paused, B Playing -> B 0312 session_bus.call(self.mpris_interface.APP_INTERFACE, Mpris2.OBJECT_PATH, Mpris2.PLAYER_IFACE.get_string(), "Pause", None, None, Gio.DBusSendMessageFlags.NONE, 1000) 0313 wait.until(EC.presence_of_element_located((AppiumBy.NAME, player_b_metadata[0]["xesam:title"].get_string()))) 0314 wait.until_not(EC.element_to_be_clickable((AppiumBy.NAME, "Next Track"))) 0315 wait.until_not(EC.element_to_be_clickable((AppiumBy.NAME, "Previous Track"))) 0316 0317 # Close B -> A 0318 self.player_b.terminate() 0319 self.player_b.wait(10) 0320 self.player_b = None 0321 wait.until(EC.presence_of_element_located((AppiumBy.NAME, self.mpris_interface.metadata[self.mpris_interface.current_index]["xesam:title"].get_string()))) 0322 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "Play"))) 0323 wait.until(EC.element_to_be_clickable((AppiumBy.NAME, "Next Track"))) 0324 wait.until(EC.element_to_be_clickable((AppiumBy.NAME, "Previous Track"))) 0325 self.assertFalse(player_selector.is_displayed()) # Tabbar is hidden again 0326 0327 def _cleanup_filter_plasma_browser_integration(self) -> None: 0328 """ 0329 A cleanup function to be called after the test is completed 0330 """ 0331 if self.player_browser: 0332 self.player_browser.terminate() 0333 self.player_browser.wait(10) 0334 self.player_browser = None 0335 if self.player_plasma_browser_integration: 0336 self.player_plasma_browser_integration.terminate() 0337 self.player_plasma_browser_integration.wait(10) 0338 self.player_plasma_browser_integration = None 0339 0340 def test_filter_plasma_browser_integration(self) -> None: 0341 """ 0342 When Plasma Browser Integration is installed, the widget should only show the player from p-b-i, and hide the player from the browser. 0343 """ 0344 self.addCleanup(self._cleanup_filter_plasma_browser_integration) 0345 0346 # Make sure the active player is not the browser so the bug can be tested 0347 wait = WebDriverWait(self.driver, 3) 0348 wait.until(EC.presence_of_element_located((AppiumBy.NAME, self.mpris_interface.metadata[self.mpris_interface.current_index]["xesam:title"].get_string()))) # Title 0349 0350 player_browser_json_path: str = path.join(getcwd(), "resources/player_browser.json") 0351 with open(player_browser_json_path, "r", encoding="utf-8") as f: 0352 browser_json_data = json.load(f) 0353 self.player_browser = subprocess.Popen(("python3", path.join(getcwd(), "utils/mediaplayer.py"), player_browser_json_path)) 0354 wait.until(EC.visibility_of_element_located((AppiumBy.ACCESSIBILITY_ID, "playerSelector"))) 0355 browser_tab: WebElement = wait.until(EC.presence_of_element_located((AppiumBy.NAME, browser_json_data["base_properties"]["Identity"]))) 0356 browser_tab.click() 0357 wait.until(EC.presence_of_element_located((AppiumBy.NAME, browser_json_data["metadata"][0]["xesam:title"]))) 0358 wait.until(EC.presence_of_element_located((AppiumBy.NAME, browser_json_data["metadata"][0]["xesam:album"]))) 0359 self.assertFalse(self.driver.find_element(by=AppiumBy.NAME, value="Next Track").is_enabled()) 0360 0361 with open(path.join(getcwd(), "resources/player_plasma_browser_integration.json"), "r", encoding="utf-8") as f: 0362 pbi_json_data = json.load(f) 0363 pbi_json_data["metadata"][0]["kde:pid"] = self.player_browser.pid # Simulate Plasma Browser Integration 0364 with NamedTemporaryFile("w", encoding="utf-8", suffix=".json", delete=False) as temp_file: 0365 json.dump(pbi_json_data, temp_file) 0366 temp_file.flush() 0367 0368 self.player_plasma_browser_integration = subprocess.Popen(("python3", path.join(getcwd(), "utils/mediaplayer.py"), temp_file.name)) 0369 wait.until(EC.presence_of_element_located((AppiumBy.NAME, pbi_json_data["base_properties"]["Identity"]))).click() 0370 wait.until(EC.presence_of_element_located((AppiumBy.NAME, pbi_json_data["metadata"][0]["xesam:title"]))) 0371 wait.until(EC.presence_of_element_located((AppiumBy.NAME, pbi_json_data["metadata"][0]["xesam:album"]))) 0372 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "Play"))) 0373 wait.until(EC.element_to_be_clickable((AppiumBy.NAME, "Next Track"))) 0374 self.assertFalse(browser_tab.is_displayed()) 0375 0376 # When a browser starts playing a video 0377 # 1. It registers the browser MPRIS instance with PlaybackStatus: Stopped/Paused, and Mpris2FilterProxyModel has it but the active player can be others 0378 # 2. p-b-i also registers its MPRIS instance, and Mpris2FilterProxyModel filters out the Chromium MPRIS instance, so the browser MPRIS instance in Mpris2FilterProxyModel becomes invalid 0379 # 3. PlaybackStatus changes to Playing, and the container of the browser MPRIS instance emits playbackStatusChanged() signal. However, the signal should be ignored by Multiplexer (disconnect in Multiplexer::onRowsAboutToBeRemoved) 0380 session_bus: Gio.DBusConnection = Gio.bus_get_sync(Gio.BusType.SESSION) 0381 session_bus.call(f"org.mpris.MediaPlayer2.appiumtest.instance{str(self.player_browser.pid)}", Mpris2.OBJECT_PATH, Mpris2.PLAYER_IFACE.get_string(), "Play", None, None, Gio.DBusSendMessageFlags.NONE, 1000) 0382 session_bus.call(f"org.mpris.MediaPlayer2.appiumtest.instance{str(self.player_plasma_browser_integration.pid)}", Mpris2.OBJECT_PATH, Mpris2.PLAYER_IFACE.get_string(), "Play", None, None, Gio.DBusSendMessageFlags.NONE, 1000) 0383 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "Pause"))) # Confirm the backend does not crash 0384 0385 self._cleanup_filter_plasma_browser_integration() 0386 0387 def test_bug477144_invalid_player(self) -> None: 0388 """ 0389 Do not crash when a player is invalid or its DBus interface returns any errors on initialization 0390 @see https://bugs.kde.org/show_bug.cgi?id=477144 0391 """ 0392 if self.mpris_interface is not None: 0393 self.mpris_interface.quit() 0394 0395 placeholder_element: WebElement = self.driver.find_element(AppiumBy.NAME, "No media playing") 0396 self.mpris_interface = InvalidMpris2() 0397 self.assertTrue(self.mpris_interface.registered_event.wait(10)) 0398 self.assertTrue(placeholder_element.is_displayed()) 0399 0400 def test_bug477335_decode_xesam_url(self) -> None: 0401 """ 0402 Make sure "xesam_url" is decoded before using it in other places like album name 0403 """ 0404 if self.mpris_interface is not None: 0405 self.mpris_interface.quit() 0406 0407 player_with_encoded_url_json_path: str = path.join(getcwd(), "resources/player_with_encoded_url.json") 0408 with subprocess.Popen(("python3", path.join(getcwd(), "utils/mediaplayer.py"), player_with_encoded_url_json_path)) as player_with_encoded_url: 0409 wait = WebDriverWait(self.driver, 3) 0410 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "Flash Funk"))) 0411 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "League of Legends"))) # Album name deduced from folder name 0412 # Overflow check, 2160000000 (microsecond) > INT_MAX (2147483647) 0413 wait.until(EC.presence_of_element_located((AppiumBy.NAME, "-36:00"))) 0414 player_with_encoded_url.terminate() 0415 player_with_encoded_url.wait(10) 0416 0417 0418 if __name__ == '__main__': 0419 unittest.main()