Schek/add_explicit_waits_for_the_rest_of_the_elements (#852)

* commit with waits added to several pages

* reverted changes for review and formatted files with black
This commit is contained in:
aschek 2023-09-22 12:12:09 +03:00 коммит произвёл GitHub
Родитель 5d9640b8ec
Коммит eca60af776
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
39 изменённых файлов: 6859 добавлений и 971 удалений

Двоичные данные
.DS_Store поставляемый

Двоичный файл не отображается.

Просмотреть файл

@ -1 +1 @@
w0mmdr9zr2n0n1n3i3zgf7xisjwmu7el
yqi0af1kceg0mb31low9l318o3qtm8xc

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -7,25 +7,24 @@ from selenium.webdriver.support import expected_conditions as EC
class AboutAddons(Page):
_addon_cards_locator = (By.CLASS_NAME, 'card.addon')
_search_box_locator = (By.CSS_SELECTOR, '.main-search search-textbox')
_addon_cards_locator = (By.CLASS_NAME, "card.addon")
_search_box_locator = (By.CSS_SELECTOR, ".main-search search-textbox")
_extension_tab_button_locator = (By.CSS_SELECTOR, 'button[name = "extension"]')
_theme_tab_button_locator = (By.CSS_SELECTOR, 'button[name = "theme"]')
_dictionary_tab_button_locator = (By.CSS_SELECTOR, 'button[name = "dictionary"]')
_langpack_tab_button_locator = (By.CSS_SELECTOR, 'button[name = "locale"]')
_extension_disable_toggle_locator = (By.CLASS_NAME, 'extension-enable-button')
_enabled_theme_status_locator = (By.CLASS_NAME, 'card.addon')
_installed_addon_cards_locator = (By.CSS_SELECTOR, '.card.addon')
_enabled_theme_image_locator = (By.CLASS_NAME, 'card-heading-image')
_installed_addon_name_locator = (By.CSS_SELECTOR, '.addon-name a')
_installed_addon_author_locator = (By.CSS_SELECTOR, '.addon-detail-row-author a')
_find_more_addons_button_locator = (By.CLASS_NAME, 'primary')
_extension_disable_toggle_locator = (By.CLASS_NAME, "extension-enable-button")
_enabled_theme_status_locator = (By.CLASS_NAME, "card.addon")
_installed_addon_cards_locator = (By.CSS_SELECTOR, ".card.addon")
_enabled_theme_image_locator = (By.CLASS_NAME, "card-heading-image")
_installed_addon_name_locator = (By.CSS_SELECTOR, ".addon-name a")
_installed_addon_author_locator = (By.CSS_SELECTOR, ".addon-detail-row-author a")
_find_more_addons_button_locator = (By.CLASS_NAME, "primary")
_installed_extension_version_locator = (
By.CSS_SELECTOR,
'.addon-detail-row-version',
".addon-detail-row-version",
)
_options_button_locator = (By.CSS_SELECTOR, '.more-options-button')
_options_button_locator = (By.CSS_SELECTOR, ".more-options-button")
def wait_for_page_to_load(self):
self.wait.until(
@ -34,6 +33,7 @@ class AboutAddons(Page):
return self
def search_box(self, value):
self.wait.until(EC.visibility_of_element_located(self._search_box_locator))
search_field = self.find_element(*self._search_box_locator)
search_field.send_keys(value)
# send Enter to initiate search redirection to AMO
@ -41,7 +41,7 @@ class AboutAddons(Page):
# AMO search results open in a new tab, so we need to switch windows
self.wait.until(
EC.number_of_windows_to_be(2),
message=f'Number of windows was {len(self.driver.window_handles)}, expected 2',
message=f"Number of windows was {len(self.driver.window_handles)}, expected 2",
)
self.driver.switch_to.window(self.driver.window_handles[1])
from pages.desktop.frontend.search import Search
@ -49,42 +49,52 @@ class AboutAddons(Page):
return Search(self.driver, self.base_url).wait_for_page_to_load()
def click_extensions_side_button(self):
self.wait.until(EC.element_to_be_clickable(self._extension_tab_button_locator))
self.find_element(*self._extension_tab_button_locator).click()
self.wait.until(
EC.text_to_be_present_in_element(
(By.CLASS_NAME, 'list-section-heading'), 'Enabled'
(By.CLASS_NAME, "list-section-heading"), "Enabled"
)
)
def click_themes_side_button(self):
self.wait.until(EC.element_to_be_clickable(self._theme_tab_button_locator))
self.find_element(*self._theme_tab_button_locator).click()
self.wait.until(
EC.text_to_be_present_in_element(
(By.CLASS_NAME, 'list-section-heading'), 'Enabled'
(By.CLASS_NAME, "list-section-heading"), "Enabled"
)
)
def click_dictionaries_side_button(self):
self.wait.until(EC.element_to_be_clickable(self._dictionary_tab_button_locator))
self.find_element(*self._dictionary_tab_button_locator).click()
self.wait.until(
EC.text_to_be_present_in_element(
(By.CLASS_NAME, 'list-section-heading'), 'Enabled'
(By.CLASS_NAME, "list-section-heading"), "Enabled"
)
)
def click_language_side_button(self):
self.wait.until(EC.element_to_be_clickable(self._langpack_tab_button_locator))
self.find_element(*self._langpack_tab_button_locator).click()
self.wait.until(
EC.text_to_be_present_in_element(
(By.CLASS_NAME, 'list-section-heading'), 'Enabled'
(By.CLASS_NAME, "list-section-heading"), "Enabled"
)
)
def disable_extension(self):
self.wait.until(
EC.element_to_be_clickable(self._extension_disable_toggle_locator)
)
self.find_element(*self._extension_disable_toggle_locator).click()
@property
def installed_addon_cards(self):
self.wait.until(
EC.visibility_of_element_located(self._installed_addon_cards_locator)
)
return self.find_elements(*self._installed_addon_cards_locator)
@property
@ -93,31 +103,44 @@ class AboutAddons(Page):
@property
def installed_addon_author_name(self):
self.wait.until(
EC.visibility_of_element_located(self._installed_addon_author_locator)
)
return self.find_element(*self._installed_addon_author_locator).text
@property
def enabled_theme_active_status(self):
"""Verifies if a theme is enabled"""
self.wait.until(
EC.visibility_of_element_located(self._enabled_theme_status_locator)
)
el = self.find_elements(*self._enabled_theme_status_locator)
return el[0].get_attribute('active')
return el[0].get_attribute("active")
@property
def enabled_theme_image(self):
self.wait.until(
EC.visibility_of_element_located(self._enabled_theme_image_locator)
)
return self.find_elements(*self._enabled_theme_image_locator)[0].get_attribute(
'src'
"src"
)
@property
def addon_cards_items(self):
self.wait.until(EC.visibility_of_element_located(self._addon_cards_locator))
items = self.find_elements(*self._addon_cards_locator)
return [self.AddonCards(self, el) for el in items]
def click_find_more_addons(self):
self.wait.until(
EC.element_to_be_clickable(self._find_more_addons_button_locator)
)
self.find_element(*self._find_more_addons_button_locator).click()
# this button opens AMO homepage in a new tab
self.wait.until(
EC.number_of_windows_to_be(2),
message=f'Number of windows was {len(self.driver.window_handles)}, expected 2',
message=f"Number of windows was {len(self.driver.window_handles)}, expected 2",
)
self.driver.switch_to.window(self.driver.window_handles[1])
from pages.desktop.frontend.home import Home
@ -126,24 +149,28 @@ class AboutAddons(Page):
@property
def installed_version_number(self):
self.wait.until(
EC.visibility_of_element_located(self._installed_extension_version_locator)
)
return self.find_element(
*self._installed_extension_version_locator
).text.replace('Version\n', '')
).text.replace("Version\n", "")
def click_options_button(self):
self.wait.until(EC.element_to_be_clickable(self._options_button_locator))
self.find_element(*self._options_button_locator).click()
class AddonCards(Region):
_theme_image_locator = (By.CLASS_NAME, 'card-heading-image')
_extension_icon_locator = (By.CLASS_NAME, 'card-heading-icon')
_disco_addon_name_locator = (By.CLASS_NAME, 'disco-addon-name')
_disco_addon_author_locator = (By.CSS_SELECTOR, '.disco-addon-author a')
_extension_summary_locator = (By.CLASS_NAME, 'disco-description-main')
_theme_image_locator = (By.CLASS_NAME, "card-heading-image")
_extension_icon_locator = (By.CLASS_NAME, "card-heading-icon")
_disco_addon_name_locator = (By.CLASS_NAME, "disco-addon-name")
_disco_addon_author_locator = (By.CSS_SELECTOR, ".disco-addon-author a")
_extension_summary_locator = (By.CLASS_NAME, "disco-description-main")
_extension_rating_locator = (
By.CSS_SELECTOR,
'.disco-description-statistics moz-five-star',
".disco-description-statistics moz-five-star",
)
_extension_users_count_locator = (By.CLASS_NAME, 'disco-user-count')
_extension_users_count_locator = (By.CLASS_NAME, "disco-user-count")
_addon_install_button_locator = (
By.CSS_SELECTOR,
'button[action="install-addon"]',
@ -160,25 +187,35 @@ class AboutAddons(Page):
@property
def theme_image(self):
self.wait.until(EC.visibility_of_element_located(self._theme_image_locator))
return self.find_element(*self._theme_image_locator)
@property
def extension_image(self):
self.wait.until(
EC.visibility_of_element_located(self._extension_icon_locator)
)
return self.find_element(*self._extension_icon_locator)
@property
def disco_addon_name(self):
self.wait.until(
EC.visibility_of_element_located(self._disco_addon_name_locator)
)
return self.find_element(*self._disco_addon_name_locator)
@property
def disco_addon_author(self):
self.wait.until(
EC.visibility_of_element_located(self._disco_addon_author_locator)
)
return self.find_element(*self._disco_addon_author_locator)
def click_disco_addon_author(self):
self.disco_addon_author.click()
self.wait.until(
EC.number_of_windows_to_be(2),
message=f'Number of windows was {len(self.driver.window_handles)}, expected 2',
message=f"Number of windows was {len(self.driver.window_handles)}, expected 2",
)
self.driver.switch_to.window(self.driver.window_handles[1])
from pages.desktop.frontend.details import Detail
@ -187,26 +224,38 @@ class AboutAddons(Page):
@property
def disco_extension_summary(self):
self.wait.until(
EC.visibility_of_element_located(self._extension_summary_locator)
)
return self.find_element(*self._extension_summary_locator).text
@property
def disco_extension_rating(self):
self.wait.until(
EC.visibility_of_element_located(self._extension_rating_locator)
)
return self.find_element(*self._extension_rating_locator)
@property
def rating_score(self):
return self.disco_extension_rating.get_attribute('rating')
return self.disco_extension_rating.get_attribute("rating")
@property
def disco_extension_users(self):
self.wait.until(
EC.visibility_of_element_located(self._extension_users_count_locator)
)
return self.find_element(*self._extension_users_count_locator)
@property
def user_count(self):
return int(
self.disco_extension_users.text.replace('Users: ', '').replace(',', '')
self.disco_extension_users.text.replace("Users: ", "").replace(",", "")
)
@property
def install_button(self):
self.wait.until(
EC.visibility_of_element_located(self._addon_install_button_locator)
)
return self.find_element(*self._addon_install_button_locator)

Просмотреть файл

@ -12,8 +12,8 @@ from selenium.webdriver.support import expected_conditions as EC
class Base(Page):
_url = '{base_url}'
_amo_header = (By.CLASS_NAME, 'Header')
_url = "{base_url}"
_amo_header = (By.CLASS_NAME, "Header")
def __init__(self, selenium, base_url, **kwargs):
super(Base, self).__init__(selenium, base_url, timeout=30, **kwargs)
@ -21,33 +21,29 @@ class Base(Page):
def wait_for_page_to_load(self):
self.wait.until(
lambda _: self.find_element(*self._amo_header).is_displayed(),
message='AMO header was not loaded',
message="AMO header was not loaded",
)
return self
def wait_for_title_update(self, term):
self.wait.until(
EC.title_contains(term),
message=f'Page title was {self.driver.title}, expected {term}',
message=f"Page title was {self.driver.title}, expected {term}",
)
return self
def wait_for_current_url(self, term):
self.wait.until(
EC.url_contains(term), message=f'The url was {self.driver.current_url}'
EC.url_contains(term), message=f"The url was {self.driver.current_url}"
)
return self
def wait_for_element_to_be_displayed(self, element):
self.wait.until(
EC.visibility_of_element_located(element)
)
self.wait.until(EC.visibility_of_element_located(element))
return self
def wait_for_element_to_be_clickable(self, element):
self.wait.until(
EC.element_to_be_clickable(element)
)
self.wait.until(EC.element_to_be_clickable(element))
return self
@property
@ -70,14 +66,14 @@ class Base(Page):
EC.visibility_of_element_located(
(
By.CSS_SELECTOR,
'.Header-user-and-external-links .DropdownMenu-button-text',
".Header-user-and-external-links .DropdownMenu-button-text",
)
),
message='LOGIN FAILED: The user name was not displayed in the header after login.',
message="LOGIN FAILED: The user name was not displayed in the header after login.",
)
break
except StaleElementReferenceException as exception:
print(f'{exception}: Try to find the element again')
print(f"{exception}: Try to find the element again")
count += 1
return self
@ -89,27 +85,27 @@ class Base(Page):
fxa = self.header.click_login()
# wait for the FxA login page to load
self.wait.until(
EC.visibility_of_element_located((By.NAME, 'email')),
message=f'FxA email input field was not displayed in {self.driver.current_url}',
EC.visibility_of_element_located((By.NAME, "email")),
message=f"FxA email input field was not displayed in {self.driver.current_url}",
)
fxa.account(user)
# verifies that the AMO page was fully loaded after login
WebDriverWait(self.driver, 30).until(
EC.invisibility_of_element_located((By.CLASS_NAME, 'LoadingText')),
message='AMO was not loaded properly after login.',
EC.invisibility_of_element_located((By.CLASS_NAME, "LoadingText")),
message="AMO was not loaded properly after login.",
)
# assess that the user has been logged in
self.wait.until(
lambda _: self.logged_in,
message=f'Log in flow was not successful. URL at fail time was {self.driver.current_url}',
message=f"Log in flow was not successful. URL at fail time was {self.driver.current_url}",
)
def register(self):
fxa_register_page = self.header.click_login()
self.wait.until(EC.visibility_of_element_located((By.NAME, 'email')))
self.wait.until(EC.visibility_of_element_located((By.NAME, "email")))
fxa_register_page.fxa_register()
# wait for transition between FxA page and AMO
self.wait.until(EC.url_contains('addons'))
self.wait.until(EC.url_contains("addons"))
self.wait.until(lambda _: self.logged_in)
def logout(self):
@ -117,60 +113,61 @@ class Base(Page):
class Header(Region):
_root_locator = (By.CLASS_NAME, 'Header')
_header_title_locator = (By.CLASS_NAME, 'Header-title')
_firefox_logo_locator = (By.CLASS_NAME, 'Header-title')
_root_locator = (By.CLASS_NAME, "Header")
_header_title_locator = (By.CLASS_NAME, "Header-title")
_firefox_logo_locator = (By.CLASS_NAME, "Header-title")
_extensions_locator = (
By.CSS_SELECTOR,
'.SectionLinks \
> li:nth-child(1) > a:nth-child(1)',
".SectionLinks \
> li:nth-child(1) > a:nth-child(1)",
)
_extensions_active_button_locator = (
By.CLASS_NAME,
'SectionLinks-link-extension.SectionLinks-link--active',
"SectionLinks-link-extension.SectionLinks-link--active",
)
_login_locator = (By.CLASS_NAME, 'Header-authenticate-button')
_login_locator = (By.CLASS_NAME, "Header-authenticate-button")
_user_locator = (
By.CSS_SELECTOR,
'.Header-user-and-external-links .DropdownMenu-button-text',
".Header-user-and-external-links .DropdownMenu-button-text",
)
_account_dropdown_locator = (
By.CSS_SELECTOR,
'.DropdownMenu.Header-authenticate-button .DropdownMenu-items',
".DropdownMenu.Header-authenticate-button .DropdownMenu-items",
)
_user_menu_links_locator = (
By.CSS_SELECTOR,
'.Header-user-and-external-links .DropdownMenuItem-link a',
".Header-user-and-external-links .DropdownMenuItem-link a",
)
_logout_locator = (
By.CSS_SELECTOR,
'.DropdownMenu-items .Header-logout-button button',
".DropdownMenu-items .Header-logout-button button",
)
_more_menu_locator = (
By.CSS_SELECTOR,
'.Header-SectionLinks .SectionLinks-dropdown',
".Header-SectionLinks .SectionLinks-dropdown",
)
_more_dropdown_locator = (
By.CSS_SELECTOR,
'.SectionLinks-dropdown .DropdownMenu-items',
".SectionLinks-dropdown .DropdownMenu-items",
)
_more_dropdown_sections_locator = (By.CSS_SELECTOR, '.DropdownMenuItem-section')
_more_dropdown_links_locator = (By.CSS_SELECTOR, '.DropdownMenuItem a')
_more_dropdown_sections_locator = (By.CSS_SELECTOR, ".DropdownMenuItem-section")
_more_dropdown_links_locator = (By.CSS_SELECTOR, ".DropdownMenuItem a")
_themes_locator = (
By.CSS_SELECTOR,
'.SectionLinks > li:nth-child(2) > \
a:nth-child(1)',
".SectionLinks > li:nth-child(2) > \
a:nth-child(1)",
)
_themes_active_button_locator = (
By.CLASS_NAME,
'SectionLinks-link-theme.SectionLinks-link--active',
"SectionLinks-link-theme.SectionLinks-link--active",
)
_devhub_locator = (By.CLASS_NAME, 'Header-developer-hub-link')
_extension_workshop_locator = (By.CLASS_NAME, 'Header-extension-workshop-link')
_blog_link_locator = (By.CLASS_NAME, 'Header-blog-link')
_active_link_locator = (By.CLASS_NAME, 'SectionLinks-link--active')
_devhub_locator = (By.CLASS_NAME, "Header-developer-hub-link")
_extension_workshop_locator = (By.CLASS_NAME, "Header-extension-workshop-link")
_blog_link_locator = (By.CLASS_NAME, "Header-blog-link")
_active_link_locator = (By.CLASS_NAME, "SectionLinks-link--active")
def click_extensions(self):
self.wait.until(EC.element_to_be_clickable(self._extensions_locator))
self.find_element(*self._extensions_locator).click()
from pages.desktop.frontend.extensions import Extensions
@ -178,18 +175,24 @@ class Header(Region):
@property
def extensions_text(self):
self.wait.until(EC.visibility_of_element_located(self._extensions_locator))
return self.find_element(*self._extensions_locator).text
@property
def extensions_button_active(self):
"""Checks that the 'Extensions' menu button is highlighted when selected"""
self.wait.until(
EC.visibility_of_element_located(self._extensions_active_button_locator)
)
return self.find_element(*self._extensions_active_button_locator)
@property
def themes_link(self):
self.wait.until(EC.visibility_of_element_located(self._themes_locator))
return self.find_element(*self._themes_locator)
def click_themes(self):
self.wait.until(EC.element_to_be_clickable(self._themes_locator))
self.find_element(*self._themes_locator).click()
from pages.desktop.frontend.themes import Themes
@ -198,6 +201,9 @@ class Header(Region):
@property
def themes_button_active(self):
"""Checks that the 'Themes' menu button is highlighted when selected"""
self.wait.until(
EC.visibility_of_element_located(self._themes_active_button_locator)
)
return self.find_element(*self._themes_active_button_locator)
def click_title(self):
@ -221,7 +227,7 @@ class Header(Region):
self.driver, 30, ignored_exceptions=StaleElementReferenceException
).until(
EC.text_to_be_present_in_element(self._user_locator, value),
message='The expected displayed name was not visible',
message="The expected displayed name was not visible",
)
def click_logout(self):
@ -231,7 +237,7 @@ class Header(Region):
EC.element_to_be_clickable(
(
By.CSS_SELECTOR,
'.Header-authenticate-button',
".Header-authenticate-button",
)
)
)
@ -251,10 +257,11 @@ class Header(Region):
action.perform()
self.wait.until(
lambda s: self.is_element_displayed(*self._login_locator),
message='The login button was not displayed after logout',
message="The login button was not displayed after logout",
)
def user_menu_link(self, count):
self.wait.until(EC.visibility_of_element_located(self._user_menu_links_locator))
return self.find_elements(*self._user_menu_links_locator)[count]
def click_user_menu_links(self, count, landing_page):
@ -264,7 +271,7 @@ class Header(Region):
EC.element_to_be_clickable(
(
By.CSS_SELECTOR,
'.Header-authenticate-button',
".Header-authenticate-button",
)
)
)
@ -282,11 +289,12 @@ class Header(Region):
# waits for the landing page to open
self.wait.until(
EC.visibility_of_element_located((By.CSS_SELECTOR, landing_page)),
message=f'Expected page not loaded; page was {self.driver.current_url}',
message=f"Expected page not loaded; page was {self.driver.current_url}",
)
@property
def more_menu_link(self):
self.wait.until(EC.visibility_of_element_located(self._more_menu_locator))
return self.find_element(*self._more_menu_locator)
@property
@ -313,43 +321,51 @@ class Header(Region):
@property
def developer_hub_link(self):
self.wait.until(EC.visibility_of_element_located(self._devhub_locator))
return self.find_element(*self._devhub_locator)
def click_developer_hub(self):
self.wait.until(EC.element_to_be_clickable(self._devhub_locator))
self.find_element(*self._devhub_locator).click()
self.wait.until(
EC.number_of_windows_to_be(2),
message=f'Number of windows was {len(self.driver.window_handles)}, expected 2',
message=f"Number of windows was {len(self.driver.window_handles)}, expected 2",
)
new_tab = self.driver.window_handles[1]
self.driver.switch_to.window(new_tab)
self.wait.until(
EC.visibility_of_element_located((By.CLASS_NAME, 'DevHub-Navigation-Logo')),
message=f'DevHub homepage not loaded; page was {self.driver.current_url}',
EC.visibility_of_element_located((By.CLASS_NAME, "DevHub-Navigation-Logo")),
message=f"DevHub homepage not loaded; page was {self.driver.current_url}",
)
@property
def extension_workshop_link(self):
self.wait.until(
EC.visibility_of_element_located(self._extension_workshop_locator)
)
return self.find_element(*self._extension_workshop_locator)
def click_extension_workshop(self):
self.wait.until(EC.element_to_be_clickable(self._extension_workshop_locator))
self.find_element(*self._extension_workshop_locator).click()
self.wait.until(
EC.number_of_windows_to_be(2),
message=f'Number of windows was {len(self.driver.window_handles)}, expected 2',
message=f"Number of windows was {len(self.driver.window_handles)}, expected 2",
)
new_tab = self.driver.window_handles[1]
self.driver.switch_to.window(new_tab)
self.wait.until(
EC.visibility_of_element_located((By.CLASS_NAME, 'logo')),
message=f'Extension Workshop not loaded; page was {self.driver.current_url}',
EC.visibility_of_element_located((By.CLASS_NAME, "logo")),
message=f"Extension Workshop not loaded; page was {self.driver.current_url}",
)
@property
def firefox_addons_blog_link(self):
self.wait.until(EC.visibility_of_element_located(self._blog_link_locator))
return self.find_element(*self._blog_link_locator)
def click_firefox_addons_blog(self):
self.wait.until(EC.element_to_be_clickable(self._blog_link_locator))
self.find_element(*self._blog_link_locator).click()
@property
@ -357,28 +373,32 @@ class Header(Region):
return self.find_element(*self._active_link_locator).text
class SearchBox(Region):
_root_locator = (By.CLASS_NAME, 'AutoSearchInput')
_query_field_locator = (By.ID, 'AutoSearchInput-q')
_root_locator = (By.CLASS_NAME, "AutoSearchInput")
_query_field_locator = (By.ID, "AutoSearchInput-q")
_search_suggestions_list_locator = (
By.CLASS_NAME,
'AutoSearchInput-suggestions-list',
"AutoSearchInput-suggestions-list",
)
_search_suggestions_item_locator = (
By.CLASS_NAME,
'AutoSearchInput-suggestions-item',
"AutoSearchInput-suggestions-item",
)
_search_textbox_locator = (By.CLASS_NAME, 'AutoSearchInput-query')
_search_item_name = (By.CSS_SELECTOR, '.SearchSuggestion-name')
_search_textbox_locator = (By.CLASS_NAME, "AutoSearchInput-query")
_search_item_name = (By.CSS_SELECTOR, ".SearchSuggestion-name")
_highlighted_selected_locator = (
By.CSS_SELECTOR,
'.AutoSearchInput-suggestions-item--highlighted',
".AutoSearchInput-suggestions-item--highlighted",
)
@property
def search_field(self):
self.wait.until(EC.visibility_of_element_located(self._query_field_locator))
return self.find_element(*self._query_field_locator)
def search_for(self, term, execute=True):
self.wait.until(
EC.visibility_of_element_located(self._search_textbox_locator)
)
textbox = self.find_element(*self._search_textbox_locator)
textbox.click()
textbox.send_keys(term)
@ -389,7 +409,7 @@ class Header(Region):
return Search(self.driver, self.page).wait_for_page_to_load()
WebDriverWait(self.driver, 30).until(
EC.invisibility_of_element_located((By.CLASS_NAME, 'LoadingText'))
EC.invisibility_of_element_located((By.CLASS_NAME, "LoadingText"))
)
return self.search_suggestions
@ -399,10 +419,10 @@ class Header(Region):
lambda _: self.is_element_displayed(
*self._search_suggestions_list_locator
),
message='Search suggestions list did not open',
message="Search suggestions list did not open",
)
WebDriverWait(self.driver, 30).until(
EC.invisibility_of_element_located((By.CLASS_NAME, 'LoadingText'))
EC.invisibility_of_element_located((By.CLASS_NAME, "LoadingText"))
)
el_list = self.find_element(*self._search_suggestions_list_locator)
items = el_list.find_elements(*self._search_suggestions_item_locator)
@ -410,26 +430,35 @@ class Header(Region):
@property
def highlighted_suggestion(self):
self.wait.until(
EC.visibility_of_element_located(self._highlighted_selected_locator)
)
return self.find_element(*self._highlighted_selected_locator)
class SearchSuggestionItem(Region):
_item_name_locator = (By.CLASS_NAME, 'SearchSuggestion-name')
_item_icon_locator = (By.CLASS_NAME, 'SearchSuggestion-icon')
_promoted_icon_locator = (By.CSS_SELECTOR, '.IconPromotedBadge > span')
_item_name_locator = (By.CLASS_NAME, "SearchSuggestion-name")
_item_icon_locator = (By.CLASS_NAME, "SearchSuggestion-icon")
_promoted_icon_locator = (By.CSS_SELECTOR, ".IconPromotedBadge > span")
@property
def name(self):
self.wait.until(
EC.visibility_of_element_located(self._item_name_locator)
)
return self.find_element(*self._item_name_locator).text
@property
def addon_icon(self):
self.wait.until(
EC.visibility_of_element_located(self._item_icon_locator)
)
return self.find_element(*self._item_icon_locator)
@property
def promoted_icon(self):
WebDriverWait(self.driver, 10).until(
EC.visibility_of_element_located(self._promoted_icon_locator),
message='Promoted icon was not found for these search suggestions',
message="Promoted icon was not found for these search suggestions",
)
return self.find_element(*self._promoted_icon_locator).text
@ -442,55 +471,76 @@ class Header(Region):
class Footer(Region):
_root_locator = (By.CSS_SELECTOR, '.Footer-wrapper')
_footer_amo_links_locator = (By.CSS_SELECTOR, '.Footer-amo-links')
_footer_browsers_links_locator = (By.CSS_SELECTOR, '.Footer-browsers-links')
_footer_products_links_locator = (By.CSS_SELECTOR, '.Footer-product-links')
_footer_mozilla_link_locator = (By.CSS_SELECTOR, '.Footer-mozilla-link')
_footer_social_locator = (By.CSS_SELECTOR, '.Footer-links-social')
_footer_links_locator = (By.CSS_SELECTOR, '.Footer-links li a')
_footer_legal_locator = (By.CSS_SELECTOR, '.Footer-legal-links ')
_footer_copyright_links_locator = (By.CSS_SELECTOR, '.Footer-copyright a')
_copyright_message_locator = (By.CSS_SELECTOR, '.Footer-copyright')
_language_picker_locator = (By.ID, 'lang-picker')
_root_locator = (By.CSS_SELECTOR, ".Footer-wrapper")
_footer_amo_links_locator = (By.CSS_SELECTOR, ".Footer-amo-links")
_footer_browsers_links_locator = (By.CSS_SELECTOR, ".Footer-browsers-links")
_footer_products_links_locator = (By.CSS_SELECTOR, ".Footer-product-links")
_footer_mozilla_link_locator = (By.CSS_SELECTOR, ".Footer-mozilla-link")
_footer_social_locator = (By.CSS_SELECTOR, ".Footer-links-social")
_footer_links_locator = (By.CSS_SELECTOR, ".Footer-links li a")
_footer_legal_locator = (By.CSS_SELECTOR, ".Footer-legal-links ")
_footer_copyright_links_locator = (By.CSS_SELECTOR, ".Footer-copyright a")
_copyright_message_locator = (By.CSS_SELECTOR, ".Footer-copyright")
_language_picker_locator = (By.ID, "lang-picker")
@property
def addon_links(self):
self.wait.until(
EC.visibility_of_element_located(self._footer_amo_links_locator)
)
element = self.find_element(*self._footer_amo_links_locator)
return element.find_elements(*self._footer_links_locator)
@property
def browsers_links(self):
self.wait.until(
EC.visibility_of_element_located(self._footer_browsers_links_locator)
)
element = self.find_element(*self._footer_browsers_links_locator)
return element.find_elements(*self._footer_links_locator)
@property
def products_links(self):
self.wait.until(
EC.visibility_of_element_located(self._footer_products_links_locator)
)
element = self.find_element(*self._footer_products_links_locator)
return element.find_elements(*self._footer_links_locator)
@property
def mozilla_link(self):
self.wait.until(
EC.visibility_of_element_located(self._footer_mozilla_link_locator)
)
return self.find_element(*self._footer_mozilla_link_locator)
@property
def social_links(self):
self.wait.until(EC.visibility_of_element_located(self._footer_social_locator))
element = self.find_element(*self._footer_social_locator)
return element.find_elements(By.CSS_SELECTOR, 'li a')
return element.find_elements(By.CSS_SELECTOR, "li a")
@property
def legal_links(self):
self.wait.until(EC.visibility_of_element_located(self._footer_legal_locator))
element = self.find_element(*self._footer_legal_locator)
return element.find_elements(By.CSS_SELECTOR, 'li a')
return element.find_elements(By.CSS_SELECTOR, "li a")
@property
def copyright_links(self):
self.wait.until(
EC.visibility_of_element_located(self._footer_copyright_links_locator)
)
return self.find_elements(*self._footer_copyright_links_locator)
@property
def copyright_message(self):
self.wait.until(
EC.visibility_of_element_located(self._copyright_message_locator)
)
return self.find_element(*self._copyright_message_locator)
def language_picker(self, value):
self.wait.until(EC.visibility_of_element_located(self._language_picker_locator))
select = Select(self.find_element(*self._language_picker_locator))
select.select_by_visible_text(value)

Просмотреть файл

@ -8,16 +8,15 @@ from pages.desktop.developers.edit_addon import EditAddon
class ManageAddons(Base):
_my_addons_page_logo = (By.CSS_SELECTOR, '.site-titles')
_page_title_locator = (By.CSS_SELECTOR, '.hero > h1')
_my_addons_page_logo = (By.CSS_SELECTOR, ".site-titles")
_page_title_locator = (By.CSS_SELECTOR, ".hero > h1")
_my_themes_section_button_locator = (
By.CSS_SELECTOR,
'.submission-type-tabs > a:nth-child(2)',
".submission-type-tabs > a:nth-child(2)",
)
_sort_by_created_locator = (By.LINK_TEXT, 'Created')
_addon_items_locator = (By.CLASS_NAME, 'item.addon')
_submit_addon_button_locator = (By.CSS_SELECTOR, '#submit-addon > a')
_sort_by_created_locator = (By.LINK_TEXT, "Created")
_addon_items_locator = (By.CLASS_NAME, "item.addon")
_submit_addon_button_locator = (By.CSS_SELECTOR, "#submit-addon > a")
def wait_for_page_to_load(self):
self.wait.until(
@ -27,10 +26,12 @@ class ManageAddons(Base):
@property
def my_addons_page_logo(self):
self.wait_for_element_to_be_displayed(self._my_addons_page_logo)
return self.find_element(*self._my_addons_page_logo)
@property
def my_addons_page_title(self):
self.wait_for_element_to_be_displayed(self._page_title_locator)
return self.find_element(*self._page_title_locator)
def click_on_my_themes(self):
@ -38,8 +39,8 @@ class ManageAddons(Base):
self.wait.until(
lambda _: self.find_element(
*self._my_themes_section_button_locator
).get_attribute('class')
== 'active'
).get_attribute("class")
== "active"
)
def sort_by_created(self):
@ -51,13 +52,15 @@ class ManageAddons(Base):
return [self.AddonDetails(self, el) for el in items]
class AddonDetails(Region):
_addon_name_locator = (By.CSS_SELECTOR, '.item.addon .info > h3')
_addon_edit_link_locator = (By.CSS_SELECTOR, '.item.addon .info > h3 > a')
_addon_name_locator = (By.CSS_SELECTOR, ".item.addon .info > h3")
_addon_edit_link_locator = (By.CSS_SELECTOR, ".item.addon .info > h3 > a")
@property
def name(self):
self.wait.until(EC.visibility_of_element_located(self._addon_name_locator))
return self.find_element(*self._addon_name_locator).text
def click_addon_name(self):
self.wait.until(EC.element_to_be_clickable(self._addon_edit_link_locator))
self.find_element(*self._addon_edit_link_locator).click()
return EditAddon(self.driver, self.page.base_url).wait_for_page_to_load()

