| # Copyright 2015 Google Inc. All rights reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| import os |
| import sys |
| import time |
| |
| from selenium import webdriver |
| from selenium.common.exceptions import NoSuchElementException |
| from selenium.webdriver.common.by import By |
| from selenium.webdriver.support.ui import WebDriverWait |
| from selenium.webdriver.support import expected_conditions as EC |
| |
| # Global application (there should only ever be one!) |
| |
| g_app = None |
| |
| # Services |
| |
| class Services: |
| def __init__(self, driver): |
| self.driver = driver |
| |
| # DeviceConfig |
| |
| class DeviceConfig: |
| def __init__(self, root): |
| self.root = root |
| |
| # Find child relevant elements. |
| # \todo [petri] would it be better to wrap these as functions instead of exposing Selenium elements? |
| # \todo [petri] always query Selenium elements on-demand to avoid any potential issues with dynamically loaded/changing data? |
| self.titleBar = root.find_element_by_id("titleBar") |
| self.expandButton = root.find_element_by_id("expandButton") |
| self.name = root.find_element_by_id("name") |
| self.targetAddress = root.find_element_by_id("targetAddress") |
| self.targetPort = root.find_element_by_id("targetPort") |
| self.spawnLocalProcess = root.find_element_by_id("spawnLocalProcess") |
| self.binaryName = root.find_element_by_id("testBinaryName") |
| self.workingDir = root.find_element_by_id("testBinaryWorkingDir") |
| self.commandLine = root.find_element_by_id("testBinaryCommandLine") |
| self.saveButton = root.find_element_by_id("saveButton") |
| try: # \note 'new device' doesn't have delete button |
| self.deleteButton = root.find_element_by_id("deleteButton") |
| except NoSuchElementException: |
| pass |
| |
| def select(self): |
| self.titleBar.click() |
| g_app.waitRPC() |
| |
| # DeviceConfigList |
| |
| class DeviceConfigList: |
| def __init__(self, root): |
| self.root = root |
| |
| def getNewDevice(self): |
| return DeviceConfig(self.root.find_element_by_xpath("//div[@id='newDeviceConfig']")) |
| |
| def getDeviceByName(self, name): |
| deviceRoot = self.root.find_element_by_xpath("//b[text() = '%s']/ancestor::div[@id='deviceConfig']" % name) |
| return DeviceConfig(deviceRoot) |
| |
| # TestSet |
| |
| class TestSet: |
| def __init__(self, root): |
| self.root = root |
| |
| def foo(self): |
| pass |
| |
| # TestCaseSelection |
| |
| class TestCaseSelection: |
| def __init__(self, root): |
| self.root = root |
| |
| #self.testSetList = TestSetList(self.root_find_element_by_id()) |
| self.extraTestFilters = self.root.find_element_by_id("testNameFilters") |
| |
| # TestLaunchPage |
| |
| class TestLaunchPage: |
| def __init__(self, driver): |
| self.driver = driver |
| |
| self.executeButton = driver.find_element_by_id("executeButton") |
| |
| def getDeviceConfigList(self): |
| return DeviceConfigList(self.driver.find_element_by_xpath("//div[@id='deviceConfigList']")) |
| |
| def getTestCaseSelection(self): |
| return TestCaseSelection(self.driver.find_element_by_id("testCaseSelection")) |
| |
| def execute(self): |
| self.executeButton.click() |
| |
| # BatchResult |
| |
| class BatchResult: |
| def __init__(self, root): |
| self.root = root |
| self.name = root.find_element_by_id("batchResultName").text |
| |
| def __str__(self): |
| return self.name |
| |
| # BatchResultListPage |
| |
| class BatchResultListPage: |
| def __init__(self, driver): |
| self.driver = driver |
| |
| def getBatchResults(self): |
| elems = self.driver.find_elements_by_xpath("//a[@id='batchResult']") |
| return [BatchResult(elem) for elem in elems] |
| |
| # TreeNodeType |
| |
| class TreeNodeType: |
| Group = 0 |
| Leaf = 1 |
| |
| @staticmethod |
| def getName(value): |
| #print dir(TreeNodeType) |
| return dir(TreeNodeType)[value] |
| |
| # TestCaseTreeNode |
| |
| class TestCaseTreeNode: |
| def __init__(self, root, nodeType): |
| self.root = root |
| self.nodeType = nodeType |
| self.labelNode = root.find_element_by_id('nodeLabel') |
| self.label = self.labelNode.text |
| self.path = root.find_element_by_id('nodePath').get_attribute('node-path') |
| assert(self.label != '') |
| |
| def select(self): |
| self.labelNode.click() |
| g_app.waitRPC() |
| |
| def isExpanded(self): |
| classes = self.root.get_attribute('class').split(' ') |
| #print "CL:", classes |
| return 'tree-expanded' in classes |
| |
| def toggleExpand(self): |
| assert(self.nodeType == TreeNodeType.Group) |
| treeHead = self.root.find_element_by_css_selector('.tree-branch-head') |
| treeHead.click() # \todo [petri] check that node is currently unexpanded? |
| |
| def __str__(self): |
| return "node: path='%s' type=%s" % (self.path, TreeNodeType.getName(self.nodeType)) |
| |
| # TestCaseTree |
| |
| class TestCaseTree: |
| def __init__(self, root): |
| self.root = root |
| |
| def getVisibleGroupNodes(self): |
| elems = self.root.find_elements_by_id('testCaseGroupNode') |
| elems = [elem.find_element_by_xpath('(ancestor::li)[last()]') for elem in elems] # get matching <li> node (real root for tree node) |
| return [TestCaseTreeNode(elem, TreeNodeType.Group) for elem in elems] |
| |
| def getVisibleLeafNodes(self): |
| elems = self.root.find_elements_by_id('testCaseLeafNode') |
| elems = [elem.find_element_by_xpath('(ancestor::li)[last()]') for elem in elems] # get matching <li> node (real root for tree node) |
| return [TestCaseTreeNode(elem, TreeNodeType.Leaf) for elem in elems] |
| |
| def getVisibleNodes(self): |
| return self.getVisibleGroupNodes() + self.getVisibleLeafNodes() |
| |
| def getTestCaseNode(self, nodePath): |
| allNodes = self.getVisibleNodes() |
| for node in allNodes: |
| if node.path == nodePath: |
| #print "FOUND:", node.label, node.path |
| return node |
| else: |
| # \todo [petri] expand parent nodes until desired node is found? |
| raise Exception("test case node not found with path '%s'" % nodePath) |
| |
| def expandVisibleGroup(self, nodePath): |
| groupNodes = self.getVisibleGroupNodes() |
| for node in groupNodes: |
| if node.path == nodePath: |
| assert(not node.isExpanded()) |
| node.toggleExpand() |
| g_app.waitRPC() |
| break |
| else: |
| raise Exception('no visible group node found with path="%s"' % nodePath) |
| |
| # def getVisibleNodes(self): |
| # return |
| |
| # DetailsView |
| |
| class DetailsView: |
| def __init__(self, root): |
| self.root = root |
| |
| self.title = root.find_element_by_id('title').text |
| self.status = root.find_element_by_id('status').text |
| |
| # BatchResultPage |
| |
| class BatchResultPage: |
| def __init__(self, driver): |
| self.driver = driver |
| |
| self.name = self.driver.find_element_by_id('batchResultName') |
| self.status = self.driver.find_element_by_id('batchResultStatus') |
| |
| def waitUntilFinished(self): |
| WebDriverWait(self.driver, 600).until(EC.invisibility_of_element_located((By.XPATH, "//div[@id='batchResultSpinner']/div"))) |
| |
| def getStatus(self): |
| return self.driver.find_element_by_id('batchResultStatus').text |
| |
| def getTestCaseTree(self): |
| return TestCaseTree(self.driver.find_element_by_id('testCaseTree')) |
| |
| def getDetailsView(self): |
| return DetailsView(self.driver.find_element_by_id('testCaseContainer')) |
| |
| # Application |
| |
| class Application: |
| def __init__(self, driver, browser): |
| self.driver = driver |
| self.browser = browser |
| #self.spinner = driver.find_element_by_id("globalSpinner") |
| |
| # \todo [petri] bit of a kludge but didn't really want to pass this all over the place |
| global g_app |
| g_app = self |
| |
| def waitRPC(self): |
| # Kludge extra sleep with IE, because it doesn't seem to wait for animations to complete automatically. |
| if g_app.browser == 'ie': |
| time.sleep(1) |
| |
| # RPC loading is ready when globalSpinner has no 'div' as its child. |
| WebDriverWait(self.driver, 10).until(EC.invisibility_of_element_located((By.XPATH, "//div[@id='globalSpinner']/div"))) |
| |
| def gotoTestLaunchPage(self): |
| self.driver.find_element_by_link_text('Tests').click() |
| self.waitRPC() |
| |
| def gotoBatchResultListPage(self): |
| self.driver.find_element_by_link_text('Results').click() |
| self.waitRPC() |
| |
| def gotoBatchResultPage(self, batchResultId): |
| # id is of form: 2014-05-15T15:47:35+03:00 |
| url = '/#/results/batch/%s/testGroup/' % batchResultId |
| # \note using driver.get() apparently doesn't work with Angular-style #-prefixed urls |
| self.driver.execute_script('window.location = "%s";' % url) |
| self.waitRPC() |
| |
| def getTestLaunchPage(self): |
| # \todo [petri] assert we're on right page or make this func gotoTestLaunchPage? |
| return TestLaunchPage(self.driver) |
| |
| def getBatchResultListPage(self): |
| return BatchResultListPage(self.driver) |
| |
| def getBatchResultPage(self): |
| return BatchResultPage(self.driver) |