class ShishitoControlTest(object):
    """ Base class for ControlTest objects. """

    def __init__(self):
        self.shishito_support = ShishitoSupport()

        # create control environment object
        control_env_obj = self.shishito_support.get_module('test_environment')
        self.test_environment = control_env_obj(self.shishito_support)

        self.driver = None

    def start_browser(self):
        """ Webdriver startup function.

        :return: initialized webdriver
        """

        base_url = self.shishito_support.get_opt('base_url')
        config_section = self.shishito_support.get_opt('environment_configuration')

        # call browser from proper environment
        self.driver = self.test_environment.call_browser(config_section)

        # load init url
        if base_url:
            self.test_init(base_url)

        return self.driver

    def start_test(self, reload_page=None):
        """ To be executed before every test-case (test function).

        :param reload_page:
        """

    def stop_browser(self):
        """ Webdriver termination function. """

        self.driver.quit()

    def stop_test(self, test_info):
        """ To be executed after every test-case (test function). If test failed, function saves
        screenshots created during test.

        :param test_info: information about test
        """

        if test_info.test_status not in ('passed', None):
            # save screenshot in case test fails
            screenshot_folder = os.path.join(self.shishito_support.project_root, 'screenshots')
            if not os.path.exists(screenshot_folder):
                os.makedirs(screenshot_folder)

            file_name = re.sub('[^A-Za-z0-9_. ]+', '', test_info.test_name)
            self.driver.save_screenshot(os.path.join(screenshot_folder, file_name + '.png'))

    def test_init(self, url):
        """ Executed only once after browser starts.
Esempio n. 2
0
class CircleAPI(object):
    """Handles communication with Circle CI via REST API"""
    def __init__(self):
        self.shishito_support = ShishitoSupport()
        self.api_token = self.shishito_support.get_opt('circleci_api_token')
        self.circle_username = self.shishito_support.get_opt(
            'circleci_username')
        self.circle_project = self.shishito_support.get_opt('circleci_project')
        self.circle_branch = self.shishito_support.get_opt('circleci_branch')

    def collect_artifacts(self, destination_folder):
        """downloads build artifacts from CircleCI for latest build from specific branch"""
        if not os.path.exists(destination_folder):
            os.makedirs(destination_folder)
        artifact_data = self.get_artifact_data()
        for artifact in artifact_data:
            self.save_artifact(artifact, destination_folder)
        return bool(os.listdir(destination_folder))

    def save_artifact(self, artifact, destination_folder):
        """ saves artifact into specified folder """
        file_name = artifact['url'].split('/')[-1]
        file_request_url = artifact['url'] + '?circle-token=' + self.api_token

        response = requests.get(file_request_url, stream=True)
        response.raise_for_status()

        with open(os.path.join(destination_folder, file_name),
                  'wb') as extension:
            for block in response.iter_content(1024):
                extension.write(block)

    def get_artifact_data(self):
        """returns json with artifact urls"""
        latest_dev_build_url = (
            'https://circleci.com/api/v1/project/{circle_username}/{circle_project}/tree/'
            '{circle_branch}?circle-token={api_token}&limit=1').format(
                **self.__dict__)

        headers = {'Accept': 'application/json'}
        response = requests.get(latest_dev_build_url, headers=headers)
        builds_json = json.loads(response.text)
        build_number = builds_json[0]['build_num']

        artifacts_url = (
            'https://circleci.com/api/v1/project/{circle_username}/'
            '{circle_project}/{build_number}/artifacts?circle-token={api_token}'
        ).format(build_number=build_number, **self.__dict__)

        response = requests.get(artifacts_url, headers=headers)
        return json.loads(response.text)
Esempio n. 3
0
class CircleAPI(object):
    """Handles communication with Circle CI via REST API"""

    def __init__(self):
        self.shishito_support = ShishitoSupport()
        self.api_token = self.shishito_support.get_opt("circleci_api_token")
        self.circle_username = self.shishito_support.get_opt("circleci_username")
        self.circle_project = self.shishito_support.get_opt("circleci_project")
        self.circle_branch = self.shishito_support.get_opt("circleci_branch")

    def collect_artifacts(self, destination_folder):
        """downloads build artifacts from CircleCI for latest build from specific branch"""
        if not os.path.exists(destination_folder):
            os.makedirs(destination_folder)
        artifact_data = self.get_artifact_data()
        for artifact in artifact_data:
            self.save_artifact(artifact, destination_folder)
        return bool(os.listdir(destination_folder))

    def save_artifact(self, artifact, destination_folder):
        """ saves artifact into specified folder """
        file_name = artifact["url"].split("/")[-1]
        file_request_url = artifact["url"] + "?circle-token=" + self.api_token

        response = requests.get(file_request_url, stream=True)
        response.raise_for_status()

        with open(os.path.join(destination_folder, file_name), "wb") as extension:
            for block in response.iter_content(1024):
                extension.write(block)

    def get_artifact_data(self):
        """returns json with artifact urls"""
        latest_dev_build_url = (
            "https://circleci.com/api/v1/project/{circle_username}/{circle_project}/tree/"
            "{circle_branch}?circle-token={api_token}&limit=1"
        ).format(**self.__dict__)

        headers = {"Accept": "application/json"}
        response = requests.get(latest_dev_build_url, headers=headers)
        builds_json = json.loads(response.text)
        build_number = builds_json[0]["build_num"]

        artifacts_url = (
            "https://circleci.com/api/v1/project/{circle_username}/"
            "{circle_project}/{build_number}/artifacts?circle-token={api_token}"
        ).format(build_number=build_number, **self.__dict__)

        response = requests.get(artifacts_url, headers=headers)
        return json.loads(response.text)
Esempio n. 4
0
class EmailIMAP(object):
    def __init__(self):
        self.shishito_support = ShishitoSupport()
        self.email_address = self.shishito_support.get_opt('email_address')
        self.email_imap = self.shishito_support.get_opt('email_imap')
        self.email_password = self.shishito_support.get_opt('email_password')
        self.email_mailbox = self.shishito_support.get_opt('email_mailbox')

        self.mail = imaplib.IMAP4_SSL(self.email_imap)
        self.mail.login(self.email_address, self.email_password)

    def retrieve_latest_email(self, timeout=36):
        """ tries to retrieve latest email. If there are no emails,
        it waits for 5 seconds and then tries again, until total (default 36*5 = 180s) timeout is reached """
        count = 0
        while count < timeout:
            id_list = self.get_all_email_ids()
            if id_list:
                return self.get_message(id_list[-1])
            time.sleep(5)
            count += 1

    def cleanup_emails(self):
        """ cleans up email folder """
        for num in self.get_all_email_ids():
            self.mail.store(num, '+FLAGS', '\\Deleted')
        self.mail.expunge()

    def is_pattern_in_message(self, pattern, message):
        """ return Boolean if pattern is to be found in message """
        return bool(re.match(pattern, message))

    # SUPPORT METHODS

    def get_message(self, message_id):
        """ gets email message """
        data = self.mail.fetch(
            message_id,
            "(RFC822)")[1]  # fetch the email body (RFC822) for the given ID
        raw_email = data[0][
            1]  # here's the body, which is raw text of the whole email
        return email.message_from_string(raw_email)

    def get_all_email_ids(self):
        """ connects to gmail account and return list of all email ids """
        self.mail.select(self.email_mailbox)
        data = self.mail.search(None, "ALL")[1]
        return data[0].split()  #ids split
Esempio n. 5
0
class EmailIMAP(object):
    def __init__(self):
        self.shishito_support = ShishitoSupport()
        self.email_address = self.shishito_support.get_opt('email_address')
        self.email_imap = self.shishito_support.get_opt('email_imap')
        self.email_password = self.shishito_support.get_opt('email_password')
        self.email_mailbox = self.shishito_support.get_opt('email_mailbox')

        self.mail = imaplib.IMAP4_SSL(self.email_imap)
        self.mail.login(self.email_address, self.email_password)

    def retrieve_latest_email(self, timeout=36):
        """ tries to retrieve latest email. If there are no emails,
        it waits for 5 seconds and then tries again, until total (default 36*5 = 180s) timeout is reached """
        count = 0
        while count < timeout:
            id_list = self.get_all_email_ids()
            if id_list:
                return self.get_message(id_list[-1])
            time.sleep(5)
            count += 1

    def cleanup_emails(self):
        """ cleans up email folder """
        for num in self.get_all_email_ids():
            self.mail.store(num, '+FLAGS', '\\Deleted')
        self.mail.expunge()

    def is_pattern_in_message(self, pattern, message):
        """ return Boolean if pattern is to be found in message """
        return bool(re.match(pattern, message))

    # SUPPORT METHODS

    def get_message(self, message_id):
        """ gets email message """
        data = self.mail.fetch(message_id, "(RFC822)")[1]  # fetch the email body (RFC822) for the given ID
        raw_email = data[0][1]  # here's the body, which is raw text of the whole email
        return email.message_from_string(raw_email)

    def get_all_email_ids(self):
        """ connects to gmail account and return list of all email ids """
        self.mail.select(self.email_mailbox)
        data = self.mail.search(None, "ALL")[1]
        return data[0].split() #ids split
Esempio n. 6
0
class ShishitoRunner(object):
    """ Base shishito test runner.

    - runs python selenium tests on customizable configurations (using PyTest)
    - archive the test results in .zip file """

    def __init__(self, project_root):
        # set project root
        self.project_root = project_root

        # test timestamp - for storing results
        self.test_timestamp = time.strftime("%Y-%m-%d_%H-%M-%S")
        self.epoch = int(time.time())

        # parse cmd  args
        self.cmd_args = self.handle_cmd_args()

        # Get SUT build for use in reporting
        self.test_build = self.cmd_args['build']

        self.reporter = Reporter(project_root, self.test_timestamp)
        self.shishito_support = ShishitoSupport(
            cmd_args=self.cmd_args,
            project_root=self.project_root
        )

    def handle_cmd_args(self):
        """ Retrieve command line arguments passed to the script.

        :return: dict with parsed command line arguments
        """

        parser = argparse.ArgumentParser(description='Selenium Python test runner execution arguments.')

        parser.add_argument('--platform',
                            help='Platform on which run tests.',
                            dest='test_platform')
        parser.add_argument('--environment',
                            help='Environment for which run tests.',
                            dest='test_environment')
        parser.add_argument('--test_directory',
                            help='Directory where to lookup for tests')
        parser.add_argument('--smoke',
                            help='Run only smoke tests',
                            action='store_true')
        parser.add_argument('--browserstack',
                            help='BrowserStack credentials; format: "username:token"')
        parser.add_argument('--saucelabs',
                            help='Saucelabs credentials; format: "username:token"')
        parser.add_argument('--test_rail',
                            help='TestRail Test Management tool credentials; format: "username:password"')
        parser.add_argument('--qastats',
                            help='QAStats Test Management tool credentials; format: "token"')
        parser.add_argument('--node_webkit_chromedriver_path',
                            help='Path to chromedriver located in same directory as node-webkit application')
        parser.add_argument('--app',
                            help='Path to appium application')
        parser.add_argument('--test',
                            help='Run specified test (PyTest string expression)')
        parser.add_argument('--build',
                            help='Specify build number for reporting purposes')
        parser.add_argument('--maxfail',
                            help='stop after x failures')
        args = parser.parse_args()

        # return args dict --> for use in other classes
        return vars(args)

    def run_tests(self):
        """ Execute tests for given platform and environment. Platform and Environment can be passed as command lines
        argument or settings in config file.
        """

        if __name__ == "__main__":
            sys.exit('The runner cannot be executed directly.'
                     ' You need to import it within project specific runner. Session terminated.')

        # cleanup previous results
        self.reporter.cleanup_results()

        # import execution class
        executor_class = self.shishito_support.get_module('platform_execution')
        # executor_class = getattr(import_module(platform_path), 'ControlExecution')
        executor = executor_class(self.shishito_support, self.test_timestamp)

        # run test
        exit_code = executor.run_tests()

        # archive results + generate combined report
        self.reporter.archive_results()
        self.reporter.generate_combined_report()

        # upload results to QAStats test management app
        qastats_credentials = self.shishito_support.get_opt('qastats')
        if qastats_credentials:
            try:
                qas_user, qas_password = qastats_credentials.split(':', 1)
            except (AttributeError, ValueError):
                raise ValueError('QAStats credentials were not specified! Unable to connect to QAStats.')

            qastats = QAStats(qas_user, qas_password, self.test_timestamp, self.epoch, self.test_build)
            qastats.post_results()

        # upload results to TestRail test management app
        test_rail_credentials = self.shishito_support.get_opt('test_rail')
        if test_rail_credentials:
            try:
                tr_user, tr_password = test_rail_credentials.split(':', 1)
            except (AttributeError, ValueError):
                raise ValueError('TestRail credentials were not specified! Unable to connect to TestRail.')

            test_rail = TestRail(tr_user, tr_password, self.test_timestamp, self.test_build)
            test_rail.post_results()

        return exit_code
Esempio n. 7
0
class ShishitoControlTest(object):
    """ Base class for ControlTest objects. """

    def __init__(self):
        self.shishito_support = ShishitoSupport()

        # create control environment object
        control_env_obj = self.shishito_support.get_module('test_environment')
        self.test_environment = control_env_obj(self.shishito_support)

        self.drivers = []

    def start_browser(self, base_url = None):
        """ Webdriver startup function.

        :return: initialized webdriver
        """

        config_section = self.shishito_support.get_opt('environment_configuration')

        # call browser from proper environment
        driver = self.test_environment.call_browser(config_section)
        self.drivers.append(driver)

        # load init url
        if not base_url:
            base_url = self.shishito_support.get_opt('base_url')

        if base_url:
            self.test_init(driver, base_url)
        else:
            self.test_init(driver)
        return driver

    def start_test(self, reload_page=None):
        """ To be executed before every test-case (test function).

        :param reload_page:
        """

    def stop_browser(self):
        """ Webdriver termination function. """

        for driver in self.drivers:
            driver.quit()   # Cleanup the driver info
        del self.drivers[:]

    def stop_test(self, test_info, debug_events=None):
        """ To be executed after every test-case (test function). If test failed, function saves
        screenshots created during test.

        :param test_info: information about test
        """

        if test_info.test_status not in ('passed', None):
            # save screenshot in case test fails
            test_name = re.sub('[^A-Za-z0-9_.]+', '_', test_info.test_name)

            # Capture screenshot and debug info from driver(s)
            for driver in self.drivers:
                if(self.shishito_support.test_platform == 'mobile'):
                    browser_name = 'appium'
                else:
                    browser_name = driver.name
                file_name = browser_name + '_' + test_name
                ts = SeleniumTest(driver)
                ts.save_screenshot(name=file_name)

                #Save debug info to file
                if debug_events is not None:
                    debugevent_folder = os.path.join(self.shishito_support.project_root, 'debug_events')

                    if not os.path.exists(debugevent_folder):
                        os.makedirs(debugevent_folder)

                    with open(os.path.join(debugevent_folder, file_name + '.json'), 'w') as logfile:
                            json.dump(debug_events, logfile)

    def test_init(self, driver, url=None):
        """ Executed only once after browser starts.