Просмотреть файл

@ -30,7 +30,10 @@ class DevhubAddonValidate(Base):
_on_your_own_text_checkbox = (By.CSS_SELECTOR, "#id_channel_1")
_upload_details_text = (By.CSS_SELECTOR, ".upload-details")
_upload_status_results_succes = (By.ID, "upload-status-results")
_upload_status_results_failed = (By.CSS_SELECTOR, "div.upload-status div.status-fail strong")
_upload_status_results_failed = (
By.CSS_SELECTOR,
"div.upload-status div.status-fail strong",
)
_upload_status_bar_results_approved = (By.CSS_SELECTOR, "div.bar-success")
_upload_status_bar_results_failed = (By.CSS_SELECTOR, "div.bar-fail")
_upload_status = (By.ID, "uploadstatus")
@ -44,11 +47,15 @@ class DevhubAddonValidate(Base):
def is_validation_approved(self):
"""Wait for addon validation to complete; if not successful, the test will fail"""
self.wait.until(EC.visibility_of_element_located(self._upload_status_bar_results_approved))
self.wait.until(
EC.visibility_of_element_located(self._upload_status_bar_results_approved)
)
def is_not_validated(self):
"""Wait for addon validation to complete; if successful, the test will fail"""
self.wait.until(EC.visibility_of_element_located(self._upload_status_bar_results_failed))
self.wait.until(
EC.visibility_of_element_located(self._upload_status_bar_results_failed)
)
@property
def addon_on_your_site(self):

Просмотреть файл

