Beispiel #1
0
 def __init__(self, app, size, caption, message, buttons):
     DialogBox.__init__(self, app, size, caption)
     if hasattr(size, 'getSize'):
         size = size.getSize(app)
     textFont = app.screenManager.fonts.labelFont
     lines = wrapWords(app, message, textFont, size[0])
     y = 5
     for text in lines:
         textPos = Location(DialogBoxAttachedPoint(self, (0, y), 'midtop'),
                            'midtop')
         self.elements.append(TextElement(app, text, textFont, textPos))
         y += textFont.getHeight(app)
     buttonFont = app.screenManager.fonts.captionFont
     for x in range(0, len(buttons)):
         dialogResult = buttons[x]
         location = Location(
             DialogBoxAttachedPoint(self,
                                    (0, -textFont.getHeight(self.app) / 2),
                                    'midbottom'), 'midbottom')
     buttonFont = app.screenManager.fonts.captionFont
     for x in range(0, len(buttons)):
         dialogResult = buttons[x]
         location = Location(
             ((x + 1) * size[0] / (len(buttons) + 1), size[1] * 2 / 3),
             'midtop')
         button = TextButton(app, location, DialogResult.text[dialogResult],
                             buttonFont, (255, 0, 0), (255, 100, 200))
         button._mbResult = dialogResult
         button.onClick.addListener(self._setResultAndClose)
         self.elements.append(button)
     self.result = None
Beispiel #2
0
class DialogPage(BasePageObject):
    """ Base for various dialog pages. """

    dialog_title = TextElement((By.XPATH, '../div/span'))
    close_button = ButtonElement((By.XPATH, '../div/a/span'))
    dialog_resize = ButtonElement((By.XPATH, '../div[contains(@class, "ui-resizable-se")]'))

    def __init__(self, browser, port, locator):
        root = WebDriverWait(browser, TMO).until(
                   lambda browser: browser.find_element(*locator))
        super(DialogPage, self).__init__(browser, port, root)

    @property
    def is_visible(self):
        """ True if dialog is visible. """
        return self('dialog_title').is_visible

    def close(self):
        """ Close dialog. """
        # Ensure close button is on screen.
        width = self.browser.get_window_size()['width']
        x = self('close_button').location['x']
        shift = width - x
        if shift < 0:
            self.move(shift, 0)
        self('close_button').click()

    def move(self, delta_x, delta_y):
        """ Move dialog. """
        chain = ActionChains(self.browser)
        chain.click_and_hold(self('dialog_title').element)
        chain.move_by_offset(int(delta_x), int(delta_y))
        chain.release(None)
        chain.perform()
        time.sleep(0.5)
Beispiel #3
0
class DialogPage(BasePageObject):
    """ Base for various dialog pages. """

    dialog_title = TextElement((By.XPATH, '../div/span'))
    close_button = ButtonElement((By.XPATH, '../div/a'))

    def __init__(self, browser, port, locator):
        root = WebDriverWait(
            browser, TMO).until(lambda browser: browser.find_element(*locator))
        super(DialogPage, self).__init__(browser, port, root)

    @property
    def is_visible(self):
        """ True if dialog is visible. """
        return self('dialog_title').is_visible

    def close(self):
        """ Close dialog. """
        self('close_button').click()

    def move(self, delta_x, delta_y):
        """ Move dialog. """
        chain = ActionChains(self.browser)
        chain.click_and_hold(self('dialog_title').element)
        chain.move_by_offset(delta_x, delta_y)
        chain.release(None)
        chain.perform()
Beispiel #4
0
class BootstrapModal(BasePageObject):
    modal_title = TextElement((By.XPATH, "div[@class='modal-header']/h3"))
    close_button = TextElement((By.XPATH, "div[@class='modal-header']/button"))
    """ Base for Twitter Bootstrap modals """
    def __init__(self, browser, port, locator):
        root = WebDriverWait(
            browser, TMO).until(lambda browser: browser.find_element(*locator))
        super(BootstrapModal, self).__init__(browser, port, root)

    @property
    def is_visible(self):
        """ True if modal is visible. """
        return self('modal_title').is_visible

    def close(self):
        """ Close modal. """
        self('close_button').click()
class EdgeNode(BasePageObject):
    """ An edge node in the Edges tree. """

    name = TextElement((By.XPATH, './a'))
    viz = CheckboxElement((By.XPATH, './div/span/input[@value=1]'))
    grd = CheckboxElement((By.XPATH, './div/span/input[@value=16]'))
    ori = CheckboxElement((By.XPATH, './div/span/input[@value=8]'))

    def __init__(self, browser, port, root):
        super(EdgeNode, self).__init__(browser, port, root)
class FaceNode(BasePageObject):
    """ A face node in the Faces tree. """

    name = TextElement((By.XPATH, './a'))
    viz = CheckboxElement((By.XPATH, './div/span/input[@value=1]'))
    grd = CheckboxElement((By.XPATH, './div/span/input[@value=32]'))
    trn = CheckboxElement((By.XPATH, './div/span/input[@value=2]'))

    def __init__(self, browser, port, root):
        super(FaceNode, self).__init__(browser, port, root)