Esempio n. 8
0
class TestRail(object):
    """ TestRail object """

    def __init__(self, user, password, timestamp, build):
        self.shishito_support = ShishitoSupport()
        self.test_rail_instance = self.shishito_support.get_opt('test_rail_url')
        self.user = user
        self.password = password
        self.timestamp = timestamp

        # project specific config
        self.project_id = self.shishito_support.get_opt('test_rail_project_id')
        self.section_id = self.shishito_support.get_opt('test_rail_section_id')
        self.test_plan_id = self.shishito_support.get_opt('test_rail_test_plan_id')
        self.test_plan_name = self.shishito_support.get_opt('test_rail_test_plan_name') or build
        self.suite_id = self.shishito_support.get_opt('test_rail_suite_id')

        # shishito results
        self.reporter = Reporter()
        self.shishito_results = self.reporter.get_xunit_test_cases(timestamp)

        self.default_headers = {'Content-Type': 'application/json'}
        self.uri_base = self.test_rail_instance + '/index.php?/api/v2/'

    def post_results(self):
        """ Create test-cases on TestRail, adds a new test run and update results for the run """
        self.create_missing_test_cases()

        if self.test_plan_name:
            test_plan_id = self.add_test_plan()
        else:
            test_plan_id = self.test_plan_id

        test_run = self.add_test_run(test_plan_id)
        self.add_test_results(test_run)

    def tr_get(self, url):
        """ GET request for TestRail API

        :param url: url endpoint snippet
        :return: response JSON
        """
        response = requests.get(self.uri_base + url, auth=(self.user, self.password), headers=self.default_headers)
        #print(self.uri_base + url, response, response.text)
        return response.json()

    def tr_post(self, url, payload):
        """ GET request for TestRail API

        :param url: url endpoint snippet
        :param payload: payload for the POST api call
        :return: response object
        """
        return requests.post(self.uri_base + url, auth=(self.user, self.password), data=json.dumps(payload),
                             headers=self.default_headers)

    def get_all_test_plans(self):
        """ Gets list of all test-plans from certain project

        :return: list of test-plans (names = strings)
        """
        test_plans_list = self.tr_get('get_plans/{}'.format(self.project_id))
        return [{'name': test_plan['name'], 'id': test_plan['id']} for test_plan in test_plans_list]

    def get_all_test_cases(self):
        """ Gets list of all test-cases from certain project

        :return: list of test-cases (names = strings)
        """
        test_case_list = self.tr_get('get_cases/{}&section_id={}&suite_id={}'.format(self.project_id, self.section_id, self.suite_id))
        return [{'title': test_case['title'], 'id': test_case['id']} for test_case in test_case_list]

    def create_test_case(self, title):
        """ Creates a new test case in TestRail

        :param title: Title of the test-case
        :return: response object
        """
        return self.tr_post('add_case/{}'.format(self.section_id), {"title": title})

    def create_missing_test_cases(self):
        """ Creates new test-cases on TestRail for those in test project (those run by pytest).
         Does not create test-cases if already existed on TestRail.

        :return: list of test-cases that could not be created on TestRail (post failure)
        """
        post_errors = []
        test_case_names = [item['title'] for item in self.get_all_test_cases()]
        # Iterate over results for each environment combination
        for result_combination in self.shishito_results:
            # Create TestRail entry for every test-case in combination (if missing)
            for item in result_combination['cases']:
                if item['name'] not in test_case_names:
                    response = self.create_test_case(item['name'])
                    if response.status_code != requests.codes.ok:
                        post_errors.append(item['name'])
                    else:
                        test_case_names.append(item['name'])
        return post_errors

    def add_test_plan(self):
        test_plan_id = 0

        # Check if already exists
        for plan in self.get_all_test_plans():
            if plan['name'] == self.test_plan_name:
                return plan['id']

        result = self.tr_post('add_plan/{}'.format(self.project_id), {"name": self.test_plan_name})

        return json.loads(result.text)['id']

    def add_test_run(self, test_plan_id = None):
        """ Adds new test run under certain test plan into TestRail

        :return: dictionary of TestRail run names & IDs
        """
        test_plan_id = test_plan_id or self.test_plan_id
        runs_created = []
        # Iterate over results for each environment combination
        for result_combination in self.shishito_results:
            run_name = '{} ({})'.format(result_combination['name'][:-4], self.timestamp)
            test_run = {"case_ids": [case['id'] for case in self.get_all_test_cases()]}
            result = self.tr_post('add_plan_entry/{}'.format(test_plan_id),
                                  {"suite_id": self.suite_id, "name": run_name, "runs": [test_run]}).json()
            # lookup test run id
            for run in result['runs']:
                if run['name'] == run_name:
                    runs_created.append({'combination': result_combination['name'], 'id': run['id']})
        return runs_created

    def add_test_results(self, test_runs):
        """ Add test results for specific test run based on parsed xUnit results

        :return: list of test run IDs for which results could not be added (post failure)
        """
        post_errors = []
        run_ids = {r['combination']: r['id'] for r in test_runs}
        # Iterate over results for each environment combination
        for result in self.shishito_results:
            run_id = run_ids.get(result['name'])
            if not run_id:
                continue

            test_results = []
            tr_tests = {t['title']: t['id'] for t in self.tr_get('get_tests/{}'.format(run_id))}
            # Create TestRail entry for every test-case in combination (if missing)
            for xunit_test in result['cases']:
                tr_test_id = tr_tests.get(xunit_test['name'])
                result_id = {'success': 1, 'failure': 5}.get(xunit_test['result'])
                if tr_test_id and result_id:
                    # Add result content into the payload list
                    result = {'test_id': tr_test_id, 'status_id': result_id}
                    if result_id == 5:
                        result['comment'] = xunit_test['failure_message']
                    test_results.append(result)

            response = self.tr_post('add_results/{}'.format(run_id), {'results': test_results})
            if response.status_code != requests.codes.ok:
                post_errors.append(run_id)
        return post_errors
