Exemple #1
0
class ComponentPage(DialogPage):
    """ Component editor page. """

    dataflow_tab = ButtonElement((By.XPATH, "dl/dt[text()='Dataflow']"))
    inputs_tab = ButtonElement((By.XPATH, "dl/dt[text()='Inputs']"))
    slots_tab = ButtonElement((By.XPATH, "dl/dt[text()='Slots']"))
    outputs_tab = ButtonElement((By.XPATH, "dl/dt[text()='Outputs']"))

    inputs = GridElement((By.ID, 'Inputs_props'))
    outputs = GridElement((By.ID, 'Outputs_props'))

    def get_inputs(self):
        """ Return inputs grid. """
        self('inputs_tab').click()
        return self.inputs

    def set_input(self, name, value):
        """ Set input `name` to `value`. """
        self('inputs_tab').click()
        grid = self.inputs
        found = []
        for row in grid.rows:
            if row[0] == name:
                row[2] = value
                return
            found.append(row[0])
        raise RuntimeError('%r not found in inputs %s' % (name, found))

    def get_outputs(self):
        """ Return outputs grid. """
        self('outputs_tab').click()
        return self.outputs
class DriverPage(ComponentPage): 
    """ Driver editor page. """ 
 
    parameters_tab  = ButtonElement((By.XPATH, "div/ul/li/a[text()='Parameters']")) 
    workflow_tab    = ButtonElement((By.XPATH, "div/ul/li/a[text()='Workflow']")) 
    objectives_tab  = ButtonElement((By.XPATH, "div/ul/li/a[text()='Objectives']")) 
    constraints_tab = ButtonElement((By.XPATH, "div/ul/li/a[text()='Constraints']")) 
 
    parameters  = GridElement((By.ID, 'Parameters_parms')) 
    objectives  = GridElement((By.ID, 'Objectives_objectives')) 
    constraints = GridElement((By.ID, 'Constraints_constraints')) 
 
    add_parameter  = ButtonElement((By.XPATH, "//div[text()='Add Parameter']"))
    add_objective  = ButtonElement((By.XPATH, "//div[text()='Add Objective']"))
    add_constraint = ButtonElement((By.XPATH, "//div[text()='Add Constraint']"))

    def get_parameters(self): 
        """ Return parameters grid. """ 
        self('parameters_tab').click() 
        return self.parameters 
 
    def get_objectives(self): 
        """ Return objectives grid. """ 
        self('objectives_tab').click() 
        return self.objectives 
 
    def get_constraints(self): 
        """ Return constraints grid. """ 
        self('constraints_tab').click() 
        return self.constraints
 
    def new_parameter(self):
        """ Return :class:`ParameterDialog`. """
        self('add_parameter').click()
        return ParameterDialog(self.browser, self.port,
                               (By.XPATH, "//div[@id='parameter-dialog']/.."))

    def new_objective(self):
        """ Return :class:`ObjectiveDialog`. """
        self('add_objective').click()
        return ObjectiveDialog(self.browser, self.port,
                               (By.XPATH, "//div[@id='objective-dialog']/.."))

    def new_constraint(self):
        """ Return :class:`ConstraintDialog`. """
        self('add_constraint').click()
        return ConstraintDialog(self.browser, self.port,
                                (By.XPATH, "//div[@id='constraint-dialog']/.."))

    def show_workflow(self): 
        """switch to workflow tab""" 
        self('workflow_tab').click() 
 
    def get_workflow_component_figures(self):
        """ Return workflow component figure elements. """
        return find_workflow_component_figures(self)
Exemple #3
0
 def __init__(self, colour=BLACK, grid_camera=None, tile_positions=tuple()):
     GridElement.__init__(self, element=ElementCollection(),
                          grid_position=TetrominoPosition(),
                          grid_camera=grid_camera)
     self.old_tile_positions = None
     self.tile_positions = tile_positions
     self.mapping = {}
     self.colour = colour
     self.reset_position_mapping()
     return
Exemple #4
0
class ImplicitComponentPage(ComponentPage):
    """ Implicit Component editor page. """

    states_tab = ButtonElement((By.XPATH, "div/ul/li/a[text()='States']"))
    residuals_tab = ButtonElement(
        (By.XPATH, "div/ul/li/a[text()='Residuals']"))

    states = GridElement((By.ID, 'States_props'))
    residuals = GridElement((By.ID, 'Residuals_props'))

    states_filter = InputElement((By.ID, 'States_variableFilter'))
    states_clear = ButtonElement((By.ID, 'States_clear'))

    residuals_filter = InputElement((By.ID, 'Residuals_variableFilter'))
    residuals_clear = ButtonElement((By.ID, 'Residuals_clear'))

    def set_state(self, name, value):
        """ Set state `name` to `value`. """
        self('states_tab').click()
        grid = self.states
        found = []
        for row in grid.rows:
            if row[1] == name:
                row[2] = value
                return
            found.append(row[1])
        raise RuntimeError('%r not found in states %s' % (name, found))

    def get_states(self, return_type=None):
        """ Return states grid. """
        self('states_tab').click()
        return self._get_variables(self.states, return_type)

    def get_residuals(self, return_type=None):
        """ Return residuals grid. """
        self('residuals_tab').click()
        return self._get_variables(self.residuals, return_type)

    def get_state(self, name, return_type=None):
        """ Return first state variable with `name`. """
        self('states_tab').click()
        return self._get_variable(name, self.states, return_type)

    def get_residual(self, name, return_type=None):
        """ Return first residual variable with `name`. """
        self('residuals_tab').click()
        return self._get_variable(name, self.residuals, return_type)

    def show_states(self):
        """switch to states tab"""
        self('states_tab').click()

    def show_residuals(self):
        """switch to residuals tab"""
        self('residuals_tab').click()
Exemple #5
0
 def update(self):
     self.element.rect = self.grid_camera.rect
     if self.tile_positions != self.grid_position.tile_positions:
         self.refresh()
         self.reset_position_mapping()
     for position in self.mapping.keys():
         tile = self.mapping[position]
         tile.colour = self.colour
         tile.grid_camera = self.grid_camera
         tile.grid_position.x = self.grid_position.x + position[0]
         tile.grid_position.y = self.grid_position.y + position[1]
     GridElement.update(self)
     return
Exemple #6
0
class ComponentPage(DialogPage):
    """ Component editor page. """

    inputs_tab = ButtonElement((By.XPATH, "div/ul/li/a[text()='Inputs']"))
    slots_tab = ButtonElement((By.XPATH, "div/ul/li/a[text()='Slots']"))
    outputs_tab = ButtonElement((By.XPATH, "div/ul/li/a[text()='Outputs']"))
    events_tab = ButtonElement((By.XPATH, "div/ul/li/a[text()='Events']"))

    inputs = GridElement((By.ID, 'Inputs_props'))
    outputs = GridElement((By.ID, 'Outputs_props'))

    def __init__(self, browser, port, locator):
        super(ComponentPage, self).__init__(browser, port, locator)
        # It takes a while for the full load to complete.
        NotifierPage.wait(self)

    def get_inputs(self):
        """ Return inputs grid. """
        self('inputs_tab').click()
        return self.inputs

    def set_input(self, name, value):
        """ Set input `name` to `value`. """
        self('inputs_tab').click()
        grid = self.inputs
        found = []
        for row in grid.rows:
            if row[0] == name:
                row[2] = value
                return
            found.append(row[0])
        raise RuntimeError('%r not found in inputs %s' % (name, found))

    def get_events(self):
        """ Return events grid. """
        self('events_tab').click()
        return self.events

    def get_outputs(self):
        """ Return outputs grid. """
        self('outputs_tab').click()
        return self.outputs

    def show_slots(self):
        """switch to slots tab"""
        self('slots_tab').click()
Exemple #7
0
class PropertiesPage(DialogPage):
    """ Component properties page. """

    header = TextElement((By.XPATH, 'h3[1]'))
    inputs = GridElement((By.ID, 'Inputs_props'))
    outputs = GridElement((By.ID, 'Outputs_props'))

    def set_input(self, name, value):
        """ Set input `name` to `value`. """
        self('inputs_tab').click()
        grid = self.inputs
        found = []
        for row in grid.rows:
            if row[0] == name:
                row[1] = value
                return
            found.append(row[0])
        raise RuntimeError('%r not found in inputs %s' % (name, found))