Beispiel #7
0
class ProjectInfoPage(ProjectPage):
    """ Page displaying project information. """

    project_header = TextElement((By.XPATH, '/html/body/div/div[2]/h2'))
    update_button = ButtonElement((By.XPATH,
                                   '/html/body/div/div[2]/form/input'))
    delete_button = ButtonElement((By.XPATH,
                                   '/html/body/div/div[2]/form[2]/input[2]'))
    load_button = ButtonElement((By.XPATH,
                                 '/html/body/div/div[2]/form[3]/input[2]'))
    save_button = ButtonElement((By.XPATH,
                                 '/html/body/div/div[2]/form[4]/input[2]'))
    back_button = ButtonElement((By.LINK_TEXT, 'Back to Projects'))
    logout_button = ButtonElement((By.LINK_TEXT, 'Exit'))

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

    @staticmethod
    def project_title(name):
        """ Return page title for project `name`. """
        return '%s %s' % (ProjectInfoPage.title_prefix, name)

    def load_project(self):
        """ Clicks the 'load' button. Returns :class:`WorkspacePage`. """
        self('load_button').click()
        from workspace import WorkspacePage
        return WorkspacePage.verify(self.browser, self.port)

    def delete_project(self):
        """ Clicks the 'delete' button. Returns :class:`ProjectsListPage`. """
        self('delete_button').click()
        return ProjectsListPage.verify(self.browser, self.port)

    def go_to_projects_page(self):
        """ Clicks the 'back' button. Returns :class:`ProjectsListPage`. """
        self('back_button').click()
        return ProjectsListPage.verify(self.browser, self.port)

    def logout(self):
        """
        Clicks the 'logout' button. Returns :class:`LoginPage`.

        ..warning::

            Since login isn't done anymore, this actually results in
            exiting the server.

        """
        self('logout_button').click()
        from login import LoginPage
        return LoginPage.verify(self.browser, self.port)
Beispiel #8
0
    def __init__(self,
                 app,
                 pos,
                 text,
                 font,
                 colour,
                 initValue=False,
                 hotkey=None,
                 style='circle',
                 fillColour=None):
        super(CheckBox, self).__init__(app)

        self.pos = pos
        self.font = font

        if hasattr(pos, 'apply'):
            self.text = TextElement(
                app, ' ' + text, font,
                Location(
                    AttachedPoint(ScaledSize(self._getBoxSize()[0] / 5, 2),
                                  self._getBoxRect, 'midright'), 'midleft'),
                colour)
        else:
            self.text = TextElement(
                app, ' ' + text, font,
                Location((pos[0] + self._getBoxSize()[0],
                          pos[1] - self._getBoxSize()[0] / 10), 'topleft'),
                colour)

        self.value = initValue
        self.colour = colour
        if fillColour is None:
            self.fillColour = tuple((256 * 3 + i) / 4 for i in colour)
        else:
            self.fillColour = fillColour
        self.hotkey = hotkey
        self.style = style
        self.onValueChanged = Event()
Beispiel #9
0
class LogViewer(DialogPage):
    """ Log message viewer. """

    data = TextElement((By.ID, 'logdata'))

    # Context menu.
    pause_button = ButtonElement((By.XPATH, 'ul/li[1]'))
    popout_button = ButtonElement((By.XPATH, 'ul/li[2]'))
    filter_button = ButtonElement((By.XPATH, 'ul/li[3]'))
    copy_button = ButtonElement((By.XPATH, 'ul/li[4]'))
    clear_button = ButtonElement((By.XPATH, 'ul/li[6]'))

    def __init__(self, browser, port):
        super(LogViewer, self).__init__(browser, port, (By.ID, 'logframe'))
        time.sleep(1)  # Wait for display to load-up.

    def clear(self):
        """ Clear display. """
        self._context_click('clear_button')

    def filter(self):
        """ Return filtering dialog. """
        self._context_click('filter_button')
        return FilterDialog.verify(self.browser, self.port)

    def pause(self):
        """ Pause/resume display. """
        return self._context_click('pause_button')

    def popout(self):
        """ Pop-out display to separate window. """
        return self._context_click('popout_button')

    def _context_click(self, name):
        """ Display context menu. """
        chain = ActionChains(self.browser)
        # Just using center of self.root had an isolated 'overlap' failure.
        chain.move_to_element_with_offset(self('data').element, 2, 2)
        chain.context_click(None)
        chain.perform()
        time.sleep(0.5)  # Wait for menu to display.
        text = self(name).text
        self(name).click()
        time.sleep(0.5)  # Wait to pop-down.
        return text

    def get_messages(self):
        """ Return messages as a list. """
        time.sleep(0.5)  # Wait for update.
        return self.data.split('\n')
Beispiel #10
0
class DialogPage(BasePageObject):
    """ Base for various dialog pages. """

    dialog_title = TextElement((By.XPATH, '../div/span'))
    close_button = ButtonElement((By.XPATH, '../div/a'))

    def __init__(self, browser, port, locator):
        root = WebDriverWait(
            browser, TMO).until(lambda browser: browser.find_element(*locator))
        super(DialogPage, self).__init__(browser, port, root)

    def close(self):
        """ Close dialog. """
        self('close_button').click()
Beispiel #11
0
class ConfirmationPage(BasePageObject):
    """ Overlay displayed by ``openmdao.Util.confirm()``. """

    prompt = TextElement((By.ID, 'confirm-prompt'))
    ok_button = ButtonElement((By.ID, 'confirm-ok'))
    cancel_button = ButtonElement((By.ID, 'confirm-cancel'))

    def __init__(self, parent):
        super(ConfirmationPage, self).__init__(parent.browser, parent.port)

    def click_ok(self):
        self('ok_button').click()

    def click_cancel(self):
        self('cancel_button').click()
Beispiel #12
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))
Beispiel #13
0
class ValuePrompt(BasePageObject):
    """ Overlay displayed by ``openmdao.Util.promptForValue()``. """

    prompt = TextElement((By.ID, 'get-value-prompt'))
    value = InputElement((By.ID, 'get-value-input'))
    ok_button = ButtonElement((By.ID, 'get-value-ok'))
    cancel_button = ButtonElement((By.ID, 'get-value-cancel'))

    def set_value(self, value):
        self.value = value + Keys.RETURN

    def set_text(self, text):
        self.value = text

    def click_ok(self):
        self('ok_button').click()

    def click_cancel(self):
        self('cancel_button').click()