Esempio n. 9
0
class SeleniumTest(object):
    def __init__(self, driver):
        self.driver = driver
        self.shishito_support = ShishitoSupport()
        self.base_url = self.shishito_support.get_opt("base_url")
        self.default_implicit_wait = int(self.shishito_support.get_opt("default_implicit_wait"))
        self.timeout = int(self.shishito_support.get_opt("timeout"))

    def save_screenshot(self, name, project_root):
        """ Saves application screenshot """
        screenshot_folder = os.path.join(project_root, "screenshots")
        if not os.path.exists(screenshot_folder):
            os.makedirs(screenshot_folder)
        self.driver.save_screenshot(os.path.join(screenshot_folder, name + ".png"))

    def save_file_from_url(self, file_path, url):
        """ Saves file from url """
        if os.path.isfile(file_path):
            print "File %s already exists." % file_path
            return

        response = requests.get(url, stream=True)
        response.raise_for_status()

        with open(file_path, "wb") as save_file:
            for block in response.iter_content(1024):
                if not block:
                    break
                save_file.write(block)

    # Deprecated use property directly
    def get_base_url(self):
        return self.base_url

    # Deprecated use property directly
    def get_current_url(self):
        return self.current_url

    @property
    def current_url(self):
        """ Return the url for the current page."""
        return self.driver.current_url

    def hover_on(self, element):
        """ Mouse over specific element """
        mouse_over = ActionChains(self.driver).move_to_element(element)
        mouse_over.perform()

    def go_to_page(self, url):
        """ Opens url in currently active window """
        self.driver.get(url)
        self.driver.implicitly_wait(self.default_implicit_wait)

    def click_and_wait(self, element, locator=None, wait_time=None):
        """ clicks on a element and then waits for specific element to be present or simply waits implicitly """
        element.click()
        if locator:
            self.wait_for_element_ready(locator, wait_time)
        else:
            self.driver.implicitly_wait(10)

    def check_images_are_loaded(self):
        """ checks all images on the pages and verifies if they are properly loaded """
        script = (
            "return arguments[0].complete && typeof arguments[0].naturalWidth"
            ' != "undefined" && arguments[0].naturalWidth > 0'
        )
        images_not_loaded = []
        for image in self.driver.find_elements_by_tag_name("img"):
            loaded = self.driver.execute_script(script, image)
            if not loaded and image.get_attribute("src"):
                images_not_loaded.append("%s: %s" % (self.driver.title, image.get_attribute("src")))
        return images_not_loaded

    def is_element_present(self, locator):
        """
        True if the element at the specified locator is present in the DOM.
        Note: It returns false immediately if the element is not found.
        """
        self.driver.implicitly_wait(0)
        try:
            self.driver.find_element(*locator)
            return True
        except NoSuchElementException:
            return False
        finally:
            # set the implicit wait back
            self.driver.implicitly_wait(self.default_implicit_wait)

    def is_element_visible(self, locator):
        """
        True if the element at the specified locator is visible in the browser.
        Note: It uses an implicit wait if element is not immediately found.
        """
        try:
            return self.driver.find_element(*locator).is_displayed()
        except (NoSuchElementException, ElementNotVisibleException):
            return False

    def is_element_not_visible(self, locator):
        """
        True if the element at the specified locator is not visible.
        Note: It returns true immediately if the element is not found.
        """
        self.driver.implicitly_wait(0)
        try:
            return not self.driver.find_element(*locator).is_displayed()
        except (NoSuchElementException, ElementNotVisibleException):
            return True
        finally:
            # set the implicit wait back
            self.driver.implicitly_wait(self.default_implicit_wait)

    def wait_for_element_present(self, locator, timeout=None):
        """ Wait for the element at the specified locator
        to be present in the DOM. """
        timeout = timeout or self.timeout
        count = 0
        while not self.is_element_present(locator):
            time.sleep(1)
            count += 1
            if count == timeout:
                raise Exception("{0} has not loaded".format(locator))

    def wait_for_element_visible(self, locator, timeout=None):
        """
        Wait for the element at the specified locator to be visible.
        """
        timeout = timeout or self.timeout
        count = 0
        while not self.is_element_visible(locator):
            time.sleep(1)
            count += 1
            if count == timeout:
                raise Exception("{0} is not visible".format(locator))

    def wait_for_element_not_visible(self, locator, timeout=None):
        """
        Wait for the element at the specified locator not to be visible anymore.
        """
        timeout = timeout or self.timeout
        count = 0
        while self.is_element_visible(locator):
            time.sleep(1)
            count += 1
            if count == timeout:
                raise Exception("{0} is still visible".format(locator))

    def wait_for_element_not_present(self, locator, timeout=None):
        """ Wait for the element at the specified locator
         not to be present in the DOM. """
        timeout = timeout or self.timeout
        self.driver.implicitly_wait(0)
        try:
            WebDriverWait(self.driver, timeout).until(lambda s: len(self.find_elements(*locator)) < 1)
            return True
        except TimeoutException:
            Assert.fail(TimeoutException)
        finally:
            self.driver.implicitly_wait(self.default_implicit_wait)

    def wait_for_text_to_match(self, text, locator, max_count=20, delay=0.25):
        """ Waits for element text to match specified text, until certain deadline """
        element = self.driver.find_element(*locator)
        counter = 0
        while element.text != text:
            if counter < max_count:
                time.sleep(delay)
                counter += 1
                element = self.driver.find_element(*locator)
            else:
                Assert.fail(
                    '"'
                    + text
                    + '" text did not match "'
                    + element.text
                    + '" after '
                    + str(counter * delay)
                    + " seconds"
                )
                break

    def wait_for_attribute_value(self, attribute, attribute_text, locator, max_count=20, delay=0.25):
        """ Waits for element attribute value to match specified text, until certain deadline """
        element = self.driver.find_element(*locator)
        counter = 0
        while element.get_attribute(attribute) != attribute_text:
            if counter < max_count:
                time.sleep(delay)
                counter += 1
            else:
                Assert.fail(
                    '"'
                    + attribute_text
                    + '" text did not match "'
                    + element.get_attribute(attribute)
                    + '" after '
                    + str(counter * delay)
                    + " seconds"
                )
                break

    def wait_for_element_ready(self, locator, timeout=None):
        """ Waits until certain element is present and clickable """
        timeout = timeout or self.timeout
        WebDriverWait(self.driver, timeout).until(
            EC.presence_of_element_located(locator), "Element specified by {0} was not present!".format(locator)
        )
        WebDriverWait(self.driver, timeout).until(
            EC.element_to_be_clickable(locator), "Element specified by {0} did not become clickable!".format(locator)
        )

    def find_element(self, locator):
        """ Return the element at the specified locator."""
        return self.driver.find_element(*locator)

    def find_elements(self, locator):
        """ Return a list of elements at the specified locator."""
        return self.driver.find_elements(*locator)

    def find_elements_with_text(self, text, locator):
        """ Find elements that have specified text """
        elements = self.driver.find_elements(*locator)
        selected = [item for item in elements if item.text == text]
        return selected[0] if len(selected) == 1 else selected

    def link_destination(self, locator):
        """ Return the href attribute of the element at the specified locator."""
        link = self.driver.find_element(*locator)
        return link.get_attribute("href")

    def image_source(self, locator):
        """ Return the src attribute of the element at the specified locator."""
        link = self.driver.find_element(*locator)
        return link.get_attribute("src")

    def select_dropdown_value(self, select, value):
        """ Set 'select' dropdown value """
        select = Select(select)
        option = [option for option in select.options if option.text == value][0]
        option.click()

    def upload_file(self, file_path, input_field_locator, delay=5):
        """ uploads file through the file input field
            @file_path: path to file (including the file name) relative to test project root
            @input_field_locator: locator of input element with type="file"
            @delay: seconds to wait for file to upload
        """
        file_path = os.path.join(self.shishito_support.project_root, file_path)
        self.driver.find_element(*input_field_locator).send_keys(file_path)
        time.sleep(delay)

    def execute_js_script(self, script, arguments=None):
        """execute any js command with arguments or without it"""
        script_value = self.driver.execute_script(script, arguments)
        return script_value

    def open_new_tab(self, url):
        """Open new tab using keyboard, for now work only in Firefox and IE, in Chrome use js script to open tab """
        ActionChains(self.driver).send_keys(Keys.CONTROL, "t").perform()
        windows = self.driver.window_handles
        self.driver.switch_to_window(windows[-1])
        self.driver.get(url)

    def switch_new_tab(self):
        """switch to new tab/window"""
        windows = self.driver.window_handles
        self.driver.switch_to_window(windows[-1])

    def switch_first_tab(self):
        """Close current tab, switch to first tab/window"""
        windows = self.driver.window_handles
        self.driver.close()
        self.driver.switch_to_window(windows[0])