Exemple #8
0
class WorkspacePage(BasePageObject):

    title_prefix = 'OpenMDAO:'

    # Top.
    project_menu = ButtonElement((By.ID, 'project-menu'))
    commit_button = ButtonElement((By.ID, 'project-commit'))
    revert_button = ButtonElement((By.ID, 'project-revert'))
    run_button = ButtonElement((By.ID, 'project-run'))
    reload_button = ButtonElement((By.ID, 'project-reload'))
    close_button = ButtonElement((By.ID, 'project-close'))
    exit_button = ButtonElement((By.ID, 'project-exit'))

    view_menu = ButtonElement((By.ID, 'view-menu'))
    objects_button = ButtonElement((By.ID, 'view-components'))
    console_button = ButtonElement((By.ID, 'view-console'))
    dataflow_button = ButtonElement((By.ID, 'view-dataflow'))
    files_button = ButtonElement((By.ID, 'view-files'))
    library_button = ButtonElement((By.ID, 'view-library'))
    properties_button = ButtonElement((By.ID, 'view-properties'))
    workflow_button = ButtonElement((By.ID, 'view-workflow'))
    refresh_button = ButtonElement((By.ID, 'view-refresh'))

    tools_menu = ButtonElement((By.ID, 'tools-menu'))
    editor_button = ButtonElement((By.ID, 'tools-editor'))
    plotter_button = ButtonElement((By.ID, 'tools-plotter'))
    drawing_button = ButtonElement((By.ID, 'tools-drawing'))
    log_button = ButtonElement((By.ID, 'tools-log'))

    help_menu = ButtonElement((By.ID, 'help-menu'))
    doc_button = ButtonElement((By.ID, 'help-doc'))

    about_button = ButtonElement((By.ID, 'about-item'))

    # Left side.
    objects_tab = ButtonElement((By.ID, 'otree_tab'))
    files_tab = ButtonElement((By.ID, 'ftree_tab'))

    # Object context menu.
    obj_properties = ButtonElement((By.XPATH, "//a[(@rel='properties')]"))
    obj_dataflow = ButtonElement((By.XPATH, "//a[(@rel='show_dataflow')]"))
    obj_workflow = ButtonElement((By.XPATH, "//a[(@rel='show_workflow')]"))
    obj_run = ButtonElement((By.XPATH, "//a[(@rel='run')]"))
    obj_toggle = ButtonElement((By.XPATH, "//a[(@rel='toggle')]"))
    obj_remove = ButtonElement((By.XPATH, "//a[(@rel='remove')]"))

    # File menu
    file_menu = ButtonElement(
        (By.XPATH, '/html/body/div/div/div/nav2/ul/li/a'))
    newfile_button = ButtonElement(
        (By.XPATH, '/html/body/div/div/div/nav2/ul/li/ul/li[1]/a'))
    newfolder_button = ButtonElement(
        (By.XPATH, '/html/body/div/div/div/nav2/ul/li/ul/li[2]/a'))
    add_button = ButtonElement(
        (By.XPATH, '/html/body/div/div/div/nav2/ul/li/ul/li[3]/a'))

    # File context menu.
    file_create = ButtonElement((By.XPATH, "//a[(@rel='createFile')]"))
    file_add = ButtonElement((By.XPATH, "//a[(@rel='addFile')]"))
    file_folder = ButtonElement((By.XPATH, "//a[(@rel='createFolder')]"))
    #    file_rename = ButtonElement((By.XPATH, "//a[(@rel='renameFile')]"))
    #    file_view   = ButtonElement((By.XPATH, "//a[(@rel='viewFile')]"))
    file_edit = ButtonElement((By.XPATH, "//a[(@rel='editFile')]"))
    file_import = ButtonElement((By.XPATH, "//a[(@rel='importFile')]"))
    file_exec = ButtonElement((By.XPATH, "//a[(@rel='execFile')]"))
    file_delete = ButtonElement((By.XPATH, "//a[(@rel='deleteFile')]"))
    file_toggle = ButtonElement((By.XPATH, "//a[(@rel='toggle')]"))

    file_chooser = InputElement((By.ID, 'filechooser'))

    # Center.
    dataflow_tab = ButtonElement((By.ID, 'dataflow_tab'))
    workflow_tab = ButtonElement((By.ID, 'workflow_tab'))

    # Right side.
    properties_tab = ButtonElement((By.ID, 'properties_tab'))
    props_header = TextElement((By.XPATH, "//div[@id='properties_pane']/h3"))
    props_inputs = GridElement(
        (By.XPATH, "//div[@id='properties_pane']/div[1]"))
    props_outputs = GridElement(
        (By.XPATH, "//div[@id='properties_pane']/div[2]"))

    library_tab = ButtonElement((By.ID, 'library_tab'))
    library_search = InputElement((By.ID, 'objtt-select'))
    library_clear = ButtonElement((By.ID, 'objtt-clear'))

    # Bottom.
    history = TextElement((By.ID, 'history'))
    command = InputElement((By.ID, 'command'))
    submit = ButtonElement((By.ID, 'command-button'))

    def __init__(self, browser, port):
        super(WorkspacePage, self).__init__(browser, port)

        self.locators = {}
        self.locators["objects"] = (By.XPATH,
                                    "//div[@id='otree_pane']//li[@path]")
        self.locators["files"] = (
            By.XPATH, "//div[@id='ftree_pane']//a[@class='file ui-draggable']")

        # Wait for bulk of page to load.
        WebDriverWait(
            self.browser,
            TMO).until(lambda browser: len(self.get_dataflow_figures()) > 0)

        # Now wait for all WebSockets open.
        browser.execute_script('openmdao.Util.webSocketsReady(2);')
        expected = 'WebSockets open'
        try:
            msg = NotifierPage.wait(self)
        except TimeoutException:  # Typically no exception text is provided.
            raise TimeoutException('Timed-out waiting for web sockets')
        while msg != expected:
            # During 'automatic' reloads we can see 'WebSockets closed'
            logging.warning('Acknowledged %r while waiting for %r', msg,
                            expected)
            time.sleep(1)
            try:
                msg = NotifierPage.wait(self)
            except TimeoutException:
                raise TimeoutException('Timed-out waiting for web sockets')

    def find_library_button(self, name, delay=0):
        path = "//table[(@id='objtypetable')]//td[text()='%s']" % name
        for retry in range(5):
            try:
                element = WebDriverWait(self.browser, TMO).until(
                    lambda browser: browser.find_element(By.XPATH, path))
            except TimeoutException as err:
                logging.warning(str(err))
            else:
                break
        else:
            raise err
        if delay:
            time.sleep(delay)
        return element

    def find_object_button(self, name, delay=0):
        path = "//div[@id='otree_pane']//li[(@path='%s')]//a" % name
        for retry in range(5):
            try:
                element = WebDriverWait(self.browser, TMO).until(
                    lambda browser: browser.find_element(By.XPATH, path))
            except TimeoutException as err:
                logging.warning(str(err))
            else:
                break
        else:
            raise err
        if delay:
            time.sleep(delay)
        return element

    def run(self, timeout=TMO):
        """ Run current component. """
        self('project_menu').click()
        self('run_button').click()
        NotifierPage.wait(self, timeout)

    def do_command(self, cmd, timeout=TMO, ack=True):
        """ Execute a command. """
        self.command = cmd
        self('submit').click()
        if ack:
            NotifierPage.wait(self, timeout, base_id='command')

    def close_workspace(self, commit=False):
        """ Close the workspace page. Returns :class:`ProjectsListPage`. """
        if commit:
            self.commit_project()
        self.browser.execute_script('openmdao.Util.closeWebSockets();')
        NotifierPage.wait(self)
        self('project_menu').click()
        self('close_button').click()

        from project import ProjectsListPage
        return ProjectsListPage.verify(self.browser, self.port)

    def attempt_to_close_workspace(self, expectDialog, confirm):
        """ Close the workspace page. Returns :class:`ProjectsListPage`. """
        self('project_menu').click()
        self('close_button').click()

        #if you expect the "close without saving?" dialog
        if expectDialog:
            dialog = ConfirmationPage(self)
            if confirm:  #close without saving
                self.browser.execute_script('openmdao.Util.closeWebSockets();')
                NotifierPage.wait(self)
                dialog.click_ok()
                from project import ProjectsListPage
                return ProjectsListPage.verify(self.browser, self.port)
            else:  #return to the project, intact.
                dialog.click_cancel()
        else:  #no unsaved changes
            from project import ProjectsListPage
            return ProjectsListPage.verify(self.browser, self.port)

    def open_editor(self):
        """ Open code editor.  Returns :class:`EditorPage`. """
        self('tools_menu').click()
        self('editor_button').click()
        self.browser.switch_to_window('Code Editor')
        return EditorPage.verify(self.browser, self.port)

    def get_files(self):
        """ Return names in the file tree. """
        WebDriverWait(self.browser, TMO).until(
            lambda browser: browser.find_element(By.ID, 'ftree_pane'))
        # FIXME: absolute delay for tree population.
        time.sleep(1)
        file_items = self.browser.find_elements(*self.locators["files"])
        file_names = []
        for i in range(len(file_items)):
            for retry in range(10):  # This has had issues...
                try:
                    file_names.append(
                        self.browser.find_elements(
                            *self.locators["files"])[i].text.strip())
                except StaleElementReferenceException:
                    logging.warning(
                        'get_files: StaleElementReferenceException')
                else:
                    break
        return file_names

    def add_file(self, file_path):
        """ Read in `file_path` """
        if file_path.endswith('.pyc'):
            file_path = file_path[:-1]

        self('files_tab').click()
        self('file_menu').click()
        self('add_button').click()

        self.file_chooser = file_path
        time.sleep(0.5)

    def new_file_dialog(self):
        """ bring up the new file dialog """
        self('files_tab').click()
        self('file_menu').click()
        self('newfile_button').click()
        return ValuePrompt(self.browser, self.port)

    def new_file(self, filename):
        """ Make a new empty file `filename`. """
        page = self.new_file_dialog()
        page.set_value(filename)
        NotifierPage.wait(self)  # Wait for creation to complete.

    def find_file(self, filename, tmo=TMO):
        """ Return elemnt corresponding to `filename`. """
        xpath = "//a[(@path='/%s')]" % filename
        return WebDriverWait(
            self.browser,
            tmo).until(lambda browser: browser.find_element_by_xpath(xpath))

    def edit_file(self, filename, dclick=True):
        """ Edit `filename` via double-click or context menu. """
        self('files_tab').click()
        element = self.find_file(filename)
        chain = ActionChains(self.browser)
        if dclick:  # This has had issues...
            for i in range(10):
                try:
                    chain.double_click(element).perform()
                except StaleElementReferenceException:
                    logging.warning(
                        'edit_file: StaleElementReferenceException')
                    element = self.find_file(filename, 1)
                    chain = ActionChains(self.browser)
                else:
                    break
        else:
            chain.context_click(element).perform()
            self('file_edit').click()
        self.browser.switch_to_window('Code Editor')
        return EditorPage.verify(self.browser, self.port)

    def expand_folder(self, filename):
        """ Expands `filename`. """
        self('files_tab').click()
        xpath = "//div[@id='ftree_pane']//a[(@path='/%s')]/../ins" % filename
        element = WebDriverWait(
            self.browser,
            TMO).until(lambda browser: browser.find_element_by_xpath(xpath))
        element.click()
        time.sleep(1)  # Wait for cute animation.

    def toggle_files(self, filename):
        """ Toggle files display, using context menu of `filename`. """
        self('files_tab').click()
        time.sleep(0.5)
        element = self.find_file(filename)
        chain = ActionChains(self.browser)
        chain.context_click(element).perform()
        time.sleep(0.5)
        self('file_toggle').click()
        time.sleep(0.5)

    def commit_project(self, comment='no comment'):
        """ Commit current project. """
        self('project_menu').click()
        self('commit_button').click()
        page = ValuePrompt(self.browser, self.port)
        page.set_value(comment)
        NotifierPage.wait(self)

    def reload_project(self):
        """ Reload current project. """
        self('project_menu').click()
        self('reload_button').click()
        WorkspacePage.verify(self.browser, self.port)

    def get_objects_attribute(self, attribute, visible=False):
        """ Return list of `attribute` values for all objects. """
        WebDriverWait(self.browser, TMO).until(
            lambda browser: browser.find_element(By.ID, 'otree_pane'))
        object_elements = self.browser.find_elements(*self.locators["objects"])
        values = []
        for element in object_elements:
            if not visible or element.is_displayed():
                values.append(element.get_attribute(attribute))
        return values

    def select_object(self, component_name):
        """ Select `component_name`. """
        self('objects_tab').click()
        xpath = "//div[@id='otree_pane']//li[(@path='%s')]//a" % component_name
        element = WebDriverWait(
            self.browser,
            TMO).until(lambda browser: browser.find_element_by_xpath(xpath))
        element.click()

    def expand_object(self, component_name):
        """ Expands `component_name`. """
        self('objects_tab').click()
        xpath = "//div[@id='otree_pane']//li[(@path='%s')]//ins" % component_name
        element = WebDriverWait(
            self.browser,
            TMO).until(lambda browser: browser.find_element_by_xpath(xpath))
        element.click()
        time.sleep(1)  # Wait for cute animation.

    def show_dataflow(self, component_name):
        """ Show dataflow of `component_name`. """
        self('objects_tab').click()
        xpath = "//div[@id='otree_pane']//li[(@path='%s')]//a" % component_name
        element = WebDriverWait(
            self.browser,
            TMO).until(lambda browser: browser.find_element_by_xpath(xpath))
        element.click()
        time.sleep(0.5)
        # Try to recover from context menu not getting displayed.
        for retry in range(3):
            chain = ActionChains(self.browser)
            chain.context_click(element).perform()
            try:
                self('obj_dataflow').click()
                break
            except TimeoutException:
                if retry >= 2:
                    raise

    def show_workflow(self, component_name):
        """ Show workflow of `component_name`. """
        self('objects_tab').click()
        xpath = "//div[@id='otree_pane']//li[(@path='%s')]//a" % component_name
        element = WebDriverWait(
            self.browser,
            TMO).until(lambda browser: browser.find_element_by_xpath(xpath))
        element.click()
        time.sleep(0.5)
        # Try to recover from context menu not getting displayed.
        for retry in range(3):
            chain = ActionChains(self.browser)
            chain.context_click(element).perform()
            try:
                self('obj_workflow').click()
                break
            except TimeoutException:
                if retry >= 2:
                    raise

    def show_properties(self):
        """ Display properties. """
        # This has had some odd failures where the tab is highlighted as if
        # hovering over it, yet the Library tab is still the selected one.
        for retry in range(5):
            try:
                self('properties_tab').click()
                WebDriverWait(
                    self.browser,
                    1).until(lambda browser: self('props_header').is_visible)
            except TimeoutException:
                if retry:
                    logging.warning('TimeoutException in show_properties')
            else:
                break
        else:
            raise RuntimeError('Too many TimeoutExceptions')

    def show_library(self):
        """ Display library. """
        # For some reason the first try never works, so the wait is set
        # low and we expect to retry at least once.
        for retry in range(5):
            try:
                self('library_tab').click()
                WebDriverWait(
                    self.browser,
                    1).until(lambda browser: self('library_search').is_visible)
            except TimeoutException:
                if retry:
                    logging.warning('TimeoutException in show_library')
            else:
                break
        else:
            raise RuntimeError('Too many TimeoutExceptions')

    def set_library_filter(self, filter):
        """ Set the search filter text. """
        for retry in range(10):  # This has had issues...
            try:
                self.library_search = filter + '\n'
            except StaleElementReferenceException:
                logging.warning('set_library_filter:'
                                ' StaleElementReferenceException')
            else:
                break
        time.sleep(0.5)  # Wait for display update.

    def clear_library_filter(self):
        """ Clear the search filter via the 'X' button. """
        self('library_clear').click()
        time.sleep(0.5)  # Wait for display update.

    def get_object_types(self):
        """ Return displayed object types. """
        xpath = "//table[(@id='objtypetable')]//td"
        return [
            element.text
            for element in self.browser.find_elements(By.XPATH, xpath)
        ]

    def get_library_searches(self):
        """ Return stored library search terms. """
        self.library_search = 'searches'
        menu = self.browser.find_element(By.CLASS_NAME, 'ui-autocomplete')
        items = menu.find_elements(By.CLASS_NAME, 'ui-menu-item')
        searches = [item.text for item in items]
        self.clear_library_filter()
        return searches

    def get_library_item(self, item_name):
        """ Return element for library item `item_name`. """
        xpath = "//table[(@id='objtypetable')]//td[(@modpath='%s')]" % item_name
        library_item = WebDriverWait(
            self.browser,
            TMO).until(lambda browser: browser.find_element_by_xpath(xpath))
        WebDriverWait(self.browser,
                      TMO).until(lambda browser: library_item.is_displayed())
        return library_item

    def add_library_item_to_dataflow(self,
                                     item_name,
                                     instance_name,
                                     check=True,
                                     offset=None,
                                     prefix=None):
        """ Add component `item_name`, with name `instance_name`. """
        library_item = self.get_library_item(item_name)

        target = WebDriverWait(
            self.browser,
            TMO).until(lambda browser: browser.find_element_by_xpath(
                "//*[@id='-dataflow']"))

        offset = offset or (90, 90)
        chain = ActionChains(self.browser)
        if False:
            chain.drag_and_drop(library_item, target)
        else:
            chain.click_and_hold(library_item)
            chain.move_to_element_with_offset(target, offset[0], offset[1])
            chain.release(None)
        chain.perform()

        page = ValuePrompt(self.browser, self.port)
        page.set_value(instance_name)
        # Check that the prompt is gone so we can distinguish a prompt problem
        # from a dataflow update problem.
        time.sleep(0.25)
        self.browser.implicitly_wait(1)  # We don't expect to find anything.
        try:
            eq(len(self.browser.find_elements(*page('prompt')._locator)), 0)
        finally:
            self.browser.implicitly_wait(TMO)

        retval = None
        if check:  # Check that it's been added.
            retval = WebDriverWait(
                self.browser,
                TMO).until(lambda browser: self.get_dataflow_figure(
                    instance_name, prefix))
        return retval

    def get_dataflow_figures(self):
        """ Return dataflow figure elements. """
        return find_dataflow_figures(self)

    def get_dataflow_figure(self, name, prefix=None, retries=5):
        """ Return :class:`DataflowFigure` for `name`. """
        return find_dataflow_figure(self, name, prefix, retries)

    def get_dataflow_component_names(self):
        """ Return names of dataflow components. """
        return find_dataflow_component_names(self)

    def connect(self, src, dst):
        """ Return :class:`ConnectionsPage` for connecting `src` to `dst`. """
        chain = ActionChains(self.browser)
        chain.click_and_hold(src.output_port)
        # Using root rather than input_port since for some reason
        # even using a negative Y offset can select the parent's input.
        chain.move_to_element(dst.input_port)
        chain.release(None)
        chain.perform()
        parent, dot, srcname = src.pathname.rpartition('.')
        parent, dot, dstname = dst.pathname.rpartition('.')
        editor_id = 'ConnectionsFrame-%s' % (parent)
        editor_id = editor_id.replace('.', '-')
        return ConnectionsPage(self.browser, self.port, (By.ID, editor_id))

    def replace(self, name, classname, confirm=True):
        """ Replace `name` with an instance of `classname`. """
        library_item = self.get_library_item(classname)
        target = self.get_dataflow_figure(name).root

        chain = ActionChains(self.browser)
        chain.click_and_hold(library_item)
        chain.move_to_element_with_offset(target, 125, 30)
        chain.release(None)
        chain.perform()

        dialog = ConfirmationPage(self)
        if confirm:
            dialog.click_ok()
        else:
            dialog.click_cancel()

    def add_object_to_workflow(self, obj_path, target_name):
        """ Add `obj_path` object to `target_name` in workflow. """
        for retry in range(3):
            try:
                obj = self.find_object_button(obj_path)
                target = self.get_workflow_figure(target_name)
                chain = ActionChains(self.browser)
                chain.move_to_element(obj)
                chain.click_and_hold(obj)
                chain.move_to_element(target.root)
                chain.move_by_offset(2, 1)
                chain.release(None)
                chain.perform()
            except StaleElementReferenceException:
                if retry < 2:
                    logging.warning('add_object_to_workflow:'
                                    ' StaleElementReferenceException')
                else:
                    raise
            else:
                break

    def get_workflow_figures(self):
        """ Return workflow figure elements. """
        return find_workflow_figures(self)

    def get_workflow_component_figures(self):
        """ Return workflow component figure elements. """
        return find_workflow_component_figures(self)

    def get_workflow_figure(self, name, prefix=None, retries=5):
        """ Return :class:`WorkflowFigure` for `name`. """
        return find_workflow_figure(self, name, prefix, retries)

    def show_log(self):
        """ Open log viewer.  Returns :class:`LogViewer`. """
        self('tools_menu').click()
        self('log_button').click()
        return LogViewer.verify(self.browser, self.port)

    def hide_left(self):
        toggler = self.browser.find_element_by_css_selector(
            '.ui-layout-toggler-west-open')
        toggler.click()

    def show_left(self):
        toggler = self.browser.find_element_by_css_selector(
            '.ui-layout-toggler-west-closed')
        toggler.click()

    def hide_right(self):
        toggler = self.browser.find_element_by_css_selector(
            '.ui-layout-toggler-east-open')
        toggler.click()

    def show_right(self):
        toggler = self.browser.find_element_by_css_selector(
            '.ui-layout-toggler-east-closed')
        toggler.click()

    def hide_console(self):
        toggler = self.browser.find_element_by_css_selector(
            '.ui-layout-toggler-south-open')
        toggler.click()

    def show_console(self):
        toggler = self.browser.find_element_by_css_selector(
            '.ui-layout-toggler-south-closed')
        toggler.click()