Beispiel #14
0
class WorkflowFigure(BasePageObject):
    """ Represents elements within a workflow figure. """

    # parts of the WorkflowFigure div. Nothing here yet. Not sure
    #   if needed
    title_bar = TextElement((By.CLASS_NAME, 'WorkflowFigureTitleBar'))

    # Context menu. Not needed yet but will need later

    @property
    def pathname(self):
        """ Pathname of this component. """
        return self._pathname

    @pathname.setter
    def pathname(self, path):
        self._pathname = path

    @property
    def background_color(self):
        """ Figure background-color property. """
        return self.root.value_of_css_property('background-color')
Beispiel #15
0
class ArgsPrompt(BasePageObject):
    """ Dialog displayed by ``openmdao.Util.promptForArgs()``. """

    prompt = TextElement((By.ID, 'get-args-prompt'))
    name = InputElement((By.ID, 'get-args-name'))
    ok_button = ButtonElement((By.ID, 'get-args-ok'))
    cancel_button = ButtonElement((By.ID, 'get-args-cancel'))

    def set_name(self, value):
        self.name = value + Keys.RETURN

    def set_text(self, text):
        self.name = text

    def set_argument(self, index, text):
        table = self.browser.find_element(By.ID, 'get-args-tbl')
        arg_inputs = table.find_elements(By.XPATH, 'tbody/tr/td/input')
        arg_inputs[index].send_keys(text)

    def click_ok(self):
        self('ok_button').click()

    def click_cancel(self):
        self('cancel_button').click()
Beispiel #16
0
class ArgsPrompt(BasePageObject):
    """ Dialog displayed by ``openmdao.Util.promptForArgs()``. """

    prompt = TextElement((By.ID, 'get-args-prompt'))
    name = InputElement((By.ID, 'get-args-name'))
    ok_button = ButtonElement((By.ID, 'get-args-ok'))
    cancel_button = ButtonElement((By.ID, 'get-args-cancel'))

    def set_name(self, value):
        self.name = value + Keys.RETURN

    def set_text(self, text):
        self.name = text

    def set_argument(self, index, text):
        table = self.browser.find_element(By.ID, 'get-args-tbl')
        arg_inputs = table.find_elements(By.XPATH, 'tbody/tr/td/input')
        arg_inputs[index].send_keys(text)

    def argument_count(self):
        self.browser.implicitly_wait(1)
        try:
            table = self.browser.find_elements_by_css_selector('#get-args-tbl')
        finally:
            self.browser.implicitly_wait(TMO)
        if table:
            arg_inputs = table[0].find_elements(By.XPATH, 'tbody/tr/td/input')
            return len(arg_inputs)
        else:
            return 0

    def click_ok(self):
        self('ok_button').click()

    def click_cancel(self):
        self('cancel_button').click()