Esempio n. 10
0
class ShishitoRunner(object):
    """ Base shishito test runner.

    - runs python selenium tests on customizable configurations (using PyTest)
    - archive the test results in .zip file """
    def __init__(self, project_root):
        # set project root
        self.project_root = project_root

        # test timestamp - for storing results
        self.test_timestamp = time.strftime("%Y-%m-%d_%H-%M-%S")
        self.epoch = int(time.time())

        # parse cmd  args
        self.cmd_args = self.handle_cmd_args()

        # Get SUT build for use in reporting
        self.test_build = self.cmd_args['build']

        self.reporter = Reporter(project_root, self.test_timestamp)
        self.shishito_support = ShishitoSupport(cmd_args=self.cmd_args,
                                                project_root=self.project_root)

    def handle_cmd_args(self):
        """ Retrieve command line arguments passed to the script.

        :return: dict with parsed command line arguments
        """

        parser = argparse.ArgumentParser(
            description='Selenium Python test runner execution arguments.')

        parser.add_argument('--platform',
                            help='Platform on which run tests.',
                            dest='test_platform')
        parser.add_argument('--environment',
                            help='Environment for which run tests.',
                            dest='test_environment')
        parser.add_argument('--test_directory',
                            help='Directory where to lookup for tests')
        parser.add_argument('--smoke',
                            help='Run only smoke tests',
                            action='store_true')
        parser.add_argument(
            '--browserstack',
            help='BrowserStack credentials; format: "username:token"')
        parser.add_argument(
            '--saucelabs',
            help='Saucelabs credentials; format: "username:token"')
        parser.add_argument(
            '--test_rail',
            help=
            'TestRail Test Management tool credentials; format: "username:password"'
        )
        parser.add_argument(
            '--qastats',
            help='QAStats Test Management tool credentials; format: "token"')
        parser.add_argument(
            '--node_webkit_chromedriver_path',
            help=
            'Path to chromedriver located in same directory as node-webkit application'
        )
        parser.add_argument('--app', help='Path to appium application')
        parser.add_argument(
            '--test', help='Run specified test (PyTest string expression)')
        parser.add_argument('--build',
                            help='Specify build number for reporting purposes')
        args = parser.parse_args()

        # return args dict --> for use in other classes
        return vars(args)

    def run_tests(self):
        """ Execute tests for given platform and environment. Platform and Environment can be passed as command lines
        argument or settings in config file.
        """

        if __name__ == "__main__":
            sys.exit(
                'The runner cannot be executed directly.'
                ' You need to import it within project specific runner. Session terminated.'
            )

        # cleanup previous results
        self.reporter.cleanup_results()

        # import execution class
        executor_class = self.shishito_support.get_module('platform_execution')
        # executor_class = getattr(import_module(platform_path), 'ControlExecution')
        executor = executor_class(self.shishito_support, self.test_timestamp)

        # run test
        exit_code = executor.run_tests()

        # archive results + generate combined report
        self.reporter.archive_results()
        self.reporter.generate_combined_report()

        # upload results to QAStats test management app
        qastats_credentials = self.shishito_support.get_opt('qastats')
        if qastats_credentials:
            try:
                qas_user, qas_password = qastats_credentials.split(':', 1)
            except (AttributeError, ValueError):
                raise ValueError(
                    'QAStats credentials were not specified! Unable to connect to QAStats.'
                )

            qastats = QAStats(qas_user, qas_password, self.test_timestamp,
                              self.epoch, self.test_build)
            qastats.post_results()

        # upload results to TestRail test management app
        test_rail_credentials = self.shishito_support.get_opt('test_rail')
        if test_rail_credentials:
            try:
                tr_user, tr_password = test_rail_credentials.split(':', 1)
            except (AttributeError, ValueError):
                raise ValueError(
                    'TestRail credentials were not specified! Unable to connect to TestRail.'
                )

            test_rail = TestRail(tr_user, tr_password, self.test_timestamp,
                                 self.test_build)
            test_rail.post_results()

        return exit_code
