Updated UI tests to use the new AMO frontend. (#7565)
* Uses addons-frontend docker image to create an instance of the new frontend. * Add testing for both mobile and desktop resolutions.
This commit is contained in:
Родитель
14f80b6a88
Коммит
9747bb84fe
|
@ -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"
|
||||
|
|
|
@ -18,4 +18,3 @@ port=9001
|
|||
|
||||
[rpcinterface:supervisor]
|
||||
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
|
@ -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
|
|
@ -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()
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
|
@ -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()
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
2
tox.ini
2
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 =
|
||||
|
|
Загрузка…
Ссылка в новой задаче