Exemple #9
0
class WorkspacePage(BasePageObject):

    title_prefix = 'OpenMDAO:'

    # Top.
    project_menu = ButtonElement((By.ID, 'project-menu'))
    save_button = ButtonElement((By.ID, 'project-save'))
    run_button = ButtonElement((By.ID, 'project-run'))
    reload_button = ButtonElement((By.ID, 'project-reload'))
    close_button = ButtonElement((By.ID, 'project-close'))
    exit_button = ButtonElement((By.ID, 'project-exit'))

    view_menu = ButtonElement((By.ID, 'view-menu'))
    cmdline_button = ButtonElement((By.ID, 'view-cmdline'))
    console_button = ButtonElement((By.ID, 'view-console'))
    libraries_button = ButtonElement((By.ID, 'view-libraries'))
    objects_button = ButtonElement((By.ID, 'view-objects'))
    properties_button = ButtonElement((By.ID, 'view-properties'))
    workflow_button = ButtonElement((By.ID, 'view-workflow'))
    dataflow_button = ButtonElement((By.ID, 'view-dataflow'))
    refresh_button = ButtonElement((By.ID, 'view-refresh'))

    tools_menu = ButtonElement((By.ID, 'tools-menu'))
    editor_button = ButtonElement((By.ID, 'tools-editor'))
    plotter_button = ButtonElement((By.ID, 'tools-plotter'))
    addons_button = ButtonElement((By.ID, 'tools-addons'))

    help_menu = ButtonElement((By.ID, 'help-menu'))
    doc_button = ButtonElement((By.ID, 'help-doc'))

    about_button = ButtonElement((By.ID, 'about-item'))

    # Left side.
    objects_tab = ButtonElement((By.ID, 'otree_tab'))

    # Object context menu.
    obj_properties = ButtonElement((By.XPATH, "//a[(@rel='properties')]"))
    obj_dataflow = ButtonElement((By.XPATH, "//a[(@rel='show_dataflow')]"))
    obj_workflow = ButtonElement((By.XPATH, "//a[(@rel='show_workflow')]"))
    obj_run = ButtonElement((By.XPATH, "//a[(@rel='run')]"))
    obj_toggle = ButtonElement((By.XPATH, "//a[(@rel='toggle')]"))
    obj_remove = ButtonElement((By.XPATH, "//a[(@rel='remove')]"))

    # Center.
    dataflow_tab = ButtonElement((By.ID, 'dataflow_tab'))
    workflow_tab = ButtonElement((By.ID, 'workflow_tab'))
    code_tab = ButtonElement((By.ID, 'code_tab'))

    # Right side.
    properties_tab = ButtonElement((By.ID, 'properties_tab'))
    props_header = TextElement((By.XPATH, "//div[@id='propertieseditor']/h3"))
    props_inputs = GridElement(
        (By.XPATH, "//div[@id='propertieseditor']/div[1]"))
    props_outputs = GridElement(
        (By.XPATH, "//div[@id='propertieseditor']/div[2]"))

    libraries_tab = ButtonElement((By.ID, 'palette_tab'))
    libraries_searchbox = InputElement((By.ID, 'objtt-select'))

    # Bottom.
    history = TextElement((By.ID, 'history'))
    command = InputElement((By.ID, 'command'))
    submit = ButtonElement((By.ID, 'command-button'))

    def __init__(self, browser, port):
        super(WorkspacePage, self).__init__(browser, port)

        self.locators = {}
        self.locators["objects"] = (By.XPATH, "//div[@id='otree']//li[@path]")

        # Wait for bulk of page to load.
        WebDriverWait(
            self.browser, 2 *
            TMO).until(lambda browser: len(self.get_dataflow_figures()) > 0)
        # Now wait for WebSockets.
        # FIXME: absolute delay before polling sockets.
        time.sleep(2)
        browser.execute_script('openmdao.Util.webSocketsReady(2);')
        NotifierPage.wait(browser, port)

    def find_palette_button(self, name):
        path = "//table[(@id='objtypetable')]//td[text()='%s']" % name
        return ButtonElement((By.XPATH, path)).get(self)

    def run(self, timeout=TMO):
        """ Run current component. """
        self('project_menu').click()
        self('run_button').click()
        NotifierPage.wait(self.browser, self.port, timeout)

    def do_command(self, cmd, timeout=TMO):
        """ Execute a command. """
        self.command = cmd
        self('submit').click()
        NotifierPage.wait(self.browser, self.port, timeout)

    def close_workspace(self):
        """ Close the workspace page. Returns :class:`ProjectsListPage`. """
        self.browser.execute_script('openmdao.Util.closeWebSockets();')
        NotifierPage.wait(self.browser, self.port)
        self('project_menu').click()

        # Sometimes chromedriver hangs here, so we click in separate thread.
        # It's a known issue on the chromedriver site.
        closer = threading.Thread(target=self._closer)
        closer.daemon = True
        closer.start()
        closer.join(60)
        if closer.is_alive():
            abort(True)
            raise SkipTest("Can't close workspace, driver hung :-(")

        from project import ProjectsListPage
        return ProjectsListPage.verify(self.browser, self.port)

    def _closer(self):
        """ Clicks the close button. """
        self('close_button').click()

    def open_editor(self):
        """ Open code editor.  Returns :class:`EditorPage`. """
        self('tools_menu').click()
        self('editor_button').click()
        self.browser.switch_to_window('Code Editor')
        return EditorPage.verify(self.browser, self.port)

    def save_project(self):
        """ Save current project. """
        self('project_menu').click()
        self('save_button').click()
        NotifierPage.wait(self.browser, self.port)

    def get_objects_attribute(self, attribute):
        """ Return list of `attribute` values for all objects. """
        WebDriverWait(
            self.browser,
            TMO).until(lambda browser: browser.find_element(By.ID, 'otree'))
        object_elements = self.browser.find_elements(*self.locators["objects"])
        values = []
        for element in object_elements:
            values.append(element.get_attribute(attribute))
        return values

    def select_object(self, component_name):
        """ Select `component_name`. """
        self('objects_tab').click()
        xpath = "//div[@id='otree']//li[(@path='%s')]//a" % component_name
        element = WebDriverWait(
            self.browser,
            TMO).until(lambda browser: browser.find_element_by_xpath(xpath))
        element.click()

    def expand_object(self, component_name):
        """ Expands `component_name`. """
        self('objects_tab').click()
        xpath = "//div[@id='otree']//li[(@path='%s')]//ins" % component_name
        element = WebDriverWait(
            self.browser,
            TMO).until(lambda browser: browser.find_element_by_xpath(xpath))
        element.click()

    def show_dataflow(self, component_name):
        """ Show dataflow of `component_name`. """
        self('objects_tab').click()
        xpath = "//div[@id='otree']//li[(@path='%s')]//a" % component_name
        element = WebDriverWait(
            self.browser,
            TMO).until(lambda browser: browser.find_element_by_xpath(xpath))
        chain = ActionChains(self.browser)
        chain.context_click(element).perform()
        self('obj_dataflow').click()

    @property
    def libraries_search(self):
        """ The contents of the libraries_search box. """
        return self('libraries_searchbox').value

    @libraries_search.setter
    def libraries_search(self, value):
        """ Set the value of the libraries_search box. """
        self.libraries_searchbox = value

    def add_library_item_to_dataflow(self, item_name, instance_name):
        """ Add component `item_name`, with name `instance_name`. """
        #xpath = "//div[(@id='palette')]//div[(@path='%s')]" % item_name
        xpath = "//table[(@id='objtypetable')]//td[(@modpath='%s')]" % item_name
        library_item = WebDriverWait(
            self.browser,
            TMO).until(lambda browser: browser.find_element_by_xpath(xpath))
        WebDriverWait(self.browser,
                      TMO).until(lambda browser: library_item.is_displayed())
        # FIXME: absolute delay to wait for 'slide' to complete.
        time.sleep(1)

        target = WebDriverWait(
            self.browser,
            TMO).until(lambda browser: browser.find_element_by_xpath(
                "//*[@id='-dataflow']"))

        chain = ActionChains(self.browser)
        if False:
            chain = chain.drag_and_drop(library_item, target)
        else:
            chain = chain.click_and_hold(library_item)
            chain = chain.move_to_element_with_offset(target, 100, 100)
            chain = chain.release(None)
        chain.perform()

        page = ValuePrompt(self.browser, self.port)
        page.set_value(instance_name)
        # Check that the prompt is gone so we can distinguish a prompt problem
        # from a dataflow update problem.
        time.sleep(0.25)
        eq(len(self.browser.find_elements(*page('prompt')._locator)), 0)
        WebDriverWait(self.browser,
                      TMO).until(lambda browser: instance_name in self.
                                 get_dataflow_component_names())

    def get_dataflow_figures(self):
        """ Return dataflow figure elements. """
        return self.browser.find_elements_by_class_name('DataflowFigure')

    def get_dataflow_figure(self, name, prefix=None, retries=5):
        """ Return :class:`DataflowFigure` for `name`. """
        for retry in range(retries):
            time.sleep(0.5)
            figures = self.browser.find_elements_by_class_name(
                'DataflowFigure')
            if not figures:
                continue
            fig_name = None
            for figure in figures:
                try:
                    header = figure.find_elements_by_class_name(
                        'DataflowFigureHeader')
                    if len(header) == 0:
                        # the outermost figure (globals) has no header or name
                        if name == '' and prefix is None:
                            fig = DataflowFigure(self.browser, self.port,
                                                 figure)
                            return fig
                        else:
                            continue
                    fig_name = figure.find_elements_by_class_name(
                        'DataflowFigureHeader')[0].text
                except StaleElementReferenceException:
                    logging.warning('get_dataflow_figure:'
                                    ' StaleElementReferenceException')
                else:
                    if fig_name == name:
                        fig = DataflowFigure(self.browser, self.port, figure)
                        if prefix is not None:
                            if prefix:
                                fig.pathname = '%s.%s' % (prefix, name)
                            else:
                                fig.pathname = name
                        return fig
        return None

    def get_dataflow_component_names(self):
        """ Return names of dataflow components. """
        names = []

        # Assume there should be at least 1, wait for number to not change.
        n_found = 0
        for retry in range(10):
            dataflow_component_headers = \
                self.browser.find_elements_by_class_name('DataflowFigureHeader')
            if dataflow_component_headers:
                n_headers = len(dataflow_component_headers)
                if n_found:
                    if n_headers == n_found:
                        break
                n_found = n_headers
        else:
            logging.error('get_dataflow_component_names: n_found %s', n_found)
            return names

        for i in range(len(dataflow_component_headers)):
            for retry in range(10):  # This has had issues...
                try:
                    names.append(
                        self.browser.find_elements_by_class_name(
                            'DataflowFigureHeader')[i].text)
                except StaleElementReferenceException:
                    logging.warning('get_dataflow_component_names:'
                                    ' StaleElementReferenceException')
                except IndexError:
                    logging.warning(
                        'get_dataflow_component_names:'
                        ' IndexError for i=%s, headers=%s', i,
                        len(dataflow_component_headers))
                else:
                    break

        if len(names) != len(dataflow_component_headers):
            logging.error(
                'get_dataflow_component_names:'
                ' expecting %d names, got %s', len(dataflow_component_headers),
                names)
        return names

    def connect(self, src, dst):
        """ Return :class:`ConnectionsPage` for connecting `src` to `dst`. """
        chain = ActionChains(self.browser)
        chain = chain.click_and_hold(src.output_port)
        # Using root rather than input_port since for some reason
        # even using a negative Y offset can select the parent's input.
        chain = chain.move_to_element(dst.root)
        chain = chain.release(None)
        chain.perform()
        parent, dot, srcname = src.pathname.rpartition('.')
        parent, dot, dstname = dst.pathname.rpartition('.')
        editor_id = 'ConnectionsFrame-%s' % (parent)
        editor_id = editor_id.replace('.', '-')
        return ConnectionsPage(self.browser, self.port, (By.ID, editor_id))

    def hide_left(self):
        toggler = self.browser.find_element_by_css_selector(
            '.ui-layout-toggler-west-open')
        toggler.click()

    def show_left(self):
        toggler = self.browser.find_element_by_css_selector(
            '.ui-layout-toggler-west-closed')
        toggler.click()

    def hide_right(self):
        toggler = self.browser.find_element_by_css_selector(
            '.ui-layout-toggler-east-open')
        toggler.click()

    def show_right(self):
        toggler = self.browser.find_element_by_css_selector(
            '.ui-layout-toggler-east-closed')
        toggler.click()

    def hide_console(self):
        toggler = self.browser.find_element_by_css_selector(
            '.ui-layout-toggler-south-open')
        toggler.click()

    def show_console(self):
        toggler = self.browser.find_element_by_css_selector(
            '.ui-layout-toggler-south-closed')
        toggler.click()