Esempio n. 11
0
class TestRail(object):
    """ TestRail object """
    def __init__(self, user, password, timestamp, build):
        self.shishito_support = ShishitoSupport()
        self.test_rail_instance = self.shishito_support.get_opt(
            'test_rail_url')
        self.user = user
        self.password = password
        self.timestamp = timestamp

        # project specific config
        self.project_id = self.shishito_support.get_opt('test_rail_project_id')
        self.section_id = self.shishito_support.get_opt('test_rail_section_id')
        self.test_plan_id = self.shishito_support.get_opt(
            'test_rail_test_plan_id')
        self.test_plan_name = self.shishito_support.get_opt(
            'test_rail_test_plan_name') or build
        self.suite_id = self.shishito_support.get_opt('test_rail_suite_id')

        # shishito results
        self.reporter = Reporter()
        self.shishito_results = self.reporter.get_xunit_test_cases(timestamp)

        self.default_headers = {'Content-Type': 'application/json'}
        self.uri_base = self.test_rail_instance + '/index.php?/api/v2/'

    def post_results(self):
        """ Create test-cases on TestRail, adds a new test run and update results for the run """
        self.create_missing_test_cases()

        if self.test_plan_name:
            test_plan_id = self.add_test_plan()
        else:
            test_plan_id = self.test_plan_id

        test_run = self.add_test_run(test_plan_id)
        self.add_test_results(test_run)

    def tr_get(self, url):
        """ GET request for TestRail API

        :param url: url endpoint snippet
        :return: response JSON
        """
        response = requests.get(self.uri_base + url,
                                auth=(self.user, self.password),
                                headers=self.default_headers)
        #print(self.uri_base + url, response, response.text)
        return response.json()

    def tr_post(self, url, payload):
        """ GET request for TestRail API

        :param url: url endpoint snippet
        :param payload: payload for the POST api call
        :return: response object
        """
        return requests.post(self.uri_base + url,
                             auth=(self.user, self.password),
                             data=json.dumps(payload),
                             headers=self.default_headers)

    def get_all_test_plans(self):
        """ Gets list of all test-plans from certain project

        :return: list of test-plans (names = strings)
        """
        test_plans_list = self.tr_get('get_plans/{}'.format(self.project_id))
        return [{
            'name': test_plan['name'],
            'id': test_plan['id']
        } for test_plan in test_plans_list]

    def get_all_test_cases(self):
        """ Gets list of all test-cases from certain project

        :return: list of test-cases (names = strings)
        """
        test_case_list = self.tr_get(
            'get_cases/{}&section_id={}&suite_id={}'.format(
                self.project_id, self.section_id, self.suite_id))
        return [{
            'title': test_case['title'],
            'id': test_case['id']
        } for test_case in test_case_list]

    def create_test_case(self, title):
        """ Creates a new test case in TestRail

        :param title: Title of the test-case
        :return: response object
        """
        return self.tr_post('add_case/{}'.format(self.section_id),
                            {"title": title})

    def create_missing_test_cases(self):
        """ Creates new test-cases on TestRail for those in test project (those run by pytest).
         Does not create test-cases if already existed on TestRail.

        :return: list of test-cases that could not be created on TestRail (post failure)
        """
        post_errors = []
        test_case_names = [item['title'] for item in self.get_all_test_cases()]
        # Iterate over results for each environment combination
        for result_combination in self.shishito_results:
            # Create TestRail entry for every test-case in combination (if missing)
            for item in result_combination['cases']:
                if item['name'] not in test_case_names:
                    response = self.create_test_case(item['name'])
                    if response.status_code != requests.codes.ok:
                        post_errors.append(item['name'])
                    else:
                        test_case_names.append(item['name'])
        return post_errors

    def add_test_plan(self):
        test_plan_id = 0

        # Check if already exists
        for plan in self.get_all_test_plans():
            if plan['name'] == self.test_plan_name:
                return plan['id']

        result = self.tr_post('add_plan/{}'.format(self.project_id),
                              {"name": self.test_plan_name})

        return json.loads(result.text)['id']

    def add_test_run(self, test_plan_id=None):
        """ Adds new test run under certain test plan into TestRail

        :return: dictionary of TestRail run names & IDs
        """
        test_plan_id = test_plan_id or self.test_plan_id
        runs_created = []
        # Iterate over results for each environment combination
        for result_combination in self.shishito_results:
            run_name = '{} ({})'.format(result_combination['name'][:-4],
                                        self.timestamp)
            test_run = {
                "case_ids": [case['id'] for case in self.get_all_test_cases()]
            }
            result = self.tr_post('add_plan_entry/{}'.format(test_plan_id), {
                "suite_id": self.suite_id,
                "name": run_name,
                "runs": [test_run]
            }).json()
            # lookup test run id
            for run in result['runs']:
                if run['name'] == run_name:
                    runs_created.append({
                        'combination':
                        result_combination['name'],
                        'id':
                        run['id']
                    })
        return runs_created

    def add_test_results(self, test_runs):
        """ Add test results for specific test run based on parsed xUnit results

        :return: list of test run IDs for which results could not be added (post failure)
        """
        post_errors = []
        run_ids = {r['combination']: r['id'] for r in test_runs}
        # Iterate over results for each environment combination
        for result in self.shishito_results:
            run_id = run_ids.get(result['name'])
            if not run_id:
                continue

            test_results = []
            tr_tests = {
                t['title']: t['id']
                for t in self.tr_get('get_tests/{}'.format(run_id))
            }
            # Create TestRail entry for every test-case in combination (if missing)
            for xunit_test in result['cases']:
                tr_test_id = tr_tests.get(xunit_test['name'])
                result_id = {
                    'success': 1,
                    'failure': 5
                }.get(xunit_test['result'])
                if tr_test_id and result_id:
                    # Add result content into the payload list
                    result = {'test_id': tr_test_id, 'status_id': result_id}
                    if result_id == 5:
                        result['comment'] = xunit_test['failure_message']
                    test_results.append(result)

            response = self.tr_post('add_results/{}'.format(run_id),
                                    {'results': test_results})
            if response.status_code != requests.codes.ok:
                post_errors.append(run_id)
        return post_errors