@ -18,90 +18,90 @@ from pages.desktop.developers.submit_addon import SubmitAddon
class DevHubHome(Base):
"""AMO Developer Hub homepage"""
URL_TEMPLATE = 'developers/'
URL_TEMPLATE = "developers/"
_logo_locator = (By.CLASS_NAME, 'DevHub-Navigation-Logo')
_ext_workshop_link_locator = (By.LINK_TEXT, 'Extension Workshop')
_documentation_link_locator = (By.LINK_TEXT, 'Documentation')
_support_link_locator = (By.LINK_TEXT, 'Support')
_blog_link_locator = (By.LINK_TEXT, 'Blog')
_fxa_login_button_locator = (By.CLASS_NAME, 'DevHub-Navigation-Register')
_page_overview_title_locator = (By.CSS_SELECTOR, '.DevHub-Overview h1')
_page_overview_summary_locator = (By.CSS_SELECTOR, '.DevHub-Overview p')
_learn_how_button_locator = (By.CSS_SELECTOR, '.DevHub-Overview a')
_logo_locator = (By.CLASS_NAME, "DevHub-Navigation-Logo")
_ext_workshop_link_locator = (By.LINK_TEXT, "Extension Workshop")
_documentation_link_locator = (By.LINK_TEXT, "Documentation")
_support_link_locator = (By.LINK_TEXT, "Support")
_blog_link_locator = (By.LINK_TEXT, "Blog")
_fxa_login_button_locator = (By.CLASS_NAME, "DevHub-Navigation-Register")
_page_overview_title_locator = (By.CSS_SELECTOR, ".DevHub-Overview h1")
_page_overview_summary_locator = (By.CSS_SELECTOR, ".DevHub-Overview p")
_learn_how_button_locator = (By.CSS_SELECTOR, ".DevHub-Overview a")
_page_content_title_locator = (
By.CSS_SELECTOR,
'.DevHub-content-header--submit-or-manage',
".DevHub-content-header--submit-or-manage",
)
_page_content_summary_locator = (
By.CSS_SELECTOR,
'.DevHub-content-copy p:nth-child(3)',
".DevHub-content-copy p:nth-child(3)",
)
_page_content_login_link_locator = (
By.CSS_SELECTOR,
'.DevHub-content-copy:nth-child(1) > a',
".DevHub-content-copy:nth-child(1) > a",
)
_page_content_featured_image_locator = (
By.CSS_SELECTOR,
'.DevHub-content-image--submit-or-manage',
".DevHub-content-image--submit-or-manage",
)
_get_involved_title_locator = (
By.CSS_SELECTOR,
'.DevHub-content-container--get-involved h3',
".DevHub-content-container--get-involved h3",
)
_get_involved_summary_locator = (
By.CSS_SELECTOR,
'.DevHub-content-container--get-involved p',
".DevHub-content-container--get-involved p",
)
_dev_community_link_locator = (
By.CSS_SELECTOR,
'.DevHub-content-container--get-involved a',
".DevHub-content-container--get-involved a",
)
_get_involved_image_locator = (By.CLASS_NAME, 'DevHub-content-image--get-involved')
_footer_language_picker_locator = (By.ID, 'language')
_footer_products_section_locator = (By.CSS_SELECTOR, '.Footer-products-links')
_footer_links_locator = (By.CSS_SELECTOR, '.Footer-links li a')
_get_involved_image_locator = (By.CLASS_NAME, "DevHub-content-image--get-involved")
_footer_language_picker_locator = (By.ID, "language")
_footer_products_section_locator = (By.CSS_SELECTOR, ".Footer-products-links")
_footer_links_locator = (By.CSS_SELECTOR, ".Footer-links li a")
# elements visible only to logged in users
_sign_out_link_locator = (
By.CSS_SELECTOR,
'.DevHub-Navigation-SignOut a:nth-child(1)',
".DevHub-Navigation-SignOut a:nth-child(1)",
)
_user_logged_in_avatar_locator = (
By.CSS_SELECTOR,
'.DevHub-Navigation-SignOut a:nth-child(2)',
".DevHub-Navigation-SignOut a:nth-child(2)",
)
_my_addons_header_link_locator = (By.LINK_TEXT, 'My Add-ons')
_my_addons_header_link_locator = (By.LINK_TEXT, "My Add-ons")
_user_profile_picture_locator = (
By.CSS_SELECTOR,
'.DevHub-Navigation-SignOut a img',
".DevHub-Navigation-SignOut a img",
)
_logged_in_hero_banner_header_locator = (
By.CSS_SELECTOR,
'.DevHub-callout-box--banner h2',
".DevHub-callout-box--banner h2",
)
_logged_in_hero_banner_text_locator = (
By.CSS_SELECTOR,
'.DevHub-callout-box--banner p',
".DevHub-callout-box--banner p",
)
_logged_in_hero_banner_link_locator = (
By.CSS_SELECTOR,
'.DevHub-callout-box--banner a',
".DevHub-callout-box--banner a",
)
_my_addons_section_header_locator = (By.CSS_SELECTOR, '.DevHub-MyAddons h2')
_my_addons_section_paragraph_locator = (By.CSS_SELECTOR, '.DevHub-MyAddons-copy')
_my_addons_section_list_locator = (By.CSS_SELECTOR, '.DevHub-MyAddons-item')
_my_addons_section_header_locator = (By.CSS_SELECTOR, ".DevHub-MyAddons h2")
_my_addons_section_paragraph_locator = (By.CSS_SELECTOR, ".DevHub-MyAddons-copy")
_my_addons_section_list_locator = (By.CSS_SELECTOR, ".DevHub-MyAddons-item")
_see_all_addons_link_locator = (
By.CSS_SELECTOR,
'.DevHub-MyAddons-item-buttons-all',
".DevHub-MyAddons-item-buttons-all",
)
_submit_addon_button_locator = (
By.CSS_SELECTOR,
'.DevHub-MyAddons .Button:nth-of-type(1)',
".DevHub-MyAddons .Button:nth-of-type(1)",
)
_submit_theme_button_locator = (
By.CSS_SELECTOR,
'.DevHub-MyAddons .Button:nth-of-type(2)',
".DevHub-MyAddons .Button:nth-of-type(2)",
)
def wait_for_page_to_load(self):
@ -117,7 +117,7 @@ class DevHubHome(Base):
# There are a few Devhub links that redirect to the Extension Workshop(EW) page.
# The following method can be used by tests which verify that EW has loaded.
self.wait.until(
EC.visibility_of_element_located((By.CSS_SELECTOR, '.banner.intro h1'))
EC.visibility_of_element_located((By.CSS_SELECTOR, ".banner.intro h1"))
)
@property
@ -130,7 +130,7 @@ class DevHubHome(Base):
self.find_element(*self._documentation_link_locator).click()
self.wait.until(
EC.text_to_be_present_in_element(
(By.CSS_SELECTOR, '.main-content h1'), 'Browser extensions'
(By.CSS_SELECTOR, ".main-content h1"), "Browser extensions"
),
message=f'Expected "Browser Extensions" in the page title but got'
f' "{self.find_element(By.CSS_SELECTOR, ".main-content h1").text}".',
@ -143,7 +143,7 @@ class DevHubHome(Base):
def click_blog(self):
self.wait_for_element_to_be_clickable(self._blog_link_locator)
self.find_element(*self._blog_link_locator).click()
self.wait.until(EC.visibility_of_element_located((By.ID, 'site-title')))
self.wait.until(EC.visibility_of_element_located((By.ID, "site-title")))
@property
def header_login_button(self):
@ -174,14 +174,14 @@ class DevHubHome(Base):
fxa = self.click_header_login_button()
# wait for the FxA login page to load
self.wait.until(
EC.visibility_of_element_located((By.NAME, 'email')),
message=f'FxA email input field was not displayed in {self.driver.current_url}',
EC.visibility_of_element_located((By.NAME, "email")),
message=f"FxA email input field was not displayed in {self.driver.current_url}",
)
fxa.account(user)
# assess that the user has been logged in
self.wait.until(
lambda _: self.sign_out_link.is_displayed(),
message=f'Log in flow was not successful. URL at fail time was {self.driver.current_url}',
message=f"Log in flow was not successful. URL at fail time was {self.driver.current_url}",
)
@property
@ -240,8 +240,9 @@ class DevHubHome(Base):
@property
def user_profile_icon(self):
# get the 'alt' attribute to determine if img is uploaded by the user and is not the default avatar
self.wait_for_element_to_be_displayed(self._user_profile_picture_locator)
return self.find_element(*self._user_profile_picture_locator).get_attribute(
'alt'
"alt"
)
def click_user_profile_picture(self):
@ -260,7 +261,9 @@ class DevHubHome(Base):
@property
def logged_in_hero_banner_header(self):
self.wait_for_element_to_be_displayed(self._logged_in_hero_banner_header_locator)
self.wait_for_element_to_be_displayed(
self._logged_in_hero_banner_header_locator
)
return self.find_element(*self._logged_in_hero_banner_header_locator).text
@property
@ -279,6 +282,7 @@ class DevHubHome(Base):
return self.find_element(*self._logged_in_hero_banner_text_locator).text
def click_logged_in_hero_banner_extension_workshop_link(self):
self.wait_for_element_to_be_clickable(self._logged_in_hero_banner_link_locator)
self.find_element(*self._logged_in_hero_banner_link_locator).click()
def click_see_all_addons_link(self):
@ -320,30 +324,36 @@ class DevHubHome(Base):
return element.find_elements(*self._footer_links_locator)
class MyAddonsList(Region):
_my_addon_icon_locator = (By.CSS_SELECTOR, '.DevHub-MyAddons-item-icon')
_my_addon_name_locator = (By.CSS_SELECTOR, '.DevHub-MyAddons-item-name')
_my_addon_edit_link_locator = (By.CSS_SELECTOR, '.DevHub-MyAddons-item-edit')
_my_addon_icon_locator = (By.CSS_SELECTOR, ".DevHub-MyAddons-item-icon")
_my_addon_name_locator = (By.CSS_SELECTOR, ".DevHub-MyAddons-item-name")
_my_addon_edit_link_locator = (By.CSS_SELECTOR, ".DevHub-MyAddons-item-edit")
_my_addon_version_number_locator = (
By.CSS_SELECTOR,
'.DevHub-MyAddons-item-versions',
".DevHub-MyAddons-item-versions",
)
_my_addon_version_status_locator = (
By.CSS_SELECTOR,
'.DevHub-MyAddons-VersionStatus',
".DevHub-MyAddons-VersionStatus",
)
_my_addon_rating_text_locator = (By.CSS_SELECTOR, '.addon-rating strong')
_my_addon_rating_stars_locator = (By.CSS_SELECTOR, '.stars')
_my_addon_rating_text_locator = (By.CSS_SELECTOR, ".addon-rating strong")
_my_addon_rating_stars_locator = (By.CSS_SELECTOR, ".stars")
_my_addon_last_modified_date_locator = (
By.CSS_SELECTOR,
'.DevHub-MyAddons-item-modified span:nth-of-type(2)',
".DevHub-MyAddons-item-modified span:nth-of-type(2)",
)
@property
def my_addon_icon(self):
self.wait.until(
EC.visibility_of_element_located(self._my_addon_icon_locator)
)
return self.find_element(*self._my_addon_icon_locator)
@property
def my_addon_name(self):
self.wait.until(
EC.visibility_of_element_located(self._my_addon_name_locator)
)
return self.find_element(*self._my_addon_name_locator)
def click_my_addon_edit_link(self):
@ -352,22 +362,39 @@ class DevHubHome(Base):
@property
def my_addon_version_number(self):
self.wait.until(
EC.visibility_of_element_located(self._my_addon_version_number_locator)
)
return self.find_element(*self._my_addon_version_number_locator)
@property
def my_addon_version_status(self):
self.wait.until(
EC.visibility_of_element_located(self._my_addon_version_status_locator)
)
return self.find_element(*self._my_addon_version_status_locator)
@property
def my_addon_rating_text(self):
self.wait.until(
EC.visibility_of_element_located(self._my_addon_rating_text_locator)
)
return self.find_element(*self._my_addon_rating_text_locator)
@property
def my_addon_rating_stars(self):
self.wait.until(
EC.visibility_of_element_located(self._my_addon_rating_stars_locator)
)
return self.find_element(*self._my_addon_rating_stars_locator)
@property
def my_addon_last_modified_date(self):
self.wait.until(
EC.visibility_of_element_located(
self._my_addon_last_modified_date_locator
)
)
return self.find_element(*self._my_addon_last_modified_date_locator)
@property
@ -386,8 +413,8 @@ class DevHubHome(Base):
try:
# DevHub homepage will display only the Approved or Awaiting Review statuses, so we check these first
if (
'Approved' in status.listed_addon_status
or 'Awaiting Review' in status.listed_addon_status
"Approved" in status.listed_addon_status
or "Awaiting Review" in status.listed_addon_status
):
return True
else:
@ -407,100 +434,138 @@ class DevHubHome(Base):
class ConnectFooter(Region):
_connect_footer_title_locator = (
By.CSS_SELECTOR,
'.DevHub-content-header--Connect h2',
".DevHub-content-header--Connect h2",
)
_twitter_column_title_locator = (
By.CSS_SELECTOR,
'.Devhub-content-copy--Connect div:nth-child(1) h4:nth-child(1)',
".Devhub-content-copy--Connect div:nth-child(1) h4:nth-child(1)",
)
_twitter_links_locator = (
By.CSS_SELECTOR,
'.DevHub-content-copy--Connect-twitter-list a',
".DevHub-content-copy--Connect-twitter-list a",
)
_more_column_title_locator = (
By.CSS_SELECTOR,
'.Devhub-content-copy--Connect div:nth-child(2) h4:nth-child(1)',
".Devhub-content-copy--Connect div:nth-child(2) h4:nth-child(1)",
)
_more_contact_links_locator = (
By.CSS_SELECTOR,
'.DevHub-Connect-section:nth-child(2) > ul a',
".DevHub-Connect-section:nth-child(2) > ul a",
)
_newsletter_header_locator = (
By.CSS_SELECTOR,
'.Devhub-content-copy--Connect div:nth-child(3) h4',
".Devhub-content-copy--Connect div:nth-child(3) h4",
)
_newsletter_info_text_locator = (
By.CSS_SELECTOR,
'.Devhub-content-copy--Connect div:nth-child(3) p',
".Devhub-content-copy--Connect div:nth-child(3) p",
)
_newsletter_email_input_field_locator = (By.ID, 'email')
_newsletter_sign_up_button_locator = (By.CSS_SELECTOR, '.btn-success')
_newsletter_privacy_checkbox_locator = (By.ID, 'privacy')
_newsletter_privacy_notice_link_locator = (By.CSS_SELECTOR, '.form_group-agree a')
_newsletter_email_input_field_locator = (By.ID, "email")
_newsletter_sign_up_button_locator = (By.CSS_SELECTOR, ".btn-success")
_newsletter_privacy_checkbox_locator = (By.ID, "privacy")
_newsletter_privacy_notice_link_locator = (By.CSS_SELECTOR, ".form_group-agree a")
_newsletter_sign_up_confirmation_header_locator = (
By.CSS_SELECTOR,
'.newsletter_thanks h2',
".newsletter_thanks h2",
)
_newsletter_sign_up_confirmation_message_locator = (
By.CSS_SELECTOR,
'.newsletter_thanks p',
".newsletter_thanks p",
)
@property
def connect_footer_title(self):
self.wait.until(
EC.visibility_of_element_located(self._connect_footer_title_locator)
)
return self.find_element(*self._connect_footer_title_locator).text
@property
def connect_twitter_title(self):
self.wait.until(
EC.visibility_of_element_located(self._twitter_column_title_locator)
)
return self.find_element(*self._twitter_column_title_locator).text
@property
def twitter_links(self):
self.wait.until(EC.visibility_of_element_located(self._twitter_links_locator))
return self.find_elements(*self._twitter_links_locator)
@property
def connect_more_title(self):
self.wait.until(
EC.visibility_of_element_located(self._more_column_title_locator)
)
return self.find_element(*self._more_column_title_locator).text
@property
def more_connect_links(self):
self.wait.until(
EC.visibility_of_element_located(self._more_contact_links_locator)
)
return self.find_elements(*self._more_contact_links_locator)
@property
def newsletter_section_header(self):
self.wait.until(
EC.visibility_of_element_located(self._newsletter_header_locator)
)
return self.find_element(*self._newsletter_header_locator).text
@property
def newsletter_info_text(self):
self.wait.until(
EC.visibility_of_element_located(self._newsletter_info_text_locator)
)
return self.find_element(*self._newsletter_info_text_locator).text
def newsletter_email_input_field(self, email):
self.wait.until(
EC.element_to_be_clickable(self._newsletter_email_input_field_locator)
)
self.find_element(*self._newsletter_email_input_field_locator).send_keys(email)
@property
def newsletter_sign_up(self):
self.wait.until(
EC.visibility_of_element_located(self._newsletter_sign_up_button_locator)
)
return self.find_element(*self._newsletter_sign_up_button_locator)
def click_privacy_checkbox(self):
self.wait.until(
EC.element_to_be_clickable(self._newsletter_privacy_checkbox_locator)
)
self.find_element(*self._newsletter_privacy_checkbox_locator).click()
def click_newsletter_privacy_notice_link(self):
self.find_element(*self._newsletter_privacy_notice_link_locator).click()
self.wait.until(
EC.number_of_windows_to_be(2),
message=f'Number of windows was {len(self.driver.window_handles)}, expected 2',
message=f"Number of windows was {len(self.driver.window_handles)}, expected 2",
)
new_tab = self.driver.window_handles[1]
self.driver.switch_to.window(new_tab)
@property
def newsletter_signup_confirmation_header(self):
self.wait.until(
EC.visibility_of_element_located(
self._newsletter_sign_up_confirmation_header_locator
)
)
return self.find_element(
*self._newsletter_sign_up_confirmation_header_locator
).text
@property
def newsletter_signup_confirmation_message(self):
self.wait.until(
EC.visibility_of_element_located(
self._newsletter_sign_up_confirmation_message_locator
)
)
return self.find_element(
*self._newsletter_sign_up_confirmation_message_locator
).text
@ -509,16 +574,16 @@ class ConnectFooter(Region):
retry = 0
while retry < 10:
# verify that a response is available and get the email subject
request = requests.get(f'https://restmail.net/mail/{email}', timeout=10)
request = requests.get(f"https://restmail.net/mail/{email}", timeout=10)
response = request.json()
if response:
confirmation = [key['subject'] for key in response]
confirmation = [key["subject"] for key in response]
return confirmation
elif not response:
print('Confirmation email not received yet')
print("Confirmation email not received yet")
# fail if we retired 10 times and there was no email received
if retry == 9:
pytest.fail('Newsletter confirmation email was not sent')
pytest.fail("Newsletter confirmation email was not sent")
# pause between subsequent requests to give more time to the email to be sent
time.sleep(2)
retry += 1
@ -528,95 +593,112 @@ class ConnectFooter(Region):
class ResourcesFooter(Region):
_documentation_section_header_locator = (
By.CSS_SELECTOR,
'.DevHub-Footer-sections:nth-of-type(1) .DevHub-Footer-section:nth-of-type(1) h4',
".DevHub-Footer-sections:nth-of-type(1) .DevHub-Footer-section:nth-of-type(1) h4",
)
_documentation_section_locator = (
By.CSS_SELECTOR,
'.DevHub-Footer-sections:nth-of-type(1) .DevHub-Footer-section:nth-of-type(1)',
".DevHub-Footer-sections:nth-of-type(1) .DevHub-Footer-section:nth-of-type(1)",
)
_tools_section_header_locator = (
By.CSS_SELECTOR,
'.DevHub-Footer-sections:nth-of-type(1) .DevHub-Footer-section:nth-of-type(2) h4',
".DevHub-Footer-sections:nth-of-type(1) .DevHub-Footer-section:nth-of-type(2) h4",
)
_tools_section_locator = (
By.CSS_SELECTOR,
'.DevHub-Footer-sections:nth-of-type(1) .DevHub-Footer-section:nth-of-type(2)',
".DevHub-Footer-sections:nth-of-type(1) .DevHub-Footer-section:nth-of-type(2)",
)
_promote_section_header_locator = (
By.CSS_SELECTOR,
'.DevHub-Footer-sections:nth-of-type(1) .DevHub-Footer-section:nth-of-type(3) h4',
".DevHub-Footer-sections:nth-of-type(1) .DevHub-Footer-section:nth-of-type(3) h4",
)
_promote_section_locator = (
By.CSS_SELECTOR,
'.DevHub-Footer-sections:nth-of-type(1) .DevHub-Footer-section:nth-of-type(3)',
".DevHub-Footer-sections:nth-of-type(1) .DevHub-Footer-section:nth-of-type(3)",
)
_resources_footer_section_links = (By.CSS_SELECTOR, '.DevHub-Footer-sections li a')
_resources_footer_section_links = (By.CSS_SELECTOR, ".DevHub-Footer-sections li a")
_addon_review_section_header_locator = (
By.CSS_SELECTOR,
'.DevHub-Footer-sections:nth-of-type(2) .DevHub-Footer-section:nth-of-type(1) h4',
".DevHub-Footer-sections:nth-of-type(2) .DevHub-Footer-section:nth-of-type(1) h4",
)
_addon_review_info_text_locator = (
By.CSS_SELECTOR,
'.DevHub-Footer-sections:nth-of-type(2) .DevHub-Footer-section:nth-of-type(1) p',
".DevHub-Footer-sections:nth-of-type(2) .DevHub-Footer-section:nth-of-type(1) p",
)
_addon_review_link_locator = (
By.CSS_SELECTOR,
'.DevHub-Footer-sections:nth-of-type(2) .DevHub-Footer-section:nth-of-type(1) a',
".DevHub-Footer-sections:nth-of-type(2) .DevHub-Footer-section:nth-of-type(1) a",
)
_write_code_section_header_locator = (
By.CSS_SELECTOR,
'.DevHub-Footer-sections:nth-of-type(2) .DevHub-Footer-section:nth-of-type(2) h4',
".DevHub-Footer-sections:nth-of-type(2) .DevHub-Footer-section:nth-of-type(2) h4",
)
_write_code_info_text_locator = (
By.CSS_SELECTOR,
'.DevHub-Footer-sections:nth-of-type(2) .DevHub-Footer-section:nth-of-type(2) p',
".DevHub-Footer-sections:nth-of-type(2) .DevHub-Footer-section:nth-of-type(2) p",
)
_write_code_link_locator = (
By.CSS_SELECTOR,
'.DevHub-Footer-sections:nth-of-type(2) .DevHub-Footer-section:nth-of-type(2) a',
".DevHub-Footer-sections:nth-of-type(2) .DevHub-Footer-section:nth-of-type(2) a",
)
_participate_section_header_locator = (
By.CSS_SELECTOR,
'.DevHub-Footer-sections:nth-of-type(2) .DevHub-Footer-section:nth-of-type(3) h4',
".DevHub-Footer-sections:nth-of-type(2) .DevHub-Footer-section:nth-of-type(3) h4",
)
_participate_info_text_locator = (
By.CSS_SELECTOR,
'.DevHub-Footer-sections:nth-of-type(2) .DevHub-Footer-section:nth-of-type(3) p',
".DevHub-Footer-sections:nth-of-type(2) .DevHub-Footer-section:nth-of-type(3) p",
)
_participate_link_locator = (
By.CSS_SELECTOR,
'.DevHub-Footer-sections:nth-of-type(2) .DevHub-Footer-section:nth-of-type(3) a',
".DevHub-Footer-sections:nth-of-type(2) .DevHub-Footer-section:nth-of-type(3) a",
)
@property
def documentation_section_header(self):
self.wait.until(
EC.visibility_of_element_located(self._documentation_section_header_locator)
)
return self.find_element(*self._documentation_section_header_locator).text
@property
def documentation_section_links(self):
self.wait.until(
EC.visibility_of_element_located(self._documentation_section_locator)
)
el = self.find_element(*self._documentation_section_locator)
return el.find_elements(*self._resources_footer_section_links)
@property
def tools_section_header(self):
self.wait.until(
EC.visibility_of_element_located(self._tools_section_header_locator)
)
return self.find_element(*self._tools_section_header_locator).text
@property
def tools_section_links(self):
self.wait.until(EC.visibility_of_element_located(self._tools_section_locator))
el = self.find_element(*self._tools_section_locator)
return el.find_elements(*self._resources_footer_section_links)
@property
def promote_section_header(self):
self.wait.until(
EC.visibility_of_element_located(self._promote_section_header_locator)
)
return self.find_element(*self._promote_section_header_locator).text
@property
def promote_section_links(self):
self.wait.until(EC.visibility_of_element_located(self._promote_section_locator))
el = self.find_element(*self._promote_section_locator)
return el.find_elements(*self._resources_footer_section_links)
@property
def review_addons_section_header(self):
self.wait.until(
EC.visibility_of_element_located(self._addon_review_section_header_locator)
)
return self.find_element(*self._addon_review_section_header_locator).text
@property
@ -661,4 +743,5 @@ class ResourcesFooter(Region):
return self.find_element(*self._participate_info_text_locator).text
def click_participate_section_link(self):
self.wait.until(EC.element_to_be_clickable(self._participate_link_locator))
self.find_element(*self._participate_link_locator).click()

Просмотреть файл

@ -8,19 +8,19 @@ from pages.desktop.developers.submit_addon import SubmitAddon
class EditAddon(Base):
"""Edit page for a specific addon."""
_root_locator = (By.CLASS_NAME, 'section')
_edit_addon_navbar_locator = (By.CLASS_NAME, 'edit-addon-nav')
_addon_name_locator = (By.CSS_SELECTOR, '.section header h2')
_listed_addon_status_locator = (By.CSS_SELECTOR, '.addon-listed-status a')
_last_modified_date_locator = (By.CLASS_NAME, 'date-updated')
_root_locator = (By.CLASS_NAME, "section")
_edit_addon_navbar_locator = (By.CLASS_NAME, "edit-addon-nav")
_addon_name_locator = (By.CSS_SELECTOR, ".section header h2")
_listed_addon_status_locator = (By.CSS_SELECTOR, ".addon-listed-status a")
_last_modified_date_locator = (By.CLASS_NAME, "date-updated")
_unlisted_version_tooltip_locator = (
By.CLASS_NAME,
'distribution-tag-unlisted.tooltip',
"distribution-tag-unlisted.tooltip",
)
_submit_new_version_link_locator = (By.CLASS_NAME, 'version-upload')
_submit_new_version_link_locator = (By.CLASS_NAME, "version-upload")
_manage_versions_link_locator = (
By.CSS_SELECTOR,
'#edit-addon-nav ul:nth-child(1) li:nth-child(3)',
"#edit-addon-nav ul:nth-child(1) li:nth-child(3)",
)
def wait_for_page_to_load(self):
@ -29,6 +29,7 @@ class EditAddon(Base):
@property
def name(self):
self.wait_for_element_to_be_displayed(self._addon_name_locator)
return self.find_element(*self._addon_name_locator).text
@property
@ -43,6 +44,7 @@ class EditAddon(Base):
@property
def unlisted_version_tooltip(self):
self.wait_for_element_to_be_displayed(self._unlisted_version_tooltip_locator)
return self.find_element(*self._unlisted_version_tooltip_locator)
@property
@ -50,8 +52,8 @@ class EditAddon(Base):
"""Get the date string from the Last Update date section and format it"""
site_date = (
self.find_element(*self._last_modified_date_locator)
.text.split('Last Updated: ')[1]
.replace('.', '')
.text.split("Last Updated: ")[1]
.replace(".", "")
)
month = site_date.split()[0]
# get only the first three letters in the month to have a uniform date structure
@ -59,6 +61,7 @@ class EditAddon(Base):
return final_date
def click_manage_versions_link(self):
self.wait_for_element_to_be_clickable(self._manage_versions_link_locator)
self.find_element(*self._manage_versions_link_locator).click()
from pages.desktop.developers.manage_versions import ManageVersions

Просмотреть файл

@ -10,8 +10,8 @@ class EditTheme(Base):
been approved.
"""
_root_locator = (By.CLASS_NAME, 'section')
_edit_addon_navbar_locator = (By.CLASS_NAME, 'edit-addon-nav')
_root_locator = (By.CLASS_NAME, "section")
_edit_addon_navbar_locator = (By.CLASS_NAME, "edit-addon-nav")
def wait_for_page_to_load(self):
self.wait.until(

Просмотреть файл

@ -11,49 +11,49 @@ class ManageVersions(Page):
_addon_name_title_locator = (By.CSS_SELECTOR, 'div[class="section"] header h2')
_listing_visibility_section_locator = (
By.CSS_SELECTOR,
'#edit-addon h3:nth-child(1)',
"#edit-addon h3:nth-child(1)",
)
_visible_listing_radio_locator = (By.CSS_SELECTOR, 'input[value="listed"]')
_visible_explainer_text_locator = (
By.CSS_SELECTOR,
'#addon-current-state label:nth-child(1)',
"#addon-current-state label:nth-child(1)",
)
_invisible_listing_radio_locator = (By.CSS_SELECTOR, 'input[value="hidden"]')
_invisible_explainer_text_locator = (
By.CSS_SELECTOR,
'#addon-current-state label:nth-of-type(2)',
"#addon-current-state label:nth-of-type(2)",
)
_hide_addon_confirmation_text_locator = (
By.CSS_SELECTOR,
'#modal-disable p:nth-child(1)',
"#modal-disable p:nth-child(1)",
)
_hide_addon_button_locator = (By.CSS_SELECTOR, '#modal-disable p button')
_hide_addon_cancel_link_locator = (By.CSS_SELECTOR, '#modal-disable p a')
_hide_addon_button_locator = (By.CSS_SELECTOR, "#modal-disable p button")
_hide_addon_cancel_link_locator = (By.CSS_SELECTOR, "#modal-disable p a")
_incomplete_status_locator = (By.CSS_SELECTOR, '.status-incomplete b')
_addon_listed_status_locator = (By.CSS_SELECTOR, '.addon-listed-status b')
_delete_addon_button_locator = (By.CLASS_NAME, 'delete-button.delete-addon')
_incomplete_status_locator = (By.CSS_SELECTOR, ".status-incomplete b")
_addon_listed_status_locator = (By.CSS_SELECTOR, ".addon-listed-status b")
_delete_addon_button_locator = (By.CLASS_NAME, "delete-button.delete-addon")
# Version List Section
_version_list_locator = (By.ID, 'version-list')
_version_list_locator = (By.ID, "version-list")
_current_version_status_locator = (
By.CSS_SELECTOR,
'#current-version-status .file-status > div:nth-child(1)',
"#current-version-status .file-status > div:nth-child(1)",
)
_version_approval_status_locator = (
By.CSS_SELECTOR,
'#version-list .file-status div:nth-child(1)',
"#version-list .file-status div:nth-child(1)",
)
_disable_delete_version_button_locator = (By.CSS_SELECTOR, '.version-delete a')
_delete_version_help_text_locator = (By.CSS_SELECTOR, '.current-version-warning')
_delete_version_warning_locator = (By.CSS_SELECTOR, '.highlight.warning')
_delete_version_button_locator = (By.CSS_SELECTOR, '.modal-actions .delete-button')
_disable_delete_version_button_locator = (By.CSS_SELECTOR, ".version-delete a")
_delete_version_help_text_locator = (By.CSS_SELECTOR, ".current-version-warning")
_delete_version_warning_locator = (By.CSS_SELECTOR, ".highlight.warning")
_delete_version_button_locator = (By.CSS_SELECTOR, ".modal-actions .delete-button")
_disable_version_button_locator = (
By.CSS_SELECTOR,
'.modal-actions .disable-button',
".modal-actions .disable-button",
)
_cancel_disable_version_link_locator = (By.CSS_SELECTOR, '.modal-actions .close')
_enable_version_button_locator = (By.CSS_SELECTOR, '.file-status button')
_cancel_disable_version_link_locator = (By.CSS_SELECTOR, ".modal-actions .close")
_enable_version_button_locator = (By.CSS_SELECTOR, ".file-status button")
def wait_for_page_to_load(self):
self.wait.until(EC.visibility_of_element_located(self._version_list_locator))
@ -61,10 +61,16 @@ class ManageVersions(Page):
@property
def version_page_title(self):
self.wait.until(
EC.visibility_of_element_located(self._addon_name_title_locator)
)
return self.find_element(*self._addon_name_title_locator).text
@property
def listing_visibility_section(self):
self.wait.until(
EC.visibility_of_element_located(self._listing_visibility_section_locator)
)
return self.find_element(*self._listing_visibility_section_locator).text
def set_addon_visible(self):
@ -75,10 +81,16 @@ class ManageVersions(Page):
@property
def visible_status_explainer(self):
self.wait.until(
EC.visibility_of_element_located(self._visible_explainer_text_locator)
)
return self.find_element(*self._visible_explainer_text_locator).text
@property
def invisible_status_explainer(self):
self.wait.until(
EC.visibility_of_element_located(self._invisible_explainer_text_locator)
)
return self.find_element(*self._invisible_explainer_text_locator).text
def set_addon_invisible(self):
@ -87,7 +99,7 @@ class ManageVersions(Page):
el.click()
self.wait.until(
EC.element_to_be_clickable(
(By.CSS_SELECTOR, '#modal-disable p:nth-child(1)')
(By.CSS_SELECTOR, "#modal-disable p:nth-child(1)")
)
)
@ -96,6 +108,9 @@ class ManageVersions(Page):
@property
def hide_addon_confirmation_text(self):
self.wait.until(
EC.visibility_of_element_located(self._hide_addon_confirmation_text_locator)
)
return self.find_element(*self._hide_addon_confirmation_text_locator).text
def cancel_hide_addon_process(self):
@ -105,48 +120,74 @@ class ManageVersions(Page):
@property
def addon_listed_status(self):
self.wait.until(
EC.visibility_of_element_located(self._addon_listed_status_locator)
)
return self.find_element(*self._addon_listed_status_locator).text
@property
def incomplete_status(self):
self.wait.until(
EC.visibility_of_element_located(self._incomplete_status_locator)
)
return self.find_element(*self._incomplete_status_locator)
@property
def current_version_status(self):
self.wait.until(
EC.visibility_of_element_located(self._current_version_status_locator)
)
return self.find_element(*self._current_version_status_locator).text
@property
def version_approval_status(self):
self.wait.until(
EC.visibility_of_element_located(self._version_approval_status_locator)
)
return self.find_elements(*self._version_approval_status_locator)
def click_delete_disable_version(self):
self.find_element(*self._disable_delete_version_button_locator).click()
self.wait.until(
EC.element_to_be_clickable(
(By.CSS_SELECTOR, '.modal-actions .delete-button')
(By.CSS_SELECTOR, ".modal-actions .delete-button")
),
message='The Delete/Disable modal was not opened',
message="The Delete/Disable modal was not opened",
)
@property
def delete_disable_version_helptext(self):
self.wait.until(
EC.visibility_of_element_located(self._delete_version_help_text_locator)
)
return self.find_element(*self._delete_version_help_text_locator).text
@property
def delete_disable_version_warning(self):
self.wait.until(
EC.visibility_of_element_located(self._delete_version_warning_locator)
)
return self.find_element(*self._delete_version_warning_locator).text
def click_delete_version_button(self):
self.wait.until(EC.element_to_be_clickable(self._delete_version_button_locator))
self.find_element(*self._delete_version_button_locator).click()
def click_disable_version_button(self):
self.wait.until(
EC.element_to_be_clickable(self._disable_version_button_locator)
)
self.find_element(*self._disable_version_button_locator).click()
def click_cancel_version_delete_link(self):
self.wait.until(
EC.element_to_be_clickable(self._cancel_disable_version_link_locator)
)
return self.find_element(*self._cancel_disable_version_link_locator).click()
def click_enable_version(self):
"""Allows developers to re-enable a version that was previously disabled"""
self.wait.until(EC.element_to_be_clickable(self._enable_version_button_locator))
self.find_element(*self._enable_version_button_locator).click()
def wait_for_version_autoapproval(self, value):
@ -166,7 +207,7 @@ class ManageVersions(Page):
# if auto-approval took longer than normal, we want to fail the test and capture the final status
else:
pytest.fail(
f'Autoapproval took longer than normal; '
f"Autoapproval took longer than normal; "
f'Addon final status was "{self.version_approval_status[0].text}" instead of "{value}"'
)
@ -175,16 +216,16 @@ class ManageVersions(Page):
return self.DeleteAddonModal(self).wait_for_region_to_load()
class DeleteAddonModal(Region):
_root_locator = (By.ID, 'modal-delete')
_root_locator = (By.ID, "modal-delete")
_delete_confirmation_string_locator = (
By.CSS_SELECTOR,
'p:nth-of-type(2) > label',
"p:nth-of-type(2) > label",
)
_delete_confirmation_text_input_locator = (
By.CSS_SELECTOR,
'input[name="slug"]',
)
_delete_button_locator = (By.CSS_SELECTOR, '.delete-button')
_delete_button_locator = (By.CSS_SELECTOR, ".delete-button")
def wait_for_region_to_load(self):
self.wait.until(EC.visibility_of_element_located(self._root_locator))

Просмотреть файл

@ -12,40 +12,40 @@ from pages.desktop.developers.manage_versions import ManageVersions
class SubmitAddon(Page):
"""A class holding all the components used for addon submissions in DevHub"""
_my_addons_page_logo_locator = (By.CSS_SELECTOR, '.site-titles')
_submission_form_header_locator = (By.CSS_SELECTOR, '.is_addon')
_my_addons_page_logo_locator = (By.CSS_SELECTOR, ".site-titles")
_submission_form_header_locator = (By.CSS_SELECTOR, ".is_addon")
_addon_distribution_header_locator = (
By.CSS_SELECTOR,
'.addon-submission-process h3',
".addon-submission-process h3",
)
_developer_notification_box_locator = (By.CSS_SELECTOR, '.notification-box')
_developer_notification_box_locator = (By.CSS_SELECTOR, ".notification-box")
_distribution_page_explainer_locator = (
By.CSS_SELECTOR,
'.addon-submission-process p:nth-of-type(1)',
".addon-submission-process p:nth-of-type(1)",
)
_distribution_agreement_checkbox_locator = (By.ID, 'id_distribution_agreement')
_distribution_agreement_checkbox_locator = (By.ID, "id_distribution_agreement")
_distribution_agreement_link_locator = (
By.CSS_SELECTOR,
'.addon-submission-process li:nth-of-type(1) a',
".addon-submission-process li:nth-of-type(1) a",
)
_review_policies_checkbox_locator = (By.ID, 'id_review_policy')
_review_policies_checkbox_locator = (By.ID, "id_review_policy")
_review_policies_link_locator = (
By.CSS_SELECTOR,
'.addon-submission-process li:nth-of-type(2) a',
".addon-submission-process li:nth-of-type(2) a",
)
_user_consent_text_locator = (
By.CSS_SELECTOR,
'.addon-submission-process p:nth-of-type(2)',
".addon-submission-process p:nth-of-type(2)",
)
_recaptcha_locator = (By.ID, 'id_recaptcha')
_recaptcha_checkbox_locator = (By.ID, 'recaptcha-anchor')
_recaptcha_locator = (By.ID, "id_recaptcha")
_recaptcha_checkbox_locator = (By.ID, "recaptcha-anchor")
_recaptcha_checkbox_is_selected_locator = (
By.CSS_SELECTOR,
'span[aria-checked="true"]',
)
_accept_agreement_button = (By.ID, 'accept-agreement')
_cancel_agreement_button = (By.CSS_SELECTOR, '.submit-buttons a')
_dev_accounts_info_link_locator = (By.CSS_SELECTOR, '.addon-submission-process p a')
_accept_agreement_button = (By.ID, "accept-agreement")
_cancel_agreement_button = (By.CSS_SELECTOR, ".submit-buttons a")
_dev_accounts_info_link_locator = (By.CSS_SELECTOR, ".addon-submission-process p a")
_listed_option_locator = (By.CSS_SELECTOR, 'input[value="listed"]')
_listed_option_helptext_locator = (
@ -59,77 +59,103 @@ class SubmitAddon(Page):
)
_distribution_and_signing_helptext_locator = (
By.CSS_SELECTOR,
'.addon-submit-distribute p:nth-of-type(3)',
".addon-submit-distribute p:nth-of-type(3)",
)
_addon_policies_helptext_locator = (
By.CSS_SELECTOR,
'.addon-submit-distribute p:nth-of-type(4)',
".addon-submit-distribute p:nth-of-type(4)",
)
_change_distribution_link_locator = (By.CSS_SELECTOR, '.addon-submit-distribute a')
_continue_button_locator = (By.CSS_SELECTOR, '.addon-submission-field button')
_file_upload_process_helptext_locator = (By.CSS_SELECTOR, '.new-addon-file p')
_upload_file_button_locator = (By.CSS_SELECTOR, '.invisible-upload input')
_accepted_file_types_locator = (By.CLASS_NAME, 'upload-details')
_compatibility_helptext_locator = (By.CSS_SELECTOR, '.compatible-apps label')
_compatibility_error_message_locator = (By.CSS_SELECTOR, '.errorlist li')
_firefox_compat_checkbox_locator = (By.CSS_SELECTOR, '.app.firefox input')
_android_compat_checkbox_locator = (By.CSS_SELECTOR, '.app.android input')
_change_distribution_link_locator = (By.CSS_SELECTOR, ".addon-submit-distribute a")
_continue_button_locator = (By.CSS_SELECTOR, ".addon-submission-field button")
_file_upload_process_helptext_locator = (By.CSS_SELECTOR, ".new-addon-file p")
_upload_file_button_locator = (By.CSS_SELECTOR, ".invisible-upload input")
_accepted_file_types_locator = (By.CLASS_NAME, "upload-details")
_compatibility_helptext_locator = (By.CSS_SELECTOR, ".compatible-apps label")
_compatibility_error_message_locator = (By.CSS_SELECTOR, ".errorlist li")
_firefox_compat_checkbox_locator = (By.CSS_SELECTOR, ".app.firefox input")
_android_compat_checkbox_locator = (By.CSS_SELECTOR, ".app.android input")
_create_theme_subheader_locator = (
By.CSS_SELECTOR,
'.addon-create-theme-section h3',
".addon-create-theme-section h3",
)
_create_theme_button_locator = (By.ID, 'wizardlink')
_submit_file_button_locator = (By.ID, 'submit-upload-file-finish')
_addon_validation_success_locator = (By.CLASS_NAME, 'bar-success')
_validation_fail_bar_locator = (By.CLASS_NAME, 'bar-fail')
_validation_support_link_locator = (By.CSS_SELECTOR, '#upload-status-results a')
_create_theme_button_locator = (By.ID, "wizardlink")
_submit_file_button_locator = (By.ID, "submit-upload-file-finish")
_addon_validation_success_locator = (By.CLASS_NAME, "bar-success")
_validation_fail_bar_locator = (By.CLASS_NAME, "bar-fail")
_validation_support_link_locator = (By.CSS_SELECTOR, "#upload-status-results a")
_validation_failed_message_locator = (
By.CSS_SELECTOR,
'#upload-status-results strong',
"#upload-status-results strong",
)
_validation_warning_message_locator = (By.CSS_SELECTOR, '.submission-warning p')
_validation_summary_link_locator = (By.CSS_SELECTOR, '.submission-warning a')
_validation_fail_reason_locator = (By.CSS_SELECTOR, '#upload_errors li')
_validation_status_text_locator = (By.ID, 'upload-status-text')
_validation_success_message_locator = (By.ID, 'upload-status-results')
_validation_warning_message_locator = (By.CSS_SELECTOR, ".submission-warning p")
_validation_summary_link_locator = (By.CSS_SELECTOR, ".submission-warning a")
_validation_fail_reason_locator = (By.CSS_SELECTOR, "#upload_errors li")
_validation_status_text_locator = (By.ID, "upload-status-text")
_validation_success_message_locator = (By.ID, "upload-status-results")
@property
def my_addons_page_logo(self):
self.wait.until(
EC.visibility_of_element_located(self._my_addons_page_logo_locator)
)
return self.find_element(*self._my_addons_page_logo_locator)
@property
def submission_form_header(self):
self.wait.until(
EC.visibility_of_element_located(self._submission_form_header_locator)
)
return self.find_element(*self._submission_form_header_locator)
@property
def submission_form_subheader(self):
self.wait.until(
EC.visibility_of_element_located(self._addon_distribution_header_locator)
)
return self.find_element(*self._addon_distribution_header_locator)
@property
def developer_notification_box(self):
self.wait.until(
EC.visibility_of_element_located(self._developer_notification_box_locator)
)
return self.find_element(*self._developer_notification_box_locator)
@property
def distribution_page_explainer(self):
self.wait.until(
EC.visibility_of_element_located(self._distribution_page_explainer_locator)
)
return self.find_element(*self._distribution_page_explainer_locator).text
@property
def distribution_agreement_checkbox(self):
self.wait.until(
EC.visibility_of_element_located(self._distribution_agreement_checkbox_locator)
EC.visibility_of_element_located(
self._distribution_agreement_checkbox_locator
)
)
return self.find_element(*self._distribution_agreement_checkbox_locator)
@property
def distribution_agreement_article_link(self):
self.wait.until(
EC.visibility_of_element_located(self._distribution_agreement_link_locator)
)
return self.find_element(*self._distribution_agreement_link_locator)
@property
def review_policies_checkbox(self):
self.wait.until(
EC.visibility_of_element_located(self._review_policies_checkbox_locator)
)
return self.find_element(*self._review_policies_checkbox_locator)
@property
def review_policies_article_link(self):
self.wait.until(
EC.visibility_of_element_located(self._review_policies_link_locator)
)
return self.find_element(*self._review_policies_link_locator)
def click_extension_workshop_article_link(self, link, text):
@ -138,12 +164,12 @@ class SubmitAddon(Page):
link.click()
self.wait.until(
EC.number_of_windows_to_be(2),
message=f'Number of windows was {len(self.driver.window_handles)}, expected 2',
message=f"Number of windows was {len(self.driver.window_handles)}, expected 2",
)
new_tab = self.driver.window_handles[1]
self.driver.switch_to.window(new_tab)
self.wait.until(
EC.text_to_be_present_in_element((By.CSS_SELECTOR, '.page-hero h1'), text)
EC.text_to_be_present_in_element((By.CSS_SELECTOR, ".page-hero h1"), text)
)
self.driver.close()
# return to the main tab
@ -151,10 +177,14 @@ class SubmitAddon(Page):
@property
def user_consent_text(self):
self.wait.until(
EC.visibility_of_element_located(self._user_consent_text_locator)
)
return self.find_element(*self._user_consent_text_locator).text
@property
def recaptcha(self):
self.wait.until(EC.visibility_of_element_located(self._recaptcha_locator))
return self.find_element(*self._recaptcha_locator)
def click_recaptcha_checkbox(self):
@ -165,10 +195,12 @@ class SubmitAddon(Page):
@property
def accept_agreement(self):
self.wait.until(EC.visibility_of_element_located(self._accept_agreement_button))
return self.find_element(*self._accept_agreement_button)
@property
def cancel_agreement(self):
self.wait.until(EC.visibility_of_element_located(self._cancel_agreement_button))
return self.find_element(*self._cancel_agreement_button)
def click_dev_accounts_info_link(self):
@ -177,56 +209,64 @@ class SubmitAddon(Page):
self.find_element(*self._dev_accounts_info_link_locator).click()
self.wait.until(
EC.text_to_be_present_in_element(
(By.CSS_SELECTOR, '.page-hero h1'), 'Developer accounts'
(By.CSS_SELECTOR, ".page-hero h1"), "Developer accounts"
)
)
@property
def listed_option_helptext(self):
self.wait.until(
EC.visibility_of_element_located(self._listed_option_helptext_locator)
)
return self.find_element(*self._listed_option_helptext_locator).text
@property
def unlisted_option_helptext(self):
self.wait.until(
EC.visibility_of_element_located(self._unlisted_option_helptext_locator)
)
return self.find_element(*self._unlisted_option_helptext_locator)
@property
def update_url_link(self):
return self.unlisted_option_helptext.find_element(By.CSS_SELECTOR, 'a')
return self.unlisted_option_helptext.find_element(By.CSS_SELECTOR, "a")
@property
def listed_option_radiobutton(self):
self.wait.until(
EC.visibility_of_element_located(self._listed_option_locator)
)
self.wait.until(EC.visibility_of_element_located(self._listed_option_locator))
return self.find_element(*self._listed_option_locator)
def select_listed_option(self):
self.wait.until(
EC.element_to_be_clickable(self._listed_option_locator)
)
self.wait.until(EC.element_to_be_clickable(self._listed_option_locator))
self.find_element(*self._listed_option_locator).click()
def select_unlisted_option(self):
self.wait.until(
EC.element_to_be_clickable(self._unlisted_option_locator)
)
self.wait.until(EC.element_to_be_clickable(self._unlisted_option_locator))
self.find_element(*self._unlisted_option_locator).click()
@property
def distribution_and_signing_helptext(self):
self.wait.until(
EC.visibility_of_element_located(
self._distribution_and_signing_helptext_locator
)
)
return self.find_element(*self._distribution_and_signing_helptext_locator)
@property
def distribution_and_signing_link(self):
return self.distribution_and_signing_helptext.find_element(By.CSS_SELECTOR, 'a')
return self.distribution_and_signing_helptext.find_element(By.CSS_SELECTOR, "a")
@property
def addon_policies_helptext(self):
self.wait.until(
EC.visibility_of_element_located(self._addon_policies_helptext_locator)
)
return self.find_element(*self._addon_policies_helptext_locator)
@property
def addon_policies_link(self):
return self.addon_policies_helptext.find_element(By.CSS_SELECTOR, 'a')
return self.addon_policies_helptext.find_element(By.CSS_SELECTOR, "a")
def change_version_distribution(self):
"""Changes the distribution (listed/unlisted) when submitting a new version"""
@ -238,24 +278,36 @@ class SubmitAddon(Page):
@property
def file_upload_helptext(self):
self.wait.until(
EC.visibility_of_element_located(self._file_upload_process_helptext_locator)
)
return self.find_elements(*self._file_upload_process_helptext_locator)
def upload_addon(self, addon):
"""Selects an addon from the 'sample-addons' folder and uploads it"""
button = self.find_element(*self._upload_file_button_locator)
archive = Path(f'{os.getcwd()}/sample-addons/{addon}')
archive = Path(f"{os.getcwd()}/sample-addons/{addon}")
button.send_keys(str(archive))
@property
def accepted_file_types(self):
self.wait.until(
EC.visibility_of_element_located(self._accepted_file_types_locator)
)
return self.find_element(*self._accepted_file_types_locator).text
@property
def compatibility_helptext(self):
self.wait.until(
EC.visibility_of_element_located(self._compatibility_helptext_locator)
)
return self.find_elements(*self._compatibility_helptext_locator)[0].text
@property
def compatibility_error_message(self):
self.wait.until(
EC.visibility_of_element_located(self._compatibility_error_message_locator)
)
return self.find_element(*self._compatibility_error_message_locator).text
@property
@ -264,10 +316,16 @@ class SubmitAddon(Page):
@property
def android_compat_checkbox(self):
self.wait.until(
EC.visibility_of_element_located(self._android_compat_checkbox_locator)
)
return self.find_element(*self._android_compat_checkbox_locator)
@property
def create_theme_subheader(self):
self.wait.until(
EC.visibility_of_element_located(self._create_theme_subheader_locator)
)
return self.find_element(*self._create_theme_subheader_locator).text
def click_create_theme_button(self):
@ -282,50 +340,68 @@ class SubmitAddon(Page):
@property
def failed_validation_bar(self):
self.wait.until(
EC.visibility_of_element_located(self._validation_fail_bar_locator)
)
return self.find_element(*self._validation_fail_bar_locator)
@property
def validation_status_title(self):
self.wait.until(
EC.visibility_of_element_located(self._validation_status_text_locator)
)
return self.find_element(*self._validation_status_text_locator).text
def click_validation_support_link(self):
self.find_element(*self._validation_support_link_locator).click()
self.wait.until(
EC.number_of_windows_to_be(2),
message=f'Number of windows was {len(self.driver.window_handles)}, expected 2',
message=f"Number of windows was {len(self.driver.window_handles)}, expected 2",
)
new_tab = self.driver.window_handles[1]
self.driver.switch_to.window(new_tab)
self.wait.until(EC.url_contains('/mozilla/addons-linter/'))
self.wait.until(EC.url_contains("/mozilla/addons-linter/"))
self.driver.close()
# return to the main tab
self.driver.switch_to.window(self.driver.window_handles[0])
@property
def validation_failed_message(self):
self.wait.until(
EC.visibility_of_element_located(self._validation_failed_message_locator)
)
return self.find_element(*self._validation_failed_message_locator).text
@property
def validation_failed_reason(self):
self.wait.until(
EC.visibility_of_element_located(self._validation_fail_reason_locator)
)
return self.find_elements(*self._validation_fail_reason_locator)
@property
def validation_warning_message(self):
self.wait.until(
EC.visibility_of_element_located(self._validation_warning_message_locator)
)
return self.find_element(*self._validation_warning_message_locator).text
def click_validation_summary(self):
self.find_element(*self._validation_summary_link_locator).click()
self.wait.until(
EC.number_of_windows_to_be(2),
message=f'Number of windows was {len(self.driver.window_handles)}, expected 2',
message=f"Number of windows was {len(self.driver.window_handles)}, expected 2",
)
new_tab = self.driver.window_handles[1]
self.driver.switch_to.window(new_tab)
self.wait.until(EC.visibility_of_element_located((By.CLASS_NAME, 'results')))
self.wait.until(EC.visibility_of_element_located((By.CLASS_NAME, "results")))
return ValidationResults(self.driver, self.base_url)
@property
def success_validation_message(self):
self.wait.until(
EC.visibility_of_element_located(self._validation_success_message_locator)
)
return self.find_element(*self._validation_success_message_locator)
def click_continue_upload_button(self):
@ -333,7 +409,7 @@ class SubmitAddon(Page):
return UploadSource(self.driver, self.base_url)
def submit_button_disabled(self):
self.find_element(*self._submit_file_button_locator).get_attribute('disabled')
self.find_element(*self._submit_file_button_locator).get_attribute("disabled")
class ValidationResults(Page):
@ -341,74 +417,109 @@ class ValidationResults(Page):
By.CSS_SELECTOR,
"div[class='section'] header h2",
)
_validation_summary_shelf_locator = (By.CLASS_NAME, 'tiers')
_validation_general_results_locator = (By.ID, 'suite-results-tier-1')
_validation_security_results_locator = (By.ID, 'suite-results-tier-2')
_validation_extension_results_locator = (By.ID, 'suite-results-tier-3')
_validation_localization_results_locator = (By.ID, 'suite-results-tier-4')
_validation_compatibility_results_locator = (By.ID, 'suite-results-tier-5')
_validation_summary_shelf_locator = (By.CLASS_NAME, "tiers")
_validation_general_results_locator = (By.ID, "suite-results-tier-1")
_validation_security_results_locator = (By.ID, "suite-results-tier-2")
_validation_extension_results_locator = (By.ID, "suite-results-tier-3")
_validation_localization_results_locator = (By.ID, "suite-results-tier-4")
_validation_compatibility_results_locator = (By.ID, "suite-results-tier-5")
@property
def validation_results_header(self):
self.wait.until(
EC.visibility_of_element_located(self._validation_results_header_locator)
)
return self.find_element(*self._validation_results_header_locator).text
@property
def validation_summary_shelf(self):
self.wait.until(
EC.visibility_of_element_located(self._validation_results_header_locator)
)
return self.find_element(*self._validation_summary_shelf_locator)
@property
def validation_general_results(self):
self.wait.until(
EC.visibility_of_element_located(self._validation_general_results_locator)
)
return self.find_element(*self._validation_general_results_locator)
@property
def validation_security_results(self):
self.wait.until(
EC.visibility_of_element_located(self._validation_security_results_locator)
)
return self.find_element(*self._validation_security_results_locator)
@property
def validation_extension_results(self):
self.wait.until(
EC.visibility_of_element_located(self._validation_extension_results_locator)
)
return self.find_element(*self._validation_extension_results_locator)
@property
def validation_localization_results(self):
self.wait.until(
EC.visibility_of_element_located(
self._validation_localization_results_locator
)
)
return self.find_element(*self._validation_localization_results_locator)
@property
def validation_compatibility_results(self):
self.wait.until(
EC.visibility_of_element_located(
self._validation_compatibility_results_locator
)
)
return self.find_element(*self._validation_compatibility_results_locator)
class UploadSource(Page):
_submit_source_code_page_header_locator = (
By.CSS_SELECTOR,
'.addon-submission-process h3',
".addon-submission-process h3",
)
_yes_submit_source_radio_button_locator = (By.ID, 'id_has_source_0')
_no_submit_source_radio_button_locator = (By.ID, 'id_has_source_1')
_choose_source_file_button_locator = (By.ID, 'id_source')
_yes_submit_source_radio_button_locator = (By.ID, "id_has_source_0")
_no_submit_source_radio_button_locator = (By.ID, "id_has_source_1")
_choose_source_file_button_locator = (By.ID, "id_source")
_continue_button_locator = (
By.CSS_SELECTOR,
'.submission-buttons button:nth-child(1)',
".submission-buttons button:nth-child(1)",
)
_upload_source_error_message_locator = (By.CSS_SELECTOR, '.errorlist li')
_upload_source_error_message_locator = (By.CSS_SELECTOR, ".errorlist li")
_cancel_and_disable_version_locator = (
By.CSS_SELECTOR,
'.confirm-submission-cancel',
".confirm-submission-cancel",
)
_cancel_and_disable_explainer_text_locator = (
By.CSS_SELECTOR,
'#modal-confirm-submission-cancel p',
"#modal-confirm-submission-cancel p",
)
_cancel_version_confirm_button_locator = (
By.CSS_SELECTOR,
'.modal-actions .delete-button',
".modal-actions .delete-button",
)
_do_not_cancel_version_link_locator = (By.CSS_SELECTOR, '.modal-actions a')
_do_not_cancel_version_link_locator = (By.CSS_SELECTOR, ".modal-actions a")
@property
def submit_source_page_header(self):
self.wait.until(
EC.visibility_of_element_located(
self._submit_source_code_page_header_locator
)
)
return self.find_element(*self._submit_source_code_page_header_locator).text
def select_yes_to_submit_source(self):
self.wait.until(
EC.visibility_of_element_located(
self._yes_submit_source_radio_button_locator
)
)
self.find_element(*self._yes_submit_source_radio_button_locator).click()
def select_no_to_omit_source(self):
@ -416,7 +527,7 @@ class UploadSource(Page):
def choose_source(self, file):
button = self.find_element(*self._choose_source_file_button_locator)
archive = Path(f'{os.getcwd()}/sample-addons/{file}')
archive = Path(f"{os.getcwd()}/sample-addons/{file}")
button.send_keys(str(archive))
def continue_unlisted_submission(self):
@ -433,6 +544,9 @@ class UploadSource(Page):
@property
def source_upload_fail_message(self):
self.wait.until(
EC.visibility_of_element_located(self._upload_source_error_message_locator)
)
return self.find_element(*self._upload_source_error_message_locator).text
def click_cancel_and_disable_version(self):
@ -440,6 +554,11 @@ class UploadSource(Page):
@property
def cancel_and_disable_explainer_text(self):
self.wait.until(
EC.visibility_of_element_located(
self._cancel_and_disable_explainer_text_locator
)
)
return self.find_element(*self._cancel_and_disable_explainer_text_locator).text
def click_do_not_cancel_version(self):
@ -451,61 +570,61 @@ class UploadSource(Page):
class ListedAddonSubmissionForm(Page):
_addon_name_field_locator = (By.CSS_SELECTOR, '#trans-name input:nth-child(1)')
_edit_addon_slug_link_locator = (By.ID, 'edit_slug')
_edit_addon_slug_field_locator = (By.ID, 'id_slug')
_addon_summary_field_locator = (By.ID, 'id_summary_0')
_addon_detail_fields_info_text_locator = (By.CSS_SELECTOR, '.edit-addon-details')
_addon_name_field_locator = (By.CSS_SELECTOR, "#trans-name input:nth-child(1)")
_edit_addon_slug_link_locator = (By.ID, "edit_slug")
_edit_addon_slug_field_locator = (By.ID, "id_slug")
_addon_summary_field_locator = (By.ID, "id_summary_0")
_addon_detail_fields_info_text_locator = (By.CSS_SELECTOR, ".edit-addon-details")
_summary_character_count_locator = (
By.CSS_SELECTOR,
".char-count[data-for-startswith='id_summary_'] > b",
)
_addon_description_field_locator = (By.ID, 'id_description_0')
_is_experimental_checkbox_locator = (By.ID, 'id_is_experimental')
_requires_payment_checkbox_locator = (By.ID, 'id_requires_payment')
_categories_section_locator = (By.ID, 'addon-categories-edit')
_addon_description_field_locator = (By.ID, "id_description_0")
_is_experimental_checkbox_locator = (By.ID, "id_is_experimental")
_requires_payment_checkbox_locator = (By.ID, "id_requires_payment")
_categories_section_locator = (By.ID, "addon-categories-edit")
_firefox_categories_locator = (
By.CSS_SELECTOR,
'.addon-app-cats:nth-of-type(1) > ul input',
".addon-app-cats:nth-of-type(1) > ul input",
)
_android_categories_locator = (
By.CSS_SELECTOR,
'.addon-app-cats:nth-of-type(2) > ul input',
".addon-app-cats:nth-of-type(2) > ul input",
)
_email_input_field_locator = (By.ID, 'id_support_email_0')
_support_site_input_field_locator = (By.ID, 'id_support_url_0')
_license_options_locator = (By.CLASS_NAME, 'license')
_license_details_link_locator = (By.CSS_SELECTOR, '.xx.extra')
_custom_license_name_locator = (By.ID, 'id_license-name')
_custom_license_text_locator = (By.ID, 'id_license-text')
_privacy_policy_checkbox_locator = (By.ID, 'id_has_priv')
_privacy_policy_textarea_locator = (By.ID, 'id_privacy_policy_0')
_reviewer_notes_textarea_locator = (By.ID, 'id_approval_notes')
_email_input_field_locator = (By.ID, "id_support_email_0")
_support_site_input_field_locator = (By.ID, "id_support_url_0")
_license_options_locator = (By.CLASS_NAME, "license")
_license_details_link_locator = (By.CSS_SELECTOR, ".xx.extra")
_custom_license_name_locator = (By.ID, "id_license-name")
_custom_license_text_locator = (By.ID, "id_license-text")
_privacy_policy_checkbox_locator = (By.ID, "id_has_priv")
_privacy_policy_textarea_locator = (By.ID, "id_privacy_policy_0")
_reviewer_notes_textarea_locator = (By.ID, "id_approval_notes")
_submit_addon_button_locator = (
By.CSS_SELECTOR,
'.submission-buttons button:nth-child(1)',
".submission-buttons button:nth-child(1)",
)
_cancel_addon_submission_button_locator = (
By.CSS_SELECTOR,
'.submission-buttons button:nth-child(2)',
".submission-buttons button:nth-child(2)",
)
# _theme_categories_locator = (By.CSS_SELECTOR, '#addon-categories-edit > ul input') - temporary not available
_theme_categories_locator = (By.CSS_SELECTOR, '#id_category input')
_theme_categories_locator = (By.CSS_SELECTOR, "#id_category input")
_theme_licence_sharing_rights_locator = (
By.CSS_SELECTOR,
'#cc-chooser ul:nth-of-type(1) input',
"#cc-chooser ul:nth-of-type(1) input",
)
_theme_license_commercial_use_locator = (
By.CSS_SELECTOR,
'#cc-chooser ul:nth-of-type(2) input',
"#cc-chooser ul:nth-of-type(2) input",
)
_theme_license_creation_rights_locator = (
By.CSS_SELECTOR,
'#cc-chooser ul:nth-of-type(3) input',
"#cc-chooser ul:nth-of-type(3) input",
)
_selected_theme_license_locator = (By.CSS_SELECTOR, '#cc-license')
_open_theme_licenses_list_locator = (By.CLASS_NAME, 'select-license')
_theme_licenses_list_locator = (By.CSS_SELECTOR, '#id_license-builtin input')
_selected_theme_license_locator = (By.CSS_SELECTOR, "#cc-license")
_open_theme_licenses_list_locator = (By.CLASS_NAME, "select-license")
_theme_licenses_list_locator = (By.CSS_SELECTOR, "#id_license-builtin input")
def wait_for_page_to_load(self):
self.wait.until(
@ -518,6 +637,9 @@ class ListedAddonSubmissionForm(Page):
@property
def addon_name_field(self):
self.wait.until(
EC.visibility_of_element_located(self._addon_name_field_locator)
)
return self.find_element(*self._addon_name_field_locator)
def edit_addon_slug(self, value):
@ -535,6 +657,9 @@ class ListedAddonSubmissionForm(Page):
@property
def summary_character_count(self):
self.wait.until(
EC.visibility_of_element_located(self._summary_character_count_locator)
)
return self.find_element(*self._summary_character_count_locator).text
def set_addon_description(self, value):
@ -542,14 +667,23 @@ class ListedAddonSubmissionForm(Page):
@property
def is_experimental(self):
self.wait.until(
EC.visibility_of_element_located(self._is_experimental_checkbox_locator)
)
return self.find_element(*self._is_experimental_checkbox_locator)
@property
def requires_payment(self):
self.wait.until(
EC.visibility_of_element_located(self._requires_payment_checkbox_locator)
)
return self.find_element(*self._requires_payment_checkbox_locator)
@property
def categories_section(self):
self.wait.until(
EC.visibility_of_element_located(self._categories_section_locator)
)
return self.find_element(*self._categories_section_locator)
def select_firefox_categories(self, count):
@ -569,24 +703,40 @@ class ListedAddonSubmissionForm(Page):
@property
def select_license_options(self):
self.wait.until(EC.visibility_of_element_located(self._license_options_locator))
return self.find_elements(*self._license_options_locator)
def license_option_names(self, count, value):
return self.select_license_options[count].get_attribute(value)
def license_details_link(self):
self.wait.until(
EC.visibility_of_element_located(self._license_details_link_locator)
)
self.find_element(*self._license_details_link_locator).click()
def set_custom_license_name(self, value):
self.wait.until(
EC.visibility_of_element_located(self._custom_license_name_locator)
)
self.find_element(*self._custom_license_name_locator).send_keys(value)
def set_custom_license_text(self, value):
self.wait.until(
EC.visibility_of_element_located(self._custom_license_text_locator)
)
self.find_element(*self._custom_license_text_locator).send_keys(value)
def select_theme_licence_sharing_rights(self, count):
self.wait.until(
EC.visibility_of_element_located(self._theme_licence_sharing_rights_locator)
)
self.find_elements(*self._theme_licence_sharing_rights_locator)[count].click()
def select_theme_license_commercial_use(self, count):
self.wait.until(
EC.visibility_of_element_located(self._theme_license_commercial_use_locator)
)
self.find_elements(*self._theme_license_commercial_use_locator)[count].click()
def select_theme_license_creation_rights(self, count):
@ -594,15 +744,27 @@ class ListedAddonSubmissionForm(Page):
@property
def generated_theme_license(self):
self.wait.until(
EC.visibility_of_element_located(self._selected_theme_license_locator)
)
return self.find_element(*self._selected_theme_license_locator)
def open_theme_licenses_list(self):
self.wait.until(
EC.visibility_of_element_located(self._open_theme_licenses_list_locator)
)
self.find_element(*self._open_theme_licenses_list_locator).click()
def select_theme_license_from_list(self):
self.wait.until(
EC.visibility_of_element_located(self._theme_licenses_list_locator)
)
self.find_elements(*self._theme_licenses_list_locator)
def set_privacy_policy(self, value):
self.wait.until(
EC.element_to_be_clickable(self._privacy_policy_checkbox_locator)
)
self.find_element(*self._privacy_policy_checkbox_locator).click()
self.find_element(*self._privacy_policy_textarea_locator).send_keys(value)
@ -623,25 +785,26 @@ class ListedAddonSubmissionForm(Page):
class ThemeWizard(Page):
_wizard_header_locator = (By.CSS_SELECTOR, '.addon-submission-process > h3')
_theme_name_input_field = (By.ID, 'theme-name')
_upload_theme_image_button_locator = (By.ID, 'header-img')
_uploaded_image_preview_locator = (By.CLASS_NAME, 'preview.loaded')
_change_image_button_locator = (By.CLASS_NAME, 'reset')
_browser_preview_locator = (By.ID, 'preview-svg-root')
_browser_preview_header_image_locator = (By.ID, 'svg-header-img')
_submit_theme_button_locator = (By.CLASS_NAME, 'button.upload')
_wizard_header_locator = (By.CSS_SELECTOR, ".addon-submission-process > h3")
_theme_name_input_field = (By.ID, "theme-name")
_upload_theme_image_button_locator = (By.ID, "header-img")
_uploaded_image_preview_locator = (By.CLASS_NAME, "preview.loaded")
_change_image_button_locator = (By.CLASS_NAME, "reset")
_browser_preview_locator = (By.ID, "preview-svg-root")
_browser_preview_header_image_locator = (By.ID, "svg-header-img")
_submit_theme_button_locator = (By.CLASS_NAME, "button.upload")
_cancel_submission_button_locator = (
By.CSS_SELECTOR,
'.submission-buttons .delete-button',
".submission-buttons .delete-button",
)
def wait_for_page_to_load(self):
self.wait.until(EC.visibility_of_element_located((By.ID, 'theme-header')))
self.wait.until(EC.visibility_of_element_located((By.ID, "theme-header")))
return self
@property
def wizard_header(self):
self.wait.until(EC.visibility_of_element_located(self._wizard_header_locator))
return self.find_element(*self._wizard_header_locator).text
def set_theme_name(self, value):
@ -649,20 +812,24 @@ class ThemeWizard(Page):
def upload_theme_header(self, img):
button = self.find_element(*self._upload_theme_image_button_locator)
header_img = Path(f'{os.getcwd()}/img/{img}')
header_img = Path(f"{os.getcwd()}/img/{img}")
button.send_keys(str(header_img))
@property
def uploaded_image_preview(self):
self.wait.until(
EC.visibility_of_element_located(self._uploaded_image_preview_locator)
)
return self.find_element(*self._uploaded_image_preview_locator)
@property
def uploaded_image_source(self):
"""Fetch the source of the uploaded theme image"""
return self.uploaded_image_preview.get_attribute('src')
return self.uploaded_image_preview.get_attribute("src")
@property
def browser_preview(self):
self.wait.until(EC.visibility_of_element_located(self._browser_preview_locator))
return self.find_element(*self._browser_preview_locator)
@property
@ -670,7 +837,7 @@ class ThemeWizard(Page):
"""Fetch the source of tha generated theme preview image"""
return self.find_element(
*self._browser_preview_header_image_locator
).get_attribute('href')
).get_attribute("href")
def submit_theme(self):
self.find_element(*self._submit_theme_button_locator).click()
@ -686,16 +853,16 @@ class ThemeWizard(Page):
class SubmissionConfirmationPage(Page):
_confirmation_page_header_locator = (
By.CSS_SELECTOR,
'.addon-submission-process h3',
".addon-submission-process h3",
)
_confirmation_messages_locator = (By.CSS_SELECTOR, '.addon-submission-process p')
_manage_listing_button_locator = (By.LINK_TEXT, 'Go to My Submissions')
_confirmation_messages_locator = (By.CSS_SELECTOR, ".addon-submission-process p")
_manage_listing_button_locator = (By.LINK_TEXT, "Go to My Submissions")
_edit_version_button_locator = (
By.CSS_SELECTOR,
'.addon-submission-process p:nth-child(6) > a',
".addon-submission-process p:nth-child(6) > a",
)
_edit_listing_button_locator = (By.LINK_TEXT, 'Manage Listing')
_theme_preview_locator = (By.CSS_SELECTOR, '.addon-submission-process img')
_edit_listing_button_locator = (By.LINK_TEXT, "Manage Listing")
_theme_preview_locator = (By.CSS_SELECTOR, ".addon-submission-process img")
def wait_for_page_to_load(self):
self.wait.until(
@ -705,19 +872,25 @@ class SubmissionConfirmationPage(Page):
@property
def submission_confirmation_messages(self):
self.wait.until(
EC.visibility_of_element_located(self._confirmation_messages_locator)
)
return self.find_elements(*self._confirmation_messages_locator)
def click_manage_listing_button(self):
self.wait.until(EC.element_to_be_clickable(self._manage_listing_button_locator))
self.find_element(*self._manage_listing_button_locator).click()
from pages.desktop.developers.addons_manage import ManageAddons
return ManageAddons(self.driver, self.base_url).wait_for_page_to_load()
def click_edit_version_button(self):
self.wait.until(EC.element_to_be_clickable(self._edit_version_button_locator))
self.find_element(*self._edit_version_button_locator).click()
return ManageVersions(self.driver, self.base_url)
def click_edit_listing_button(self):
self.wait.until(EC.element_to_be_clickable(self._edit_listing_button_locator))
self.find_element(*self._edit_listing_button_locator).click()
from pages.desktop.developers.edit_addon import EditAddon
@ -725,4 +898,5 @@ class SubmissionConfirmationPage(Page):
@property
def generated_theme_preview(self):
self.wait.until(EC.visibility_of_element_located(self._theme_preview_locator))
return self.find_element(*self._theme_preview_locator)

Просмотреть файл

@ -9,9 +9,9 @@ from pages.desktop.base import Base
class BlogHomepage(Base):
URL_TEMPLATE = 'blog/'
URL_TEMPLATE = "blog/"
_articles_locator = (By.CSS_SELECTOR, '.blog-entry')
_articles_locator = (By.CSS_SELECTOR, ".blog-entry")
@property
def articles(self):
@ -19,49 +19,54 @@ class BlogHomepage(Base):
return [self.ArticlesList(self, el) for el in items]
class ArticlesList(Region):
_image_locator = (By.CSS_SELECTOR, '.blog-entry-featured-image > img')
_image_link_locator = (By.CSS_SELECTOR, '.blog-entry-featured-image')
_title_locator = (By.CLASS_NAME, 'blog-entry-title')
_date_locator = (By.CLASS_NAME, 'blog-entry-date')
_intro_text_locator = (By.CSS_SELECTOR, '.blog-entry-excerpt > p:nth-child(1)')
_read_more_link_locator = (By.CSS_SELECTOR, '.blog-entry-read-more > a')
_image_locator = (By.CSS_SELECTOR, ".blog-entry-featured-image > img")
_image_link_locator = (By.CSS_SELECTOR, ".blog-entry-featured-image")
_title_locator = (By.CLASS_NAME, "blog-entry-title")
_date_locator = (By.CLASS_NAME, "blog-entry-date")
_intro_text_locator = (By.CSS_SELECTOR, ".blog-entry-excerpt > p:nth-child(1)")
_read_more_link_locator = (By.CSS_SELECTOR, ".blog-entry-read-more > a")
@property
def image(self):
self.wait.until(EC.visibility_of_element_located(self._image_locator))
return self.find_element(*self._image_locator)
@property
def title(self):
self.wait.until(EC.visibility_of_element_located(self._image_locator))
return self.find_element(*self._title_locator)
@property
def date(self):
self.wait.until(EC.visibility_of_element_located(self._image_locator))
return self.find_element(*self._date_locator)
@property
def intro_text(self):
self.wait.until(EC.visibility_of_element_located(self._image_locator))
return self.find_element(*self._intro_text_locator)
@property
def read_more_link(self):
self.wait.until(EC.visibility_of_element_located(self._image_locator))
return self.find_element(*self._read_more_link_locator)
def click_read_more_link(self):
self.wait.until(EC.element_to_be_clickable(self._image_locator))
self.read_more_link.click()
return ArticlePage(self.driver, self.page.base_url).wait_for_page_to_load()
class ArticlePage(Base):
_header_logo_locator = (By.CLASS_NAME, 'header-logo')
_article_title = (By.CLASS_NAME, 'header-title')
_navigation_bar_locator = (By.CSS_SELECTOR, '.blogpost-breadcrumb ol li')
_content_paragraphs_locator = (By.CSS_SELECTOR, '.blogpost-content-wrapper > p')
_last_updated_date_locator = (By.CSS_SELECTOR, 'dd.updated')
_previous_article_link_locator = (By.CSS_SELECTOR, '.blogpost-nav-prev a p')
_next_article_link_locator = (By.CSS_SELECTOR, '.blogpost-nav-next a p')
_author_info_section_locator = (By.CLASS_NAME, 'blogpost-meta')
_static_addon_card_locator = (By.CLASS_NAME, 'StaticAddonCard')
_header_logo_locator = (By.CLASS_NAME, "header-logo")
_article_title = (By.CLASS_NAME, "header-title")
_navigation_bar_locator = (By.CSS_SELECTOR, ".blogpost-breadcrumb ol li")
_content_paragraphs_locator = (By.CSS_SELECTOR, ".blogpost-content-wrapper > p")
_last_updated_date_locator = (By.CSS_SELECTOR, "dd.updated")
_previous_article_link_locator = (By.CSS_SELECTOR, ".blogpost-nav-prev a p")
_next_article_link_locator = (By.CSS_SELECTOR, ".blogpost-nav-next a p")
_author_info_section_locator = (By.CLASS_NAME, "blogpost-meta")
_static_addon_card_locator = (By.CLASS_NAME, "StaticAddonCard")
def wait_for_page_to_load(self):
self.wait.until(
@ -71,30 +76,37 @@ class ArticlePage(Base):
@property
def header_logo(self):
self.wait_for_element_to_be_displayed(self._header_logo_locator)
return self.find_element(*self._header_logo_locator)
@property
def title(self):
self.wait_for_element_to_be_displayed(self._article_title)
return self.find_element(*self._article_title)
@property
def nav_bar_links(self):
self.wait_for_element_to_be_displayed(self._navigation_bar_locator)
return self.find_elements(*self._navigation_bar_locator)
@property
def content_paragraphs(self):
self.wait_for_element_to_be_displayed(self._content_paragraphs_locator)
return self.find_elements(*self._content_paragraphs_locator)
@property
def last_updated_date(self):
self.wait_for_element_to_be_displayed(self._last_updated_date_locator)
return self.find_element(*self._last_updated_date_locator)
@property
def next_article(self):
self.wait_for_element_to_be_displayed(self._next_article_link_locator)
return self.find_element(*self._next_article_link_locator)
@property
def previous_article(self):
self.wait_for_element_to_be_displayed(self._previous_article_link_locator)
return self.find_element(*self._previous_article_link_locator)
@property
@ -102,25 +114,31 @@ class ArticlePage(Base):
return self.Author(self, self.find_element(*self._author_info_section_locator))
class Author(Region):
_name_locator = (By.CSS_SELECTOR, 'dd.author')
_picture_locator = (By.CSS_SELECTOR, '.author-avatar > img')
_twitter_link_locator = (By.CLASS_NAME, 'share-twitter-link')
_pocket_link_locator = (By.CLASS_NAME, 'share-pocket-link')
_name_locator = (By.CSS_SELECTOR, "dd.author")
_picture_locator = (By.CSS_SELECTOR, ".author-avatar > img")
_twitter_link_locator = (By.CLASS_NAME, "share-twitter-link")
_pocket_link_locator = (By.CLASS_NAME, "share-pocket-link")
@property
def name(self):
self.wait.until(EC.visibility_of_element_located(self._name_locator))
return self.find_element(*self._name_locator)
@property
def picture(self):
self.wait.until(EC.visibility_of_element_located(self._picture_locator))
return self.find_element(*self._picture_locator)
@property
def twitter_link(self):
self.wait.until(
EC.visibility_of_element_located(self._twitter_link_locator)
)
return self.find_element(*self._twitter_link_locator)
@property
def pocket_link(self):
self.wait.until(EC.visibility_of_element_located(self._pocket_link_locator))
return self.find_element(*self._pocket_link_locator)
@property
@ -131,46 +149,56 @@ class ArticlePage(Base):
]
class AddonCard(Region):
_title_locator = (By.CSS_SELECTOR, '.AddonTitle > a')
_author_locator = (By.CSS_SELECTOR, '.AddonTitle-author > a')
_summary_locator = (By.CLASS_NAME, 'StaticAddonCard-summary')
_rating_locator = (By.CLASS_NAME, 'Rating')
_users_number_locator = (By.CLASS_NAME, 'StaticAddonCard-metadata-adu')
_add_to_firefox_button_locator = (By.CLASS_NAME, 'GetFirefoxButton-button')
_title_locator = (By.CSS_SELECTOR, ".AddonTitle > a")
_author_locator = (By.CSS_SELECTOR, ".AddonTitle-author > a")
_summary_locator = (By.CLASS_NAME, "StaticAddonCard-summary")
_rating_locator = (By.CLASS_NAME, "Rating")
_users_number_locator = (By.CLASS_NAME, "StaticAddonCard-metadata-adu")
_add_to_firefox_button_locator = (By.CLASS_NAME, "GetFirefoxButton-button")
_recommended_badge_link_locator = (
By.CLASS_NAME,
'PromotedBadge-link--recommended',
"PromotedBadge-link--recommended",
)
@property
def title(self):
self.wait.until(EC.visibility_of_element_located(self._title_locator))
return self.find_element(*self._title_locator)
@property
def author(self):
self.wait.until(EC.visibility_of_element_located(self._author_locator))
return self.find_element(*self._author_locator)
@property
def summary(self):
self.wait.until(EC.visibility_of_element_located(self._summary_locator))
return self.find_element(*self._summary_locator).text
@property
def rating(self):
rating = self.find_element(*self._rating_locator).get_attribute('title')
if 'There are no ratings yet' in rating:
self.wait.until(EC.visibility_of_element_located(self._rating_locator))
rating = self.find_element(*self._rating_locator).get_attribute("title")
if "There are no ratings yet" in rating:
return 0
return float(rating.split()[1])
@property
def users_number(self):
self.wait.until(
EC.visibility_of_element_located(self._users_number_locator)
)
return int(
self.find_element(*self._users_number_locator)
.text.split('Users: ')[1]
.text.split("Users: ")[1]
.replace(",", "")
)
@property
def add_to_firefox_button(self):
self.wait.until(
EC.visibility_of_element_located(self._add_to_firefox_button_locator)
)
return self.find_element(*self._add_to_firefox_button_locator)
@property
@ -185,4 +213,7 @@ class ArticlePage(Base):
@property
def recommended_link(self):
self.wait.until(
EC.visibility_of_element_located(self._recommended_badge_link_locator)
)
return self.find_element(*self._recommended_badge_link_locator)

Просмотреть файл

@ -10,41 +10,51 @@ from pages.desktop.base import Base
class Collections(Base):
URL_TEMPLATE = 'collections/'
URL_TEMPLATE = "collections/"
_collections_card_header_locator = (By.CSS_SELECTOR, '.CollectionList-info header')
_collections_card_summary_locator = (By.CLASS_NAME, 'CollectionList-info-text')
_collections_create_button_locator = (By.CLASS_NAME, 'CollectionList-create')
_collections_card_header_locator = (By.CSS_SELECTOR, ".CollectionList-info header")
_collections_card_summary_locator = (By.CLASS_NAME, "CollectionList-info-text")
_collections_create_button_locator = (By.CLASS_NAME, "CollectionList-create")
_my_collections_list_header_locator = (
By.CSS_SELECTOR,
'.CollectionList-list header',
".CollectionList-list header",
)
_collection_item_locator = (By.CLASS_NAME, 'UserCollection')
_collection_item_locator = (By.CLASS_NAME, "UserCollection")
def wait_for_page_to_load(self):
"""Waits for various page components to be loaded"""
self.wait.until(
EC.invisibility_of_element_located((By.CLASS_NAME, 'LoadingText')),
message='The collections page was not loaded',
EC.invisibility_of_element_located((By.CLASS_NAME, "LoadingText")),
message="The collections page was not loaded",
)
return self
@property
def list(self):
self.wait.until(EC.visibility_of_element_located(self._collection_item_locator))
"""Represents the list of collections form My Collections page"""
items = self.find_elements(*self._collection_item_locator)
return [self.Collection(self, el) for el in items]
@property
def collections_list_header(self):
self.wait.until(
EC.visibility_of_element_located(self._my_collections_list_header_locator)
)
return self.find_element(*self._my_collections_list_header_locator).text
@property
def collections_summary_card_header(self):
self.wait.until(
EC.visibility_of_element_located(self._collections_card_header_locator)
)
return self.find_element(*self._collections_card_header_locator).text
@property
def collections_card_summary(self):
self.wait.until(
EC.visibility_of_element_located(self._collections_card_summary_locator)
)
return self.find_element(*self._collections_card_summary_locator).text
def select_collection(self, count):
@ -60,15 +70,18 @@ class Collections(Base):
@property
def create_collection_button(self):
self.wait.until(
EC.visibility_of_element_located(self._collections_create_button_locator)
)
return self.find_element(*self._collections_create_button_locator)
def click_create_collection(self):
self.find_element(*self._collections_create_button_locator).click()
self.wait.until(
lambda _: self.is_element_displayed(
By.CSS_SELECTOR, '.CollectionManager-submit.Button--disabled'
By.CSS_SELECTOR, ".CollectionManager-submit.Button--disabled"
),
message='The collection save button was not displayed. The collection create form was not loaded properly',
message="The collection save button was not displayed. The collection create form was not loaded properly",
)
@property
@ -78,65 +91,80 @@ class Collections(Base):
class Collection(Region):
"""Represents an individual collection in My collections list."""
_name_locator = (By.CLASS_NAME, 'UserCollection-name')
_link_locator = (By.CLASS_NAME, 'UserCollection-link')
_addon_number_locator = (By.CLASS_NAME, 'UserCollection-number')
_name_locator = (By.CLASS_NAME, "UserCollection-name")
_link_locator = (By.CLASS_NAME, "UserCollection-link")
_addon_number_locator = (By.CLASS_NAME, "UserCollection-number")
@property
def name(self):
self.wait.until(EC.visibility_of_element_located(self._name_locator))
return self.find_element(*self._name_locator)
@property
def link(self):
self.wait.until(EC.visibility_of_element_located(self._link_locator))
return self.find_element(*self._link_locator)
@property
def number_of_addons(self):
self.wait.until(
EC.visibility_of_element_located(self._addon_number_locator)
)
return self.find_element(*self._addon_number_locator)
@property
def list_addons_count(self):
count = self.number_of_addons.text
return count.split()[0].replace(' add-ons', '')
return count.split()[0].replace(" add-ons", "")
class CollectionCreate(Region):
"""Represents the collections create form."""
_name_input_locator = (By.ID, 'collectionName')
_description_input_locator = (By.ID, 'collectionDescription')
_slug_input_locator = (By.ID, 'collectionSlug')
_cancel_button_locator = (By.CLASS_NAME, 'CollectionManager-cancel')
_name_input_locator = (By.ID, "collectionName")
_description_input_locator = (By.ID, "collectionDescription")
_slug_input_locator = (By.ID, "collectionSlug")
_cancel_button_locator = (By.CLASS_NAME, "CollectionManager-cancel")
_create_button_disabled_locator = (
By.CSS_SELECTOR,
'.CollectionManager-submit.Button--disabled',
".CollectionManager-submit.Button--disabled",
)
_create_button_locator = (By.CSS_SELECTOR, '.CollectionManager-submit')
_placeholder_locator = (By.CLASS_NAME, 'Collection-placeholder')
_add_success_message_locator = (By.CSS_SELECTOR, '.Notice-success p')
_add_error_message_locator = (By.CSS_SELECTOR, '.Notice-error p')
_removed_addon_notice_locator = (By.CSS_SELECTOR, '.Notice-generic p')
_create_button_locator = (By.CSS_SELECTOR, ".CollectionManager-submit")
_placeholder_locator = (By.CLASS_NAME, "Collection-placeholder")
_add_success_message_locator = (By.CSS_SELECTOR, ".Notice-success p")
_add_error_message_locator = (By.CSS_SELECTOR, ".Notice-error p")
_removed_addon_notice_locator = (By.CSS_SELECTOR, ".Notice-generic p")
_edit_collection_addons_list_locator = (
By.CSS_SELECTOR,
'.EditableCollectionAddon',
".EditableCollectionAddon",
)
_warning_text_locator = (By.CSS_SELECTOR, '.Notice-error .Notice-text')
_warning_text_locator = (By.CSS_SELECTOR, ".Notice-error .Notice-text")
def set_name(self, value):
self.find_element(*self._name_input_locator).send_keys(value)
@property
def name_value(self):
self.wait.until(EC.visibility_of_element_located(self._name_input_locator))
return self.find_element(*self._name_input_locator)
def set_description(self, value):
self.wait.until(
EC.visibility_of_element_located(self._description_input_locator)
)
self.find_element(*self._description_input_locator).send_keys(value)
def clear_description(self):
self.wait.until(
EC.visibility_of_element_located(self._description_input_locator)
)
self.find_element(*self._description_input_locator).clear()
@property
def description_value(self):
self.wait.until(
EC.visibility_of_element_located(self._description_input_locator)
)
return self.find_element(*self._description_input_locator).text
def set_slug(self, value):
@ -144,18 +172,28 @@ class Collections(Base):
@property
def slug_value(self):
self.wait.until(
EC.visibility_of_element_located(self._description_input_locator)
)
return self.find_element(*self._description_input_locator).text
@property
def slug_label_element(self):
self.wait.until(EC.visibility_of_element_located(self._slug_input_locator))
return self.find_element(*self._slug_input_locator)
@property
def cancel_creation(self):
self.wait.until(
EC.visibility_of_element_located(self._cancel_button_locator)
)
return self.find_element(*self._cancel_button_locator)
@property
def create_button_disabled(self):
self.wait.until(
EC.visibility_of_element_located(self._create_button_disabled_locator)
)
return self.find_element(*self._create_button_disabled_locator)
def save_collection(self):
@ -178,20 +216,21 @@ class Collections(Base):
return self.AddonSearch(self)
class AddonSearch(Region):
_header_locator = (By.CSS_SELECTOR, '.AutoSearchInput-label')
_root_locator = (By.CSS_SELECTOR, '.CollectionAddAddon')
_search_field_locator = (By.ID, 'AutoSearchInput-collection-addon-query')
_header_locator = (By.CSS_SELECTOR, ".AutoSearchInput-label")
_root_locator = (By.CSS_SELECTOR, ".CollectionAddAddon")
_search_field_locator = (By.ID, "AutoSearchInput-collection-addon-query")
_search_list_locator = (
By.CSS_SELECTOR,
'.AutoSearchInput-suggestions-list',
".AutoSearchInput-suggestions-list",
)
_search_item_locator = (
By.CSS_SELECTOR,
'.AutoSearchInput-suggestions-item',
".AutoSearchInput-suggestions-item",
)
@property
def header(self):
self.wait.until(EC.visibility_of_element_located(self._header_locator))
return self.find_element(*self._header_locator)
def search(self, term):
@ -203,28 +242,31 @@ class Collections(Base):
def search_items(self):
self.wait.until(
lambda _: self.is_element_displayed(*self._search_list_locator),
message='Search suggestions list was not loaded',
message="Search suggestions list was not loaded",
)
WebDriverWait(self.driver, 30).until(
EC.invisibility_of_element_located((By.CLASS_NAME, 'LoadingText')),
message='There were no search suggestions loaded for the used query',
EC.invisibility_of_element_located((By.CLASS_NAME, "LoadingText")),
message="There were no search suggestions loaded for the used query",
)
search_list = self.find_element(*self._search_list_locator)
items = search_list.find_elements(*self._search_item_locator)
return [self.SearchItems(self, el) for el in items]
class SearchItems(Region):
_item_name_locator = (By.CSS_SELECTOR, '.SearchSuggestion-name')
_item_name_locator = (By.CSS_SELECTOR, ".SearchSuggestion-name")
@property
def name(self):
self.wait.until(
EC.visibility_of_element_located(self._item_name_locator)
)
return self.find_element(*self._item_name_locator)
@property
def addon_add_confirmation(self):
self.wait.until(
EC.visibility_of_element_located(self._add_success_message_locator),
message='There was no success message displayed after the addon was added to the collection',
message="There was no success message displayed after the addon was added to the collection",
)
return self.find_element(*self._add_success_message_locator).text
@ -232,7 +274,7 @@ class Collections(Base):
def addon_add_failure(self):
self.wait.until(
EC.visibility_of_element_located(self._add_error_message_locator),
message='There was no error message displayed when the addon failed to be added to the collection',
message="There was no error message displayed when the addon failed to be added to the collection",
)
return self.find_element(*self._add_error_message_locator).text
@ -240,52 +282,60 @@ class Collections(Base):
def removed_addon_confirmation(self):
self.wait.until(
EC.visibility_of_element_located(self._removed_addon_notice_locator),
message='There was no success message displayed after the addon was removed from the collection',
message="There was no success message displayed after the addon was removed from the collection",
)
return self.find_element(*self._removed_addon_notice_locator).text
@property
def edit_addons_list(self):
self.wait.until(
EC.visibility_of_element_located(
self._edit_collection_addons_list_locator
)
)
items = self.find_elements(*self._edit_collection_addons_list_locator)
return [self.EditAddonsList(self, el) for el in items]
class EditAddonsList(Region):
_edit_list_addon_name_locator = (
By.CLASS_NAME,
'EditableCollectionAddon-name',
"EditableCollectionAddon-name",
)
_add_note_button_locator = (
By.CLASS_NAME,
'EditableCollectionAddon-leaveNote-button',
"EditableCollectionAddon-leaveNote-button",
)
_add_note_textarea_locator = (By.CLASS_NAME, 'DismissibleTextForm-textarea')
_save_note_button_locator = (By.CLASS_NAME, 'DismissibleTextForm-submit')
_add_note_textarea_locator = (By.CLASS_NAME, "DismissibleTextForm-textarea")
_save_note_button_locator = (By.CLASS_NAME, "DismissibleTextForm-submit")
_note_text_locator = (
By.CLASS_NAME,
'EditableCollectionAddon-notes-content',
"EditableCollectionAddon-notes-content",
)
_edit_addon_note_button_locator = (
By.CLASS_NAME,
'EditableCollectionAddon-notes-edit-button',
"EditableCollectionAddon-notes-edit-button",
)
_delete_addon_note_button_locator = (
By.CLASS_NAME,
'DismissibleTextForm-delete',
"DismissibleTextForm-delete",
)
_remove_addon_button_locator = (
By.CLASS_NAME,
'EditableCollectionAddon-remove-button',
"EditableCollectionAddon-remove-button",
)
@property
def edit_list_addon_name(self):
self.wait.until(
EC.visibility_of_element_located(self._edit_list_addon_name_locator)
)
return self.find_element(*self._edit_list_addon_name_locator).text
def click_add_note(self):
self.find_element(*self._add_note_button_locator).click()
self.wait.until(
EC.visibility_of_element_located(self._add_note_textarea_locator),
message='Collection addon note text input area was not displayed',
message="Collection addon note text input area was not displayed",
)
def note_input_text(self, value):
@ -296,27 +346,36 @@ class Collections(Base):
@property
def note_input_value(self):
self.wait.until(
EC.visibility_of_element_located(self._add_note_textarea_locator)
)
return self.find_element(*self._add_note_textarea_locator).text
def click_save_note(self):
self.find_element(*self._save_note_button_locator).click()
self.wait.until(
EC.visibility_of_element_located(self._note_text_locator),
message='The collection addon note was not visible after saving',
message="The collection addon note was not visible after saving",
)
@property
def note_text(self):
self.wait.until(
EC.visibility_of_element_located(self._note_text_locator)
)
return self.find_element(*self._note_text_locator).text
def click_edit_note(self):
self.find_element(*self._edit_addon_note_button_locator).click()
self.wait.until(
EC.visibility_of_element_located(self._add_note_textarea_locator),
message='Collection addon note text input area was not displayed',
message="Collection addon note text input area was not displayed",
)
def click_delete_note(self):
self.wait.until(
EC.element_to_be_clickable(self._delete_addon_note_button_locator)
)
self.find_element(*self._delete_addon_note_button_locator).click()
# waiting for the comment textarea to be closed after the note is deleted
try:
@ -324,13 +383,13 @@ class Collections(Base):
EC.invisibility_of_element_located(
self._add_note_textarea_locator
),
message='The collection note could not be deleted',
message="The collection note could not be deleted",
)
# if the note could not be deleted because of a field error,
# we need to catch that error and force the test to fail
except TimeoutException:
error = self.driver.find_element(
By.CSS_SELECTOR, '.ErrorList p'
By.CSS_SELECTOR, ".ErrorList p"
).text
pytest.fail(error)
@ -338,92 +397,106 @@ class Collections(Base):
self.find_element(*self._remove_addon_button_locator).click()
self.wait.until(
EC.visibility_of_element_located(
(By.CSS_SELECTOR, '.Notice-generic')
(By.CSS_SELECTOR, ".Notice-generic")
),
message='Success message was not displayed after the addon has been removed from the collection',
message="Success message was not displayed after the addon has been removed from the collection",
)
class CollectionDetail(Region):
"""Represents the detail page of a collection."""
_name_locator = (By.CLASS_NAME, 'CollectionDetails-title')
_summary_locator = (By.CLASS_NAME, 'CollectionDetails-description')
_addon_count_locator = (By.CSS_SELECTOR, '.MetadataCard dl:nth-child(1)')
_collection_creator_locator = (By.CSS_SELECTOR, '.MetadataCard dl:nth-child(2)')
_last_modified_date_locator = (By.CSS_SELECTOR, '.MetadataCard dl:nth-child(3)')
_stats_data_locator = (By.CSS_SELECTOR, '.MetadataCard dd')
_collection_addons_list_locator = (By.CSS_SELECTOR, '.SearchResult-result')
_edit_button_locator = (By.CLASS_NAME, 'CollectionDetails-edit-button')
_name_locator = (By.CLASS_NAME, "CollectionDetails-title")
_summary_locator = (By.CLASS_NAME, "CollectionDetails-description")
_addon_count_locator = (By.CSS_SELECTOR, ".MetadataCard dl:nth-child(1)")
_collection_creator_locator = (By.CSS_SELECTOR, ".MetadataCard dl:nth-child(2)")
_last_modified_date_locator = (By.CSS_SELECTOR, ".MetadataCard dl:nth-child(3)")
_stats_data_locator = (By.CSS_SELECTOR, ".MetadataCard dd")
_collection_addons_list_locator = (By.CSS_SELECTOR, ".SearchResult-result")
_edit_button_locator = (By.CLASS_NAME, "CollectionDetails-edit-button")
_edit_details_button_locator = (
By.CLASS_NAME,
'CollectionDetails-edit-details-button',
"CollectionDetails-edit-details-button",
)
_cancel_collection_edit_locator = (
By.CLASS_NAME,
'CollectionDetails-back-to-collection-button',
"CollectionDetails-back-to-collection-button",
)
_cancel_meta_edit_button_locator = (
By.CSS_SELECTOR,
'.CollectionManager-cancel',
".CollectionManager-cancel",
)
_delete_button_locator = (By.CLASS_NAME, 'Collection-delete-button')
_delete_button_locator = (By.CLASS_NAME, "Collection-delete-button")
_confirm_delete_dialog_locator = (
By.CSS_SELECTOR,
'.ConfirmationDialog-message',
".ConfirmationDialog-message",
)
_cancel_delete_button_locator = (
By.CSS_SELECTOR,
'.ConfirmationDialog-cancel-button',
".ConfirmationDialog-cancel-button",
)
_confirm_delete_button_locator = (
By.CSS_SELECTOR,
'.ConfirmationDialog-confirm-button',
".ConfirmationDialog-confirm-button",
)
_collection_addons_sort_locator = (By.ID, 'CollectionSort-select')
_collection_addons_sort_locator = (By.ID, "CollectionSort-select")
def wait_for_details_to_load(self):
"""Waits for various page components to be loaded"""
self.wait.until(
EC.invisibility_of_element_located((By.CLASS_NAME, 'LoadingText')),
message='The collections detail page was not loaded',
EC.invisibility_of_element_located((By.CLASS_NAME, "LoadingText")),
message="The collections detail page was not loaded",
)
return self
@property
def collection_name(self):
self.wait.until(EC.visibility_of_element_located(self._name_locator))
return self.find_element(*self._name_locator).text
@property
def collection_description(self):
self.wait.until(EC.visibility_of_element_located(self._summary_locator))
return self.find_element(*self._summary_locator)
@property
def collection_addons_number(self):
self.wait.until(EC.visibility_of_element_located(self._addon_count_locator))
return self.find_element(*self._addon_count_locator)
@property
def collection_creator(self):
self.wait.until(
EC.visibility_of_element_located(self._collection_creator_locator)
)
return self.find_element(*self._collection_creator_locator)
@property
def collection_last_update_date(self):
self.wait.until(
EC.visibility_of_element_located(self._last_modified_date_locator)
)
return self.find_element(*self._last_modified_date_locator)
@property
def collection_stats(self):
self.wait.until(EC.visibility_of_element_located(self._stats_data_locator))
return self.find_elements(*self._stats_data_locator)
@property
def collection_addons_list(self):
self.wait.until(
EC.visibility_of_element_located(self._collection_addons_list_locator)
)
return self.find_elements(*self._collection_addons_list_locator)
def click_edit_collection_button(self):
self.wait.until(EC.element_to_be_clickable(self._edit_button_locator))
self.find_element(*self._edit_button_locator).click()
self.wait.until(
lambda _: self.is_element_displayed(
By.ID, 'AutoSearchInput-collection-addon-query'
By.ID, "AutoSearchInput-collection-addon-query"
),
message='The edit collection search component was not loaded',
message="The edit collection search component was not loaded",
)
def click_back_to_collection(self):
@ -437,46 +510,64 @@ class Collections(Base):
self.find_element(*self._edit_details_button_locator).click()
self.wait.until(
EC.visibility_of_element_located(self._cancel_meta_edit_button_locator),
message='The edit collection meta form was not displayed',
message="The edit collection meta form was not displayed",
)
def cancel_edit_collection_meta(self):
self.find_element(*self._cancel_meta_edit_button_locator).click()
self.wait.until(
EC.element_to_be_clickable(self._edit_details_button_locator),
message='The edit collection meta form could not be closed',
message="The edit collection meta form could not be closed",
)
def delete_collection(self):
self.find_element(*self._delete_button_locator).click()
self.wait.until(
EC.element_to_be_clickable(self._confirm_delete_button_locator),
message='The delete collection confirmation section was not displayed',
message="The delete collection confirmation section was not displayed",
)
@property
def confirm_delete_dialog_message(self):
self.wait.until(
EC.visibility_of_element_located(self._confirm_delete_dialog_locator)
)
return self.find_element(*self._confirm_delete_dialog_locator)
@property
def cancel_delete_collection_button(self):
self.wait.until(
EC.visibility_of_element_located(self._cancel_delete_button_locator)
)
return self.find_element(*self._cancel_delete_button_locator)
def cancel_delete_collection(self):
self.wait.until(
EC.element_to_be_clickable(self._cancel_delete_button_locator)
)
self.find_element(*self._cancel_delete_button_locator).click()
self.wait.until(
EC.element_to_be_clickable(self._delete_button_locator),
message='The delete collection confirmation section could not be closed',
message="The delete collection confirmation section could not be closed",
)
@property
def confirm_delete_collection_button(self):
self.wait.until(
EC.visibility_of_element_located(self._confirm_delete_button_locator)
)
return self.find_element(*self._confirm_delete_button_locator)
def confirm_delete_collection(self):
self.wait.until(
EC.element_to_be_clickable(self._confirm_delete_button_locator)
)
self.find_element(*self._confirm_delete_button_locator).click()
return Collections(self.driver, self.page).wait_for_page_to_load()
@property
def sort_addons(self):
self.wait.until(
EC.visibility_of_element_located(self._collection_addons_sort_locator)
)
return self.find_element(*self._collection_addons_sort_locator)

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -4,12 +4,14 @@ from pages.desktop.base import Base
from regions.desktop.categories import Categories
from regions.desktop.shelves import Shelves
from selenium.webdriver.support import expected_conditions as EC
class Extensions(Base):
URL_TEMPLATE = 'extensions/'
URL_TEMPLATE = "extensions/"
_title_locator = (By.CLASS_NAME, 'LandingPage-addonType-name')
_header_summary_locator = (By.CSS_SELECTOR, '.LandingPage-header p')
_title_locator = (By.CLASS_NAME, "LandingPage-addonType-name")
_header_summary_locator = (By.CSS_SELECTOR, ".LandingPage-header p")
def wait_for_page_to_load(self):
self.wait.until(lambda _: self.is_element_displayed(*self._title_locator))
@ -17,10 +19,12 @@ class Extensions(Base):
@property
def title(self):
self.wait.until(EC.visibility_of_element_located(self._title_locator))
return self.find_element(*self._title_locator).text
@property
def header_summary(self):
self.wait.until(EC.visibility_of_element_located(self._header_summary_locator))
return self.find_element(*self._header_summary_locator).text
@property

Просмотреть файл

@ -12,92 +12,114 @@ from pages.desktop.frontend.details import Detail
class Home(Base):
"""Addons Home page"""
_recommended_extensions_locator = (By.CLASS_NAME, 'Home-Recommended-extensions')
_recommended_themes_locator = (By.CLASS_NAME, 'Home-Recommended-themes')
_hero_locator = (By.CLASS_NAME, 'HeroRecommendation')
_secondary_hero_locator = (By.CLASS_NAME, 'SecondaryHero')
_popular_extensions_locator = (By.CLASS_NAME, 'Home-PopularExtensions')
_popular_themes_locator = (By.CLASS_NAME, 'Home-Popular-themes')
_themes_category_locator = (By.CLASS_NAME, 'Home-CuratedThemes')
_toprated_themes_locator = (By.CLASS_NAME, 'Home-TopRatedThemes')
_featured_collections_locator = (By.CLASS_NAME, 'Home-FeaturedCollection')
_shelves_titles_locator = (By.CSS_SELECTOR, '.CardList .Card-header-text')
_recommended_extensions_locator = (By.CLASS_NAME, "Home-Recommended-extensions")
_recommended_themes_locator = (By.CLASS_NAME, "Home-Recommended-themes")
_hero_locator = (By.CLASS_NAME, "HeroRecommendation")
_secondary_hero_locator = (By.CLASS_NAME, "SecondaryHero")
_popular_extensions_locator = (By.CLASS_NAME, "Home-PopularExtensions")
_popular_themes_locator = (By.CLASS_NAME, "Home-Popular-themes")
_themes_category_locator = (By.CLASS_NAME, "Home-CuratedThemes")
_toprated_themes_locator = (By.CLASS_NAME, "Home-TopRatedThemes")
_featured_collections_locator = (By.CLASS_NAME, "Home-FeaturedCollection")
_shelves_titles_locator = (By.CSS_SELECTOR, ".CardList .Card-header-text")
_shelves_see_more_links_locator = (
By.CSS_SELECTOR,
'.Card-shelf-footer-in-header a',
".Card-shelf-footer-in-header a",
)
def wait_for_page_to_load(self):
"""Waits for various page components to be loaded"""
self.wait.until(
EC.invisibility_of_element_located((By.CLASS_NAME, 'LoadingText'))
EC.invisibility_of_element_located((By.CLASS_NAME, "LoadingText"))
)
return self
@property
def primary_hero(self):
self.wait.until(EC.visibility_of_element_located(self._hero_locator))
return self.find_element(*self._hero_locator)
@property
def hero_banner(self):
self.wait.until(EC.visibility_of_element_located(self._hero_locator))
el = self.find_element(*self._hero_locator)
return self.PrimaryHero(self, el)
@property
def addon_shelf_titles(self):
self.wait.until(EC.visibility_of_element_located(self._shelves_titles_locator))
return [
title.text for title in self.find_elements(*self._shelves_titles_locator)
]
@property
def popular_extensions(self):
self.wait.until(
EC.visibility_of_element_located(self._popular_extensions_locator)
)
el = self.find_element(*self._popular_extensions_locator)
return self.Extensions(self, el)
@property
def recommended_extensions(self):
self.wait.until(
EC.visibility_of_element_located(self._recommended_extensions_locator)
)
el = self.find_element(*self._recommended_extensions_locator)
return self.Extensions(self, el)
@property
def recommended_themes(self):
self.wait.until(
EC.visibility_of_element_located(self._recommended_themes_locator)
)
el = self.find_element(*self._recommended_themes_locator)
return self.Themes(self, el)
@property
def popular_themes(self):
self.wait.until(EC.visibility_of_element_located(self._popular_themes_locator))
el = self.find_element(*self._popular_themes_locator)
return self.Themes(self, el)
@property
def toprated_themes(self):
self.wait.until(EC.visibility_of_element_located(self._toprated_themes_locator))
el = self.find_element(*self._toprated_themes_locator)
return self.Themes(self, el)
@property
def theme_category(self):
self.wait.until(EC.visibility_of_element_located(self._themes_category_locator))
el = self.find_element(*self._themes_category_locator)
return self.ThemeCategory(self, el)
@property
def secondary_hero(self):
self.wait.until(EC.visibility_of_element_located(self._secondary_hero_locator))
el = self.find_element(*self._secondary_hero_locator)
return self.SecondaryHero(self, el)
@property
def featured_collections(self):
self.wait.until(
EC.visibility_of_element_located(self._featured_collections_locator)
)
el = self.find_element(*self._featured_collections_locator)
return self.Extensions(self, el)
@property
def see_more_links_in_shelves(self):
self.wait.until(
EC.visibility_of_element_located(self._shelves_see_more_links_locator)
)
return self.find_elements(*self._shelves_see_more_links_locator)
def click_see_more_links(self, count):
link = [el for el in self.see_more_links_in_shelves]
target = link[count].get_attribute('target')
target = link[count].get_attribute("target")
# external links are opened in new tabs, so we need to account for multiple windows
if target == '_blank':
if target == "_blank":
home_tab = self.driver.current_window_handle
link[count].click()
self.wait.until(EC.number_of_windows_to_be(2))
@ -105,11 +127,11 @@ class Home(Base):
self.driver.switch_to.window(new_tab)
# see more external links can contain variable content we might not know in advance (especially on prod)
# the solution used here is to verify that the content we link to is available (i.e. page response = 200)
self.wait.until(custom_waits.url_not_contains('about:blank'))
self.wait.until(custom_waits.url_not_contains("about:blank"))
page = requests.head(self.driver.current_url)
assert (
page.status_code == 200
), f'The response status code was {page.status_code}'
), f"The response status code was {page.status_code}"
self.driver.close()
self.driver.switch_to.window(home_tab)
else:
@ -117,46 +139,58 @@ class Home(Base):
# in this case, we check that the content exists inside the AMO domain
link[count].click()
self.wait.until(
EC.invisibility_of_element_located((By.CLASS_NAME, 'LoadingText'))
EC.invisibility_of_element_located((By.CLASS_NAME, "LoadingText"))
)
assert 'addons' in self.driver.current_url
assert "addons" in self.driver.current_url
page = requests.head(self.driver.current_url)
assert (
page.status_code == 200
), f'The response status code was {page.status_code}'
), f"The response status code was {page.status_code}"
self.driver.back()
# waits for the homepage to reload
self.wait.until(
EC.invisibility_of_element_located((By.CLASS_NAME, 'LoadingText'))
EC.invisibility_of_element_located((By.CLASS_NAME, "LoadingText"))
)
class ThemeCategory(Region):
_home_theme_category_locator = (By.CLASS_NAME, 'Home-SubjectShelf-list-item')
_shelf_summary_locator = (By.CLASS_NAME, 'Home-SubjectShelf-subheading')
_home_theme_category_locator = (By.CLASS_NAME, "Home-SubjectShelf-list-item")
_shelf_summary_locator = (By.CLASS_NAME, "Home-SubjectShelf-subheading")
@property
def list(self):
self.wait.until(
EC.visibility_of_element_located(self._home_theme_category_locator)
)
items = self.find_elements(*self._home_theme_category_locator)
return [self.CategoryDetail(self.page, el) for el in items]
@property
def shelf_summary(self):
self.wait.until(
EC.visibility_of_element_located(self._shelf_summary_locator)
)
return self.find_element(*self._shelf_summary_locator).text
class CategoryDetail(Region):
_category_link_locator = (By.CLASS_NAME, 'Home-SubjectShelf-link')
_category_link_locator = (By.CLASS_NAME, "Home-SubjectShelf-link")
_category_name_locator = (
By.CSS_SELECTOR,
'.Home-SubjectShelf-link span:nth-child(2)',
".Home-SubjectShelf-link span:nth-child(2)",
)
_category_icon_locator = (By.CLASS_NAME, 'CategoryIcon')
_category_icon_locator = (By.CLASS_NAME, "CategoryIcon")
@property
def name(self):
self.wait.until(
EC.visibility_of_element_located(self._category_name_locator)
)
return self.find_element(*self._category_name_locator).text
@property
def category_icon(self):
self.wait.until(
EC.visibility_of_element_located(self._category_icon_locator)
)
return self.find_element(*self._category_icon_locator)
def click(self):
@ -166,16 +200,18 @@ class Home(Base):
return Search(self.driver, self.page.base_url)
class Extensions(Region):
_browse_all_locator = (By.CSS_SELECTOR, '.Card-shelf-footer-in-header a')
_extensions_locator = (By.CLASS_NAME, 'SearchResult')
_promo_card_header_locator = (By.CLASS_NAME, 'Card-header-text')
_browse_all_locator = (By.CSS_SELECTOR, ".Card-shelf-footer-in-header a")
_extensions_locator = (By.CLASS_NAME, "SearchResult")
_promo_card_header_locator = (By.CLASS_NAME, "Card-header-text")
@property
def list(self):
self.wait.until(EC.visibility_of_element_located(self._extensions_locator))
items = self.find_elements(*self._extensions_locator)
return [Home.PromoShelvesAddons(self.page, el) for el in items]
def browse_all(self):
self.wait.until(EC.visibility_of_element_located(self._browse_all_locator))
self.find_element(*self._browse_all_locator).click()
from pages.desktop.frontend.search import Search
@ -184,23 +220,29 @@ class Home(Base):
@property
def card_header(self):
self.wait.until(
EC.visibility_of_element_located(self._promo_card_header_locator)
)
return self.find_element(*self._promo_card_header_locator).text
def see_collection_details(self):
self.wait.until(EC.visibility_of_element_located(self._browse_all_locator))
self.find_element(*self._browse_all_locator).click()
# TODO: add additional validations when I'm covering collections
class Themes(Region):
_browse_all_locator = (By.CSS_SELECTOR, '.Card-shelf-footer-in-header a')
_themes_locator = (By.CLASS_NAME, 'SearchResult--theme')
_promo_card_header_locator = (By.CLASS_NAME, 'Card-header-text')
_browse_all_locator = (By.CSS_SELECTOR, ".Card-shelf-footer-in-header a")
_themes_locator = (By.CLASS_NAME, "SearchResult--theme")
_promo_card_header_locator = (By.CLASS_NAME, "Card-header-text")
@property
def list(self):
self.wait.until(EC.visibility_of_element_located(self._themes_locator))
items = self.find_elements(*self._themes_locator)
return [Home.PromoShelvesAddons(self.page, el) for el in items]
def browse_all(self):
self.wait.until(EC.visibility_of_element_located(self._browse_all_locator))
self.find_element(*self._browse_all_locator).click()
from pages.desktop.frontend.search import Search
@ -209,20 +251,25 @@ class Home(Base):
@property
def card_header(self):
self.wait.until(
EC.visibility_of_element_located(self._promo_card_header_locator)
)
return self.find_element(*self._promo_card_header_locator).text
class PromoShelvesAddons(Region):
_addon_link_locator = (By.CLASS_NAME, 'SearchResult-link')
_addon_name_locator = (By.CLASS_NAME, 'SearchResult-name')
_addon_icon_locator = (By.CLASS_NAME, 'SearchResult-icon')
_addon_users_locator = (By.CLASS_NAME, 'SearchResult-users-text')
_addon_rating_locator = (By.CLASS_NAME, 'SearchResult-rating')
_addon_link_locator = (By.CLASS_NAME, "SearchResult-link")
_addon_name_locator = (By.CLASS_NAME, "SearchResult-name")
_addon_icon_locator = (By.CLASS_NAME, "SearchResult-icon")
_addon_users_locator = (By.CLASS_NAME, "SearchResult-users-text")
_addon_rating_locator = (By.CLASS_NAME, "SearchResult-rating")
@property
def name(self):
self.wait.until(EC.visibility_of_element_located(self._addon_name_locator))
return self.find_element(*self._addon_name_locator)
def click(self):
self.wait.until(EC.element_to_be_clickable(self._addon_link_locator))
self.find_element(*self._addon_link_locator).click()
from pages.desktop.frontend.extensions import Extensions
@ -230,14 +277,19 @@ class Home(Base):
@property
def addon_icon_preview(self):
self.wait.until(EC.visibility_of_element_located(self._addon_icon_locator))
return self.find_element(*self._addon_icon_locator)
@property
def addon_users_preview(self):
self.wait.until(EC.visibility_of_element_located(self._addon_users_locator))
return self.find_element(*self._addon_users_locator)
@property
def addon_rating_preview(self):
self.wait.until(
EC.visibility_of_element_located(self._addon_rating_locator)
)
return self.find_element(*self._addon_rating_locator)
def shelf_item_elements(self, item):
@ -246,56 +298,75 @@ class Home(Base):
assert item.addon_users_preview.is_displayed()
class PrimaryHero(Region):
_hero_locator = (By.CLASS_NAME, 'HeroRecommendation')
_hero_image_locator = (By.CLASS_NAME, 'HeroRecommendation-image')
_hero_title_locator = (By.CLASS_NAME, 'HeroRecommendation-title-text')
_hero_extension_name_locator = (By.CLASS_NAME, 'HeroRecommendation-heading')
_hero_extension_summary_locator = (By.CLASS_NAME, 'HeroRecommendation-body')
_extension_button_locator = (By.CLASS_NAME, 'HeroRecommendation-link')
_extension_link_locator = (By.CSS_SELECTOR, '.HeroRecommendation-info a')
_hero_locator = (By.CLASS_NAME, "HeroRecommendation")
_hero_image_locator = (By.CLASS_NAME, "HeroRecommendation-image")
_hero_title_locator = (By.CLASS_NAME, "HeroRecommendation-title-text")
_hero_extension_name_locator = (By.CLASS_NAME, "HeroRecommendation-heading")
_hero_extension_summary_locator = (By.CLASS_NAME, "HeroRecommendation-body")
_extension_button_locator = (By.CLASS_NAME, "HeroRecommendation-link")
_extension_link_locator = (By.CSS_SELECTOR, ".HeroRecommendation-info a")
@property
def primary_hero_banner(self):
self.wait.until(EC.visibility_of_element_located(self._hero_locator))
return self.find_element(*self._hero_locator)
@property
def primary_hero_image(self):
self.wait.until(EC.visibility_of_element_located(self._hero_image_locator))
return self.find_element(*self._hero_image_locator)
@property
def primary_hero_title(self):
self.wait.until(EC.visibility_of_element_located(self._hero_title_locator))
return self.find_element(*self._hero_title_locator).text
@property
def primary_hero_extension_name(self):
self.wait.until(
EC.visibility_of_element_located(self._hero_extension_name_locator)
)
return self.find_element(*self._hero_extension_name_locator).text
@property
def primary_hero_extension_summary(self):
self.wait.until(
EC.visibility_of_element_located(self._hero_extension_summary_locator)
)
return self.find_element(*self._hero_extension_summary_locator)
def click_hero_extension_link(self):
self.wait.until(EC.element_to_be_clickable(self._extension_button_locator))
self.find_element(*self._extension_button_locator).click()
return Detail(self.driver, self.page.base_url).wait_for_page_to_load()
class SecondaryHero(Region):
_secondary_headline_locator = (By.CLASS_NAME, 'SecondaryHero-message-headline')
_secondary_headline_locator = (By.CLASS_NAME, "SecondaryHero-message-headline")
_secondary_description_locator = (
By.CLASS_NAME,
'SecondaryHero-message-description',
"SecondaryHero-message-description",
)
_see_all_extensions_locator = (By.CLASS_NAME, 'SecondaryHero-message-link')
_modules_locator = (By.CLASS_NAME, 'SecondaryHero-module')
_see_all_extensions_locator = (By.CLASS_NAME, "SecondaryHero-message-link")
_modules_locator = (By.CLASS_NAME, "SecondaryHero-module")
@property
def secondary_hero_headline(self):
self.wait.until(
EC.visibility_of_element_located(self._secondary_headline_locator)
)
return self.find_element(*self._secondary_headline_locator).text
@property
def secondary_hero_description(self):
self.wait.until(
EC.visibility_of_element_located(self._secondary_description_locator)
)
return self.find_element(*self._secondary_description_locator).text
def see_all_extensions(self):
self.wait.until(
EC.element_to_be_clickable(self._see_all_extensions_locator)
)
self.find_element(*self._see_all_extensions_locator).click()
from pages.desktop.frontend.extensions import Extensions
@ -303,31 +374,38 @@ class Home(Base):
@property
def secondary_hero_modules(self):
self.wait.until(EC.visibility_of_element_located(self._modules_locator))
element = self.find_elements(*self._modules_locator)
return [self.SecondaryHeroModules(self.page, el) for el in element]
class SecondaryHeroModules(Region):
_module_icon_locator = (By.CLASS_NAME, 'SecondaryHero-module-icon')
_module_icon_locator = (By.CLASS_NAME, "SecondaryHero-module-icon")
_module_description_locator = (
By.CLASS_NAME,
'SecondaryHero-module-description',
"SecondaryHero-module-description",
)
_module_link_locator = (By.CSS_SELECTOR, '.SecondaryHero-module a')
_module_link_text_locator = (By.CLASS_NAME, 'SecondaryHero-module-linkText')
_module_link_locator = (By.CSS_SELECTOR, ".SecondaryHero-module a")
_module_link_text_locator = (By.CLASS_NAME, "SecondaryHero-module-linkText")
@property
def module_icon(self):
self.wait.until(
EC.visibility_of_element_located(self._module_icon_locator)
)
return self.find_element(*self._module_icon_locator)
@property
def module_description(self):
self.wait.until(
EC.visibility_of_element_located(self._module_description_locator)
)
return self.find_element(*self._module_description_locator)
def click_secondary_module_link(self):
link = self.find_element(*self._module_link_locator)
target = link.get_attribute('target')
target = link.get_attribute("target")
# external links are opened in new tabs, so we need to account for multiple windows
if target == '_blank':
if target == "_blank":
link.click()
self.wait.until(EC.number_of_windows_to_be(2))
new_tab = self.driver.window_handles[1]
@ -336,11 +414,11 @@ class Home(Base):
# in advance what that content might be; also, we want to avoid frequent maintenance for
# these tests; the solution used is to verify that the content we link to is available
# (i.e. we check that the page response status is 200)
self.wait.until(custom_waits.url_not_contains('about:blank'))
self.wait.until(custom_waits.url_not_contains("about:blank"))
page = requests.head(self.driver.current_url)
assert (
page.status_code == 200
), f'The response status code was {page.status_code}'
), f"The response status code was {page.status_code}"
else:
# this condition handles links that open on the amo domain; again, we might not know the
# content in advance, so the best we can do is check that the page opens in AMO
@ -348,11 +426,11 @@ class Home(Base):
link.click()
self.wait.until(
EC.invisibility_of_element_located(
(By.CLASS_NAME, 'LoadingText')
(By.CLASS_NAME, "LoadingText")
)
)
assert 'addons' in self.driver.current_url
assert "addons" in self.driver.current_url
page = requests.head(self.driver.current_url)
assert (
page.status_code == 200
), f'The response status code was {page.status_code}'
), f"The response status code was {page.status_code}"

Просмотреть файл

@ -5,42 +5,60 @@ from pages.desktop.base import Base
class LanguageTools(Base):
URL_TEMPLATE = '/language-tools/'
URL_TEMPLATE = "/language-tools/"
_language_tools_header_locator = (By.CLASS_NAME, 'Card-header-text')
_langpacks_info_text_locator = (By.CSS_SELECTOR, '.Card-contents p:nth-child(2)')
_dictionaries_info_text_locator = (By.CSS_SELECTOR, '.Card-contents p:nth-child(1)')
_language_list_column_locator = (By.CSS_SELECTOR, '.pivoted:nth-child(1) strong')
_langpacks_list_column_locator = (By.CSS_SELECTOR, '.pivoted:nth-child(2) a')
_dictionaries_list_column_locator = (By.CSS_SELECTOR, '.pivoted:nth-child(3) a')
_language_tools_header_locator = (By.CLASS_NAME, "Card-header-text")
_langpacks_info_text_locator = (By.CSS_SELECTOR, ".Card-contents p:nth-child(2)")
_dictionaries_info_text_locator = (By.CSS_SELECTOR, ".Card-contents p:nth-child(1)")
_language_list_column_locator = (By.CSS_SELECTOR, ".pivoted:nth-child(1) strong")
_langpacks_list_column_locator = (By.CSS_SELECTOR, ".pivoted:nth-child(2) a")
_dictionaries_list_column_locator = (By.CSS_SELECTOR, ".pivoted:nth-child(3) a")
def loaded(self):
"""Waits for various page components to be loaded"""
self.wait.until(
EC.invisibility_of_element_located((By.CLASS_NAME, 'LoadingText'))
EC.invisibility_of_element_located((By.CLASS_NAME, "LoadingText"))
)
return self
@property
def language_tools_header(self):
self.wait.until(
EC.visibility_of_element_located(self._language_tools_header_locator)
)
return self.find_element(*self._language_tools_header_locator).text
@property
def language_packs_info_text(self):
self.wait.until(
EC.visibility_of_element_located(self._langpacks_info_text_locator)
)
return self.find_element(*self._langpacks_info_text_locator).text
@property
def dictionaries_info_text(self):
self.wait.until(
EC.visibility_of_element_located(self._dictionaries_info_text_locator)
)
return self.find_element(*self._dictionaries_info_text_locator).text
@property
def supported_languages_list(self):
self.wait.until(
EC.visibility_of_element_located(self._language_list_column_locator)
)
return self.find_elements(*self._language_list_column_locator)
@property
def available_language_packs(self):
self.wait.until(
EC.visibility_of_element_located(self._langpacks_list_column_locator)
)
return self.find_elements(*self._langpacks_list_column_locator)
@property
def available_dictionaries(self):
self.wait.until(
EC.visibility_of_element_located(self._dictionaries_list_column_locator)
)
return self.find_elements(*self._dictionaries_list_column_locator)

Просмотреть файл

@ -17,97 +17,137 @@ class Login(Base):
Project level Environment Variables and are picked up at runtime"""
# 1. user that performs normal operations on the site, like writing add-on reviews
REGULAR_USER_EMAIL = os.environ.get('REGULAR_USER_EMAIL')
REGULAR_USER_PASSWORD = os.environ.get('REGULAR_USER_PASSWORD')
REGULAR_USER_EMAIL = os.environ.get("REGULAR_USER_EMAIL")
REGULAR_USER_PASSWORD = os.environ.get("REGULAR_USER_PASSWORD")
# 2. user with elevated permissions that can perform special actions on the site
ADMIN_USER_EMAIL = os.environ.get('ADMIN_USER_EMAIL')
ADMIN_USER_PASSWORD = os.environ.get('ADMIN_USER_PASSWORD')
ADMIN_USER_EMAIL = os.environ.get("ADMIN_USER_EMAIL")
ADMIN_USER_PASSWORD = os.environ.get("ADMIN_USER_PASSWORD")
# 3. user who has published add-ons on AMO
DEVELOPER_EMAIL = os.environ.get('DEVELOPER_EMAIL')
DEVELOPER_PASSWORD = os.environ.get('DEVELOPER_PASSWORD')
DEVELOPER_EMAIL = os.environ.get("DEVELOPER_EMAIL")
DEVELOPER_PASSWORD = os.environ.get("DEVELOPER_PASSWORD")
# 4. user who re-creates accounts on AMO after having deleted them previously
REUSABLE_USER_EMAIL = os.environ.get('REUSABLE_USER_EMAIL')
REUSABLE_USER_PASSWORD = os.environ.get('REUSABLE_USER_PASSWORD')
REUSABLE_USER_EMAIL = os.environ.get("REUSABLE_USER_EMAIL")
REUSABLE_USER_PASSWORD = os.environ.get("REUSABLE_USER_PASSWORD")
# 5. user used for the ratings tests
RATING_USER_EMAIL = os.environ.get('RATING_USER_EMAIL')
RATING_USER_PASSWORD = os.environ.get('RATING_USER_PASSWORD')
RATING_USER_EMAIL = os.environ.get("RATING_USER_EMAIL")
RATING_USER_PASSWORD = os.environ.get("RATING_USER_PASSWORD")
# 6. user used for collections tests
COLLECTION_USER_EMAIL = os.environ.get('COLLECTION_USER_EMAIL')
COLLECTION_USER_PASSWORD = os.environ.get('COLLECTION_USER_PASSWORD')
COLLECTION_USER_EMAIL = os.environ.get("COLLECTION_USER_EMAIL")
COLLECTION_USER_PASSWORD = os.environ.get("COLLECTION_USER_PASSWORD")
# 7. user used for add-on submissions
SUBMISSIONS_USER_EMAIL = os.environ.get('SUBMISSIONS_USER_EMAIL')
SUBMISSIONS_USER_PASSWORD = os.environ.get('SUBMISSIONS_USER_PASSWORD')
SUBMISSIONS_USER_EMAIL = os.environ.get("SUBMISSIONS_USER_EMAIL")
SUBMISSIONS_USER_PASSWORD = os.environ.get("SUBMISSIONS_USER_PASSWORD")
# 8. user used in API tests
API_USER_EMAIL = os.environ.get('API_USER_EMAIL')
API_USER_PASSWORD = os.environ.get('API_USER_PASSWORD')
API_USER_EMAIL = os.environ.get("API_USER_EMAIL")
API_USER_PASSWORD = os.environ.get("API_USER_PASSWORD")
# 9. user with a mozilla account that has specific submission permissions
STAFF_USER_EMAIL = os.environ.get('STAFF_USER_EMAIL')
STAFF_USER_PASSWORD = os.environ.get('STAFF_USER_PASSWORD')
STAFF_USER_EMAIL = os.environ.get("STAFF_USER_EMAIL")
STAFF_USER_PASSWORD = os.environ.get("STAFF_USER_PASSWORD")
# 10. account added to the list of banned user emails for rating and addon submissions
RESTRICTED_USER_EMAIL = os.environ.get('RESTRICTED_USER_EMAIL')
RESTRICTED_USER_PASSWORD = os.environ.get('RESTRICTED_USER_PASSWORD')
RESTRICTED_USER_EMAIL = os.environ.get("RESTRICTED_USER_EMAIL")
RESTRICTED_USER_PASSWORD = os.environ.get("RESTRICTED_USER_PASSWORD")
# # KEYS FOR AUTHENTICATOR DEV
DEVELOPER_USER_KEY_DEV = os.environ.get('DEVELOPER_USER_KEY_DEV')
RATING_USER_KEY_DEV = os.environ.get('RATING_USER_KEY_DEV')
SUBMISSIONS_USER_KEY_DEV = os.environ.get('SUBMISSIONS_USER_KEY_DEV')
API_USER_KEY_DEV = os.environ.get('API_USER_KEY_DEV')
STAFF_USER_KEY_DEV = os.environ.get('STAFF_USER_KEY_DEV')
DEVELOPER_USER_KEY_DEV = os.environ.get("DEVELOPER_USER_KEY_DEV")
RATING_USER_KEY_DEV = os.environ.get("RATING_USER_KEY_DEV")
SUBMISSIONS_USER_KEY_DEV = os.environ.get("SUBMISSIONS_USER_KEY_DEV")
API_USER_KEY_DEV = os.environ.get("API_USER_KEY_DEV")
STAFF_USER_KEY_DEV = os.environ.get("STAFF_USER_KEY_DEV")
DEVELOPER_USER_KEY_STAGE = os.environ.get('DEVELOPER_USER_KEY_STAGE')
RATING_USER_KEY_STAGE = os.environ.get('RATING_USER_KEY_STAGE')
SUBMISSIONS_USER_KEY_STAGE = os.environ.get('SUBMISSIONS_USER_KEY_STAGE')
API_USER_KEY_STAGE = os.environ.get('API_USER_KEY_STAGE')
STAFF_USER_KEY_STAGE = os.environ.get('STAFF_USER_KEY_STAGE')
DEVELOPER_USER_KEY_STAGE = os.environ.get("DEVELOPER_USER_KEY_STAGE")
RATING_USER_KEY_STAGE = os.environ.get("RATING_USER_KEY_STAGE")
SUBMISSIONS_USER_KEY_STAGE = os.environ.get("SUBMISSIONS_USER_KEY_STAGE")
API_USER_KEY_STAGE = os.environ.get("API_USER_KEY_STAGE")
STAFF_USER_KEY_STAGE = os.environ.get("STAFF_USER_KEY_STAGE")
_email_locator = (By.NAME, 'email')
_continue_locator = (By.CSS_SELECTOR, '.button-row button')
_password_locator = (By.ID, 'password')
_login_btn_locator = (By.ID, 'submit-btn')
_repeat_password_locator = (By.ID, 'vpassword')
_age_locator = (By.ID, 'age')
_code_input_locator = (By.CSS_SELECTOR, '.tooltip-below')
_login_card_header_locator = (By.CSS_SELECTOR, '.card header h1')
_2fa_input_locator = (By.CSS_SELECTOR, '.tooltip-below')
_confirm_2fa_button_locator = (By.ID, 'use-logged-in')
_error_2fa_code_locator = (By.CSS_SELECTOR, '.tooltip-below.invalid')
_email_locator = (By.NAME, "email")
_continue_locator = (By.CSS_SELECTOR, ".button-row button")
_password_locator = (By.ID, "password")
_login_btn_locator = (By.ID, "submit-btn")
_repeat_password_locator = (By.ID, "vpassword")
_age_locator = (By.ID, "age")
_code_input_locator = (By.CSS_SELECTOR, ".tooltip-below")
_login_card_header_locator = (By.CSS_SELECTOR, ".card header h1")
_2fa_input_locator = (By.CSS_SELECTOR, ".tooltip-below")
_confirm_2fa_button_locator = (By.ID, "use-logged-in")
_error_2fa_code_locator = (By.CSS_SELECTOR, ".tooltip-below.invalid")
def account(self, user):
if user == 'reusable_user':
self.fxa_login(self.REUSABLE_USER_EMAIL, self.REUSABLE_USER_PASSWORD, '')
elif user == 'admin':
self.fxa_login(self.ADMIN_USER_EMAIL, self.ADMIN_USER_PASSWORD, '')
elif user == 'developer':
if user == "reusable_user":
self.fxa_login(self.REUSABLE_USER_EMAIL, self.REUSABLE_USER_PASSWORD, "")
elif user == "admin":
self.fxa_login(self.ADMIN_USER_EMAIL, self.ADMIN_USER_PASSWORD, "")
elif user == "developer":
if "dev.allizom" not in self.base_url:
self.fxa_login(self.DEVELOPER_EMAIL, self.DEVELOPER_PASSWORD, self.DEVELOPER_USER_KEY_STAGE)
self.fxa_login(
self.DEVELOPER_EMAIL,
self.DEVELOPER_PASSWORD,
self.DEVELOPER_USER_KEY_STAGE,
)
else:
self.fxa_login(self.DEVELOPER_EMAIL, self.DEVELOPER_PASSWORD, self.DEVELOPER_USER_KEY_DEV)
elif user == 'rating_user':
self.fxa_login(
self.DEVELOPER_EMAIL,
self.DEVELOPER_PASSWORD,
self.DEVELOPER_USER_KEY_DEV,
)
elif user == "rating_user":
if "dev.allizom" not in self.base_url:
self.fxa_login(self.RATING_USER_EMAIL, self.RATING_USER_PASSWORD, self.RATING_USER_KEY_STAGE)
self.fxa_login(
self.RATING_USER_EMAIL,
self.RATING_USER_PASSWORD,
self.RATING_USER_KEY_STAGE,
)
else:
self.fxa_login(self.RATING_USER_EMAIL, self.RATING_USER_PASSWORD, self.RATING_USER_KEY_DEV)
elif user == 'collection_user':
self.fxa_login(self.COLLECTION_USER_EMAIL, self.COLLECTION_USER_PASSWORD, '')
elif user == 'submissions_user':
self.fxa_login(
self.RATING_USER_EMAIL,
self.RATING_USER_PASSWORD,
self.RATING_USER_KEY_DEV,
)
elif user == "collection_user":
self.fxa_login(
self.COLLECTION_USER_EMAIL, self.COLLECTION_USER_PASSWORD, ""
)
elif user == "submissions_user":
if "dev.allizom" not in self.base_url:
self.fxa_login(self.SUBMISSIONS_USER_EMAIL, self.SUBMISSIONS_USER_PASSWORD, self.SUBMISSIONS_USER_KEY_STAGE)
self.fxa_login(
self.SUBMISSIONS_USER_EMAIL,
self.SUBMISSIONS_USER_PASSWORD,
self.SUBMISSIONS_USER_KEY_STAGE,
)
else:
self.fxa_login(self.SUBMISSIONS_USER_EMAIL, self.SUBMISSIONS_USER_PASSWORD, self.SUBMISSIONS_USER_KEY_DEV)
elif user == 'api_user':
self.fxa_login(
self.SUBMISSIONS_USER_EMAIL,
self.SUBMISSIONS_USER_PASSWORD,
self.SUBMISSIONS_USER_KEY_DEV,
)
elif user == "api_user":
if "dev.allizom" not in self.base_url:
self.fxa_login(self.API_USER_EMAIL, self.API_USER_PASSWORD, self.API_USER_KEY_STAGE)
self.fxa_login(
self.API_USER_EMAIL, self.API_USER_PASSWORD, self.API_USER_KEY_STAGE
)
else:
self.fxa_login(self.API_USER_EMAIL, self.API_USER_PASSWORD, self.API_USER_KEY_DEV)
elif user == 'staff_user':
self.fxa_login(
self.API_USER_EMAIL, self.API_USER_PASSWORD, self.API_USER_KEY_DEV
)
elif user == "staff_user":
if "dev.allizom" not in self.base_url:
self.fxa_login(self.STAFF_USER_EMAIL, self.STAFF_USER_PASSWORD, self.STAFF_USER_KEY_STAGE)
self.fxa_login(
self.STAFF_USER_EMAIL,
self.STAFF_USER_PASSWORD,
self.STAFF_USER_KEY_STAGE,
)
else:
self.fxa_login(self.STAFF_USER_EMAIL, self.STAFF_USER_PASSWORD, self.STAFF_USER_KEY_DEV)
elif user == 'restricted_user':
self.fxa_login(self.RESTRICTED_USER_EMAIL, self.RESTRICTED_USER_PASSWORD, '')
self.fxa_login(
self.STAFF_USER_EMAIL,
self.STAFF_USER_PASSWORD,
self.STAFF_USER_KEY_DEV,
)
elif user == "restricted_user":
self.fxa_login(
self.RESTRICTED_USER_EMAIL, self.RESTRICTED_USER_PASSWORD, ""
)
else:
self.fxa_login(self.REGULAR_USER_EMAIL, self.REGULAR_USER_PASSWORD, '')
self.fxa_login(self.REGULAR_USER_EMAIL, self.REGULAR_USER_PASSWORD, "")
def fxa_login(self, email, password, key):
self.find_element(*self._email_locator).send_keys(email)
@ -120,7 +160,7 @@ class Login(Base):
# here, I'm capturing that TimeoutException and trying to push the script to continue to the next steps.
try:
continue_btn = self.wait.until(
EC.element_to_be_clickable((By.ID, 'submit-btn'))
EC.element_to_be_clickable((By.ID, "submit-btn"))
)
continue_btn.click()
except TimeoutException as error:
@ -129,8 +169,8 @@ class Login(Base):
print('The "click continue button" event occurred.')
self.wait.until(
EC.element_to_be_clickable(self._password_locator),
message=f'Password input field not displayed; '
f'FxA card header was {self.find_element(*self._login_card_header_locator).text}',
message=f"Password input field not displayed; "
f"FxA card header was {self.find_element(*self._login_card_header_locator).text}",
)
print(
f'The script should be on the password input screen here. We should see "Sign in" in the header.'
@ -139,18 +179,14 @@ class Login(Base):
self.find_element(*self._password_locator).send_keys(password)
# waits for the password to be filled in
self.wait.until(
EC.invisibility_of_element_located((By.CSS_SELECTOR, '.password.empty')),
message='There was no input added in the password field',
EC.invisibility_of_element_located((By.CSS_SELECTOR, ".password.empty")),
message="There was no input added in the password field",
)
self.find_element(*self._login_btn_locator).click()
# logic for 2fa enabled accounts
if key != '':
self.wait.until(
EC.url_contains('signin_totp_code')
)
self.wait.until(
EC.visibility_of_element_located(self._2fa_input_locator)
)
if key != "":
self.wait.until(EC.url_contains("signin_totp_code"))
self.wait.until(EC.visibility_of_element_located(self._2fa_input_locator))
time.sleep(30)
totp = pyotp.TOTP(key)
self.find_element(*self._2fa_input_locator).send_keys(totp.now())
@ -168,20 +204,20 @@ class Login(Base):
# wait for transition between FxA page and AMO
self.wait.until(
EC.url_contains('addons'),
message=f'AMO could not be loaded in {self.driver.current_url}. '
f'Response status code was {requests.head(self.driver.current_url).status_code}',
EC.url_contains("addons"),
message=f"AMO could not be loaded in {self.driver.current_url}. "
f"Response status code was {requests.head(self.driver.current_url).status_code}",
)
def fxa_register(self):
email = f'{reusables.get_random_string(10)}@restmail.net'
email = f"{reusables.get_random_string(10)}@restmail.net"
password = reusables.get_random_string(10)
self.find_element(*self._email_locator).send_keys(email)
# catching the geckodriver click() issue, in cae it happens here
# issue - https://github.com/mozilla/geckodriver/issues/1608
try:
continue_btn = self.wait.until(
EC.element_to_be_clickable((By.ID, 'submit-btn'))
EC.element_to_be_clickable((By.ID, "submit-btn"))
)
continue_btn.click()
except TimeoutException as error:
@ -190,8 +226,8 @@ class Login(Base):
# verify that the fxa register form was opened
self.wait.until(
EC.element_to_be_clickable(self._password_locator),
message=f'Password input field not displayed; '
f'FxA card header was {self.find_element(*self._login_card_header_locator).text}',
message=f"Password input field not displayed; "
f"FxA card header was {self.find_element(*self._login_card_header_locator).text}",
)
self.find_element(*self._password_locator).send_keys(password)
self.find_element(*self._repeat_password_locator).send_keys(password)
@ -204,7 +240,7 @@ class Login(Base):
self.find_element(*self._login_btn_locator).click()
def get_verification_code(self, mail):
request = requests.get(f'https://restmail.net/mail/{mail}', timeout=10)
request = requests.get(f"https://restmail.net/mail/{mail}", timeout=10)
response = request.json()
# creating a timed loop to address a possible communication delay between
# FxA and restmail; this loop polls the endpoint for 20s to await a response
@ -213,10 +249,10 @@ class Login(Base):
while time.time() < timeout_start + 20:
if response:
verification_code = [
key['headers']['x-verify-short-code'] for key in response
key["headers"]["x-verify-short-code"] for key in response
]
return verification_code
elif not response:
requests.get(f'https://restmail.net/mail/{mail}', timeout=10)
print('Restmail did not receive an email from FxA')
requests.get(f"https://restmail.net/mail/{mail}", timeout=10)
print("Restmail did not receive an email from FxA")
return self

Просмотреть файл

@ -7,25 +7,25 @@ from pages.desktop.base import Base
class Reviews(Base):
_review_count_title_locator = (By.CLASS_NAME, 'AddonReviewList-reviewCount')
_filter_by_score_locator = (By.CLASS_NAME, 'AddonReviewList-filterByScoreSelector')
_user_review_permalink_locator = (By.CSS_SELECTOR, '.FeaturedAddonReview header')
_addon_summary_card_locator = (By.CLASS_NAME, 'AddonSummaryCard')
_featured_review_card_locator = (By.CSS_SELECTOR, '.FeaturedAddonReview-card')
_reviews_list_locator = (By.CSS_SELECTOR, '.AddonReviewList-reviews-listing li')
_editable_rating_stars_locator = (By.CSS_SELECTOR, '.Rating--editable button')
_review_count_title_locator = (By.CLASS_NAME, "AddonReviewList-reviewCount")
_filter_by_score_locator = (By.CLASS_NAME, "AddonReviewList-filterByScoreSelector")
_user_review_permalink_locator = (By.CSS_SELECTOR, ".FeaturedAddonReview header")
_addon_summary_card_locator = (By.CLASS_NAME, "AddonSummaryCard")
_featured_review_card_locator = (By.CSS_SELECTOR, ".FeaturedAddonReview-card")
_reviews_list_locator = (By.CSS_SELECTOR, ".AddonReviewList-reviews-listing li")
_editable_rating_stars_locator = (By.CSS_SELECTOR, ".Rating--editable button")
_score_star_highlight_locator = (
By.CSS_SELECTOR,
'.Rating--editable .Rating-selected-star',
".Rating--editable .Rating-selected-star",
)
_rating_score_bars_locator = (By.CSS_SELECTOR, '.RatingsByStar-barContainer')
_bar_rating_score_locator = (By.CSS_SELECTOR, '.RatingsByStar-star')
_rating_score_bars_locator = (By.CSS_SELECTOR, ".RatingsByStar-barContainer")
_bar_rating_score_locator = (By.CSS_SELECTOR, ".RatingsByStar-star")
def wait_for_page_to_load(self):
"""Waits for various page components to be loaded"""
self.wait.until(
expected.invisibility_of_element_located((By.CLASS_NAME, 'LoadingText')),
message='All reviews page could not be loaded',
expected.invisibility_of_element_located((By.CLASS_NAME, "LoadingText")),
message="All reviews page could not be loaded",
)
return self
@ -36,8 +36,8 @@ class Reviews(Base):
@property
def reviews_title_count(self):
count = self.reviews_page_title
review_count = count.split()[0].replace(' reviews', '')
return int(review_count.replace(',', ''))
review_count = count.split()[0].replace(" reviews", "")
return int(review_count.replace(",", ""))
@property
def filter_by_score(self):
@ -77,9 +77,9 @@ class Reviews(Base):
return self.FeaturedReview(self, el)
class FeaturedReview(Region):
_author_locator = (By.CSS_SELECTOR, '.AddonReviewCard-authorByLine')
_body_locator = (By.CSS_SELECTOR, '.ShowMoreCard-contents > div')
_rating_stars_locator = (By.CSS_SELECTOR, '.Rating--small')
_author_locator = (By.CSS_SELECTOR, ".AddonReviewCard-authorByLine")
_body_locator = (By.CSS_SELECTOR, ".ShowMoreCard-contents > div")
_rating_stars_locator = (By.CSS_SELECTOR, ".Rating--small")
@property
def author(self):
@ -99,36 +99,36 @@ class Reviews(Base):
return [self.UserReview(self, el) for el in items]
class UserReview(Region):
_rating_stars_locator = (By.CSS_SELECTOR, '.Rating--small')
_rating_user_locator = (By.CSS_SELECTOR, '.AddonReviewCard-authorByLine')
_rating_permalink_locator = (By.CSS_SELECTOR, '.AddonReviewCard-authorByLine a')
_rating_stars_locator = (By.CSS_SELECTOR, ".Rating--small")
_rating_user_locator = (By.CSS_SELECTOR, ".AddonReviewCard-authorByLine")
_rating_permalink_locator = (By.CSS_SELECTOR, ".AddonReviewCard-authorByLine a")
_selected_star_locator = (
By.CSS_SELECTOR,
'.UserReview-byLine .Rating-selected-star',
".UserReview-byLine .Rating-selected-star",
)
_review_body_locator = (By.CSS_SELECTOR, '.UserReview-body')
_review_body_locator = (By.CSS_SELECTOR, ".UserReview-body")
_delete_confirm_locator = (
By.CSS_SELECTOR,
'.ConfirmationDialog-confirm-button',
".ConfirmationDialog-confirm-button",
)
_flag_review_button_locator = (By.CSS_SELECTOR, '.FlagReviewMenu-menu')
_flag_review_menu_options = (By.CSS_SELECTOR, '.TooltipMenu-inner button')
_flag_review_success_text = (By.CSS_SELECTOR, '.TooltipMenu-inner li')
_flag_review_button_locator = (By.CSS_SELECTOR, ".FlagReviewMenu-menu")
_flag_review_menu_options = (By.CSS_SELECTOR, ".TooltipMenu-inner button")
_flag_review_success_text = (By.CSS_SELECTOR, ".TooltipMenu-inner li")
_flag_review_login_button = (
By.CSS_SELECTOR,
'.TooltipMenu-list .Button--micro',
".TooltipMenu-list .Button--micro",
)
_reply_to_review_locator = (By.CSS_SELECTOR, '.AddonReviewCard-allControls a')
_reply_to_review_locator = (By.CSS_SELECTOR, ".AddonReviewCard-allControls a")
_review_reply_textarea_locator = (
By.CSS_SELECTOR,
'.DismissibleTextForm-textarea',
".DismissibleTextForm-textarea",
)
_publish_reply_button_locator = (By.CSS_SELECTOR, '.DismissibleTextForm-submit')
_publish_reply_button_locator = (By.CSS_SELECTOR, ".DismissibleTextForm-submit")
_reply_text_locator = (
By.CSS_SELECTOR,
'.AddonReviewCard-reply .ShowMoreCard-contents > div',
".AddonReviewCard-reply .ShowMoreCard-contents > div",
)
_dev_reply_header_locator = (By.CSS_SELECTOR, '.UserReview-reply-header')
_dev_reply_header_locator = (By.CSS_SELECTOR, ".UserReview-reply-header")
@property
def rating_stars(self):
@ -157,9 +157,9 @@ class Reviews(Base):
self.find_element(*self._flag_review_button_locator).click()
self.wait.until(
expected.visibility_of_element_located(
(By.CSS_SELECTOR, '.TooltipMenu-list')
(By.CSS_SELECTOR, ".TooltipMenu-list")
),
message='The flag review menu did not open',
message="The flag review menu did not open",
)
@property
@ -169,9 +169,9 @@ class Reviews(Base):
def select_flag_option(self, count):
self.wait.until(
expected.element_to_be_clickable(
(By.CSS_SELECTOR, '.TooltipMenu-list li:nth-of-type(1)')
(By.CSS_SELECTOR, ".TooltipMenu-list li:nth-of-type(1)")
),
message='Flag menu options were not loaded',
message="Flag menu options were not loaded",
)
# using JavaScriptExecutor to avoid ElementClickInterceptedException
self.driver.execute_script(
@ -179,9 +179,9 @@ class Reviews(Base):
)
self.wait.until(
expected.text_to_be_present_in_element(
self._flag_review_button_locator, 'Flagged'
self._flag_review_button_locator, "Flagged"
),
message='Flag review button state did not change',
message="Flag review button state did not change",
)
@property
@ -192,19 +192,19 @@ class Reviews(Base):
def flag_review_login_button(self):
self.wait.until(
expected.element_to_be_clickable(self._flag_review_login_button),
message='Login button in Flag review menu was not loaded',
message="Login button in Flag review menu was not loaded",
)
return self.find_element(*self._flag_review_login_button)
def click_reply_to_review(self):
reply = self.wait.until(
expected.element_to_be_clickable(self._reply_to_review_locator),
message='Reply button was not loaded',
message="Reply button was not loaded",
)
reply.click()
self.wait.until(
expected.element_to_be_clickable(self._review_reply_textarea_locator),
message='Reply text area was not opened',
message="Reply text area was not opened",
)
def reply_text_input(self, value):
@ -217,7 +217,7 @@ class Reviews(Base):
self.find_element(*self._publish_reply_button_locator).click()
self.wait.until(
expected.visibility_of_element_located(self._dev_reply_header_locator),
message='Developer reply section header was not displayed',
message="Developer reply section header was not displayed",
)
@property

Просмотреть файл

@ -7,29 +7,29 @@ from selenium.webdriver.support import expected_conditions as EC
class Search(Page):
_context_card_locator = (By.CLASS_NAME, 'SearchContextCard-header')
_search_box_locator = (By.CLASS_NAME, 'AutoSearchInput-query')
_submit_button_locator = (By.CLASS_NAME, 'AutoSearchInput-submit-button')
_search_filters_sort_locator = (By.ID, 'SearchFilters-Sort')
_search_filters_type_locator = (By.ID, 'SearchFilters-AddonType')
_search_filters_os_locator = (By.ID, 'SearchFilters-OperatingSystem')
_search_filters_badging_locator = (By.ID, 'SearchFilters-Badging')
_recommended_checkbox_locator = (By.ID, 'SearchFilters-Recommended')
_pagination_next_locator = (By.CSS_SELECTOR, '.Paginate-item--next')
_pagination_previous_locator = (By.CLASS_NAME, 'Paginate-item--previous')
_selected_page_locator = (By.CLASS_NAME, 'Paginate-page-number')
_context_card_locator = (By.CLASS_NAME, "SearchContextCard-header")
_search_box_locator = (By.CLASS_NAME, "AutoSearchInput-query")
_submit_button_locator = (By.CLASS_NAME, "AutoSearchInput-submit-button")
_search_filters_sort_locator = (By.ID, "SearchFilters-Sort")
_search_filters_type_locator = (By.ID, "SearchFilters-AddonType")
_search_filters_os_locator = (By.ID, "SearchFilters-OperatingSystem")
_search_filters_badging_locator = (By.ID, "SearchFilters-Badging")
_recommended_checkbox_locator = (By.ID, "SearchFilters-Recommended")
_pagination_next_locator = (By.CSS_SELECTOR, ".Paginate-item--next")
_pagination_previous_locator = (By.CLASS_NAME, "Paginate-item--previous")
_selected_page_locator = (By.CLASS_NAME, "Paginate-page-number")
def wait_for_page_to_load(self):
self.wait.until(
expected.invisibility_of_element_located((By.CLASS_NAME, 'LoadingText')),
message='Search page could not be loaded',
expected.invisibility_of_element_located((By.CLASS_NAME, "LoadingText")),
message="Search page could not be loaded",
)
return self
def wait_for_contextcard_update(self, value):
self.wait.until(
expected.text_to_be_present_in_element(
(By.CLASS_NAME, 'SearchContextCard-header'), value
(By.CLASS_NAME, "SearchContextCard-header"), value
),
message=f'Expected search term "{value}" not found in "{self.find_element(*self._context_card_locator).text}"',
)
@ -39,11 +39,12 @@ class Search(Page):
contains a certain number of items"""
self.wait.until(
lambda _: len(self.result_list.search_results) >= count,
message=f'Expected search results to be {count} but the list returned {len(self.result_list.search_results)}',
message=f"Expected search results to be {count} but the list returned {len(self.result_list.search_results)}",
)
@property
def results_info(self):
self.wait.until(EC.visibility_of_element_located(self._context_card_locator))
return self.find_element(*self._context_card_locator)
@property
@ -52,46 +53,64 @@ class Search(Page):
@property
def filter_by_sort(self):
self.wait.until(
EC.visibility_of_element_located(self._search_filters_sort_locator)
)
return self.find_element(*self._search_filters_sort_locator)
@property
def filter_by_type(self):
self.wait.until(
EC.visibility_of_element_located(self._search_filters_type_locator)
)
return self.find_element(*self._search_filters_type_locator)
@property
def filter_by_os(self):
self.wait.until(
EC.visibility_of_element_located(self._search_filters_os_locator)
)
return self.find_element(*self._search_filters_os_locator)
@property
def filter_by_badging(self):
self.wait.until(
EC.visibility_of_element_located(self._search_filters_badging_locator)
)
return self.find_element(*self._search_filters_badging_locator)
@property
def recommended_filter(self) -> object:
self.wait.until(
EC.visibility_of_element_located(self._recommended_checkbox_locator)
)
return self.find_element(*self._recommended_checkbox_locator)
def next_page(self):
self.wait.until(EC.visibility_of_element_located(self._pagination_next_locator))
self.find_element(*self._pagination_next_locator).click()
def previous_page(self):
self.wait.until(EC.element_to_be_clickable(self._pagination_previous_locator))
self.find_element(*self._pagination_previous_locator).click()
@property
def page_number(self):
self.wait.until(
lambda _: self.is_element_displayed(*self._selected_page_locator),
message='Pagination items were not loaded',
message="Pagination items were not loaded",
)
return self.find_element(*self._selected_page_locator).text
class SearchResultList(Region):
_result_locator = (By.CLASS_NAME, 'SearchResult')
_result_link_locator = (By.CLASS_NAME, 'SearchResult-link')
_theme_locator = (By.CLASS_NAME, 'SearchResult--theme')
_extension_locator = (By.CLASS_NAME, 'SearchResult-name')
_result_locator = (By.CLASS_NAME, "SearchResult")
_result_link_locator = (By.CLASS_NAME, "SearchResult-link")
_theme_locator = (By.CLASS_NAME, "SearchResult--theme")
_extension_locator = (By.CLASS_NAME, "SearchResult-name")
@property
def search_results(self):
self.wait.until(EC.visibility_of_element_located(self._result_locator))
items = self.find_elements(*self._result_locator)
return [self.ResultListItems(self, el) for el in items]
@ -102,33 +121,41 @@ class Search(Page):
@property
def extension(self):
self.wait.until(EC.visibility_of_element_located(self._extension_locator))
items = self.find_elements(*self._extension_locator)
return [self.ResultListItems(self, el) for el in items]
def click_search_result(self, count):
self.wait.until(EC.element_to_be_clickable(self._result_link_locator))
self.find_elements(*self._result_link_locator)[count].click()
from pages.desktop.frontend.details import Detail
return Detail(self.driver, self.page.base_url).wait_for_page_to_load()
class ResultListItems(Region):
_rating_locator = (By.CSS_SELECTOR, '.Rating--small')
_search_item_name_locator = (By.CSS_SELECTOR, '.SearchResult-link')
_promoted_badge_locator = (By.CSS_SELECTOR, '.PromotedBadge')
_promoted_badge_label_locator = (By.CSS_SELECTOR, '.PromotedBadge-label')
_users_locator = (By.CLASS_NAME, 'SearchResult-users')
_users_number_locator = (By.CLASS_NAME, 'SearchResult-users-text')
_icon_locator = (By.CLASS_NAME, 'SearchResult-icon')
_rating_stars_locator = (By.CLASS_NAME, 'SearchResult-rating')
_author_locator = (By.CLASS_NAME, 'SearchResult-author')
_summary_locator = (By.CLASS_NAME, 'SearchResult-summary')
_rating_locator = (By.CSS_SELECTOR, ".Rating--small")
_search_item_name_locator = (By.CSS_SELECTOR, ".SearchResult-link")
_promoted_badge_locator = (By.CSS_SELECTOR, ".PromotedBadge")
_promoted_badge_label_locator = (By.CSS_SELECTOR, ".PromotedBadge-label")
_users_locator = (By.CLASS_NAME, "SearchResult-users")
_users_number_locator = (By.CLASS_NAME, "SearchResult-users-text")
_icon_locator = (By.CLASS_NAME, "SearchResult-icon")
_rating_stars_locator = (By.CLASS_NAME, "SearchResult-rating")
_author_locator = (By.CLASS_NAME, "SearchResult-author")
_summary_locator = (By.CLASS_NAME, "SearchResult-summary")
@property
def search_name(self):
self.wait.until(
EC.visibility_of_element_located(self._search_item_name_locator)
)
return self.find_element(*self._search_item_name_locator)
@property
def name(self):
self.wait.until(
EC.visibility_of_element_located(self._search_item_name_locator)
)
return self.find_element(*self._search_item_name_locator).text
def link(self):
@ -143,43 +170,57 @@ class Search(Page):
@property
def users(self):
self.wait.until(
EC.visibility_of_element_located(self._users_number_locator)
)
users = self.find_element(*self._users_number_locator).text
return int(users.split()[0].replace(',', '').replace('users', ''))
return int(users.split()[0].replace(",", "").replace("users", ""))
@property
def rating(self):
"""Returns the rating"""
rating = self.find_element(*self._rating_locator).get_property('title')
self.wait.until(EC.visibility_of_element_located(self._rating_locator))
rating = self.find_element(*self._rating_locator).get_property("title")
return float(rating.split()[1])
@property
def search_result_icon(self):
self.wait.until(EC.visibility_of_element_located(self._icon_locator))
return self.find_element(*self._icon_locator)
@property
def search_result_rating_stars(self):
self.wait.until(
EC.visibility_of_element_located(self._rating_stars_locator)
)
return self.find_element(*self._rating_stars_locator)
@property
def search_result_author(self):
self.wait.until(EC.visibility_of_element_located(self._author_locator))
return self.find_element(*self._author_locator)
@property
def search_result_users(self):
self.wait.until(EC.visibility_of_element_located(self._users_locator))
return self.find_element(*self._users_locator)
@property
def search_result_summary(self):
self.wait.until(EC.visibility_of_element_located(self._summary_locator))
return self.find_element(*self._summary_locator)
@property
def promoted_badge(self):
WebDriverWait(self.driver, 10).until(
EC.visibility_of_element_located(self._promoted_badge_locator),
message='Promoted badge was not found for these search results',
message="Promoted badge was not found for these search results",
)
return self
@property
def promoted_badge_label(self):
self.wait.until(
EC.visibility_of_element_located(self._promoted_badge_label_locator)
)
return self.find_element(*self._promoted_badge_label_locator).text

Просмотреть файл

@ -9,70 +9,89 @@ class StaticPages(Base):
"""This class will store informative pages, such as 404 pages, server error pages
and other pages that have only an informative scope"""
_not_found_page_locator = (By.CSS_SELECTOR, '.NotFound')
_notice_text_locator = (By.CSS_SELECTOR, '.Notice-warning')
_page_header_locator = (By.CSS_SELECTOR, '.Card-header-text')
_content_locator = (By.CSS_SELECTOR, '.Card-contents')
_content_card_links_locator = (By.CSS_SELECTOR, '.Card-contents a')
_not_found_page_locator = (By.CSS_SELECTOR, ".NotFound")
_notice_text_locator = (By.CSS_SELECTOR, ".Notice-warning")
_page_header_locator = (By.CSS_SELECTOR, ".Card-header-text")
_content_locator = (By.CSS_SELECTOR, ".Card-contents")
_content_card_links_locator = (By.CSS_SELECTOR, ".Card-contents a")
# ------- Review Guidelines page
_review_guidelines_page_forum_link_locator = (By.CSS_SELECTOR, 'section > p > a')
_review_guidelines_page_forum_link_locator = (By.CSS_SELECTOR, "section > p > a")
# ------- About Firefox Add-ons page
_thunderbird_link_locator = (By.CSS_SELECTOR, '#about > p > a:nth-child(1)')
_seamonkey_link_locator = (By.CSS_SELECTOR, '#about > p > a:nth-child(2)')
_thunderbird_link_locator = (By.CSS_SELECTOR, "#about > p > a:nth-child(1)")
_seamonkey_link_locator = (By.CSS_SELECTOR, "#about > p > a:nth-child(2)")
_get_involved_links_locator = (
By.CSS_SELECTOR,
'.Card-contents section > ul > li > a',
".Card-contents section > ul > li > a",
)
# ------- Blocked Add-on page
_blocked_addon_page_links_locator = (By.CSS_SELECTOR, '.Card-contents > p > a')
_blocked_addon_page_links_locator = (By.CSS_SELECTOR, ".Card-contents > p > a")
# ------- Login Expired page
_reload_the_page_link_locator = (By.CSS_SELECTOR, '.ReloadPageLink')
_reload_the_page_link_locator = (By.CSS_SELECTOR, ".ReloadPageLink")
def wait_for_page_to_load(self):
"""Waits for various page components to be loaded"""
self.wait.until(
EC.invisibility_of_element_located((By.CLASS_NAME, 'LoadingText')),
EC.invisibility_of_element_located((By.CLASS_NAME, "LoadingText")),
message="The requested page could not be loaded",
)
return self
@property
def not_found_page(self):
self.wait.until(EC.visibility_of_element_located(self._not_found_page_locator))
return self.find_element(*self._not_found_page_locator)
@property
def notice_messages(self):
self.wait.until(EC.visibility_of_element_located(self._notice_text_locator))
return self.find_elements(*self._notice_text_locator)
@property
def page_header(self):
self.wait.until(EC.visibility_of_element_located(self._page_header_locator))
return self.find_element(*self._page_header_locator).text
@property
def content(self):
self.wait.until(EC.visibility_of_element_located(self._content_locator))
return self.find_element(*self._content_locator)
# ------- Review Guidelines page
@property
def forum_link(self):
self.wait.until(
EC.visibility_of_element_located(
self._review_guidelines_page_forum_link_locator
)
)
return self.find_element(*self._review_guidelines_page_forum_link_locator)
# ------- About Firefox Add-ons page
@property
def page_links(self):
self.wait.until(
EC.visibility_of_element_located(self._content_card_links_locator)
)
return self.find_elements(*self._content_card_links_locator)
@property
def thunderbird_link(self):
self.wait.until(
EC.visibility_of_element_located(self._thunderbird_link_locator)
)
return self.find_element(*self._thunderbird_link_locator)
@property
def seamonkey_link(self):
self.wait.until(EC.visibility_of_element_located(self._seamonkey_link_locator))
return self.find_element(*self._seamonkey_link_locator)
@property
def get_involved_links(self):
# add all the links except 'wiki'
self.wait.until(
EC.visibility_of_element_located(self._get_involved_links_locator)
)
links = self.find_elements(*self._get_involved_links_locator)
# add the 'wiki' link
links.append(self.find_elements(*self._content_card_links_locator)[10])
@ -80,34 +99,53 @@ class StaticPages(Base):
@property
def report_an_issue_links(self):
self.wait.until(
EC.visibility_of_element_located(self._content_card_links_locator)
)
return self.find_elements(*self._content_card_links_locator)[11:15]
@property
def get_support_links(self):
self.wait.until(
EC.visibility_of_element_located(self._content_card_links_locator)
)
return self.find_elements(*self._content_card_links_locator)[15:]
# ------- Blocked Add-on page
@property
def addon_policies_link(self):
self.wait.until(
EC.visibility_of_element_located(self._blocked_addon_page_links_locator)
)
return self.find_elements(*self._blocked_addon_page_links_locator)[0]
@property
def certain_criteria_link(self):
self.wait.until(
EC.visibility_of_element_located(self._blocked_addon_page_links_locator)
)
return self.find_elements(*self._blocked_addon_page_links_locator)[1]
@property
def this_support_article_link(self):
self.wait.until(
EC.visibility_of_element_located(self._blocked_addon_page_links_locator)
)
return self.find_elements(*self._blocked_addon_page_links_locator)[2]
# ------- Login Expired page
@property
def logged_out_notice_message(self):
self.wait.until(
EC.visibility_of_element_located(self._logged_out_notice_locator)
)
return self.find_element(*self._logged_out_notice_locator)
@property
def click_reload_page_link(self):
self.wait.until(EC.element_to_be_clickable(self._reload_the_page_link_locator))
self.find_element(*self._reload_the_page_link_locator).click()
self.wait.until(
EC.visibility_of_element_located((By.CSS_SELECTOR, '.AddonTitle'))
EC.visibility_of_element_located((By.CSS_SELECTOR, ".AddonTitle"))
)
return Detail(self.driver, self.base_url)

Просмотреть файл

@ -3,13 +3,14 @@ from selenium.webdriver.common.by import By
from pages.desktop.base import Base
from regions.desktop.categories import Categories
from regions.desktop.shelves import Shelves
from selenium.webdriver.support import expected_conditions as EC
class Themes(Base):
URL_TEMPLATE = 'themes/'
URL_TEMPLATE = "themes/"
_title_locator = (By.CLASS_NAME, 'LandingPage-addonType-name')
_header_summary_locator = (By.CSS_SELECTOR, '.LandingPage-header p')
_title_locator = (By.CLASS_NAME, "LandingPage-addonType-name")
_header_summary_locator = (By.CSS_SELECTOR, ".LandingPage-header p")
def wait_for_page_to_load(self):
self.wait.until(lambda _: self.is_element_displayed(*self._title_locator))
@ -17,10 +18,12 @@ class Themes(Base):
@property
def title(self):
self.wait.until(EC.visibility_of_element_located(self._title_locator))
return self.find_element(*self._title_locator).text
@property
def header_summary(self):
self.wait.until(EC.visibility_of_element_located(self._header_summary_locator))
return self.find_element(*self._header_summary_locator).text
@property

Просмотреть файл

@ -14,14 +14,14 @@ from pages.desktop.frontend.search import Search
class User(Base):
URL_TEMPLATE = '/users/edit'
URL_TEMPLATE = "/users/edit"
_display_name_locator = (By.CLASS_NAME, 'UserProfile-name')
_display_name_locator = (By.CLASS_NAME, "UserProfile-name")
def wait_for_page_to_load(self):
"""Waits for various page components to be loaded"""
self.wait.until(
EC.invisibility_of_element_located((By.CLASS_NAME, 'LoadingText'))
EC.invisibility_of_element_located((By.CLASS_NAME, "LoadingText"))
)
return self
@ -33,6 +33,7 @@ class User(Base):
@property
def user_display_name(self):
self.wait.until(EC.visibility_of_element_located(self._display_name_locator))
return self.find_element(*self._display_name_locator)
@property
@ -44,100 +45,103 @@ class User(Base):
return self.EditProfile(self)
class ViewProfile(Region):
_user_icon_placeholder_locator = (By.CSS_SELECTOR, '.Icon-anonymous-user')
_user_profile_image_locator = (By.CSS_SELECTOR, '.UserAvatar-image')
_user_developer_role_locator = (By.CSS_SELECTOR, '.UserProfile-developer')
_user_developer_role_icon_locator = (By.CSS_SELECTOR, '.Icon-developer')
_user_artist_role_locator = (By.CSS_SELECTOR, '.UserProfile-artist')
_user_artist_role_icon_locator = (By.CSS_SELECTOR, '.Icon-artist')
_user_homepage_locator = (By.CSS_SELECTOR, '.UserProfile-homepage a')
_user_location_locator = (By.CSS_SELECTOR, '.UserProfile-location')
_user_occupation_locator = (By.CSS_SELECTOR, '.UserProfile-occupation')
_user_creation_date_locator = (By.CSS_SELECTOR, '.UserProfile-user-since')
_user_addons_count_locator = (By.CSS_SELECTOR, '.UserProfile-number-of-addons')
_user_icon_placeholder_locator = (By.CSS_SELECTOR, ".Icon-anonymous-user")
_user_profile_image_locator = (By.CSS_SELECTOR, ".UserAvatar-image")
_user_developer_role_locator = (By.CSS_SELECTOR, ".UserProfile-developer")
_user_developer_role_icon_locator = (By.CSS_SELECTOR, ".Icon-developer")
_user_artist_role_locator = (By.CSS_SELECTOR, ".UserProfile-artist")
_user_artist_role_icon_locator = (By.CSS_SELECTOR, ".Icon-artist")
_user_homepage_locator = (By.CSS_SELECTOR, ".UserProfile-homepage a")
_user_location_locator = (By.CSS_SELECTOR, ".UserProfile-location")
_user_occupation_locator = (By.CSS_SELECTOR, ".UserProfile-occupation")
_user_creation_date_locator = (By.CSS_SELECTOR, ".UserProfile-user-since")
_user_addons_count_locator = (By.CSS_SELECTOR, ".UserProfile-number-of-addons")
_user_addon_average_rating_locator = (
By.CSS_SELECTOR,
'.UserProfile-rating-average',
".UserProfile-rating-average",
)
_user_biography_locator = (By.CSS_SELECTOR, '.UserProfile-biography')
_user_profile_edit_link_locator = (By.CSS_SELECTOR, '.UserProfile-edit-link')
_user_extensions_card_locator = (By.CSS_SELECTOR, '.AddonsCard--vertical')
_user_biography_locator = (By.CSS_SELECTOR, ".UserProfile-biography")
_user_profile_edit_link_locator = (By.CSS_SELECTOR, ".UserProfile-edit-link")
_user_extensions_card_locator = (By.CSS_SELECTOR, ".AddonsCard--vertical")
_user_extensions_card_header_locator = (
By.CSS_SELECTOR,
'.AddonsCard--vertical .Card-header-text',
".AddonsCard--vertical .Card-header-text",
)
_user_extensions_results_locator = (
By.CSS_SELECTOR,
'.AddonsCard--vertical .SearchResult',
".AddonsCard--vertical .SearchResult",
)
_user_themes_card_locator = (By.CSS_SELECTOR, '.AddonsByAuthorsCard--theme')
_user_themes_card_locator = (By.CSS_SELECTOR, ".AddonsByAuthorsCard--theme")
_user_themes_card_header_locator = (
By.CSS_SELECTOR,
'.AddonsByAuthorsCard--theme .Card-header-text',
".AddonsByAuthorsCard--theme .Card-header-text",
)
_user_themes_results_locator = (By.CLASS_NAME, 'SearchResult--theme')
_user_reviews_card_locator = (By.CSS_SELECTOR, '.UserProfile-reviews')
_user_themes_results_locator = (By.CLASS_NAME, "SearchResult--theme")
_user_reviews_card_locator = (By.CSS_SELECTOR, ".UserProfile-reviews")
_extensions_pagination_locator = (
By.CSS_SELECTOR,
'.AddonsCard--vertical .Paginate',
".AddonsCard--vertical .Paginate",
)
_extensions_next_page_locator = (
By.CSS_SELECTOR,
'.AddonsCard--vertical .Paginate-item--next',
".AddonsCard--vertical .Paginate-item--next",
)
_extensions_page_number_locator = (
By.CSS_SELECTOR,
'.AddonsCard--vertical .Paginate-page-number',
".AddonsCard--vertical .Paginate-page-number",
)
_themes_pagination_locator = (
By.CSS_SELECTOR,
'.AddonsByAuthorsCard--theme .Paginate',
".AddonsByAuthorsCard--theme .Paginate",
)
_themes_next_page_locator = (
By.CSS_SELECTOR,
'.AddonsByAuthorsCard--theme .Paginate-item--next',
".AddonsByAuthorsCard--theme .Paginate-item--next",
)
_themes_page_number_locator = (
By.CSS_SELECTOR,
'.AddonsByAuthorsCard--theme .Paginate-page-number',
".AddonsByAuthorsCard--theme .Paginate-page-number",
)
_user_review_list_locator = (By.CSS_SELECTOR, '.AddonReviewCard-viewOnly')
_user_review_list_locator = (By.CSS_SELECTOR, ".AddonReviewCard-viewOnly")
_user_abuse_report_button_locator = (
By.CSS_SELECTOR,
'.ReportUserAbuse-show-more',
".ReportUserAbuse-show-more",
)
_abuse_report_form_header_locator = (By.CSS_SELECTOR, '.ReportUserAbuse-header')
_abuse_report_form_header_locator = (By.CSS_SELECTOR, ".ReportUserAbuse-header")
_abuse_report_form_help_text = (
By.CSS_SELECTOR,
'.ReportUserAbuse-form p:nth-child(2)',
".ReportUserAbuse-form p:nth-child(2)",
)
_abuse_report_form_additional_help_text = (
By.CSS_SELECTOR,
'.ReportUserAbuse-form p:nth-child(3)',
".ReportUserAbuse-form p:nth-child(3)",
)
_abuse_report_textarea_locator = (
By.CSS_SELECTOR,
'.DismissibleTextForm-textarea',
".DismissibleTextForm-textarea",
)
_abuse_report_cancel_button_locator = (
By.CSS_SELECTOR,
'.DismissibleTextForm-dismiss',
".DismissibleTextForm-dismiss",
)
_abuse_report_submit_disabled_button_locator = (
By.CSS_SELECTOR,
'.DismissibleTextForm-submit.Button--disabled',
".DismissibleTextForm-submit.Button--disabled",
)
_abuse_report_submit_button_locator = (
By.CSS_SELECTOR,
'.DismissibleTextForm-submit',
".DismissibleTextForm-submit",
)
_abuse_report_confirm_message_locator = (
By.CSS_SELECTOR,
'.ReportUserAbuse--report-sent p:nth-child(2)',
".ReportUserAbuse--report-sent p:nth-child(2)",
)
@property
def user_profile_icon_placeholder(self):
self.wait.until(
EC.visibility_of_element_located(self._user_icon_placeholder_locator)
)
return self.find_element(*self._user_icon_placeholder_locator)
@property
@ -146,49 +150,78 @@ class User(Base):
@property
def icon_source(self):
self.wait.until(
EC.visibility_of_element_located(self._user_profile_image_locator)
)
return self.user_profile_icon.get_attribute('src')
return self.user_profile_icon.get_attribute("src")
@property
def developer_role(self):
self.wait.until(
EC.visibility_of_element_located(self._user_developer_role_locator)
)
return self.find_element(*self._user_developer_role_locator)
@property
def developer_role_icon(self):
self.wait.until(
EC.visibility_of_element_located(self._user_developer_role_icon_locator)
)
return self.find_element(*self._user_developer_role_icon_locator)
@property
def artist_role(self):
self.wait.until(
EC.visibility_of_element_located(self._user_artist_role_locator)
)
return self.find_element(*self._user_artist_role_locator)
@property
def artist_role_icon(self):
self.wait.until(
EC.visibility_of_element_located(self._user_artist_role_icon_locator)
)
return self.find_element(*self._user_artist_role_icon_locator)
@property
def user_homepage(self):
return self.find_element(*self._user_homepage_locator).get_attribute('href')
self.wait.until(
EC.visibility_of_element_located(self._user_homepage_locator)
)
return self.find_element(*self._user_homepage_locator).get_attribute("href")
@property
def user_location(self):
self.wait.until(
EC.visibility_of_element_located(self._user_location_locator)
)
return self.find_element(*self._user_location_locator).text
@property
def user_occupation(self):
self.wait.until(
EC.visibility_of_element_located(self._user_occupation_locator)
)
return self.find_element(*self._user_occupation_locator).text
@property
def user_profile_creation_date(self):
self.wait.until(
EC.visibility_of_element_located(self._user_creation_date_locator)
)
return self.find_element(*self._user_creation_date_locator)
@property
def user_addons_number(self):
self.wait.until(
EC.visibility_of_element_located(self._user_addons_count_locator)
)
return self.find_element(*self._user_addons_count_locator)
@property
def user_addons_average_rating(self):
self.wait.until(
EC.visibility_of_element_located(
self._user_addon_average_rating_locator
)
)
return self.find_element(*self._user_addon_average_rating_locator)
@property
@ -197,23 +230,40 @@ class User(Base):
@property
def edit_profile_button(self):
self.wait.until(
EC.visibility_of_element_located(self._user_profile_edit_link_locator)
)
return self.find_element(*self._user_profile_edit_link_locator)
def click_edit_profile_button(self):
self.wait.until(
EC.element_to_be_clickable(self._user_profile_edit_link_locator)
)
self.find_element(*self._user_profile_edit_link_locator).click()
return User(self.driver, self.page.base_url).wait_for_page_to_load()
@property
def user_extensions(self):
self.wait.until(
EC.visibility_of_element_located(self._user_extensions_card_locator)
)
self.find_element(*self._user_extensions_card_locator)
return Search(self.driver, self.page.base_url).wait_for_page_to_load()
@property
def user_extensions_card_header(self):
self.wait.until(
EC.visibility_of_element_located(
self._user_extensions_card_header_locator
)
)
return self.find_element(*self._user_extensions_card_header_locator).text
@property
def user_extensions_results(self):
self.wait.until(
EC.visibility_of_element_located(self._user_extensions_results_locator)
)
items = self.find_elements(*self._user_extensions_results_locator)
return [
Search(
@ -224,15 +274,24 @@ class User(Base):
@property
def user_themes(self):
self.wait.until(
EC.visibility_of_element_located(self._user_themes_card_locator)
)
self.find_element(*self._user_themes_card_locator)
return Search(self.driver, self.page.base_url).wait_for_page_to_load()
@property
def user_themes_card_header(self):
self.wait.until(
EC.visibility_of_element_located(self._user_themes_card_header_locator)
)
return self.find_element(*self._user_themes_card_header_locator).text
@property
def user_themes_results(self):
self.wait.until(
EC.visibility_of_element_located(self._user_themes_results_locator)
)
items = self.find_elements(*self._user_themes_results_locator)
return [
Search(
@ -243,14 +302,23 @@ class User(Base):
@property
def extensions_pagination(self):
self.wait.until(
EC.visibility_of_element_located(self._extensions_pagination_locator)
)
return self.find_element(*self._extensions_pagination_locator)
def extensions_next_page(self):
self.wait.until(
EC.element_to_be_clickable(self._extensions_next_page_locator)
)
self.find_element(*self._extensions_next_page_locator).click()
return User(self.driver, self.page.base_url).wait_for_page_to_load()
@property
def extensions_page_number(self):
self.wait.until(
EC.visibility_of_element_located(self._extensions_page_number_locator)
)
return self.find_element(*self._extensions_page_number_locator).text
@property
@ -258,11 +326,15 @@ class User(Base):
return self.find_element(*self._themes_pagination_locator)
def themes_next_page(self):
self.wait.until(EC.element_to_be_clickable(self._themes_next_page_locator))
self.find_element(*self._themes_next_page_locator).click()
return User(self.driver, self.page.base_url).wait_for_page_to_load()
@property
def themes_page_number(self):
self.wait.until(
EC.visibility_of_element_located(self._themes_page_number_locator)
)
return self.find_element(*self._themes_page_number_locator).text
def user_reviews_section_loaded(self):
@ -277,6 +349,9 @@ class User(Base):
return [reviews.UserReview(self, el) for el in items]
def click_user_abuse_report(self):
self.wait.until(
EC.element_to_be_clickable(self._user_abuse_report_button_locator)
)
self.find_element(*self._user_abuse_report_button_locator).click()
self.wait.until(
EC.element_to_be_clickable(self._abuse_report_textarea_locator)
@ -284,20 +359,37 @@ class User(Base):
@property
def abuse_report_form_header(self):
self.wait.until(
EC.visibility_of_element_located(self._abuse_report_form_header_locator)
)
return self.find_element(*self._abuse_report_form_header_locator).text
@property
def abuse_report_form_help_text(self):
self.wait.until(
EC.visibility_of_element_located(self._abuse_report_form_help_text)
)
return self.find_element(*self._abuse_report_form_help_text).text
@property
def abuse_report_form_additional_help_text(self):
self.wait.until(
EC.visibility_of_element_located(
self._abuse_report_form_additional_help_text
)
)
return self.find_element(*self._abuse_report_form_additional_help_text).text
def user_abuse_report_input_text(self, value):
self.wait.until(
EC.visibility_of_element_located(self._abuse_report_textarea_locator)
)
self.find_element(*self._abuse_report_textarea_locator).send_keys(value)
def cancel_abuse_report_form(self):
self.wait.until(
EC.element_to_be_clickable(self._abuse_report_cancel_button_locator)
)
self.find_element(*self._abuse_report_cancel_button_locator).click()
self.wait.until(
EC.element_to_be_clickable(self._user_abuse_report_button_locator)
@ -305,9 +397,17 @@ class User(Base):
@property
def abuse_report_submit_disabled(self):
self.wait.until(
EC.visibility_of_element_located(
self._abuse_report_submit_disabled_button_locator
)
)
return self.find_element(*self._abuse_report_submit_disabled_button_locator)
def submit_user_abuse_report(self):
self.wait.until(
EC.element_to_be_clickable(self._abuse_report_submit_button_locator)
)
self.find_element(*self._abuse_report_submit_button_locator).click()
self.wait.until(
EC.invisibility_of_element_located(
@ -317,90 +417,95 @@ class User(Base):
@property
def user_abuse_confirmation_message(self):
self.wait.until(
EC.visibility_of_element_located(
self._abuse_report_confirm_message_locator
)
)
return self.find_element(*self._abuse_report_confirm_message_locator).text
class EditProfile(Region):
_view_profile_link_locator = (By.CSS_SELECTOR, '.UserProfileEdit-user-links a')
_user_email_locator = (By.CSS_SELECTOR, '.UserProfileEdit-email')
_view_profile_link_locator = (By.CSS_SELECTOR, ".UserProfileEdit-user-links a")
_user_email_locator = (By.CSS_SELECTOR, ".UserProfileEdit-email")
_user_email_help_text_locator = (
By.CSS_SELECTOR,
'.UserProfileEdit-email--help',
".UserProfileEdit-email--help",
)
_user_email_help_link_locator = (
By.CSS_SELECTOR,
'.UserProfileEdit-email--help a',
".UserProfileEdit-email--help a",
)
_fxa_account_link_locator = (
By.CSS_SELECTOR,
'.UserProfileEdit-manage-account-link',
".UserProfileEdit-manage-account-link",
)
_edit_display_name_locator = (By.CSS_SELECTOR, '.UserProfileEdit-displayName')
_edit_homepage_locator = (By.CSS_SELECTOR, '.UserProfileEdit-homepage')
_edit_location_locator = (By.CSS_SELECTOR, '.UserProfileEdit-location')
_edit_occupation_locator = (By.CSS_SELECTOR, '.UserProfileEdit-occupation')
_profile_picture_placeholder_locator = (By.CSS_SELECTOR, '.Icon-anonymous-user')
_edit_display_name_locator = (By.CSS_SELECTOR, ".UserProfileEdit-displayName")
_edit_homepage_locator = (By.CSS_SELECTOR, ".UserProfileEdit-homepage")
_edit_location_locator = (By.CSS_SELECTOR, ".UserProfileEdit-location")
_edit_occupation_locator = (By.CSS_SELECTOR, ".UserProfileEdit-occupation")
_profile_picture_placeholder_locator = (By.CSS_SELECTOR, ".Icon-anonymous-user")
_upload_picture_button_locator = (
By.CSS_SELECTOR,
'.UserProfileEditPicture-file-input',
".UserProfileEditPicture-file-input",
)
_uploaded_profile_picture_locator = (By.CSS_SELECTOR, '.UserAvatar-image')
_uploaded_profile_picture_locator = (By.CSS_SELECTOR, ".UserAvatar-image")
_delete_picture_button_locator = (
By.CSS_SELECTOR,
'.UserProfileEditPicture-delete-button button',
".UserProfileEditPicture-delete-button button",
)
_cancel_delete_picture_locator = (
By.CSS_SELECTOR,
'.ConfirmationDialog-cancel-button',
".ConfirmationDialog-cancel-button",
)
_confirm_delete_picture_locator = (
By.CSS_SELECTOR,
'.ConfirmationDialog-confirm-button',
".ConfirmationDialog-confirm-button",
)
_picture_delete_success_text_locator = (By.CSS_SELECTOR, '.Notice-success p')
_edit_biography_locator = (By.CSS_SELECTOR, '.UserProfileEdit-biography')
_picture_delete_success_text_locator = (By.CSS_SELECTOR, ".Notice-success p")
_edit_biography_locator = (By.CSS_SELECTOR, ".UserProfileEdit-biography")
_notifications_info_text_locator = (
By.CSS_SELECTOR,
'.UserProfileEdit-notifications-aside',
".UserProfileEdit-notifications-aside",
)
_notification_checkbox_locator = (
By.CSS_SELECTOR,
'.UserProfileEditNotification-input',
".UserProfileEditNotification-input",
)
_notification_text_locator = (
By.CSS_SELECTOR,
'.UserProfileEditNotification label',
".UserProfileEditNotification label",
)
_notifications_help_text_locator = (
By.CSS_SELECTOR,
'.UserProfileEdit-notifications--help',
".UserProfileEdit-notifications--help",
)
_edit_profile_submit_disabled_button_locator = (
By.CSS_SELECTOR,
'.UserProfileEdit-submit-button.Button--disabled',
".UserProfileEdit-submit-button.Button--disabled",
)
_edit_profile_submit_button_locator = (
By.CSS_SELECTOR,
'.UserProfileEdit-submit-button',
".UserProfileEdit-submit-button",
)
_delete_profile_button_locator = (
By.CSS_SELECTOR,
'.UserProfileEdit-delete-button',
".UserProfileEdit-delete-button",
)
_delete_profile_overlay_locator = (
By.CSS_SELECTOR,
'.UserProfileEdit-deletion-modal',
".UserProfileEdit-deletion-modal",
)
_cancel_delete_profile_button_locator = (
By.CSS_SELECTOR,
'.UserProfileEdit-deletion-modal .UserProfileEdit-cancel-button',
".UserProfileEdit-deletion-modal .UserProfileEdit-cancel-button",
)
_confirm_delete_profile_button_locator = (
By.CSS_SELECTOR,
'.UserProfileEdit-deletion-modal .UserProfileEdit-confirm-button',
".UserProfileEdit-deletion-modal .UserProfileEdit-confirm-button",
)
_invalid_url_error_text_locator = (
By.CSS_SELECTOR,
'.Notice-error .Notice-text',
".Notice-error .Notice-text",
)
def click_view_profile_link(self):
@ -409,62 +514,99 @@ class User(Base):
).until(EC.element_to_be_clickable(self._view_profile_link_locator))
link.click()
self.wait.until(
EC.visibility_of_element_located((By.CSS_SELECTOR, '.UserProfile-name'))
EC.visibility_of_element_located((By.CSS_SELECTOR, ".UserProfile-name"))
)
@property
def email_field(self):
return self.find_element(*self._user_email_locator).get_attribute('value')
self.wait.until(EC.visibility_of_element_located(self._user_email_locator))
return self.find_element(*self._user_email_locator).get_attribute("value")
@property
def email_field_help_text(self):
self.wait.until(
EC.visibility_of_element_located(self._user_email_help_text_locator)
)
return self.find_element(*self._user_email_help_text_locator).text
def email_field_help_link(self):
self.wait.until(
EC.element_to_be_clickable(self._user_email_help_link_locator)
)
self.find_element(*self._user_email_help_link_locator).click()
# waits for the fxa support page to be opened
self.wait.until(
EC.visibility_of_element_located(
(By.CSS_SELECTOR, '.sumo-page-heading')
(By.CSS_SELECTOR, ".sumo-page-heading")
)
)
def link_to_fxa_account(self):
self.wait.until(EC.element_to_be_clickable(self._fxa_account_link_locator))
self.find_element(*self._fxa_account_link_locator).click()
# waits for the fxa account page to be opened - check logo visibility
self.wait.until(
EC.visibility_of_element_located((By.CSS_SELECTOR, '.flex h1 span'))
EC.visibility_of_element_located((By.CSS_SELECTOR, ".flex h1 span"))
)
def display_name(self, value):
self.wait.until(
EC.visibility_of_element_located(self._edit_display_name_locator)
)
self.find_element(*self._edit_display_name_locator).send_keys(value)
@property
def display_name_field(self):
self.wait.until(
EC.visibility_of_element_located(self._edit_display_name_locator)
)
return self.find_element(*self._edit_display_name_locator)
def homepage_link(self, value):
self.wait.until(
EC.visibility_of_element_located(self._edit_homepage_locator)
)
self.find_element(*self._edit_homepage_locator).send_keys(value)
@property
def homepage_link_field(self):
self.wait.until(
EC.visibility_of_element_located(self._edit_homepage_locator)
)
return self.find_element(*self._edit_homepage_locator)
def location(self, value):
self.wait.until(
EC.visibility_of_element_located(self._edit_location_locator)
)
self.find_element(*self._edit_location_locator).send_keys(value)
@property
def location_field(self):
self.wait.until(
EC.visibility_of_element_located(self._edit_location_locator)
)
return self.find_element(*self._edit_location_locator)
def occupation(self, value):
self.wait.until(
EC.visibility_of_element_located(self._edit_occupation_locator)
)
self.find_element(*self._edit_occupation_locator).send_keys(value)
@property
def profile_avatar_placeholder(self):
self.wait.until(
EC.visibility_of_element_located(
self._profile_picture_placeholder_locator
)
)
return self.find_element(*self._profile_picture_placeholder_locator)
def upload_picture(self, image):
self.wait.until(
EC.visibility_of_element_located(self._upload_picture_button_locator)
)
button = self.find_element(*self._upload_picture_button_locator)
path = Path(os.getcwd())
img = str(path / "img" / image)
@ -480,9 +622,12 @@ class User(Base):
@property
def picture_source(self):
self.wait.until(
EC.visibility_of_element_located(self._uploaded_profile_picture_locator)
)
return self.find_element(
*self._uploaded_profile_picture_locator
).get_attribute('src')
).get_attribute("src")
def delete_profile_picture(self):
self.find_element(*self._delete_picture_button_locator).click()
@ -491,12 +636,18 @@ class User(Base):
)
def cancel_delete_picture(self):
self.wait.until(
EC.element_to_be_clickable(self._cancel_delete_picture_locator)
)
self.find_element(*self._cancel_delete_picture_locator).click()
self.wait.until(
EC.element_to_be_clickable(self._delete_picture_button_locator)
)
def confirm_delete_picture(self):
self.wait.until(
EC.element_to_be_clickable(self._confirm_delete_picture_locator)
)
self.find_element(*self._confirm_delete_picture_locator).click()
self.wait.until(
EC.visibility_of_element_located(
@ -506,13 +657,24 @@ class User(Base):
@property
def picture_delete_success_message(self):
self.wait.until(
EC.visibility_of_element_located(
self._picture_delete_success_text_locator
)
)
return self.find_element(*self._picture_delete_success_text_locator).text
def biography(self, value):
self.wait.until(
EC.visibility_of_element_located(self._edit_biography_locator)
)
self.find_element(*self._edit_biography_locator).send_keys(value)
@property
def notifications_info_text(self):
self.wait.until(
EC.visibility_of_element_located(self._notifications_info_text_locator)
)
return self.find_element(*self._notifications_info_text_locator).text
@property
@ -520,22 +682,28 @@ class User(Base):
"""function used for developer notifications"""
self.wait.until(
lambda _: len(self.notification_text) == 8,
message=f'There were {len(self.notification_text)} notifications displayed, expected 8',
message=f"There were {len(self.notification_text)} notifications displayed, expected 8",
)
return self.find_elements(*self._notification_checkbox_locator)
@property
def notification_text(self):
self.wait.until(
EC.visibility_of_element_located(self._notification_text_locator)
)
items = self.find_elements(*self._notification_text_locator)
# the notifications endpoint takes a bit longer to respond, so a wait is helpful here
self.wait.until(
lambda _: len(items) > 0,
message=f'Expected notifications list to be loaded but the list contains {len(items)} items',
message=f"Expected notifications list to be loaded but the list contains {len(items)} items",
)
return items
@property
def notifications_help_text(self):
self.wait.until(
EC.visibility_of_element_located(self._notifications_help_text_locator)
)
return self.find_element(*self._notifications_help_text_locator).text
@property
@ -544,22 +712,34 @@ class User(Base):
def update_profile(self):
"""Updates a user profile and expects to remain on the Edit Profile page (likely due to an error)"""
self.wait.until(
EC.element_to_be_clickable(self._edit_profile_submit_button_locator)
)
self.find_element(*self._edit_profile_submit_button_locator).click()
def submit_changes(self):
"""Updates a user profile and expects to navigate to the View Profile page"""
self.wait.until(
EC.element_to_be_clickable(self._edit_profile_submit_button_locator)
)
self.find_element(*self._edit_profile_submit_button_locator).click()
self.wait.until(
EC.visibility_of_element_located((By.CLASS_NAME, 'UserProfile-name'))
EC.visibility_of_element_located((By.CLASS_NAME, "UserProfile-name"))
)
def delete_account(self):
self.wait.until(
EC.element_to_be_clickable(self._delete_profile_button_locator)
)
self.find_element(*self._delete_profile_button_locator).click()
self.wait.until(
EC.visibility_of_element_located(self._delete_profile_overlay_locator)
)
def cancel_delete_account(self):
self.wait.until(
EC.element_to_be_clickable(self._cancel_delete_profile_button_locator)
)
self.find_element(*self._cancel_delete_profile_button_locator).click()
self.wait.until(
EC.invisibility_of_element_located(
@ -568,9 +748,15 @@ class User(Base):
)
def confirm_delete_account(self):
self.wait.until(
EC.element_to_be_clickable(self._confirm_delete_profile_button_locator)
)
self.find_element(*self._confirm_delete_profile_button_locator).click()
return Home(self.driver, self.page.base_url).wait_for_page_to_load()
@property
def invalid_url_error_text(self):
self.wait.until(
EC.visibility_of_element_located(self._invalid_url_error_text_locator)
)
return self.find_element(*self._invalid_url_error_text_locator).text

Просмотреть файл

@ -2,45 +2,49 @@ from pypom import Region
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as expected
from selenium.webdriver.support import expected_conditions as EC
from pages.desktop.base import Base
from regions.desktop.rating_stats_card import RatingStats
class Versions(Base):
_versions_page_header_locator = (By.CSS_SELECTOR, '.AddonVersions-versions header')
_latest_version_locator = (By.CSS_SELECTOR, '.Card-contents li:nth-child(2) h2')
_versions_list_locator = (By.CSS_SELECTOR, '.AddonVersionCard')
_versions_page_header_locator = (By.CSS_SELECTOR, ".AddonVersions-versions header")
_latest_version_locator = (By.CSS_SELECTOR, ".Card-contents li:nth-child(2) h2")
_versions_list_locator = (By.CSS_SELECTOR, ".AddonVersionCard")
_notice_message_locator = (
By.CSS_SELECTOR,
'.Card-contents .Notice-warning .Notice-text',
".Card-contents .Notice-warning .Notice-text",
)
_notice_message_text_locator = (By.CSS_SELECTOR, '.AddonVersions-warning-text')
_rating_card_locator = (By.CSS_SELECTOR, '.AddonSummaryCard')
_notice_message_text_locator = (By.CSS_SELECTOR, ".AddonVersions-warning-text")
_rating_card_locator = (By.CSS_SELECTOR, ".AddonSummaryCard")
def wait_for_page_to_load(self):
"""Waits for various page components to be loaded"""
self.wait.until(
expected.invisibility_of_element_located((By.CLASS_NAME, 'LoadingText'))
expected.invisibility_of_element_located((By.CLASS_NAME, "LoadingText"))
)
return self
@property
def versions_page_header(self):
self.wait_for_element_to_be_displayed(self._versions_page_header_locator)
return self.find_element(*self._versions_page_header_locator)
@property
def latest_version_number(self):
self.wait_for_element_to_be_displayed(self._latest_version_locator)
el = self.find_element(*self._latest_version_locator).text
return el.split()[1].replace('Version ', '')
return el.split()[1].replace("Version ", "")
@property
def notice_message(self):
self.wait_for_element_to_be_displayed(self._notice_message_locator)
return self.find_element(*self._notice_message_locator)
@property
def rating_card(self):
el = self.find_element(By.CLASS_NAME, 'AddonSummaryCard')
el = self.find_element(By.CLASS_NAME, "AddonSummaryCard")
return RatingStats(self, el)
@property
@ -49,41 +53,53 @@ class Versions(Base):
return [self.VersionCard(self, el) for el in items]
class VersionCard(Region):
_version_number_locator = (By.CSS_SELECTOR, '.AddonVersionCard-version')
_released_date_locator = (By.CSS_SELECTOR, '.AddonVersionCard-fileInfo')
_version_number_locator = (By.CSS_SELECTOR, ".AddonVersionCard-version")
_released_date_locator = (By.CSS_SELECTOR, ".AddonVersionCard-fileInfo")
_version_release_notes_locator = (
By.CSS_SELECTOR,
'.AddonVersionCard-releaseNotes',
".AddonVersionCard-releaseNotes",
)
_license_link_locator = (By.CSS_SELECTOR, '.AddonVersionCard-license > a')
_license_text_locator = (By.CSS_SELECTOR, '.AddonVersionCard-license')
_warning_message_locator = (By.CSS_SELECTOR, '.Notice-text')
_warning_learn_more_button_locator = (By.CSS_SELECTOR, '.Notice-button')
_add_to_firefox_button_locator = (By.CSS_SELECTOR, '.AMInstallButton-button')
_license_link_locator = (By.CSS_SELECTOR, ".AddonVersionCard-license > a")
_license_text_locator = (By.CSS_SELECTOR, ".AddonVersionCard-license")
_warning_message_locator = (By.CSS_SELECTOR, ".Notice-text")
_warning_learn_more_button_locator = (By.CSS_SELECTOR, ".Notice-button")
_add_to_firefox_button_locator = (By.CSS_SELECTOR, ".AMInstallButton-button")
_download_link_locator = (
By.CSS_SELECTOR,
'.InstallButtonWrapper-download-link',
".InstallButtonWrapper-download-link",
)
@property
def version_number(self):
self.wait.until(
EC.visibility_of_element_located(self._version_number_locator)
)
return self.find_element(*self._version_number_locator).text.split()[1]
@property
def released_date(self):
self.wait.until(
EC.visibility_of_element_located(self._released_date_locator)
)
text = self.find_element(*self._released_date_locator).text
text = text.split('Released ')[1]
text = text.split('-')[0][:-1]
text = text.split("Released ")[1]
text = text.split("-")[0][:-1]
return text
@property
def version_size(self): # memory size, ex: 7.35 KB
return self.find_element(*self._released_date_locator).text.split('-')[1][
self.wait.until(
EC.visibility_of_element_located(self._released_date_locator)
)
return self.find_element(*self._released_date_locator).text.split("-")[1][
1:
]
@property
def version_release_notes(self):
self.wait.until(
EC.visibility_of_element_located(self._version_release_notes_locator)
)
return self.find_element(*self._version_release_notes_locator)
@property
@ -97,22 +113,37 @@ class Versions(Base):
@property
def license_text(self):
self.wait.until(
EC.visibility_of_element_located(self._license_text_locator)
)
text = self.find_element(*self._license_text_locator).text
if self.license_link:
text += self.license_link.get_attribute('href')
text += self.license_link.get_attribute("href")
return text
@property
def warning_message(self):
self.wait.until(
EC.visibility_of_element_located(self._warning_message_locator)
)
return self.find_element(*self._warning_message_locator)
@property
def warning_learn_more_button(self):
self.wait.until(
EC.visibility_of_element_located(
self._warning_learn_more_button_locator
)
)
return self.find_element(*self._warning_learn_more_button_locator)
@property
def add_to_firefox_button(self):
self.wait.until(
EC.visibility_of_element_located(self._add_to_firefox_button_locator)
)
return self.find_element(*self._add_to_firefox_button_locator)
def click_download_link(self):
self.wait.until(EC.element_to_be_clickable(self._download_link_locator))
self.find_element(*self._download_link_locator).click()

Просмотреть файл

@ -1 +1 @@
i2zodbyg6ucjgrmr5mimdsavz43g5jom
g13jfwts2dnkrlu5foykzonz30fb4458

743
ratings-test-results.html Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -1,6 +1,7 @@
from pypom import Region
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
class Categories(Region):
@ -15,10 +16,12 @@ class Categories(Region):
@property
def categories_list_header(self):
self.wait.until(EC.visibility_of_element_located(self._categories_card_header_locator))
return self.find_element(*self._categories_card_header_locator)
@property
def category_list(self):
self.wait.until(EC.visibility_of_element_located(self._categories_locator))
items = self.find_elements(*self._categories_locator)
return [self.CategoryList(self, el) for el in items]
@ -27,6 +30,7 @@ class Categories(Region):
@property
def category_button_name(self):
self.wait.until(EC.visibility_of_element_located(self._name_locator))
return self.find_element(*self._name_locator).text
def click(self):

Просмотреть файл

@ -1,5 +1,6 @@
from pypom import Region
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
class RatingStats(Region):
@ -20,9 +21,11 @@ class RatingStats(Region):
@property
def addon_title(self):
self.wait.until(EC.visibility_of_element_located(self._addon_title_locator))
return self.find_element(*self._addon_title_locator)
def click_addon_title(self):
self.wait.until(EC.element_to_be_clickable(self._addon_title_locator))
self.find_element(*self._addon_title_locator).click()
from pages.desktop.frontend.details import Detail
@ -30,9 +33,11 @@ class RatingStats(Region):
@property
def addon_image(self):
self.wait.until(EC.visibility_of_element_located(self._addon_image_locator))
return self.find_element(*self._addon_image_locator)
def click_addon_image(self):
self.wait.until(EC.element_to_be_clickable(self._addon_image_locator))
self.find_element(*self._addon_image_locator).click()
from pages.desktop.frontend.details import Detail
@ -43,6 +48,7 @@ class RatingStats(Region):
return [i.text for i in self.find_elements(*self._addon_author_locator)]
def click_author_name(self, index=0):
self.wait.until(EC.element_to_be_clickable(self._addon_author_locator))
self.find_elements(*self._addon_author_locator)[index].click()
from pages.desktop.frontend.users import User
@ -50,28 +56,34 @@ class RatingStats(Region):
@property
def rating_stars(self):
self.wait.until(EC.visibility_of_element_located(self._rating_stars_locator))
return self.find_elements(*self._rating_stars_locator)
@property
def rating(self):
self.wait.until(EC.visibility_of_element_located(self._addon_rating_locator))
rating = self.find_element(*self._addon_rating_locator).text.split()[0]
return float(rating)
@property
def rating_bars(self):
self.wait.until(EC.visibility_of_element_located(self._rating_bars_locator))
return self.find_elements(*self._rating_bars_locator)
def click_see_all_reviews_with_specific_stars(self, count):
self.wait.until(EC.element_to_be_clickable(self._rating_by_star_locator))
self.find_elements(*self._rating_by_star_locator)[count].click()
from pages.desktop.frontend.reviews import Reviews
return Reviews(self.driver, self.page)
def number_of_reviews_with_specific_stars(self, count):
self.wait.until(EC.visibility_of_element_located(self._number_of_reviews_locator))
return int(self.find_elements(*self._number_of_reviews_locator)[count].text)
@property
def number_of_filled_stars(self):
self.wait.until(EC.visibility_of_element_located(self._filled_stars_locator))
return len(self.find_elements(*self._filled_stars_locator))
@property

Просмотреть файл

@ -1,6 +1,7 @@
from pypom import Region
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
class Shelves(Region):
@ -11,16 +12,19 @@ class Shelves(Region):
@property
def recommended_addons(self):
self.wait.until(EC.visibility_of_element_located(self._recommended_addons_locator))
el = self.find_element(*self._recommended_addons_locator)
return self.ShelfList(self, el)
@property
def top_rated_addons(self):
self.wait.until(EC.visibility_of_element_located(self._top_rated_locator))
el = self.find_element(*self._top_rated_locator)
return self.ShelfList(self, el)
@property
def trending_addons(self):
self.wait.until(EC.visibility_of_element_located(self._trending_addons_locator))
el = self.find_element(*self._trending_addons_locator)
return self.ShelfList(self, el)
@ -31,14 +35,17 @@ class Shelves(Region):
@property
def list(self):
self.wait.until(EC.visibility_of_element_located(self._addon_item_locator))
items = self.find_elements(*self._addon_item_locator)
return [self.ShelfDetail(self.page, el) for el in items]
@property
def card_header(self):
self.wait.until(EC.visibility_of_element_located(self._promo_card_header_locator))
return self.find_element(*self._promo_card_header_locator).text
def browse_all(self):
self.wait.until(EC.element_to_be_clickable(self._browse_all_locator))
self.find_element(*self._browse_all_locator).click()
from pages.desktop.frontend.search import Search
@ -52,12 +59,15 @@ class Shelves(Region):
@property
def name(self):
self.wait.until(EC.visibility_of_element_located(self._addon_name_locator))
return self.find_element(*self._addon_name_locator).text
@property
def addon_icon_preview(self):
self.wait.until(EC.visibility_of_element_located(self._addon_icon_locator))
return self.find_element(*self._addon_icon_locator)
@property
def addon_users_preview(self):
self.wait.until(EC.visibility_of_element_located(self._addon_users_locator))
return self.find_element(*self._addon_users_locator)

Просмотреть файл

@ -1 +1 @@
iqv9l8hycnspa1g8f1apo7g361rox60g
vqxln2g4ouuxhftphcty1cyajq5x9zzx

Двоичные данные
sample-addons/make-addon.zip

Двоичный файл не отображается.

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -1 +1 @@
zw1vzjh06mbms2i13tcr0iu0yov38qkp
j42ho2lqc0i8mei9uoyz8i0fk1s97mic

Просмотреть файл

@ -726,6 +726,7 @@ def test_edit_version_valid_compatibility_values(
url=f"{base_url}{_addon_create}{addon}",
headers={"Authorization": f"Session {session_auth}"},
)
print(request)
# get the version id of the version we want to edit
version = request.json()["current_version"]["id"]
payload = {**payloads.edit_version_details, "compatibility": request_value}

Просмотреть файл

@ -266,6 +266,7 @@ def test_submit_listed_addon(selenium, base_url, variables, wait):
edit_listing = confirmation_page.click_edit_listing_button()
assert addon_name in edit_listing.name
@pytest.mark.sanity
@pytest.mark.serial
@pytest.mark.create_session("submissions_user")
@ -311,13 +312,14 @@ def test_submit_addon_3mb_size(selenium, base_url, wait, variables):
# submit the add-on details
confirmation_page = details_form.submit_addon()
assert (
variables["listed_submission_confirmation"]
in confirmation_page.submission_confirmation_messages[0].text
variables["listed_submission_confirmation"]
in confirmation_page.submission_confirmation_messages[0].text
)
# go to the addon edit listing page and check that it was created
edit_listing = confirmation_page.click_edit_listing_button()
assert addon_name in edit_listing.name
@pytest.mark.serial
@pytest.mark.create_session("submissions_user")
def test_addon_last_modified_date(selenium, base_url):

Просмотреть файл

@ -384,6 +384,7 @@ def test_flag_review_menu_options(selenium, base_url, variables):
@pytest.mark.serial
@pytest.mark.nondestructive
@pytest.mark.create_session("rating_user")
def test_click_on_review_posting_time_link(selenium, base_url, variables):
# this test checks that if we go to all reviews page and clik on a review's posting time link (ex: 2 months ago)
# it displays the review in a different section from the others

782
user-test-results.html Normal file
Просмотреть файл

@ -0,0 +1,782 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Test Report</title>
<style>body {
font-family: Helvetica, Arial, sans-serif;
font-size: 12px;
/* do not increase min-width as some may use split screens */
min-width: 800px;
color: #999;
}
h1 {
font-size: 24px;
color: black;
}
h2 {
font-size: 16px;
color: black;
}
p {
color: black;
}
a {
color: #999;
}
table {
border-collapse: collapse;
}
/******************************
* SUMMARY INFORMATION
******************************/
#environment td {
padding: 5px;
border: 1px solid #E6E6E6;
}
#environment tr:nth-child(odd) {
background-color: #f6f6f6;
}
/******************************
* TEST RESULT COLORS
******************************/
span.passed,
.passed .col-result {
color: green;
}
span.skipped,
span.xfailed,
span.rerun,
.skipped .col-result,
.xfailed .col-result,
.rerun .col-result {
color: orange;
}
span.error,
span.failed,
span.xpassed,
.error .col-result,
.failed .col-result,
.xpassed .col-result {
color: red;
}
/******************************
* RESULTS TABLE
*
* 1. Table Layout
* 2. Extra
* 3. Sorting items
*
******************************/
/*------------------
* 1. Table Layout
*------------------*/
#results-table {
border: 1px solid #e6e6e6;
color: #999;
font-size: 12px;
width: 100%;
}
#results-table th,
#results-table td {
padding: 5px;
border: 1px solid #E6E6E6;
text-align: left;
}
#results-table th {
font-weight: bold;
}
/*------------------
* 2. Extra
*------------------*/
.log {
background-color: #e6e6e6;
border: 1px solid #e6e6e6;
color: black;
display: block;
font-family: "Courier New", Courier, monospace;
height: 230px;
overflow-y: scroll;
padding: 5px;
white-space: pre-wrap;
}
.log:only-child {
height: inherit;
}
div.image {
border: 1px solid #e6e6e6;
float: right;
height: 240px;
margin-left: 5px;
overflow: hidden;
width: 320px;
}
div.image img {
width: 320px;
}
div.video {
border: 1px solid #e6e6e6;
float: right;
height: 240px;
margin-left: 5px;
overflow: hidden;
width: 320px;
}
div.video video {
overflow: hidden;
width: 320px;
height: 240px;
}
.collapsed {
display: none;
}
.expander::after {
content: " (show details)";
color: #BBB;
font-style: italic;
cursor: pointer;
}
.collapser::after {
content: " (hide details)";
color: #BBB;
font-style: italic;
cursor: pointer;
}
/*------------------
* 3. Sorting items
*------------------*/
.sortable {
cursor: pointer;
}
.sort-icon {
font-size: 0px;
float: left;
margin-right: 5px;
margin-top: 5px;
/*triangle*/
width: 0;
height: 0;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
}
.inactive .sort-icon {
/*finish triangle*/
border-top: 8px solid #E6E6E6;
}
.asc.active .sort-icon {
/*finish triangle*/
border-bottom: 8px solid #999;
}
.desc.active .sort-icon {
/*finish triangle*/
border-top: 8px solid #999;
}
</style></head>
<body onLoad="init()">
<script>/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
function toArray(iter) {
if (iter === null) {
return null;
}
return Array.prototype.slice.call(iter);
}
function find(selector, elem) { // eslint-disable-line no-redeclare
if (!elem) {
elem = document;
}
return elem.querySelector(selector);
}
function findAll(selector, elem) {
if (!elem) {
elem = document;
}
return toArray(elem.querySelectorAll(selector));
}
function sortColumn(elem) {
toggleSortStates(elem);
const colIndex = toArray(elem.parentNode.childNodes).indexOf(elem);
let key;
if (elem.classList.contains('result')) {
key = keyResult;
} else if (elem.classList.contains('links')) {
key = keyLink;
} else {
key = keyAlpha;
}
sortTable(elem, key(colIndex));
}
function showAllExtras() { // eslint-disable-line no-unused-vars
findAll('.col-result').forEach(showExtras);
}
function hideAllExtras() { // eslint-disable-line no-unused-vars
findAll('.col-result').forEach(hideExtras);
}
function showExtras(colresultElem) {
const extras = colresultElem.parentNode.nextElementSibling;
const expandcollapse = colresultElem.firstElementChild;
extras.classList.remove('collapsed');
expandcollapse.classList.remove('expander');
expandcollapse.classList.add('collapser');
}
function hideExtras(colresultElem) {
const extras = colresultElem.parentNode.nextElementSibling;
const expandcollapse = colresultElem.firstElementChild;
extras.classList.add('collapsed');
expandcollapse.classList.remove('collapser');
expandcollapse.classList.add('expander');
}
function showFilters() {
const filterItems = document.getElementsByClassName('filter');
for (let i = 0; i < filterItems.length; i++)
filterItems[i].hidden = false;
}
function addCollapse() {
// Add links for show/hide all
const resulttable = find('table#results-table');
const showhideall = document.createElement('p');
showhideall.innerHTML = '<a href="javascript:showAllExtras()">Show all details</a> / ' +
'<a href="javascript:hideAllExtras()">Hide all details</a>';
resulttable.parentElement.insertBefore(showhideall, resulttable);
// Add show/hide link to each result
findAll('.col-result').forEach(function(elem) {
const collapsed = getQueryParameter('collapsed') || 'Passed';
const extras = elem.parentNode.nextElementSibling;
const expandcollapse = document.createElement('span');
if (extras.classList.contains('collapsed')) {
expandcollapse.classList.add('expander');
} else if (collapsed.includes(elem.innerHTML)) {
extras.classList.add('collapsed');
expandcollapse.classList.add('expander');
} else {
expandcollapse.classList.add('collapser');
}
elem.appendChild(expandcollapse);
elem.addEventListener('click', function(event) {
if (event.currentTarget.parentNode.nextElementSibling.classList.contains('collapsed')) {
showExtras(event.currentTarget);
} else {
hideExtras(event.currentTarget);
}
});
});
}
function getQueryParameter(name) {
const match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
}
function init () { // eslint-disable-line no-unused-vars
resetSortHeaders();
addCollapse();
showFilters();
sortColumn(find('.initial-sort'));
findAll('.sortable').forEach(function(elem) {
elem.addEventListener('click',
function() {
sortColumn(elem);
}, false);
});
}
function sortTable(clicked, keyFunc) {
const rows = findAll('.results-table-row');
const reversed = !clicked.classList.contains('asc');
const sortedRows = sort(rows, keyFunc, reversed);
/* Whole table is removed here because browsers acts much slower
* when appending existing elements.
*/
const thead = document.getElementById('results-table-head');
document.getElementById('results-table').remove();
const parent = document.createElement('table');
parent.id = 'results-table';
parent.appendChild(thead);
sortedRows.forEach(function(elem) {
parent.appendChild(elem);
});
document.getElementsByTagName('BODY')[0].appendChild(parent);
}
function sort(items, keyFunc, reversed) {
const sortArray = items.map(function(item, i) {
return [keyFunc(item), i];
});
sortArray.sort(function(a, b) {
const keyA = a[0];
const keyB = b[0];
if (keyA == keyB) return 0;
if (reversed) {
return keyA < keyB ? 1 : -1;
} else {
return keyA > keyB ? 1 : -1;
}
});
return sortArray.map(function(item) {
const index = item[1];
return items[index];
});
}
function keyAlpha(colIndex) {
return function(elem) {
return elem.childNodes[1].childNodes[colIndex].firstChild.data.toLowerCase();
};
}
function keyLink(colIndex) {
return function(elem) {
const dataCell = elem.childNodes[1].childNodes[colIndex].firstChild;
return dataCell == null ? '' : dataCell.innerText.toLowerCase();
};
}
function keyResult(colIndex) {
return function(elem) {
const strings = ['Error', 'Failed', 'Rerun', 'XFailed', 'XPassed',
'Skipped', 'Passed'];
return strings.indexOf(elem.childNodes[1].childNodes[colIndex].firstChild.data);
};
}
function resetSortHeaders() {
findAll('.sort-icon').forEach(function(elem) {
elem.parentNode.removeChild(elem);
});
findAll('.sortable').forEach(function(elem) {
const icon = document.createElement('div');
icon.className = 'sort-icon';
icon.textContent = 'vvv';
elem.insertBefore(icon, elem.firstChild);
elem.classList.remove('desc', 'active');
elem.classList.add('asc', 'inactive');
});
}
function toggleSortStates(elem) {
//if active, toggle between asc and desc
if (elem.classList.contains('active')) {
elem.classList.toggle('asc');
elem.classList.toggle('desc');
}
//if inactive, reset all other functions and add ascending active
if (elem.classList.contains('inactive')) {
resetSortHeaders();
elem.classList.remove('inactive');
elem.classList.add('active');
}
}
function isAllRowsHidden(value) {
return value.hidden == false;
}
function filterTable(elem) { // eslint-disable-line no-unused-vars
const outcomeAtt = 'data-test-result';
const outcome = elem.getAttribute(outcomeAtt);
const classOutcome = outcome + ' results-table-row';
const outcomeRows = document.getElementsByClassName(classOutcome);
for(let i = 0; i < outcomeRows.length; i++){
outcomeRows[i].hidden = !elem.checked;
}
const rows = findAll('.results-table-row').filter(isAllRowsHidden);
const allRowsHidden = rows.length == 0 ? true : false;
const notFoundMessage = document.getElementById('not-found-message');
notFoundMessage.hidden = !allRowsHidden;
}
</script>
<h1>user-test-results.html</h1>
<p>Report generated on 20-Sep-2023 at 15:56:44 by <a href="https://pypi.python.org/pypi/pytest-html">pytest-html</a> v3.1.1</p>
<h2>Summary</h2>
<p>33 tests ran in 400.54 seconds. </p>
<p class="filter" hidden="true">(Un)check the boxes to filter the results.</p><input checked="true" class="filter" data-test-result="passed" hidden="true" name="filter_checkbox" onChange="filterTable(this)" type="checkbox"/><span class="passed">33 passed</span>, <input checked="true" class="filter" data-test-result="skipped" hidden="true" name="filter_checkbox" onChange="filterTable(this)" type="checkbox"/><span class="skipped">1 skipped</span>, <input checked="true" class="filter" data-test-result="failed" disabled="true" hidden="true" name="filter_checkbox" onChange="filterTable(this)" type="checkbox"/><span class="failed">0 failed</span>, <input checked="true" class="filter" data-test-result="error" disabled="true" hidden="true" name="filter_checkbox" onChange="filterTable(this)" type="checkbox"/><span class="error">0 errors</span>, <input checked="true" class="filter" data-test-result="xfailed" disabled="true" hidden="true" name="filter_checkbox" onChange="filterTable(this)" type="checkbox"/><span class="xfailed">0 expected failures</span>, <input checked="true" class="filter" data-test-result="xpassed" disabled="true" hidden="true" name="filter_checkbox" onChange="filterTable(this)" type="checkbox"/><span class="xpassed">0 unexpected passes</span>, <input checked="true" class="filter" data-test-result="rerun" disabled="true" hidden="true" name="filter_checkbox" onChange="filterTable(this)" type="checkbox"/><span class="rerun">0 rerun</span>
<h2>Results</h2>
<table id="results-table">
<thead id="results-table-head">
<tr>
<th class="sortable result initial-sort" col="result">Result</th>
<th class="sortable" col="name">Test</th>
<th class="sortable" col="duration">Duration</th>
<th class="sortable links" col="links">Links</th></tr>
<tr hidden="true" id="not-found-message">
<th colspan="4">No results found. Try to check the filters</th></tr></thead>
<tbody class="skipped results-table-row">
<tr>
<td class="col-result">Skipped</td>
<td class="col-name">tests/frontend/test_users.py::test_user_notifications_subscriptions[Desktop]</td>
<td class="col-duration">0.00</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="log">(&#x27;/Users/alexandru.schek/addons-release-tests/tests/frontend/test_users.py&#x27;, 365, &#x27;Skipped: Intermittent issue, see https://github.com/mozilla/addons-server/issues/20965&#x27;)<br/></div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_login[Desktop]</td>
<td class="col-duration">11.88</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="log"> ------------------------------Captured stdout call------------------------------ <br/>The &quot;click continue button&quot; event occurred.
The script should be on the password input screen here. We should see &quot;Sign in&quot; in the header. The card header title is &quot;Enter your password
for your Firefox account&quot;
<br/></div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_logout[Desktop]</td>
<td class="col-duration">20.69</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="log"> ------------------------------Captured stdout call------------------------------ <br/>The &quot;click continue button&quot; event occurred.
The script should be on the password input screen here. We should see &quot;Sign in&quot; in the header. The card header title is &quot;Enter your password
for your Firefox account&quot;
<br/></div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_menu_collections_link[Desktop]</td>
<td class="col-duration">17.12</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="log"> ------------------------------Captured stdout call------------------------------ <br/>The &quot;click continue button&quot; event occurred.
The script should be on the password input screen here. We should see &quot;Sign in&quot; in the header. The card header title is &quot;Enter your password
for your Firefox account&quot;
<br/></div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_menu_view_profile[Desktop]</td>
<td class="col-duration">16.19</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="log"> ------------------------------Captured stdout call------------------------------ <br/>The &quot;click continue button&quot; event occurred.
The script should be on the password input screen here. We should see &quot;Sign in&quot; in the header. The card header title is &quot;Enter your password
for your Firefox account&quot;
<br/></div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_menu_edit_profile[Desktop]</td>
<td class="col-duration">16.15</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="log"> ------------------------------Captured stdout call------------------------------ <br/>The &quot;click continue button&quot; event occurred.
The script should be on the password input screen here. We should see &quot;Sign in&quot; in the header. The card header title is &quot;Enter your password
for your Firefox account&quot;
<br/></div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_register_new_account[Desktop]</td>
<td class="col-duration">17.33</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_menu_click_user_menu_links[Desktop]</td>
<td class="col-duration">64.59</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="log"> -----------------------------Captured stdout setup------------------------------ <br/>The &quot;click continue button&quot; event occurred.
The script should be on the password input screen here. We should see &quot;Sign in&quot; in the header. The card header title is &quot;Enter your password
for your Firefox account&quot;
<br/></div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_developer_notifications[Desktop]</td>
<td class="col-duration">7.56</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_mandatory_notifications[Desktop]</td>
<td class="col-duration">9.78</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_edit_profile[Desktop]</td>
<td class="col-duration">15.21</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="log"> -----------------------------Captured stdout setup------------------------------ <br/>The &quot;click continue button&quot; event occurred.
The script should be on the password input screen here. We should see &quot;Sign in&quot; in the header. The card header title is &quot;Enter your password
for your Firefox account&quot;
<br/></div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_view_profile[Desktop]</td>
<td class="col-duration">5.89</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_change_profile_picture[Desktop]</td>
<td class="col-duration">6.73</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_delete_profile_picture[Desktop]</td>
<td class="col-duration">7.15</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_update_profile[Desktop]</td>
<td class="col-duration">6.40</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_update_url[Desktop]</td>
<td class="col-duration">6.41</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_delete_profile[Desktop]</td>
<td class="col-duration">6.73</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_account_manage_section[Desktop]</td>
<td class="col-duration">19.46</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="log"> -----------------------------Captured stdout setup------------------------------ <br/>The &quot;click continue button&quot; event occurred.
The script should be on the password input screen here. We should see &quot;Sign in&quot; in the header. The card header title is &quot;Enter your password
for your Firefox account&quot;
<br/></div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_data_for_deleted_profile[Desktop]</td>
<td class="col-duration">5.43</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_regular_has_no_role[Desktop]</td>
<td class="col-duration">6.73</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_regular_notifications[Desktop]</td>
<td class="col-duration">12.05</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="log"> ------------------------------Captured stdout call------------------------------ <br/>The &quot;click continue button&quot; event occurred.
The script should be on the password input screen here. We should see &quot;Sign in&quot; in the header. The card header title is &quot;Enter your password
for your Firefox account&quot;
<br/></div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_developer_role[Desktop]</td>
<td class="col-duration">4.44</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_theme_artist_role[Desktop]</td>
<td class="col-duration">5.08</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_artist_and_developer_role[Desktop]</td>
<td class="col-duration">4.60</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_non_developer_user_profile_is_not_public[Desktop]</td>
<td class="col-duration">4.64</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_addon_cards_for_users_with_multiple_roles[Desktop]</td>
<td class="col-duration">5.55</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_profile_extensions_card[Desktop]</td>
<td class="col-duration">5.63</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_profile_themes_card[Desktop]</td>
<td class="col-duration">4.35</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="log"> ------------------------------Captured stdout call------------------------------ <br/>The user had 1 themes, so pagination is not present
<br/></div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_profile_open_extension_detail_page[Desktop]</td>
<td class="col-duration">5.51</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_profile_open_theme_detail_page[Desktop]</td>
<td class="col-duration">5.23</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_profile_write_review[Desktop]</td>
<td class="col-duration">55.38</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="log"> -----------------------------Captured stdout setup------------------------------ <br/>The &quot;click continue button&quot; event occurred.
The script should be on the password input screen here. We should see &quot;Sign in&quot; in the header. The card header title is &quot;Enter your password
for your Firefox account&quot;
<br/></div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_profile_edit_review[Desktop]</td>
<td class="col-duration">7.66</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_profile_delete_review[Desktop]</td>
<td class="col-duration">6.63</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">tests/frontend/test_users.py::test_user_abuse_report[Desktop]</td>
<td class="col-duration">6.07</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody></table></body></html>