class WorkspacePage(BasePageObject):

    title_prefix = 'OpenMDAO:'

    # Top.
    project_menu = ButtonElement((By.ID, 'project-menu'))
    commit_button = ButtonElement((By.ID, 'project-commit'))
    revert_button = ButtonElement((By.ID, 'project-revert'))
    reload_button = ButtonElement((By.ID, 'project-reload'))
    close_button = ButtonElement((By.ID, 'project-close'))
    exit_button = ButtonElement((By.ID, 'project-exit'))

    view_menu = ButtonElement((By.ID, 'view-menu'))
    objects_button = ButtonElement((By.ID, 'view-components'))
    console_button = ButtonElement((By.ID, 'view-console'))
    dataflow_button = ButtonElement((By.ID, 'view-dataflow'))
    files_button = ButtonElement((By.ID, 'view-files'))
    library_button = ButtonElement((By.ID, 'view-library'))
    properties_button = ButtonElement((By.ID, 'view-properties'))
    workflow_button = ButtonElement((By.ID, 'view-workflow'))
    refresh_button = ButtonElement((By.ID, 'view-refresh'))

    tools_menu = ButtonElement((By.ID, 'tools-menu'))
    editor_button = ButtonElement((By.ID, 'tools-editor'))
    plotter_button = ButtonElement((By.ID, 'tools-plotter'))
    drawing_button = ButtonElement((By.ID, 'tools-drawing'))
    log_button = ButtonElement((By.ID, 'tools-log'))

    help_menu = ButtonElement((By.ID, 'help-menu'))
    doc_button = ButtonElement((By.ID, 'help-doc'))

    about_button = ButtonElement((By.ID, 'about-item'))

    # Left side.
    objects_tab = ButtonElement((By.ID, 'otree_tab'))
    objects_selector = SelectElement((By.ID, 'otree_pane_select'))
    files_tab = ButtonElement((By.ID, 'ftree_tab'))

    # Object context menu.
    obj_properties = ButtonElement((By.XPATH, "//a[(@rel='properties')]"))
    obj_dataflow = ButtonElement((By.XPATH, "//a[(@rel='show_dataflow')]"))
    obj_workflow = ButtonElement((By.XPATH, "//a[(@rel='show_workflow')]"))
    obj_run = ButtonElement((By.XPATH, "//a[(@rel='run')]"))
    #obj_toggle     = ButtonElement((By.XPATH, "//a[(@rel='toggle')]"))
    obj_remove = ButtonElement((By.XPATH, "//a[(@rel='remove')]"))

    # File menu
    file_menu = ButtonElement(
        (By.XPATH, '/html/body/div/div/div/nav2/ul/li/a'))
    newfile_button = ButtonElement(
        (By.XPATH, '/html/body/div/div/div/nav2/ul/li/ul/li[1]/a'))
    newfolder_button = ButtonElement(
        (By.XPATH, '/html/body/div/div/div/nav2/ul/li/ul/li[2]/a'))
    add_button = ButtonElement(
        (By.XPATH, '/html/body/div/div/div/nav2/ul/li/ul/li[3]/a'))
    delete_files_button = ButtonElement(
        (By.XPATH, '/html/body/div/div/div/nav2/ul/li/ul/li[4]/a'))

    # File tree pane context menu
    ftree_newfile_button = ButtonElement(
        (By.XPATH, "//ul[@id='ftree_pane-context-menu']/li[1]]"))
    ftree_newfolder_button = ButtonElement(
        (By.XPATH, "//ul[@id='ftree_pane-context-menu']/li[2]"))
    ftree_add_button = ButtonElement(
        (By.XPATH, "//ul[@id='ftree_pane-context-menu']/li[3]"))
    ftree_toggle_files_button = ButtonElement(
        (By.XPATH, "//ul[@id='ftree_pane-context-menu']/li[4]"))

    # File context menu.
    file_create = ButtonElement((By.XPATH, "//a[(@rel='createFile')]"))
    file_add = ButtonElement((By.XPATH, "//a[(@rel='addFile')]"))
    file_folder = ButtonElement((By.XPATH, "//a[(@rel='createFolder')]"))
    file_view = ButtonElement((By.XPATH, "//a[(@rel='viewFile')]"))
    file_edit = ButtonElement((By.XPATH, "//a[(@rel='editFile')]"))
    file_exec = ButtonElement((By.XPATH, "//a[(@rel='execFile')]"))
    file_image = ButtonElement((By.XPATH, "//a[(@rel='viewImage')]"))
    file_geometry = ButtonElement((By.XPATH, "//a[(@rel='viewGeometry')]"))
    file_rename = ButtonElement((By.XPATH, "//a[(@rel='renameFile')]"))
    file_delete = ButtonElement((By.XPATH, "//a[(@rel='deleteFile')]"))
    file_toggle = ButtonElement((By.XPATH, "//a[(@rel='toggle')]"))

    file_chooser = InputElement((By.ID, 'filechooser'))

    # Center.
    dataflow_tab = ButtonElement((By.ID, 'dataflow_tab'))
    workflow_tab = ButtonElement((By.ID, 'workflow_tab'))

    # Right side.
    properties_tab = ButtonElement((By.ID, 'properties_tab'))
    props_header = TextElement((By.XPATH, "//div[@id='properties_pane']/h3"))
    props_inputs = GridElement(
        (By.XPATH, "//div[@id='properties_pane']/div[1]"))
    props_outputs = GridElement(
        (By.XPATH, "//div[@id='properties_pane']/div[2]"))

    library_tab = ButtonElement((By.ID, 'library_tab'))
    library_search = InputElement((By.ID, 'objtt-filter'))
    library_clear = ButtonElement((By.ID, 'objtt-clear'))

    library_item_docs = ButtonElement(
        (By.XPATH, "//ul[@id='lib-cmenu']/li[1]"))
    library_item_metadata = ButtonElement(
        (By.XPATH, "//ul[@id='lib-cmenu']/li[2]"))

    # Bottom.
    history = TextElement((By.ID, 'history'))
    command = InputElement((By.ID, 'cmdline'))
    submit = ButtonElement((By.ID, 'command-button'))

    def __init__(self, browser, port):
        super(WorkspacePage, self).__init__(browser, port)

        self.locators = {}
        self.locators["objects"] = \
            (By.XPATH, "//div[@id='otree_pane']//li[@path]")
        self.locators["files"] = \
            (By.XPATH, "//div[@id='ftree_pane']//a[@class='file ui-draggable']")

        # Wait for bulk of page to load.
        WebDriverWait(
            self.browser,
            TMO).until(lambda browser: len(self.get_dataflow_figures()) > 0)

        # Now wait for all WebSockets open.
        browser.execute_script('openmdao.project.webSocketsReady(2);')

        try:  # We may get 2 notifiers: sockets open and sockets closed.
            NotifierPage.wait(self, base_id='ws_open')
        except Exception as exc:
            if 'Element is not clickable' in str(exc):
                NotifierPage.wait(self, base_id='ws_closed')
                NotifierPage.wait(self, base_id='ws_open')
            else:
                raise
        else:
            self.browser.implicitly_wait(1)
            try:
                NotifierPage.wait(self,
                                  timeout=1,
                                  base_id='ws_closed',
                                  retries=0)
            except TimeoutException:
                pass  # ws closed dialog may not exist
            finally:
                self.browser.implicitly_wait(TMO)

    def find_library_button(self, name, delay=0):
        path = "//table[(@id='objtypetable')]//td[text()='%s']" % name
        for retry in range(5):
            try:
                element = WebDriverWait(self.browser, TMO).until(
                    lambda browser: browser.find_element(By.XPATH, path))
            except TimeoutException as err:
                logging.warning(str(err))
            else:
                break
        else:
            raise err
        if delay:
            time.sleep(delay)
        return element

    def find_object_button(self, name, delay=0):
        path = "//div[@id='otree_pane']//li[(@path='%s')]//a" % name
        for retry in range(5):
            try:
                element = WebDriverWait(self.browser, TMO).until(
                    lambda browser: browser.find_element(By.XPATH, path))
            except TimeoutException as err:
                logging.warning(str(err))
            else:
                break
        else:
            raise err
        if delay:
            time.sleep(delay)
        return element

    def do_command(self, cmd, timeout=TMO, ack=True):
        """ Execute a command. """
        self.command = cmd
        self('submit').click()
        if ack:
            NotifierPage.wait(self, timeout, base_id='command')

    def close_workspace(self, commit=False):
        """ Close the workspace page. Returns :class:`ProjectsPage`. """
        if commit:
            self.commit_project()
        self.browser.execute_script('openmdao.project.closeWebSockets();')
        NotifierPage.wait(self, base_id='ws_closed')
        self('project_menu').click()
        self('close_button').click()

        from project import ProjectsPage
        return ProjectsPage.verify(self.browser, self.port)

    def attempt_to_close_workspace(self, expectDialog, confirm):
        """ Close the workspace page. Returns :class:`ProjectsPage`. """
        self('project_menu').click()
        self('close_button').click()

        #if you expect the "close without saving?" dialog
        if expectDialog:
            dialog = ConfirmationPage(self)
            if confirm:  # close without saving
                self.browser.execute_script(
                    'openmdao.project.closeWebSockets();')
                NotifierPage.wait(self)
                dialog.click_ok()
                from project import ProjectsPage
                return ProjectsPage.verify(self.browser, self.port)
            else:  # return to the project, intact.
                dialog.click_cancel()
        else:  # no unsaved changes
            from project import ProjectsPage
            return ProjectsPage.verify(self.browser, self.port)

    def open_editor(self):
        """ Open code editor.  Returns :class:`EditorPage`. """
        self('tools_menu').click()
        self('editor_button').click()
        self.browser.switch_to_window('Code Editor')
        return EditorPage.verify(self.browser, self.port)

    def get_files(self):
        """ Return names in the file tree. """
        self('files_tab').click()
        WebDriverWait(self.browser, TMO).until(
            lambda browser: browser.find_element(By.ID, 'ftree_pane'))
        # FIXME: absolute delay for tree population.
        time.sleep(1)
        file_items = self.browser.find_elements(*self.locators["files"])
        file_names = []
        for i in range(len(file_items)):
            for retry in range(10):  # This has had issues...
                try:
                    file_names.append(
                        self.browser.find_elements(
                            *self.locators["files"])[i].text.strip())
                except StaleElementReferenceException:
                    logging.warning(
                        'get_files: StaleElementReferenceException')
                else:
                    break
        return file_names

    def add_file(self, file_path):
        """ Read in `file_path` """
        if file_path.endswith('.pyc'):
            file_path = file_path[:-1]

        self('files_tab').click()
        self('file_menu').click()
        self('add_button').click()

        self.file_chooser = file_path
        time.sleep(1)  # Some extra time for the file tree update.
        self.find_file(os.path.basename(file_path))  # Verify added.
        time.sleep(1)  # Some extra time for the library update.

    def add_file_to_folder(self, folder_path, file_path):
        """ Read in `file_path` """
        if file_path.endswith('.pyc'):
            file_path = file_path[:-1]

        self('files_tab').click()
        element = self.find_file(folder_path)
        chain = ActionChains(self.browser)
        chain.context_click(element).perform()
        time.sleep(0.5)
        self('file_add').click()
        time.sleep(0.5)

        self.file_chooser = file_path
        time.sleep(1)  # Some extra time for the file tree update.

    def new_file_dialog(self):
        """ bring up the new file dialog """
        self('files_tab').click()
        self('file_menu').click()
        self('newfile_button').click()
        return ValuePrompt(self.browser, self.port)

    def new_folder_dialog(self):
        """ bring up the new folder dialog """
        self('files_tab').click()
        self('file_menu').click()
        self('newfolder_button').click()
        return ValuePrompt(self.browser, self.port)

    def new_file(self, filename):
        """ Make a new empty file `filename`. """
        page = self.new_file_dialog()
        page.set_value(filename)
        NotifierPage.wait(self)  # Wait for creation to complete.

    def new_folder(self, foldername):
        """ Make a new empty folder `foldername`. """
        page = self.new_folder_dialog()
        page.set_value(foldername)

    def find_file(self, filename, tmo=TMO):
        """ Return element corresponding to `filename`. """
        xpath = "//a[(@path='/%s')]" % filename
        return WebDriverWait(
            self.browser,
            tmo).until(lambda browser: browser.find_element_by_xpath(xpath))

    def edit_file(self, filename, dclick=True):
        """ Edit `filename` via double-click or context menu. """
        self('files_tab').click()
        element = self.find_file(filename)
        chain = ActionChains(self.browser)
        if dclick:  # This has had issues...
            for i in range(10):
                try:
                    chain.double_click(element).perform()
                except StaleElementReferenceException:
                    logging.warning(
                        'edit_file: StaleElementReferenceException')
                    element = self.find_file(filename, 1)
                    chain = ActionChains(self.browser)
                else:
                    break
        else:
            chain.context_click(element).perform()
            self('file_edit').click()
        self.browser.switch_to_window('Code Editor')
        return EditorPage.verify(self.browser, self.port)

    def view_file(self, filename):
        """ View image `filename` in another window via context menu. """
        self('files_tab').click()
        element = self.find_file(filename)
        chain = ActionChains(self.browser)
        chain.context_click(element).perform()
        self('file_view').click()
        self.browser.switch_to_window(self.browser.window_handles[-1])
        return BasePageObject.verify(self.browser, self.port)

    def view_image(self, filename, dclick=True):
        """ View image `filename` via double-click or context menu. """
        self('files_tab').click()
        element = self.find_file(filename)
        chain = ActionChains(self.browser)
        if dclick:  # This has had issues...
            for i in range(10):
                try:
                    chain.double_click(element).perform()
                except StaleElementReferenceException:
                    logging.warning(
                        'edit_file: StaleElementReferenceException')
                    element = self.find_file(filename, 1)
                    chain = ActionChains(self.browser)
                else:
                    break
        else:
            chain.context_click(element).perform()
            self('file_image').click()
        self.browser.switch_to_window(self.browser.window_handles[-1])
        return ImagesPage.verify(self.browser, self.port)

    def view_geometry(self, filename, dclick=True):
        """ View geometry `filename` via double-click or context menu. """
        self('files_tab').click()
        element = self.find_file(filename)
        chain = ActionChains(self.browser)
        if dclick:  # This has had issues...
            for i in range(10):
                try:
                    chain.double_click(element).perform()
                except StaleElementReferenceException:
                    logging.warning(
                        'edit_file: StaleElementReferenceException')
                    element = self.find_file(filename, 1)
                    chain = ActionChains(self.browser)
                else:
                    break
        else:
            chain.context_click(element).perform()
            self('file_geometry').click()
        self.browser.switch_to_window(self.browser.window_handles[-1])
        return GeometryPage.verify(self.browser, self.port)

    def delete_file(self, filename, confirm=True):
        """ Delete `filename`. """
        self('files_tab').click()
        element = self.find_file(filename)
        chain = ActionChains(self.browser)
        chain.context_click(element).perform()
        time.sleep(0.5)
        self('file_delete').click()
        time.sleep(0.5)
        page = ConfirmationPage(self)
        if confirm:
            page.click_ok()
        else:
            page.click_cancel()

    def delete_files(self, file_paths, confirm=True):
        """ Delete all the files in the list `file_paths` """

        # need select all the files given in file_paths
        self('files_tab').click()
        for filename in file_paths:
            element = self.find_file(filename)
            chain = ActionChains(self.browser)
            #Mac OSX does not use CONTROL key
            if sys.platform == 'darwin':
                chain.key_down(Keys.SHIFT).click(element).key_up(
                    Keys.SHIFT).perform()
            else:
                chain.key_down(Keys.CONTROL).click(element).key_up(
                    Keys.CONTROL).perform()

        self('files_tab').click()
        self('file_menu').click()
        self('delete_files_button').click()
        page = ConfirmationPage(self)
        if confirm:
            page.click_ok()
        else:
            page.click_cancel()

    def expand_folder(self, filename):
        """ Expands `filename`. """
        self('files_tab').click()
        xpath = "//div[@id='ftree_pane']//a[(@path='/%s')]/../ins" % filename
        element = WebDriverWait(
            self.browser,
            TMO).until(lambda browser: browser.find_element_by_xpath(xpath))
        element.click()
        time.sleep(1)  # Wait for cute animation.

    def rename_file(self, old, new):
        """ Rename `old` to `new`. """
        self('files_tab').click()
        element = self.find_file(old)
        chain = ActionChains(self.browser)
        chain.context_click(element).perform()
        self('file_rename').click()
        prompt = ValuePrompt(self.browser, self.port)
        prompt.set_value(new)

    def toggle_files(self, filename=None):
        """ Toggle display of hidden files.
            Use context menu of `filename` if provided,
            otherwise use the context menu of the file tree pane.
        """
        self('files_tab').click()
        time.sleep(0.5)
        if filename:
            element = self.find_file(filename)
            chain = ActionChains(self.browser)
            chain.context_click(element).perform()
            time.sleep(0.5)
            self('file_toggle').click()
            time.sleep(0.5)
        else:
            element = self.browser.find_element(By.ID, 'ftree_pane')
            chain = ActionChains(self.browser)
            chain.context_click(element).perform()
            time.sleep(0.5)
            self('ftree_toggle_files_button').click()
            time.sleep(0.5)

    def commit_project(self, comment='no comment'):
        """ Commit current project. """
        self('project_menu').click()
        self('commit_button').click()
        page = ValuePrompt(self.browser, self.port)
        page.set_value(comment)
        NotifierPage.wait(self)

    def revert_project(self):
        """ Revert current project. """
        self('project_menu').click()
        self('revert_button').click()
        page = ConfirmationPage(self)
        page.click_ok()
        return WorkspacePage.verify(self.browser, self.port)

    def reload_project(self):
        """ Reload current project. """
        self('project_menu').click()
        self('reload_button').click()
        WorkspacePage.verify(self.browser, self.port)

    def get_objects_attribute(self, attribute, visible=False):
        """ Return list of `attribute` values for all objects. """
        WebDriverWait(self.browser, TMO).until(
            lambda browser: browser.find_element(By.ID, 'otree_pane'))
        object_elements = self.browser.find_elements(*self.locators["objects"])
        values = []
        for element in object_elements:
            if not visible or element.is_displayed():
                values.append(element.get_attribute(attribute))
        return values

    def select_objects_view(self, tree_name):
        """ Select the object tree view ('Components' or 'Workflow)'. """
        self('objects_selector').value = tree_name

    def select_object(self, component_name):
        """ Select `component_name`. """
        self('objects_tab').click()
        xpath = "//div[@id='otree_pane']//li[(@path='%s')]//a" % component_name
        element = WebDriverWait(
            self.browser,
            TMO).until(lambda browser: browser.find_element_by_xpath(xpath))
        element.click()

    def expand_object(self, component_name):
        """ Expands `component_name`. """
        self('objects_tab').click()
        xpath = "//div[@id='otree_pane']//li[(@path='%s')]//ins" % component_name
        element = WebDriverWait(
            self.browser,
            TMO).until(lambda browser: browser.find_element_by_xpath(xpath))
        element.click()
        time.sleep(1)  # Wait for cute animation.

    def show_dataflow(self, component_name=None):
        """ Show dataflow of `component_name`. """
        if component_name is None:
            self('dataflow_tab').click()
            return

        self('objects_tab').click()
        xpath = "//div[@id='otree_pane']//li[(@path='%s')]//a" % component_name
        element = WebDriverWait(
            self.browser,
            TMO).until(lambda browser: browser.find_element_by_xpath(xpath))
        element.click()
        time.sleep(0.5)
        # Try to recover from context menu not getting displayed.
        for retry in range(3):
            chain = ActionChains(self.browser)
            chain.context_click(element).perform()
            try:
                self('obj_dataflow').click()
                break
            except TimeoutException:
                if retry >= 2:
                    raise

    def show_workflow(self, component_name=None):
        """ Show workflow of `component_name`. """
        if component_name is None:
            self('workflow_tab').click()
            return

        self('objects_tab').click()
        xpath = "//div[@id='otree_pane']//li[(@path='%s')]//a" % component_name
        element = WebDriverWait(
            self.browser,
            TMO).until(lambda browser: browser.find_element_by_xpath(xpath))
        element.click()
        time.sleep(0.5)
        # Try to recover from context menu not getting displayed.
        for retry in range(3):
            chain = ActionChains(self.browser)
            chain.context_click(element).perform()
            try:
                self('obj_workflow').click()
                break
            except TimeoutException:
                if retry >= 2:
                    raise

    def show_properties(self):
        """ Display properties. """
        # This has had some odd failures where the tab is highlighted as if
        # hovering over it, yet the Library tab is still the selected one.
        for retry in range(5):
            try:
                self('properties_tab').click()
                WebDriverWait(
                    self.browser,
                    1).until(lambda browser: self('props_header').is_visible)
            except TimeoutException:
                if retry:
                    logging.warning('TimeoutException in show_properties')
            else:
                break
        else:
            raise RuntimeError('Too many TimeoutExceptions')

    def get_properties(self, name, prefix=None):
        self.show_properties()
        self.show_dataflow()
        obj = self.get_dataflow_figure(name, prefix=prefix)
        chain = ActionChains(self.browser)
        chain.click(obj.root)
        chain.perform()
        time.sleep(0.5)
        return (self.props_header, self.props_inputs, self.props_outputs)

    def show_library(self):
        """ Display library. """
        # For some reason the first try never works, so the wait is set
        # low and we expect to retry at least once.
        for retry in range(5):
            try:
                self('library_tab').click()
                WebDriverWait(
                    self.browser,
                    1).until(lambda browser: self('library_search').is_visible)
            except TimeoutException:
                if retry:
                    logging.warning('TimeoutException in show_library')
            else:
                break
        else:
            raise RuntimeError('Too many TimeoutExceptions')

    def set_library_filter(self, filter):
        """ Set the search filter text. """
        for retry in range(3):  # This has had issues...
            try:
                self.library_search = filter + Keys.RETURN
            except StaleElementReferenceException:
                logging.warning('set_library_filter:'
                                ' StaleElementReferenceException')
            else:
                break
        time.sleep(1)  # Wait for display update.

    def clear_library_filter(self):
        """ Clear the search filter via the 'X' button. """
        self('library_clear').click()
        time.sleep(0.5)  # Wait for display update.

    def get_object_types(self):
        """ Return displayed object types. """
        xpath = "//table[(@id='objtypetable')]//td"
        return [
            element.text
            for element in self.browser.find_elements(By.XPATH, xpath)
        ]

    def get_library_searches(self):
        """ Return stored library search terms. """
        searches = []

        self.library_search = 'searches'
        for menu in self.browser.find_elements(By.CLASS_NAME,
                                               'ui-autocomplete'):
            items = menu.find_elements(By.CLASS_NAME, 'ui-menu-item')
            searches = [item.text for item in items]
            if len(searches) > 0 and searches[0] == 'In Project':
                break

        self.clear_library_filter()
        return searches

    def get_library_item(self, item_name):
        """ Return element for library item `item_name`. """
        xpath = "//table[(@id='objtypetable')]//td[(@modpath='%s')]" % item_name
        library_item = WebDriverWait(
            self.browser,
            TMO).until(lambda browser: browser.find_element_by_xpath(xpath))
        for retry in range(3):
            try:
                WebDriverWait(
                    self.browser,
                    TMO).until(lambda browser: library_item.is_displayed())
            except StaleElementReferenceException:
                if retry < 2:
                    logging.warning('get_library_item:'
                                    ' StaleElementReferenceException')
                    library_item = WebDriverWait(self.browser, TMO).until(
                        lambda browser: browser.find_element_by_xpath(xpath))
                else:
                    raise
            else:
                break
        return library_item

    def view_library_item_docs(self, item_name):
        chain = ActionChains(self.browser)
        current_windows = set(self.browser.window_handles)
        self.show_library()
        item = self.get_library_item(item_name)
        chain.context_click(item).perform()
        self("library_item_docs").click()
        new_windows = set(self.browser.window_handles) - current_windows
        docs_window = list(new_windows)[0]

        return docs_window

    def add_library_item_to_dataflow(self,
                                     item_name,
                                     instance_name,
                                     target_name=None,
                                     check=True,
                                     offset=None,
                                     prefix=None,
                                     args=None):
        """ Add component `item_name`, with name `instance_name`. """
        library_item = self.get_library_item(item_name)

        if target_name:
            target = self.get_dataflow_figure(
                target_name).get_drop_targets()[0]
            offset = offset or (2, 2)  # top left corner of target content area
        else:
            xpath = "//*[@id='-dataflow']"
            target = WebDriverWait(self.browser, TMO).until(
                lambda browser: browser.find_element_by_xpath(xpath))
            offset = offset or (50, 50)

        for retry in range(3):
            try:
                chain = ActionChains(self.browser)
                if False:
                    chain.drag_and_drop(library_item, target)
                else:
                    chain.click_and_hold(library_item)
                    chain.move_to_element_with_offset(target, offset[0],
                                                      offset[1])
                    chain.release(None)
                chain.perform()
            except StaleElementReferenceException:
                if retry < 2:
                    logging.warning('add_library_item_to_dataflow:'
                                    ' StaleElementReferenceException')
                    library_item = self.get_library_item(item_name)
                    target = WebDriverWait(self.browser, TMO).until(
                        lambda browser: browser.find_element_by_xpath(xpath))
                else:
                    raise
            else:
                break

        page = ArgsPrompt(self.browser, self.port)
        argc = page.argument_count()
        page.set_name(instance_name)
        if argc > 0:
            if args is not None:
                for i, arg in enumerate(args):
                    page.set_argument(i, arg)
            page.click_ok()

        # Check that the prompt is gone so we can distinguish a prompt problem
        # from a dataflow update problem.
        time.sleep(0.25)
        self.browser.implicitly_wait(1)  # We don't expect to find anything.
        try:
            eq(len(self.browser.find_elements(*page('prompt')._locator)), 0)
        finally:
            self.browser.implicitly_wait(TMO)

        retval = None
        if check:  # Check that it's been added.
            retval = WebDriverWait(
                self.browser,
                TMO).until(lambda browser: self.get_dataflow_figure(
                    instance_name, prefix))
        return retval

    def fill_slot_from_library(self, slot, classname, args=None):
        """ Fill slot with `classname` instance from library. """
        for retry in range(3):
            try:
                button = self.find_library_button(classname)
                chain = ActionChains(self.browser)
                chain.move_to_element(button)
                chain.click_and_hold(button)
                chain.move_to_element(slot.root)
                chain.release(None)
                chain.perform()
            except StaleElementReferenceException:
                if retry < 2:
                    logging.warning('fill_slot_from_library:'
                                    'StaleElementReferenceException')
                else:
                    raise
            else:
                break

        # Handle arguments for the slotted class
        page = ArgsPrompt(self.browser, self.port)
        argc = page.argument_count()
        if argc > 0:
            if args is not None:
                for i, arg in enumerate(args):
                    page.set_argument(i, arg)
            page.click_ok()

        # Check that the prompt is gone so we can distinguish a prompt problem
        # from a dataflow update problem.
        time.sleep(0.5)
        self.browser.implicitly_wait(1)  # We don't expect to find anything.
        try:
            eq(len(self.browser.find_elements(*page('prompt')._locator)), 0)
        finally:
            self.browser.implicitly_wait(TMO)

    def get_dataflow_figures(self):
        """ Return dataflow figure elements. """
        return find_dataflow_figures(self)

    def get_dataflow_figure(self, name, prefix=None, retries=5):
        """ Return :class:`DataflowFigure` for `name`. """
        return find_dataflow_figure(self, name, prefix, retries)

    def get_dataflow_component_names(self):
        """ Return names of dataflow components. """
        return find_dataflow_component_names(self)

    def connect(self, src, dst):
        """ Return :class:`ConnectionsPage` for connecting `src` to `dst`. """
        chain = ActionChains(self.browser)
        chain.click_and_hold(src.output_port)
        # Using root rather than input_port since for some reason
        # even using a negative Y offset can select the parent's input.
        chain.move_to_element(dst.input_port)
        chain.release(None)
        chain.perform()
        parent, dot, srcname = src.pathname.rpartition('.')
        parent, dot, dstname = dst.pathname.rpartition('.')
        editor_id = 'ConnectionsFrame-%s' % (parent)
        editor_id = editor_id.replace('.', '-')
        return ConnectionsPage(self.browser, self.port, (By.ID, editor_id))

    def replace(self, name, classname, confirm=True):
        """ Replace `name` with an instance of `classname`. """
        library_item = self.get_library_item(classname)
        target = self.get_dataflow_figure(name).root

        chain = ActionChains(self.browser)
        chain.click_and_hold(library_item)
        chain.move_to_element(target)
        chain.release(None)
        chain.perform()

        dialog = ConfirmationPage(self)
        if confirm:
            dialog.click_ok()
        else:
            dialog.click_cancel()

    def add_object_to_workflow(self, obj_path, target_name):
        """ Add `obj_path` object to `target_name` in workflow. """
        for retry in range(3):
            try:
                items = obj_path.split('.')
                parent = items[:-1]
                comp = items[-1]
                obj = self.get_dataflow_figure(comp, parent)

                target = self.find_object_button(target_name)

                chain = ActionChains(self.browser)
                chain.drag_and_drop(obj.root, target)
                chain.perform()

                #obj = self.find_object_button(obj_path)
                #workflow = self.get_workflow_figure(target_name)
                #flow_fig = workflow.flow
                #chain = ActionChains(self.browser)
                #chain.move_to_element(obj)
                #chain.click_and_hold(obj)
                #chain.move_to_element(flow_fig)
                #chain.move_by_offset(2, 1)
                #chain.release(None)
                #chain.perform()
            except StaleElementReferenceException:
                if retry < 2:
                    logging.warning('add_object_to_workflow:'
                                    ' StaleElementReferenceException')
                else:
                    raise
            else:
                break

    def add_object_to_workflow_figure(self,
                                      obj_path,
                                      target_name,
                                      target_page=None):
        """ Add `obj_path` object to `target_name` in workflow diagram. """

        if target_page is None:
            target_page = self

        for retry in range(3):
            try:
                items = obj_path.split('.')
                parent = items[:-1]
                comp = items[-1]
                obj = self.get_dataflow_figure(comp, parent)

                workflow = target_page.get_workflow_figure(target_name)
                flow_fig = workflow.flow

                chain = ActionChains(self.browser)
                chain.drag_and_drop(obj.root, flow_fig)
                chain.perform()
            except StaleElementReferenceException:
                if retry < 2:
                    logging.warning('add_object_to_workflow_figure:'
                                    ' StaleElementReferenceException')
                else:
                    raise
            else:
                break

    def get_workflow_figures(self):
        """ Return workflow figure elements. """
        return find_workflow_figures(self)

    def get_workflow_component_figures(self):
        """ Return workflow component figure elements. """
        return find_workflow_component_figures(self)

    def get_workflow_figure(self, name, prefix=None, retries=5):
        """ Return :class:`WorkflowFigure` for `name`. """
        return find_workflow_figure(self, name, prefix, retries)

    def get_workflow_component_figure(self, name, prefix=None, retries=5):
        """ Return :class:`WorkflowComponentFigure` for `name`. """
        return find_workflow_component_figure(self, name, prefix, retries)

    def show_log(self):
        """ Open log viewer.  Returns :class:`LogViewer`. """
        self('tools_menu').click()
        self('log_button').click()
        return LogViewer.verify(self.browser, self.port)

    def hide_left(self):
        toggler = self.browser.find_element_by_css_selector(
            '.ui-layout-toggler-west-open')
        toggler.click()

    def show_left(self):
        toggler = self.browser.find_element_by_css_selector(
            '.ui-layout-toggler-west-closed')
        toggler.click()

    def hide_right(self):
        toggler = self.browser.find_element_by_css_selector(
            '.ui-layout-toggler-east-open')
        toggler.click()

    def show_right(self):
        toggler = self.browser.find_element_by_css_selector(
            '.ui-layout-toggler-east-closed')
        toggler.click()

    def hide_console(self):
        toggler = self.browser.find_element_by_css_selector(
            '.ui-layout-toggler-south-open')
        toggler.click()

    def show_console(self):
        toggler = self.browser.find_element_by_css_selector(
            '.ui-layout-toggler-south-closed')
        toggler.click()

    def drag_element_to(self, element, drag_to, center):
        '''Drag one element over to another element'''
        chain = ActionChains(self.browser)
        chain.move_to_element(element)
        chain.click_and_hold(element)
        if center:
            # move to center of element
            chain.move_to_element(drag_to)
        else:
            # move to offset from top left of element
            chain.move_to_element_with_offset(drag_to, 2, 2)
        chain.perform()
        return chain

    def release(self, chain):
        '''The drop part of the ActionChain when doing drag and drop'''
        chain.release(on_element=None).perform()
        time.sleep(0.5)  # Pacing for diagram update.

    def replace_driver(self, assembly_name, driver_type):
        ''' find 'driver_type' in the library, drag and drop it on to
            the driver figure of the 'assembly_name'
        '''
        newdriver = self.find_library_button(driver_type)
        assembly = self.get_dataflow_figure(assembly_name)
        driver_element = self.get_dataflow_figure('driver')

        div = driver_element.get_drop_targets()[0]
        chain = self.drag_element_to(newdriver, div, True)
        self.check_highlighting(
            driver_element('content_area').element, True,
            "Driver's content_area")
        self.release(chain)

        # brings up a confirm dialog for replacing the existing driver.
        dialog = ConfirmationPage(assembly)
        dialog.click_ok()

    def resize_editor(self, editor):
        '''ensure that the editor is not covering the library
        (or else we cannot drag things from it!)'''
        browser = self.browser

        page_width = browser.get_window_size()['width']

        lib_tab = self('library_tab').find_element_by_xpath('..')
        lib_width = lib_tab.size['width']
        lib_position = lib_tab.location['x']

        dialog_title = editor('dialog_title').find_element_by_xpath('../..')
        dialog_width = dialog_title.size['width']
        dialog_position = dialog_title.location['x']

        # how much overlap do we have?
        overlap = lib_position - (dialog_position + dialog_width)

        if overlap < 0:  # we are overlapping
            # check to see if we have enough room to move out of the way
            if page_width < dialog_width + lib_width:
                # not enough, need to rezize the editor

                # look for the resize handle
                sibblings = dialog_title.find_elements_by_xpath('../../div')
                handle = None
                for sib in sibblings:
                    if "ui-resizable-se" in sib.get_attribute('class'):
                        handle = sib

                # do the resizing
                chain = ActionChains(browser)
                chain.click_and_hold(handle)
                # we can resize editor down to 425px, any less and we cover drop targets
                chain.move_by_offset(450 - dialog_width, 0).perform()
                # must click because release is not working. why? I do not know.
                chain.click().perform()
                chain.release(None).perform()

                # recalculate the overlap
                dialog_title = editor('dialog_title').find_element_by_xpath(
                    '../..')
                dialog_width = dialog_title.size['width']
                dialog_position = dialog_title.location['x']
                overlap = lib_position - (dialog_position + dialog_width)

            # We are good, move out!
            chain = ActionChains(browser)
            chain.click_and_hold(editor('dialog_title').element)
            chain.move_by_offset(overlap, 0).perform()
            # must click because release is not working. why? I do not know.
            chain.click().perform()
            chain.release(None).perform()

            # recalculate the overlap
            dialog_title = editor('dialog_title').find_element_by_xpath(
                '../..')
            dialog_width = dialog_title.size['width']
            dialog_position = dialog_title.location['x']
            overlap = lib_position - (dialog_position + dialog_width)

            if overlap < 0:
                # we still have a problem.
                eq(
                    True, False,
                    "Could not move or rezise the editor dialog so it is not "
                    "overlapping the library. The browser window is too small")

    def get_dataflow_fig_in_globals(self, name):
        '''Find the named dataflow fig in the global dataflow editor'''
        all_figs = self.get_dataflow_figures()
        for fig in all_figs:
            location = fig.get_parent().get_attribute('id')
            if location == "top-dataflow":
                return fig

        return None

    def put_element_on_grid(self, type_str):
        '''find 'type_str' in the Library, drag and drop it onto the grid'''
        browser = self.browser

        grid = browser.find_element_by_xpath('//div[@id="-dataflow"]')

        for retry in range(3):
            try:
                objtype = self.find_library_button(type_str)
                chain = ActionChains(browser)
                chain.click_and_hold(objtype)
                chain.move_to_element_with_offset(grid, 10, 10).perform()
            except StaleElementReferenceException:
                if retry < 2:
                    logging.warning(
                        'put_element_on_grid %s:'
                        ' StaleElementReferenceException', type_str)
                else:
                    raise
            else:
                break

        self.check_highlighting(grid, True, "Grid")
        self.release(chain)

        # deal with the modal dialog
        name = NameInstanceDialog(self).create_and_dismiss()

        # make sure it is on the grid
        self.ensure_names_in_workspace(
            [name], "Dragging '" + type_str +
            "' to grid did not produce a new element on page")

        return name

    def drag_and_drop(self, element, target, should_drop, message='Target'):
        '''Drag and drop an element onto a target'''
        chain = self.drag_element_to(element, target, True)
        chain.move_by_offset(25, 0).perform()
        time.sleep(1.0)  # give it a second to update the figure
        self.check_highlighting(target,
                                should_highlight=should_drop,
                                message=message)
        self.release(chain)

    def ensure_names_in_workspace(self, names, message=None):
        """ensures the list of element names in included in the workspace"""

        allnames = self.get_dataflow_component_names()

        # sometimes does not load all of the names for some reason.
        # Reloading seems to fix the problem
        try_reload = False
        for name in names:
            if not name in allnames:
                try_reload = True
        if try_reload:
            time.sleep(.1)
            allnames = self.get_dataflow_component_names()

        # now we will assert that the elements that we added appear on the page
        for name in names:
            eq(name in allnames, True, '%s: %s' % (message, name))

    def check_highlighting(self,
                           element,
                           should_highlight=True,
                           message='Element'):
        '''check to see that the background-color of the element is highlighted
        '''
        if 'SlotFigure' in element.get_attribute('class'):
            # a slot figure is a div containing a ul element (the context menu) and
            # one or more svg elements, each of which contains a rect and two texts
            # the last rect fill style is what we need to check for highlighting
            rect = element.find_elements_by_css_selector('svg rect')[-1]
            style = rect.get_attribute('style')
        else:
            style = element.get_attribute('style')
        highlighted = ('background-color: rgb(207, 214, 254)' in style) \
                    or ('highlighted.png' in style) \
                    or ('fill: #cfd6fe' in style)
        eq(
            highlighted, should_highlight, message +
            (' did not highlight (and should have) '
             if should_highlight else ' highlighed (and should not have) ') +
            'when dragging a dropable element to it')