Esempio n. 12
0
class QAStats(object):
    """ QAStats object """
    def __init__(self, user, password, timestamp, epoch, build):
        self.shishito_support = ShishitoSupport()
        self.qastats_base_url = self.shishito_support.get_opt('qastats_url')
        self.user = user
        self.password = password
        self.timestamp = timestamp
        self.epoch = epoch
        self.build = build

        # project specific config
        self.project_id = self.shishito_support.get_opt('qastats_project_id')

        # shishito results
        self.reporter = Reporter()
        self.shishito_results = self.reporter.get_xunit_test_cases(timestamp)

        self.default_headers = {'Content-Type': 'application/json'}
        self.result_url = self.qastats_base_url + '/api/v1/results'
        self.project_url = self.shishito_support.get_opt('base_url')

    def post_results(self):
        """ Create test-cases on QAStats, adds a new test run and update results for the run
            {
               "project_id": 123,
               "timestamp": 1470133472,
               "build": "773",              // optional
               "environment": "Firefox"     // optional
               "branch": "develop",         // optional
               "git": "ae232a",             // optional
               "results": [
                  { "test": "test_login", "result": "pass" },   // [pass fail err nr]
                  ...
               ],
            }
        """

        for (i, run) in enumerate(self.shishito_results):
            environment = run['name']
            m = re.match('^(.*)\.xml$', environment)
            if m != None: environment = m.group(1)

            payload = {
                'project_id': self.project_id,
                'timestamp': self.epoch + i,
                'environment': environment
            }
            if self.build:
                payload['build'] = self.build
            if 'QA_BRANCH_TO_TEST' in os.environ:
                payload['branch'] = os.environ['QA_BRANCH_TO_TEST']
            if 'QA_GIT_COMMIT' in os.environ:
                payload['git'] = os.environ["QA_GIT_COMMIT"]
            elif 'CIRCLE_REPOSITORY_URL' in os.environ:
                print('github url is known')
                payload['git'] = os.environ['CIRCLE_REPOSITORY_URL']
            if 'CIRCLE_TEST_REPORTS' in os.environ:
                print('result url is known')
                payload['reporturl'] = os.environ.get('CIRCLE_TEST_REPORTS')
            if self.project_url is not None:
                payload['testurl'] = self.project_url
            status_map = {
                'error': 'err',
                'failure': 'fail',
                'success': 'pass',
                'skipped': 'nr'
            }
            results = [{
                'test': t['name'],
                'result': status_map[t['result']]
            } for t in run['cases']]
            payload['results'] = results
            print(payload)
            json_payload = json.dumps(payload)
            r = requests.post(self.result_url,
                              auth=(self.user, self.password),
                              data=json_payload,
                              headers=self.default_headers)

            if r.status_code == requests.codes.ok:
                try:
                    resp = r.json()
                    if 'result' in resp and resp['result'] == 'OK':
                        print("Results uploaded to QAStats")
                        return True
                except (ValueError, AttributeError):
                    pass

            print("Error: uploading tests to QAStats\n\n", json_payload, "\n")
            print("\tStatus-code:\t" + str(r.status_code) + "\n")
            for n, v in r.headers.items():
                print("\t" + n + "\t" + v)
            print("")
            print(r.text)
