def test_config_reader(self): try: config_ref = configparser.ConfigParser() config_ref.read('kissenium.ini') # Test default values config = Config() self.assertEqual("Chrome", config.get_browser()) self.assertEqual("True", config.get_record_scenarios()) self.assertEqual("Full", config.get_capture_size()) # Test fallback value # We also test that CaptureSize and RecordScenarios are deactivated when we are using the parallel execution new_config = configparser.ConfigParser() new_config.read('kissenium.ini') new_config.remove_option('Kissenium', 'Browser') new_config.set('Kissenium', 'RunParallel', 'True') with open('kissenium.ini', 'w') as f: new_config.write(f) config = Config() self.assertEqual("Chrome", config.get_browser()) self.assertEqual("Browser", config.get_capture_size()) self.assertEqual("False", config.get_record_scenarios()) finally: # Resetting ini file values with open('kissenium.ini', 'w') as f: config_ref.write(f)
def __init__(self): """Init Kissenium Runner class """ self.start = datetime.datetime.now() self.prepare_for_run() self.config = Config() self.logger = Log4Kissenium().setup("Kissenium", "Kissenium") self.logger.info("Logger created.") self.loader = TestLoader() self.suites = []
def __init__(self, scenario, test): """ Initializing the Screenshot class :param scenario: Scenario name :param test: Test name """ self.scenario = scenario self.test = test self.cancelled = False self.config = Config() self.reports_folder = SmallTools.get_reports_folder(self.scenario)
def __init__(self, logger, screenshot): self.screenshot = screenshot self.logger = logger self.config = Config() self.browser = Platform.get_webdriver(self.config.get_browser(), self.config.get_headless()) if self.config.get_focus() == 'True': self.logger.debug('Focus on browser window.') self.browser.switch_to_window(self.browser.current_window_handle) self.js = JsTools(self.config.get_message_status(), self.config.get_dim_status(), self.logger, self.config.get_page_wait()) if self.config.get_browser_size() == "Maximize": self.maximize() else: width, height = self.config.get_browser_size().split('*') self.resize_window(width, height)
def __init__(self, scenario, test): # Note: if we wan't to record distant execution of kissenium (not implemented for now), we could think of using # vnc server on the remote executor threading.Thread.__init__(self) self.scenario = scenario self.reports_folder = SmallTools.get_reports_folder(self.scenario) self.test = test self.cancelled = False self.config = Config() self.logger = Log4Kissenium.get_logger("Kissenium")
class Screenshot: """ Class used to take screenshot of the full screen or the browser only """ scenario = "" def __init__(self, scenario, test): """ Initializing the Screenshot class :param scenario: Scenario name :param test: Test name """ self.scenario = scenario self.test = test self.cancelled = False self.config = Config() self.reports_folder = SmallTools.get_reports_folder(self.scenario) def capture(self, browser, suffix=''): """ Capture the current test :param browser: Selenium instance :param suffix: Suffix to put to filename :return: """ if suffix != '': suffix = '-' + suffix filename = SmallTools.sanitize_filename('%s%s.png' % (self.test, suffix)) if self.config.get_capture_size() == 'Full': self.capture_screen(filename) else: self.capture_browser(browser, filename) def capture_screen(self, filename): """ Capture the current screen (full capture) :param filename: Filename to use :return: """ with mss.mss() as sct: sct.shot(output=self.reports_folder + filename) def capture_browser(self, browser, filename): """ Capture the test inside the brpwser :param browser: Selenium instance :param filename: Filename to use :return: """ reports_folder = SmallTools.get_reports_folder(self.scenario) browser.get_screenshot_as_file(reports_folder + filename)
class Log4Kissenium: def __init__(self): self.config = Config() def setup(self, name, path): """ Every log file will be created in "reports/" folder. :param name: Filename of the log :param path: Relative path of the log :return: logger """ final_path = SmallTools.get_reports_folder(path) logger = logging.getLogger(name) logger.setLevel(self.get_log_level()) formatter = logging.Formatter( '%(asctime)s :: %(levelname)s :: %(message)s') file_handler = RotatingFileHandler(final_path + name + '.log', 'a', 1000000, 1) file_handler.setLevel(self.get_log_level()) file_handler.setFormatter(formatter) logger.addHandler(file_handler) return logger def get_log_level(self): if self.config.get_log_level() == "DEBUG": return logging.DEBUG elif self.config.get_log_level() == "INFO": return logging.INFO elif self.config.get_log_level() == "WARNING": return logging.WARNING elif self.config.get_log_level() == "ERROR": return logging.ERROR else: return logging.DEBUG @staticmethod def get_logger(name): return logging.getLogger(name)
class Selenium: """ Selenium class to use Selenium webdriver or marionette This class is intented to surround every selenium call with a maximum of reporting in case of error """ def __init__(self, logger, screenshot): self.screenshot = screenshot self.logger = logger self.config = Config() self.browser = Platform.get_webdriver(self.config.get_browser(), self.config.get_headless()) if self.config.get_focus() == 'True': self.logger.debug('Focus on browser window.') self.browser.switch_to_window(self.browser.current_window_handle) self.js = JsTools(self.config.get_message_status(), self.config.get_dim_status(), self.logger, self.config.get_page_wait()) if self.config.get_browser_size() == "Maximize": self.maximize() else: width, height = self.config.get_browser_size().split('*') self.resize_window(width, height) def __handle_exception(self, message): """ Handle Selennium exception :return: Nothing """ self.logger.error(message) if self.config.get_capture_on_fail() == "True": self.screenshot.capture(self.browser, message) if self.config.get_fail_on_error() == "True": raise Exception(message) return False def maximize(self): """ Maximize browser window :return: Nothing """ self.browser.maximize_window() self.logger.info("Browser window has been maximized.") def resize_window(self, width, height): """ Resize browser window to width and height :param width: Int :param height: Int :return: Nothing """ self.browser.set_window_size(width, height) self.logger.info("Browser window has been resized to : %s x %s." % (width, height)) def alert(self, message, duration, pause): """ Display a message into the browser (if activated in kissenium.ini file) :param message: Your message :param duration: Duration of the display :param pause: Time while nothing will be done :return: """ self.js.message(self.browser, message, duration, pause) def dim(self, e_id, duration): """ Dimming the page to get one element lighter :param e_id: The id of the element :param duration: Duration of the dimming :return: """ self.js.dim_by_id(self.browser, e_id, duration) def quit(self): """ Quitting the browser :return: """ self.browser.quit() @exception("Error getting the url") def get(self, url): """ Get one url into the browser :param url: :return: """ self.browser.get(url) def page_wait(self, by_type, element): """ Wait for one element to be present in the web page, and block execution while not present :param by_type: :param element: :return: """ try: WebDriverWait(self.browser, int(self.config.get_page_wait())).until( ec.presence_of_element_located( (By.__dict__.get(by_type), element))) self.logger.info("[page_wait] Page loaded") return True except TimeoutException as e: self.logger.error("[page_wait] TimeoutException error : %s" % e) return False except Exception as e: self.__handle_exception("[page_wait] Error : %s" % e) def page_wait_for_id(self, element): """ Wait for id to be present in the page :param element: :return: """ self.page_wait('ID', element) def page_wait_for_xpath(self, element): """ Wait for xpath to be present in the webpage :param element: :return: """ self.page_wait('XPATH', element) def test_element(self, by_type, path): """Test if the element is present in the page This method will return True or False, but will not fail. This method is intended to be used with an assert method :param by_type: Type of path :param path: Path (id, xpath) :return: Boolean """ try: self.browser.find_element(By.__dict__.get(by_type), value=path) self.logger.info("[test_element] Success : %s -- %s " % (type, path)) return True except Exception as e: self.logger.info("[test_element] Element not found : %s -- %s " % (type, path)) return False def test_element_by_xpath(self, xpath): """Wrapper for test_element() This method is intended to be used with an assert method :param xpath: Xpath for element :return: Boolean """ return self.test_element("XPATH", xpath) def test_element_by_id(self, id): """Wrapper for test_element() This method is intended to be used with an assert method :param id: Element id :return: Boolean """ return self.test_element("ID", id) def get_element(self, by_type, path): """ Get element in page :param by_type: :param path: :return: """ try: el = self.browser.find_element(By.__dict__.get(by_type), path) self.logger.info("[get_element] Success : %s -- %s " % (by_type, path)) return el except Exception as e: self.__handle_exception("[get_element] By %s : Exception : %s" % (by_type, e)) def get_elements(self, by_type, path): """ Get multiple elements by type :param by_type: Type of dom manipulation to use :param path: Path to use for the query :return: """ try: el = self.browser.find_elements(By.__dict__.get(by_type), path) self.logger.info("[get_elements] Success : %s -- %s " % (by_type, path)) return el except Exception as e: self.__handle_exception("[get_elements] By %s : Exception : %s" % (by_type, e)) @exception("Error getting the element by xpath") def get_element_by_xpath(self, xpath): """ Get element by xpath :param xpath: :return: """ return self.get_element("XPATH", xpath) @exception("Error getting the element by id") def get_element_by_id(self, e_id): """ Get element by id :param e_id: :return: """ return self.get_element("ID", e_id) @exception("Error getting the text element by xpath") def get_element_text_by_xpath(self, xpath): """ Get element by xpath :param xpath: :return: """ return self.get_first_element_by_xpath(xpath).text @exception("Error getting the text element by xpath") def get_element_text_by_id(self, xpath): """ Get element by xpath£* :param xpath: :return: """ return self.get_element_by_id(xpath).text @exception("Error getting the elements by xpath") def get_elements_by_xpath(self, xpath): """ Get all elements corresponding to xpath :param xpath: :return: """ return self.get_elements("XPATH", xpath) @exception("Error getting the first element by xpath") def get_first_element_by_xpath(self, xpath): """ Get first element corresponding to xpath :param xpath: :return: """ el = self.get_elements_by_xpath(xpath) for e in el: self.logger.debug(e.text) self.logger.info("[get_first_element_by_xpath] Success : %s " % xpath) return el[0] @exception("Error sending keys to element by id") def send_keys(self, element, keys): """ Send keys to element :param element: Element to send keys :param keys: Keys to send :return: """ element.send_keys(keys) self.logger.info("[send_keys] Success : %s " % keys) @exception("Error sending keys to element by id") def send_keys_by_id(self, e_id, keys): """ Find element by id and send keys to it :param e_id: Element id :param keys: Keys to send :return: """ e = self.get_element_by_id(e_id) self.send_keys(e, keys) self.logger.info("[send_keys_by_id] Success : %s " % e_id) @exception("Error sending keys to element by xpath") def send_keys_by_xpath(self, xpath, keys): """ Find element by xpath and send keys to it :param xpath: Xpath to find element :param keys: Keys to send :return: """ e = self.get_element_by_xpath(xpath) self.send_keys(e, keys) self.logger.info("[send_keys_by_id] Success : %s " % xpath) @exception("Error clicking the first element by xpath") def click_first_xpath(self, xpath): """ Click first element finded by xpath :param xpath: :return: """ el = self.get_first_element_by_xpath(xpath) self.browser.execute_script("arguments[0].click();", el) self.logger.info("[click_first_xpath] Success : %s " % xpath) @exception("Error clicking the element by id") def click_id(self, e_id): """ Click element by id :param e_id: :return: """ self.get_element_by_id(e_id).click() self.logger.info("[click_first_xpath] Success : %s " % e_id) @exception("Error closing the tab") def close_tab(self): """ Close the browser tab :return: """ self.browser.find_element_by_tag_name('body').send_keys(Keys.CONTROL + 'w') self.logger.info("[close_tab] Success") @exception() def scroll_to_xpath(self, xpath): """ Hover one element with xpath :param xpath: xpath element to find :return: """ self.page_wait_for_xpath(xpath) element_to_hover = self.browser.find_element_by_xpath(xpath) self.browser.execute_script("arguments[0].scrollIntoView();", element_to_hover) self.logger.info("[hover_by_xpath] Success") @exception() def scroll_to_id(self, e_id): """ Hover one element with xpath :param xpath: xpath element to find :return: """ self.page_wait_for_id(e_id) element_to_hover = self.browser.find_element_by_id(e_id) self.browser.execute_script("arguments[0].scrollIntoView();", element_to_hover) self.logger.info("[scroll_to_id] Success") @exception() def scroll_to_element(self, e): """ Hover one element :param xpath: xpath element to find :return: """ self.browser.execute_script("arguments[0].scrollIntoView();", e) self.logger.info("[scroll_to_element] Success") @exception() def move_cursor_to(self, xpath): """ Hover one element with xpath :param xpath: xpath element to find :return: """ self.page_wait_for_xpath(xpath) element_to_hover = self.browser.find_element_by_xpath(xpath) ActionChains(self.browser).move_to_element(element_to_hover).perform() self.logger.info("[move_cursor_to] Success")
def __init__(self): self.config = Config()
def get_config(self): self.config = Config()
class BaseTest(unittest.TestCase): """ In this base class we will redefine some asserts tests to be sure that we have sufficient logs in the test reports folders. We do not modify the asserts tests, we just had logs where we want to and a status report. Be sure to refer to this url if you want to had a new one : https://docs.python.org/3/library/unittest.html """ def self_setup(self): self.has_error = False self.get_logger() self.get_config() self.get_capture_handler() self.selenium = Selenium(self.logger, self.screenshot) self.browser = self.selenium.browser def self_teardown(self): self.selenium.quit() self.logger.info("End of %s - %s Test" % (self.__class__.__name__, self._testMethodName)) def get_config(self): self.config = Config() def get_logger(self): """ For internal class use only, get the logger :return: Nothing """ self.logger = Log4Kissenium().setup(self._testMethodName, self.__class__.__name__) self.logger.info("Starting %s-%s Test" % (self.__class__.__name__, self._testMethodName)) def get_capture_handler(self): """ Get capture handler :return: Nothing """ self.screenshot = Screenshot(self.__class__.__name__, self._testMethodName) def take_capture(self, suffix=''): """ Take a capture of the running test. Configuration come from kissenium.ini (CaptureSize : Full | Browser) :return: Nothing """ self.screenshot.capture(self.selenium.browser, suffix) def take_assert_capture(self, suffix=''): """ Take a capture of the failing assert moment. Configuration come from kissenium.ini (CaptureOnAssertFail : True | False) :return: Nothing """ if self.config.get_capture_on_assert_fail() == 'True': self.take_capture(suffix) @assertion_error() def l_assertEqual(self, a, b, stop_on_fail=None): """ Test if a is equal to b, standard assert test but with log and status report stop_on_fail will override FailOnAssertError from kissenium.ini. :param a: First parameter :param b: Second parameter :param stop_on_fail: True | False :return: Nothing """ self.assertEqual(a, b) self.logger.info("AssertEqual : %s is equal to %s" % (a, b)) @assertion_error() def l_assertNotEqual(self, a, b, stop_on_fail=None): """ Test if a is True, standard assert test but with log and status report stop_on_fail will override FailOnAssertError from kissenium.ini. :param a: First parameter :param b: Second parameter :param stop_on_fail: True | False :return: Nothing """ self.assertNotEqual(a, b) self.logger.info("AssertNotEqual : %s is not equal to %s" % (a, b)) @assertion_error() def l_assertTrue(self, a, stop_on_fail=None): """ Test if a is equal to b, standard assert test but with log and status report stop_on_fail will override FailOnAssertError from kissenium.ini. :param a: First parameter :param stop_on_fail: True | False :return: Nothing """ self.assertTrue(a) self.logger.info("AssertTrue : %s is True" % a) @assertion_error() def l_assertFalse(self, a, stop_on_fail=None): """ Test if a is False, standard assert test but with log and status report stop_on_fail will override FailOnAssertError from kissenium.ini. :param a: First parameter :param stop_on_fail: True | False :return: Nothing """ self.assertFalse(a) self.logger.info("AssertFalse : %s is False" % a) @assertion_error() def l_assertIsNone(self, a, stop_on_fail=None): """ Test if a is None, standard assert test but with log and status report stop_on_fail will override FailOnAssertError from kissenium.ini. :param a: First parameter :param stop_on_fail: True | False :return: Nothing """ self.assertIsNone(a) self.logger.info("AssertIsNone : %s is None" % a) @assertion_error() def l_assertIsNotNone(self, a, stop_on_fail=None): """ Test if a is not None, standard assert test but with log and status report stop_on_fail will override FailOnAssertError from kissenium.ini. :param a: First parameter :param stop_on_fail: True | False :return: Nothing """ self.assertIsNotNone(a) self.logger.info("AssertIsNotNone : %s is not None" % a) @assertion_error() def l_assertIn(self, a, b, stop_on_fail=None): """ Test if a is in b, standard assert test but with log and status report stop_on_fail will override FailOnAssertError from kissenium.ini. :param a: First parameter :param b: Second parameter :param stop_on_fail: True | False :return: Nothing """ self.assertIn(a, b) @assertion_error() def l_assertNotIn(self, a, b, stop_on_fail=None): """ Test if a is not in b, standard assert test but with log and status report stop_on_fail will override FailOnAssertError from kissenium.ini. :param a: First parameter :param b: Second parameter :param stop_on_fail: True | False :return: Nothing """ self.assertNotIn(a, b) self.logger.info("AssertNotIn : %s is not in %s" % (a, b))
class Kissenium: """Kissenium is a free software to run selenium tests To define new scenarios, please check the examples given in ``./scenarios`` folder. First run: You will need to create a new class file and add it to ``__init__.py``. Then, you can declare your new scenario like the following:: def __init__(self): ... self.test_classes_to_run = [scenarios.TestDemo, scenarios.ParallelDemo] ... Then you can run your tests: From your virtual environment:: $(Kissenium) ./kissenium.py Note: Don't forget to check the `kissenium.ini`` file. Here you will be able to activate or deactivate the main functionalities. Developers: Here are the commands that you will need: * Pylint:: $(Kissenium) pylint kissenium.py base/ * Documentation:: $(Kissenium) make clean && make html """ def __init__(self): """Init Kissenium Runner class """ self.start = datetime.datetime.now() self.prepare_for_run() self.config = Config() self.logger = Log4Kissenium().setup("Kissenium", "Kissenium") self.logger.info("Logger created.") self.loader = TestLoader() self.suites = [] @staticmethod def clean_reports_folder(): """Clean reports folder on every run :return: """ reports_list = glob.glob("reports/*") globs = [reports_list] for g in globs: SmallTools.delete_from_glob(g) def prepare_for_run(self): """Prepare the report folders for Kissenium execution :return: """ self.clean_reports_folder() SmallTools.check_path("reports/tmp") def execution(self): """Execute Kissenium with a single test runner :return: """ results = {} for test_class in scenarios.__actives__: suite = self.loader.loadTestsFromTestCase(test_class) self.suites.append(suite) suite = TestSuite(self.suites) test_runner = HTMLTestRunner( output='html', template='resources/html/kissenium-template.html', report_title='Test report') results['single_runner'] = test_runner.run(suite) return (results['single_runner'].wasSuccessful()), results def parallel_execution(self): """Execute kissenium with parallels tests runners You can disable (or enable) the parallels runners, and modify the max number of threads in ``kissenium.ini``:: RunParallel = True MaxParallel = 5 Solution for parrallel execution finded here: https://stackoverflow.com/questions/38189461/how-can-i-execute-in-parallel-selenium-python-tests-with-unittest :return: """ suite = TestSuite() results = {} for test in scenarios.__actives__: suite.addTest(TestLoader().loadTestsFromTestCase(test)) with ThreadPoolExecutor( max_workers=int(self.config.get_max_parallel())) as executor: list_of_suites = list(suite) for test in list_of_suites: results[str(test)] = executor.submit( HTMLTestRunner( output='html', template='resources/html/kissenium-template.html', report_title=str(test)).run, test) executor.shutdown(wait=True) for key, future in results.items(): result = future.result() self.logger.debug('[%s] Result is : %s', key, result.wasSuccessful()) if not result.wasSuccessful(): return False, results return True, results def run(self): """Run Kissenium tests :return: """ self.logger.info('Launching tests ...') if self.config.get_run_parallel() == 'True': self.logger.info("Test are parallel") status, results = self.parallel_execution() else: self.logger.info("Test are not parallel") status, results = self.execution() self.logger.info( "All tests have been executed. Kissenium will stop now.") HtmlRender(results, self.start).create_index() JunitResults(results, self.start).generate() sys.exit(not status)