Beispiel #17
0
class DataflowFigure(BasePageObject):
    """ Represents elements within a dataflow figure. """

    name = TextElement((By.CLASS_NAME, 'DataflowFigureHeader'))

    top_left = GenericElement((By.CLASS_NAME, 'DataflowFigureTopLeft'))
    header = GenericElement((By.CLASS_NAME, 'DataflowFigureHeader'))
    top_right = ButtonElement((By.CLASS_NAME, 'DataflowFigureTopRight'))
    content_area = GenericElement((By.CLASS_NAME, 'DataflowFigureContentArea'))

    bottom_left = GenericElement((By.CLASS_NAME, 'DataflowFigureBottomLeft'))
    bottom_right = GenericElement((By.CLASS_NAME, 'DataflowFigureBottomRight'))
    footer = GenericElement((By.CLASS_NAME, 'DataflowFigureFooter'))

    # Context menu.
    edit_button = ButtonElement((By.XPATH, "../div/a[text()='Edit']"))
    properties_button = ButtonElement(
        (By.XPATH, "../div/a[text()='Properties']"))
    evaluate_button = ButtonElement((By.XPATH, "../div/a[text()='Evaluate']"))
    run_button = ButtonElement((By.XPATH, "../div/a[text()='Run']"))
    connections_button = ButtonElement(
        (By.XPATH, "../div/a[text()='Edit Data Connections']"))
    show_dataflows = ButtonElement(
        (By.XPATH, "../div/a[text()='Show Data Connections']"))
    hide_dataflows = ButtonElement(
        (By.XPATH, "../div/a[text()='Hide Data Connections']"))
    show_driverflows = ButtonElement(
        (By.XPATH, "../div/a[text()='Show Driver Connections']"))
    hide_driverflows = ButtonElement(
        (By.XPATH, "../div/a[text()='Hide Driver Connections']"))
    disconnect_button = ButtonElement(
        (By.XPATH, "../div/a[text()='Disconnect']"))
    remove_button = ButtonElement((By.XPATH, "../div/a[text()='Remove']"))

    # Port context menus.
    edit_connections = ButtonElement(
        (By.XPATH, "../div/a[text()='Edit Connections']"))
    edit_passthroughs = ButtonElement(
        (By.XPATH, "../div/a[text()='Edit Passthroughs']"))
    edit_driver = ButtonElement((By.XPATH, "../div/a[text()='Edit Driver']"))

    def __init__(self, browser, port, root):
        super(DataflowFigure, self).__init__(browser, port, root)
        self._pathname = None

    @property
    def pathname(self):
        """ Pathname of this component. """
        if self._pathname is None:
            # Much slower than if explicitly set.
            parent = self('header').find_element_by_xpath('..')
            fig_id = parent.get_attribute('id')
            script = "return jQuery('#" + fig_id + "').data('pathname')"
            self._pathname = self.browser.execute_script(script)
        return self._pathname

    @pathname.setter
    def pathname(self, path):
        self._pathname = path

    @property
    def input_port(self):
        """ Input port element, `pathname` must be set previously. """
        return self.root.find_element_by_id(self.pathname + '-input')

    @property
    def output_port(self):
        """ Output port element, `pathname` must be set previously. """
        return self.root.find_element_by_id(self.pathname + '-output')

    @property
    def border(self):
        """ Figure border property. """
        return self.root.value_of_css_property('border')

    @property
    def state(self):
        """ Exec state of this component. """
        border = self.border
        if ('rgb(255, 0, 0)' in border):
            return 'INVALID'
        elif ('rgb(0, 255, 0)' in border):
            return 'VALID'
        elif ('rgb(0, 0, 255)' in border):
            return 'RUNNING'
        else:
            return 'UNKNOWN'

    @property
    def background_color(self):
        """ Figure background-color property. """
        return self.root.value_of_css_property('background-color')

    @property
    def coords(self):
        """ Figure (left, top). """
        left = self.root.value_of_css_property('left')
        left = int(left[0:-2])  # Drop 'px'.
        top = self.root.value_of_css_property('top')
        top = int(top[0:-2])  # Drop 'px'.
        return (left, top)

    def editor_page(self,
                    double_click=True,
                    base_type='Component',
                    version=ComponentPage.Version.OLD):
        """ Return :class:`ComponentPage` for this component. """
        chain = ActionChains(self.browser)
        if double_click:
            chain.double_click(self.root).perform()
        else:
            self._context_click('edit_button')
        editor_id = 'ObjectFrame_%s' % self.pathname.replace('.', '-')
        chain.release(None).perform()
        if base_type == 'Assembly':
            return AssemblyPage(self.browser, self.port, (By.ID, editor_id))
        elif base_type == 'Driver':
            return DriverPage(self.browser, self.port, (By.ID, editor_id))
        elif base_type == 'ImplicitComponent':
            return ImplicitComponentPage(self.browser,
                                         self.port, (By.ID, editor_id),
                                         version=version)
        else:
            return ComponentPage(self.browser,
                                 self.port, (By.ID, editor_id),
                                 version=version)

    def properties_page(self):
        """ Return :class:`PropertiesPage` for this component. """
        self._context_click('properties_button')
        props_id = '%s-properties' % self.pathname.replace('.', '-')
        return PropertiesPage(self.browser, self.port, (By.ID, props_id))

    def connections_page(self):
        """ Return :class:`ConnectionsPage` for this component. """
        self._context_click('connections_button')
        frame_id = 'ConnectionsFrame-%s' % self.pathname.replace('.', '-')
        return ConnectionsPage(self.browser, self.port, (By.ID, frame_id))

    def input_edit_driver(self, driver_pathname):
        """ Return :class:`DriverPage` associated with the input port. """
        chain = ActionChains(self.browser)
        chain.context_click(self.input_port).perform()
        time.sleep(0.5)
        self('edit_driver').click()
        editor_id = 'ObjectFrame_%s' % driver_pathname.replace('.', '-')
        return DriverPage(self.browser, self.port, (By.ID, editor_id))

    def output_edit_driver(self, driver_pathname):
        """ Return :class:`DriverPage` associated with the output port. """
        # FIXME: can't get response from context click.
        chain = ActionChains(self.browser)
        chain.context_click(self.output_port).perform()
        time.sleep(0.5)
        self('edit_driver').click()
        editor_id = 'ObjectFrame_%s' % driver_pathname.replace('.', '-')
        return DriverPage(self.browser, self.port, (By.ID, editor_id))

    def evaluate(self):
        """ Evaluate this component. (only available for ImplicitComponent) """
        self._context_click('evaluate_button')

    def run(self):
        """ Run this component. """
        self._context_click('run_button')

    def disconnect(self):
        """ Disconnect this component. """
        self._context_click('disconnect_button')

    def remove(self):
        """ Remove this component. """
        self._context_click('remove_button')

    def display_dataflows(self, show):
        """ Show/hide data flows. """
        if show:
            self._context_click('show_dataflows')
        else:
            self._context_click('hide_dataflows')

    def display_driverflows(self, show):
        """ Show/hide driver flows. """
        if show:
            self._context_click('show_driverflows')
        else:
            self._context_click('hide_driverflows')

    def _context_click(self, name):
        """ Display context menu. """
        chain = ActionChains(self.browser)
        # Default is centered which causes problems in some contexts.
        # Offset is apparently limited, (20, 20) had problems.
        chain.move_to_element_with_offset(self.root, 15, 15)
        chain.context_click(None)
        chain.perform()
        time.sleep(0.5)
        self(name).click()

    def get_pathname(self):
        '''Get the OpenMDAO pathname for a DataflowFigure'''
        figid = self.root.get_attribute('id')  # get the ID of the element here
        script = "return jQuery('#" + figid + "').data('pathname')"
        return self.browser.execute_script(script)

    def get_parent(self):
        '''get the parent element of this DataflowFigure'''
        return self.root.find_element_by_xpath("..")

    def get_drop_targets(self):
        '''Dataflow figures are made of many subelements. This function
        returns a list of them so that we can try dropping on any one
        of the elements
        '''
        # return [self(area).element for area in \
        #        ['top_left','header','top_right', 'content_area',
        #         'bottom_left', 'footer', 'bottom_right']]

        # add back 'top_left' 'bottom_left' at some point. right now that test fails
        arr = ['content_area', 'header', 'footer', 'bottom_right', 'top_right']
        return [self(area).element for area in arr]
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')
Beispiel #19
0
class DataflowFigure(BasePageObject):
    """ Represents elements within a dataflow figure. """

    name = TextElement((By.CLASS_NAME, 'DataflowFigureHeader'))

    top_left = GenericElement((By.CLASS_NAME, 'DataflowFigureTopLeft'))
    header = GenericElement((By.CLASS_NAME, 'DataflowFigureHeader'))
    top_right = ButtonElement((By.CLASS_NAME, 'DataflowFigureTopRight'))
    content_area = GenericElement((By.CLASS_NAME, 'DataflowFigureContentArea'))

    bottom_left = GenericElement((By.CLASS_NAME, 'DataflowFigureBottomLeft'))
    bottom_right = GenericElement((By.CLASS_NAME, 'DataflowFigureBottomRight'))
    footer = GenericElement((By.CLASS_NAME, 'DataflowFigureFooter'))

    # Context menu.
    edit_button = ButtonElement((By.XPATH, "../div/a[text()='Edit']"))
    properties_button = ButtonElement(
        (By.XPATH, "../div/a[text()='Properties']"))
    run_button = ButtonElement((By.XPATH, "../div/a[text()='Run']"))
    connections_button = ButtonElement(
        (By.XPATH, "../div/a[text()='Edit Data Connections']"))
    show_dataflows = ButtonElement(
        (By.XPATH, "../div/a[text()='Show Data Connections']"))
    hide_dataflows = ButtonElement(
        (By.XPATH, "../div/a[text()='Hide Data Connections']"))
    show_driverflows = ButtonElement(
        (By.XPATH, "../div/a[text()='Show Driver Connections']"))
    hide_driverflows = ButtonElement(
        (By.XPATH, "../div/a[text()='Hide Driver Connections']"))
    disconnect_button = ButtonElement(
        (By.XPATH, "../div/a[text()='Disconnect']"))
    remove_button = ButtonElement((By.XPATH, "../div/a[text()='Remove']"))

    # Port context menus.
    edit_connections = ButtonElement(
        (By.XPATH, "../div/a[text()='Edit Connections']"))
    edit_passthroughs = ButtonElement(
        (By.XPATH, "../div/a[text()='Edit Passthroughs']"))
    edit_driver = ButtonElement((By.XPATH, "../div/a[text()='Edit Driver']"))

    def __init__(self, browser, port, root):
        super(DataflowFigure, self).__init__(browser, port, root)
        self._pathname = None

    @property
    def pathname(self):
        """ Pathname of this component. """
        if self._pathname is None:
            # Much slower than if explicitly set.
            parent = self('header').find_element_by_xpath('..')
            fig_id = parent.get_attribute('id')
            script = "return jQuery('#" + fig_id + "').data('pathname')"
            self._pathname = self.browser.execute_script(script)
        return self._pathname

    @pathname.setter
    def pathname(self, path):
        self._pathname = path

    @property
    def input_port(self):
        """ Input port element, `pathname` must be set previously. """
        return self.root.find_element_by_id(self.pathname + '-input')

    @property
    def output_port(self):
        """ Output port element, `pathname` must be set previously. """
        return self.root.find_element_by_id(self.pathname + '-output')

    @property
    def border(self):
        """ Figure border property. """
        return self.root.value_of_css_property('border')

    @property
    def background_color(self):
        """ Figure background-color property. """
        return self.root.value_of_css_property('background-color')

    @property
    def coords(self):
        """ Figure (left, top). """
        left = self.root.value_of_css_property('left')
        left = int(left[0:-2])  # Drop 'px'.
        top = self.root.value_of_css_property('top')
        top = int(top[0:-2])  # Drop 'px'.
        return (left, top)

    def editor_page(self, double_click=True, base_type='Component'):
        """ Return :class:`ComponentPage` for this component. """
        chain = ActionChains(self.browser)
        if double_click:
            chain.double_click(self.root).perform()
        else:
            self._context_click('edit_button')
        editor_id = 'CE-%s' % self.pathname.replace('.', '-')
        if base_type == 'Assembly':
            return AssemblyPage(self.browser, self.port, (By.ID, editor_id))
        elif base_type == 'Driver':
            return DriverPage(self.browser, self.port, (By.ID, editor_id))
        else:
            return ComponentPage(self.browser, self.port, (By.ID, editor_id))

    def properties_page(self):
        """ Return :class:`PropertiesPage` for this component. """
        self._context_click('properties_button')
        props_id = '%s-properties' % self.pathname.replace('.', '-')
        return PropertiesPage(self.browser, self.port, (By.ID, props_id))

    def connections_page(self):
        """ Return :class:`ConnectionsPage` for this component. """
        self._context_click('connections_button')
        frame_id = 'ConnectionsFrame-%s' % self.pathname.replace('.', '-')
        return ConnectionsPage(self.browser, self.port, (By.ID, frame_id))

    def input_edit_driver(self, driver_pathname):
        """ Return :class:`DriverPage` associated with the input port. """
        chain = ActionChains(self.browser)
        chain.context_click(self.input_port).perform()
        time.sleep(0.5)
        self('edit_driver').click()
        editor_id = 'CE-%s' % driver_pathname.replace('.', '-')
        return DriverPage(self.browser, self.port, (By.ID, editor_id))

    def output_edit_driver(self, driver_pathname):
        """ Return :class:`DriverPage` associated with the output port. """
        # FIXME: can't get response from context click.
        chain = ActionChains(self.browser)
        chain.context_click(self.output_port).perform()
        time.sleep(0.5)
        self('edit_driver').click()
        editor_id = 'CE-%s' % driver_pathname.replace('.', '-')
        return DriverPage(self.browser, self.port, (By.ID, editor_id))

    def run(self):
        """ Run this component. """
        self._context_click('run_button')

    def disconnect(self):
        """ Disconnect this component. """
        self._context_click('disconnect_button')

    def remove(self):
        """ Remove this component. """
        self._context_click('remove_button')

    def display_dataflows(self, show):
        """ Show/hide data flows. """
        if show:
            self._context_click('show_dataflows')
        else:
            self._context_click('hide_dataflows')

    def display_driverflows(self, show):
        """ Show/hide driver flows. """
        if show:
            self._context_click('show_driverflows')
        else:
            self._context_click('hide_driverflows')

    def _context_click(self, name):
        """ Display context menu. """
        chain = ActionChains(self.browser)
        # Default is centered which causes problems in some contexts.
        # Offset is apparently limited, (20, 20) had problems.
        chain.move_to_element_with_offset(self.root, 15, 15)
        chain.context_click(None)
        chain.perform()
        time.sleep(0.5)
        self(name).click()
