from time import sleep import re from appium.webdriver.common.appiumby import AppiumBy as By from selenium.common.exceptions import TimeoutException from helpers.platform.android.locators import ( resource_id_xpath, text_view_xpath) from helpers.appium.keyboard import hide_keyboard from page_objects.base_screen import BaseScreen from page_objects.mixins.utils import parse_custom_format, \ retry_on_stale_element class ArtistScreen(BaseScreen): artist_header = 'participantHeader' cover_art = resource_id_xpath('coverArt') artist_name = resource_id_xpath('participantName') flag = resource_id_xpath('flag') favorite_btn = resource_id_xpath('favoriteButton') share_btn = resource_id_xpath('shareButton') share_header_text = resource_id_xpath('headerText') share_link_btn = resource_id_xpath('shareLink') share_as_image_btn = resource_id_xpath('shareSnapshot') share_close_btn = resource_id_xpath('closeButton') streaming_trend = text_view_xpath("Streaming Trend") social_followers = text_view_xpath("Social Followers") top_songs = text_view_xpath("Top Songs") top_playlists = text_view_xpath("Top Playlists") spotify_monthly = resource_id_xpath('spotifyMonthlyListenersOpacity') spotify = 'SPOTIFY' youtube = 'YOUTUBE' instagram = 'INSTAGRAM' facebook = 'FACEBOOK' deezer = 'DEEZER' twitter = 'TWITTER' tiktok = 'accessibility.participantScreen.TIKTOK' # soundcloud = 'SOUNDCLOUD' access = By.ACCESSIBILITY_ID social_media_title = '//android.view.View[@text="SOCIAL FOLLOWERS"]' manage_linked_accounts_btn = 'MANAGE LINKED ACCOUNTS' fan_conversion_ratio_btn = ( resource_id_xpath('spotifyFanConversionPercentage')) fan_conversion_title = \ ('//android.widget.TextView[@text="Fan Conversion Ratio" ' 'and @resource-id="headerText"]') fan_conversion_content = text_view_xpath( 'Followers divided by monthly listeners. Not all followers are ' 'listeners, but this ratio shows how effectively the artist is ' 'capturing the audience - those who listen to the music.') fan_conversion_heatmap_meter = resource_id_xpath('heatmapMeter') fan_conversion_close_btn = resource_id_xpath('closeButton') manage_linked_accounts_title = \ f'({text_view_xpath("MANAGE LINKED ACCOUNTS")})[1]' manage_linked_accounts_close_btn = resource_id_xpath('closeButton') top_songs_sort_btn = resource_id_xpath('sortToggle') top_song = ('//android.view.ViewGroup' '[contains(@resource-id, "topTracksItem")]') song_name = ( cover_art + '/following-sibling::android.widget.TextView[1]') song_artist = ( cover_art + '/following-sibling::android.widget.TextView[2]') song_tile_name = resource_id_xpath('songTileName') song_tile_artist_name = resource_id_xpath('songTileArtistName') see_all_btn = 'SEE ALL' filters_btn = resource_id_xpath('filterToggle') filters_country = text_view_xpath("COUNTRY") filters_country_option = resource_id_xpath('countryRow') filters_search_bar = 'searchBar' filters_apply_btn = 'APPLY' def assert_all_sections(self, artist): self.assert_header(artist) self.assert_summary() # TODO: Re-enable once bug GO-4843 is fixed # self.assert_social_metrics() def assert_header(self, artist): header_id = f'{self.artist_header}-{artist}' try: self.assert_element(self.access, header_id, 60) except TimeoutException: self.waiter( 3, By.XPATH, f'//android.widget.TextView[contains(@text, "{artist}")]') self.waiter(3, By.XPATH, '//android.widget.TextView[' '@text="This artist is not in your catalog.\n' 'No performance data to display."]') def assert_artist_header_elements_present(self, artist): for locator in [self.cover_art, self.artist_name, self.flag, self.favorite_btn]: self.waiter(10, By.XPATH, locator) def star_current_artist(self, context): self.wait_for_element_invisibility( By.XPATH, self.active_item_path) self.waiter(20, By.XPATH, self.favorite_btn).click() self.waiter(5, By.XPATH, self.active_item_path) artist_name = self.current_artist_name(context) context.starred_artist_names.append(artist_name) # Workaround for an app issue: # the artist may not appear in My Favorites # when navigating there immediately after starring it. sleep(5) def unstar_current_artist(self, context): artist_name = self.current_artist_name(context) self.waiter(20, By.XPATH, self.favorite_btn).click() self.wait_for_element_invisibility( By.XPATH, self.active_item_path) context.starred_artist_names.remove(artist_name) @retry_on_stale_element() def current_artist_name(self, context): screen_artist_name = ( self.waiter(5, By.XPATH, self.artist_name).text.strip()) normalized_artist_name = re.sub(r'\s+0+$', '', screen_artist_name) expected_artist_name = getattr(context, 'artist_name', None) if (expected_artist_name and screen_artist_name.startswith(expected_artist_name)): return expected_artist_name.strip() return normalized_artist_name def tap_share_button(self): self.click_with_retry(10, By.XPATH, self.share_btn) def assert_share_screen_options_displayed(self): self.waiter(10, By.XPATH, self.share_header_text) self.waiter(3, By.XPATH, self.share_link_btn) # self.waiter(3, By.XPATH, self.share_as_image_btn) self.click_with_retry(3, By.XPATH, self.share_close_btn) def assert_summary(self): self.assert_element(By.XPATH, self.spotify_monthly, 10) def assert_social_metrics(self): socials = [self.youtube, self.spotify, self.instagram, self.facebook, self.deezer, self.tiktok] for bar_chart in socials: self.scroll_and_assert(self.access, bar_chart, small_scroll=True, max_scrolls=20) def go_to_section(self, context, section_name): sections = { 'Streaming Trend': self.streaming_trend, 'Social Followers': self.social_followers, 'Top Songs': self.top_songs, 'Top Playlists': self.top_playlists } self.scroll_and_assert(By.XPATH, sections[section_name], 5) def assert_social_networks_sorted_by_followers_desc(self): followers_xpath = ( text_view_xpath("Social Followers") + '/' 'following-sibling::android.view.ViewGroup/' 'android.widget.TextView[2]' ) elements = self.waiter(10, By.XPATH, followers_xpath, multiple=True) followers_counts = [parse_custom_format(el.text) for el in elements if el.text != '—'] assert len( followers_counts) > 1, "Not enough elements to validate sorting" assert followers_counts == sorted(followers_counts, reverse=True), ( f"Followers list is not sorted in desc order: {followers_counts}" ) def assert_manage_linked_accounts_visible(self): self.scroll_and_assert(By.ACCESSIBILITY_ID, self.manage_linked_accounts_btn, max_scrolls=10, small_scroll=True) def open_social_followers_from_artist_screen(self): social_followers_xpath = \ (text_view_xpath("Social Followers") + '/' 'following-sibling::android.view.ViewGroup[1]') self.click_with_retry(10, By.XPATH, social_followers_xpath) def assert_social_followers_page_displayed(self): self.waiter(10, By.XPATH, self.social_media_title) def assert_expected_metrics(self): metrics = [ "FOLLOWERS", "MONTHLY LISTENERS", "FAN CONVERSION RATIO" ] for metric in metrics: metric_xpath = text_view_xpath(metric) self.waiter(10, By.XPATH, metric_xpath) def assert_graph_displayed_for_each_connected_social_network(self): platforms = [ "Spotify Followers", "Instagram Followers", # TODO: Glitch on the ChartMetric side # "YouTube Subscribers", # TODO: Temporarily disabled Facebook Fans # "Facebook Fans", # TODO(GO-4355): Temporarily disabled due to backend issue # Re-enable once the issue is resolved # "TikTok Followers", "Deezer Fans" ] for platform in platforms: chart_suffix = '/following-sibling::android.view.ViewGroup[1]' title_xpath = \ text_view_xpath(platform) chart_xpath = title_xpath + chart_suffix for path in [title_xpath, chart_xpath]: self.scroll_and_assert(By.XPATH, path, small_scroll=True, max_scrolls=15) def tap_fan_conversion_metric(self): self.scroll_and_assert(By.XPATH, self.fan_conversion_ratio_btn, 10, 'up') self.click_with_retry(3, By.XPATH, self.fan_conversion_ratio_btn) def assert_fan_conversion_ratio_displayed(self): self.waiter(10, By.XPATH, self.fan_conversion_title) self.waiter(3, By.XPATH, self.fan_conversion_content) self.waiter(3, By.XPATH, self.fan_conversion_heatmap_meter) self.click_with_retry(3, By.XPATH, self.fan_conversion_close_btn) def tap_manage_linked_accounts_button(self): self.click_with_retry(10, By.ACCESSIBILITY_ID, self.manage_linked_accounts_btn) def assert_manage_linked_accounts_page_displayed(self): self.waiter(10, By.XPATH, self.manage_linked_accounts_title) def assert_listed_social_platforms(self, expected_platforms): for platform in expected_platforms: self.waiter(10, By.XPATH, resource_id_xpath(platform.upper())) self.click_with_retry(10, By.XPATH, self.manage_linked_accounts_close_btn) def assert_see_all_button_visible(self): self.scroll_and_assert(self.access, self.see_all_btn, small_scroll=True, max_scrolls=10) def assert_number_of_displayed_songs(self, number): top_songs = self.waiter(10, By.XPATH, self.top_song, multiple=True) assert len(top_songs) == int(number) def assert_number_of_displayed_songs_exceed(self, number, timeout=10, poll_interval=1): self.waiter(10, By.XPATH, self.filters_btn) elapsed = 0 while elapsed < timeout: top_songs = self.waiter(10, By.XPATH, self.top_song, multiple=True) if len(top_songs) > int(number): return sleep(poll_interval) elapsed += poll_interval raise AssertionError( f"Expected more than {number} top songs, but got {len(top_songs)}") def tap_song_number(self, context, song_number): sleep(1) song_names = self.waiter( 10, By.XPATH, self.song_name, multiple=True) song_artists = self.waiter( 3, By.XPATH, self.song_artist, multiple=True) context.track_name_of_song = song_names[song_number].text context.track_artist_of_song = song_artists[song_number].text self.tap_after_overlap_check(song_names[song_number]) @retry_on_stale_element(max_retries=2, delay=3) def tap_on_bottom_part_of_playlist_card(self, context, card_number): song_names = self.waiter( 10, By.XPATH, self.song_tile_name, multiple=True) song_artists = self.waiter( 3, By.XPATH, self.song_tile_artist_name, multiple=True) if card_number > len(song_names) or card_number > len(song_artists): sleep(3) song_names = self.waiter(5, By.XPATH, self.song_tile_name, multiple=True) song_artists = self.waiter(5, By.XPATH, self.song_tile_artist_name, multiple=True) context.track_name_of_song = song_names[card_number].text context.track_artist_of_song = song_artists[card_number].text self.tap_after_overlap_check(song_names[card_number]) def tap_filter_icon(self): self.click_with_retry(10, By.XPATH, self.filters_btn) def select_country_filter(self): self.click_with_retry(10, By.XPATH, self.filters_country) @staticmethod def _get_country_filter_summary_xpath(countries): countries = countries.split(',') return (f'//android.view.ViewGroup[' f'@content-desc="COUNTRY, {countries[0]}, ,{countries[-1]}"]') def assert_country_filter_summary(self, countries): self.waiter(10, By.XPATH, self.filters_country) summary_xpath = self._get_country_filter_summary_xpath(countries) self.waiter(10, By.XPATH, summary_xpath) def assert_country_filter_summary_is_empty(self, excluded_countries): summary_xpath = self._get_country_filter_summary_xpath( excluded_countries) self.wait_for_element_invisibility(By.XPATH, summary_xpath, 5) def reset_filter(self): self.click_with_retry(10, By.ACCESSIBILITY_ID, 'RESET') def close_filter_window(self): close_btn = resource_id_xpath('closeButton') self.click_with_retry(10, By.XPATH, close_btn) def assert_country_list_with_global_selected_by_default(self): countries = self.waiter(10, By.XPATH, self.filters_country_option, multiple=True) assert len(countries) > 10 def select_countries_from_market_selector(self, countries): for country in countries: country_xpath = ( self.filters_country_option + text_view_xpath(country)) try: self.click_with_retry(10, By.XPATH, country_xpath) except TimeoutException: self.click_with_retry( 10, By.ACCESSIBILITY_ID, self.filters_search_bar) (self.waiter( 10, By.ACCESSIBILITY_ID, self.filters_search_bar). send_keys(country)) self.click_with_retry(10, By.XPATH, country_xpath) hide_keyboard(self.driver) self.click_with_retry(10, By.ACCESSIBILITY_ID, self.filters_apply_btn) def assert_songs_are_sorted_by(self, filter_option): current_filter_option_xpath = \ (f'//android.view.ViewGroup[@content-desc="{filter_option}" ' f'and @resource-id="sortToggle"]') self.waiter(20, By.XPATH, current_filter_option_xpath) def change_top_songs_sorting_to(self, sorting_option): title = text_view_xpath("Sort By") sorting_option_xpath = \ (f'//android.view.ViewGroup[@content-desc="{sorting_option}" ' f'and @resource-id="sortingOption"]') self.click_with_retry(10, By.XPATH, self.top_songs_sort_btn) self.waiter(10, By.XPATH, title) self.click_with_retry(5, By.XPATH, sorting_option_xpath) def tap_see_all_songs_button(self): self.waiter(10, By.ACCESSIBILITY_ID, self.see_all_btn) see_all_btn_elem = ( self.driver.find_element(By.ACCESSIBILITY_ID, self.see_all_btn)) self.tap_after_overlap_check(see_all_btn_elem)