Esempio n. 13
0
class QAStats(object):
    """ QAStats object """

    def __init__(self, user, password, timestamp, epoch, build):
        self.shishito_support = ShishitoSupport()
        self.qastats_base_url = self.shishito_support.get_opt('qastats_url')
        self.user = user
        self.password = password
        self.timestamp = timestamp
        self.epoch = epoch
        self.build = build

        # project specific config
        self.project_id = self.shishito_support.get_opt('qastats_project_id')

        # shishito results
        self.reporter = Reporter()
        self.shishito_results = self.reporter.get_xunit_test_cases(timestamp)

        self.default_headers = {'Content-Type': 'application/json'}
        self.result_url = self.qastats_base_url + '/api/v1/results'
        self.project_url = self.shishito_support.get_opt('base_url')

    def post_results(self):
        """ Create test-cases on QAStats, adds a new test run and update results for the run
            {
               "project_id": 123,
               "timestamp": 1470133472,
               "build": "773",              // optional
               "environment": "Firefox"     // optional
               "branch": "develop",         // optional
               "git": "ae232a",             // optional
               "results": [
                  { "test": "test_login", "result": "pass" },   // [pass fail err nr]
                  ...
               ],
            }
        """

        for (i, run) in enumerate(self.shishito_results):
            environment = run['name'];
            m =re.match('^(.*)\.xml$', environment)
            if m != None: environment = m.group(1)

            payload = {
                    'project_id': self.project_id,
                    'timestamp': self.epoch + i,
                    'environment': environment
            }
            if self.build:
                payload['build'] = self.build
            if 'QA_BRANCH_TO_TEST' in os.environ:
                payload['branch'] = os.environ['QA_BRANCH_TO_TEST']
            if 'QA_GIT_COMMIT' in os.environ:
                payload['git'] = os.environ["QA_GIT_COMMIT"]
            elif 'CIRCLE_REPOSITORY_URL' in os.environ:
                print('github url is known')
                payload['git'] = os.environ['CIRCLE_REPOSITORY_URL']
            if 'CIRCLE_TEST_REPORTS' in os.environ:
                print('result url is known')
                payload['reporturl'] = os.environ.get('CIRCLE_TEST_REPORTS')
            if self.project_url is not None:
                payload['testurl']=self.project_url
            status_map = {
                'error': 'err',
                'failure': 'fail',
                'success': 'pass',
                'skipped': 'nr'
            }
            results = [ {'test': t['name'], 'result': status_map[t['result']]} for t in run['cases'] ]
            payload['results'] = results
            print(payload)
            json_payload = json.dumps(payload)
            r = requests.post(self.result_url, auth=(self.user, self.password), data=json_payload,
                                 headers=self.default_headers)

            if r.status_code == requests.codes.ok:
                try:
                    resp = r.json()
                    if 'result' in resp and resp['result'] == 'OK':
                        print("Results uploaded to QAStats")
                        return True
                except (ValueError, AttributeError):
                    pass

            print("Error: uploading tests to QAStats\n\n", json_payload, "\n")
            print("\tStatus-code:\t" + str(r.status_code) + "\n")
            for n, v in r.headers.items():
                print("\t" + n + "\t" + v)
            print("")
            print(r.text)