Beispiel #20
0
class EditorPage(BasePageObject):
    """ Code editor window. """

    title_prefix = 'OpenMDAO:'

    # Left side.
    file_menu = ButtonElement((By.XPATH, '/html/body/div/div/nav2/ul/li/a'))
    newfile_button = ButtonElement(
        (By.XPATH, '/html/body/div/div/nav2/ul/li/ul/li[1]/a'))
    newfolder_button = ButtonElement(
        (By.XPATH, '/html/body/div/div/nav2/ul/li/ul/li[2]/a'))
    add_button = ButtonElement(
        (By.XPATH, '/html/body/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')]"))

    # Right side.
    editor_new_button = ButtonElement((By.ID, 'code_pane-uiBar-new'))
    editor_save_button = ButtonElement((By.ID, 'code_pane-uiBar-save'))
    editor_find_button = ButtonElement((By.ID, 'code_pane-uiBar-find'))
    editor_replace_button = ButtonElement((By.ID, 'code_pane-uiBar-replace'))
    editor_replaceAll_button = ButtonElement(
        (By.ID, 'code_pane-uiBar-replaceAll'))
    editor_undo_button = ButtonElement((By.ID, 'code_pane-uiBar-undo'))
    editor_overwrite_button = ButtonElement((By.ID, 'code_pane-overwrite'))
    editor_cancel_button = ButtonElement((By.ID, 'code_pane-cancel'))

    editor_label = TextElement((By.ID, 'code_pane-label'))

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

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

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

    def get_code(self):
        return self.browser.execute_script(
            "return openmdao.frames.code_pane.editor.getValue()")

    def get_tab_label(self):
        label = self.browser.execute_script(
            "return openmdao.frames.code_pane.currentTablabel()")
        return ''.join(label.split('*'))  # ignore changed / unchanged status

    def get_files(self):
        """ Return names in the file tree. """
        WebDriverWait(self.browser, TMO).until(
            lambda browser: browser.find_element(By.ID, 'file_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('file_menu').click()
        self('add_button').click()

        self.file_chooser = file_path

    def add_files(self, *file_paths):
        """ Read in multiple 'file_path's
            FIXME: doesn't work, see elements._InputElement
            Have to use multiple calls to add_file for now
        """
        self('file_menu').click()
        self('add_button').click()
        self('file_chooser').set_values(*file_paths)

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

    def find_text(self, text):
        #click the 'find' button, and enter text. Not yet functional
        self('editor_find_button').click()
        alert = self.browser.switch_to_alert()
        chain = ActionChains(alert)
        chain.send_keys(text).perform()
        chain.send_keys(Keys.RETURN).perform()
        return

    def replace_text(self, old_text, new_text, replace_all=False):
        #click the 'replace' or 'replace all 'button,
        # and enter text to find and replace. Not yet functional
        if replace_all:
            self('editor_replace_button').click()
        else:
            self('editor_replaceAll_button').click()
        return

    def undo(self):
        #click the 'undo' button
        self('editor_undo_button').click()
        return

    def new_file(self, filename, code, check=True):
        """ Make a new file `filename` with contents `code`. """
        page = self.new_file_dialog()
        page.set_value(filename)

        NotifierPage.wait(self)  # Wait for creation to complete.

        # Switch to editor textarea
        code_input_element = self.get_text_area()

        # Go to the bottom of the code editor window
        for i in range(4):
            code_input_element.send_keys(Keys.ARROW_DOWN)
        # Type in the code.
        code_input_element.send_keys(code)

        self.save_document(check=check)

    def edit_file(self, filename, dclick=True):
        """ Edit `filename` via double-click or context menu. """
        xpath = "//a[(@path='/%s')]" % filename
        element = WebDriverWait(
            self.browser,
            TMO).until(lambda browser: browser.find_element_by_xpath(xpath))
        chain = ActionChains(self.browser)
        for i in range(10):
            try:
                if dclick:
                    chain.double_click(element).perform()
                else:
                    chain.context_click(element).perform()
                    self('file_edit').click()
            except StaleElementReferenceException:
                logging.warning('edit_file: StaleElementReferenceException')
                element = WebDriverWait(self.browser, 1).until(
                    lambda browser: browser.find_element_by_xpath(xpath))
                chain = ActionChains(self.browser)
            else:
                break

    def get_text_area(self):
        code_input_element = WebDriverWait(self.browser, TMO).until(
            lambda browser: browser.find_element_by_css_selector('textarea'))
        # FIXME: absolute delay for editor to get ready.
        #        Problem is Firefox sometimes sends arrow key to scrollbar.
        #        Sadly this didn't completely fix the issue.
        time.sleep(1)
        return code_input_element

    def save_document(self, overwrite=False, check=True, cancel=False):
        #use 'save' button to save code
        self('editor_save_button').click()
        if overwrite:
            self('editor_overwrite_button').click()
        elif cancel:
            self('editor_cancel_button').click()
        if check:
            NotifierPage.wait(self)

    def add_text_to_file(self, text):
        """ Add the given text to the current file.  """
        # Switch to editor textarea
        code_input_element = self.get_text_area()

        # Type in the code.
        code_input_element.send_keys(text)
        return code_input_element
Beispiel #21
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()
Beispiel #22
0
class CheckBox(Element):
    def __init__(self,
                 app,
                 pos,
                 text,
                 font,
                 colour,
                 initValue=False,
                 hotkey=None,
                 style='circle',
                 fillColour=None):
        super(CheckBox, self).__init__(app)

        self.pos = pos
        self.font = font

        if hasattr(pos, 'apply'):
            self.text = TextElement(
                app, ' ' + text, font,
                Location(
                    AttachedPoint(ScaledSize(self._getBoxSize()[0] / 5, 2),
                                  self._getBoxRect, 'midright'), 'midleft'),
                colour)
        else:
            self.text = TextElement(
                app, ' ' + text, font,
                Location((pos[0] + self._getBoxSize()[0],
                          pos[1] - self._getBoxSize()[0] / 10), 'topleft'),
                colour)

        self.value = initValue
        self.colour = colour
        if fillColour is None:
            self.fillColour = tuple((256 * 3 + i) / 4 for i in colour)
        else:
            self.fillColour = fillColour
        self.hotkey = hotkey
        self.style = style
        self.onValueChanged = Event()

    def _getRect(self):
        return self._getBoxRect().union(self.text._getRect())

    def _getBorderWidth(self):
        return max(1, self._getBoxSize()[0] / 8)

    def _getBoxSize(self):
        return (int(self.font.getHeight(self.app) / 1.5),
                int(self.font.getHeight(self.app) / 1.5))

    def _getBoxRect(self):
        if hasattr(self.pos, 'apply'):
            box = pygame.rect.Rect((0, 0), self._getBoxSize())
            self.pos.apply(self.app, box)
        else:
            box = pygame.rect.Rect(self.pos, self._getBoxSize())
        return box

    def draw(self, surface):
        box = self._getBoxRect()
        if self.value:
            if self.style == 'fill':
                pygame.draw.rect(surface, self.fillColour, box, 0)
            elif self.style == 'cross':
                pygame.draw.line(surface, self.fillColour, box.topright,
                                 box.bottomleft, self._getBorderWidth())
                pygame.draw.line(surface, self.fillColour, box.topleft,
                                 box.bottomright, self._getBorderWidth())
            elif self.style == 'circle':
                pygame.draw.circle(surface, self.fillColour, box.center,
                                   box.width / 2 - 2)
        pygame.draw.rect(surface, self.colour, box, self._getBorderWidth())

        self.text.draw(surface)

    def setValue(self, val):
        if val != self.value:
            self.value = val
            self.onValueChanged.execute(self)

    def getValue(self):
        return self.value

    def processEvent(self, event):
        box = self._getBoxRect()
        # Active events.
        if (event.type == pygame.MOUSEBUTTONDOWN and event.button == 1 and
                box.collidepoint(event.pos)) or (event.type == pygame.KEYDOWN
                                                 and event.key == self.hotkey):
            self.setValue(not self.value)
            self.onValueChanged.execute(self)
        else:
            return event
        return None
Beispiel #23
0
class DataflowFigure(BasePageObject):
    """ Represents elements within a dataflow figure. """

    name = TextElement((By.CLASS_NAME, 'DataflowFigureHeader'))
    top_right = ButtonElement((By.CLASS_NAME, 'DataflowFigureTopRight'))

    # Context menu.
    edit_button = ButtonElement((By.XPATH, "../div/a[text()='Edit']"))
    properties_button = ButtonElement(
        (By.XPATH, "../div/a[text()='Properties']"))
    connections_button = ButtonElement(
        (By.XPATH, "../div/a[text()='Connections']"))
    disconnect_button = ButtonElement(
        (By.XPATH, "../div/a[text()='Disconnect']"))
    run_button = ButtonElement((By.XPATH, "../div/a[text()='Run']"))
    remove_button = ButtonElement((By.XPATH, "../div/a[text()='Remove']"))

    @property
    def pathname(self):
        """ Pathname of this component. """
        return self._pathname

    @pathname.setter
    def pathname(self, path):
        self._pathname = path

    @property
    def input_port(self):
        """ Input port element, `pathname` must be set previously. """
        return self.root.find_element_by_id(self.pathname + '-input')

    @property
    def output_port(self):
        """ Output port element, `pathname` must be set previously. """
        return self.root.find_element_by_id(self.pathname + '-output')

    @property
    def border(self):
        """ Figure border property. """
        return self.root.value_of_css_property('border')

    @property
    def background_color(self):
        """ Figure background-color property. """
        return self.root.value_of_css_property('background-color')

    def editor_page(self, double_click=True):
        """ Return :class:`ComponentPage` for this component. """
        chain = ActionChains(self.browser)
        if double_click:
            chain.double_click(self.root).perform()
        else:
            chain.context_click(self.root).perform()
            self('edit_button').click()
        editor_id = 'CE-%s' % self.pathname.replace('.', '-')
        return ComponentPage(self.browser, self.port, (By.ID, editor_id))

    def properties_page(self):
        """ Return :class:`PropertiesPage` for this component. """
        chain = ActionChains(self.browser)
        chain.context_click(self.root).perform()
        self('properties_button').click()
        props_id = '%s-properties' % self.pathname.replace('.', '-')
        return PropertiesPage(self.browser, self.port, (By.ID, props_id))

    def connections_page(self):
        """ Return :class:`ConnectionsPage` for this component. """
        chain = ActionChains(self.browser)
        chain.move_to_element_with_offset(self.root, 15,
                                          15).context_click(None).perform()
        self('connections_button').click()
        frame_id = 'ConnectionsFrame-%s' % self.pathname.replace('.', '-')
        return ConnectionsPage(self.browser, self.port, (By.ID, frame_id))

    def run(self):
        """ Run this component. """
        chain = ActionChains(self.browser)
        chain.context_click(self.root).perform()
        self('run_button').click()

    def disconnect(self):
        """ Disconnect this component. """
        chain = ActionChains(self.browser)
        chain.move_to_element_with_offset(self.root, 2,
                                          2).context_click(None).perform()
        self('disconnect_button').click()

    def remove(self):
        """ Remove this component. """
        chain = ActionChains(self.browser)
        chain.context_click(self.root).perform()
        self('remove_button').click()
Beispiel #24
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()
Beispiel #25
0
class ProjectsPage(BasePageObject):
    """ Displays list of projects. """

    url = '/projects'
    title_prefix = 'Projects'

    welcome_text = TextElement((By.XPATH, "//h3/strong"))
    search_input = InputElement(
        (By.XPATH, "//div[@id='project_table_filter']/label/input"))
    import_button = ButtonElement((By.LINK_TEXT, 'Import Project'))
    new_button = ButtonElement((By.LINK_TEXT, 'New Project'))
    logout_button = ButtonElement((By.LINK_TEXT, 'Exit'))

    def new_project(self):
        """ Clicks the 'new' button. Returns :class:`NewDialog`. """
        self('new_button').click()
        page = NewDialog(self.browser, self.port, (By.ID, "newProjectModal"))
        WebDriverWait(
            self.browser,
            TMO).until(lambda browser: page.modal_title[:11] == 'New Project')
        return page

    def import_project(self):
        """ Clicks the 'import' button. Returns :class:`ImportDialog`. """
        self('import_button').click()
        page = ImportDialog(self.browser, self.port,
                            (By.ID, "importProjectModal"))
        time.sleep(1)  # Wait for silly fade-in.
        return page

    def logout(self):
        """
        Clicks the 'logout' button. Returns :class:`LoginPage`.

        ..warning::

            Since login isn't done anymore, this actually results in
            exiting the server.

        """
        self('logout_button').click()
        from login import LoginPage
        return LoginPage.verify(self.browser, self.port)

    def contains(self, project_name, expected=True):
        """ Returns True if `project_name` is in the list of projects. """
        if expected:
            self.search_input = project_name
            elements = self.browser.find_elements_by_link_text(project_name)
        else:
            self.browser.implicitly_wait(1)  # Not expecting to find anything.
            try:
                self.search_input = project_name  # No search input if no projects.
                elements = self.browser.find_elements_by_link_text(
                    project_name)
            except TimeoutException:
                elements = []
            finally:
                self.browser.implicitly_wait(TMO)
        return len(elements) > 0

    def open_project(self, project_name):
        """ Clicks the named link. Returns :class:`WorkspacePage`. """
        self.search_input = project_name
        element = WebDriverWait(self.browser, TMO).until(
            lambda browser: browser.find_element_by_link_text(project_name))
        element.click()
        from workspace import WorkspacePage
        return WorkspacePage.verify(self.browser, self.port)

    def edit_project(self, project_name):
        """ Clicks the 'edit' button. Returns :class:`EditDialog`. """
        self.search_input = project_name
        element = WebDriverWait(self.browser, TMO).until(
            lambda browser: browser.find_element_by_link_text(project_name))
        element = element.find_element_by_xpath('../../td[6]/a')
        element.click()

        page = EditDialog(self.browser, self.port, (By.ID, "editProjectModal"))
        time.sleep(1)  # Wait for silly fade-in.
        return page

    def export_project(self, project_name):
        """ Clicks the 'export' button. Returns :class:`ExportDialog`. """
        self.search_input = project_name
        element = WebDriverWait(self.browser, TMO).until(
            lambda browser: browser.find_element_by_link_text(project_name))
        element = element.find_element_by_xpath('../../td[6]/form[2]/button')
        element.click()

    def delete_project(self, project_name):
        self.search_input = project_name
        element = WebDriverWait(self.browser, TMO).until(
            lambda browser: browser.find_element_by_link_text(project_name))
        element = element.find_element_by_xpath('../../td[6]/form[1]/button')
        element.click()

        delete_dialog = DeleteDialog(self.browser, self.port,
                                     (By.XPATH, "/html/body/div[2]"))
        time.sleep(1)  # Wait for silly fade-in.
        delete_dialog.submit()
        time.sleep(1)  # Wait for silly fade-out.

    def delete_projects(self, project_filter, verbose=False):
        """ Removes all projects with 'test project' in the name. """
        self.search_input = project_filter + '\n'
        elements = self.browser.find_elements_by_partial_link_text(
            project_filter)
        while len(elements) > 0:
            for i in range(len(elements)):
                element = WebDriverWait(self.browser, TMO).until(
                    lambda browser: browser.find_element_by_partial_link_text(
                        project_filter))

                project_name = element.text
                self.delete_project(project_name)
                if verbose:
                    print >> sys.stderr, 'Deleted', project_name
                self.search_input = project_filter + '\n'
            # there may be more that were previously hidden due to the row limit
            elements = self.browser.find_elements_by_partial_link_text(
                project_filter)

    def get_project_metadata(self, project_name):
        self.search_input = project_name
        element = WebDriverWait(self.browser, TMO).until(
            lambda browser: browser.find_element_by_link_text(project_name))

        elements = element.find_elements_by_xpath('../../td')

        metadata = {
            "name": elements[0].text,
            "description": elements[1].text,
            "version": elements[2].text,
            "created": elements[3].text,
            "last_saved": elements[4].text,
        }

        return metadata
Beispiel #26
0
from elements import ImageElement
from elements import RectangleDrawer
from elements import ImageDrawer
from elements import CircleDrawer
from elements import TextElement
from elements import ChooseImageElement
from elements import ClearElement
from db import Db
import os

db = Db()

okno = Okno()

element1 = TextElement(name='button rect red 10',
                       color='blue',
                       drawer=RectangleDrawer(okno.canvas, 'blue', 20, db))
okno.addElement(element1)

element2 = TextElement(name='button circ red 10',
                       color='blue',
                       drawer=CircleDrawer(okno.canvas, 'blue', 20, db))
okno.addElement(element2)

element3 = ImageElement(image='D:\Pycharm\paint2\like--v2.png',
                        color='blue',
                        drawer=ImageDrawer(okno.canvas, 'like--v2.png', db))
okno.addElement(element3)

element4 = ChooseImageElement(name='choose',
                              color='blue',