class ComponentPage(DialogPage): """ Component editor page. """ dataflow_tab = ButtonElement((By.XPATH, "dl/dt[text()='Dataflow']")) inputs_tab = ButtonElement((By.XPATH, "dl/dt[text()='Inputs']")) slots_tab = ButtonElement((By.XPATH, "dl/dt[text()='Slots']")) outputs_tab = ButtonElement((By.XPATH, "dl/dt[text()='Outputs']")) inputs = GridElement((By.ID, 'Inputs_props')) outputs = GridElement((By.ID, 'Outputs_props')) def get_inputs(self): """ Return inputs grid. """ self('inputs_tab').click() return self.inputs def set_input(self, name, value): """ Set input `name` to `value`. """ self('inputs_tab').click() grid = self.inputs found = [] for row in grid.rows: if row[0] == name: row[2] = value return found.append(row[0]) raise RuntimeError('%r not found in inputs %s' % (name, found)) def get_outputs(self): """ Return outputs grid. """ self('outputs_tab').click() return self.outputs
class DriverPage(ComponentPage): """ Driver editor page. """ parameters_tab = ButtonElement((By.XPATH, "div/ul/li/a[text()='Parameters']")) workflow_tab = ButtonElement((By.XPATH, "div/ul/li/a[text()='Workflow']")) objectives_tab = ButtonElement((By.XPATH, "div/ul/li/a[text()='Objectives']")) constraints_tab = ButtonElement((By.XPATH, "div/ul/li/a[text()='Constraints']")) parameters = GridElement((By.ID, 'Parameters_parms')) objectives = GridElement((By.ID, 'Objectives_objectives')) constraints = GridElement((By.ID, 'Constraints_constraints')) add_parameter = ButtonElement((By.XPATH, "//div[text()='Add Parameter']")) add_objective = ButtonElement((By.XPATH, "//div[text()='Add Objective']")) add_constraint = ButtonElement((By.XPATH, "//div[text()='Add Constraint']")) def get_parameters(self): """ Return parameters grid. """ self('parameters_tab').click() return self.parameters def get_objectives(self): """ Return objectives grid. """ self('objectives_tab').click() return self.objectives def get_constraints(self): """ Return constraints grid. """ self('constraints_tab').click() return self.constraints def new_parameter(self): """ Return :class:`ParameterDialog`. """ self('add_parameter').click() return ParameterDialog(self.browser, self.port, (By.XPATH, "//div[@id='parameter-dialog']/..")) def new_objective(self): """ Return :class:`ObjectiveDialog`. """ self('add_objective').click() return ObjectiveDialog(self.browser, self.port, (By.XPATH, "//div[@id='objective-dialog']/..")) def new_constraint(self): """ Return :class:`ConstraintDialog`. """ self('add_constraint').click() return ConstraintDialog(self.browser, self.port, (By.XPATH, "//div[@id='constraint-dialog']/..")) def show_workflow(self): """switch to workflow tab""" self('workflow_tab').click() def get_workflow_component_figures(self): """ Return workflow component figure elements. """ return find_workflow_component_figures(self)
def __init__(self, colour=BLACK, grid_camera=None, tile_positions=tuple()): GridElement.__init__(self, element=ElementCollection(), grid_position=TetrominoPosition(), grid_camera=grid_camera) self.old_tile_positions = None self.tile_positions = tile_positions self.mapping = {} self.colour = colour self.reset_position_mapping() return
class ImplicitComponentPage(ComponentPage): """ Implicit Component editor page. """ states_tab = ButtonElement((By.XPATH, "div/ul/li/a[text()='States']")) residuals_tab = ButtonElement( (By.XPATH, "div/ul/li/a[text()='Residuals']")) states = GridElement((By.ID, 'States_props')) residuals = GridElement((By.ID, 'Residuals_props')) states_filter = InputElement((By.ID, 'States_variableFilter')) states_clear = ButtonElement((By.ID, 'States_clear')) residuals_filter = InputElement((By.ID, 'Residuals_variableFilter')) residuals_clear = ButtonElement((By.ID, 'Residuals_clear')) def set_state(self, name, value): """ Set state `name` to `value`. """ self('states_tab').click() grid = self.states found = [] for row in grid.rows: if row[1] == name: row[2] = value return found.append(row[1]) raise RuntimeError('%r not found in states %s' % (name, found)) def get_states(self, return_type=None): """ Return states grid. """ self('states_tab').click() return self._get_variables(self.states, return_type) def get_residuals(self, return_type=None): """ Return residuals grid. """ self('residuals_tab').click() return self._get_variables(self.residuals, return_type) def get_state(self, name, return_type=None): """ Return first state variable with `name`. """ self('states_tab').click() return self._get_variable(name, self.states, return_type) def get_residual(self, name, return_type=None): """ Return first residual variable with `name`. """ self('residuals_tab').click() return self._get_variable(name, self.residuals, return_type) def show_states(self): """switch to states tab""" self('states_tab').click() def show_residuals(self): """switch to residuals tab""" self('residuals_tab').click()
def update(self): self.element.rect = self.grid_camera.rect if self.tile_positions != self.grid_position.tile_positions: self.refresh() self.reset_position_mapping() for position in self.mapping.keys(): tile = self.mapping[position] tile.colour = self.colour tile.grid_camera = self.grid_camera tile.grid_position.x = self.grid_position.x + position[0] tile.grid_position.y = self.grid_position.y + position[1] GridElement.update(self) return
class ComponentPage(DialogPage): """ Component editor page. """ inputs_tab = ButtonElement((By.XPATH, "div/ul/li/a[text()='Inputs']")) slots_tab = ButtonElement((By.XPATH, "div/ul/li/a[text()='Slots']")) outputs_tab = ButtonElement((By.XPATH, "div/ul/li/a[text()='Outputs']")) events_tab = ButtonElement((By.XPATH, "div/ul/li/a[text()='Events']")) inputs = GridElement((By.ID, 'Inputs_props')) outputs = GridElement((By.ID, 'Outputs_props')) def __init__(self, browser, port, locator): super(ComponentPage, self).__init__(browser, port, locator) # It takes a while for the full load to complete. NotifierPage.wait(self) def get_inputs(self): """ Return inputs grid. """ self('inputs_tab').click() return self.inputs def set_input(self, name, value): """ Set input `name` to `value`. """ self('inputs_tab').click() grid = self.inputs found = [] for row in grid.rows: if row[0] == name: row[2] = value return found.append(row[0]) raise RuntimeError('%r not found in inputs %s' % (name, found)) def get_events(self): """ Return events grid. """ self('events_tab').click() return self.events def get_outputs(self): """ Return outputs grid. """ self('outputs_tab').click() return self.outputs def show_slots(self): """switch to slots tab""" self('slots_tab').click()
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))
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()
class WorkspacePage(BasePageObject): title_prefix = 'OpenMDAO:' # Top. project_menu = ButtonElement((By.ID, 'project-menu')) save_button = ButtonElement((By.ID, 'project-save')) run_button = ButtonElement((By.ID, 'project-run')) reload_button = ButtonElement((By.ID, 'project-reload')) close_button = ButtonElement((By.ID, 'project-close')) exit_button = ButtonElement((By.ID, 'project-exit')) view_menu = ButtonElement((By.ID, 'view-menu')) cmdline_button = ButtonElement((By.ID, 'view-cmdline')) console_button = ButtonElement((By.ID, 'view-console')) libraries_button = ButtonElement((By.ID, 'view-libraries')) objects_button = ButtonElement((By.ID, 'view-objects')) properties_button = ButtonElement((By.ID, 'view-properties')) workflow_button = ButtonElement((By.ID, 'view-workflow')) dataflow_button = ButtonElement((By.ID, 'view-dataflow')) refresh_button = ButtonElement((By.ID, 'view-refresh')) tools_menu = ButtonElement((By.ID, 'tools-menu')) editor_button = ButtonElement((By.ID, 'tools-editor')) plotter_button = ButtonElement((By.ID, 'tools-plotter')) addons_button = ButtonElement((By.ID, 'tools-addons')) help_menu = ButtonElement((By.ID, 'help-menu')) doc_button = ButtonElement((By.ID, 'help-doc')) about_button = ButtonElement((By.ID, 'about-item')) # Left side. objects_tab = ButtonElement((By.ID, 'otree_tab')) # Object context menu. obj_properties = ButtonElement((By.XPATH, "//a[(@rel='properties')]")) obj_dataflow = ButtonElement((By.XPATH, "//a[(@rel='show_dataflow')]")) obj_workflow = ButtonElement((By.XPATH, "//a[(@rel='show_workflow')]")) obj_run = ButtonElement((By.XPATH, "//a[(@rel='run')]")) obj_toggle = ButtonElement((By.XPATH, "//a[(@rel='toggle')]")) obj_remove = ButtonElement((By.XPATH, "//a[(@rel='remove')]")) # Center. dataflow_tab = ButtonElement((By.ID, 'dataflow_tab')) workflow_tab = ButtonElement((By.ID, 'workflow_tab')) code_tab = ButtonElement((By.ID, 'code_tab')) # Right side. properties_tab = ButtonElement((By.ID, 'properties_tab')) props_header = TextElement((By.XPATH, "//div[@id='propertieseditor']/h3")) props_inputs = GridElement( (By.XPATH, "//div[@id='propertieseditor']/div[1]")) props_outputs = GridElement( (By.XPATH, "//div[@id='propertieseditor']/div[2]")) libraries_tab = ButtonElement((By.ID, 'palette_tab')) libraries_searchbox = InputElement((By.ID, 'objtt-select')) # Bottom. history = TextElement((By.ID, 'history')) command = InputElement((By.ID, 'command')) submit = ButtonElement((By.ID, 'command-button')) def __init__(self, browser, port): super(WorkspacePage, self).__init__(browser, port) self.locators = {} self.locators["objects"] = (By.XPATH, "//div[@id='otree']//li[@path]") # Wait for bulk of page to load. WebDriverWait( self.browser, 2 * TMO).until(lambda browser: len(self.get_dataflow_figures()) > 0) # Now wait for WebSockets. # FIXME: absolute delay before polling sockets. time.sleep(2) browser.execute_script('openmdao.Util.webSocketsReady(2);') NotifierPage.wait(browser, port) def find_palette_button(self, name): path = "//table[(@id='objtypetable')]//td[text()='%s']" % name return ButtonElement((By.XPATH, path)).get(self) def run(self, timeout=TMO): """ Run current component. """ self('project_menu').click() self('run_button').click() NotifierPage.wait(self.browser, self.port, timeout) def do_command(self, cmd, timeout=TMO): """ Execute a command. """ self.command = cmd self('submit').click() NotifierPage.wait(self.browser, self.port, timeout) def close_workspace(self): """ Close the workspace page. Returns :class:`ProjectsListPage`. """ self.browser.execute_script('openmdao.Util.closeWebSockets();') NotifierPage.wait(self.browser, self.port) self('project_menu').click() # Sometimes chromedriver hangs here, so we click in separate thread. # It's a known issue on the chromedriver site. closer = threading.Thread(target=self._closer) closer.daemon = True closer.start() closer.join(60) if closer.is_alive(): abort(True) raise SkipTest("Can't close workspace, driver hung :-(") from project import ProjectsListPage return ProjectsListPage.verify(self.browser, self.port) def _closer(self): """ Clicks the close button. """ self('close_button').click() def open_editor(self): """ Open code editor. Returns :class:`EditorPage`. """ self('tools_menu').click() self('editor_button').click() self.browser.switch_to_window('Code Editor') return EditorPage.verify(self.browser, self.port) def save_project(self): """ Save current project. """ self('project_menu').click() self('save_button').click() NotifierPage.wait(self.browser, self.port) def get_objects_attribute(self, attribute): """ Return list of `attribute` values for all objects. """ WebDriverWait( self.browser, TMO).until(lambda browser: browser.find_element(By.ID, 'otree')) object_elements = self.browser.find_elements(*self.locators["objects"]) values = [] for element in object_elements: values.append(element.get_attribute(attribute)) return values def select_object(self, component_name): """ Select `component_name`. """ self('objects_tab').click() xpath = "//div[@id='otree']//li[(@path='%s')]//a" % component_name element = WebDriverWait( self.browser, TMO).until(lambda browser: browser.find_element_by_xpath(xpath)) element.click() def expand_object(self, component_name): """ Expands `component_name`. """ self('objects_tab').click() xpath = "//div[@id='otree']//li[(@path='%s')]//ins" % component_name element = WebDriverWait( self.browser, TMO).until(lambda browser: browser.find_element_by_xpath(xpath)) element.click() def show_dataflow(self, component_name): """ Show dataflow of `component_name`. """ self('objects_tab').click() xpath = "//div[@id='otree']//li[(@path='%s')]//a" % component_name element = WebDriverWait( self.browser, TMO).until(lambda browser: browser.find_element_by_xpath(xpath)) chain = ActionChains(self.browser) chain.context_click(element).perform() self('obj_dataflow').click() @property def libraries_search(self): """ The contents of the libraries_search box. """ return self('libraries_searchbox').value @libraries_search.setter def libraries_search(self, value): """ Set the value of the libraries_search box. """ self.libraries_searchbox = value def add_library_item_to_dataflow(self, item_name, instance_name): """ Add component `item_name`, with name `instance_name`. """ #xpath = "//div[(@id='palette')]//div[(@path='%s')]" % item_name xpath = "//table[(@id='objtypetable')]//td[(@modpath='%s')]" % item_name library_item = WebDriverWait( self.browser, TMO).until(lambda browser: browser.find_element_by_xpath(xpath)) WebDriverWait(self.browser, TMO).until(lambda browser: library_item.is_displayed()) # FIXME: absolute delay to wait for 'slide' to complete. time.sleep(1) target = WebDriverWait( self.browser, TMO).until(lambda browser: browser.find_element_by_xpath( "//*[@id='-dataflow']")) chain = ActionChains(self.browser) if False: chain = chain.drag_and_drop(library_item, target) else: chain = chain.click_and_hold(library_item) chain = chain.move_to_element_with_offset(target, 100, 100) chain = chain.release(None) chain.perform() page = ValuePrompt(self.browser, self.port) page.set_value(instance_name) # Check that the prompt is gone so we can distinguish a prompt problem # from a dataflow update problem. time.sleep(0.25) eq(len(self.browser.find_elements(*page('prompt')._locator)), 0) WebDriverWait(self.browser, TMO).until(lambda browser: instance_name in self. get_dataflow_component_names()) def get_dataflow_figures(self): """ Return dataflow figure elements. """ return self.browser.find_elements_by_class_name('DataflowFigure') def get_dataflow_figure(self, name, prefix=None, retries=5): """ Return :class:`DataflowFigure` for `name`. """ for retry in range(retries): time.sleep(0.5) figures = self.browser.find_elements_by_class_name( 'DataflowFigure') if not figures: continue fig_name = None for figure in figures: try: header = figure.find_elements_by_class_name( 'DataflowFigureHeader') if len(header) == 0: # the outermost figure (globals) has no header or name if name == '' and prefix is None: fig = DataflowFigure(self.browser, self.port, figure) return fig else: continue fig_name = figure.find_elements_by_class_name( 'DataflowFigureHeader')[0].text except StaleElementReferenceException: logging.warning('get_dataflow_figure:' ' StaleElementReferenceException') else: if fig_name == name: fig = DataflowFigure(self.browser, self.port, figure) if prefix is not None: if prefix: fig.pathname = '%s.%s' % (prefix, name) else: fig.pathname = name return fig return None def get_dataflow_component_names(self): """ Return names of dataflow components. """ names = [] # Assume there should be at least 1, wait for number to not change. n_found = 0 for retry in range(10): dataflow_component_headers = \ self.browser.find_elements_by_class_name('DataflowFigureHeader') if dataflow_component_headers: n_headers = len(dataflow_component_headers) if n_found: if n_headers == n_found: break n_found = n_headers else: logging.error('get_dataflow_component_names: n_found %s', n_found) return names for i in range(len(dataflow_component_headers)): for retry in range(10): # This has had issues... try: names.append( self.browser.find_elements_by_class_name( 'DataflowFigureHeader')[i].text) except StaleElementReferenceException: logging.warning('get_dataflow_component_names:' ' StaleElementReferenceException') except IndexError: logging.warning( 'get_dataflow_component_names:' ' IndexError for i=%s, headers=%s', i, len(dataflow_component_headers)) else: break if len(names) != len(dataflow_component_headers): logging.error( 'get_dataflow_component_names:' ' expecting %d names, got %s', len(dataflow_component_headers), names) return names def connect(self, src, dst): """ Return :class:`ConnectionsPage` for connecting `src` to `dst`. """ chain = ActionChains(self.browser) chain = chain.click_and_hold(src.output_port) # Using root rather than input_port since for some reason # even using a negative Y offset can select the parent's input. chain = chain.move_to_element(dst.root) chain = chain.release(None) chain.perform() parent, dot, srcname = src.pathname.rpartition('.') parent, dot, dstname = dst.pathname.rpartition('.') editor_id = 'ConnectionsFrame-%s' % (parent) editor_id = editor_id.replace('.', '-') return ConnectionsPage(self.browser, self.port, (By.ID, editor_id)) def hide_left(self): toggler = self.browser.find_element_by_css_selector( '.ui-layout-toggler-west-open') toggler.click() def show_left(self): toggler = self.browser.find_element_by_css_selector( '.ui-layout-toggler-west-closed') toggler.click() def hide_right(self): toggler = self.browser.find_element_by_css_selector( '.ui-layout-toggler-east-open') toggler.click() def show_right(self): toggler = self.browser.find_element_by_css_selector( '.ui-layout-toggler-east-closed') toggler.click() def hide_console(self): toggler = self.browser.find_element_by_css_selector( '.ui-layout-toggler-south-open') toggler.click() def show_console(self): toggler = self.browser.find_element_by_css_selector( '.ui-layout-toggler-south-closed') toggler.click()
class WorkspacePage(BasePageObject): title_prefix = 'OpenMDAO:' # Top. project_menu = ButtonElement((By.ID, 'project-menu')) commit_button = ButtonElement((By.ID, 'project-commit')) revert_button = ButtonElement((By.ID, 'project-revert')) reload_button = ButtonElement((By.ID, 'project-reload')) close_button = ButtonElement((By.ID, 'project-close')) exit_button = ButtonElement((By.ID, 'project-exit')) view_menu = ButtonElement((By.ID, 'view-menu')) objects_button = ButtonElement((By.ID, 'view-components')) console_button = ButtonElement((By.ID, 'view-console')) dataflow_button = ButtonElement((By.ID, 'view-dataflow')) files_button = ButtonElement((By.ID, 'view-files')) library_button = ButtonElement((By.ID, 'view-library')) properties_button = ButtonElement((By.ID, 'view-properties')) workflow_button = ButtonElement((By.ID, 'view-workflow')) refresh_button = ButtonElement((By.ID, 'view-refresh')) tools_menu = ButtonElement((By.ID, 'tools-menu')) editor_button = ButtonElement((By.ID, 'tools-editor')) plotter_button = ButtonElement((By.ID, 'tools-plotter')) drawing_button = ButtonElement((By.ID, 'tools-drawing')) log_button = ButtonElement((By.ID, 'tools-log')) help_menu = ButtonElement((By.ID, 'help-menu')) doc_button = ButtonElement((By.ID, 'help-doc')) about_button = ButtonElement((By.ID, 'about-item')) # Left side. objects_tab = ButtonElement((By.ID, 'otree_tab')) objects_selector = SelectElement((By.ID, 'otree_pane_select')) files_tab = ButtonElement((By.ID, 'ftree_tab')) # Object context menu. obj_properties = ButtonElement((By.XPATH, "//a[(@rel='properties')]")) obj_dataflow = ButtonElement((By.XPATH, "//a[(@rel='show_dataflow')]")) obj_workflow = ButtonElement((By.XPATH, "//a[(@rel='show_workflow')]")) obj_run = ButtonElement((By.XPATH, "//a[(@rel='run')]")) #obj_toggle = ButtonElement((By.XPATH, "//a[(@rel='toggle')]")) obj_remove = ButtonElement((By.XPATH, "//a[(@rel='remove')]")) # File menu file_menu = ButtonElement( (By.XPATH, '/html/body/div/div/div/nav2/ul/li/a')) newfile_button = ButtonElement( (By.XPATH, '/html/body/div/div/div/nav2/ul/li/ul/li[1]/a')) newfolder_button = ButtonElement( (By.XPATH, '/html/body/div/div/div/nav2/ul/li/ul/li[2]/a')) add_button = ButtonElement( (By.XPATH, '/html/body/div/div/div/nav2/ul/li/ul/li[3]/a')) delete_files_button = ButtonElement( (By.XPATH, '/html/body/div/div/div/nav2/ul/li/ul/li[4]/a')) # File tree pane context menu ftree_newfile_button = ButtonElement( (By.XPATH, "//ul[@id='ftree_pane-context-menu']/li[1]]")) ftree_newfolder_button = ButtonElement( (By.XPATH, "//ul[@id='ftree_pane-context-menu']/li[2]")) ftree_add_button = ButtonElement( (By.XPATH, "//ul[@id='ftree_pane-context-menu']/li[3]")) ftree_toggle_files_button = ButtonElement( (By.XPATH, "//ul[@id='ftree_pane-context-menu']/li[4]")) # File context menu. file_create = ButtonElement((By.XPATH, "//a[(@rel='createFile')]")) file_add = ButtonElement((By.XPATH, "//a[(@rel='addFile')]")) file_folder = ButtonElement((By.XPATH, "//a[(@rel='createFolder')]")) file_view = ButtonElement((By.XPATH, "//a[(@rel='viewFile')]")) file_edit = ButtonElement((By.XPATH, "//a[(@rel='editFile')]")) file_exec = ButtonElement((By.XPATH, "//a[(@rel='execFile')]")) file_image = ButtonElement((By.XPATH, "//a[(@rel='viewImage')]")) file_geometry = ButtonElement((By.XPATH, "//a[(@rel='viewGeometry')]")) file_rename = ButtonElement((By.XPATH, "//a[(@rel='renameFile')]")) file_delete = ButtonElement((By.XPATH, "//a[(@rel='deleteFile')]")) file_toggle = ButtonElement((By.XPATH, "//a[(@rel='toggle')]")) file_chooser = InputElement((By.ID, 'filechooser')) # Center. dataflow_tab = ButtonElement((By.ID, 'dataflow_tab')) workflow_tab = ButtonElement((By.ID, 'workflow_tab')) # Right side. properties_tab = ButtonElement((By.ID, 'properties_tab')) props_header = TextElement((By.XPATH, "//div[@id='properties_pane']/h3")) props_inputs = GridElement( (By.XPATH, "//div[@id='properties_pane']/div[1]")) props_outputs = GridElement( (By.XPATH, "//div[@id='properties_pane']/div[2]")) library_tab = ButtonElement((By.ID, 'library_tab')) library_search = InputElement((By.ID, 'objtt-filter')) library_clear = ButtonElement((By.ID, 'objtt-clear')) library_item_docs = ButtonElement( (By.XPATH, "//ul[@id='lib-cmenu']/li[1]")) library_item_metadata = ButtonElement( (By.XPATH, "//ul[@id='lib-cmenu']/li[2]")) # Bottom. history = TextElement((By.ID, 'history')) command = InputElement((By.ID, 'cmdline')) submit = ButtonElement((By.ID, 'command-button')) def __init__(self, browser, port): super(WorkspacePage, self).__init__(browser, port) self.locators = {} self.locators["objects"] = \ (By.XPATH, "//div[@id='otree_pane']//li[@path]") self.locators["files"] = \ (By.XPATH, "//div[@id='ftree_pane']//a[@class='file ui-draggable']") # Wait for bulk of page to load. WebDriverWait( self.browser, TMO).until(lambda browser: len(self.get_dataflow_figures()) > 0) # Now wait for all WebSockets open. browser.execute_script('openmdao.project.webSocketsReady(2);') try: # We may get 2 notifiers: sockets open and sockets closed. NotifierPage.wait(self, base_id='ws_open') except Exception as exc: if 'Element is not clickable' in str(exc): NotifierPage.wait(self, base_id='ws_closed') NotifierPage.wait(self, base_id='ws_open') else: raise else: self.browser.implicitly_wait(1) try: NotifierPage.wait(self, timeout=1, base_id='ws_closed', retries=0) except TimeoutException: pass # ws closed dialog may not exist finally: self.browser.implicitly_wait(TMO) def find_library_button(self, name, delay=0): path = "//table[(@id='objtypetable')]//td[text()='%s']" % name for retry in range(5): try: element = WebDriverWait(self.browser, TMO).until( lambda browser: browser.find_element(By.XPATH, path)) except TimeoutException as err: logging.warning(str(err)) else: break else: raise err if delay: time.sleep(delay) return element def find_object_button(self, name, delay=0): path = "//div[@id='otree_pane']//li[(@path='%s')]//a" % name for retry in range(5): try: element = WebDriverWait(self.browser, TMO).until( lambda browser: browser.find_element(By.XPATH, path)) except TimeoutException as err: logging.warning(str(err)) else: break else: raise err if delay: time.sleep(delay) return element def do_command(self, cmd, timeout=TMO, ack=True): """ Execute a command. """ self.command = cmd self('submit').click() if ack: NotifierPage.wait(self, timeout, base_id='command') def close_workspace(self, commit=False): """ Close the workspace page. Returns :class:`ProjectsPage`. """ if commit: self.commit_project() self.browser.execute_script('openmdao.project.closeWebSockets();') NotifierPage.wait(self, base_id='ws_closed') self('project_menu').click() self('close_button').click() from project import ProjectsPage return ProjectsPage.verify(self.browser, self.port) def attempt_to_close_workspace(self, expectDialog, confirm): """ Close the workspace page. Returns :class:`ProjectsPage`. """ self('project_menu').click() self('close_button').click() #if you expect the "close without saving?" dialog if expectDialog: dialog = ConfirmationPage(self) if confirm: # close without saving self.browser.execute_script( 'openmdao.project.closeWebSockets();') NotifierPage.wait(self) dialog.click_ok() from project import ProjectsPage return ProjectsPage.verify(self.browser, self.port) else: # return to the project, intact. dialog.click_cancel() else: # no unsaved changes from project import ProjectsPage return ProjectsPage.verify(self.browser, self.port) def open_editor(self): """ Open code editor. Returns :class:`EditorPage`. """ self('tools_menu').click() self('editor_button').click() self.browser.switch_to_window('Code Editor') return EditorPage.verify(self.browser, self.port) def get_files(self): """ Return names in the file tree. """ self('files_tab').click() WebDriverWait(self.browser, TMO).until( lambda browser: browser.find_element(By.ID, 'ftree_pane')) # FIXME: absolute delay for tree population. time.sleep(1) file_items = self.browser.find_elements(*self.locators["files"]) file_names = [] for i in range(len(file_items)): for retry in range(10): # This has had issues... try: file_names.append( self.browser.find_elements( *self.locators["files"])[i].text.strip()) except StaleElementReferenceException: logging.warning( 'get_files: StaleElementReferenceException') else: break return file_names def add_file(self, file_path): """ Read in `file_path` """ if file_path.endswith('.pyc'): file_path = file_path[:-1] self('files_tab').click() self('file_menu').click() self('add_button').click() self.file_chooser = file_path time.sleep(1) # Some extra time for the file tree update. self.find_file(os.path.basename(file_path)) # Verify added. time.sleep(1) # Some extra time for the library update. def add_file_to_folder(self, folder_path, file_path): """ Read in `file_path` """ if file_path.endswith('.pyc'): file_path = file_path[:-1] self('files_tab').click() element = self.find_file(folder_path) chain = ActionChains(self.browser) chain.context_click(element).perform() time.sleep(0.5) self('file_add').click() time.sleep(0.5) self.file_chooser = file_path time.sleep(1) # Some extra time for the file tree update. def new_file_dialog(self): """ bring up the new file dialog """ self('files_tab').click() self('file_menu').click() self('newfile_button').click() return ValuePrompt(self.browser, self.port) def new_folder_dialog(self): """ bring up the new folder dialog """ self('files_tab').click() self('file_menu').click() self('newfolder_button').click() return ValuePrompt(self.browser, self.port) def new_file(self, filename): """ Make a new empty file `filename`. """ page = self.new_file_dialog() page.set_value(filename) NotifierPage.wait(self) # Wait for creation to complete. def new_folder(self, foldername): """ Make a new empty folder `foldername`. """ page = self.new_folder_dialog() page.set_value(foldername) def find_file(self, filename, tmo=TMO): """ Return element corresponding to `filename`. """ xpath = "//a[(@path='/%s')]" % filename return WebDriverWait( self.browser, tmo).until(lambda browser: browser.find_element_by_xpath(xpath)) def edit_file(self, filename, dclick=True): """ Edit `filename` via double-click or context menu. """ self('files_tab').click() element = self.find_file(filename) chain = ActionChains(self.browser) if dclick: # This has had issues... for i in range(10): try: chain.double_click(element).perform() except StaleElementReferenceException: logging.warning( 'edit_file: StaleElementReferenceException') element = self.find_file(filename, 1) chain = ActionChains(self.browser) else: break else: chain.context_click(element).perform() self('file_edit').click() self.browser.switch_to_window('Code Editor') return EditorPage.verify(self.browser, self.port) def view_file(self, filename): """ View image `filename` in another window via context menu. """ self('files_tab').click() element = self.find_file(filename) chain = ActionChains(self.browser) chain.context_click(element).perform() self('file_view').click() self.browser.switch_to_window(self.browser.window_handles[-1]) return BasePageObject.verify(self.browser, self.port) def view_image(self, filename, dclick=True): """ View image `filename` via double-click or context menu. """ self('files_tab').click() element = self.find_file(filename) chain = ActionChains(self.browser) if dclick: # This has had issues... for i in range(10): try: chain.double_click(element).perform() except StaleElementReferenceException: logging.warning( 'edit_file: StaleElementReferenceException') element = self.find_file(filename, 1) chain = ActionChains(self.browser) else: break else: chain.context_click(element).perform() self('file_image').click() self.browser.switch_to_window(self.browser.window_handles[-1]) return ImagesPage.verify(self.browser, self.port) def view_geometry(self, filename, dclick=True): """ View geometry `filename` via double-click or context menu. """ self('files_tab').click() element = self.find_file(filename) chain = ActionChains(self.browser) if dclick: # This has had issues... for i in range(10): try: chain.double_click(element).perform() except StaleElementReferenceException: logging.warning( 'edit_file: StaleElementReferenceException') element = self.find_file(filename, 1) chain = ActionChains(self.browser) else: break else: chain.context_click(element).perform() self('file_geometry').click() self.browser.switch_to_window(self.browser.window_handles[-1]) return GeometryPage.verify(self.browser, self.port) def delete_file(self, filename, confirm=True): """ Delete `filename`. """ self('files_tab').click() element = self.find_file(filename) chain = ActionChains(self.browser) chain.context_click(element).perform() time.sleep(0.5) self('file_delete').click() time.sleep(0.5) page = ConfirmationPage(self) if confirm: page.click_ok() else: page.click_cancel() def delete_files(self, file_paths, confirm=True): """ Delete all the files in the list `file_paths` """ # need select all the files given in file_paths self('files_tab').click() for filename in file_paths: element = self.find_file(filename) chain = ActionChains(self.browser) #Mac OSX does not use CONTROL key if sys.platform == 'darwin': chain.key_down(Keys.SHIFT).click(element).key_up( Keys.SHIFT).perform() else: chain.key_down(Keys.CONTROL).click(element).key_up( Keys.CONTROL).perform() self('files_tab').click() self('file_menu').click() self('delete_files_button').click() page = ConfirmationPage(self) if confirm: page.click_ok() else: page.click_cancel() def expand_folder(self, filename): """ Expands `filename`. """ self('files_tab').click() xpath = "//div[@id='ftree_pane']//a[(@path='/%s')]/../ins" % filename element = WebDriverWait( self.browser, TMO).until(lambda browser: browser.find_element_by_xpath(xpath)) element.click() time.sleep(1) # Wait for cute animation. def rename_file(self, old, new): """ Rename `old` to `new`. """ self('files_tab').click() element = self.find_file(old) chain = ActionChains(self.browser) chain.context_click(element).perform() self('file_rename').click() prompt = ValuePrompt(self.browser, self.port) prompt.set_value(new) def toggle_files(self, filename=None): """ Toggle display of hidden files. Use context menu of `filename` if provided, otherwise use the context menu of the file tree pane. """ self('files_tab').click() time.sleep(0.5) if filename: element = self.find_file(filename) chain = ActionChains(self.browser) chain.context_click(element).perform() time.sleep(0.5) self('file_toggle').click() time.sleep(0.5) else: element = self.browser.find_element(By.ID, 'ftree_pane') chain = ActionChains(self.browser) chain.context_click(element).perform() time.sleep(0.5) self('ftree_toggle_files_button').click() time.sleep(0.5) def commit_project(self, comment='no comment'): """ Commit current project. """ self('project_menu').click() self('commit_button').click() page = ValuePrompt(self.browser, self.port) page.set_value(comment) NotifierPage.wait(self) def revert_project(self): """ Revert current project. """ self('project_menu').click() self('revert_button').click() page = ConfirmationPage(self) page.click_ok() return WorkspacePage.verify(self.browser, self.port) def reload_project(self): """ Reload current project. """ self('project_menu').click() self('reload_button').click() WorkspacePage.verify(self.browser, self.port) def get_objects_attribute(self, attribute, visible=False): """ Return list of `attribute` values for all objects. """ WebDriverWait(self.browser, TMO).until( lambda browser: browser.find_element(By.ID, 'otree_pane')) object_elements = self.browser.find_elements(*self.locators["objects"]) values = [] for element in object_elements: if not visible or element.is_displayed(): values.append(element.get_attribute(attribute)) return values def select_objects_view(self, tree_name): """ Select the object tree view ('Components' or 'Workflow)'. """ self('objects_selector').value = tree_name def select_object(self, component_name): """ Select `component_name`. """ self('objects_tab').click() xpath = "//div[@id='otree_pane']//li[(@path='%s')]//a" % component_name element = WebDriverWait( self.browser, TMO).until(lambda browser: browser.find_element_by_xpath(xpath)) element.click() def expand_object(self, component_name): """ Expands `component_name`. """ self('objects_tab').click() xpath = "//div[@id='otree_pane']//li[(@path='%s')]//ins" % component_name element = WebDriverWait( self.browser, TMO).until(lambda browser: browser.find_element_by_xpath(xpath)) element.click() time.sleep(1) # Wait for cute animation. def show_dataflow(self, component_name=None): """ Show dataflow of `component_name`. """ if component_name is None: self('dataflow_tab').click() return self('objects_tab').click() xpath = "//div[@id='otree_pane']//li[(@path='%s')]//a" % component_name element = WebDriverWait( self.browser, TMO).until(lambda browser: browser.find_element_by_xpath(xpath)) element.click() time.sleep(0.5) # Try to recover from context menu not getting displayed. for retry in range(3): chain = ActionChains(self.browser) chain.context_click(element).perform() try: self('obj_dataflow').click() break except TimeoutException: if retry >= 2: raise def show_workflow(self, component_name=None): """ Show workflow of `component_name`. """ if component_name is None: self('workflow_tab').click() return self('objects_tab').click() xpath = "//div[@id='otree_pane']//li[(@path='%s')]//a" % component_name element = WebDriverWait( self.browser, TMO).until(lambda browser: browser.find_element_by_xpath(xpath)) element.click() time.sleep(0.5) # Try to recover from context menu not getting displayed. for retry in range(3): chain = ActionChains(self.browser) chain.context_click(element).perform() try: self('obj_workflow').click() break except TimeoutException: if retry >= 2: raise def show_properties(self): """ Display properties. """ # This has had some odd failures where the tab is highlighted as if # hovering over it, yet the Library tab is still the selected one. for retry in range(5): try: self('properties_tab').click() WebDriverWait( self.browser, 1).until(lambda browser: self('props_header').is_visible) except TimeoutException: if retry: logging.warning('TimeoutException in show_properties') else: break else: raise RuntimeError('Too many TimeoutExceptions') def get_properties(self, name, prefix=None): self.show_properties() self.show_dataflow() obj = self.get_dataflow_figure(name, prefix=prefix) chain = ActionChains(self.browser) chain.click(obj.root) chain.perform() time.sleep(0.5) return (self.props_header, self.props_inputs, self.props_outputs) def show_library(self): """ Display library. """ # For some reason the first try never works, so the wait is set # low and we expect to retry at least once. for retry in range(5): try: self('library_tab').click() WebDriverWait( self.browser, 1).until(lambda browser: self('library_search').is_visible) except TimeoutException: if retry: logging.warning('TimeoutException in show_library') else: break else: raise RuntimeError('Too many TimeoutExceptions') def set_library_filter(self, filter): """ Set the search filter text. """ for retry in range(3): # This has had issues... try: self.library_search = filter + Keys.RETURN except StaleElementReferenceException: logging.warning('set_library_filter:' ' StaleElementReferenceException') else: break time.sleep(1) # Wait for display update. def clear_library_filter(self): """ Clear the search filter via the 'X' button. """ self('library_clear').click() time.sleep(0.5) # Wait for display update. def get_object_types(self): """ Return displayed object types. """ xpath = "//table[(@id='objtypetable')]//td" return [ element.text for element in self.browser.find_elements(By.XPATH, xpath) ] def get_library_searches(self): """ Return stored library search terms. """ searches = [] self.library_search = 'searches' for menu in self.browser.find_elements(By.CLASS_NAME, 'ui-autocomplete'): items = menu.find_elements(By.CLASS_NAME, 'ui-menu-item') searches = [item.text for item in items] if len(searches) > 0 and searches[0] == 'In Project': break self.clear_library_filter() return searches def get_library_item(self, item_name): """ Return element for library item `item_name`. """ xpath = "//table[(@id='objtypetable')]//td[(@modpath='%s')]" % item_name library_item = WebDriverWait( self.browser, TMO).until(lambda browser: browser.find_element_by_xpath(xpath)) for retry in range(3): try: WebDriverWait( self.browser, TMO).until(lambda browser: library_item.is_displayed()) except StaleElementReferenceException: if retry < 2: logging.warning('get_library_item:' ' StaleElementReferenceException') library_item = WebDriverWait(self.browser, TMO).until( lambda browser: browser.find_element_by_xpath(xpath)) else: raise else: break return library_item def view_library_item_docs(self, item_name): chain = ActionChains(self.browser) current_windows = set(self.browser.window_handles) self.show_library() item = self.get_library_item(item_name) chain.context_click(item).perform() self("library_item_docs").click() new_windows = set(self.browser.window_handles) - current_windows docs_window = list(new_windows)[0] return docs_window def add_library_item_to_dataflow(self, item_name, instance_name, target_name=None, check=True, offset=None, prefix=None, args=None): """ Add component `item_name`, with name `instance_name`. """ library_item = self.get_library_item(item_name) if target_name: target = self.get_dataflow_figure( target_name).get_drop_targets()[0] offset = offset or (2, 2) # top left corner of target content area else: xpath = "//*[@id='-dataflow']" target = WebDriverWait(self.browser, TMO).until( lambda browser: browser.find_element_by_xpath(xpath)) offset = offset or (50, 50) for retry in range(3): try: chain = ActionChains(self.browser) if False: chain.drag_and_drop(library_item, target) else: chain.click_and_hold(library_item) chain.move_to_element_with_offset(target, offset[0], offset[1]) chain.release(None) chain.perform() except StaleElementReferenceException: if retry < 2: logging.warning('add_library_item_to_dataflow:' ' StaleElementReferenceException') library_item = self.get_library_item(item_name) target = WebDriverWait(self.browser, TMO).until( lambda browser: browser.find_element_by_xpath(xpath)) else: raise else: break page = ArgsPrompt(self.browser, self.port) argc = page.argument_count() page.set_name(instance_name) if argc > 0: if args is not None: for i, arg in enumerate(args): page.set_argument(i, arg) page.click_ok() # Check that the prompt is gone so we can distinguish a prompt problem # from a dataflow update problem. time.sleep(0.25) self.browser.implicitly_wait(1) # We don't expect to find anything. try: eq(len(self.browser.find_elements(*page('prompt')._locator)), 0) finally: self.browser.implicitly_wait(TMO) retval = None if check: # Check that it's been added. retval = WebDriverWait( self.browser, TMO).until(lambda browser: self.get_dataflow_figure( instance_name, prefix)) return retval def fill_slot_from_library(self, slot, classname, args=None): """ Fill slot with `classname` instance from library. """ for retry in range(3): try: button = self.find_library_button(classname) chain = ActionChains(self.browser) chain.move_to_element(button) chain.click_and_hold(button) chain.move_to_element(slot.root) chain.release(None) chain.perform() except StaleElementReferenceException: if retry < 2: logging.warning('fill_slot_from_library:' 'StaleElementReferenceException') else: raise else: break # Handle arguments for the slotted class page = ArgsPrompt(self.browser, self.port) argc = page.argument_count() if argc > 0: if args is not None: for i, arg in enumerate(args): page.set_argument(i, arg) page.click_ok() # Check that the prompt is gone so we can distinguish a prompt problem # from a dataflow update problem. time.sleep(0.5) self.browser.implicitly_wait(1) # We don't expect to find anything. try: eq(len(self.browser.find_elements(*page('prompt')._locator)), 0) finally: self.browser.implicitly_wait(TMO) def get_dataflow_figures(self): """ Return dataflow figure elements. """ return find_dataflow_figures(self) def get_dataflow_figure(self, name, prefix=None, retries=5): """ Return :class:`DataflowFigure` for `name`. """ return find_dataflow_figure(self, name, prefix, retries) def get_dataflow_component_names(self): """ Return names of dataflow components. """ return find_dataflow_component_names(self) def connect(self, src, dst): """ Return :class:`ConnectionsPage` for connecting `src` to `dst`. """ chain = ActionChains(self.browser) chain.click_and_hold(src.output_port) # Using root rather than input_port since for some reason # even using a negative Y offset can select the parent's input. chain.move_to_element(dst.input_port) chain.release(None) chain.perform() parent, dot, srcname = src.pathname.rpartition('.') parent, dot, dstname = dst.pathname.rpartition('.') editor_id = 'ConnectionsFrame-%s' % (parent) editor_id = editor_id.replace('.', '-') return ConnectionsPage(self.browser, self.port, (By.ID, editor_id)) def replace(self, name, classname, confirm=True): """ Replace `name` with an instance of `classname`. """ library_item = self.get_library_item(classname) target = self.get_dataflow_figure(name).root chain = ActionChains(self.browser) chain.click_and_hold(library_item) chain.move_to_element(target) chain.release(None) chain.perform() dialog = ConfirmationPage(self) if confirm: dialog.click_ok() else: dialog.click_cancel() def add_object_to_workflow(self, obj_path, target_name): """ Add `obj_path` object to `target_name` in workflow. """ for retry in range(3): try: items = obj_path.split('.') parent = items[:-1] comp = items[-1] obj = self.get_dataflow_figure(comp, parent) target = self.find_object_button(target_name) chain = ActionChains(self.browser) chain.drag_and_drop(obj.root, target) chain.perform() #obj = self.find_object_button(obj_path) #workflow = self.get_workflow_figure(target_name) #flow_fig = workflow.flow #chain = ActionChains(self.browser) #chain.move_to_element(obj) #chain.click_and_hold(obj) #chain.move_to_element(flow_fig) #chain.move_by_offset(2, 1) #chain.release(None) #chain.perform() except StaleElementReferenceException: if retry < 2: logging.warning('add_object_to_workflow:' ' StaleElementReferenceException') else: raise else: break def add_object_to_workflow_figure(self, obj_path, target_name, target_page=None): """ Add `obj_path` object to `target_name` in workflow diagram. """ if target_page is None: target_page = self for retry in range(3): try: items = obj_path.split('.') parent = items[:-1] comp = items[-1] obj = self.get_dataflow_figure(comp, parent) workflow = target_page.get_workflow_figure(target_name) flow_fig = workflow.flow chain = ActionChains(self.browser) chain.drag_and_drop(obj.root, flow_fig) chain.perform() except StaleElementReferenceException: if retry < 2: logging.warning('add_object_to_workflow_figure:' ' StaleElementReferenceException') else: raise else: break def get_workflow_figures(self): """ Return workflow figure elements. """ return find_workflow_figures(self) def get_workflow_component_figures(self): """ Return workflow component figure elements. """ return find_workflow_component_figures(self) def get_workflow_figure(self, name, prefix=None, retries=5): """ Return :class:`WorkflowFigure` for `name`. """ return find_workflow_figure(self, name, prefix, retries) def get_workflow_component_figure(self, name, prefix=None, retries=5): """ Return :class:`WorkflowComponentFigure` for `name`. """ return find_workflow_component_figure(self, name, prefix, retries) def show_log(self): """ Open log viewer. Returns :class:`LogViewer`. """ self('tools_menu').click() self('log_button').click() return LogViewer.verify(self.browser, self.port) def hide_left(self): toggler = self.browser.find_element_by_css_selector( '.ui-layout-toggler-west-open') toggler.click() def show_left(self): toggler = self.browser.find_element_by_css_selector( '.ui-layout-toggler-west-closed') toggler.click() def hide_right(self): toggler = self.browser.find_element_by_css_selector( '.ui-layout-toggler-east-open') toggler.click() def show_right(self): toggler = self.browser.find_element_by_css_selector( '.ui-layout-toggler-east-closed') toggler.click() def hide_console(self): toggler = self.browser.find_element_by_css_selector( '.ui-layout-toggler-south-open') toggler.click() def show_console(self): toggler = self.browser.find_element_by_css_selector( '.ui-layout-toggler-south-closed') toggler.click() def drag_element_to(self, element, drag_to, center): '''Drag one element over to another element''' chain = ActionChains(self.browser) chain.move_to_element(element) chain.click_and_hold(element) if center: # move to center of element chain.move_to_element(drag_to) else: # move to offset from top left of element chain.move_to_element_with_offset(drag_to, 2, 2) chain.perform() return chain def release(self, chain): '''The drop part of the ActionChain when doing drag and drop''' chain.release(on_element=None).perform() time.sleep(0.5) # Pacing for diagram update. def replace_driver(self, assembly_name, driver_type): ''' find 'driver_type' in the library, drag and drop it on to the driver figure of the 'assembly_name' ''' newdriver = self.find_library_button(driver_type) assembly = self.get_dataflow_figure(assembly_name) driver_element = self.get_dataflow_figure('driver') div = driver_element.get_drop_targets()[0] chain = self.drag_element_to(newdriver, div, True) self.check_highlighting( driver_element('content_area').element, True, "Driver's content_area") self.release(chain) # brings up a confirm dialog for replacing the existing driver. dialog = ConfirmationPage(assembly) dialog.click_ok() def resize_editor(self, editor): '''ensure that the editor is not covering the library (or else we cannot drag things from it!)''' browser = self.browser page_width = browser.get_window_size()['width'] lib_tab = self('library_tab').find_element_by_xpath('..') lib_width = lib_tab.size['width'] lib_position = lib_tab.location['x'] dialog_title = editor('dialog_title').find_element_by_xpath('../..') dialog_width = dialog_title.size['width'] dialog_position = dialog_title.location['x'] # how much overlap do we have? overlap = lib_position - (dialog_position + dialog_width) if overlap < 0: # we are overlapping # check to see if we have enough room to move out of the way if page_width < dialog_width + lib_width: # not enough, need to rezize the editor # look for the resize handle sibblings = dialog_title.find_elements_by_xpath('../../div') handle = None for sib in sibblings: if "ui-resizable-se" in sib.get_attribute('class'): handle = sib # do the resizing chain = ActionChains(browser) chain.click_and_hold(handle) # we can resize editor down to 425px, any less and we cover drop targets chain.move_by_offset(450 - dialog_width, 0).perform() # must click because release is not working. why? I do not know. chain.click().perform() chain.release(None).perform() # recalculate the overlap dialog_title = editor('dialog_title').find_element_by_xpath( '../..') dialog_width = dialog_title.size['width'] dialog_position = dialog_title.location['x'] overlap = lib_position - (dialog_position + dialog_width) # We are good, move out! chain = ActionChains(browser) chain.click_and_hold(editor('dialog_title').element) chain.move_by_offset(overlap, 0).perform() # must click because release is not working. why? I do not know. chain.click().perform() chain.release(None).perform() # recalculate the overlap dialog_title = editor('dialog_title').find_element_by_xpath( '../..') dialog_width = dialog_title.size['width'] dialog_position = dialog_title.location['x'] overlap = lib_position - (dialog_position + dialog_width) if overlap < 0: # we still have a problem. eq( True, False, "Could not move or rezise the editor dialog so it is not " "overlapping the library. The browser window is too small") def get_dataflow_fig_in_globals(self, name): '''Find the named dataflow fig in the global dataflow editor''' all_figs = self.get_dataflow_figures() for fig in all_figs: location = fig.get_parent().get_attribute('id') if location == "top-dataflow": return fig return None def put_element_on_grid(self, type_str): '''find 'type_str' in the Library, drag and drop it onto the grid''' browser = self.browser grid = browser.find_element_by_xpath('//div[@id="-dataflow"]') for retry in range(3): try: objtype = self.find_library_button(type_str) chain = ActionChains(browser) chain.click_and_hold(objtype) chain.move_to_element_with_offset(grid, 10, 10).perform() except StaleElementReferenceException: if retry < 2: logging.warning( 'put_element_on_grid %s:' ' StaleElementReferenceException', type_str) else: raise else: break self.check_highlighting(grid, True, "Grid") self.release(chain) # deal with the modal dialog name = NameInstanceDialog(self).create_and_dismiss() # make sure it is on the grid self.ensure_names_in_workspace( [name], "Dragging '" + type_str + "' to grid did not produce a new element on page") return name def drag_and_drop(self, element, target, should_drop, message='Target'): '''Drag and drop an element onto a target''' chain = self.drag_element_to(element, target, True) chain.move_by_offset(25, 0).perform() time.sleep(1.0) # give it a second to update the figure self.check_highlighting(target, should_highlight=should_drop, message=message) self.release(chain) def ensure_names_in_workspace(self, names, message=None): """ensures the list of element names in included in the workspace""" allnames = self.get_dataflow_component_names() # sometimes does not load all of the names for some reason. # Reloading seems to fix the problem try_reload = False for name in names: if not name in allnames: try_reload = True if try_reload: time.sleep(.1) allnames = self.get_dataflow_component_names() # now we will assert that the elements that we added appear on the page for name in names: eq(name in allnames, True, '%s: %s' % (message, name)) def check_highlighting(self, element, should_highlight=True, message='Element'): '''check to see that the background-color of the element is highlighted ''' if 'SlotFigure' in element.get_attribute('class'): # a slot figure is a div containing a ul element (the context menu) and # one or more svg elements, each of which contains a rect and two texts # the last rect fill style is what we need to check for highlighting rect = element.find_elements_by_css_selector('svg rect')[-1] style = rect.get_attribute('style') else: style = element.get_attribute('style') highlighted = ('background-color: rgb(207, 214, 254)' in style) \ or ('highlighted.png' in style) \ or ('fill: #cfd6fe' in style) eq( highlighted, should_highlight, message + (' did not highlight (and should have) ' if should_highlight else ' highlighed (and should not have) ') + 'when dragging a dropable element to it')
class ComponentPage(DialogPage): class Variable(object): def __init__(self, row, headers): self._cells = row.cells def getter(cls, index=0): return cls._cells[index] def setter(cls, value, index=0): if not cls._cells[index].editable: raise AttributeError("can't set attribute") else: cls._cells[index].value = value for index in range(len(headers)): if headers[index].value != "": setattr( self.__class__, headers[index].value.lower(), property(partial(getter, index=index), partial(setter, index=index))) """ Component editor page. """ Version = type('Enum', (), {"OLD": 1, "NEW": 2}) As = type('Enum', (), {"GRID": 0, "ROW": 1, "VARIABLE": 2}) SortOrder = type('Enum', (), {"ASCENDING": 0, "DESCENDING": 1}) inputs_tab = ButtonElement((By.XPATH, "div/ul/li/a[text()='Inputs']")) slots_tab = ButtonElement((By.XPATH, "div/ul/li/a[text()='Slots']")) outputs_tab = ButtonElement((By.XPATH, "div/ul/li/a[text()='Outputs']")) events_tab = ButtonElement((By.XPATH, "div/ul/li/a[text()='Events']")) inputs = GridElement((By.ID, 'Inputs_props')) outputs = GridElement((By.ID, 'Outputs_props')) inputs_filter = InputElement((By.ID, 'Inputs_variableFilter')) inputs_clear = ButtonElement((By.ID, 'Inputs_clear')) outputs_filter = InputElement((By.ID, 'Outputs_variableFilter')) outputs_clear = ButtonElement((By.ID, 'Outputs_clear')) def __init__(self, browser, port, locator, version=Version.OLD): super(ComponentPage, self).__init__(browser, port, locator) # It takes a while for the full load to complete. NotifierPage.wait(self) self.version = version self._sort_order = {"inputs": 0, "outputs": 0} self._column_picker = None def get_tab_labels(self): """ Return a list of the tab labels. """ elements = self.root.find_elements_by_class_name('ui-tabs-anchor') labels = [element.text for element in elements] return labels def set_input(self, name, value): """ Set input `name` to `value`. """ self('inputs_tab').click() grid = self.inputs found = [] for row in grid.rows: if row[1] == name: row[2] = value return found.append(row[1]) raise RuntimeError('%r not found in inputs %s' % (name, found)) def filter_inputs(self, filter_text): """ Filter out input variables from grid using `filter_text`. """ self.inputs_filter = filter_text def filter_outputs(self, filter_text): """ Filter out output variables from grid using `filter_text`. """ self.outputs_filter = filter_text def clear_inputs_filter(self): """ Clear input variable filter. """ self('inputs_clear').click() def clear_outputs_filter(self): """ Clear output variable filter. """ self('outputs_clear').click() def _sort_column(self, grid, column_name, sort_order, tab): """ Sorts the variables in column `column_name`""" header = [ header for header in grid.headers if header.value == column_name ] if (not header): raise Exception("Grid has no column named %s" % column_name) header = header[0] if (sort_order == self.SortOrder.ASCENDING): while ((self._sort_order[tab] % 2) == 0): self._sort_order[tab] = self._sort_order[tab] + 1 header.click() else: while ((self._sort_order[tab] % 2) != 0): self._sort_order[tab] = self._sort_order[tab] + 1 header.click() def sort_inputs_column(self, column_name, sort_order=SortOrder.ASCENDING): """ Sort `column_name` in inputs grid in `sort_order` """ self("inputs_tab").click() self._sort_column(self.inputs, column_name, sort_order, "inputs") def sort_outputs_column(self, column_name, sort_order=SortOrder.ASCENDING): """ Sort `column_name` in outputs grid in `sort_order` """ self("outputs_tab").click() self._sort_column(self.outputs, column_name, sort_order, "outputs") def get_events(self): """ Return events grid. """ self('events_tab').click() return self.events def show_inputs(self): """switch to inputs tab""" self('inputs_tab').click() def show_outputs(self): """switch to outputs tab""" self('outputs_tab').click() def show_slots(self): """switch to slots tab""" self('slots_tab').click() def toggle_column_visibility(self, column_name): self._toggle_column_visibility(self._active_grid, column_name) @property def _active_grid(self): if self.inputs.displayed: return self.inputs elif self.outputs.displayed: return self.outputs def _toggle_column_visibility(self, grid, column_name): if not self._column_picker: self._column_picker = self._get_column_picker(grid) elif not self._column_picker.displayed: self._column_picker = self._get_column_picker(grid) self._column_picker.get_option(column_name).click() def _get_column_picker(self, grid): grid.headers[0].context_click() column_pickers = [ GridColumnPicker(self.browser, element) for element in self.browser.find_elements(By.CLASS_NAME, "slick-columnpicker") ] for column_picker in column_pickers: if column_picker.displayed: return column_picker def get_inputs(self, return_type=None): """ Return inputs grid. """ self('inputs_tab').click() return self._get_variables(self.inputs, return_type) def get_outputs(self, return_type=None): """ Return outputs grid. """ self('outputs_tab').click() return self._get_variables(self.outputs, return_type) def get_input(self, name, return_type=None): """ Return first input variable with `name`. """ self('inputs_tab').click() return self._get_variable(name, self.inputs, return_type) def get_output(self, name, return_type=None): """ Return first output variable with `name`. """ self('outputs_tab').click() return self._get_variable(name, self.outputs, return_type) def _get_variables(self, grid, return_type): if (return_type == self.As.GRID or self.version == self.Version.OLD): return grid headers = grid.headers rows = grid.rows return [self.Variable(row, headers) for row in rows] def _get_variable(self, name, grid, return_type): found = [] for row in grid.rows: if row[1] == name: if (return_type == self.As.ROW or self.version == self.Version.OLD): return row else: return self.Variable(row, grid.headers) found.append(row[1]) raise RuntimeError('%r not found in inputs %s' % (name, found))