Exemple #11
0
class ComponentPage(DialogPage):
    class Variable(object):
        def __init__(self, row, headers):
            self._cells = row.cells

            def getter(cls, index=0):
                return cls._cells[index]

            def setter(cls, value, index=0):
                if not cls._cells[index].editable:
                    raise AttributeError("can't set attribute")
                else:
                    cls._cells[index].value = value

            for index in range(len(headers)):
                if headers[index].value != "":
                    setattr(
                        self.__class__, headers[index].value.lower(),
                        property(partial(getter, index=index),
                                 partial(setter, index=index)))

    """ Component editor page. """

    Version = type('Enum', (), {"OLD": 1, "NEW": 2})
    As = type('Enum', (), {"GRID": 0, "ROW": 1, "VARIABLE": 2})
    SortOrder = type('Enum', (), {"ASCENDING": 0, "DESCENDING": 1})

    inputs_tab = ButtonElement((By.XPATH, "div/ul/li/a[text()='Inputs']"))
    slots_tab = ButtonElement((By.XPATH, "div/ul/li/a[text()='Slots']"))
    outputs_tab = ButtonElement((By.XPATH, "div/ul/li/a[text()='Outputs']"))
    events_tab = ButtonElement((By.XPATH, "div/ul/li/a[text()='Events']"))

    inputs = GridElement((By.ID, 'Inputs_props'))
    outputs = GridElement((By.ID, 'Outputs_props'))

    inputs_filter = InputElement((By.ID, 'Inputs_variableFilter'))
    inputs_clear = ButtonElement((By.ID, 'Inputs_clear'))

    outputs_filter = InputElement((By.ID, 'Outputs_variableFilter'))
    outputs_clear = ButtonElement((By.ID, 'Outputs_clear'))

    def __init__(self, browser, port, locator, version=Version.OLD):
        super(ComponentPage, self).__init__(browser, port, locator)
        # It takes a while for the full load to complete.
        NotifierPage.wait(self)
        self.version = version
        self._sort_order = {"inputs": 0, "outputs": 0}
        self._column_picker = None

    def get_tab_labels(self):
        """ Return a list of the tab labels. """
        elements = self.root.find_elements_by_class_name('ui-tabs-anchor')
        labels = [element.text for element in elements]
        return labels

    def set_input(self, name, value):
        """ Set input `name` to `value`. """
        self('inputs_tab').click()
        grid = self.inputs
        found = []
        for row in grid.rows:
            if row[1] == name:
                row[2] = value
                return
            found.append(row[1])
        raise RuntimeError('%r not found in inputs %s' % (name, found))

    def filter_inputs(self, filter_text):
        """ Filter out input variables from grid using `filter_text`. """
        self.inputs_filter = filter_text

    def filter_outputs(self, filter_text):
        """ Filter out output variables from grid using `filter_text`. """
        self.outputs_filter = filter_text

    def clear_inputs_filter(self):
        """ Clear input variable filter. """
        self('inputs_clear').click()

    def clear_outputs_filter(self):
        """ Clear output variable filter. """
        self('outputs_clear').click()

    def _sort_column(self, grid, column_name, sort_order, tab):
        """ Sorts the variables in column `column_name`"""
        header = [
            header for header in grid.headers if header.value == column_name
        ]
        if (not header):
            raise Exception("Grid has no column named %s" % column_name)

        header = header[0]
        if (sort_order == self.SortOrder.ASCENDING):
            while ((self._sort_order[tab] % 2) == 0):
                self._sort_order[tab] = self._sort_order[tab] + 1
                header.click()
        else:
            while ((self._sort_order[tab] % 2) != 0):
                self._sort_order[tab] = self._sort_order[tab] + 1
                header.click()

    def sort_inputs_column(self, column_name, sort_order=SortOrder.ASCENDING):
        """ Sort `column_name` in inputs grid in `sort_order` """
        self("inputs_tab").click()
        self._sort_column(self.inputs, column_name, sort_order, "inputs")

    def sort_outputs_column(self, column_name, sort_order=SortOrder.ASCENDING):
        """ Sort `column_name` in outputs grid in `sort_order` """
        self("outputs_tab").click()
        self._sort_column(self.outputs, column_name, sort_order, "outputs")

    def get_events(self):
        """ Return events grid. """
        self('events_tab').click()
        return self.events

    def show_inputs(self):
        """switch to inputs tab"""
        self('inputs_tab').click()

    def show_outputs(self):
        """switch to outputs tab"""
        self('outputs_tab').click()

    def show_slots(self):
        """switch to slots tab"""
        self('slots_tab').click()

    def toggle_column_visibility(self, column_name):
        self._toggle_column_visibility(self._active_grid, column_name)

    @property
    def _active_grid(self):
        if self.inputs.displayed:
            return self.inputs

        elif self.outputs.displayed:
            return self.outputs

    def _toggle_column_visibility(self, grid, column_name):
        if not self._column_picker:
            self._column_picker = self._get_column_picker(grid)

        elif not self._column_picker.displayed:
            self._column_picker = self._get_column_picker(grid)

        self._column_picker.get_option(column_name).click()

    def _get_column_picker(self, grid):
        grid.headers[0].context_click()
        column_pickers = [
            GridColumnPicker(self.browser, element) for element in
            self.browser.find_elements(By.CLASS_NAME, "slick-columnpicker")
        ]
        for column_picker in column_pickers:
            if column_picker.displayed:
                return column_picker

    def get_inputs(self, return_type=None):
        """ Return inputs grid. """
        self('inputs_tab').click()
        return self._get_variables(self.inputs, return_type)

    def get_outputs(self, return_type=None):
        """ Return outputs grid. """
        self('outputs_tab').click()
        return self._get_variables(self.outputs, return_type)

    def get_input(self, name, return_type=None):
        """ Return first input variable with `name`. """
        self('inputs_tab').click()
        return self._get_variable(name, self.inputs, return_type)

    def get_output(self, name, return_type=None):
        """ Return first output variable with `name`. """
        self('outputs_tab').click()
        return self._get_variable(name, self.outputs, return_type)

    def _get_variables(self, grid, return_type):
        if (return_type == self.As.GRID or self.version == self.Version.OLD):
            return grid

        headers = grid.headers
        rows = grid.rows

        return [self.Variable(row, headers) for row in rows]

    def _get_variable(self, name, grid, return_type):
        found = []
        for row in grid.rows:
            if row[1] == name:
                if (return_type == self.As.ROW
                        or self.version == self.Version.OLD):
                    return row
                else:
                    return self.Variable(row, grid.headers)
            found.append(row[1])
        raise RuntimeError('%r not found in inputs %s' % (name, found))