2013-05-24 06:48:53 -05:00
|
|
|
# Authors:
|
|
|
|
# Petr Vobornik <pvoborni@redhat.com>
|
|
|
|
#
|
|
|
|
# Copyright (C) 2013 Red Hat
|
|
|
|
# see file 'COPYING' for use and warranty information
|
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
"""
|
|
|
|
Base class for UI integration tests.
|
|
|
|
|
|
|
|
Contains browser driver and common tasks.
|
|
|
|
"""
|
2018-04-05 02:21:16 -05:00
|
|
|
from __future__ import print_function, absolute_import
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2014-04-28 09:59:50 -05:00
|
|
|
from datetime import datetime
|
2013-05-24 06:48:53 -05:00
|
|
|
import time
|
|
|
|
import re
|
|
|
|
import os
|
2014-04-28 09:59:50 -05:00
|
|
|
from functools import wraps
|
2017-12-06 10:01:57 -06:00
|
|
|
import unittest
|
2018-09-27 00:47:07 -05:00
|
|
|
from urllib.error import URLError
|
|
|
|
|
2018-03-13 13:02:45 -05:00
|
|
|
import paramiko
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2016-08-24 06:37:30 -05:00
|
|
|
|
2013-05-24 06:48:53 -05:00
|
|
|
try:
|
|
|
|
from selenium import webdriver
|
|
|
|
from selenium.common.exceptions import NoSuchElementException
|
2014-04-24 10:24:59 -05:00
|
|
|
from selenium.common.exceptions import InvalidElementStateException
|
2014-04-17 02:07:10 -05:00
|
|
|
from selenium.common.exceptions import StaleElementReferenceException
|
2014-04-28 10:04:36 -05:00
|
|
|
from selenium.common.exceptions import WebDriverException
|
2018-05-10 07:35:21 -05:00
|
|
|
from selenium.common.exceptions import ElementClickInterceptedException
|
2018-10-03 09:37:06 -05:00
|
|
|
from selenium.webdriver.common.action_chains import ActionChains
|
2013-05-24 06:48:53 -05:00
|
|
|
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
|
|
|
from selenium.webdriver.common.keys import Keys
|
|
|
|
from selenium.webdriver.common.by import By
|
|
|
|
from selenium.webdriver.chrome.options import Options as ChromeOptions
|
|
|
|
from selenium.webdriver.support.wait import WebDriverWait
|
|
|
|
from selenium.webdriver.support.ui import Select
|
|
|
|
NO_SELENIUM = False
|
|
|
|
except ImportError:
|
|
|
|
NO_SELENIUM = True
|
|
|
|
try:
|
|
|
|
import yaml
|
|
|
|
NO_YAML = False
|
|
|
|
except ImportError:
|
|
|
|
NO_YAML = True
|
2014-05-29 07:47:17 -05:00
|
|
|
from ipaplatform.paths import paths
|
2013-05-24 06:48:53 -05:00
|
|
|
|
|
|
|
ENV_MAP = {
|
|
|
|
'MASTER': 'ipa_server',
|
|
|
|
'ADMINID': 'ipa_admin',
|
|
|
|
'ADMINPW': 'ipa_password',
|
|
|
|
'DOMAIN': 'ipa_domain',
|
|
|
|
'IPA_REALM': 'ipa_realm',
|
|
|
|
'IPA_IP': 'ipa_ip',
|
|
|
|
'IPA_NO_CA': 'no_ca',
|
|
|
|
'IPA_NO_DNS': 'no_dns',
|
|
|
|
'IPA_HAS_TRUSTS': 'has_trusts',
|
2016-10-25 09:53:14 -05:00
|
|
|
'IPA_HAS_KRA': 'has_kra',
|
2013-05-24 06:48:53 -05:00
|
|
|
'IPA_HOST_CSR_PATH': 'host_csr_path',
|
|
|
|
'IPA_SERVICE_CSR_PATH': 'service_csr_path',
|
|
|
|
'AD_DOMAIN': 'ad_domain',
|
|
|
|
'AD_DC': 'ad_dc',
|
|
|
|
'AD_ADMIN': 'ad_admin',
|
|
|
|
'AD_PASSWORD': 'ad_password',
|
|
|
|
'AD_DC_IP': 'ad_dc_ip',
|
|
|
|
'TRUST_SECRET': 'trust_secret',
|
|
|
|
'SEL_TYPE': 'type',
|
|
|
|
'SEL_BROWSER': 'browser',
|
|
|
|
'SEL_HOST': 'host',
|
|
|
|
'FF_PROFILE': 'ff_profile',
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFAULT_BROWSER = 'firefox'
|
|
|
|
DEFAULT_PORT = 4444
|
|
|
|
DEFAULT_TYPE = 'local'
|
|
|
|
|
2013-07-24 06:24:02 -05:00
|
|
|
|
2014-04-28 09:59:50 -05:00
|
|
|
def screenshot(fn):
|
|
|
|
"""
|
|
|
|
Decorator for saving screenshot on exception (test fail)
|
|
|
|
Should be applied on methods of UI_driver subclasses
|
|
|
|
"""
|
|
|
|
@wraps(fn)
|
2018-10-22 02:23:04 -05:00
|
|
|
def screenshot_wrapper(*args, **kwargs):
|
2014-04-28 09:59:50 -05:00
|
|
|
try:
|
2018-10-22 02:23:04 -05:00
|
|
|
return fn(*args, **kwargs)
|
2017-12-06 10:01:57 -06:00
|
|
|
except unittest.SkipTest:
|
2014-04-28 09:59:50 -05:00
|
|
|
raise
|
|
|
|
except Exception:
|
|
|
|
self = args[0]
|
|
|
|
name = '%s_%s_%s' % (
|
|
|
|
datetime.now().isoformat(),
|
|
|
|
self.__class__.__name__,
|
|
|
|
fn.__name__)
|
|
|
|
self.take_screenshot(name)
|
|
|
|
raise
|
|
|
|
|
|
|
|
return screenshot_wrapper
|
|
|
|
|
|
|
|
|
2018-09-26 04:59:50 -05:00
|
|
|
class UI_driver:
|
2013-05-24 06:48:53 -05:00
|
|
|
"""
|
|
|
|
Base class for all UI integration tests
|
|
|
|
"""
|
|
|
|
|
2018-09-18 02:17:33 -05:00
|
|
|
request_timeout = 60
|
|
|
|
|
2013-05-24 06:48:53 -05:00
|
|
|
@classmethod
|
2014-10-07 05:48:22 -05:00
|
|
|
def setup_class(cls):
|
2013-05-24 06:48:53 -05:00
|
|
|
if NO_SELENIUM:
|
2017-12-06 10:01:57 -06:00
|
|
|
raise unittest.SkipTest('Selenium not installed')
|
2018-08-30 01:58:35 -05:00
|
|
|
cls.load_config()
|
|
|
|
|
|
|
|
def setup(self):
|
2018-09-18 02:17:33 -05:00
|
|
|
self.driver = self.get_driver()
|
2015-02-19 08:04:47 -06:00
|
|
|
self.driver.maximize_window()
|
2014-10-10 09:04:05 -05:00
|
|
|
|
2018-09-11 08:24:06 -05:00
|
|
|
def teardown(self):
|
|
|
|
self.driver.delete_all_cookies()
|
2018-09-18 02:17:33 -05:00
|
|
|
self.driver.quit()
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2018-08-30 01:58:35 -05:00
|
|
|
@classmethod
|
|
|
|
def load_config(cls):
|
2013-05-24 06:48:53 -05:00
|
|
|
"""
|
|
|
|
Load configuration
|
|
|
|
|
|
|
|
1) From ~/.ipa/ui_test.conf
|
|
|
|
2) From environmental variables
|
|
|
|
"""
|
|
|
|
|
|
|
|
# load config file
|
|
|
|
path = os.path.join(os.path.expanduser("~"), ".ipa/ui_test.conf")
|
|
|
|
if not NO_YAML and os.path.isfile(path):
|
|
|
|
try:
|
|
|
|
with open(path, 'r') as conf:
|
2018-08-30 01:58:35 -05:00
|
|
|
cls.config = yaml.load(conf)
|
2015-07-30 09:49:29 -05:00
|
|
|
except yaml.YAMLError as e:
|
2017-12-06 10:01:57 -06:00
|
|
|
raise unittest.SkipTest("Invalid Web UI config.\n%s" % e)
|
2015-07-30 09:49:29 -05:00
|
|
|
except IOError as e:
|
2017-12-06 10:01:57 -06:00
|
|
|
raise unittest.SkipTest(
|
|
|
|
"Can't load Web UI test config: %s" % e
|
|
|
|
)
|
2013-05-24 06:48:53 -05:00
|
|
|
else:
|
2018-08-30 01:58:35 -05:00
|
|
|
cls.config = {}
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2018-08-30 01:58:35 -05:00
|
|
|
c = cls.config
|
2013-05-24 06:48:53 -05:00
|
|
|
|
|
|
|
# override with environmental variables
|
Use Python3-compatible dict method names
Python 2 has keys()/values()/items(), which return lists,
iterkeys()/itervalues()/iteritems(), which return iterators,
and viewkeys()/viewvalues()/viewitems() which return views.
Python 3 has only keys()/values()/items(), which return views.
To get iterators, one can use iter() or a for loop/comprehension;
for lists there's the list() constructor.
When iterating through the entire dict, without modifying the dict,
the difference between Python 2's items() and iteritems() is
negligible, especially on small dicts (the main overhead is
extra memory, not CPU time). In the interest of simpler code,
this patch changes many instances of iteritems() to items(),
iterkeys() to keys() etc.
In other cases, helpers like six.itervalues are used.
Reviewed-By: Christian Heimes <cheimes@redhat.com>
Reviewed-By: Jan Cholasta <jcholast@redhat.com>
2015-08-11 06:51:14 -05:00
|
|
|
for k, v in ENV_MAP.items():
|
2013-05-24 06:48:53 -05:00
|
|
|
val = os.environ.get(k)
|
|
|
|
if val is not None:
|
|
|
|
c[v] = val
|
|
|
|
|
|
|
|
# apply defaults
|
|
|
|
if 'port' not in c:
|
|
|
|
c['port'] = DEFAULT_PORT
|
|
|
|
if 'browser' not in c:
|
|
|
|
c['browser'] = DEFAULT_BROWSER
|
|
|
|
if 'type' not in c:
|
|
|
|
c['type'] = DEFAULT_TYPE
|
|
|
|
|
2018-08-30 01:58:35 -05:00
|
|
|
@classmethod
|
|
|
|
def get_driver(cls):
|
2013-05-24 06:48:53 -05:00
|
|
|
"""
|
|
|
|
Get WebDriver according to configuration
|
|
|
|
"""
|
2018-08-30 01:58:35 -05:00
|
|
|
browser = cls.config["browser"]
|
|
|
|
port = cls.config["port"]
|
|
|
|
driver_type = cls.config["type"]
|
2013-05-24 06:48:53 -05:00
|
|
|
|
|
|
|
options = None
|
|
|
|
|
|
|
|
if browser == 'chromium':
|
|
|
|
options = ChromeOptions()
|
2014-05-29 07:47:17 -05:00
|
|
|
options.binary_location = paths.CHROMIUM_BROWSER
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2013-07-24 10:22:40 -05:00
|
|
|
if driver_type == 'remote':
|
2018-08-30 01:58:35 -05:00
|
|
|
if 'host' not in cls.config:
|
2017-12-06 10:01:57 -06:00
|
|
|
raise unittest.SkipTest('Selenium server host not configured')
|
2018-08-30 01:58:35 -05:00
|
|
|
host = cls.config["host"]
|
2013-05-24 06:48:53 -05:00
|
|
|
|
|
|
|
if browser == 'chrome':
|
|
|
|
capabilities = DesiredCapabilities.CHROME
|
|
|
|
elif browser == 'chromium':
|
|
|
|
capabilities = options.to_capabilities()
|
|
|
|
elif browser == 'ie':
|
|
|
|
capabilities = DesiredCapabilities.INTERNETEXPLORER
|
|
|
|
else:
|
|
|
|
capabilities = DesiredCapabilities.FIREFOX
|
|
|
|
try:
|
|
|
|
driver = webdriver.Remote(
|
|
|
|
command_executor='http://%s:%d/wd/hub' % (host, port),
|
|
|
|
desired_capabilities=capabilities)
|
2015-07-30 09:49:29 -05:00
|
|
|
except URLError as e:
|
2017-12-06 10:01:57 -06:00
|
|
|
raise unittest.SkipTest(
|
|
|
|
'Error connecting to selenium server: %s' % e
|
|
|
|
)
|
2015-07-30 09:49:29 -05:00
|
|
|
except RuntimeError as e:
|
2017-12-06 10:01:57 -06:00
|
|
|
raise unittest.SkipTest(
|
|
|
|
'Error while establishing webdriver: %s' % e
|
|
|
|
)
|
2013-05-24 06:48:53 -05:00
|
|
|
else:
|
|
|
|
try:
|
|
|
|
if browser == 'chrome' or browser == 'chromium':
|
|
|
|
driver = webdriver.Chrome(chrome_options=options)
|
|
|
|
elif browser == 'ie':
|
|
|
|
driver = webdriver.Ie()
|
|
|
|
else:
|
|
|
|
fp = None
|
2018-08-30 01:58:35 -05:00
|
|
|
if "ff_profile" in cls.config:
|
|
|
|
fp = webdriver.FirefoxProfile(cls.config["ff_profile"])
|
|
|
|
ff_log_path = cls.config.get("geckodriver_log_path")
|
2017-12-07 10:17:23 -06:00
|
|
|
driver = webdriver.Firefox(fp, log_path=ff_log_path)
|
2015-07-30 09:49:29 -05:00
|
|
|
except URLError as e:
|
2017-12-06 10:01:57 -06:00
|
|
|
raise unittest.SkipTest(
|
|
|
|
'Error connecting to selenium server: %s' % e
|
|
|
|
)
|
2015-07-30 09:49:29 -05:00
|
|
|
except RuntimeError as e:
|
2017-12-06 10:01:57 -06:00
|
|
|
raise unittest.SkipTest(
|
|
|
|
'Error while establishing webdriver: %s' % e
|
|
|
|
)
|
2013-05-24 06:48:53 -05:00
|
|
|
|
|
|
|
return driver
|
|
|
|
|
2013-07-24 10:22:40 -05:00
|
|
|
def find(self, expression, by='id', context=None, many=False, strict=False):
|
2013-05-24 06:48:53 -05:00
|
|
|
"""
|
|
|
|
Helper which calls selenium find_element_by_xxx methods.
|
|
|
|
|
|
|
|
expression: search expression
|
|
|
|
by: selenium.webdriver.common.by
|
|
|
|
context: element to search on. Default: driver
|
2013-07-24 10:22:40 -05:00
|
|
|
many: all matching elements
|
2013-05-24 06:48:53 -05:00
|
|
|
strict: error out when element is not found
|
|
|
|
|
|
|
|
Returns None instead of raising exception when element is not found.
|
|
|
|
"""
|
|
|
|
|
|
|
|
assert expression, 'expression is missing'
|
|
|
|
|
|
|
|
if context is None:
|
|
|
|
context = self.driver
|
|
|
|
|
2013-07-24 10:22:40 -05:00
|
|
|
if not many:
|
2013-05-24 06:48:53 -05:00
|
|
|
method_name = 'find_element'
|
|
|
|
else:
|
|
|
|
method_name = 'find_elements'
|
|
|
|
|
|
|
|
try:
|
|
|
|
func = getattr(context, method_name)
|
|
|
|
result = func(by, expression)
|
|
|
|
except NoSuchElementException:
|
|
|
|
if strict:
|
|
|
|
raise
|
|
|
|
else:
|
|
|
|
result = None
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
2018-10-05 08:31:57 -05:00
|
|
|
def find_by_selector(self, expression, context=None, **kwargs):
|
|
|
|
return self.find(expression, by=By.CSS_SELECTOR, context=context,
|
|
|
|
**kwargs)
|
|
|
|
|
2013-05-24 06:48:53 -05:00
|
|
|
def files_loaded(self):
|
2013-06-26 09:10:35 -05:00
|
|
|
"""
|
|
|
|
Test if dependencies were loaded. (Checks if UI has been rendered)
|
|
|
|
"""
|
2014-04-17 02:07:10 -05:00
|
|
|
indicator = self.find(".global-activity-indicator", By.CSS_SELECTOR)
|
2013-05-24 06:48:53 -05:00
|
|
|
return indicator is not None
|
|
|
|
|
|
|
|
def has_ca(self):
|
2013-06-26 09:10:35 -05:00
|
|
|
"""
|
|
|
|
FreeIPA server was installed with CA.
|
|
|
|
"""
|
2013-12-06 09:39:06 -06:00
|
|
|
return not self.config.get('no_ca')
|
2013-05-24 06:48:53 -05:00
|
|
|
|
|
|
|
def has_dns(self):
|
2013-06-26 09:10:35 -05:00
|
|
|
"""
|
|
|
|
FreeIPA server was installed with DNS.
|
|
|
|
"""
|
2013-12-06 09:39:06 -06:00
|
|
|
return not self.config.get('no_dns')
|
2013-05-24 06:48:53 -05:00
|
|
|
|
|
|
|
def has_trusts(self):
|
2013-06-26 09:10:35 -05:00
|
|
|
"""
|
|
|
|
FreeIPA server was installed with Trusts.
|
|
|
|
"""
|
2013-12-06 09:39:06 -06:00
|
|
|
return self.config.get('has_trusts')
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2016-10-25 09:53:14 -05:00
|
|
|
def has_kra(self):
|
|
|
|
"""
|
|
|
|
FreeIPA server was installed with Kra.
|
|
|
|
"""
|
|
|
|
return self.config.get('has_kra')
|
|
|
|
|
2013-05-24 06:48:53 -05:00
|
|
|
def has_active_request(self):
|
|
|
|
"""
|
|
|
|
Check if there is running AJAX request
|
|
|
|
"""
|
2014-04-17 02:07:10 -05:00
|
|
|
global_indicators = self.find(".global-activity-indicator", By.CSS_SELECTOR, many=True)
|
|
|
|
for el in global_indicators:
|
|
|
|
try:
|
|
|
|
if not self.has_class(el, 'closed'):
|
|
|
|
return True
|
|
|
|
except StaleElementReferenceException:
|
|
|
|
# we don't care. Happens when indicator is part of removed dialog.
|
|
|
|
continue
|
|
|
|
return False
|
2013-05-24 06:48:53 -05:00
|
|
|
|
|
|
|
def wait(self, seconds=0.2):
|
2013-06-26 09:10:35 -05:00
|
|
|
"""
|
|
|
|
Wait specific amount of seconds
|
|
|
|
"""
|
2013-05-24 06:48:53 -05:00
|
|
|
time.sleep(seconds)
|
|
|
|
|
|
|
|
def wait_for_request(self, implicit=0.2, n=1, d=0):
|
|
|
|
"""
|
|
|
|
Wait for AJAX request to finish
|
|
|
|
"""
|
|
|
|
runner = self
|
|
|
|
|
2016-09-26 11:22:22 -05:00
|
|
|
for _i in range(n):
|
2013-05-24 06:48:53 -05:00
|
|
|
self.wait(implicit)
|
2013-06-26 08:18:05 -05:00
|
|
|
WebDriverWait(self.driver, self.request_timeout).until_not(lambda d: runner.has_active_request())
|
2013-05-24 06:48:53 -05:00
|
|
|
self.wait()
|
|
|
|
self.wait(d)
|
|
|
|
|
|
|
|
def xpath_has_val(self, attr, val):
|
|
|
|
"""
|
|
|
|
Create xpath expression for matching a presence of item in attribute
|
|
|
|
value where value is a list of items separated by space.
|
|
|
|
"""
|
|
|
|
return "contains(concat(' ',normalize-space(@%s), ' '),' %s ')" % (attr, val)
|
|
|
|
|
2013-10-29 10:01:25 -05:00
|
|
|
def init_app(self, login=None, password=None):
|
2013-05-24 06:48:53 -05:00
|
|
|
"""
|
|
|
|
Load and login
|
|
|
|
"""
|
|
|
|
self.load()
|
|
|
|
self.wait(0.5)
|
2013-10-29 10:01:25 -05:00
|
|
|
self.login(login, password)
|
2013-05-24 06:48:53 -05:00
|
|
|
# metadata + default page
|
|
|
|
self.wait_for_request(n=5)
|
|
|
|
|
|
|
|
def load(self):
|
2013-06-26 09:10:35 -05:00
|
|
|
"""
|
|
|
|
Navigate to Web UI first page and wait for loading of all dependencies.
|
|
|
|
"""
|
2018-02-08 12:10:50 -06:00
|
|
|
# if is not any of above cases, we need to load the application for
|
|
|
|
# its first time entering the URL in the address bar
|
2013-05-24 06:48:53 -05:00
|
|
|
self.driver.get(self.get_base_url())
|
|
|
|
runner = self
|
|
|
|
WebDriverWait(self.driver, 10).until(lambda d: runner.files_loaded())
|
2018-02-08 12:10:50 -06:00
|
|
|
self.wait_for_request()
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2013-10-29 10:01:25 -05:00
|
|
|
def login(self, login=None, password=None, new_password=None):
|
2013-05-24 06:48:53 -05:00
|
|
|
"""
|
|
|
|
Log in if user is not logged in.
|
|
|
|
"""
|
2018-02-09 11:38:00 -06:00
|
|
|
if self.logged_in():
|
|
|
|
return
|
|
|
|
|
2018-04-19 08:27:42 -05:00
|
|
|
if login is None:
|
2018-02-09 11:38:00 -06:00
|
|
|
login = self.config['ipa_admin']
|
2018-04-19 08:27:42 -05:00
|
|
|
if password is None:
|
2018-02-09 11:38:00 -06:00
|
|
|
password = self.config['ipa_password']
|
|
|
|
if not new_password:
|
|
|
|
new_password = password
|
|
|
|
|
|
|
|
auth = self.get_login_screen()
|
|
|
|
login_tb = self.find("//input[@type='text'][@name='username']",
|
|
|
|
'xpath', auth, strict=True)
|
|
|
|
psw_tb = self.find("//input[@type='password'][@name='password']",
|
|
|
|
'xpath', auth, strict=True)
|
|
|
|
login_tb.send_keys(login)
|
|
|
|
psw_tb.send_keys(password)
|
|
|
|
psw_tb.send_keys(Keys.RETURN)
|
|
|
|
self.wait(0.5)
|
|
|
|
self.wait_for_request(n=2)
|
|
|
|
|
|
|
|
# reset password if needed
|
2018-01-23 12:02:01 -06:00
|
|
|
if self.login_screen_visible():
|
|
|
|
newpw_tb = self.find("//input[@type='password'][@name='new_password']", 'xpath', auth)
|
|
|
|
verify_tb = self.find("//input[@type='password'][@name='verify_password']", 'xpath', auth)
|
|
|
|
if newpw_tb and newpw_tb.is_displayed():
|
|
|
|
newpw_tb.send_keys(new_password)
|
|
|
|
verify_tb.send_keys(new_password)
|
|
|
|
verify_tb.send_keys(Keys.RETURN)
|
|
|
|
self.wait(0.5)
|
|
|
|
self.wait_for_request(n=2)
|
2013-05-24 06:48:53 -05:00
|
|
|
|
|
|
|
def logged_in(self):
|
|
|
|
"""
|
|
|
|
Check if user is logged in
|
|
|
|
"""
|
2013-11-07 11:03:38 -06:00
|
|
|
login_as = self.find('loggedinas', 'class name')
|
|
|
|
visible_name = len(login_as.text) > 0
|
2014-02-21 09:44:45 -06:00
|
|
|
logged_in = not self.login_screen_visible() and visible_name
|
2013-05-24 06:48:53 -05:00
|
|
|
return logged_in
|
|
|
|
|
2013-10-29 10:01:25 -05:00
|
|
|
def logout(self):
|
2018-04-19 08:27:42 -05:00
|
|
|
|
|
|
|
runner = self
|
|
|
|
|
2013-11-07 11:03:38 -06:00
|
|
|
self.profile_menu_action('logout')
|
2018-04-19 08:27:42 -05:00
|
|
|
# it may take some time to get login screen visible
|
|
|
|
WebDriverWait(self.driver, self.request_timeout).until(
|
|
|
|
lambda d: runner.login_screen_visible())
|
|
|
|
|
2018-01-23 12:02:01 -06:00
|
|
|
assert self.login_screen_visible()
|
2013-10-29 10:01:25 -05:00
|
|
|
|
2014-02-21 09:44:45 -06:00
|
|
|
def get_login_screen(self):
|
2013-05-24 06:48:53 -05:00
|
|
|
"""
|
2014-02-21 09:44:45 -06:00
|
|
|
Get reference of login screen
|
2013-05-24 06:48:53 -05:00
|
|
|
"""
|
2018-01-23 12:02:01 -06:00
|
|
|
return self.find('.login-pf', By.CSS_SELECTOR)
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2014-02-21 09:44:45 -06:00
|
|
|
def login_screen_visible(self):
|
2013-05-24 06:48:53 -05:00
|
|
|
"""
|
2014-02-21 09:44:45 -06:00
|
|
|
Check if login screen is visible
|
2013-05-24 06:48:53 -05:00
|
|
|
"""
|
2014-02-21 09:44:45 -06:00
|
|
|
screen = self.get_login_screen()
|
|
|
|
return screen and screen.is_displayed()
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2014-04-28 09:59:50 -05:00
|
|
|
def take_screenshot(self, name):
|
|
|
|
if self.config.get('save_screenshots'):
|
|
|
|
scr_dir = self.config.get('screenshot_dir')
|
|
|
|
path = name + '.png'
|
|
|
|
if scr_dir:
|
|
|
|
path = os.path.join(scr_dir, path)
|
|
|
|
self.driver.get_screenshot_as_file(path)
|
|
|
|
|
2013-05-24 06:48:53 -05:00
|
|
|
def navigate_to_entity(self, entity, facet=None):
|
|
|
|
self.driver.get(self.get_url(entity, facet))
|
2013-07-24 06:24:02 -05:00
|
|
|
self.wait_for_request(n=3, d=0.4)
|
2013-05-24 06:48:53 -05:00
|
|
|
|
|
|
|
def navigate_by_menu(self, item, complete=True):
|
|
|
|
"""
|
|
|
|
Navigate by using menu
|
|
|
|
"""
|
|
|
|
|
|
|
|
if complete:
|
|
|
|
parts = item.split('/')
|
|
|
|
if len(parts) > 1:
|
|
|
|
parent = parts[0:-1]
|
|
|
|
self.navigate_by_menu('/'.join(parent), complete)
|
|
|
|
|
2013-11-07 11:03:38 -06:00
|
|
|
s = ".navbar a[href='#%s']" % item
|
2013-05-24 06:48:53 -05:00
|
|
|
link = self.find(s, By.CSS_SELECTOR, strict=True)
|
2014-07-02 08:09:22 -05:00
|
|
|
assert link.is_displayed(), 'Navigation link is not displayed: %s' % item
|
2013-05-24 06:48:53 -05:00
|
|
|
link.click()
|
|
|
|
self.wait_for_request()
|
|
|
|
self.wait_for_request(0.4)
|
|
|
|
|
|
|
|
def navigate_by_breadcrumb(self, item):
|
|
|
|
"""
|
|
|
|
Navigate by breadcrumb navigation
|
|
|
|
"""
|
|
|
|
facet = self.get_facet()
|
2014-04-11 06:47:27 -05:00
|
|
|
nav = self.find('.breadcrumb', By.CSS_SELECTOR, facet, strict=True)
|
2013-05-24 06:48:53 -05:00
|
|
|
a = self.find(item, By.LINK_TEXT, nav, strict=True)
|
|
|
|
a.click()
|
|
|
|
self.wait_for_request()
|
|
|
|
self.wait_for_request(0.4)
|
|
|
|
|
|
|
|
def switch_to_facet(self, name):
|
|
|
|
"""
|
|
|
|
Click on tab with given name
|
|
|
|
"""
|
|
|
|
facet = self.get_facet()
|
2016-10-25 09:54:28 -05:00
|
|
|
tabs = "div.facet-tabs"
|
|
|
|
sidebar = "div.sidebar-pf"
|
|
|
|
|
|
|
|
facets_container = self.find(tabs, By.CSS_SELECTOR, facet)
|
|
|
|
|
|
|
|
# handle sidebar instead of facet-tabs
|
|
|
|
# the webui facet can have only the facet-tabs OR sidebar, not both
|
|
|
|
if not facets_container:
|
|
|
|
facets_container = self.find(sidebar, By.CSS_SELECTOR, facet)
|
|
|
|
|
|
|
|
s = "li[name='%s'] a" % name
|
|
|
|
link = self.find(s, By.CSS_SELECTOR, facets_container, strict=True)
|
2013-05-24 06:48:53 -05:00
|
|
|
link.click()
|
|
|
|
# double wait because of facet's paging
|
|
|
|
self.wait_for_request(0.5)
|
|
|
|
self.wait_for_request()
|
|
|
|
|
|
|
|
def get_url(self, entity, facet=None):
|
|
|
|
"""
|
|
|
|
Create entity url
|
|
|
|
"""
|
|
|
|
url = [self.get_base_url(), '#', 'e', entity]
|
|
|
|
if facet:
|
|
|
|
url.append(facet)
|
|
|
|
return '/'.join(url)
|
|
|
|
|
|
|
|
def get_base_url(self):
|
2013-06-26 09:10:35 -05:00
|
|
|
"""
|
|
|
|
Get FreeIPA Web UI url
|
|
|
|
"""
|
2013-05-24 06:48:53 -05:00
|
|
|
host = self.config.get('ipa_server')
|
|
|
|
if not host:
|
|
|
|
self.skip('FreeIPA server hostname not configured')
|
|
|
|
return 'https://%s/ipa/ui' % host
|
|
|
|
|
|
|
|
def get_facet(self):
|
|
|
|
"""
|
|
|
|
Get currently displayed facet
|
|
|
|
"""
|
|
|
|
facet = self.find('.active-facet', By.CSS_SELECTOR)
|
|
|
|
assert facet is not None, "Current facet not found"
|
|
|
|
return facet
|
|
|
|
|
|
|
|
def get_facet_info(self, facet=None):
|
|
|
|
"""
|
|
|
|
Get information of currently displayed facet
|
|
|
|
"""
|
|
|
|
info = {}
|
|
|
|
|
|
|
|
# get facet
|
|
|
|
if facet is None:
|
|
|
|
facet = self.get_facet()
|
|
|
|
info["element"] = facet
|
|
|
|
|
|
|
|
#get facet name and entity
|
|
|
|
info["name"] = facet.get_attribute('data-name')
|
|
|
|
info["entity"] = facet.get_attribute('data-entity')
|
|
|
|
|
|
|
|
# get facet title
|
|
|
|
el = self.find(".facet-header h3 *:first-child", By.CSS_SELECTOR, facet)
|
2013-07-24 06:24:02 -05:00
|
|
|
if el:
|
|
|
|
info["title"] = el.text
|
2013-05-24 06:48:53 -05:00
|
|
|
|
|
|
|
# get facet pkey
|
|
|
|
el = self.find(".facet-header h3 span.facet-pkey", By.CSS_SELECTOR, facet)
|
2013-07-24 06:24:02 -05:00
|
|
|
if el:
|
|
|
|
info["pkey"] = el.text
|
2013-05-24 06:48:53 -05:00
|
|
|
|
|
|
|
return info
|
|
|
|
|
|
|
|
def get_dialogs(self, strict=False, name=None):
|
|
|
|
"""
|
|
|
|
Get all dialogs in DOM
|
|
|
|
"""
|
2014-04-16 05:04:59 -05:00
|
|
|
s = '.modal-dialog'
|
2013-05-24 06:48:53 -05:00
|
|
|
if name:
|
2014-04-16 05:04:59 -05:00
|
|
|
s += "[data-name='%s']" % name
|
2013-07-24 10:22:40 -05:00
|
|
|
dialogs = self.find(s, By.CSS_SELECTOR, many=True)
|
2013-05-24 06:48:53 -05:00
|
|
|
if strict:
|
|
|
|
assert dialogs, "No dialogs found"
|
|
|
|
return dialogs
|
|
|
|
|
|
|
|
def get_dialog(self, strict=False, name=None):
|
|
|
|
"""
|
|
|
|
Get last opened dialog
|
|
|
|
"""
|
|
|
|
dialogs = self.get_dialogs(strict, name)
|
|
|
|
dialog = None
|
|
|
|
if len(dialogs):
|
|
|
|
dialog = dialogs[-1]
|
|
|
|
return dialog
|
|
|
|
|
|
|
|
def get_last_error_dialog(self, dialog_name='error_dialog'):
|
|
|
|
"""
|
|
|
|
Get last opened error dialog or None.
|
|
|
|
"""
|
2014-04-16 05:04:59 -05:00
|
|
|
s = ".modal-dialog[data-name='%s']" % dialog_name
|
2013-07-24 10:22:40 -05:00
|
|
|
dialogs = self.find(s, By.CSS_SELECTOR, many=True)
|
2013-05-24 06:48:53 -05:00
|
|
|
dialog = None
|
|
|
|
if dialogs:
|
|
|
|
dialog = dialogs[-1]
|
|
|
|
return dialog
|
|
|
|
|
|
|
|
def get_dialog_info(self):
|
|
|
|
"""
|
|
|
|
Get last open dialog info: name, text if any.
|
|
|
|
Returns None if no dialog is open.
|
|
|
|
"""
|
|
|
|
dialog = self.get_dialog()
|
|
|
|
|
|
|
|
info = None
|
|
|
|
if dialog:
|
2014-04-16 05:04:59 -05:00
|
|
|
body = self.find('.modal-body', By.CSS_SELECTOR, dialog, strict=True)
|
2013-05-24 06:48:53 -05:00
|
|
|
info = {
|
2013-10-18 05:14:25 -05:00
|
|
|
'name': dialog.get_attribute('data-name'),
|
|
|
|
'text': body.text,
|
2013-05-24 06:48:53 -05:00
|
|
|
}
|
|
|
|
return info
|
|
|
|
|
2013-07-17 03:47:57 -05:00
|
|
|
def execute_api_from_ui(self, method, args, options, timeout=30):
|
|
|
|
"""
|
|
|
|
Executes FreeIPA API command/method from Web UI
|
|
|
|
"""
|
|
|
|
script = """
|
|
|
|
var method = arguments[0];
|
|
|
|
var args = arguments[1];
|
|
|
|
var options = arguments[2];
|
|
|
|
var callback = arguments[arguments.length - 1];
|
2014-03-10 11:05:51 -05:00
|
|
|
var rpc = require('freeipa/rpc');
|
2013-07-17 03:47:57 -05:00
|
|
|
|
2014-03-10 11:05:51 -05:00
|
|
|
var cmd = rpc.command({
|
2013-07-17 03:47:57 -05:00
|
|
|
method: method,
|
|
|
|
args: args,
|
|
|
|
options: options,
|
|
|
|
on_success: callback,
|
|
|
|
on_error: callback
|
|
|
|
});
|
|
|
|
|
|
|
|
cmd.execute();
|
|
|
|
"""
|
|
|
|
self.driver.set_script_timeout(timeout)
|
|
|
|
result = self.driver.execute_async_script(script, *[method, args, options])
|
|
|
|
return result
|
|
|
|
|
2013-05-24 06:48:53 -05:00
|
|
|
def click_on_link(self, text, parent=None):
|
|
|
|
"""
|
|
|
|
Click on link with given text and parent.
|
|
|
|
"""
|
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
|
|
|
|
|
|
|
link = self.find(text, By.LINK_TEXT, parent, strict=True)
|
|
|
|
link.click()
|
|
|
|
|
2018-05-16 05:36:43 -05:00
|
|
|
def click_undo_button(self, field, parent=None):
|
|
|
|
"""
|
|
|
|
Click undo button/s of particular field
|
|
|
|
"""
|
|
|
|
self.assert_undo_button(field)
|
|
|
|
undo_btns = self.get_undo_buttons(field, parent)
|
|
|
|
for btn in undo_btns:
|
|
|
|
btn.click()
|
|
|
|
self.assert_undo_button(field, visible=False)
|
|
|
|
|
2013-05-24 06:48:53 -05:00
|
|
|
def facet_button_click(self, name):
|
|
|
|
"""
|
|
|
|
Click on facet button with given name
|
|
|
|
"""
|
|
|
|
facet = self.get_facet()
|
2014-05-13 06:00:16 -05:00
|
|
|
s = ".facet-controls button[name=%s]" % name
|
2013-05-24 06:48:53 -05:00
|
|
|
self._button_click(s, facet, name)
|
|
|
|
|
|
|
|
def dialog_button_click(self, name, dialog=None):
|
|
|
|
"""
|
|
|
|
Click on dialog button with given name
|
|
|
|
|
|
|
|
Chooses last dialog if none is supplied
|
|
|
|
"""
|
|
|
|
if not dialog:
|
|
|
|
dialog = self.get_dialog(strict=True)
|
|
|
|
|
2014-07-22 09:39:36 -05:00
|
|
|
s = ".rcue-dialog-buttons button[name='%s']" % name
|
2013-05-24 06:48:53 -05:00
|
|
|
self._button_click(s, dialog, name)
|
|
|
|
|
|
|
|
def action_button_click(self, name, parent):
|
2013-06-26 09:10:35 -05:00
|
|
|
"""
|
|
|
|
Click on .action-button
|
|
|
|
"""
|
2013-05-24 06:48:53 -05:00
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
|
|
|
|
|
|
|
s = "a[name='%s'].action-button" % name
|
|
|
|
self._button_click(s, parent, name)
|
|
|
|
|
2016-07-20 05:59:27 -05:00
|
|
|
def button_click(self, name, parent=None,
|
|
|
|
parents_css_sel=None):
|
2013-06-26 09:10:35 -05:00
|
|
|
"""
|
|
|
|
Click on .ui-button
|
|
|
|
"""
|
2013-05-24 06:48:53 -05:00
|
|
|
if not parent:
|
2016-07-20 05:59:27 -05:00
|
|
|
if parents_css_sel:
|
|
|
|
parent = self.find(parents_css_sel, By.CSS_SELECTOR,
|
|
|
|
strict=True)
|
|
|
|
else:
|
|
|
|
parent = self.get_form()
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2013-10-17 03:28:47 -05:00
|
|
|
s = "[name='%s'].btn" % name
|
2013-05-24 06:48:53 -05:00
|
|
|
self._button_click(s, parent, name)
|
|
|
|
|
|
|
|
def _button_click(self, selector, parent, name=''):
|
|
|
|
btn = self.find(selector, By.CSS_SELECTOR, parent, strict=True)
|
2018-01-23 17:45:03 -06:00
|
|
|
self.move_to_element_in_page(btn)
|
2014-07-22 09:39:36 -05:00
|
|
|
disabled = btn.get_attribute("disabled")
|
2013-05-24 06:48:53 -05:00
|
|
|
assert btn.is_displayed(), 'Button is not displayed: %s' % name
|
|
|
|
assert not disabled, 'Invalid button state: disabled. Button: %s' % name
|
|
|
|
btn.click()
|
|
|
|
self.wait_for_request()
|
|
|
|
|
2018-01-23 17:45:03 -06:00
|
|
|
def move_to_element_in_page(self, element):
|
|
|
|
# workaround to move the page until the element is visible
|
|
|
|
# more in https://github.com/mozilla/geckodriver/issues/776
|
|
|
|
self.driver.execute_script('arguments[0].scrollIntoView(true);',
|
|
|
|
element)
|
|
|
|
|
2014-04-17 02:07:10 -05:00
|
|
|
def profile_menu_action(self, name):
|
2013-11-07 11:03:38 -06:00
|
|
|
"""
|
|
|
|
Execute action from profile menu
|
|
|
|
"""
|
|
|
|
menu_toggle = self.find('[name=profile-menu] > a', By.CSS_SELECTOR)
|
|
|
|
menu_toggle.click()
|
|
|
|
s = "[name=profile-menu] a[href='#%s']" % name
|
|
|
|
btn = self.find(s, By.CSS_SELECTOR, strict=True)
|
|
|
|
btn.click()
|
2014-04-16 05:04:59 -05:00
|
|
|
# action is usually followed by opening a dialog, add wait to compensate
|
|
|
|
# possible dialog transition effect
|
|
|
|
self.wait(0.5)
|
2013-11-07 11:03:38 -06:00
|
|
|
|
2018-02-16 16:54:40 -06:00
|
|
|
def close_notifications(self):
|
|
|
|
"""
|
|
|
|
Close all notifications like success messages, warnings, infos
|
|
|
|
"""
|
|
|
|
self.wait()
|
|
|
|
while True:
|
|
|
|
# get close button of notification
|
|
|
|
s = ".notification-area .alert button"
|
|
|
|
button = self.find(s, By.CSS_SELECTOR, strict=False)
|
|
|
|
if button:
|
|
|
|
button.click()
|
|
|
|
self.wait()
|
|
|
|
else:
|
|
|
|
break
|
|
|
|
|
2018-03-26 05:34:25 -05:00
|
|
|
def close_all_dialogs(self):
|
|
|
|
"""
|
|
|
|
Close all currently opened dialogs
|
|
|
|
"""
|
|
|
|
self.wait()
|
|
|
|
while True:
|
|
|
|
s = ".modal.fade.in .modal-header button.close"
|
|
|
|
btn = self.find(s, By.CSS_SELECTOR)
|
|
|
|
if btn:
|
|
|
|
btn.click()
|
|
|
|
self.wait(0.5)
|
|
|
|
else:
|
|
|
|
break
|
|
|
|
|
2013-05-24 06:48:53 -05:00
|
|
|
def get_form(self):
|
|
|
|
"""
|
|
|
|
Get last dialog or visible facet
|
|
|
|
"""
|
|
|
|
form = self.get_dialog()
|
|
|
|
if not form:
|
|
|
|
form = self.get_facet()
|
|
|
|
return form
|
|
|
|
|
|
|
|
def select(self, selector, value, parent=None):
|
2013-06-26 09:10:35 -05:00
|
|
|
"""
|
|
|
|
Select option with given value in select element
|
|
|
|
"""
|
2013-05-24 06:48:53 -05:00
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
|
|
|
el = self.find(selector, By.CSS_SELECTOR, parent, strict=True)
|
|
|
|
Select(el).select_by_value(value)
|
|
|
|
|
|
|
|
def fill_text(self, selector, value, parent=None):
|
|
|
|
"""
|
|
|
|
Clear and enter text into input defined by selector.
|
|
|
|
Use for non-standard fields.
|
|
|
|
"""
|
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
|
|
|
tb = self.find(selector, By.CSS_SELECTOR, parent, strict=True)
|
2014-04-24 10:24:59 -05:00
|
|
|
try:
|
2018-09-27 07:42:44 -05:00
|
|
|
tb.send_keys(Keys.CONTROL + 'a')
|
|
|
|
tb.send_keys(Keys.DELETE)
|
2014-04-24 10:24:59 -05:00
|
|
|
tb.send_keys(value)
|
|
|
|
except InvalidElementStateException as e:
|
|
|
|
msg = "Invalid Element State, el: %s, value: %s, error: %s" % (selector, value, e)
|
|
|
|
assert False, msg
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2013-07-24 10:22:40 -05:00
|
|
|
def fill_input(self, name, value, input_type="text", parent=None):
|
2013-06-26 09:10:35 -05:00
|
|
|
"""
|
|
|
|
Type into input element specified by name and type.
|
|
|
|
"""
|
2013-07-24 10:22:40 -05:00
|
|
|
s = "div[name='%s'] input[type='%s'][name='%s']" % (name, input_type, name)
|
2013-05-24 06:48:53 -05:00
|
|
|
self.fill_text(s, value, parent)
|
|
|
|
|
|
|
|
def fill_textarea(self, name, value, parent=None):
|
|
|
|
"""
|
|
|
|
Clear and fill textarea.
|
|
|
|
"""
|
|
|
|
s = "textarea[name='%s']" % (name)
|
|
|
|
self.fill_text(s, value, parent)
|
|
|
|
|
|
|
|
def fill_textbox(self, name, value, parent=None):
|
|
|
|
"""
|
|
|
|
Clear and fill textbox.
|
|
|
|
"""
|
|
|
|
self.fill_input(name, value, "text", parent)
|
|
|
|
|
|
|
|
def fill_password(self, name, value, parent=None):
|
|
|
|
"""
|
|
|
|
Clear and fill input[type=password]
|
|
|
|
"""
|
|
|
|
self.fill_input(name, value, "password", parent)
|
|
|
|
|
2018-04-27 05:04:32 -05:00
|
|
|
def fill_search_filter(self, value, parent=None):
|
|
|
|
search_field_s = '.search-filter input[name=filter]'
|
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
|
|
|
self.fill_text(search_field_s, value, parent)
|
|
|
|
|
2018-10-03 09:37:06 -05:00
|
|
|
def apply_search_filter(self, value):
|
|
|
|
self.fill_search_filter(value)
|
|
|
|
actions = ActionChains(self.driver)
|
|
|
|
actions.send_keys(Keys.ENTER).perform()
|
|
|
|
|
2013-05-24 06:48:53 -05:00
|
|
|
def add_multivalued(self, name, value, parent=None):
|
|
|
|
"""
|
2013-06-26 09:10:35 -05:00
|
|
|
Add new value to multivalued textbox
|
2013-05-24 06:48:53 -05:00
|
|
|
"""
|
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
|
|
|
s = "div[name='%s'].multivalued-widget" % name
|
|
|
|
w = self.find(s, By.CSS_SELECTOR, parent, strict=True)
|
2014-05-13 10:36:40 -05:00
|
|
|
add_btn = self.find("button[name=add]", By.CSS_SELECTOR, w, strict=True)
|
2013-05-24 06:48:53 -05:00
|
|
|
add_btn.click()
|
|
|
|
s = "div[name=value] input"
|
2013-07-24 10:22:40 -05:00
|
|
|
inputs = self.find(s, By.CSS_SELECTOR, w, many=True)
|
2013-05-24 06:48:53 -05:00
|
|
|
last = inputs[-1]
|
|
|
|
last.send_keys(value)
|
|
|
|
|
2018-04-19 08:27:42 -05:00
|
|
|
def edit_multivalued(self, name, value, new_value, parent=None):
|
|
|
|
"""
|
|
|
|
Edit multivalued textbox
|
|
|
|
"""
|
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
|
|
|
s = "div[name='%s'].multivalued-widget" % name
|
|
|
|
w = self.find(s, By.CSS_SELECTOR, parent, strict=True)
|
|
|
|
s = "div[name=value] input"
|
|
|
|
inputs = self.find(s, By.CSS_SELECTOR, w, many=True)
|
|
|
|
|
|
|
|
for i in inputs:
|
|
|
|
val = i.get_attribute('value')
|
|
|
|
if val == value:
|
|
|
|
i.clear()
|
|
|
|
i.send_keys(new_value)
|
|
|
|
|
|
|
|
def undo_multivalued(self, name, value, parent=None):
|
|
|
|
"""
|
|
|
|
Undo multivalued change
|
|
|
|
"""
|
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
|
|
|
s = "div[name='%s'].multivalued-widget" % name
|
|
|
|
w = self.find(s, By.CSS_SELECTOR, parent, strict=True)
|
|
|
|
s = "div[name=value] input"
|
|
|
|
inputs = self.find(s, By.CSS_SELECTOR, w, many=True)
|
|
|
|
clicked = False
|
|
|
|
for i in inputs:
|
|
|
|
val = i.get_attribute('value')
|
|
|
|
n = i.get_attribute('name')
|
|
|
|
if val == value:
|
|
|
|
s = "input[name='%s'] ~ .input-group-btn button[name=undo]" % n
|
|
|
|
link = self.find(s, By.CSS_SELECTOR, w, strict=True)
|
|
|
|
link.click()
|
|
|
|
self.wait()
|
|
|
|
clicked = True
|
|
|
|
# lets try to find the undo button element again to check if
|
|
|
|
# it is not present or displayed
|
|
|
|
link = self.find(s, By.CSS_SELECTOR, w)
|
|
|
|
assert not link or not link.is_displayed(), 'Undo btn present'
|
|
|
|
|
|
|
|
assert clicked, 'Value was not undone: %s' % value
|
|
|
|
|
2013-06-26 08:18:05 -05:00
|
|
|
def del_multivalued(self, name, value, parent=None):
|
2013-06-26 09:10:35 -05:00
|
|
|
"""
|
|
|
|
Mark value in multivalued textbox as deleted.
|
|
|
|
"""
|
2013-05-24 06:48:53 -05:00
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
|
|
|
s = "div[name='%s'].multivalued-widget" % name
|
|
|
|
w = self.find(s, By.CSS_SELECTOR, parent, strict=True)
|
|
|
|
s = "div[name=value] input"
|
2013-07-24 10:22:40 -05:00
|
|
|
inputs = self.find(s, By.CSS_SELECTOR, w, many=True)
|
2013-05-24 06:48:53 -05:00
|
|
|
clicked = False
|
|
|
|
for i in inputs:
|
|
|
|
val = i.get_attribute('value')
|
|
|
|
n = i.get_attribute('name')
|
2013-07-24 06:24:02 -05:00
|
|
|
if val == value:
|
2014-04-11 02:24:06 -05:00
|
|
|
s = "input[name='%s'] ~ .input-group-btn button[name=remove]" % n
|
2013-05-24 06:48:53 -05:00
|
|
|
link = self.find(s, By.CSS_SELECTOR, w, strict=True)
|
|
|
|
link.click()
|
|
|
|
self.wait()
|
|
|
|
clicked = True
|
|
|
|
|
2013-06-26 08:18:05 -05:00
|
|
|
assert clicked, 'Value was not removed: %s' % value
|
|
|
|
|
2018-03-01 06:29:18 -06:00
|
|
|
def undo_all_multivalued(self, name, parent=None):
|
|
|
|
"""
|
|
|
|
Undo all new values to multivalued textbox
|
|
|
|
"""
|
|
|
|
if parent is None:
|
|
|
|
parent = self.get_form()
|
|
|
|
label = "div[name='{}'].multivalued-widget".format(name)
|
|
|
|
widget = self.find(label, By.CSS_SELECTOR, parent, strict=True)
|
|
|
|
add_btn = self.find("button[name=undo_all]", By.CSS_SELECTOR, widget,
|
|
|
|
strict=True)
|
|
|
|
add_btn.click()
|
|
|
|
|
2013-06-26 08:18:05 -05:00
|
|
|
def fill_multivalued(self, name, instructions, parent=None):
|
|
|
|
"""
|
|
|
|
Add or delete a value from multivalued field
|
|
|
|
"""
|
|
|
|
for instruction in instructions:
|
|
|
|
t = instruction[0]
|
|
|
|
value = instruction[1]
|
|
|
|
if t == 'add':
|
|
|
|
self.add_multivalued(name, value, parent)
|
|
|
|
else:
|
|
|
|
self.del_multivalued(name, value, parent)
|
2013-05-24 06:48:53 -05:00
|
|
|
|
|
|
|
def check_option(self, name, value=None, parent=None):
|
2018-09-24 03:49:45 -05:00
|
|
|
r"""
|
2013-10-17 03:52:59 -05:00
|
|
|
Find checkbox or radio with name which matches ^NAME\d$ and
|
|
|
|
check it by clicking on a label.
|
2013-05-24 06:48:53 -05:00
|
|
|
"""
|
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
2013-07-24 06:24:02 -05:00
|
|
|
s = "//input[@type='checkbox' or 'radio'][contains(@name, '%s')]" % name
|
2013-05-24 06:48:53 -05:00
|
|
|
if value is not None:
|
|
|
|
s += "[@value='%s']" % value
|
2013-07-24 10:22:40 -05:00
|
|
|
opts = self.find(s, "xpath", parent, many=True)
|
2013-10-17 03:52:59 -05:00
|
|
|
label = None
|
2018-05-10 07:35:21 -05:00
|
|
|
checkbox = None
|
2013-05-24 06:48:53 -05:00
|
|
|
# Select only the one which matches exactly the name
|
|
|
|
for o in opts:
|
|
|
|
n = o.get_attribute("name")
|
2018-09-24 03:49:45 -05:00
|
|
|
if n == name or re.match(r"^%s\d+$" % name, n):
|
2013-10-17 03:52:59 -05:00
|
|
|
s = "label[for='%s']" % o.get_attribute("id")
|
|
|
|
label = self.find(s, By.CSS_SELECTOR, parent, strict=True)
|
2018-05-10 07:35:21 -05:00
|
|
|
checkbox = o
|
2013-05-24 06:48:53 -05:00
|
|
|
break
|
2013-10-17 03:52:59 -05:00
|
|
|
assert label is not None, "Option not found: %s" % name
|
2018-05-10 07:35:21 -05:00
|
|
|
|
|
|
|
try:
|
|
|
|
label.click()
|
|
|
|
except ElementClickInterceptedException:
|
|
|
|
checkbox.click()
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2016-02-25 08:00:49 -06:00
|
|
|
def select_combobox(self, name, value, parent=None, combobox_input=None):
|
2013-05-24 06:48:53 -05:00
|
|
|
"""
|
|
|
|
Select value in a combobox. Search if not found.
|
|
|
|
"""
|
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
|
|
|
s = "[name='%s'].combobox-widget" % name
|
|
|
|
cb = self.find(s, By.CSS_SELECTOR, parent, strict=True)
|
2013-11-05 12:21:57 -06:00
|
|
|
open_btn = self.find('a[name=open] i', By.CSS_SELECTOR, cb, strict=True)
|
2013-05-24 06:48:53 -05:00
|
|
|
open_btn.click()
|
|
|
|
self.wait()
|
|
|
|
self.wait_for_request()
|
|
|
|
|
2014-04-04 10:45:56 -05:00
|
|
|
list_cnt = self.find('.combobox-widget-list', By.CSS_SELECTOR, cb, strict=True)
|
2013-05-24 06:48:53 -05:00
|
|
|
opt_s = "select[name=list] option[value='%s']" % value
|
|
|
|
option = self.find(opt_s, By.CSS_SELECTOR, cb)
|
|
|
|
|
2016-02-25 08:00:49 -06:00
|
|
|
if combobox_input:
|
|
|
|
if not option:
|
2018-02-08 12:12:27 -06:00
|
|
|
open_btn.click()
|
2018-03-13 16:14:49 -05:00
|
|
|
self.fill_textbox(combobox_input, value, cb)
|
2016-02-25 08:00:49 -06:00
|
|
|
else:
|
|
|
|
if not option:
|
|
|
|
# try to search
|
|
|
|
self.fill_textbox('filter', value, cb)
|
2018-03-13 16:14:49 -05:00
|
|
|
search_btn = self.find('a[name=search] i', By.CSS_SELECTOR, cb,
|
|
|
|
strict=True)
|
2016-02-25 08:00:49 -06:00
|
|
|
search_btn.click()
|
|
|
|
self.wait_for_request()
|
|
|
|
option = self.find(opt_s, By.CSS_SELECTOR, cb, strict=True)
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2016-02-25 08:00:49 -06:00
|
|
|
option.click()
|
2013-05-24 06:48:53 -05:00
|
|
|
|
|
|
|
# Chrome does not close search area on click
|
2014-04-04 10:45:56 -05:00
|
|
|
if list_cnt.is_displayed():
|
2013-05-24 06:48:53 -05:00
|
|
|
self.driver.switch_to_active_element().send_keys(Keys.RETURN)
|
|
|
|
|
|
|
|
self.wait()
|
|
|
|
|
2013-06-27 02:45:17 -05:00
|
|
|
def get_text(self, selector, parent=None):
|
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
|
|
|
|
|
|
|
el = self.find(selector, By.CSS_SELECTOR, parent, strict=True)
|
|
|
|
return el.text
|
|
|
|
|
|
|
|
def get_value(self, selector, parent=None):
|
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
|
|
|
el = self.find(selector, By.CSS_SELECTOR, parent, strict=True)
|
|
|
|
value = el.get_attribute('value')
|
|
|
|
return value
|
|
|
|
|
2014-04-04 10:45:56 -05:00
|
|
|
def get_field_text(self, name, parent=None, element='p'):
|
2013-06-27 02:45:17 -05:00
|
|
|
|
2014-04-04 10:45:56 -05:00
|
|
|
s = ".controls %s[name='%s']" % (element, name)
|
2013-06-27 02:45:17 -05:00
|
|
|
return self.get_text(s, parent)
|
|
|
|
|
|
|
|
def get_field_value(self, name, parent=None, element='input'):
|
2014-04-04 10:45:56 -05:00
|
|
|
s = ".controls %s[name='%s']" % (element, name)
|
2013-06-27 02:45:17 -05:00
|
|
|
return self.get_value(s, parent)
|
|
|
|
|
|
|
|
def get_multivalued_value(self, name, parent=None):
|
|
|
|
|
|
|
|
s = "div[name='%s'] div[name='value'] input[name^='%s']" % (name, name)
|
2013-07-24 10:22:40 -05:00
|
|
|
els = self.find(s, By.CSS_SELECTOR, parent, many=True)
|
2013-06-27 02:45:17 -05:00
|
|
|
values = []
|
|
|
|
for el in els:
|
|
|
|
values.append(el.get_attribute('value'))
|
|
|
|
return values
|
|
|
|
|
|
|
|
def get_field_checked(self, name, parent=None):
|
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
2014-01-10 10:28:27 -06:00
|
|
|
s = "div[name='%s'] input[name^='%s']" % (name, name)
|
2013-07-24 10:22:40 -05:00
|
|
|
els = self.find(s, By.CSS_SELECTOR, parent, strict=True, many=True)
|
2013-06-27 02:45:17 -05:00
|
|
|
values = []
|
|
|
|
for el in els:
|
|
|
|
if el.is_selected():
|
|
|
|
values.append(el.get_attribute('value'))
|
|
|
|
return values
|
|
|
|
|
|
|
|
def get_field_selected(self, name, parent=None):
|
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
|
|
|
s = "div[name='%s'] select[name='%s']" % (name, name)
|
|
|
|
el = self.find(s, By.CSS_SELECTOR, parent, strict=True)
|
|
|
|
select = Select(el)
|
|
|
|
selected = select.all_selected_options
|
|
|
|
values = []
|
|
|
|
for opt in selected:
|
|
|
|
values.append(opt.get_attribute('value'))
|
|
|
|
return values
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2013-06-26 08:18:05 -05:00
|
|
|
def get_undo_buttons(self, field, parent):
|
2013-05-24 06:48:53 -05:00
|
|
|
"""
|
|
|
|
Get field undo button
|
|
|
|
"""
|
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
2013-10-21 05:37:48 -05:00
|
|
|
s = ".controls div[name='%s'] .btn.undo" % (field)
|
2013-07-24 10:22:40 -05:00
|
|
|
undos = self.find(s, By.CSS_SELECTOR, parent, strict=True, many=True)
|
2013-06-26 08:18:05 -05:00
|
|
|
return undos
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2013-08-16 11:18:53 -05:00
|
|
|
def get_rows(self, parent=None, name=None):
|
2013-05-24 06:48:53 -05:00
|
|
|
"""
|
|
|
|
Return all rows of search table.
|
|
|
|
"""
|
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
|
|
|
|
|
|
|
# select table rows
|
2013-08-16 11:18:53 -05:00
|
|
|
s = self.get_table_selector(name)
|
|
|
|
s += ' tbody tr'
|
2013-07-24 10:22:40 -05:00
|
|
|
rows = self.find(s, By.CSS_SELECTOR, parent, many=True)
|
2013-05-24 06:48:53 -05:00
|
|
|
return rows
|
|
|
|
|
2013-08-16 11:18:53 -05:00
|
|
|
def get_row(self, pkey, parent=None, name=None):
|
|
|
|
"""
|
|
|
|
Get row element of search table with given pkey. None if not found.
|
|
|
|
"""
|
|
|
|
rows = self.get_rows(parent, name)
|
|
|
|
s = "input[value='%s']" % pkey
|
|
|
|
for row in rows:
|
|
|
|
has = self.find(s, By.CSS_SELECTOR, row)
|
|
|
|
if has:
|
|
|
|
return row
|
|
|
|
return None
|
|
|
|
|
2013-05-24 06:48:53 -05:00
|
|
|
def navigate_to_row_record(self, row, pkey_column=None):
|
2013-06-26 09:10:35 -05:00
|
|
|
"""
|
|
|
|
Navigate to record by clicking on a link.
|
|
|
|
"""
|
2013-05-24 06:48:53 -05:00
|
|
|
s = 'a'
|
|
|
|
if pkey_column:
|
|
|
|
s = "div[name='%s'] a" % pkey_column
|
|
|
|
link = self.find(s, By.CSS_SELECTOR, row, strict=True)
|
|
|
|
link.click()
|
|
|
|
self.wait_for_request(0.4)
|
|
|
|
self.wait_for_request()
|
|
|
|
|
|
|
|
def get_table_selector(self, name=None):
|
2013-06-26 09:10:35 -05:00
|
|
|
"""
|
|
|
|
Construct table selector
|
|
|
|
"""
|
2013-05-24 06:48:53 -05:00
|
|
|
s = "table"
|
|
|
|
if name:
|
|
|
|
s += "[name='%s']" % name
|
2014-04-04 05:59:24 -05:00
|
|
|
s += '.table'
|
2013-05-24 06:48:53 -05:00
|
|
|
return s
|
|
|
|
|
2018-03-22 01:53:40 -05:00
|
|
|
def select_record(self, pkey, parent=None,
|
|
|
|
table_name=None, unselect=False):
|
2013-05-24 06:48:53 -05:00
|
|
|
"""
|
|
|
|
Select record with given pkey in search table.
|
|
|
|
"""
|
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
|
|
|
|
|
|
|
s = self.get_table_selector(table_name)
|
2014-07-22 09:39:36 -05:00
|
|
|
input_s = s + " tbody td input[value='%s']" % pkey
|
|
|
|
checkbox = self.find(input_s, By.CSS_SELECTOR, parent, strict=True)
|
2014-04-28 10:04:36 -05:00
|
|
|
try:
|
2018-01-23 17:45:03 -06:00
|
|
|
self.move_to_element_in_page(checkbox)
|
|
|
|
checkbox.click()
|
2014-04-28 10:04:36 -05:00
|
|
|
except WebDriverException as e:
|
|
|
|
assert False, 'Can\'t click on checkbox label: %s \n%s' % (s, e)
|
2014-07-22 09:39:36 -05:00
|
|
|
self.wait()
|
2018-03-22 01:53:40 -05:00
|
|
|
if unselect:
|
|
|
|
assert checkbox.is_selected() is not True
|
|
|
|
self.wait()
|
|
|
|
else:
|
|
|
|
assert checkbox.is_selected(), \
|
|
|
|
'Record was not checked: %s' % input_s
|
|
|
|
self.wait()
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2018-03-26 05:34:25 -05:00
|
|
|
def select_multiple_records(self, records):
|
|
|
|
"""
|
|
|
|
Select multiple records
|
|
|
|
"""
|
|
|
|
|
|
|
|
for data in records:
|
|
|
|
pkey = data['pkey']
|
|
|
|
self.select_record(pkey)
|
|
|
|
|
2013-08-16 11:18:53 -05:00
|
|
|
def get_record_value(self, pkey, column, parent=None, table_name=None):
|
|
|
|
"""
|
|
|
|
Get table column's text value
|
|
|
|
"""
|
|
|
|
row = self.get_row(pkey, parent, table_name)
|
|
|
|
s = "div[name=%s]" % column
|
|
|
|
val = None
|
|
|
|
if row:
|
|
|
|
el = self.find(s, By.CSS_SELECTOR, row)
|
|
|
|
val = el.text
|
|
|
|
return val
|
|
|
|
|
2013-05-24 06:48:53 -05:00
|
|
|
def has_record(self, pkey, parent=None, table_name=None):
|
2013-06-26 09:10:35 -05:00
|
|
|
"""
|
|
|
|
Check if table contains specific record.
|
|
|
|
"""
|
2013-05-24 06:48:53 -05:00
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
|
|
|
|
|
|
|
s = self.get_table_selector(table_name)
|
|
|
|
s += " tbody td input[value='%s']" % pkey
|
|
|
|
checkbox = self.find(s, By.CSS_SELECTOR, parent)
|
|
|
|
return checkbox is not None
|
|
|
|
|
|
|
|
def navigate_to_record(self, pkey, parent=None, table_name=None, entity=None, facet='search'):
|
|
|
|
"""
|
|
|
|
Clicks on record with given pkey in search table and thus cause
|
|
|
|
navigation to the record.
|
|
|
|
"""
|
|
|
|
if entity:
|
|
|
|
self.navigate_to_entity(entity, facet)
|
|
|
|
|
|
|
|
if not parent:
|
|
|
|
parent = self.get_facet()
|
|
|
|
|
|
|
|
s = self.get_table_selector(table_name)
|
|
|
|
s += " tbody"
|
|
|
|
table = self.find(s, By.CSS_SELECTOR, parent, strict=True)
|
|
|
|
link = self.find(pkey, By.LINK_TEXT, table, strict=True)
|
|
|
|
link.click()
|
|
|
|
self.wait_for_request()
|
|
|
|
|
2016-02-19 07:59:19 -06:00
|
|
|
def delete_record(
|
|
|
|
self, pkeys, fields=None, parent=None, table_name=None,
|
2018-03-13 16:04:07 -05:00
|
|
|
facet_btn='remove', confirm_btn='ok'):
|
2013-05-24 06:48:53 -05:00
|
|
|
"""
|
|
|
|
Delete records with given pkeys in currently opened search table.
|
|
|
|
"""
|
|
|
|
if type(pkeys) is not list:
|
|
|
|
pkeys = [pkeys]
|
|
|
|
|
|
|
|
# select
|
|
|
|
selected = False
|
|
|
|
for pkey in pkeys:
|
|
|
|
delete = self.has_record(pkey, parent, table_name)
|
|
|
|
if delete:
|
|
|
|
self.select_record(pkey, parent, table_name)
|
|
|
|
selected = True
|
|
|
|
|
|
|
|
# exec and confirm
|
|
|
|
if selected:
|
|
|
|
if table_name and parent:
|
|
|
|
s = self.get_table_selector(table_name)
|
|
|
|
table = self.find(s, By.CSS_SELECTOR, parent, strict=True)
|
2016-02-19 07:59:19 -06:00
|
|
|
self.button_click(facet_btn, table)
|
2013-05-24 06:48:53 -05:00
|
|
|
else:
|
2016-02-19 07:59:19 -06:00
|
|
|
self.facet_button_click(facet_btn)
|
2013-05-24 06:48:53 -05:00
|
|
|
if fields:
|
|
|
|
self.fill_fields(fields)
|
2018-03-13 16:04:07 -05:00
|
|
|
if not confirm_btn:
|
|
|
|
return
|
|
|
|
self.dialog_button_click(confirm_btn)
|
2013-05-24 06:48:53 -05:00
|
|
|
self.wait_for_request(n=2)
|
|
|
|
self.wait()
|
|
|
|
|
|
|
|
def delete(self, entity, data_list, facet='search', navigate=True):
|
|
|
|
"""
|
|
|
|
Delete entity records:
|
|
|
|
"""
|
|
|
|
if navigate:
|
|
|
|
self.navigate_to_entity(entity, facet)
|
|
|
|
for data in data_list:
|
|
|
|
pkey = data.get('pkey')
|
|
|
|
fields = data.get('del')
|
|
|
|
self.delete_record(pkey, fields)
|
|
|
|
|
2016-02-25 08:00:49 -06:00
|
|
|
def fill_fields(
|
|
|
|
self, fields, parent=None, undo=False, combobox_input=None):
|
2013-05-24 06:48:53 -05:00
|
|
|
"""
|
|
|
|
Fill dialog or facet inputs with give data.
|
|
|
|
|
|
|
|
Expected format:
|
|
|
|
[
|
|
|
|
('widget_type', 'key', value'),
|
|
|
|
('widget_type', 'key2', value2'),
|
|
|
|
]
|
|
|
|
"""
|
|
|
|
|
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
|
|
|
|
|
|
|
for field in fields:
|
2013-07-24 10:22:40 -05:00
|
|
|
widget_type = field[0]
|
2013-05-24 06:48:53 -05:00
|
|
|
key = field[1]
|
|
|
|
val = field[2]
|
|
|
|
|
2014-05-07 07:30:29 -05:00
|
|
|
if undo and not hasattr(key, '__call__'):
|
2013-05-24 06:48:53 -05:00
|
|
|
self.assert_undo_button(key, False, parent)
|
|
|
|
|
2013-07-24 10:22:40 -05:00
|
|
|
if widget_type == 'textbox':
|
2013-05-24 06:48:53 -05:00
|
|
|
self.fill_textbox(key, val, parent)
|
2013-07-24 10:22:40 -05:00
|
|
|
elif widget_type == 'textarea':
|
2013-05-24 06:48:53 -05:00
|
|
|
self.fill_textarea(key, val, parent)
|
2013-07-24 10:22:40 -05:00
|
|
|
elif widget_type == 'password':
|
2013-05-24 06:48:53 -05:00
|
|
|
self.fill_password(key, val, parent)
|
2013-07-24 10:22:40 -05:00
|
|
|
elif widget_type == 'radio':
|
2013-05-24 06:48:53 -05:00
|
|
|
self.check_option(key, val, parent)
|
2013-07-24 10:22:40 -05:00
|
|
|
elif widget_type == 'checkbox':
|
2014-02-27 11:21:05 -06:00
|
|
|
self.check_option(key, val, parent=parent)
|
2013-09-25 04:34:11 -05:00
|
|
|
elif widget_type == 'selectbox':
|
|
|
|
self.select('select[name=%s]' % key, val, parent)
|
2013-07-24 10:22:40 -05:00
|
|
|
elif widget_type == 'combobox':
|
2016-02-25 08:00:49 -06:00
|
|
|
self.select_combobox(
|
|
|
|
key, val, parent, combobox_input=combobox_input)
|
2013-07-24 10:22:40 -05:00
|
|
|
elif widget_type == 'add_table_record':
|
2013-05-24 06:48:53 -05:00
|
|
|
self.add_table_record(key, val, parent)
|
2013-07-24 10:22:40 -05:00
|
|
|
elif widget_type == 'add_table_association':
|
2013-05-24 06:48:53 -05:00
|
|
|
self.add_table_associations(key, val, parent)
|
2013-07-24 10:22:40 -05:00
|
|
|
elif widget_type == 'multivalued':
|
2013-06-26 08:18:05 -05:00
|
|
|
self.fill_multivalued(key, val, parent)
|
2013-07-24 10:22:40 -05:00
|
|
|
elif widget_type == 'table':
|
2013-05-24 06:48:53 -05:00
|
|
|
self.select_record(val, parent, key)
|
2014-05-07 07:30:29 -05:00
|
|
|
# this meta field specifies a function, to extend functionality of
|
|
|
|
# field checking
|
|
|
|
elif widget_type == 'callback':
|
|
|
|
if hasattr(key, '__call__'):
|
|
|
|
key(val)
|
2013-05-24 06:48:53 -05:00
|
|
|
self.wait()
|
2014-05-07 07:30:29 -05:00
|
|
|
if undo and not hasattr(key, '__call__'):
|
2013-05-24 06:48:53 -05:00
|
|
|
self.assert_undo_button(key, True, parent)
|
|
|
|
|
2013-07-24 10:22:40 -05:00
|
|
|
def validate_fields(self, fields, parent=None):
|
2013-06-27 02:45:17 -05:00
|
|
|
"""
|
|
|
|
Validate that fields on a page or dialog have desired values.
|
|
|
|
"""
|
|
|
|
if not fields:
|
|
|
|
return
|
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
|
|
|
|
|
|
|
for field in fields:
|
|
|
|
ftype = field[0]
|
|
|
|
key = field[1]
|
|
|
|
expected = field[2]
|
|
|
|
actual = None
|
|
|
|
|
|
|
|
if ftype == 'label':
|
|
|
|
actual = self.get_field_text(key, parent)
|
|
|
|
elif ftype in ('textbox', 'password', 'combobox'):
|
|
|
|
actual = self.get_field_value(key, parent, 'input')
|
|
|
|
elif ftype == 'textarea':
|
|
|
|
actual = self.get_field_value(key, parent, 'textarea')
|
|
|
|
elif ftype == 'radio':
|
|
|
|
actual = self.get_field_checked(key, parent)
|
|
|
|
elif ftype == 'checkbox':
|
|
|
|
actual = self.get_field_checked(key, parent)
|
|
|
|
elif ftype == 'multivalued':
|
|
|
|
actual = self.get_multivalued_value(key, parent)
|
|
|
|
elif ftype == 'table_record':
|
|
|
|
if self.has_record(expected, parent, key):
|
|
|
|
actual = expected
|
|
|
|
|
|
|
|
valid = False
|
|
|
|
if type(expected) == list:
|
|
|
|
valid = type(actual) == list and sorted(expected) == sorted(actual)
|
|
|
|
else:
|
|
|
|
# compare other values, usually strings:
|
|
|
|
valid = actual == expected
|
|
|
|
|
|
|
|
assert valid, "Values don't match. Expected: '%s', Got: '%s'" % (expected, actual)
|
|
|
|
|
2013-05-24 06:48:53 -05:00
|
|
|
def find_record(self, entity, data, facet='search', dummy='XXXXXXX'):
|
2013-06-26 09:10:35 -05:00
|
|
|
"""
|
|
|
|
Test search functionality of search facet.
|
|
|
|
|
|
|
|
1. search for non-existent value and test if result set is empty.
|
|
|
|
2. search for specific pkey and test if it's present on the page
|
|
|
|
3. reset search page by not using search criteria
|
|
|
|
"""
|
2013-05-24 06:48:53 -05:00
|
|
|
|
|
|
|
self.assert_facet(entity, facet)
|
|
|
|
|
|
|
|
facet = self.get_facet()
|
|
|
|
search_field_s = '.search-filter input[name=filter]'
|
|
|
|
key = data.get('pkey')
|
|
|
|
|
|
|
|
self.fill_text(search_field_s, dummy, facet)
|
|
|
|
self.action_button_click('find', facet)
|
|
|
|
self.wait_for_request(n=2)
|
|
|
|
self.assert_record(key, negative=True)
|
|
|
|
|
|
|
|
self.fill_text(search_field_s, key, facet)
|
|
|
|
self.action_button_click('find', facet)
|
|
|
|
self.wait_for_request(n=2)
|
|
|
|
self.assert_record(key)
|
|
|
|
|
|
|
|
self.fill_text(search_field_s, '', facet)
|
|
|
|
self.action_button_click('find', facet)
|
|
|
|
self.wait_for_request(n=2)
|
|
|
|
|
|
|
|
def add_record(self, entity, data, facet='search', facet_btn='add',
|
2018-04-05 02:51:51 -05:00
|
|
|
dialog_btn='add', add_another_btn='add_and_add_another',
|
|
|
|
delete=False, pre_delete=True, dialog_name='add',
|
|
|
|
navigate=True, combobox_input=None, negative=False):
|
2013-05-24 06:48:53 -05:00
|
|
|
"""
|
|
|
|
Add records.
|
|
|
|
|
2018-03-26 05:34:25 -05:00
|
|
|
When negative=True we are skipping final assertions.
|
|
|
|
|
2013-05-24 06:48:53 -05:00
|
|
|
Expected data format:
|
|
|
|
{
|
|
|
|
'pkey': 'key',
|
|
|
|
add: [
|
|
|
|
('widget_type', 'key', 'value'),
|
|
|
|
('widget_type', 'key2', 'value2'),
|
|
|
|
],
|
|
|
|
}
|
|
|
|
"""
|
2018-04-05 02:51:51 -05:00
|
|
|
if type(data) is not list:
|
|
|
|
data = [data]
|
|
|
|
|
|
|
|
last_element = data[len(data) - 1]
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2018-04-05 02:51:51 -05:00
|
|
|
pkeys = []
|
|
|
|
|
|
|
|
for record in data:
|
|
|
|
pkeys.append(record['pkey'])
|
2013-05-24 06:48:53 -05:00
|
|
|
if navigate:
|
|
|
|
self.navigate_to_entity(entity, facet)
|
|
|
|
|
|
|
|
# check facet
|
|
|
|
self.assert_facet(entity, facet)
|
|
|
|
|
|
|
|
# delete if exists, ie. from previous test fail
|
2018-04-05 02:51:51 -05:00
|
|
|
|
2013-05-24 06:48:53 -05:00
|
|
|
if pre_delete:
|
2018-04-05 02:51:51 -05:00
|
|
|
self.delete(entity, data, navigate=False)
|
2013-05-24 06:48:53 -05:00
|
|
|
|
|
|
|
# current row count
|
|
|
|
self.wait_for_request(0.5)
|
|
|
|
count = len(self.get_rows())
|
|
|
|
|
|
|
|
# open add dialog
|
|
|
|
self.assert_no_dialog()
|
|
|
|
self.facet_button_click(facet_btn)
|
|
|
|
self.assert_dialog(dialog_name)
|
|
|
|
|
2018-04-05 02:51:51 -05:00
|
|
|
for record in data:
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2018-04-05 02:51:51 -05:00
|
|
|
# fill dialog
|
|
|
|
self.fill_fields(record['add'], combobox_input=combobox_input)
|
|
|
|
|
|
|
|
btn = dialog_btn
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2018-04-05 02:51:51 -05:00
|
|
|
if record != last_element:
|
|
|
|
btn = add_another_btn
|
|
|
|
|
2018-05-14 06:31:06 -05:00
|
|
|
if not dialog_btn:
|
|
|
|
return
|
|
|
|
|
2018-04-05 02:51:51 -05:00
|
|
|
self.dialog_button_click(btn)
|
|
|
|
self.wait_for_request()
|
2013-05-24 06:48:53 -05:00
|
|
|
self.wait_for_request()
|
|
|
|
|
2018-04-05 02:51:51 -05:00
|
|
|
# check expected error/warning/info
|
|
|
|
expected = ['error_4304_info']
|
|
|
|
dialog_info = self.get_dialog_info()
|
|
|
|
if dialog_info and dialog_info['name'] in expected:
|
|
|
|
self.dialog_button_click('ok')
|
|
|
|
self.wait_for_request()
|
2018-03-26 05:34:25 -05:00
|
|
|
|
2018-04-05 02:51:51 -05:00
|
|
|
if negative:
|
|
|
|
return
|
|
|
|
|
|
|
|
# check for error
|
|
|
|
self.assert_no_error_dialog()
|
|
|
|
self.wait_for_request()
|
|
|
|
self.wait_for_request(0.4)
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2018-04-05 02:51:51 -05:00
|
|
|
if dialog_btn == 'add_and_edit':
|
|
|
|
page_pkey = self.get_text('.facet-pkey')
|
|
|
|
assert record['pkey'] in page_pkey
|
|
|
|
# we cannot delete because we are on different page
|
|
|
|
return
|
|
|
|
elif dialog_btn == add_another_btn:
|
|
|
|
# dialog is still open, we cannot check for records on search page
|
|
|
|
# or delete the records
|
|
|
|
return
|
|
|
|
elif dialog_btn == 'cancel':
|
|
|
|
return
|
|
|
|
# when standard 'add' was used then it will land on search page
|
|
|
|
# and we can check if new item was added - table has more rows
|
2013-05-24 06:48:53 -05:00
|
|
|
new_count = len(self.get_rows())
|
|
|
|
# adjust because of paging
|
2018-04-05 02:51:51 -05:00
|
|
|
expected = count + len(data)
|
2013-05-24 06:48:53 -05:00
|
|
|
if count == 20:
|
|
|
|
expected = 20
|
|
|
|
self.assert_row_count(expected, new_count)
|
|
|
|
|
|
|
|
# delete record
|
|
|
|
if delete:
|
2018-04-05 02:51:51 -05:00
|
|
|
self.delete(entity, data, navigate=False)
|
2013-05-24 06:48:53 -05:00
|
|
|
new_count = len(self.get_rows())
|
|
|
|
self.assert_row_count(count, new_count)
|
|
|
|
|
2018-04-19 08:27:42 -05:00
|
|
|
def mod_record(self, entity, data, facet='details', facet_btn='save',
|
|
|
|
negative=False):
|
2013-05-24 06:48:53 -05:00
|
|
|
"""
|
|
|
|
Mod record
|
|
|
|
|
|
|
|
Assumes that it is already on details page.
|
|
|
|
"""
|
|
|
|
|
|
|
|
self.assert_facet(entity, facet)
|
|
|
|
# TODO assert pkey
|
|
|
|
self.assert_facet_button_enabled(facet_btn, enabled=False)
|
|
|
|
self.fill_fields(data['mod'], undo=True)
|
|
|
|
self.assert_facet_button_enabled(facet_btn)
|
|
|
|
self.facet_button_click(facet_btn)
|
|
|
|
self.wait_for_request()
|
|
|
|
self.wait_for_request()
|
2018-04-19 08:27:42 -05:00
|
|
|
|
|
|
|
if negative:
|
|
|
|
return
|
2013-05-24 06:48:53 -05:00
|
|
|
self.assert_facet_button_enabled(facet_btn, enabled=False)
|
|
|
|
|
|
|
|
def basic_crud(self, entity, data,
|
2013-07-24 06:24:02 -05:00
|
|
|
parent_entity=None,
|
|
|
|
details_facet='details',
|
|
|
|
search_facet='search',
|
|
|
|
default_facet='details',
|
2013-05-24 06:48:53 -05:00
|
|
|
add_facet_btn='add',
|
|
|
|
add_dialog_btn='add',
|
|
|
|
add_dialog_name='add',
|
2015-05-05 07:33:27 -05:00
|
|
|
update_btn='save',
|
2013-05-24 06:48:53 -05:00
|
|
|
breadcrumb=None,
|
|
|
|
navigate=True,
|
2018-02-08 12:07:49 -06:00
|
|
|
mod=True,
|
2013-05-24 06:48:53 -05:00
|
|
|
delete=True):
|
|
|
|
"""
|
|
|
|
Basic CRUD operation sequence.
|
|
|
|
|
|
|
|
Expected data format:
|
|
|
|
{
|
|
|
|
'pkey': 'key',
|
|
|
|
'add': [
|
|
|
|
('widget_type', 'key', 'value'),
|
|
|
|
('widget_type', 'key2', 'value2'),
|
|
|
|
],
|
|
|
|
'mod': [
|
|
|
|
('widget_type', 'key', 'value'),
|
|
|
|
('widget_type', 'key2', 'value2'),
|
|
|
|
],
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
|
|
|
|
# important for nested entities. Ie. autoumount maps
|
|
|
|
if not parent_entity:
|
|
|
|
parent_entity = entity
|
|
|
|
|
|
|
|
pkey = data['pkey']
|
|
|
|
|
|
|
|
# 1. Open Search Facet
|
|
|
|
if navigate:
|
|
|
|
self.navigate_to_entity(parent_entity)
|
|
|
|
self.assert_facet(parent_entity, search_facet)
|
|
|
|
self.wait_for_request()
|
|
|
|
|
|
|
|
# 2. Add record
|
2018-04-05 02:51:51 -05:00
|
|
|
self.add_record(parent_entity, data, facet=search_facet,
|
|
|
|
navigate=False, facet_btn=add_facet_btn,
|
|
|
|
dialog_name=add_dialog_name, dialog_btn=add_dialog_btn)
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2018-03-24 16:13:49 -05:00
|
|
|
self.close_notifications()
|
|
|
|
|
2013-05-24 06:48:53 -05:00
|
|
|
# Find
|
|
|
|
self.find_record(parent_entity, data, search_facet)
|
|
|
|
|
|
|
|
# 3. Navigate to details facet
|
|
|
|
self.navigate_to_record(pkey)
|
|
|
|
self.assert_facet(entity, default_facet)
|
|
|
|
self.wait_for_request(0.5)
|
|
|
|
if default_facet != details_facet:
|
|
|
|
self.switch_to_facet(details_facet)
|
|
|
|
self.assert_facet(entity, details_facet)
|
|
|
|
|
2013-06-27 02:45:17 -05:00
|
|
|
self.validate_fields(data.get('add_v'))
|
|
|
|
|
2013-05-24 06:48:53 -05:00
|
|
|
# 4. Mod values
|
2018-02-08 12:07:49 -06:00
|
|
|
if mod and data.get('mod'):
|
2013-05-24 06:48:53 -05:00
|
|
|
self.mod_record(entity, data, details_facet, update_btn)
|
2013-06-27 02:45:17 -05:00
|
|
|
self.validate_fields(data.get('mod_v'))
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2018-03-24 16:13:49 -05:00
|
|
|
self.close_notifications()
|
|
|
|
|
2013-05-24 06:48:53 -05:00
|
|
|
if not breadcrumb:
|
|
|
|
self.navigate_to_entity(entity, search_facet)
|
|
|
|
else:
|
|
|
|
self.navigate_by_breadcrumb(breadcrumb)
|
|
|
|
|
|
|
|
# 5. Delete record
|
|
|
|
if delete:
|
|
|
|
self.delete_record(pkey, data.get('del'))
|
2018-03-24 16:13:49 -05:00
|
|
|
self.close_notifications()
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2018-10-05 08:31:57 -05:00
|
|
|
def add_table_record(self, name, data, parent=None, add_another=False):
|
2013-05-24 06:48:53 -05:00
|
|
|
"""
|
|
|
|
Add record to dnsrecord table, association table and similar
|
|
|
|
"""
|
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
|
|
|
s = self.get_table_selector(name)
|
|
|
|
table = self.find(s, By.CSS_SELECTOR, parent, strict=True)
|
2014-08-05 05:22:47 -05:00
|
|
|
s = ".btn[name=%s]" % 'add'
|
2013-05-24 06:48:53 -05:00
|
|
|
btn = self.find(s, By.CSS_SELECTOR, table, strict=True)
|
|
|
|
btn.click()
|
|
|
|
self.wait()
|
|
|
|
self.fill_fields(data['fields'])
|
2018-10-05 08:31:57 -05:00
|
|
|
|
|
|
|
if not add_another:
|
|
|
|
self.dialog_button_click('add')
|
|
|
|
self.wait_for_request()
|
|
|
|
|
|
|
|
def add_another_table_record(self, data, add_another=False):
|
|
|
|
"""
|
|
|
|
Add table record after creating previous one in the same dialog
|
|
|
|
TODO: Create class to manipulate such type of dialog
|
|
|
|
"""
|
|
|
|
self.dialog_button_click('add_and_add_another')
|
2013-05-24 06:48:53 -05:00
|
|
|
self.wait_for_request()
|
|
|
|
|
2018-10-05 08:31:57 -05:00
|
|
|
self.fill_fields(data['fields'])
|
|
|
|
|
|
|
|
if not add_another:
|
|
|
|
self.dialog_button_click('add')
|
|
|
|
self.wait_for_request()
|
|
|
|
|
2016-02-19 07:59:19 -06:00
|
|
|
def prepare_associations(
|
2018-03-13 13:07:20 -05:00
|
|
|
self, pkeys, facet=None, facet_btn='add', member_pkeys=None,
|
2018-04-05 02:51:51 -05:00
|
|
|
confirm_btn='add', search=False):
|
2013-05-24 06:48:53 -05:00
|
|
|
"""
|
2016-02-19 07:59:19 -06:00
|
|
|
Helper function for add_associations and delete_associations
|
2013-05-24 06:48:53 -05:00
|
|
|
"""
|
|
|
|
if facet:
|
|
|
|
self.switch_to_facet(facet)
|
|
|
|
|
2016-02-19 07:59:19 -06:00
|
|
|
self.facet_button_click(facet_btn)
|
2014-07-22 09:39:36 -05:00
|
|
|
self.wait()
|
2013-05-24 06:48:53 -05:00
|
|
|
self.wait_for_request()
|
|
|
|
|
2018-04-05 02:51:51 -05:00
|
|
|
if search is True:
|
|
|
|
for key in pkeys:
|
|
|
|
search_field_s = '.adder-dialog-top input[name="filter"]'
|
|
|
|
self.fill_text(search_field_s, key)
|
|
|
|
self._button_click(selector="button[name='find'].btn-default",
|
|
|
|
parent=None)
|
|
|
|
self.wait_for_request()
|
|
|
|
self.select_record(key, table_name='available')
|
|
|
|
self.button_click('add')
|
|
|
|
else:
|
|
|
|
for key in pkeys:
|
|
|
|
self.select_record(key, table_name='available')
|
2013-05-24 06:48:53 -05:00
|
|
|
self.button_click('add')
|
|
|
|
|
2018-03-13 13:07:20 -05:00
|
|
|
self.dialog_button_click(confirm_btn)
|
2013-05-24 06:48:53 -05:00
|
|
|
self.wait_for_request()
|
|
|
|
|
2016-02-19 07:59:19 -06:00
|
|
|
if member_pkeys:
|
|
|
|
check_pkeys = member_pkeys
|
|
|
|
else:
|
|
|
|
check_pkeys = pkeys
|
|
|
|
|
|
|
|
return check_pkeys
|
|
|
|
|
|
|
|
def add_associations(
|
|
|
|
self, pkeys, facet=None, delete=False, facet_btn='add',
|
2018-04-05 02:51:51 -05:00
|
|
|
member_pkeys=None, confirm_btn='add', search=False):
|
2016-02-19 07:59:19 -06:00
|
|
|
"""
|
|
|
|
Add associations
|
|
|
|
"""
|
|
|
|
check_pkeys = self.prepare_associations(
|
2018-04-05 02:51:51 -05:00
|
|
|
pkeys, facet, facet_btn, member_pkeys, confirm_btn, search)
|
2018-03-13 13:07:20 -05:00
|
|
|
|
|
|
|
# we need to return if we want to "cancel" to avoid assert record fail
|
|
|
|
if confirm_btn == 'cancel':
|
|
|
|
return
|
2016-02-19 07:59:19 -06:00
|
|
|
|
|
|
|
for key in check_pkeys:
|
2018-04-05 02:51:51 -05:00
|
|
|
|
2013-05-24 06:48:53 -05:00
|
|
|
self.assert_record(key)
|
|
|
|
if delete:
|
|
|
|
self.delete_record(key)
|
|
|
|
self.assert_record(key, negative=True)
|
|
|
|
|
2016-02-19 07:59:19 -06:00
|
|
|
def delete_associations(
|
|
|
|
self, pkeys, facet=None, facet_btn='remove', member_pkeys=None):
|
|
|
|
"""
|
|
|
|
Remove associations
|
|
|
|
"""
|
|
|
|
check_pkeys = self.prepare_associations(
|
|
|
|
pkeys, facet, facet_btn, member_pkeys)
|
|
|
|
|
|
|
|
for key in check_pkeys:
|
|
|
|
self.assert_record(key, negative=True)
|
|
|
|
|
2018-04-05 02:51:51 -05:00
|
|
|
def add_table_associations(self, table_name, pkeys, parent=False,
|
2018-05-14 06:31:06 -05:00
|
|
|
delete=False, confirm_btn='add',
|
|
|
|
negative=False):
|
2013-06-26 09:10:35 -05:00
|
|
|
"""
|
|
|
|
Add value to table (association|rule|...)
|
|
|
|
"""
|
2013-05-24 06:48:53 -05:00
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
|
|
|
|
|
|
|
s = self.get_table_selector(table_name)
|
|
|
|
table = self.find(s, By.CSS_SELECTOR, parent, strict=True)
|
|
|
|
|
2014-07-22 09:39:36 -05:00
|
|
|
s = "button[name='%s']" % 'add'
|
2013-05-24 06:48:53 -05:00
|
|
|
btn = self.find(s, By.CSS_SELECTOR, table, strict=True)
|
|
|
|
btn.click()
|
|
|
|
self.wait_for_request(0.4)
|
|
|
|
|
|
|
|
for key in pkeys:
|
|
|
|
self.select_record(key, table_name='available')
|
|
|
|
self.button_click('add')
|
2014-07-22 09:39:36 -05:00
|
|
|
self.wait()
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2018-05-14 06:31:06 -05:00
|
|
|
self.dialog_button_click(confirm_btn)
|
|
|
|
|
|
|
|
if confirm_btn == 'cancel':
|
2018-04-18 06:59:17 -05:00
|
|
|
self.assert_record(key, parent, table_name, negative=True)
|
|
|
|
return
|
2013-05-24 06:48:53 -05:00
|
|
|
self.wait_for_request(n=2)
|
|
|
|
|
2018-05-14 06:31:06 -05:00
|
|
|
if negative:
|
|
|
|
return
|
|
|
|
|
2013-05-24 06:48:53 -05:00
|
|
|
for key in pkeys:
|
|
|
|
self.assert_record(key, parent, table_name)
|
|
|
|
if delete:
|
|
|
|
self.delete_record(pkeys, None, parent, table_name)
|
|
|
|
for key in pkeys:
|
|
|
|
self.assert_record(key, parent, table_name, negative=True)
|
|
|
|
|
2016-07-20 05:59:27 -05:00
|
|
|
def action_list_action(self, name, confirm=True, confirm_btn="ok",
|
|
|
|
parents_css_sel=None):
|
2013-06-26 09:10:35 -05:00
|
|
|
"""
|
|
|
|
Execute action list action
|
|
|
|
"""
|
2016-07-20 05:59:27 -05:00
|
|
|
context = None
|
|
|
|
|
|
|
|
if not parents_css_sel:
|
|
|
|
context = self.find(".active-facet .facet-actions",
|
|
|
|
By.CSS_SELECTOR, strict=True)
|
|
|
|
else:
|
|
|
|
context = self.find(parents_css_sel, By.CSS_SELECTOR,
|
|
|
|
strict=True)
|
|
|
|
|
|
|
|
expand = self.find(".dropdown-toggle", By.CSS_SELECTOR, context,
|
|
|
|
strict=True)
|
2014-04-14 07:10:20 -05:00
|
|
|
expand.click()
|
2016-07-20 05:59:27 -05:00
|
|
|
action_link = self.find("li[data-name=%s] a" % name, By.CSS_SELECTOR,
|
|
|
|
context, strict=True)
|
2018-06-07 05:25:49 -05:00
|
|
|
self.move_to_element_in_page(action_link)
|
2014-04-14 07:10:20 -05:00
|
|
|
action_link.click()
|
|
|
|
if confirm:
|
|
|
|
self.wait(0.5) # wait for dialog
|
|
|
|
self.dialog_button_click(confirm_btn)
|
2013-05-24 06:48:53 -05:00
|
|
|
self.wait()
|
|
|
|
|
|
|
|
def action_panel_action(self, panel_name, action):
|
2013-06-26 09:10:35 -05:00
|
|
|
"""
|
|
|
|
Execute action from action panel with given name.
|
|
|
|
"""
|
2013-05-24 06:48:53 -05:00
|
|
|
s = "div[data-name='%s'].action-panel" % panel_name
|
|
|
|
s += " a[data-name='%s']" % action
|
|
|
|
link = self.find(s, By.CSS_SELECTOR, strict=True)
|
|
|
|
link.click()
|
|
|
|
self.wait()
|
|
|
|
|
|
|
|
def enable_action(self):
|
2013-06-26 09:10:35 -05:00
|
|
|
"""
|
|
|
|
Execute and test 'enable' action panel action.
|
|
|
|
"""
|
2013-05-24 06:48:53 -05:00
|
|
|
title = self.find('.active-facet div.facet-title', By.CSS_SELECTOR, strict=True)
|
|
|
|
self.action_list_action('enable')
|
|
|
|
self.wait_for_request(n=2)
|
|
|
|
self.assert_no_error_dialog()
|
|
|
|
self.assert_class(title, 'disabled', negative=True)
|
|
|
|
|
|
|
|
def disable_action(self):
|
2013-06-26 09:10:35 -05:00
|
|
|
"""
|
|
|
|
Execute and test 'disable' action panel action.
|
|
|
|
"""
|
2013-05-24 06:48:53 -05:00
|
|
|
title = self.find('.active-facet div.facet-title', By.CSS_SELECTOR, strict=True)
|
|
|
|
self.action_list_action('disable')
|
|
|
|
self.wait_for_request(n=2)
|
|
|
|
self.assert_no_error_dialog()
|
2018-06-27 08:36:32 -05:00
|
|
|
self.close_notifications()
|
|
|
|
self.move_to_element_in_page(title)
|
2013-05-24 06:48:53 -05:00
|
|
|
self.assert_class(title, 'disabled')
|
|
|
|
|
2016-07-20 09:15:34 -05:00
|
|
|
def delete_action(self, entity, pkey, action='delete', facet='search'):
|
2013-06-26 09:10:35 -05:00
|
|
|
"""
|
|
|
|
Execute and test 'delete' action panel action.
|
|
|
|
"""
|
2016-07-20 09:15:34 -05:00
|
|
|
self.action_list_action(action)
|
2013-05-24 06:48:53 -05:00
|
|
|
self.wait_for_request(n=4)
|
|
|
|
self.assert_no_error_dialog()
|
|
|
|
self.assert_facet(entity, facet)
|
|
|
|
self.assert_record(pkey, negative=True)
|
|
|
|
|
|
|
|
def mod_rule_tables(self, tables, categories, no_categories):
|
|
|
|
"""
|
|
|
|
Test functionality of rule table widgets in a facet
|
|
|
|
"""
|
|
|
|
def get_t_vals(t):
|
|
|
|
table = t[0]
|
|
|
|
k = t[1]
|
|
|
|
e = []
|
|
|
|
if len(t) > 2:
|
|
|
|
e = t[2]
|
|
|
|
return table, k, e
|
|
|
|
|
|
|
|
t_list = [t[0] for t in tables if t[0] not in no_categories]
|
|
|
|
|
|
|
|
# add values
|
|
|
|
for t in tables:
|
2016-09-26 11:22:22 -05:00
|
|
|
table, keys, _exts = get_t_vals(t)
|
2013-05-24 06:48:53 -05:00
|
|
|
# add one by one to test for #3711
|
|
|
|
for key in keys:
|
|
|
|
self.add_table_associations(table, [key])
|
|
|
|
|
|
|
|
#disable tables
|
|
|
|
for cat in categories:
|
|
|
|
self.check_option(cat, 'all')
|
|
|
|
|
|
|
|
# update
|
|
|
|
self.assert_rule_tables_enabled(t_list, False)
|
2015-05-05 07:33:27 -05:00
|
|
|
self.facet_button_click('save')
|
2013-05-24 06:48:53 -05:00
|
|
|
self.wait_for_request(n=3, d=0.3)
|
|
|
|
self.assert_rule_tables_enabled(t_list, False)
|
|
|
|
|
|
|
|
p = self.get_form()
|
|
|
|
# now tables in categories should be empty, check it
|
|
|
|
for t in tables:
|
2016-09-26 11:22:22 -05:00
|
|
|
table, keys, _exts = get_t_vals(t)
|
2013-05-24 06:48:53 -05:00
|
|
|
if table in no_categories:
|
|
|
|
# clear the rest
|
|
|
|
self.delete_record(keys, None, p, table)
|
|
|
|
continue
|
|
|
|
for key in keys:
|
|
|
|
self.assert_record(key, p, table, negative=True)
|
|
|
|
|
|
|
|
# enable tables
|
|
|
|
for cat in categories:
|
|
|
|
self.check_option(cat, '')
|
|
|
|
self.assert_rule_tables_enabled(t_list, True)
|
2015-05-05 07:33:27 -05:00
|
|
|
self.facet_button_click('save')
|
2013-05-24 06:48:53 -05:00
|
|
|
self.wait_for_request(n=3, d=0.3)
|
|
|
|
self.assert_rule_tables_enabled(t_list, True)
|
|
|
|
|
|
|
|
for t in tables:
|
2016-09-26 11:22:22 -05:00
|
|
|
table, keys, _exts = get_t_vals(t)
|
2013-05-24 06:48:53 -05:00
|
|
|
# add multiple at once and test table delete button
|
|
|
|
self.add_table_associations(table, keys, delete=True)
|
|
|
|
|
2018-04-19 08:27:42 -05:00
|
|
|
def add_sshkey_to_record(self, ssh_keys, pkey, entity='user',
|
|
|
|
navigate=False, save=True):
|
2018-03-13 12:56:25 -05:00
|
|
|
"""
|
2018-04-19 08:27:42 -05:00
|
|
|
Add ssh public key to particular record
|
2018-03-13 12:56:25 -05:00
|
|
|
|
2018-04-19 08:27:42 -05:00
|
|
|
ssh_keys (list): public ssh key(s)
|
|
|
|
pkey (str): user/host/idview to add the key to
|
|
|
|
entity (str): name of entity where to navigate if navigate=True
|
|
|
|
navigate (bool): whether we should navigate to record
|
|
|
|
save (bool): whether we should click save after adding a key
|
2018-03-13 12:56:25 -05:00
|
|
|
"""
|
|
|
|
|
2018-04-19 08:27:42 -05:00
|
|
|
if type(ssh_keys) is not list:
|
|
|
|
ssh_keys = [ssh_keys]
|
|
|
|
|
|
|
|
if navigate:
|
|
|
|
self.navigate_to_entity(entity)
|
|
|
|
self.navigate_to_record(pkey)
|
|
|
|
|
|
|
|
for key in ssh_keys:
|
|
|
|
s_add = 'div[name="ipasshpubkey"] button[name="add"]'
|
|
|
|
ssh_add_btn = self.find(s_add, By.CSS_SELECTOR, strict=True)
|
|
|
|
ssh_add_btn.click()
|
|
|
|
self.wait()
|
|
|
|
s_text_area = 'textarea.certificate'
|
|
|
|
text_area = self.find(s_text_area, By.CSS_SELECTOR, strict=True)
|
|
|
|
text_area.send_keys(key)
|
|
|
|
self.wait()
|
|
|
|
self.dialog_button_click('update')
|
|
|
|
|
|
|
|
# sometimes we do not want to save e.g. in order to test undo buttons
|
|
|
|
if save:
|
|
|
|
self.facet_button_click('save')
|
2018-03-13 12:56:25 -05:00
|
|
|
|
2018-04-19 08:27:42 -05:00
|
|
|
def delete_record_sshkeys(self, pkey, entity='user', navigate=False):
|
2018-03-13 12:56:25 -05:00
|
|
|
"""
|
2018-04-19 08:27:42 -05:00
|
|
|
Delete all ssh public keys of particular record
|
|
|
|
|
|
|
|
pkey (str): user/host/idview to add the key to
|
|
|
|
entity (str): name of entity where to navigate if navigate=True
|
|
|
|
navigate (bool): whether we should navigate to record
|
2018-03-13 12:56:25 -05:00
|
|
|
"""
|
2018-04-19 08:27:42 -05:00
|
|
|
|
|
|
|
if navigate:
|
|
|
|
self.navigate_to_entity(entity)
|
|
|
|
self.navigate_to_record(pkey)
|
2018-03-13 12:56:25 -05:00
|
|
|
|
|
|
|
ssh_pub = 'div[name="ipasshpubkey"] button[name="remove"]'
|
2018-04-19 08:27:42 -05:00
|
|
|
rm_btns = self.find(ssh_pub, By.CSS_SELECTOR, many=True)
|
|
|
|
assert rm_btns, 'No SSH keys to be deleted found on current page'
|
|
|
|
|
|
|
|
for btn in rm_btns:
|
|
|
|
btn.click()
|
|
|
|
|
2018-03-13 12:56:25 -05:00
|
|
|
self.facet_button_click('save')
|
|
|
|
|
2018-04-19 08:27:42 -05:00
|
|
|
def assert_num_ssh_keys(self, num):
|
|
|
|
"""
|
|
|
|
Assert number of SSH keys we have associated with the user
|
|
|
|
"""
|
|
|
|
|
|
|
|
s_keys = 'div[name="ipasshpubkey"] .widget[name="value"]'
|
|
|
|
ssh_keys = self.find(s_keys, By.CSS_SELECTOR, many=True)
|
|
|
|
|
|
|
|
num_ssh_keys = len(ssh_keys) if not None else 0
|
|
|
|
|
|
|
|
assert num_ssh_keys == num, \
|
|
|
|
('Number of SSH keys does not match. '
|
|
|
|
'Expected: {}, Got: {}'.format(num, num_ssh_keys))
|
|
|
|
|
|
|
|
def undo_ssh_keys(self, btn_name='undo'):
|
|
|
|
"""
|
|
|
|
Undo either one SSH key or all of them
|
|
|
|
|
|
|
|
Possible options:
|
|
|
|
btn_name='undo'
|
|
|
|
btn_name='undo_all'
|
|
|
|
"""
|
|
|
|
|
|
|
|
s_undo = 'div[name="ipasshpubkey"] button[name="{}"]'.format(btn_name)
|
|
|
|
undo = self.find(s_undo, By.CSS_SELECTOR, strict=True)
|
|
|
|
undo.click()
|
|
|
|
self.wait(0.6)
|
|
|
|
|
2018-03-13 13:02:45 -05:00
|
|
|
def run_cmd_on_ui_host(self, cmd):
|
|
|
|
"""
|
|
|
|
Run "shell" command on the UI system using "admin" user's passwd from
|
|
|
|
conf.
|
|
|
|
Use only where API does not fit.
|
|
|
|
|
|
|
|
cmd (str): command to run
|
|
|
|
"""
|
|
|
|
|
|
|
|
login = self.config.get('ipa_admin')
|
|
|
|
hostname = self.config.get('ipa_server')
|
|
|
|
password = self.config.get('ipa_password')
|
|
|
|
|
|
|
|
try:
|
|
|
|
ssh = paramiko.SSHClient()
|
|
|
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
|
|
ssh.connect(hostname=hostname, username=login, password=password)
|
|
|
|
ssh.exec_command(cmd)
|
|
|
|
except paramiko.AuthenticationException:
|
|
|
|
self.skip('Authentication to server {} failed'.format(hostname))
|
|
|
|
except paramiko.SSHException as e:
|
|
|
|
self.skip('Unable to establish SSH connection: {}'.format(e))
|
|
|
|
except Exception as e:
|
|
|
|
self.skip('Unable to proceed: {}'.format(e))
|
|
|
|
finally:
|
|
|
|
ssh.close()
|
|
|
|
|
2014-04-17 02:07:10 -05:00
|
|
|
def has_class(self, el, cls):
|
|
|
|
"""
|
|
|
|
Check if el has CSS class
|
|
|
|
"""
|
2018-09-17 07:13:38 -05:00
|
|
|
class_attr = el.get_attribute("class")
|
|
|
|
return bool(class_attr) and cls in class_attr.split()
|
2014-04-17 02:07:10 -05:00
|
|
|
|
2018-09-24 09:03:02 -05:00
|
|
|
def has_form_error(self, name):
|
|
|
|
"""
|
|
|
|
Check if form field has error
|
|
|
|
TODO: Move to some mixin class
|
|
|
|
"""
|
|
|
|
form_group = self.find(
|
|
|
|
'//input[@name="{}"]/ancestor'
|
|
|
|
'::div[contains(@class, "form-group")]'.format(name),
|
|
|
|
By.XPATH
|
|
|
|
)
|
|
|
|
return self.has_class(form_group, 'has-error')
|
|
|
|
|
2013-05-24 06:48:53 -05:00
|
|
|
def skip(self, reason):
|
2013-06-26 09:10:35 -05:00
|
|
|
"""
|
|
|
|
Skip tests
|
|
|
|
"""
|
2017-12-06 10:01:57 -06:00
|
|
|
raise unittest.SkipTest(reason)
|
2013-05-24 06:48:53 -05:00
|
|
|
|
|
|
|
def assert_text(self, selector, value, parent=None):
|
|
|
|
"""
|
|
|
|
Assert read-only text value in details page or in a form
|
|
|
|
"""
|
2013-06-27 02:45:17 -05:00
|
|
|
text = self.get_text(selector, parent)
|
|
|
|
text = text.strip()
|
2013-05-24 06:48:53 -05:00
|
|
|
value = value.strip()
|
|
|
|
assert text == value, "Invalid value: '%s' Expected: %s" % (text, value)
|
|
|
|
|
|
|
|
def assert_text_field(self, name, value, parent=None, element='label'):
|
|
|
|
"""
|
|
|
|
Assert read-only text value in details page or in a form
|
|
|
|
"""
|
|
|
|
s = "div[name='%s'] %s[name='%s']" % (name, element, name)
|
|
|
|
self.assert_text(s, value, parent)
|
|
|
|
|
2016-07-20 09:28:38 -05:00
|
|
|
def assert_empty_value(self, selector, parent=None, negative=False):
|
|
|
|
"""
|
|
|
|
Assert empty value of some field in details page or in a form
|
|
|
|
"""
|
|
|
|
value = self.get_value(selector, parent)
|
|
|
|
|
|
|
|
if negative:
|
|
|
|
assert not value == ''
|
|
|
|
else:
|
|
|
|
assert value == ''
|
|
|
|
|
2013-05-24 06:48:53 -05:00
|
|
|
def assert_no_dialog(self):
|
|
|
|
"""
|
|
|
|
Assert that no dialog is opened
|
|
|
|
"""
|
|
|
|
dialogs = self.get_dialogs()
|
|
|
|
assert not dialogs, 'Invalid state: dialog opened'
|
|
|
|
|
|
|
|
def assert_dialog(self, name=None):
|
|
|
|
"""
|
|
|
|
Assert that one dialog is opened or a dialog with given name
|
|
|
|
"""
|
|
|
|
dialogs = self.get_dialogs(name)
|
|
|
|
assert len(dialogs) == 1, 'No or more than one dialog opened'
|
|
|
|
|
|
|
|
def assert_no_error_dialog(self):
|
|
|
|
"""
|
|
|
|
Assert that no error dialog is opened
|
|
|
|
"""
|
|
|
|
dialog = self.get_last_error_dialog()
|
|
|
|
ok = dialog is None
|
|
|
|
if not ok:
|
|
|
|
msg = self.find('p', By.CSS_SELECTOR, dialog).text
|
|
|
|
assert ok, 'Unexpected error: %s' % msg
|
|
|
|
|
|
|
|
def assert_row_count(self, expected, current):
|
|
|
|
"""
|
|
|
|
Assert that row counts match
|
|
|
|
"""
|
|
|
|
assert expected == current, "Rows don't match. Expected: %d, Got: %d" % (expected, current)
|
|
|
|
|
2014-05-13 06:00:16 -05:00
|
|
|
def assert_button_enabled(self, name, context_selector=None, enabled=True):
|
|
|
|
"""
|
|
|
|
Assert that button is enabled or disabled (expects that element will be
|
|
|
|
<button>)
|
|
|
|
"""
|
|
|
|
s = ""
|
|
|
|
if context_selector:
|
|
|
|
s = context_selector
|
|
|
|
s += "button[name=%s]" % name
|
|
|
|
facet = self.get_facet()
|
|
|
|
btn = self.find(s, By.CSS_SELECTOR, facet, strict=True)
|
|
|
|
valid = enabled == btn.is_enabled()
|
|
|
|
assert btn.is_displayed(), 'Button is not displayed'
|
|
|
|
assert valid, 'Button (%s) has incorrect enabled state (enabled==%s).' % (s, enabled)
|
|
|
|
|
2013-05-24 06:48:53 -05:00
|
|
|
def assert_facet_button_enabled(self, name, enabled=True):
|
|
|
|
"""
|
|
|
|
Assert that facet button is enabled or disabled
|
|
|
|
"""
|
|
|
|
self.assert_button_enabled(name, ".facet-controls ", enabled)
|
|
|
|
|
|
|
|
def assert_table_button_enabled(self, name, table_name, enabled=True):
|
|
|
|
"""
|
|
|
|
Assert that button in table is enabled/disabled
|
|
|
|
"""
|
|
|
|
s = "table[name='%s'] " % table_name
|
2014-07-22 09:39:36 -05:00
|
|
|
self.assert_button_enabled(name, s, enabled)
|
2013-05-24 06:48:53 -05:00
|
|
|
|
|
|
|
def assert_facet(self, entity, facet=None):
|
|
|
|
"""
|
|
|
|
Assert that current facet is correct
|
|
|
|
"""
|
|
|
|
info = self.get_facet_info()
|
|
|
|
if not facet is None:
|
|
|
|
assert info["name"] == facet, "Invalid facet. Expected: %s, Got: %s " % (facet, info["name"])
|
|
|
|
assert info["entity"] == entity, "Invalid entity. Expected: %s, Got: %s " % (entity, info["entity"])
|
|
|
|
|
|
|
|
def assert_undo_button(self, field, visible=True, parent=None):
|
|
|
|
"""
|
|
|
|
Assert that undo button is or is not visible
|
|
|
|
"""
|
2013-06-26 08:18:05 -05:00
|
|
|
undos = self.get_undo_buttons(field, parent)
|
|
|
|
state = False
|
|
|
|
for undo in undos:
|
|
|
|
if undo.is_displayed():
|
|
|
|
state = True
|
|
|
|
break
|
|
|
|
if visible:
|
|
|
|
assert state, "Undo button not visible. Field: %s" % field
|
|
|
|
else:
|
|
|
|
assert not state, "Undo button visible. Field: %s" % field
|
2013-05-24 06:48:53 -05:00
|
|
|
|
|
|
|
def assert_visible(self, selector, parent=None, negative=False):
|
|
|
|
"""
|
|
|
|
Assert that element defined by selector is visible
|
|
|
|
"""
|
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
|
|
|
el = self.find(selector, By.CSS_SELECTOR, parent, strict=True)
|
|
|
|
visible = el.is_displayed()
|
|
|
|
if negative:
|
|
|
|
assert not visible, "Element visible: %s" % selector
|
|
|
|
else:
|
|
|
|
assert visible, "Element not visible: %s" % selector
|
|
|
|
|
2014-05-07 07:30:29 -05:00
|
|
|
def assert_disabled(self, selector, parent=None, negative=False):
|
|
|
|
"""
|
|
|
|
Assert that element defined by selector is disabled
|
|
|
|
"""
|
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
2016-09-26 11:22:22 -05:00
|
|
|
self.find(selector, By.CSS_SELECTOR, parent, strict=True)
|
2014-05-07 07:30:29 -05:00
|
|
|
dis = self.find(selector+"[disabled]", By.CSS_SELECTOR, parent)
|
|
|
|
if negative:
|
|
|
|
assert dis is None, "Element is disabled: %s" % selector
|
|
|
|
else:
|
|
|
|
assert dis, "Element is not disabled: %s" % selector
|
|
|
|
|
2013-05-24 06:48:53 -05:00
|
|
|
def assert_record(self, pkey, parent=None, table_name=None, negative=False):
|
|
|
|
"""
|
|
|
|
Assert that record is in current search table
|
|
|
|
"""
|
|
|
|
has = self.has_record(pkey, parent, table_name)
|
2014-09-22 05:01:47 -05:00
|
|
|
has |= self.has_record(pkey.lower(), parent, table_name)
|
2013-05-24 06:48:53 -05:00
|
|
|
if negative:
|
|
|
|
assert not has, "Record exists when it shouldn't: %s" % pkey
|
|
|
|
else:
|
|
|
|
assert has, 'Record does not exist: %s' % pkey
|
|
|
|
|
2014-09-22 05:01:47 -05:00
|
|
|
def assert_indirect_record(self, pkey, entity, facet, negative=False, switch=True):
|
2013-05-24 06:48:53 -05:00
|
|
|
"""
|
|
|
|
Switch to indirect facet and assert record.
|
|
|
|
|
|
|
|
Lowers the key by default.
|
|
|
|
"""
|
|
|
|
if switch:
|
|
|
|
self.switch_to_facet(facet)
|
|
|
|
radio_name = "%s-%s-type-radio" % (entity, facet.replace('_', '-'))
|
|
|
|
self.check_option(radio_name, 'indirect')
|
|
|
|
self.wait_for_request(n=2)
|
|
|
|
key = pkey
|
2013-07-24 10:22:40 -05:00
|
|
|
self.assert_record(key, negative=negative)
|
2013-05-24 06:48:53 -05:00
|
|
|
|
2018-03-26 05:34:25 -05:00
|
|
|
def assert_record_value(self, expected, pkeys, column, parent=None,
|
|
|
|
table_name=None):
|
2013-08-16 11:18:53 -05:00
|
|
|
"""
|
2018-03-26 05:34:25 -05:00
|
|
|
Assert that column's value of record defined by pkey equals expected
|
|
|
|
value.
|
2013-08-16 11:18:53 -05:00
|
|
|
"""
|
2018-03-26 05:34:25 -05:00
|
|
|
|
|
|
|
if type(pkeys) is not list:
|
|
|
|
pkeys = [pkeys]
|
|
|
|
|
|
|
|
for pkey in pkeys:
|
|
|
|
val = self.get_record_value(pkey, column, parent, table_name)
|
|
|
|
assert expected == val, ("Invalid value: '%s'. Expected: '%s'."
|
|
|
|
% (val, expected))
|
2013-08-16 11:18:53 -05:00
|
|
|
|
2013-05-24 06:48:53 -05:00
|
|
|
def assert_class(self, element, cls, negative=False):
|
|
|
|
"""
|
|
|
|
Assert that element has certain class
|
|
|
|
"""
|
2014-04-17 02:07:10 -05:00
|
|
|
valid = self.has_class(element, cls)
|
2013-05-24 06:48:53 -05:00
|
|
|
if negative:
|
|
|
|
assert not valid, "Element contains unwanted class: %s" % cls
|
|
|
|
else:
|
|
|
|
assert valid, "Element doesn't contain required class: %s" % cls
|
|
|
|
|
|
|
|
def assert_rule_tables_enabled(self, tables, enabled):
|
2013-06-26 09:10:35 -05:00
|
|
|
"""
|
|
|
|
Assert that rule table is editable - values can be added and removed.
|
|
|
|
"""
|
2013-05-24 06:48:53 -05:00
|
|
|
for table in tables:
|
|
|
|
self.assert_table_button_enabled('add', table, enabled)
|
2013-08-15 08:49:27 -05:00
|
|
|
|
|
|
|
def assert_menu_item(self, path, present=True):
|
|
|
|
"""
|
|
|
|
Assert that menu link is not rendered or visible
|
|
|
|
"""
|
|
|
|
s = ".navigation a[href='#%s']" % path
|
|
|
|
link = self.find(s, By.CSS_SELECTOR)
|
|
|
|
is_present = link is not None and link.is_displayed()
|
|
|
|
assert present == is_present, ('Invalid state of navigation item: %s. '
|
|
|
|
'Presence expected: %s') % (path, str(present))
|
|
|
|
|
|
|
|
def assert_action_panel_action(self, panel_name, action, visible=True, enabled=True):
|
|
|
|
"""
|
|
|
|
Assert that action panel action is visible/hidden, and enabled/disabled
|
|
|
|
|
|
|
|
Enabled is checked only if action is visible.
|
|
|
|
"""
|
|
|
|
s = "div[data-name='%s'].action-panel" % panel_name
|
|
|
|
s += " a[data-name='%s']" % action
|
|
|
|
link = self.find(s, By.CSS_SELECTOR)
|
|
|
|
|
|
|
|
is_visible = link is not None and link.is_displayed()
|
|
|
|
is_enabled = False
|
|
|
|
if is_visible:
|
2014-05-14 03:35:18 -05:00
|
|
|
is_enabled = not self.has_class(link, 'disabled')
|
2013-08-15 08:49:27 -05:00
|
|
|
|
|
|
|
assert is_visible == visible, ('Invalid visibility of action button: %s. '
|
|
|
|
'Expected: %s') % (action, str(visible))
|
|
|
|
|
|
|
|
if is_visible:
|
|
|
|
assert is_enabled == enabled, ('Invalid enabled state of action button %s. '
|
|
|
|
'Expected: %s') % (action, str(visible))
|
2014-05-14 03:35:18 -05:00
|
|
|
|
2016-07-20 05:59:27 -05:00
|
|
|
def assert_action_list_action(self, action, visible=True, enabled=True,
|
|
|
|
parent=None, parents_css_sel=None,
|
|
|
|
facet_actions=True):
|
2014-05-14 03:35:18 -05:00
|
|
|
"""
|
|
|
|
Assert that action dropdown action is visible/hidden, and enabled/disabled
|
|
|
|
|
|
|
|
Enabled is checked only if action is visible.
|
|
|
|
"""
|
2016-07-20 05:59:27 -05:00
|
|
|
|
|
|
|
li_s = " li[data-name='%s']" % action
|
|
|
|
|
2014-05-14 03:35:18 -05:00
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
|
|
|
|
2016-07-20 05:59:27 -05:00
|
|
|
if facet_actions:
|
|
|
|
li_s = ".facet-actions" + li_s
|
|
|
|
else:
|
|
|
|
li_s = parents_css_sel + li_s
|
|
|
|
|
|
|
|
li = self.find(li_s, By.CSS_SELECTOR, parent)
|
2014-06-26 07:38:05 -05:00
|
|
|
link = self.find("a", By.CSS_SELECTOR, li)
|
2014-05-14 03:35:18 -05:00
|
|
|
|
2014-06-26 07:38:05 -05:00
|
|
|
is_visible = li is not None and link is not None
|
2014-05-14 03:35:18 -05:00
|
|
|
is_enabled = False
|
|
|
|
|
|
|
|
assert is_visible == visible, ('Invalid visibility of action item: %s. '
|
|
|
|
'Expected: %s') % (action, str(visible))
|
|
|
|
|
|
|
|
if is_visible:
|
2014-06-26 07:38:05 -05:00
|
|
|
is_enabled = not self.has_class(li, 'disabled')
|
2014-05-14 03:35:18 -05:00
|
|
|
assert is_enabled == enabled, ('Invalid enabled state of action item %s. '
|
|
|
|
'Expected: %s') % (action, str(visible))
|
2018-03-13 12:51:10 -05:00
|
|
|
|
2018-05-11 01:20:39 -05:00
|
|
|
def assert_field_validation(self, expect_error, parent=None, field=None):
|
2018-03-13 12:52:45 -05:00
|
|
|
"""
|
2018-04-05 02:51:51 -05:00
|
|
|
Assert for error in field validation
|
2018-03-13 12:52:45 -05:00
|
|
|
"""
|
|
|
|
|
|
|
|
if not parent:
|
|
|
|
parent = self.get_form()
|
|
|
|
|
2018-05-11 01:20:39 -05:00
|
|
|
if field:
|
|
|
|
field_s = '.widget[name="{}"]'.format(field)
|
|
|
|
parent = self.find(field_s, By.CSS_SELECTOR, context=parent)
|
|
|
|
|
2018-03-13 12:52:45 -05:00
|
|
|
req_field_css = '.help-block[name="error_link"]'
|
|
|
|
|
|
|
|
res = self.find(req_field_css, By.CSS_SELECTOR, context=parent)
|
2018-04-05 02:51:51 -05:00
|
|
|
assert expect_error in res.text, \
|
|
|
|
'Expected error: {} not found'.format(expect_error)
|
|
|
|
|
2018-05-11 01:20:39 -05:00
|
|
|
def assert_field_validation_required(self, parent=None, field=None):
|
|
|
|
self.assert_field_validation('Required field', parent, field)
|
2018-03-13 12:52:45 -05:00
|
|
|
|
2018-03-13 12:51:10 -05:00
|
|
|
def assert_notification(self, type='success', assert_text=None):
|
|
|
|
"""
|
|
|
|
Assert whether we have a notification of particular type
|
|
|
|
|
|
|
|
type: type for assertion
|
|
|
|
assert_text: assert particular text when True
|
|
|
|
|
|
|
|
Returns True if selector/text found
|
|
|
|
"""
|
|
|
|
|
|
|
|
notification_type = 'div.notification-area .alert-{}'.format(type)
|
|
|
|
# wait for a half sec for notification to appear
|
|
|
|
self.wait(0.5)
|
|
|
|
is_present = self.find(notification_type, By.CSS_SELECTOR)
|
|
|
|
assert is_present, "Notification not present"
|
|
|
|
if assert_text:
|
|
|
|
assert assert_text in is_present.text
|
2018-03-26 05:34:25 -05:00
|
|
|
|
|
|
|
def assert_last_error_dialog(self, expected_err, details=False,
|
|
|
|
dialog_name='error_dialog'):
|
|
|
|
"""
|
|
|
|
Assert error dialog body text or when details=True click on
|
|
|
|
'Show details' and assert text there
|
|
|
|
"""
|
|
|
|
|
|
|
|
err_dialog = self.get_last_error_dialog(dialog_name=dialog_name)
|
|
|
|
|
|
|
|
if details:
|
|
|
|
# open "Show details" paragraph
|
|
|
|
s = 'a[title="Show details"]'
|
|
|
|
details = self.find(s, By.CSS_SELECTOR)
|
|
|
|
details.click()
|
|
|
|
|
|
|
|
s = 'ul.error-container li p'
|
|
|
|
self.assert_text(s, expected_err, parent=err_dialog)
|
|
|
|
|
|
|
|
else:
|
|
|
|
s = '.modal-body div p'
|
|
|
|
self.assert_text(s, expected_err, parent=err_dialog)
|
2018-04-19 08:27:42 -05:00
|
|
|
|
|
|
|
def assert_value_checked(self, values, name, negative=False):
|
|
|
|
"""
|
|
|
|
Assert particular value is checked
|
|
|
|
"""
|
|
|
|
|
|
|
|
if type(values) is not list:
|
|
|
|
values = [values]
|
|
|
|
|
|
|
|
checked_values = self.get_field_checked(name)
|
|
|
|
|
|
|
|
for value in values:
|
|
|
|
if negative:
|
|
|
|
assert value not in checked_values, (
|
|
|
|
'{} checked while it should not be'.format(value)
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
assert value in checked_values, ('{} NOT checked while it '
|
|
|
|
'should be'.format(value))
|