diff --git a/docker-compose.yml b/docker-compose.yml index 2b3fc1353b..7385fb09fb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -81,5 +81,22 @@ services: ports: - "5900" shm_size: 2g + links: + - "addons-frontend:olympia-frontend.test" + - "nginx:olympia.test" + + addons-frontend: + <<: *env + environment: + - NODE_APP_INSTANCE=amo + - NODE_ENV=development + - API_HOST=http://olympia.test + - HOSTNAME=functional.test + ports: + - "4000:4000" + expose: + - "4000" + image: addons/addons-frontend + command: yarn build && yarn start links: - "nginx:olympia.test" diff --git a/docker/supervisor.conf b/docker/supervisor.conf index f04f57c548..a71b3a4733 100644 --- a/docker/supervisor.conf +++ b/docker/supervisor.conf @@ -18,4 +18,3 @@ port=9001 [rpcinterface:supervisor] supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface - diff --git a/src/olympia/landfill/management/commands/generate_ui_test_addons.py b/src/olympia/landfill/management/commands/generate_ui_test_addons.py index 111b98fe5d..a056b2861f 100644 --- a/src/olympia/landfill/management/commands/generate_ui_test_addons.py +++ b/src/olympia/landfill/management/commands/generate_ui_test_addons.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + from django.core.cache import cache from django.core.management import call_command from django.core.management.base import BaseCommand @@ -9,31 +11,40 @@ from olympia.landfill.serializers import GenerateAddonsSerializer # Featured collections on the homepage. # Needs to be updated as the homepage is updated featured_collections = [ - 'dynamic-media-downloaders', + u'privacy-matters', + u're-imagine-search', + u'dynamic-media-downloaders', ] # Featured collections on the homepage. base_collections = [ - 'bookmark-managers', - 'password-managers', - 'ad-blockers', - 'smarter-shopping', - 'be-more-productive', - 'watching-videos', + u'bookmark-managers', + u'password-managers', + u'ad-blockers', + u'smarter-shopping', + u'be-more-productive', + u'watching-videos', ] # Addons that exist in the carousel. # Needs to be updated as the homepage is updated -carousel_addons = [ - 'wikipedia-context-menu-search', - 'momentumdash', - 'undo-close-tab-button', - 'grammarly-1', - 'facebook-filter', - 'gesturefy', - 'multi-account-containers', - 'tree-style-tab', - 'lastpass-password-manager', +hero_addons = [ + u'wikiwand-wikipedia-modernized', + u'onetab', + u'kindle-it', + u'search-site-we', + u'youtube-dark-purple', + u'temporary-containers', + u'momentumdash', + u'kimetrak', + u'mailvelope', + u'翻译侠-translate-man', + u'ublock-origin', + u'ghostery', + u'multi-account-containers', + u'searchpreview', + u'forget_me_not', + u'zoom', ] @@ -66,7 +77,7 @@ class Command(BaseCommand): for addon in base_collections: serializer.create_a_named_collection_and_addon( addon, author='mozilla') - for addon in carousel_addons: + for addon in hero_addons: serializer.create_named_addon_with_author(addon) serializer.create_installable_addon() cache.clear() diff --git a/src/olympia/landfill/serializers.py b/src/olympia/landfill/serializers.py index 4c49dcde9b..db2de22f92 100644 --- a/src/olympia/landfill/serializers.py +++ b/src/olympia/landfill/serializers.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + import mimetypes import random import waffle @@ -61,7 +63,7 @@ class GenerateAddonsSerializer(serializers.Serializer): status=STATUS_PUBLIC, users=[UserProfile.objects.get(username=author)], name=u'{}'.format(name), - slug='{}'.format(name), + slug=u'{}'.format(name), ) addon.save() else: @@ -69,7 +71,7 @@ class GenerateAddonsSerializer(serializers.Serializer): status=STATUS_PUBLIC, users=[UserProfile.objects.get(username=author.username)], name=u'{}'.format(name), - slug='{}'.format(name), + slug=u'{}'.format(name), ) addon.save() return addon diff --git a/tests/ui/conftest.py b/tests/ui/conftest.py index f99472835e..8ac92e682b 100644 --- a/tests/ui/conftest.py +++ b/tests/ui/conftest.py @@ -12,12 +12,19 @@ from olympia import amo @pytest.fixture def firefox_options(firefox_options): + """Firefox options. + + These options configure firefox to allow for addon installation, + as well as allowing it to run headless. + + """ firefox_options.set_preference( 'extensions.install.requireBuiltInCerts', False) firefox_options.set_preference('xpinstall.signatures.required', False) firefox_options.set_preference('extensions.webapi.testing', True) firefox_options.set_preference('ui.popup.disable_autohide', True) firefox_options.add_argument('-foreground') + firefox_options.add_argument('-headless') firefox_options.log.level = 'trace' return firefox_options @@ -27,6 +34,23 @@ def firefox_notifications(notifications): return notifications +@pytest.fixture(scope='function', + params=[(1080, 1920), (414, 738)], + ids=['Resolution: 1080x1920', 'Resolution: 414x738']) +def selenium(selenium, request): + """Fixture to set custom selenium parameters. + + This fixture will also parametrize all of the tests to run them on both a + Desktop resolution and a mobile resolution. + + Desktop size: 1920x1080 + Mobile size: 738x414 (iPhone 7+) + + """ + selenium.set_window_size(*request.param) + return selenium + + @pytest.fixture def fxa_account(base_url): """Account used to login to the AMO site.""" diff --git a/tests/ui/pages/desktop/base.py b/tests/ui/pages/desktop/base.py index 5cd3e8fbaa..4fc237952d 100644 --- a/tests/ui/pages/desktop/base.py +++ b/tests/ui/pages/desktop/base.py @@ -6,7 +6,7 @@ from selenium.webdriver.common.action_chains import ActionChains class Base(Page): _url = '{base_url}/{locale}' - _amo_header = (By.CLASS_NAME, 'amo-header') + _amo_header = (By.CLASS_NAME, 'Header-title') def __init__(self, selenium, base_url, locale='en-US', **kwargs): super(Base, self).__init__( @@ -19,7 +19,7 @@ class Base(Page): @property def header(self): - return self.Header(self) + return Header(self) @property def logged_in(self): @@ -36,33 +36,59 @@ class Base(Page): def logout(self): self.header.click_logout() - class Header(Region): - _root_locator = (By.CLASS_NAME, 'amo-header') - _login_locator = (By.CSS_SELECTOR, '#aux-nav .account a:nth-child(2)') - _logout_locator = (By.CSS_SELECTOR, '.logout > a') - _user_locator = (By.CSS_SELECTOR, '#aux-nav .account .user') - _search_button_locator = (By.CSS_SELECTOR, '.search-button') - _search_textbox_locator = (By.ID, 'search-q') +class Header(Region): - def click_login(self): - self.find_element(*self._login_locator).click() - from pages.desktop.login import Login - return Login(self.selenium, self.page.base_url, timeout=30) + _root_locator = (By.CLASS_NAME, 'Header') + _header_title_locator = (By.CLASS_NAME, 'Header-title') + _explore_locator = (By.CSS_SELECTOR, '.SectionLinks > li:nth-child(1) \ + > a:nth-child(1)') + _firefox_logo_locator = (By.CLASS_NAME, 'Header-title') + _extensions_locator = (By.CSS_SELECTOR, '.SectionLinks \ + > li:nth-child(2) > a:nth-child(1)') + _login_locator = (By.CSS_SELECTOR, '.Header-auth-button') + _logout_locator = (By.CSS_SELECTOR, '') + _themes_locator = (By.CSS_SELECTOR, '.SectionLinks > li:nth-child(3) > \ + a:nth-child(1)') + _user_locator = (By.CSS_SELECTOR, '') + _search_textbox_locator = (By.CLASS_NAME, 'SearchForm-query') - def click_logout(self): - user = self.find_element(*self._user_locator) - logout = self.find_element(*self._logout_locator) - action = ActionChains(self.selenium) - action.move_to_element(user) - action.move_to_element(logout) - action.click() - action.perform() - self.wait.until(lambda s: self.is_element_displayed( - *self._login_locator)) + def click_explore(self): + self.find_element(*self._firefox_logo_locator).click() - def search_for(self, term): - self.find_element(*self._search_textbox_locator).send_keys(term) - self.find_element(*self._search_button_locator).click() - from pages.desktop.search import SearchResultList - return SearchResultList(self.selenium, self.page.base_url) + def click_extensions(self): + self.find_element(*self._extensions_locator).click() + from pages.desktop.extensions import Extensions + return Extensions( + self.selenium, self.page.base_url).wait_for_page_to_load() + + def click_themes(self): + self.find_element(*self._themes_locator).click() + from pages.desktop.themes import Themes + return Themes( + self.selenium, self.page.base_url).wait_for_page_to_load() + + def click_login(self): + self.find_element(*self._login_locator).click() + from pages.desktop.login import Login + return Login(self.selenium, self.page.base_url, timeout=30) + + def click_logout(self): + user = self.find_element(*self._user_locator) + logout = self.find_element(*self._logout_locator) + action = ActionChains(self.selenium) + action.move_to_element(user) + action.move_to_element(logout) + action.click() + action.perform() + self.wait.until(lambda s: self.is_element_displayed( + *self._login_locator)) + + def search_for(self, term): + textbox = self.find_element(*self._search_textbox_locator) + textbox.click() + textbox.send_keys(term) + # Send 'enter' since the mobile page does not have a submit button + textbox.send_keys(u'\ue007') + from pages.desktop.search import Search + return Search(self.selenium, self.page).wait_for_page_to_load() diff --git a/tests/ui/pages/desktop/categories.py b/tests/ui/pages/desktop/categories.py new file mode 100644 index 0000000000..0307ad0746 --- /dev/null +++ b/tests/ui/pages/desktop/categories.py @@ -0,0 +1,36 @@ +from pypom import Region +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as expected + +from pages.desktop.base import Base + + +class Categories(Base): + + URL_TEMPLATE = 'extensions/categories/' + + _categories_locator = (By.CLASS_NAME, 'Categories-item') + _mobile_categories_locator = (By.CLASS_NAME, 'LandingPage-button') + + def wait_for_page_to_load(self): + self.wait.until( + expected.invisibility_of_element_located( + (By.CLASS_NAME, 'LoadingText'))) + + @property + def category_list(self): + categories = self.find_elements(*self._categories_locator) + return [self.CategoryItem(self, el) for el in categories] + + class CategoryItem(Region): + + _link_locator = (By.CLASS_NAME, 'Categories-link') + + @property + def name(self): + return self.find_element(*self._link_locator).text + + def click(self): + self.find_element(*self._link_locator).click() + from pages.desktop.category import Category + return Category(self.selenium, self.page) diff --git a/tests/ui/pages/desktop/category.py b/tests/ui/pages/desktop/category.py new file mode 100644 index 0000000000..3f661eec7c --- /dev/null +++ b/tests/ui/pages/desktop/category.py @@ -0,0 +1,29 @@ +from pypom import Region +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as expected + +from pages.desktop.base import Base + + +class Category(Base): + + _root_locator = (By.CLASS_NAME, 'Category') + _category_header_locator = (By.CLASS_NAME, 'CategoryHeader') + + def wait_for_page_to_load(self): + self.wait.until( + expected.invisibility_of_element_located( + (By.CLASS_NAME, 'LoadingText'))) + return self + + @property + def header(self): + return self.Header(self) + + class Header(Region): + + _category_name_locator = (By.CLASS_NAME, 'CategoryHeader-name') + + @property + def name(self): + return self.find_element(*self._category_name_locator).text diff --git a/tests/ui/pages/desktop/details.py b/tests/ui/pages/desktop/details.py index a748353b4e..341accf385 100644 --- a/tests/ui/pages/desktop/details.py +++ b/tests/ui/pages/desktop/details.py @@ -1,29 +1,24 @@ -from pypom import Region from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as expected -from base import Base +from pages.desktop.base import Base -class Details(Base): - """Details page.""" +class Detail(Base): + + _root_locator = (By.CLASS_NAME, 'Addon-extension') + _addon_name_locator = (By.CLASS_NAME, 'Addon-title') + _install_button_locator = (By.CLASS_NAME, 'InstallButton-button') + def wait_for_page_to_load(self): - self.wait.until(lambda _: self.description_header.name) + self.wait.until( + expected.invisibility_of_element_located( + (By.CLASS_NAME, 'LoadingText'))) return self @property - def description_header(self): - return self.DescriptionHeader(self) + def name(self): + return self.find_element(*self._addon_name_locator).text - class DescriptionHeader(Region): - """Represents the header of the detail page.""" - _root_locator = (By.CLASS_NAME, 'addon-description-header') - _install_button_locator = (By.CLASS_NAME, 'add') - _name_locator = (By.TAG_NAME, 'h1') - - @property - def name(self): - return self.find_element(*self._name_locator).text - - @property - def install_button(self): - return self.find_element(*self._install_button_locator) + def install(self): + self.find_element(*self._install_button_locator).click() diff --git a/tests/ui/pages/desktop/extensions.py b/tests/ui/pages/desktop/extensions.py new file mode 100644 index 0000000000..1712919627 --- /dev/null +++ b/tests/ui/pages/desktop/extensions.py @@ -0,0 +1,60 @@ +from pypom import Region +from selenium.webdriver.common.by import By + +from pages.desktop.base import Base + + +class Extensions(Base): + + URL_TEMPLATE = 'extensions/' + + _featured_addons_locator = (By.CLASS_NAME, 'FeaturedAddons') + _top_rated_locator = (By.CLASS_NAME, 'HighlyRatedAddons') + _title_locator = (By.CLASS_NAME, 'LandingPage-addonType-name') + _trending_addons_locator = (By.CLASS_NAME, 'TrendingAddons') + + def wait_for_page_to_load(self): + self.wait.until( + lambda _: self.is_element_displayed(*self._title_locator)) + element = self.find_element(*self._title_locator) + return element + + @property + def extension_header(self): + return self.ExtensionHeader(self) + + @property + def featured_extensions(self): + items = self.find_elements(*self._featured_addons_locator) + return [self.ExtensionDetail(self, el) for el in items] + + @property + def categories(self): + from regions.desktop.categories import Categories + return Categories(self) + + class ExtensionHeader(Region): + _root_locator = (By.CLASS_NAME, 'Category') + _header_locator = (By.CLASS_NAME, 'CategoryHeader') + _category_name_locator = (By.CLASS_NAME, 'CategoryHeader-name') + + @property + def name(self): + return self.find_element(*self._category_name_locator).text + + class ExtensionsList(Region): + + _extensions_locator = (By.CLASS_NAME, 'SearchResult') + + @property + def list(self): + items = self.find_elements(*self._extensions_locator) + return [self.ExtensionDetail(self.page, el) for el in items] + + class ExtensionDetail(Region): + + _extension_name_locator = (By.CLASS_NAME, 'SearchResult-name') + + @property + def name(self): + return self.find_element(*self._extension_name_locator).text diff --git a/tests/ui/pages/desktop/home.py b/tests/ui/pages/desktop/home.py index ac92761635..53e5ba9fe8 100644 --- a/tests/ui/pages/desktop/home.py +++ b/tests/ui/pages/desktop/home.py @@ -1,144 +1,115 @@ from pypom import Region from selenium.webdriver.common.by import By -from base import Base +from pages.desktop.base import Base class Home(Base): """Addons Home page""" + + _extensions_category_locator = (By.CLASS_NAME, 'Home-CuratedCollections') + _featured_extensions_locator = (By.CLASS_NAME, 'Home-FeaturedExtensions') + _featured_themes_locator = (By.CLASS_NAME, 'Home-FeaturedThemes') + _popular_extensions_locator = (By.CLASS_NAME, 'Home-PopularExtensions') + _popular_themes_locator = (By.CLASS_NAME, 'Home-PopularThemes') + _themes_category_locator = (By.CLASS_NAME, 'Home-CuratedThemes') + @property - def most_popular(self): - return self.MostPopular(self) + def popular_extensions(self): + el = self.find_element(*self._popular_extensions_locator) + return self.Extensions(self, el) @property def featured_extensions(self): - return self.FeaturedExtensions(self) - - @property - def featured_collections(self): - return self.FeaturedCollections(self) + el = self.find_element(*self._featured_extensions_locator) + return self.Extensions(self, el) @property def featured_themes(self): - return self.FeaturedThemes(self) + el = self.find_element(*self._featured_themes_locator) + return self.Themes(self, el) - class MostPopular(Region): - """Most popular extensions region""" - _root_locator = (By.ID, 'popular-extensions') - _extension_locator = (By.CSS_SELECTOR, '.toplist li') + @property + def popular_themes(self): + el = self.find_element(*self._popular_themes_locator) + return self.Themes(self, el) + + @property + def extension_category(self): + el = self.find_element(*self._extensions_category_locator) + return self.Category(self, el) + + @property + def theme_category(self): + el = self.find_element(*self._themes_category_locator) + return self.Category(self, el) + + class Category(Region): + _extensions_locator = (By.CLASS_NAME, 'Home-SubjectShelf-list-item') @property - def extensions(self): - extensions = self.find_elements(*self._extension_locator) - return [self.Extension(self.page, el) for el in extensions] + def list(self): + items = self.find_elements(*self._extensions_locator) + return [self.CategoryDetail(self.page, el) for el in items] - class Extension(Region): + class CategoryDetail(Region): + _extension_link_locator = (By.CLASS_NAME, 'Home-SubjectShelf-link') + _extension_name_locator = ( + By.CSS_SELECTOR, '.Home-SubjectShelf-link span') - _name_locator = (By.CLASS_NAME, 'name') - _users_locator = (By.TAG_NAME, 'small') - - def __repr__(self): - return '{0.name} ({0.users:,} users)'.format(self) + @property + def name(self): + return self.find_element(*self._extension_name_locator).text def click(self): - """Clicks on the addon.""" - self.find_element(*self._name_locator).click() - from pages.desktop.details import Details - return Details( - self.selenium, self.page.base_url).wait_for_page_to_load() + self.root.click() + from pages.desktop.extensions import Extensions + return Extensions(self.selenium, self.page.base_url) - @property - def name(self): - """Extension name""" - return self.find_element(*self._name_locator).text - - @property - def users(self): - """Number of users that have downloaded the extension""" - users_str = self.find_element(*self._users_locator).text - return int(users_str.split()[0].replace(',', '')) - - class FeaturedExtensions(Region): - """Featured Extension region""" - _root_locator = (By.ID, 'featured-extensions') - _extension_locator = (By.CSS_SELECTOR, 'section > li > .addon') - _see_all_locator = (By.CSS_SELECTOR, 'h2 > a') + class Extensions(Region): + _browse_all_locator = (By.CSS_SELECTOR, '.Card-footer-link > a') + _extensions_locator = (By.CLASS_NAME, 'SearchResult') + _extension_card_locator = (By.CSS_SELECTOR, '.Home-category-li') @property - def extensions(self): - extentions = self.find_elements(*self._extension_locator) - return [self.Extension(self.page, el) for el in extentions] - - class Extension(Region): - - _name_locator = (By.CSS_SELECTOR, 'h3') - _link_locator = (By.CSS_SELECTOR, '.addon .summary a') - - def click(self): - """Clicks the addon link""" - self.find_element(*self._link_locator).click() - from pages.desktop.details import Details - return Details( - self.selenium, self.page.base_url).wait_for_page_to_load() - - @property - def name(self): - return self.find_element(*self._name_locator).text - - class FeaturedThemes(Region): - """Featured Themes region""" - _root_locator = (By.ID, 'featured-themes') - _themes_locator = (By.CSS_SELECTOR, 'li') - _see_all_link = (By.CLASS_NAME, 'seeall') + def list(self): + items = self.find_elements(*self._extensions_locator) + return [Home.ExtensionsList(self.page, el) for el in items] @property - def themes(self): - """Represents all themes found within the Featured Themes region. - """ - themes = self.find_elements(*self._themes_locator) - return [self.Theme(self, el) for el in themes] + def browse_all(self): + self.find_element(*self._browse_all_locator).click() + from pages.desktop.search import Search + search = Search(self.selenium, self.page.base_url) + return search.wait_for_page_to_load() - def see_all(self): - """Clicks the 'See All' link.""" - self.find_element(*self._see_all_link).click() - from pages.desktop.themes import Themes - return Themes( - self.selenium, self.page.base_url).wait_for_page_to_load() - - class Theme(Region): - - _name_locator = (By.CSS_SELECTOR, 'h3') - - @property - def name(self): - """Theme Name""" - return self.find_element(*self._name_locator).text - - class FeaturedCollections(Region): - """Featured Collections region""" - _root_locator = (By.ID, 'featured-collections') - _items_locator = (By.CSS_SELECTOR, 'li') - _see_all_link = (By.CSS_SELECTOR, 'h2 a') + class Themes(Region): + _browse_all_locator = (By.CSS_SELECTOR, '.Card-footer-link > a') + _themes_locator = (By.CLASS_NAME, 'SearchResult--theme') + _theme_card_locator = (By.CSS_SELECTOR, '.Home-category-li') @property - def collections(self): - """Represents all Collections found within the Featured Collections - """ - collections = self.find_elements(*self._items_locator) - return[self.Collection(self.page, el) for el in collections] + def list(self): + items = self.find_elements(*self._themes_locator) + return [Home.ExtensionsList(self.page, el) for el in items] - def see_all(self): - """Clicks the 'See All' link.""" - self.find_element(*self._see_all_link).click() - from pages.desktop.collections import Collections - return Collections( - self.selenium, self.page.base_url).wait_for_page_to_load() + @property + def browse_all(self): + self.find_element(*self._browse_all_locator).click() + from pages.desktop.search import Search + search = Search(self.selenium, self.page.base_url) + return search.wait_for_page_to_load() - class Collection(Region): - """Individual Collection region""" - _name_locator = (By.TAG_NAME, 'h3') + class ExtensionsList(Region): - @property - def name(self): - """Collection name""" - return self.find_element(*self._name_locator).text + _extension_link_locator = (By.CLASS_NAME, 'SearchResult-link') + _extension_name_locator = (By.CLASS_NAME, 'SearchResult-name') + + @property + def name(self): + return self.find_element(*self._extension_name_locator).text + + def click(self): + self.find_element(*self._extension_link_locator).click() + from pages.desktop.extensions import Extensions + return Extensions(self.selenium, self.page.base_url) diff --git a/tests/ui/pages/desktop/regions/sorter.py b/tests/ui/pages/desktop/regions/sorter.py deleted file mode 100644 index d7f33af60e..0000000000 --- a/tests/ui/pages/desktop/regions/sorter.py +++ /dev/null @@ -1,18 +0,0 @@ -from selenium.webdriver.common.by import By -import selenium.webdriver.support.expected_conditions as EC - -from pypom import Region - - -class Sorter(Region): - """Helper class for sorting.""" - _root_locator = (By.ID, 'sorter') - _sort_by_type_locator = (By.CSS_SELECTOR, 'ul > li') - _updating_locator = (By.CSS_SELECTOR, '.updating') - - def sort_by(self, sort): - """Clicks the sort button for the requested sort-order.""" - els = self.find_elements(*self._sort_by_type_locator) - next(el for el in els if el.text == sort).click() - self.wait.until( - EC.invisibility_of_element_located(self._updating_locator)) diff --git a/tests/ui/pages/desktop/search.py b/tests/ui/pages/desktop/search.py index 5ae197f7e1..5a1a22c768 100644 --- a/tests/ui/pages/desktop/search.py +++ b/tests/ui/pages/desktop/search.py @@ -1,54 +1,77 @@ -from pypom import Region -from selenium.common.exceptions import NoSuchElementException +from pypom import Page, Region from selenium.webdriver.common.by import By - -from base import Base +from selenium.webdriver.support import expected_conditions as expected -class SearchResultList(Base): - """Search page""" - _results_locator = (By.CSS_SELECTOR, 'div.items div.item.addon') - _search_text_locator = (By.CSS_SELECTOR, '.primary > h1') +class Search(Page): + + _search_box_locator = (By.CLASS_NAME, 'SearchForm-query') + _submit_button_locator = (By.CLASS_NAME, 'SearchForm-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') def wait_for_page_to_load(self): - self.wait.until(lambda _: self.find_element( - self._search_text_locator).is_displayed()) + self.wait.until( + expected.invisibility_of_element_located( + (By.CLASS_NAME, 'LoadingText'))) return self @property - def results(self): - """List of results""" - elements = self.selenium.find_elements(*self._results_locator) - return [self.SearchResultItem(self, el) for el in elements] + def result_list(self): + return self.SearchResultList(self) - def sort_by(self, category, attribute): - from pages.desktop.regions.sorter import Sorter - Sorter(self).sort_by(category) + def filter_by_sort(self, value): + self.find_element(*self._search_filters_sort_locator).click() + self.find_element(*self._search_filters_sort_locator).send_keys(value) - class SearchResultItem(Region): - """Represents individual results on the search page.""" - _name_locator = (By.CSS_SELECTOR, 'h3 > a') - _rating_locator = (By.CSS_SELECTOR, '.rating .stars') - _users_sort_locator = (By.CSS_SELECTOR, '.vitals .adu') + def filter_by_type(self, value): + self.find_element(*self._search_filters_type_locator).click() + self.find_element(*self._search_filters_type_locator).send_keys(value) + + def filter_by_os(self, value): + self.find_element(*self._search_filters_os_locator).click() + self.find_element(*self._search_filters_os_locator).send_keys(value) + + class SearchResultList(Region): + + _result_locator = (By.CLASS_NAME, 'SearchResult') + _theme_locator = (By.CLASS_NAME, 'SearchResult--theme') + _extension_locator = (By.CLASS_NAME, 'SearchResult-name') @property - def name(self): - """Extension Name""" - return self.find_element(*self._name_locator).text + def extensions(self): + items = self.find_elements(*self._result_locator) + return [self.ResultListItems(self, el) for el in items] @property - def users(self): - """Extensions users""" - number = self.find_element(*self._users_sort_locator).text - if 'downloads' in number: - raise AssertionError('Found weekly downloads instead') - return int(number.split()[0].replace(',', '')) + def themes(self): + items = self.find_elements(*self._theme_locator) + return [self.ResultListItems(self, el) for el in items] - @property - def rating(self): - """Returns the rating""" - try: - rating = self.find_element(*self._rating_locator).text + class ResultListItems(Region): + + _rating_locator = (By.CSS_SELECTOR, '.Rating--small') + _search_item_name_locator = (By.CSS_SELECTOR, + '.SearchResult-contents > h2') + _users_locator = (By.CLASS_NAME, 'SearchResult-users-text') + + @property + def name(self): + return self.find_element(*self._search_item_name_locator).text + + def link(self): + self.find_element(*self._search_item_name_locator).click() + + @property + def users(self): + users = self.find_element(*self._users_locator).text + 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') return int(rating.split()[1]) - except NoSuchElementException: - return 0 diff --git a/tests/ui/pages/desktop/themes.py b/tests/ui/pages/desktop/themes.py index b29f9b7e89..292e453611 100644 --- a/tests/ui/pages/desktop/themes.py +++ b/tests/ui/pages/desktop/themes.py @@ -1,33 +1,20 @@ -from pypom import Region from selenium.webdriver.common.by import By -from base import Base +from pages.desktop.base import Base class Themes(Base): - """Themes page.""" + + URL_TEMPLATE = 'themes/' + + _browse_all_locator = (By.CSS_SELECTOR, '.Card-footer-link > a') + _title_locator = (By.CLASS_NAME, 'LandingPage-addonType-name') + def wait_for_page_to_load(self): - self.wait.until(lambda _: self.featured.themes[0].name) - return self + self.wait.until( + lambda _: self.is_element_displayed(*self._title_locator)) + return self.find_element(*self._title_locator) @property - def featured(self): - return self.Featured(self) - - class Featured(Region): - """Represents the Featured region on the themes page.""" - _root_locator = (By.CLASS_NAME, 'personas-featured') - _theme_locator = (By.CSS_SELECTOR, '.persona') - - @property - def themes(self): - theme = self.find_elements(*self._theme_locator) - return [Themes.Theme(self.page, el) for el in theme] - - class Theme(Region): - """Represents an individual theme.""" - _name_locator = (By.CSS_SELECTOR, 'h3') - - @property - def name(self): - return self.find_element(*self._name_locator).text + def browse_all(self): + self.find_element(*self._browse_all_locator).click() diff --git a/tests/ui/pages/desktop/regions/__init__.py b/tests/ui/regions/__init__.py similarity index 100% rename from tests/ui/pages/desktop/regions/__init__.py rename to tests/ui/regions/__init__.py diff --git a/tests/ui/regions/desktop/__init__.py b/tests/ui/regions/desktop/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/ui/regions/desktop/categories.py b/tests/ui/regions/desktop/categories.py new file mode 100644 index 0000000000..07b428e16b --- /dev/null +++ b/tests/ui/regions/desktop/categories.py @@ -0,0 +1,26 @@ +from pypom import Region +from selenium.webdriver.common.by import By + + +class Categories(Region): + + _root_locator = (By.CLASS_NAME, 'Categories') + _categories_locator = (By.CLASS_NAME, 'Categories-item') + _mobile_categories_locator = (By.CLASS_NAME, 'LandingPage-button') + + @property + def category_list(self): + items = self.find_elements(*self._categories_locator) + return [self.CategoryList(self, el) for el in items] + + class CategoryList(Region): + _name_locator = (By.CLASS_NAME, 'Categories-link') + + @property + def name(self): + return self.find_element(*self._name_locator).text + + def click(self): + self.root.click() + from pages.desktop.category import Category + return Category(self.selenium, self.page) diff --git a/tests/ui/regions/desktop/search.py b/tests/ui/regions/desktop/search.py new file mode 100644 index 0000000000..47ada71025 --- /dev/null +++ b/tests/ui/regions/desktop/search.py @@ -0,0 +1,34 @@ +from pypom import Region +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as expected + + +class Search(Region): + + _search_results_locator = (By.CLASS_NAME, 'SearchForm-suggestions-item') + + def wait_for_region_to_load(self): + self.wait.until( + expected.invisibility_of_element_located( + (By.CLASS_NAME, 'LoadingText'))) + return self + + @property + def result_list(self): + items = self.find_elements(*self._search_results_locator) + return [self.SearchResultList(self.page, el) for el in items] + + class SearchResultList(Region): + + _search_item_name_locator = (By.CLASS_NAME, 'Suggestion-name') + _search_item_link_locator = (By.CLASS_NAME, 'Suggestion') + + @property + def name(self): + return self.find_element(*self._search_item_name_locator).text + + def link(self): + self.find_element(*self._search_item_link_locator).click() + from pages.desktop.detail import Detail + return Detail( + self.selenium, self.page.base_url).wait_for_page_to_load() diff --git a/tests/ui/setup.cfg b/tests/ui/setup.cfg index 90bfa0df9f..be4d8d79c3 100644 --- a/tests/ui/setup.cfg +++ b/tests/ui/setup.cfg @@ -1,6 +1,6 @@ [tool:pytest] -addopts = -r=a -vs --showlocals --tb=short +addopts = -r=a -vs --showlocals --tb=short --html=ui-test.html --self-contained-html sensitive_url = mozilla\.(com|org) xfail_strict = true DJANGO_SETTINGS_MODULE = settings -base_url = http://olympia.test:80 +base_url = http://olympia-frontend.test:4000 diff --git a/tests/ui/test_home.py b/tests/ui/test_home.py index 4fbf6bfd4f..40a3eceb78 100644 --- a/tests/ui/test_home.py +++ b/tests/ui/test_home.py @@ -1,70 +1,71 @@ import pytest +from pages.desktop.categories import Categories +from pages.desktop.extensions import Extensions from pages.desktop.home import Home @pytest.mark.nondestructive -def test_there_are_ten_most_popular_extensions( - base_url, selenium): - """Ten most popular add-ons are listed""" +def test_there_are_6_extension_categories(base_url, selenium): page = Home(selenium, base_url).open() - assert len(page.most_popular.extensions) == 10 + assert len(page.extension_category.list) == 6 @pytest.mark.nondestructive -def test_most_popular_extensions_are_sorted_by_users( - base_url, selenium): - """Most popular add-ons are sorted by popularity""" +def test_there_are_6_theme_categories(base_url, selenium): page = Home(selenium, base_url).open() - extensions_page = page.most_popular.extensions - sorted_by_users = sorted(extensions_page, - key=lambda e: e.users, reverse=True) - assert sorted_by_users == extensions_page - - -@pytest.mark.smoke -@pytest.mark.nondestructive -def test_that_clicking_on_addon_name_loads_details_page( - base_url, selenium): - """Details page addon name matches clicked addon""" - page = Home(selenium, base_url).open() - name = page.most_popular.extensions[0].name - extension_page = page.most_popular.extensions[0].click() - assert name in extension_page.description_header.name - - -@pytest.mark.smoke -@pytest.mark.nondestructive -def test_that_featured_themes_exist_on_the_home( - base_url, selenium): - """Featured themes are displayed""" - page = Home(selenium, base_url).open() - assert len(page.featured_themes.themes) == 6 + assert len(page.theme_category.list) == 6 @pytest.mark.nondestructive -def test_that_clicking_see_all_themes_link_works( - base_url, selenium): - """Amount of featured themes matches on both pages""" +def test_extensions_section_load_correctly(base_url, selenium): page = Home(selenium, base_url).open() - themes = page.featured_themes.themes - theme_page = page.featured_themes.see_all() - assert len(themes) == len(theme_page.featured.themes) + ext_page = page.header.click_extensions() + assert 'Extensions' in ext_page.text @pytest.mark.nondestructive -def test_that_featured_extensions_exist_on_the_home( - base_url, selenium): - """Featured extensions exist on home page""" - page = Home(selenium, base_url).open() - assert len(page.featured_extensions.extensions) >= 1 +def test_explore_section_loads(base_url, selenium): + page = Extensions(selenium, base_url).open() + page.header.click_explore() + assert 'firefox/' in selenium.current_url @pytest.mark.nondestructive -def test_that_clicking_see_all_collections_link_works( - base_url, selenium): - """Amount of featured themes matches on both pages""" +def test_themes_section_loads(base_url, selenium): page = Home(selenium, base_url).open() - collections = page.featured_collections.collections - collections_page = page.featured_collections.see_all() - assert len(collections_page.collections) >= len(collections) + themes_page = page.header.click_themes() + assert 'Themes' in themes_page.text + + +@pytest.mark.nondestructive +def test_browse_all_button_loads_correct_page(base_url, selenium): + page = Home(selenium, base_url).open() + page.featured_extensions.browse_all + assert 'type=extension' in selenium.current_url + + +@pytest.mark.nondestructive +def test_browse_all_themes_button_loads_correct_page( + base_url, selenium): + page = Home(selenium, base_url).open() + page.popular_themes.browse_all + assert 'type=persona' in selenium.current_url + + +@pytest.mark.nondestructive +def test_category_loads_extensions(base_url, selenium): + page = Home(selenium, base_url).open() + category = page.extension_category.list[0] + category_name = category.name + category.click() + assert category_name in selenium.current_url + + +@pytest.mark.nondestructive +def test_category_section_loads_correct_category(base_url, selenium): + page = Categories(selenium, base_url).open() + item = page.category_list[0] + name = item.name + category = item.click() + assert name in category.header.name diff --git a/tests/ui/test_install.py b/tests/ui/test_install.py index c17713d20b..1c22c4517a 100644 --- a/tests/ui/test_install.py +++ b/tests/ui/test_install.py @@ -1,17 +1,17 @@ import pytest -from pages.desktop.details import Details +from pages.desktop.details import Detail @pytest.mark.nondestructive def test_addon_install( base_url, selenium, firefox, firefox_notifications): """Test that navigates to an addon and installs it.""" - selenium.get('{}/firefox/addon/ui-test-install'.format(base_url)) - addon = Details(selenium, base_url) - assert 'Ui-Addon-Install' in addon.description_header.name - addon.description_header.install_button.click() + selenium.get('{}/addon/ui-test-install'.format(base_url)) + addon = Detail(selenium, base_url) + assert 'Ui-Addon-Install' in addon.name + addon.install() firefox.browser.wait_for_notification( firefox_notifications.AddOnInstallBlocked).allow() firefox.browser.wait_for_notification( diff --git a/tests/ui/test_search.py b/tests/ui/test_search.py index 4297022de2..3a989104ae 100644 --- a/tests/ui/test_search.py +++ b/tests/ui/test_search.py @@ -1,31 +1,50 @@ +# -*- coding: utf-8 -*- import pytest from pages.desktop.home import Home +from pages.desktop.extensions import Extensions +from pages.desktop.themes import Themes +from pages.desktop.search import Search -@pytest.mark.smoke @pytest.mark.nondestructive -def test_that_searching_for_addon_returns_addon_as_first_result( - base_url, es_test, selenium): - """Test searching for an addon returns the addon.""" +def test_search_loads_and_navigates_to_correct_page(base_url, selenium): page = Home(selenium, base_url).open() - name = page.most_popular.extensions[0].name - search_page = page.search_for(name) - assert name in search_page.results[0].name - assert name in selenium.title + addon_name = page.featured_extensions.list[0].name + search = page.header.search_for(addon_name) + search_name = search.result_list.extensions[0].name + assert addon_name in search_name + assert search_name in search.result_list.extensions[0].name -@pytest.mark.native @pytest.mark.nondestructive +def test_search_loads_correct_results(base_url, selenium): + page = Home(selenium, base_url).open() + addon_name = page.featured_extensions.list[0].name + items = page.search_for(addon_name) + assert addon_name in items.result_list.extensions[0].name + + +@pytest.mark.nondestructive +def test_legacy_extensions_do_not_load(base_url, selenium): + page = Home(selenium, base_url).open() + term = 'Video Download Manager' + items = page.search_for(term) + for item in items.result_list.extensions: + assert term not in item.name + + @pytest.mark.parametrize('category, sort_attr', [ ['Most Users', 'users'], ['Top Rated', 'rating']]) def test_sorting_by( base_url, selenium, es_test, category, sort_attr): """Test searching for an addon and sorting.""" - page = Home(selenium, base_url).open() - name = page.most_popular.extensions[0].name - search_page = page.search_for(name) - search_page.sort_by(category, sort_attr) - results = [getattr(i, sort_attr) for i in search_page.results] + Home(selenium, base_url).open() + addon_name = 'Ui-addon' + selenium.get('{}/search/?&q={}&sort={}'.format( + base_url, addon_name, sort_attr)) + search_page = Search(selenium, base_url) + results = [getattr(i, sort_attr) + for i in search_page.result_list.extensions] assert sorted(results, reverse=True) == results diff --git a/tox.ini b/tox.ini index c179873b22..0b8173b554 100644 --- a/tox.ini +++ b/tox.ini @@ -56,7 +56,7 @@ commands = commands = make -f Makefile-docker update_deps make -f Makefile-docker ui-tests - pytest --driver Firefox --variables tests/ui/variables.json --html=ui-test.html --self-contained-html tests/ui {posargs} + pytest --driver Firefox -v tests/ui/ {posargs} [testenv:assets] commands =