dallinger.bots — Dallinger 3.4.0 documentation

# Source code for dallinger.bots

    """Bots."""

    import logging
    from cached_property import cached_property
    from urlparse import urlparse, parse_qs

    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.common.exceptions import TimeoutException
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC

    logger = logging.getLogger(__file__)

    class BotBase(object):
        """A base class for Bots that works with the built-in demos."""

        def __init__(self, URL, assignment_id='', worker_id=''):
            logger.info("Creating bot with URL: %s." % URL)
            self.URL = URL

            parts = urlparse(URL)
            query = parse_qs(parts.query)
            if not assignment_id:
                assignment_id = query.get('assignmentId', [''])[0]
            self.assignment_id = assignment_id
            if not worker_id:
                worker_id = query.get('workerId', [''])[0]
            self.worker_id = worker_id
            self.unique_id = worker_id + ':' + assignment_id

        @cached_property
        def driver(self):
            """Returns a Selenium WebDriver instance of the type requested in the
            configuration."""
            from dallinger.config import get_config
            config = get_config()
            if not config.ready:
                config.load()
            driver_url = config.get('webdriver_url', None)
            driver_type = config.get('webdriver_type', 'phantomjs').lower()

            if driver_url:
                capabilities = {}
                if driver_type == 'firefox':
                    capabilities = webdriver.DesiredCapabilities.FIREFOX
                elif driver_type == 'chrome':
                    capabilities = webdriver.DesiredCapabilities.CHROME
                elif driver_type == 'phantomjs':
                    capabilities = webdriver.DesiredCapabilities.PHANTOMJS
                else:
                    raise ValueError(
                        'Unsupported remote webdriver_type: {}'.format(driver_type))
                driver = webdriver.Remote(
                    desired_capabilities=capabilities,
                    command_executor=driver_url
                )
            elif driver_type == 'phantomjs':
                driver = webdriver.PhantomJS()
            elif driver_type == 'firefox':
                driver = webdriver.Firefox()
            elif driver_type == 'chrome':
                driver = webdriver.Chrome()
            else:
                raise ValueError(
                    'Unsupported webdriver_type: {}'.format(driver_type))
            driver.set_window_size(1024, 768)
            logger.info("Created {} webdriver.".format(driver_type))
            return driver

        def sign_up(self):
            """Accept HIT, give consent and start experiment."""
            try:
                self.driver.get(self.URL)
                logger.info("Loaded ad page.")
                begin = WebDriverWait(self.driver, 10).until(
                    EC.element_to_be_clickable((By.CLASS_NAME, 'btn-primary')))
                begin.click()
                logger.info("Clicked begin experiment button.")
                WebDriverWait(self.driver, 10).until(
                    lambda d: len(d.window_handles) == 2)
                self.driver.switch_to_window(self.driver.window_handles[-1])
                self.driver.set_window_size(1024, 768)
                logger.info("Switched to experiment popup.")
                consent = WebDriverWait(self.driver, 10).until(
                    EC.element_to_be_clickable((By.ID, 'consent')))
                consent.click()
                logger.info("Clicked consent button.")
                participate = WebDriverWait(self.driver, 10).until(
                    EC.element_to_be_clickable((By.CLASS_NAME, 'btn-success')))
                participate.click()
                logger.info("Clicked start button.")
                return True
            except TimeoutException:
                logger.error("Error during experiment sign up.")
                return False

        def participate(self):
            """Participate in the experiment."""
            logger.error("Bot class does not define participate method.")
            raise NotImplementedError

        def complete_questionnaire(self):
            """Complete the standard debriefing form."""
            pass

        def sign_off(self):
            """Submit questionnaire and finish."""
            try:
                feedback = WebDriverWait(self.driver, 10).until(
                    EC.element_to_be_clickable((By.ID, 'submit-questionnaire')))
                self.complete_questionnaire()
                feedback.click()
                logger.info("Clicked submit questionnaire button.")
                self.driver.switch_to_window(self.driver.window_handles[0])
                self.driver.set_window_size(1024, 768)
                logger.info("Switched back to initial window.")
                return True
            except TimeoutException:
                logger.error("Error during experiment sign off.")
                return False

        def complete_experiment(self, status):
            url = self.driver.current_url
            p = urlparse(url)
            complete_url = '%s://%s/%s?uniqueId=%s'
            complete_url = complete_url % (p.scheme,
                                           p.netloc,
                                           status,
                                           self.unique_id)
            self.driver.get(complete_url)
            logger.info("Forced call to %s: %s" % (status, complete_url))

        def run_experiment(self):
            """Sign up, run the ``participate`` method, then sign off and close
            the driver."""
            try:
                self.sign_up()
                self.participate()
                if self.sign_off():
                    self.complete_experiment('worker_complete')
                else:
                    self.complete_experiment('worker_failed')
            finally:
                self.driver.quit()