Esempio n. 14
0
class SeleniumTest(object):
    def __init__(self, driver):
        self.driver = driver
        self.shishito_support = ShishitoSupport()
        self.base_url = self.shishito_support.get_opt('base_url')
        self.default_implicit_wait = int(
            self.shishito_support.get_opt('default_implicit_wait'))
        self.timeout = int(self.shishito_support.get_opt('timeout'))

    def save_screenshot(self, name=None, project_root=None):
        """ Saves application screenshot """
        if not name:
            # Use the name of browser and caller function (e.g. 'chrome_test_google_search'
            name = self.driver.name + "_" + inspect.stack()[1][3]
        if not project_root:
            project_root = self.shishito_support.project_root
        screenshot_folder = os.path.join(project_root, 'screenshots')
        if not os.path.exists(screenshot_folder):
            os.makedirs(screenshot_folder)

        existing_images = glob.glob(
            os.path.join(screenshot_folder, name + '_*.png'))
        actual_pic_nr = len(existing_images) + 1
        self.driver.save_screenshot(
            os.path.join(screenshot_folder,
                         '{}_{}.png'.format(name, actual_pic_nr)))

    def save_file_from_url(self, file_path, url):
        """ Saves file from url """
        if os.path.isfile(file_path):
            print('File %s already exists.' % file_path)
            return

        response = requests.get(url, stream=True)
        response.raise_for_status()

        with open(file_path, 'wb') as save_file:
            for block in response.iter_content(1024):
                if not block:
                    break
                save_file.write(block)

    # Deprecated use property directly
    def get_base_url(self):
        return self.base_url

    # Deprecated use property directly
    def get_current_url(self):
        return self.current_url

    @property
    def current_url(self):
        """ Return the url for the current page."""
        return self.driver.current_url

    def hover_on(self, element):
        """ Mouse over specific element """
        mouse_over = ActionChains(self.driver).move_to_element(element)
        mouse_over.perform()

    def go_to_page(self, url):
        """ Opens url in currently active window """
        self.driver.get(url)
        self.driver.implicitly_wait(self.default_implicit_wait)

    def click_and_wait(self, element, locator=None):
        """ clicks on a element and then waits for specific element to be present or simply waits implicitly """
        element.click()
        if locator:
            self.wait_for_element_ready(locator)
        else:
            self.driver.implicitly_wait(10)

    def check_images_are_loaded(self):
        """ checks all images on the pages and verifies if they are properly loaded """
        script = 'return arguments[0].complete && typeof arguments[0].naturalWidth' \
                 ' != "undefined" && arguments[0].naturalWidth > 0'
        images_not_loaded = []
        for image in self.driver.find_elements_by_tag_name('img'):
            loaded = self.driver.execute_script(script, image)
            if not loaded and image.get_attribute('src'):
                images_not_loaded.append(
                    '%s: %s' % (self.driver.title, image.get_attribute('src')))
        return images_not_loaded

    def is_element_present(self, locator):
        """
        True if the element at the specified locator is present in the DOM.
        Note: It returns false immediately if the element is not found.
        """
        self.driver.implicitly_wait(0)
        try:
            self.driver.find_element(*locator)
            return True
        except NoSuchElementException:
            return False
        finally:
            # set the implicit wait back
            self.driver.implicitly_wait(self.default_implicit_wait)

    def is_element_visible(self, locator):
        """
        True if the element at the specified locator is visible in the browser.
        Note: It uses an implicit wait if element is not immediately found.
        """
        try:
            return self.driver.find_element(*locator).is_displayed()
        except (NoSuchElementException, ElementNotVisibleException):
            return False

    def is_element_not_visible(self, locator):
        """
        True if the element at the specified locator is not visible.
        Note: It returns true immediately if the element is not found.
        """
        self.driver.implicitly_wait(0)
        try:
            return not self.driver.find_element(*locator).is_displayed()
        except (NoSuchElementException, ElementNotVisibleException):
            return True
        finally:
            # set the implicit wait back
            self.driver.implicitly_wait(self.default_implicit_wait)

    def wait_for_element_present(self, locator, timeout=None):
        """ Wait for the element at the specified locator
        to be present in the DOM. """
        timeout = timeout or self.timeout
        count = 0
        while not self.is_element_present(locator):
            time.sleep(1)
            count += 1
            if count == timeout:
                raise Exception('{0} has not loaded'.format(locator))

    def wait_for_element_visible(self, locator, timeout=None):
        """
        Wait for the element at the specified locator to be visible.
        """
        timeout = timeout or self.timeout
        count = 0
        while not self.is_element_visible(locator):
            time.sleep(1)
            count += 1
            if count == timeout:
                raise Exception("{0} is not visible".format(locator))

    def wait_for_element_not_visible(self, locator, timeout=None):
        """
        Wait for the element at the specified locator not to be visible anymore.
        """
        timeout = timeout or self.timeout
        count = 0
        while self.is_element_visible(locator):
            time.sleep(1)
            count += 1
            if count == timeout:
                raise Exception("{0} is still visible".format(locator))

    def wait_for_element_not_present(self, locator, timeout=None):
        """ Wait for the element at the specified locator
         not to be present in the DOM. """
        timeout = timeout or self.timeout
        self.driver.implicitly_wait(0)
        try:
            WebDriverWait(
                self.driver,
                timeout).until(lambda s: len(self.find_elements(*locator)) < 1)
            return True
        except TimeoutException:
            Assert.fail(TimeoutException)
        finally:
            self.driver.implicitly_wait(self.default_implicit_wait)

    def wait_for_text_to_match(self, text, locator, max_count=20, delay=0.25):
        """ Waits for element text to match specified text, until certain deadline """
        element = self.driver.find_element(*locator)
        counter = 0
        while element.text != text:
            if counter < max_count:
                time.sleep(delay)
                counter += 1
                element = self.driver.find_element(*locator)
            else:
                Assert.fail('"' + text + '" text did not match "' +
                            element.text + '" after ' + str(counter * delay) +
                            ' seconds')
                break

    def wait_for_attribute_value(self,
                                 attribute,
                                 attribute_text,
                                 locator,
                                 max_count=20,
                                 delay=0.25):
        """ Waits for element attribute value to match specified text, until certain deadline """
        element = self.driver.find_element(*locator)
        counter = 0
        while element.get_attribute(attribute) != attribute_text:
            if counter < max_count:
                time.sleep(delay)
                counter += 1
            else:
                Assert.fail('"' + attribute_text + '" text did not match "' +
                            element.get_attribute(attribute) + '" after ' +
                            str(counter * delay) + ' seconds')
                break

    def wait_for_element_ready(self, locator, timeout=None):
        """ Waits until certain element is present and clickable """
        timeout = timeout or self.timeout
        WebDriverWait(self.driver, timeout).until(
            EC.presence_of_element_located(locator),
            'Element specified by {0} was not present!'.format(locator))
        WebDriverWait(self.driver, timeout).until(
            EC.element_to_be_clickable(locator),
            'Element specified by {0} did not become clickable!'.format(
                locator))

    def find_element(self, locator):
        """ Return the element at the specified locator."""
        return self.driver.find_element(*locator)

    def find_elements(self, locator):
        """ Return a list of elements at the specified locator."""
        return self.driver.find_elements(*locator)

    def find_elements_with_text(self, text, locator):
        """ Find elements that have specified text """
        elements = self.driver.find_elements(*locator)
        selected = [item for item in elements if item.text == text]
        return selected[0] if len(selected) == 1 else selected

    def link_destination(self, locator):
        """ Return the href attribute of the element at the specified locator."""
        link = self.driver.find_element(*locator)
        return link.get_attribute('href')

    def image_source(self, locator):
        """ Return the src attribute of the element at the specified locator."""
        link = self.driver.find_element(*locator)
        return link.get_attribute('src')

    def select_dropdown_value(self, select, value):
        """ Set 'select' dropdown value """
        select = Select(select)
        option = [option for option in select.options
                  if option.text == value][0]
        option.click()

    def upload_file(self, file_path, input_field_locator, delay=5):
        """ uploads file through the file input field
            @file_path: path to file (including the file name) relative to test project root
            @input_field_locator: locator of input element with type="file"
            @delay: seconds to wait for file to upload
        """
        file_path = os.path.join(self.shishito_support.project_root, file_path)
        self.driver.find_element(*input_field_locator).send_keys(file_path)
        time.sleep(delay)

    def execute_js_script(self, script, arguments=None):
        """execute any js command with arguments or without it"""
        script_value = self.driver.execute_script(script, arguments)
        return script_value

    def open_new_tab(self, url):
        """Open new tab using keyboard, for now work only in Firefox and IE, in Chrome use js script to open tab """
        ActionChains(self.driver).send_keys(Keys.CONTROL, "t").perform()
        windows = self.driver.window_handles
        self.driver.switch_to_window(windows[-1])
        self.driver.get(url)

    def switch_new_tab(self):
        """switch to new tab/window"""
        windows = self.driver.window_handles
        self.driver.switch_to_window(windows[-1])

    def switch_first_tab(self):
        """Close current tab, switch to first tab/window"""
        windows = self.driver.window_handles
        self.driver.close()
        self.driver.switch_to_window(windows[0])
Esempio n. 15
0
class ShishitoControlTest(object):
    """ Base class for ControlTest objects. """
    def __init__(self):
        self.shishito_support = ShishitoSupport()

        # create control environment object
        control_env_obj = self.shishito_support.get_module('test_environment')
        self.test_environment = control_env_obj(self.shishito_support)

        self.drivers = []

    def start_browser(self, base_url=None):
        """ Webdriver startup function.

        :return: initialized webdriver
        """

        config_section = self.shishito_support.get_opt(
            'environment_configuration')

        # call browser from proper environment
        driver = self.test_environment.call_browser(config_section)
        self.drivers.append(driver)

        # load init url
        if not base_url:
            base_url = self.shishito_support.get_opt('base_url')

        if base_url:
            self.test_init(driver, base_url)

        return driver

    def start_test(self, reload_page=None):
        """ To be executed before every test-case (test function).

        :param reload_page:
        """

    def stop_browser(self):
        """ Webdriver termination function. """

        for driver in self.drivers:
            driver.quit()

    def stop_test(self, test_info, debug_events=None):
        """ To be executed after every test-case (test function). If test failed, function saves
        screenshots created during test.

        :param test_info: information about test
        """

        if test_info.test_status not in ('passed', None):
            # save screenshot in case test fails
            test_name = re.sub('[^A-Za-z0-9_.]+', '_', test_info.test_name)

            # Capture screenshot and debug info from driver(s)
            for driver in self.drivers:
                browser_name = driver.name
                file_name = browser_name + '_' + test_name

                ts = SeleniumTest(driver)
                ts.save_screenshot(file_name)

                #Save debug info to file
                if debug_events is not None:
                    debugevent_folder = os.path.join(
                        self.shishito_support.project_root, 'debug_events')

                    if not os.path.exists(debugevent_folder):
                        os.makedirs(debugevent_folder)

                    with open(
                            os.path.join(debugevent_folder,
                                         file_name + '.json'), 'w') as logfile:
                        json.dump(debug_events, logfile)

    def test_init(self, driver, url):
        """ Executed only once after browser starts.