├── dist ├── knitter-0.1.0.zip ├── knitter-0.2.0.zip ├── knitter-0.3.0.zip ├── knitter-0.3.1.zip ├── knitter-0.3.2.zip ├── knitter-0.3.3.zip ├── knitter-0.3.5.zip ├── knitter-0.4.0.zip └── knitter-1.0.0.tar.gz ├── .gitignore ├── knitter ├── __init__.py ├── configure.py ├── datadriver.py ├── library.py ├── executor.py ├── reportor.py ├── logger.py └── webelement.py ├── README.md ├── setup.py ├── doc ├── version-history.md └── tutorial.md └── LICENSE /dist/knitter-0.1.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hw712/knitter/HEAD/dist/knitter-0.1.0.zip -------------------------------------------------------------------------------- /dist/knitter-0.2.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hw712/knitter/HEAD/dist/knitter-0.2.0.zip -------------------------------------------------------------------------------- /dist/knitter-0.3.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hw712/knitter/HEAD/dist/knitter-0.3.0.zip -------------------------------------------------------------------------------- /dist/knitter-0.3.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hw712/knitter/HEAD/dist/knitter-0.3.1.zip -------------------------------------------------------------------------------- /dist/knitter-0.3.2.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hw712/knitter/HEAD/dist/knitter-0.3.2.zip -------------------------------------------------------------------------------- /dist/knitter-0.3.3.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hw712/knitter/HEAD/dist/knitter-0.3.3.zip -------------------------------------------------------------------------------- /dist/knitter-0.3.5.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hw712/knitter/HEAD/dist/knitter-0.3.5.zip -------------------------------------------------------------------------------- /dist/knitter-0.4.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hw712/knitter/HEAD/dist/knitter-0.4.0.zip -------------------------------------------------------------------------------- /dist/knitter-1.0.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hw712/knitter/HEAD/dist/knitter-1.0.0.tar.gz -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.pyc 3 | 4 | .project 5 | .settings 6 | .pydevproject 7 | .idea/ 8 | 9 | *.egg-info/ 10 | 11 | venv/ 12 | 13 | -------------------------------------------------------------------------------- /knitter/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | HISTORY 4 | Version 1.0.1: 5 | - Support Python 3.5 and plus. Python 2.7 is deprecated. 6 | - Deprecated the multi-threading feature. 7 | - Rewrote all implementations of executor. 8 | - Updated HTML report. 9 | - Updated WebElement methods. 10 | """ 11 | __version__ = "1.0.1" 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Knitter ['nitə] 2 | -------------------------------------------- 3 | 4 | A Web Automation Test Framework Based on Selenium WebDriver. (Python 3, Selenium) 5 | 6 | 7 | Documents 8 | -------------------------------------------- 9 | 10 | - https://outsidematrix.com/note/QA/Knitter.html 11 | 12 | - https://outsidematrix.com/note/QA/Knitter-Tutorial.html 13 | 14 | 15 | Example 16 | -------------------------------------------- 17 | 18 | - https://github.com/hw712/knitter-example 19 | 20 | 21 | License 22 | -------------------------------------------- 23 | 24 | - GPL License 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /knitter/configure.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Browser: 4 | StartURL = "" 5 | RunningBrowser = None 6 | AvailableBrowsers = [] 7 | HeadlessMode = False 8 | 9 | class FireFox: 10 | Driver = "" 11 | Binary = "" 12 | 13 | class Chrome: 14 | Driver = "" 15 | 16 | class IE: 17 | Driver = "" 18 | 19 | 20 | class General: 21 | class TestCase: 22 | Name = "" 23 | Pass = True 24 | Warnings = "" 25 | StartTime = "" 26 | EndTime = "" 27 | ScreenShot = "" 28 | 29 | class TestModule: 30 | Name = "" 31 | 32 | class Total: 33 | StartTime = "" 34 | EndTime = "" 35 | NumberOfTestCasePass = 0 36 | NumberOfTestCaseFail = 0 37 | 38 | class Path: 39 | Result = "" 40 | 41 | VersionInfo = {} 42 | 43 | HTMLReporterLines = [] 44 | 45 | # Only show log in the console, and exit all testing when any case fail. 46 | QuickTest = False 47 | 48 | 49 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | 2 | from setuptools import setup 3 | 4 | 5 | setup( 6 | name='knitter', 7 | version='1.0.0', 8 | 9 | author='Henry Wang', 10 | author_email='outsidematrix@126.com', 11 | maintainer='Henry Wang', 12 | maintainer_email='outsidematrix@126.com', 13 | 14 | url='https://github.com/hw712/knitter', 15 | 16 | description='A Web Automation Test Framework Based On Selenium WebDriver', 17 | long_description="Knitter['nitə] is a web automation test framework, with which you can develop " 18 | "the web ui automation with good maintainability and extendability.", 19 | 20 | # https://pypi.org/classifiers/ 21 | classifiers=['License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 22 | 'Topic :: Software Development :: Testing', 23 | 'Topic :: Software Development :: Quality Assurance', 24 | 'Topic :: Software Development :: Libraries :: Application Frameworks'], 25 | 26 | platforms=['linux', 'windows'], 27 | 28 | license='BSD License', 29 | 30 | packages=['knitter'], 31 | 32 | install_requires=['selenium', 'xlrd'], 33 | ) 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /doc/version-history.md: -------------------------------------------------------------------------------- 1 | **Version History** 2 | 3 | ----------------------------------------------------------------------- 4 | 5 | ##### 1.0.0 (2018-05-08) 6 | 7 | - Support Python 3.5 and plus. Python 2.7 is deprecated. 8 | 9 | - Deprecated the multi-threading feature. 10 | 11 | - Rewrote all implementations of executor and configure. 12 | 13 | - Updated HTML report. 14 | 15 | - Updated WebElement methods to support the latest selenium. 16 | 17 | ----------------------------------------------------------------------- 18 | 19 | ##### 0.3.5 (2015-06-06) 20 | 21 | + Fixed bugs. 22 | 23 | ----------------------------------------------------------------------- 24 | 25 | ##### 0.3.4 (2015-02-05) 26 | 27 | + Fixed command line running error issue. 28 | 29 | ----------------------------------------------------------------------- 30 | 31 | ##### 0.3.3 (2014-09-22) 32 | 33 | + Added new operations in WebElement. 34 | 35 | + Bug fix. 36 | 37 | ----------------------------------------------------------------------- 38 | 39 | ##### 0.3.2 (2014-09-17) 40 | 41 | + Added wingui.py to support upload/download dialog on MS Windows System. (Need install "SendKeys" and "PyWin32" manually) 42 | 43 | + Added support for multi-window operations. 44 | 45 | + Updated alert handling. 46 | 47 | + Fix bugs of datadriver and webelement operations. 48 | 49 | ----------------------------------------------------------------------- 50 | 51 | ##### 0.3.1 (2014-09-15) 52 | 53 | + Bug fix 54 | 55 | ----------------------------------------------------------------------- 56 | 57 | #### 0.3.0 (2014-09-01) 58 | 59 | + Add webelement::WebBrowser, for all browser operations, such as scroll to, etc. 60 | 61 | + Finished all normal supports. First version for real test. 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /knitter/datadriver.py: -------------------------------------------------------------------------------- 1 | from knitter import logger 2 | import xlrd 3 | 4 | 5 | class ExcelSheet: 6 | def __init__(self, excel, sheet): 7 | """ 8 | :param excel: Location of Excel. e.g. "C:/Archive/TestExcel.xlsx" 9 | :param sheet: Sheet Name. e.g. "Sheet1" 10 | """ 11 | self.excel = xlrd.open_workbook(excel) 12 | self.sheet = self.excel.sheet_by_name(sheet) 13 | 14 | # something like: [['', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', '']] 15 | self.data = [["" for x in range(self.sheet.ncols)] for y in range(self.sheet.nrows)] 16 | 17 | for i in range(self.sheet.nrows): 18 | for j in range(self.sheet.ncols): 19 | self.data[i][j] = self.cellxy(i, j) 20 | 21 | def nrows(self): 22 | return self.sheet.nrows 23 | 24 | def ncols(self): 25 | return self.sheet.ncols 26 | 27 | def cellxy(self, rowx, colx): 28 | """ 29 | Description: 30 | If the cell value is number, 1234 will get as 1234.0, so fix this issue. 31 | 32 | Reference: 33 | http://stackoverflow.com/questions/2739989/reading-numeric-excel-data-as-text-using-xlrd-in-python 34 | http://www.lexicon.net/sjmachin/xlrd.html (Search for "ctype") 35 | 36 | self.sheet.cell(rowx, colx).ctype: 37 | Type symbol Type number Python value 38 | XL_CELL_EMPTY 0 empty string u'' 39 | XL_CELL_TEXT 1 a Unicode string 40 | XL_CELL_NUMBER 2 float 41 | XL_CELL_DATE 3 float 42 | XL_CELL_BOOLEAN 4 int; 1 means TRUE, 0 means FALSE 43 | ...... 44 | """ 45 | 46 | cell_value = self.sheet.cell(rowx, colx).value 47 | 48 | if self.sheet.cell(rowx, colx).ctype in (2, 3) and int(cell_value) == cell_value: 49 | cell_value = int(cell_value) 50 | 51 | return str(cell_value) 52 | 53 | def cell(self, rowx, col_name): 54 | for colx in range(0, self.ncols()): 55 | if self.cellxy(0, colx) == col_name: 56 | logger.step_normal("ExcelSheet.cellx(%s, %s)=[%s]" % (rowx, col_name, self.cellxy(rowx, colx))) 57 | return self.cellxy(rowx, colx) 58 | 59 | return None 60 | 61 | def cell_by_colname(self, rowx, col_name): 62 | for colx in range(0, self.sheet.ncols): 63 | if self.data[0][colx] == col_name: 64 | logger.step_normal("Excel Column [%s]=[%s]" % (col_name, self.data[rowx][colx])) 65 | return self.data[rowx][colx] 66 | 67 | return None 68 | 69 | def cell_by_rowname(self, row_name, colx): 70 | for rowx in range(0, self.sheet.nrows): 71 | if self.data[rowx][0] == row_name: 72 | logger.step_normal("Excel [row-name:%s, col:%s]=[%s]" % (row_name, colx, self.data[rowx][colx])) 73 | return self.data[rowx][colx] 74 | 75 | return None 76 | 77 | def cell_by_row_and_col_name(self, row_name, col_name): 78 | for rowx in range(0, self.nrows()): 79 | if self.cellxy(rowx, 0) == row_name: 80 | logger.step_normal("Excel row_name=" + row_name + ", col_name=" + col_name + ", value=" + self.cell(rowx, col_name)) 81 | return self.cell(rowx, col_name) 82 | 83 | return None 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /knitter/library.py: -------------------------------------------------------------------------------- 1 | import os 2 | import inspect 3 | import datetime 4 | import sys 5 | import shutil 6 | 7 | from knitter.configure import General 8 | from knitter import logger 9 | 10 | def get_sub_folder_names(full_path): 11 | return [ name for name in os.listdir(full_path) if os.path.isdir(os.path.join(full_path, name)) ] 12 | 13 | 14 | def get_value_from_conf(conf_file, key): 15 | 16 | if not os.path.exists(conf_file): 17 | return "" 18 | 19 | if not os.path.isfile(conf_file): 20 | return "" 21 | 22 | try: 23 | with open(conf_file, 'r') as f: 24 | while True: 25 | data = f.readline() 26 | 27 | if not data: 28 | break 29 | 30 | if len(data.split('=')) < 2: 31 | continue 32 | 33 | if data.strip()[0] == "#": 34 | continue 35 | 36 | if data.split('=')[0].strip() == key: 37 | return str(data.split('=', 1)[1].strip()) 38 | except IOError: 39 | return "" 40 | 41 | 42 | def version_info(): 43 | from knitter import __version__ as knitter_version 44 | from selenium import __version__ as selenium_version 45 | from sys import version as python_version 46 | 47 | browser_version = "" 48 | for k, v in General.VersionInfo.items(): 49 | browser_version += "%s %s, " % (k, v) 50 | 51 | return "Python %s, %sKnitter %s, Selenium %s" % (python_version.split(" ")[0], 52 | browser_version, knitter_version, selenium_version) 53 | 54 | 55 | def timestamp_date(): 56 | return datetime.datetime.now().strftime("%Y-%m-%d") 57 | 58 | 59 | def timestamp_date_and_time(): 60 | return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") 61 | 62 | 63 | def timestamp_for_file_name(): 64 | return datetime.datetime.now().strftime("%Y-%m-%d_%H_%M_%S") 65 | 66 | 67 | def exception_error(): 68 | error_message = "" 69 | for i in range(len(inspect.trace())): 70 | error_line = """ 71 | File: %s - [%s] 72 | Function: %s 73 | Statement: %s 74 | -------------------------------------------------------------------------------------------""" % \ 75 | (inspect.trace()[i][1], inspect.trace()[i][2], inspect.trace()[i][3], inspect.trace()[i][4]) 76 | 77 | error_message = "%s%s" % (error_message, error_line) 78 | error_message = """Error! 79 | %s 80 | %s 81 | ======================================== Error Message ====================================%s 82 | 83 | ======================================== Error Message ======================================================""" % \ 84 | (sys.exc_info()[0], sys.exc_info()[1], error_message) 85 | 86 | return error_message 87 | 88 | 89 | def delete_folder(folder_path): 90 | if os.path.exists(folder_path): 91 | shutil.rmtree(folder_path) 92 | 93 | 94 | def delete_file_or_folder(file_full_path): 95 | if os.path.exists(file_full_path): 96 | if os.path.isdir(file_full_path): 97 | delete_folder(file_full_path) 98 | else: 99 | os.remove(file_full_path) 100 | 101 | 102 | def copy(src, destination): 103 | try: 104 | if os.path.isdir(src): 105 | shutil.copytree(src, destination) 106 | else: 107 | shutil.copy(src, destination) 108 | except Exception as e: 109 | logger.handle_exception(e) 110 | 111 | 112 | def create_folder(path): 113 | if not os.path.exists(path): 114 | os.makedirs(path) 115 | 116 | 117 | if __name__ == "__main__": 118 | version_info() 119 | -------------------------------------------------------------------------------- /doc/tutorial.md: -------------------------------------------------------------------------------- 1 | Tutorial 2 | ======================================================================= 3 | 4 | 1. Installation 5 | ----------------------------------------------------------------------- 6 | 7 | pip install knitter 8 | 9 | 10 | 2. Preconditions 11 | ----------------------------------------------------------------------- 12 | 13 | + Python 3.5+ 14 | 15 | + Depends on "selenium", "xlrd", packages, which will be installed while installing knitter. 16 | 17 | + Drivers 18 | 19 | - [Chrome Driver](http://chromedriver.storage.googleapis.com/index.html) 20 | 21 | - [IE Driver](http://selenium-release.storage.googleapis.com/index.html) 22 | 23 | - You can also find both driver files [here](https://github.com/hw712/knitter-example/tree/master/driver). 24 | 25 | 26 | 27 | 3. Demo project 28 | ----------------------------------------------------------------------- 29 | 30 | + Demo project source code: 31 | 32 | - [https://github.com/hw712/knitter-example](https://github.com/hw712/knitter-example) 33 | 34 | + Demo project test page: 35 | 36 | - [https://outsidematrix.com/knitter/hobby.html](https://outsidematrix.com/knitter/hobby.html) 37 | 38 | 39 | #### Step 1. Create a source code package "demoprj". 40 | 41 | 42 | demoprj/ 43 | page/ 44 | __init__.py 45 | KintterDemo.py # elements of test page 46 | ... 47 | 48 | testcase/ 49 | __init__.py 50 | validations.py # test cases 51 | ... 52 | 53 | __init__.py 54 | conf.py # configurations 55 | 56 | 57 | #### Step 2. Add page elements to "page/KnitterDemo.py" 58 | 59 | 60 | # -*- coding: utf-8 -*- 61 | # All element class must inherit from "WebElement". 62 | 63 | from knitter.webelement import WebElement 64 | from selenium.webdriver.common.by import By 65 | 66 | class Name: 67 | class Title(WebElement): 68 | (by, value) = (By.ID, 'title') 69 | 70 | class Name(WebElement): 71 | (by, value) = (By.ID, 'name') 72 | 73 | class Gender: 74 | class Male(WebElement): 75 | (by, value) = (By.ID, 'male') 76 | 77 | class Female(WebElement): 78 | (by, value) = (By.ID, 'female') 79 | 80 | class Hobby: 81 | class Music(WebElement): 82 | (by, value) = (By.ID, 'music') 83 | 84 | class Sport(WebElement): 85 | (by, value) = (By.ID, 'sport') 86 | 87 | class Travel(WebElement): 88 | (by, value) = (By.ID, 'travel') 89 | 90 | 91 | class SubmitButton(WebElement): 92 | (by, value) = (By.XPATH, '//button[@onclick="do_submit();"]') 93 | 94 | class ResetButton(WebElement): 95 | (by, value) = (By.XPATH, '//button[@onclick="do_reset();"]') 96 | 97 | 98 | class Result(WebElement): 99 | (by, value) = (By.ID, 'result') 100 | 101 | 102 | 103 | 104 | #### Step 3. Add cases to "testcase/validations.py" 105 | 106 | # -*- coding: utf-8 -*- 107 | 108 | from knitter import datadriver, log 109 | from demoprj.page import KnitterDemo 110 | 111 | 112 | def TestCase001_NormalInputTest(): 113 | #### Name ### 114 | KnitterDemo.Name.Title.Select("Mr.") 115 | KnitterDemo.Name.Name.Set("Henry.Wang") 116 | 117 | ### Gender ### 118 | KnitterDemo.Gender.Male.Click() 119 | 120 | ### Hobbies ### 121 | KnitterDemo.Hobby.Music.Click() 122 | KnitterDemo.Hobby.Travel.Click() 123 | 124 | ###### Result ###### 125 | KnitterDemo.SubmitButton.Click() 126 | 127 | KnitterDemo.Result.VerifyInnerHTMLContains("Henry.Wang") 128 | KnitterDemo.Result.VerifyAttribute("innerHTML", "Music", method="contain") 129 | 130 | 131 | 132 | 133 | #### Step 4. Add configurations to "conf.py" 134 | 135 | # -*- coding: utf-8 -*- 136 | 137 | 138 | class MSWindows: 139 | BASE_URL = 'https://outsidematrix.com/knitter/hobby.html' 140 | TESTING_BROWSERS = 'Firefox' 141 | 142 | 143 | 144 | 145 | #### Step 5. Setup "run.py", and run it. 146 | 147 | # -*- coding: utf-8 -*- 148 | 149 | from knitter import executer 150 | from demoprj import conf, testcase 151 | 152 | 153 | executer.run(conf.ChromeDemo, testcase.validations.TestCase001_NormalInputTest) 154 | 155 | 156 | 157 | 158 | #### Step 6. Check result of HTML report. 159 | 160 | result/index.html 161 | 162 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /knitter/executor.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from selenium.webdriver.firefox.firefox_binary import FirefoxBinary 3 | from selenium.webdriver.common.desired_capabilities import DesiredCapabilities 4 | from selenium.webdriver.chrome.options import Options 5 | 6 | import types 7 | import os 8 | import importlib 9 | 10 | from knitter.configure import Browser, General 11 | from knitter import logger 12 | 13 | 14 | def launch_browser(browser, url): 15 | """ 16 | Launch and init a new browser, start with the 'url' 17 | 18 | """ 19 | 20 | if browser == Browser.FireFox: 21 | logger.step_normal("Launch Browser FireFox. URL = " + url) 22 | 23 | firefox_capabilities = DesiredCapabilities.FIREFOX 24 | 25 | # disable the download dialogue 26 | fp = webdriver.FirefoxProfile() 27 | fp.set_preference('browser.download.manager.showWhenStarting', False) 28 | 29 | try: 30 | if Browser.FireFox.Binary == "": 31 | Browser.RunningBrowser = webdriver.Firefox(executable_path=Browser.FireFox.Driver, firefox_profile=fp, 32 | capabilities=firefox_capabilities, 33 | log_path=os.path.join(General.Path.Result, "firefox.log")) 34 | else: 35 | Browser.RunningBrowser = webdriver.Firefox(executable_path=Browser.FireFox.Driver, firefox_profile=fp, 36 | firefox_binary=FirefoxBinary(firefox_path= 37 | Browser.FireFox.Binary), 38 | log_path=os.path.join(General.Path.Result, "firefox.log")) 39 | 40 | if Browser.FireFox.__name__ not in General.VersionInfo: 41 | General.VersionInfo[Browser.FireFox.__name__] = Browser.RunningBrowser.capabilities['browserVersion'] 42 | 43 | except Exception as e: 44 | logger.handle_exception(e) 45 | return False 46 | 47 | elif browser == Browser.Chrome: 48 | try: 49 | chrome_options = Options() 50 | 51 | if Browser.HeadlessMode is True: 52 | chrome_options.add_argument("--headless") 53 | chrome_options.add_argument("--window-size=1920x1080") 54 | chrome_options.add_argument("--no-sandbox") 55 | 56 | Browser.RunningBrowser = webdriver.Chrome(chrome_options=chrome_options, 57 | executable_path=Browser.Chrome.Driver) 58 | 59 | if Browser.Chrome.__name__ not in General.VersionInfo: 60 | General.VersionInfo[Browser.Chrome.__name__] = Browser.RunningBrowser.capabilities['version'] 61 | except Exception as e: 62 | logger.handle_exception(e) 63 | return False 64 | 65 | elif browser == Browser.IE: 66 | ''' 67 | os.popen('TASKKILL /F /IM IEDriverServer.exe') 68 | dc = DesiredCapabilities.INTERNETEXPLORER.copy() 69 | dc['nativeEvents'] = False 70 | dc['acceptSslCerts'] = True 71 | ''' 72 | 73 | try: 74 | Browser.RunningBrowser = webdriver.Ie(executable_path=Browser.IE.Driver) 75 | 76 | Browser.RunningBrowser.capabilities['acceptInsecureCerts'] = True 77 | # Browser.RunningBrowser.capabilities['nativeEvents'] = False 78 | # Browser.RunningBrowser.capabilities['ignoreProtectedModeSettings'] = True 79 | 80 | # print(Browser.RunningBrowser.capabilities) 81 | if Browser.IE.__name__ not in General.VersionInfo: 82 | General.VersionInfo[Browser.IE.__name__] = Browser.RunningBrowser.capabilities['browserVersion'] 83 | except Exception as e: 84 | logger.handle_exception(e) 85 | return False 86 | 87 | Browser.RunningBrowser.set_window_size(1366, 758) 88 | Browser.RunningBrowser.set_window_position(0, 0) 89 | Browser.RunningBrowser.maximize_window() 90 | Browser.RunningBrowser.set_page_load_timeout(300) 91 | Browser.RunningBrowser.implicitly_wait(0) 92 | 93 | Browser.RunningBrowser.get(url) 94 | 95 | return True 96 | 97 | 98 | def quit_browser(): 99 | if Browser.RunningBrowser is not None: 100 | Browser.RunningBrowser.quit() 101 | Browser.RunningBrowser = None 102 | 103 | 104 | def __run_test_module(test_module): 105 | General.TestModule.Name = test_module.__name__.split('.')[-1] 106 | 107 | cases = [] 108 | for fun in dir(test_module): 109 | if (not fun.startswith("__")) and (not fun.endswith("__")) and (isinstance(test_module.__dict__.get(fun), 110 | types.FunctionType)): 111 | if test_module.__dict__.get(fun).__module__ == test_module.__name__: 112 | cases.append(fun) 113 | 114 | for case in cases: 115 | if case == 'before_each_case' or case == 'after_each_case' or case == 'before_launch_browser': 116 | return 117 | 118 | for browser in Browser.AvailableBrowsers: 119 | try: 120 | logger.start_test_case(case, browser) 121 | 122 | if hasattr(test_module, 'before_launch_browser'): 123 | getattr(test_module, 'before_launch_browser')() 124 | 125 | if hasattr(test_module, 'before_each_case'): 126 | getattr(test_module, 'before_each_case')() 127 | 128 | if launch_browser(browser, Browser.StartURL) is False: 129 | raise AssertionError("Launch Browser [" + browser.__name__ + "] Fails!") 130 | 131 | getattr(test_module, case)() 132 | 133 | if hasattr(test_module, 'after_each_case'): 134 | getattr(test_module, 'after_each_case')() 135 | 136 | except Exception as e: 137 | logger.handle_exception(e) 138 | 139 | if General.QuickTest is True: 140 | return 141 | finally: 142 | logger.stop_test_case() 143 | quit_browser() 144 | 145 | 146 | def __run_test_case(case): 147 | test_module = importlib.import_module(case.__module__) 148 | General.TestModule.Name = case.__module__.split('.')[-1] 149 | 150 | for browser in Browser.AvailableBrowsers: 151 | try: 152 | logger.start_test_case(case.__name__, browser) 153 | 154 | if hasattr(test_module, 'before_launch_browser'): 155 | getattr(test_module, 'before_launch_browser')() 156 | 157 | if hasattr(test_module, 'before_each_case'): 158 | getattr(test_module, 'before_each_case')() 159 | 160 | if launch_browser(browser, Browser.StartURL) is False: 161 | raise AssertionError("Launch Browser [" + browser.__name__ + "] Fails!") 162 | 163 | case() 164 | 165 | if hasattr(test_module, 'after_each_case'): 166 | getattr(test_module, 'after_each_case')() 167 | 168 | except Exception as e: 169 | logger.handle_exception(e) 170 | 171 | if General.QuickTest is True: 172 | return 173 | finally: 174 | logger.stop_test_case() 175 | quit_browser() 176 | 177 | 178 | def wrapper(): 179 | def handle_func(func): 180 | def handle_args(*args): 181 | try: 182 | # args[0] should be a conf definition function. 183 | args[0]() 184 | 185 | # remove duplicated items. 186 | Browser.AvailableBrowsers = list(set(Browser.AvailableBrowsers)) 187 | 188 | logger.start_project() 189 | 190 | func(*args) 191 | 192 | logger.end_project() 193 | 194 | except Exception as e: 195 | logger.handle_exception(e) 196 | 197 | return handle_args 198 | return handle_func 199 | 200 | 201 | @wrapper() 202 | def run(*args): 203 | if len(args) < 2: 204 | print("At least 2 args needed for run(), first as configuration, others are test object(s).") 205 | 206 | for i in range(1, len(args)): 207 | run_test_object(args[i]) 208 | 209 | 210 | def run_test_object(obj): 211 | if General.QuickTest is True and General.Total.NumberOfTestCaseFail > 0: 212 | return 213 | 214 | if isinstance(obj, list): 215 | for o in obj: 216 | run_test_object(o) 217 | 218 | elif isinstance(obj, types.ModuleType): 219 | __run_test_module(obj) 220 | 221 | elif isinstance(obj, types.FunctionType): 222 | __run_test_case(obj) 223 | 224 | else: 225 | print("knitter.executor: function [run_test_objects] code error: objects = " + obj) 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | -------------------------------------------------------------------------------- /knitter/reportor.py: -------------------------------------------------------------------------------- 1 | import os 2 | from datetime import datetime 3 | 4 | from knitter import library 5 | from knitter.configure import General 6 | 7 | 8 | def html_source_header(title="Web UI Automation Test Report"): 9 | return """ 10 | 11 | 12 | 13 | 14 | %s 15 | 16 | 81 | 82 | """ % title 83 | 84 | 85 | def html_source_body(title="Web UI Automation Test Report", countdown=True): 86 | if countdown is False: 87 | return """ 88 | 89 |

%s

90 |

91 | Latest Test Report 92 |     93 | History Test Reports 94 |

95 |
96 | """ % title 97 | 98 | return """ 99 | 100 | 116 | 117 |

%s

118 |

119 | Latest Test Report 120 |     121 | History Test Reports 122 |

123 |
124 | 125 | """ % title 126 | 127 | 128 | def html_source_table1(args): 129 | return """ 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 |
Summary
Start Time/Stop TimeDurationTotal CasesPassed CasesFailed Cases
%s => %s%s%s%s%s
148 | """ % tuple(args) 149 | 150 | 151 | def html_source_table_history_header(): 152 | return """ 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | """ 164 | 165 | 166 | def html_source_table_history(*args): 167 | return """ 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | """ % args 176 | 177 | 178 | def html_source_table2(): 179 | return """ 180 |
181 |
182 |
History Reports
FolderDurationTotal CasesPassed CasesFailed Cases
%s%s%s%s%s
183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | """ 193 | 194 | 195 | def html_source_test_cases(test_cases): 196 | return_src_code = "" 197 | 198 | for test_case in test_cases: 199 | return_src_code += """ 200 | 201 | 202 | 203 | 204 | 205 | %s 206 | 207 | """ % tuple(test_case) 208 | 209 | return return_src_code 210 | 211 | 212 | def html_source_end_table(): 213 | return """ 214 |
Details
Start Time/Stop TimeTest CaseDurationBrowserResult
%s %s%s%s
215 |
216 |
217 |
218 | """ 219 | 220 | 221 | def html_source_version_info(): 222 | return u""" 223 |

%s

224 | """ % library.version_info() 225 | 226 | 227 | def html_source_foot(): 228 | return u""" 229 |
230 |

@2014-2019 Powered by Knitter

231 |
232 | 233 | 234 | """ 235 | 236 | 237 | def generate_html_report(test_status, test_cases=[], countdown=True): 238 | if General.QuickTest is True: 239 | return 240 | 241 | library.create_folder(General.Path.Result) 242 | 243 | with open(os.path.join(General.Path.Result, "index.html"), "w") as f: 244 | f.write(html_source_header()) 245 | f.write(html_source_body(countdown=countdown)) 246 | 247 | f.write(html_source_table1(test_status)) 248 | f.write(html_source_table2()) 249 | 250 | f.write(html_source_test_cases(test_cases)) 251 | 252 | f.write(html_source_end_table()) 253 | f.write(html_source_version_info()) 254 | f.write(html_source_foot()) 255 | 256 | 257 | def save_current_report_to_repository(): 258 | if General.QuickTest is True: 259 | return 260 | 261 | report_dir = os.path.join(General.Path.Result, 262 | "%s__%s" % (datetime.strptime(General.Total.StartTime, "%Y-%m-%d %H:%M:%S").strftime("%Y-%m-%d_%H%M%S"), 263 | datetime.strptime(General.Total.EndTime, "%Y-%m-%d %H:%M:%S").strftime("%Y-%m-%d_%H%M%S"))) 264 | library.delete_folder(report_dir) 265 | library.create_folder(report_dir) 266 | 267 | if os.path.exists(os.path.join(General.Path.Result, "testcase")): 268 | library.copy(os.path.join(General.Path.Result, "testcase"), os.path.join(report_dir, "testcase")) 269 | 270 | if os.path.exists(os.path.join(General.Path.Result, "screenshots")): 271 | library.copy(os.path.join(General.Path.Result, "screenshots"), os.path.join(report_dir, "screenshots")) 272 | 273 | if os.path.exists(os.path.join(General.Path.Result, "index.html")): 274 | library.copy(os.path.join(General.Path.Result, "index.html"), report_dir) 275 | 276 | with open(os.path.join(report_dir, "status.ini"), "w") as f: 277 | f.write("Duration=%s\n" % str(datetime.strptime(General.Total.EndTime, "%Y-%m-%d %H:%M:%S") - 278 | datetime.strptime(General.Total.StartTime, "%Y-%m-%d %H:%M:%S"))) 279 | f.write("TotalCases=%s\n" % str(General.Total.NumberOfTestCasePass + General.Total.NumberOfTestCaseFail)) 280 | f.write("PassedCases=%s\n" % str(General.Total.NumberOfTestCasePass)) 281 | f.write("FailedCases=%s\n" % str(General.Total.NumberOfTestCaseFail)) 282 | 283 | 284 | def generate_report_history(): 285 | if General.QuickTest is True: 286 | return 287 | 288 | folders = library.get_sub_folder_names(General.Path.Result) 289 | 290 | reports = [] 291 | for folder in folders: 292 | if folder.startswith("testcase") or folder.startswith("screenshot"): 293 | continue 294 | reports.append(folder) 295 | 296 | with open(os.path.join(General.Path.Result, "history.html"), "w") as f: 297 | f.write(html_source_header(title="Web UI Automation Test History Reports")) 298 | f.write(html_source_body(title="Web UI Automation Test History Reports")) 299 | f.write(html_source_table_history_header()) 300 | 301 | i = 0 302 | for report in sorted(reports, reverse=True): 303 | 304 | Duration = library.get_value_from_conf(os.path.join(General.Path.Result, report, "status.ini"), "Duration") 305 | TotalCases = library.get_value_from_conf(os.path.join(General.Path.Result, report, "status.ini"), "TotalCases") 306 | PassedCases = library.get_value_from_conf(os.path.join(General.Path.Result, report, "status.ini"), "PassedCases") 307 | FailedCases = library.get_value_from_conf(os.path.join(General.Path.Result, report, "status.ini"), "FailedCases") 308 | 309 | f.write(html_source_table_history(report, report, Duration, TotalCases, PassedCases, FailedCases)) 310 | 311 | i = i + 1 312 | 313 | f.write(html_source_end_table()) 314 | f.write(html_source_foot()) 315 | 316 | 317 | if __name__ == "__main__": 318 | General.Path.Result = "D:/" 319 | generate_report_history() 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | -------------------------------------------------------------------------------- /knitter/logger.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import os 3 | import sys 4 | 5 | from knitter.configure import Browser, General 6 | from knitter import library 7 | from knitter import reportor 8 | 9 | 10 | def start_project(): 11 | General.TestCase.Browser = None 12 | General.TestCase.Name = "" 13 | General.TestCase.Pass = True 14 | General.TestCase.Warnings = 0 15 | General.TestCase.StartTime = "" 16 | General.TestCase.EndTime = "" 17 | 18 | General.TestModule.Name = "" 19 | 20 | General.Total.NumberOfTestCasePass = 0 21 | General.Total.NumberOfTestCaseFail = 0 22 | General.Total.StartTime = datetime.now().strftime("%Y-%m-%d %H:%M:%S") 23 | 24 | General.HTMLReporterLines[:] = [] 25 | 26 | library.delete_folder(os.path.join(General.Path.Result, "testcase")) 27 | library.delete_folder(os.path.join(General.Path.Result, "screenshots")) 28 | 29 | reportor.generate_html_report([General.Total.StartTime, "N/A", "N/A", "N/A", "N/A", "N/A"], []) 30 | 31 | print(">>>>>> [%s] => Start Testing...... <<<<<<" % General.Total.StartTime) 32 | 33 | 34 | def end_project(): 35 | General.Total.EndTime = datetime.now().strftime("%Y-%m-%d %H:%M:%S") 36 | 37 | print(">>>>>> [%s] => Test End. Duration [%s], All [%s], Pass [%s], Fail [%s] <<<<<<" % ( 38 | General.Total.EndTime, 39 | datetime.strptime(General.Total.EndTime, "%Y-%m-%d %H:%M:%S") - datetime.strptime(General.Total.StartTime, 40 | "%Y-%m-%d %H:%M:%S"), 41 | General.Total.NumberOfTestCasePass + General.Total.NumberOfTestCaseFail, 42 | General.Total.NumberOfTestCasePass, 43 | General.Total.NumberOfTestCaseFail 44 | )) 45 | 46 | reportor.generate_html_report([General.Total.StartTime, General.Total.EndTime, 47 | datetime.strptime(General.Total.EndTime, "%Y-%m-%d %H:%M:%S") - datetime.strptime(General.Total.StartTime, "%Y-%m-%d %H:%M:%S"), 48 | General.Total.NumberOfTestCasePass + General.Total.NumberOfTestCaseFail, 49 | General.Total.NumberOfTestCasePass, 50 | General.Total.NumberOfTestCaseFail], General.HTMLReporterLines, countdown=False) 51 | reportor.save_current_report_to_repository() 52 | reportor.generate_report_history() 53 | reportor.generate_html_report([General.Total.StartTime, General.Total.EndTime, 54 | datetime.strptime(General.Total.EndTime, "%Y-%m-%d %H:%M:%S") - datetime.strptime(General.Total.StartTime, "%Y-%m-%d %H:%M:%S"), 55 | General.Total.NumberOfTestCasePass + General.Total.NumberOfTestCaseFail, 56 | General.Total.NumberOfTestCasePass, 57 | General.Total.NumberOfTestCaseFail], General.HTMLReporterLines, countdown=True) 58 | 59 | print("") 60 | print(library.version_info()) 61 | 62 | 63 | def handle_exception(e): 64 | if General.TestCase.Pass is False: 65 | return 66 | 67 | if e is not None: 68 | step_normal(e) 69 | 70 | if General.QuickTest is True: 71 | print(library.timestamp_date_and_time() + " Warning: " + str(e)) 72 | 73 | if sys.exc_info()[0] is not None: 74 | step_warning(library.exception_error()) 75 | 76 | if Browser.RunningBrowser is not None: 77 | image = "Fail__%s__%s__%s.png" % (library.timestamp_for_file_name(), General.TestCase.Name, 78 | Browser.RunningBrowser.name) 79 | save_screen_shot(image) 80 | step_normal("Screen short: [%s]" % image) 81 | else: 82 | step_normal("No screen shot for browser because browser is not running.") 83 | 84 | General.TestCase.Pass = False 85 | 86 | 87 | def start_test_case(name, browser): 88 | General.TestCase.Name = name + "-" + browser.__name__ 89 | General.TestCase.StartTime = datetime.now().replace(microsecond=0) 90 | General.TestCase.Pass = True 91 | General.TestCase.Warnings = 0 92 | 93 | write_log(os.path.join("testcase", "%s.log" % General.TestCase.Name), 94 | "\n************** Test Case [%s] [%s] ***************\n" % (name, browser.__name__)) 95 | 96 | 97 | def stop_test_case(): 98 | General.TestCase.EndTime = datetime.now().replace(microsecond=0) 99 | General.Total.EndTime = datetime.now().strftime("%Y-%m-%d %H:%M:%S") 100 | 101 | if General.TestCase.Warnings > 0: 102 | warning_message = ", has [%s] warning(s)!" % General.TestCase.Warnings 103 | else: 104 | warning_message = "" 105 | 106 | if General.TestCase.Pass is True: 107 | print(u"%s [Pass] => [%s] [%s] [%s] [%s]%s" % (library.timestamp_date_and_time(), 108 | General.TestCase.EndTime - General.TestCase.StartTime, 109 | General.TestModule.Name, 110 | General.TestCase.Name, 111 | Browser.RunningBrowser.name, 112 | warning_message)) 113 | General.Total.NumberOfTestCasePass += 1 114 | 115 | General.HTMLReporterLines.append(["%s => %s" % (General.TestCase.StartTime.strftime("%m-%d %H:%M:%S"), 116 | General.TestCase.EndTime.strftime("%m-%d %H:%M:%S")), 117 | '[%s] - %s' % (General.TestCase.Name, 118 | General.TestModule.Name, 119 | General.TestCase.Name), 120 | General.TestCase.EndTime - General.TestCase.StartTime, 121 | Browser.RunningBrowser.name, 122 | 'Pass']) 123 | 124 | else: 125 | print(u"%s [Fail] => [%s] [%s] [%s] [%s]%s" % (library.timestamp_date_and_time(), 126 | General.TestCase.EndTime - General.TestCase.StartTime, 127 | General.TestModule.Name, 128 | General.TestCase.Name, 129 | Browser.RunningBrowser.name, 130 | warning_message)) 131 | General.Total.NumberOfTestCaseFail += 1 132 | 133 | General.HTMLReporterLines.append(["%s => %s" % (General.TestCase.StartTime.strftime("%m-%d %H:%M:%S"), 134 | General.TestCase.EndTime.strftime("%m-%d %H:%M:%S")), 135 | 136 | '[%s] - %s' % (General.TestCase.Name, 137 | General.TestModule.Name, 138 | General.TestCase.Name), 139 | 140 | General.TestCase.EndTime - General.TestCase.StartTime, 141 | Browser.RunningBrowser.name, 142 | 143 | 'Fail' % 144 | General.TestCase.ScreenShot 145 | ]) 146 | 147 | reportor.generate_html_report([General.Total.StartTime, General.Total.EndTime, 148 | datetime.strptime(General.Total.EndTime, "%Y-%m-%d %H:%M:%S") - 149 | datetime.strptime(General.Total.StartTime, "%Y-%m-%d %H:%M:%S"), 150 | General.Total.NumberOfTestCasePass + General.Total.NumberOfTestCaseFail, 151 | General.Total.NumberOfTestCasePass, 152 | General.Total.NumberOfTestCaseFail], General.HTMLReporterLines, countdown=True) 153 | 154 | General.TestCase.Pass = True 155 | General.TestCase.Warnings = 0 156 | 157 | 158 | def write_log(path, message): 159 | if General.QuickTest is True: 160 | return 161 | 162 | log = os.path.join(General.Path.Result, path) 163 | library.create_folder(os.path.dirname(log)) 164 | 165 | with open(log, 'a') as f: 166 | f.write(message) 167 | 168 | 169 | def save_screen_shot(image_name): 170 | if General.QuickTest is True: 171 | return 172 | 173 | image_path = os.path.join(General.Path.Result, "screenshots") 174 | library.create_folder(image_path) 175 | 176 | try: 177 | Browser.RunningBrowser.save_screenshot(os.path.join(image_path, image_name)) 178 | General.TestCase.ScreenShot = image_name 179 | except Exception as e: 180 | handle_exception(e) 181 | 182 | 183 | def step_section(message): 184 | write_log(os.path.join("testcase", "%s.log" % General.TestCase.Name), 185 | "\n%s Section: %s\n" % (library.timestamp_date_and_time(), message)) 186 | 187 | 188 | def step_normal(message): 189 | write_log(os.path.join("testcase", "%s.log" % General.TestCase.Name), 190 | "%s Step: %s\n" % (library.timestamp_date_and_time(), message)) 191 | 192 | 193 | def step_pass(message): 194 | write_log(os.path.join("testcase", "%s.log" % General.TestCase.Name), 195 | "%s Pass: %s\n" % (library.timestamp_date_and_time(), message)) 196 | 197 | 198 | def step_fail(message): 199 | if Browser.RunningBrowser is not None: 200 | image_name = "Fail__%s__%s__%s.png" % (library.timestamp_for_file_name(), General.TestCase.Name, 201 | Browser.RunningBrowser.name) 202 | 203 | write_log(os.path.join("testcase", "%s.log" % General.TestCase.Name), 204 | "------------ Fail [%s] -------------------\n" % library.timestamp_date_and_time()) 205 | write_log(os.path.join("testcase", "%s.log" % General.TestCase.Name), 206 | "%s Fail: %s, Check ScreenShot [%s]\n" % (library.timestamp_date_and_time(), message, image_name)) 207 | write_log(os.path.join("testcase", "%s.log" % General.TestCase.Name), 208 | "------------ Fail [%s] ---------------------------------------\n" % library.timestamp_date_and_time()) 209 | 210 | save_screen_shot(image_name) 211 | else: 212 | write_log(os.path.join("testcase", "%s.log" % General.TestCase.Name), 213 | "------------ Fail [%s] -------------------\n" % library.timestamp_date_and_time()) 214 | write_log(os.path.join("testcase", "%s.log" % General.TestCase.Name), 215 | "%s Fail: %s. And the browser not open.\n" % (library.timestamp_date_and_time(), message)) 216 | write_log(os.path.join("testcase", "%s.log" % General.TestCase.Name), 217 | "------------ Fail [%s] ---------------------------------------\n" % library.timestamp_date_and_time()) 218 | 219 | General.TestCase.Pass = False 220 | 221 | if General.QuickTest is True: 222 | print(library.timestamp_date_and_time() + " Fail Step: " + message) 223 | 224 | raise AssertionError(message) 225 | 226 | 227 | def step_warning(message): 228 | if Browser.RunningBrowser is not None: 229 | image_name = "Fail__%s__%s__%s.png" % (library.timestamp_for_file_name(), General.TestCase.Name, 230 | Browser.RunningBrowser.name) 231 | write_log(os.path.join("testcase", "%s.log" % General.TestCase.Name), 232 | "------------ Warning [%s] -------------------\n" % library.timestamp_date_and_time()) 233 | write_log(os.path.join("testcase", "%s.log" % General.TestCase.Name), 234 | "%s Fail: %s, Check ScreenShot [%s]\n" % (library.timestamp_date_and_time(), message, image_name)) 235 | write_log(os.path.join("testcase", "%s.log" % General.TestCase.Name), 236 | "------------ Warning [%s] ---------------------------------------\n" % library.timestamp_date_and_time()) 237 | 238 | save_screen_shot(image_name) 239 | else: 240 | write_log(os.path.join("testcase", "%s.log" % General.TestCase.Name), 241 | "------------ Warning [%s] -------------------\n" % library.timestamp_date_and_time()) 242 | write_log(os.path.join("testcase", "%s.log" % General.TestCase.Name), 243 | "%s Fail: %s. And the browser not open.\n" % (library.timestamp_date_and_time(), message)) 244 | write_log(os.path.join("testcase", "%s.log" % General.TestCase.Name), 245 | "------------ Warning [%s] ---------------------------------------\n" % library.timestamp_date_and_time()) 246 | 247 | General.TestCase.Warnings += 1 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /knitter/webelement.py: -------------------------------------------------------------------------------- 1 | import time 2 | import sys 3 | from selenium import webdriver 4 | from selenium.webdriver.common.keys import Keys 5 | from selenium.common.exceptions import NoSuchElementException, NoAlertPresentException, UnexpectedAlertPresentException 6 | from selenium.common.exceptions import ElementClickInterceptedException 7 | 8 | from knitter.configure import Browser 9 | from knitter import logger 10 | 11 | 12 | class CompatibleMethod(object): 13 | """If the first argument(self. is a class, set/use properties of the class. If the 14 | first argument is a instance, set/use properties of the instance. 15 | 16 | This is an extention version for "@classmethod", used for multi-thread issues 17 | of the framework. 18 | 19 | EXAMPLE 20 | 21 | class A: 22 | a = 0 23 | 24 | @CompatibleMethod 25 | def test(self, aaa): 26 | self.a = aaa 27 | 28 | @CompatibleMethod 29 | def get(self): 30 | print "get ", self.a 31 | 32 | 33 | """ 34 | def __init__(self, method): 35 | self._method = method 36 | 37 | def __get__(self, obj, klass=None): 38 | if klass is None: 39 | klass = type(obj) 40 | 41 | # this is the different part with "classmethod" 42 | if isinstance(obj, klass): 43 | klass = obj 44 | klass.__name__ = klass.__class__.__name__ 45 | 46 | def newfunc(*args, **kws): 47 | return self._method(klass, *args, **kws) 48 | 49 | return newfunc 50 | 51 | 52 | class WebBrowser: 53 | 54 | @CompatibleMethod 55 | def AlertAccept(self): 56 | logger.step_normal("AlertAccept()") 57 | 58 | time.sleep(2) 59 | try: 60 | logger.step_normal("switch_to_alert()") 61 | alert = Browser.RunningBrowser.switch_to_alert() 62 | alert.accept() 63 | except NoAlertPresentException: 64 | logger.step_normal("Alert Not Found. ") 65 | 66 | try: 67 | logger.step_normal("switch_to_default_content()") 68 | Browser.RunningBrowser.switch_to_default_content() 69 | except Exception as e: 70 | logger.step_warning(e) 71 | pass 72 | 73 | @CompatibleMethod 74 | def AlertDismiss(self): 75 | logger.step_normal("AlertDismiss()") 76 | 77 | time.sleep(2) 78 | try: 79 | logger.step_normal("switch_to_alert()") 80 | alert = Browser.RunningBrowser.switch_to_alert() 81 | alert.dismiss() 82 | except NoAlertPresentException: 83 | logger.step_normal("Alert Not Found.") 84 | 85 | try: 86 | logger.step_normal("switch_to_default_content()") 87 | Browser.RunningBrowser.switch_to_default_content() 88 | except Exception as e: 89 | logger.step_normal(e) 90 | pass 91 | 92 | @CompatibleMethod 93 | def AlertSendKeys(self, value): 94 | logger.step_normal("AlertSendKeys [%s]" % value) 95 | try: 96 | Browser.RunningBrowser.switch_to.alert.send_keys(value) 97 | Browser.RunningBrowser.switch_to.default_content() 98 | except Exception as e: 99 | logger.step_normal(e) 100 | logger.step_warning(str(sys.exc_info())) 101 | 102 | @CompatibleMethod 103 | def AlertTextHave(self, txt_value): 104 | logger.step_normal("AlertTextHave [%s]" % txt_value) 105 | alert_text = Browser.RunningBrowser.switch_to_alert().text() 106 | 107 | if txt_value in alert_text: 108 | logger.step_pass("pass") 109 | else: 110 | logger.step_fail("fail") 111 | 112 | Browser.RunningBrowser.switch_to_default_content() 113 | 114 | @CompatibleMethod 115 | def DeleteAllCookies(self): 116 | logger.step_normal("Element [%s]: Browser Delete All Cookies" % (self.__name__,)) 117 | Browser.RunningBrowser.delete_all_cookies() 118 | 119 | time.sleep(3) 120 | 121 | @CompatibleMethod 122 | def IESkipCertError(self): 123 | logger.step_normal("IE Skip SSL Cert Error.") 124 | Browser.RunningBrowser.get("javascript:document.getElementById('overridelink').click();") 125 | 126 | @CompatibleMethod 127 | def NavigateTo(self, url): 128 | logger.step_normal("Element [%s]: Navigate To [%s]" % (self.__name__, url)) 129 | Browser.RunningBrowser.get(url) 130 | 131 | time.sleep(3) 132 | 133 | @CompatibleMethod 134 | def Refresh(self, times=4): 135 | logger.step_normal("Element [%s]: Browser Refresh" % (self.__name__,)) 136 | 137 | for i in range(times): 138 | action = webdriver.ActionChains(Browser.RunningBrowser) 139 | action.key_down(Keys.CONTROL).send_keys(Keys.F5).key_up(Keys.CONTROL).perform() 140 | time.sleep(5) 141 | 142 | @CompatibleMethod 143 | def ScrollTo(self, x, y): 144 | logger.step_normal("Element [%s]: Scroll To [%s, %s]" % (self.__name__, x, y)) 145 | Browser.RunningBrowser.execute_script("window.scrollTo(%s, %s);" % (x, y)) 146 | 147 | @CompatibleMethod 148 | def SwitchToDefaultContent(self): 149 | logger.step_normal("SwitchToDefaultContent()") 150 | 151 | try: 152 | Browser.RunningBrowser.switch_to.default_content() 153 | except Exception as e: 154 | logger.step_normal(e) 155 | logger.step_warning("Browser.RunningBrowser.switch_to.default_content()") 156 | 157 | @CompatibleMethod 158 | def SwitchToDefaultWindow(self): 159 | logger.step_normal("SwitchToDefaultWindow()") 160 | 161 | logger.step_normal("Switch To The Default Window of: %s" % str(Browser.RunningBrowser.window_handles)) 162 | 163 | try: 164 | Browser.RunningBrowser.switch_to.window(Browser.RunningBrowser.window_handles[0]) 165 | except Exception as e: 166 | logger.step_normal(e) 167 | logger.step_warning("Browser.RunningBrowser.switch_to.window(Browser.RunningBrowser.window_handles[0])") 168 | 169 | @CompatibleMethod 170 | def SwitchToFrame(self, frame): 171 | logger.step_normal("SwitchToFrame()") 172 | 173 | Browser.RunningBrowser.switch_to.frame(frame) 174 | 175 | @CompatibleMethod 176 | def SwitchToNewPopWindow(self): 177 | logger.step_normal("SwitchToNewPopWindow()") 178 | 179 | t = 0 180 | while t < 10: 181 | t = t + 1 182 | time.sleep(3) 183 | 184 | if len(Browser.RunningBrowser.window_handles) < 2: 185 | logger.step_normal("Pop Window Not Found. Wait 3 Seconds then Try Again!") 186 | else: 187 | break 188 | 189 | Browser.RunningBrowser.switch_to.window(Browser.RunningBrowser.window_handles[-1]) 190 | 191 | logger.step_normal("Switch To The New Window of : %s" % str(Browser.RunningBrowser.window_handles)) 192 | 193 | @CompatibleMethod 194 | def Wait(self, seconds): 195 | logger.step_normal("Element [%s]: Wait for [%s] seconds." % (self.__name__, seconds)) 196 | time.sleep(seconds) 197 | 198 | 199 | class WebElement: 200 | (by, value) = (None, None) 201 | index = 0 202 | 203 | @CompatibleMethod 204 | def __init__(self, by=None, value=None): 205 | self.by = by 206 | self.value = value 207 | 208 | @CompatibleMethod 209 | def __wait(self): 210 | t = 0 211 | while t < 300: 212 | t = t + 1 213 | 214 | try: 215 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 216 | except NoSuchElementException: 217 | logger.step_normal("Element [%s]: NoSuchElementException." % self.__name__) 218 | elements = [] 219 | except UnexpectedAlertPresentException: 220 | logger.step_warning("Element [%s]: UnexpectedAlertPresentException." % self.__name__) 221 | 222 | if len(elements) == 0: 223 | time.sleep(1) 224 | logger.step_normal("Element [%s]: Wait 1 second, By [%s :: %s :: %s]" % (self.__name__, 225 | self.by, 226 | self.value, 227 | self.index)) 228 | else: 229 | if len(elements) > 1: 230 | logger.step_normal("Element [%s]: There are [%s] Elements!" % (self.__name__, len(elements))) 231 | 232 | break 233 | 234 | if len(elements) < self.index + 1: 235 | logger.step_fail("Element [%s]: Element Index Issue! There are [%s] Elements! Index=[%s]" % (self.__name__, 236 | len(elements), 237 | self.index)) 238 | 239 | @CompatibleMethod 240 | def __wait_for_appearing(self): 241 | 242 | t = 0 243 | while t < 120: 244 | t = t + 1 245 | 246 | try: 247 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 248 | except NoSuchElementException: 249 | logger.step_normal("Element [%s]: NoSuchElementException." % self.__name__) 250 | elements = [] 251 | continue 252 | except UnexpectedAlertPresentException: 253 | logger.step_warning("Element [%s]: UnexpectedAlertPresentException." % self.__name__) 254 | 255 | if len(elements) == 0: 256 | time.sleep(0.5) 257 | logger.step_normal("Element [%s]: WaitForAppearing... Wait 1 second, By [%s]" % (self.__name__, 258 | self.value)) 259 | else: 260 | logger.step_normal("Element [%s]: Found [%s] Element. Tried [%s] Times." % (self.__name__, 261 | len(elements), t)) 262 | break 263 | 264 | @CompatibleMethod 265 | def __wait_for_disappearing(self): 266 | 267 | t = 0 268 | while t < 120: 269 | t = t + 1 270 | 271 | try: 272 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 273 | except NoSuchElementException: 274 | logger.step_normal("Element [%s]: NoSuchElementException." % self.__name__) 275 | elements = [] 276 | continue 277 | except UnexpectedAlertPresentException: 278 | logger.step_warning("Element [%s]: UnexpectedAlertPresentException." % self.__name__) 279 | 280 | if len(elements) == 0: 281 | return True 282 | else: 283 | time.sleep(0.5) 284 | logger.step_normal("Element [%s]: WairForDisappearing... Found [%s] Element. Tried [%s] Times." % 285 | (self.__name__, len(elements), t)) 286 | 287 | return False 288 | 289 | @CompatibleMethod 290 | def __wait_for_enabled(self): 291 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 292 | 293 | if elements[self.index].is_enabled(): 294 | return 295 | else: 296 | t = 0 297 | while t < 90: 298 | if elements[self.index].is_enabled(): 299 | break 300 | 301 | logger.step_normal("Element [%s]: __wait_for_enabled for 1 second, By [%s :: %s :: %s]" % 302 | (self.__name__, self.by, self.value, self.index)) 303 | time.sleep(1) 304 | 305 | @CompatibleMethod 306 | def Click(self): 307 | logger.step_normal("Element [%s]: Click()" % self.__name__) 308 | 309 | self.__wait() 310 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 311 | 312 | try: 313 | elements[self.index].click() 314 | except ElementClickInterceptedException: 315 | self.Wait(3) 316 | action = webdriver.ActionChains(Browser.RunningBrowser) 317 | action.click(elements[self.index]) 318 | action.perform() 319 | 320 | @CompatibleMethod 321 | def ClickAndHold(self): 322 | logger.step_normal("Element [%s]: ClickAndHold()" % self.__name__) 323 | 324 | self.__wait() 325 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 326 | 327 | action = webdriver.ActionChains(Browser.RunningBrowser) 328 | action.click_and_hold(elements[self.index]) 329 | action.perform() 330 | 331 | @CompatibleMethod 332 | def DoubleClick(self): 333 | logger.step_normal("Element [%s]: DoubleClick()" % self.__name__) 334 | 335 | self.__wait() 336 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 337 | 338 | action = webdriver.ActionChains(Browser.RunningBrowser) 339 | action.double_click(elements[self.index]) 340 | action.perform() 341 | 342 | @CompatibleMethod 343 | def DragAndDropByOffset(self, xoffset, yoffset): 344 | """ 345 | Holds down the left mouse button on the source element, 346 | then moves to the target offset and releases the mouse button. 347 | """ 348 | logger.step_normal("Element [%s]: drag_and_drop_by_offset()" % self.__name__) 349 | 350 | self.__wait() 351 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 352 | 353 | action = webdriver.ActionChains(Browser.RunningBrowser) 354 | action.drag_and_drop_by_offset(elements[self.index],xoffset, yoffset) 355 | action.perform() 356 | 357 | @CompatibleMethod 358 | def FetchSubElementOfXPath(self, layer): 359 | return WebElement(self.by, "/".join(self.value.split("/")[:layer+2])) 360 | 361 | @CompatibleMethod 362 | def GetAttribute(self, attr): 363 | logger.step_normal("Element [%s]: GetAttribute [%s]." % (self.__name__, attr)) 364 | 365 | self.__wait() 366 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 367 | 368 | attr_value = elements[self.index].get_attribute(attr) 369 | logger.step_normal("Element [%s]: [%s] = [%s]." % (self.__name__, attr, attr_value)) 370 | 371 | return attr_value 372 | 373 | @CompatibleMethod 374 | def GetInnerHTML(self): 375 | logger.step_normal("Element [%s]: GetInnerHTML()" % self.__name__) 376 | 377 | self.__wait() 378 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 379 | 380 | logger.step_normal("Element [%s]: InnerHTML = [%s]" % (self.__name__, elements[self.index].get_attribute('innerHTML'))) 381 | 382 | return elements[self.index].get_attribute('innerHTML') 383 | 384 | @CompatibleMethod 385 | def GetParentElement(self): 386 | logger.step_normal("Element [%s]: GetParentElement()" % self.__name__) 387 | 388 | self.__wait() 389 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 390 | 391 | return elements[self.index].parent() 392 | 393 | @CompatibleMethod 394 | def GetRepetition(self): 395 | logger.step_normal("Element [%s]: GetRepetition()." % self.__name__) 396 | 397 | self.__wait_for_appearing() 398 | 399 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 400 | logger.step_normal("Element [%s]: repetition = [%s]" % (self.__name__, len(elements))) 401 | 402 | return len(elements) 403 | 404 | @CompatibleMethod 405 | def GetRepetitionWithoutWaiting(self): 406 | """ Get real time obj counts, without waiting.""" 407 | logger.step_normal("Element [%s]: GetRepetitionWithoutWaiting()." % self.__name__) 408 | 409 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 410 | logger.step_normal("Element [%s]: repetition = [%s]" % (self.__name__, len(elements))) 411 | 412 | return len(elements) 413 | 414 | @CompatibleMethod 415 | def GetText(self): 416 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 417 | logger.step_normal("Element [%s]: Get text of the element = %s." % (self.__name__, 418 | elements[self.index].text)) 419 | return elements[self.index].text 420 | 421 | @CompatibleMethod 422 | def IsAttribute(self, attribute, value, assertion="contain"): 423 | logger.step_normal("Element [%s]: IsAttribute [%s] <%s> [%s]?" % (self.__name__, attribute, assertion, value)) 424 | 425 | self.__wait() 426 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 427 | real_value = elements[self.index].get_attribute(attribute) 428 | 429 | result = False 430 | 431 | if assertion.lower() == 'equal' and value == real_value: 432 | result = True 433 | elif assertion.lower() == 'not equal' and value != real_value: 434 | result = True 435 | elif assertion.lower() == 'contain' and value in real_value: 436 | result = True 437 | elif assertion.lower() == 'not contain' and value not in real_value: 438 | result = True 439 | elif assertion.lower() == 'in' and real_value in value: 440 | result = True 441 | else: 442 | logger.step_fail("code error.") 443 | 444 | if result is True: 445 | logger.step_normal("Yes!") 446 | else: 447 | logger.step_normal("No!") 448 | 449 | return result 450 | 451 | @CompatibleMethod 452 | def IsEnabled(self): 453 | logger.step_normal("Element [%s]: Is Enabled?" % self.__name__) 454 | 455 | self.__wait() 456 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 457 | 458 | if elements[self.index].is_enabled(): 459 | logger.step_normal("Yes!") 460 | 461 | return True 462 | else: 463 | logger.step_normal("No!") 464 | 465 | return False 466 | 467 | @CompatibleMethod 468 | def IsExist(self): 469 | logger.step_normal("Element [%s]: IsExist?" % self.__name__) 470 | 471 | time.sleep(2) 472 | 473 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 474 | logger.step_normal("Element [%s]: IsExist? Count = [%s]" % (self.__name__, len(elements))) 475 | 476 | if len(elements) > 0: 477 | return True 478 | else: 479 | return False 480 | 481 | @CompatibleMethod 482 | def IsVisible(self): 483 | logger.step_normal("Element [%s]: IsVisible?" % self.__name__) 484 | 485 | self.__wait() 486 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 487 | 488 | if elements[self.index].is_displayed(): 489 | 490 | logger.step_normal("Yes!") 491 | return True 492 | else: 493 | 494 | logger.step_normal("No!") 495 | return False 496 | 497 | @CompatibleMethod 498 | def MouseOver(self): 499 | logger.step_normal("Element [%s]: MouseOver()" % self.__name__) 500 | 501 | time.sleep(1) 502 | 503 | self.__wait() 504 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 505 | 506 | action = webdriver.ActionChains(Browser.RunningBrowser) 507 | action.move_to_element(elements[self.index]) 508 | action.perform() 509 | 510 | time.sleep(1) 511 | 512 | @CompatibleMethod 513 | def ReleaseClick(self): 514 | logger.step_normal("Element [%s]: ReleaseClick()" % self.__name__) 515 | 516 | self.__wait() 517 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 518 | 519 | action = webdriver.ActionChains(Browser.RunningBrowser) 520 | action.release(elements[self.index]) 521 | action.perform() 522 | 523 | @CompatibleMethod 524 | def ScrollIntoView(self): 525 | logger.step_normal("Element [%s]: ScrollToView()" % self.__name__) 526 | 527 | self.__wait() 528 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 529 | 530 | i = 0 531 | while not elements[self.index].is_displayed(): 532 | WebBrowser.ScrollTo(0, i) 533 | i = i + 10 534 | 535 | if i == 1000: 536 | logger.step_normal("still not displayed. break out.") 537 | 538 | @CompatibleMethod 539 | def Select(self, value): 540 | logger.step_normal("Element [%s]: Select [%s]." % (self.__name__, value)) 541 | 542 | self.__wait() 543 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 544 | 545 | is_selected = False 546 | 547 | # select 548 | if elements[self.index].tag_name == "select": 549 | options = elements[self.index].find_elements_by_tag_name('option') 550 | 551 | for option in options: 552 | logger.step_normal("Element [%s]: option [%s]" % (self.__name__, option.text)) 553 | 554 | if option.text == value: 555 | option.click() 556 | is_selected = True 557 | break 558 | 559 | # ul 560 | elif elements[self.index].tag_name == "ul": 561 | lis = elements[self.index].find_elements_by_tag_name('li') 562 | 563 | for li in lis: 564 | logger.step_normal("Element [%s]: li [%s]" % (self.__name__, li.text)) 565 | 566 | if li.text == value: 567 | li.click() 568 | is_selected = True 569 | break 570 | 571 | # not support 572 | else: 573 | logger.step_fail("Element [%s]: Tag [%s] NOT support [Select] method" % (self.__name__, elements[self.index].tag_name)) 574 | 575 | if is_selected is False: 576 | logger.step_fail("No item selected!") 577 | 578 | @CompatibleMethod 579 | def SelectByOrder(self, order): 580 | logger.step_normal("Element [%s]: Select by Order [%s]" % (self.__name__, order)) 581 | 582 | order = int(order) 583 | 584 | self.__wait() 585 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 586 | 587 | # ul 588 | if elements[self.index].tag_name == "ul": 589 | lis = elements[self.index].find_elements_by_tag_name('li') 590 | 591 | if order > 0: 592 | 593 | # wait and try more times if NO item found. 594 | t = 0 595 | while len(lis) == 0: 596 | lis = elements[self.index].find_elements_by_tag_name('li') 597 | time.sleep(3) 598 | t = t + 1 599 | logger.step_normal("Element [%s]: Wait 3 Seconds for [li]" % self.__name__) 600 | 601 | if t == 20 and len(lis) == 0: 602 | logger.step_fail("Element [%s]: List Count = [%s]." % (self.__name__, len(lis))) 603 | return 604 | 605 | logger.step_normal("Element [%s]: List Count = [%s]." % (self.__name__, len(lis))) 606 | 607 | if order > len(lis): 608 | logger.step_fail("Element [%s]: Not so many lists. [%s]" % (self.__name__, len(lis))) 609 | else: 610 | action = webdriver.ActionChains(Browser.RunningBrowser) 611 | 612 | # Added to avoid error: "Element is no longer attached to the DOM" 613 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 614 | 615 | logger.step_normal("Element [%s]: Do Click [%s]" % (self.__name__, lis[order-1].text)) 616 | lis[order-1].click() 617 | 618 | """ 619 | lis = elements[self.index].find_elements_by_tag_name('li') 620 | action.click(lis[order-1]) 621 | action.perform() 622 | """ 623 | 624 | else: 625 | logger.step_fail("Order = [%s], Value Error." % order) 626 | 627 | 628 | # select 629 | # if elements[self.index].tag_name == "select": 630 | else: 631 | options = elements[self.index].find_elements_by_tag_name('option') 632 | 633 | if order > 0: 634 | 635 | # wait and try more times if NO item found. 636 | t = 0 637 | while len(options) == 0: 638 | options = elements[self.index].find_elements_by_tag_name('option') 639 | time.sleep(3) 640 | t = t + 1 641 | logger.step_normal("Element [%s]: Wait 3 Seconds for [option]" % self.__name__) 642 | 643 | if t == 20 and len(options) == 0: 644 | logger.step_fail("Element [%s]: options Count = [%s]." % (self.__name__, len(options))) 645 | return 646 | 647 | logger.step_normal("Element [%s]: options Count = [%s]." % (self.__name__, len(options))) 648 | 649 | if order > len(options): 650 | logger.step_fail("Element [%s]: Not so many options. [%s]" % (self.__name__, len(options))) 651 | else: 652 | logger.step_normal("Element [%s]: Do Click [%s]" % (self.__name__, options[order-1].text)) 653 | options[order-1].click() 654 | 655 | """ 656 | action = webdriver.ActionChains(Browser.RunningBrowser) 657 | action.click() 658 | action.perform() 659 | """ 660 | 661 | else: 662 | logger.step_fail("Order = [%s], Value Error." % order) 663 | 664 | @CompatibleMethod 665 | def SelectByPartText(self, value): 666 | logger.step_normal("Element [%s]: Select [%s]." % (self.__name__, value)) 667 | 668 | self.__wait() 669 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 670 | 671 | is_selected = False 672 | 673 | # select 674 | if elements[self.index].tag_name == "select": 675 | options = elements[self.index].find_elements_by_tag_name('option') 676 | 677 | for option in options: 678 | if value in option.text: 679 | logger.step_normal("Element [%s]: Select [%s]." % (self.__name__, option.text)) 680 | option.click() 681 | is_selected = True 682 | break 683 | 684 | # ul 685 | elif elements[self.index].tag_name == "ul": 686 | lis = elements[self.index].find_elements_by_tag_name('li') 687 | 688 | for li in lis: 689 | if value in li.text: 690 | logger.step_normal("Element [%s]: Select [%s]." % (self.__name__, li.text)) 691 | li.click() 692 | is_selected = True 693 | break 694 | 695 | # not support 696 | else: 697 | logger.step_fail("Element [%s]: Tag [%s] NOT support [Select] method" % (self.__name__, elements[self.index].tag_name)) 698 | 699 | if is_selected is False: 700 | logger.step_fail("No item selected!") 701 | 702 | @CompatibleMethod 703 | def SendEnter(self): 704 | logger.step_normal("Element [%s]: SendEnter()" % self.__name__, ) 705 | 706 | self.__wait() 707 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 708 | 709 | action = webdriver.ActionChains(Browser.RunningBrowser) 710 | action.send_keys_to_element(elements[self.index], Keys.ENTER) 711 | action.perform() 712 | 713 | @CompatibleMethod 714 | def Set(self, value): 715 | logger.step_normal("Element [%s]: Set [%s]." % (self.__name__, value)) 716 | 717 | value = str(value) 718 | 719 | self.__wait() 720 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 721 | 722 | if elements[self.index].tag_name == "select" or elements[self.index].tag_name == "ul": 723 | self.Select(value) 724 | 725 | else: 726 | elements[self.index].clear() 727 | elements[self.index].send_keys(value) 728 | 729 | """ 730 | elements[self.index].clear() 731 | action = webdriver.ActionChains(Browser.RunningBrowser) 732 | action.send_keys_to_element(elements[self.index], value) 733 | action.perform() 734 | """ 735 | 736 | @CompatibleMethod 737 | def TypeInWithoutClear(self, value): 738 | """Input value without clear existing values""" 739 | 740 | logger.step_normal("Element [%s]: TypeInWithoutClear [%s]." % self.__name__, value) 741 | 742 | self.__wait() 743 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 744 | 745 | elements[self.index].send_keys(value) 746 | 747 | @CompatibleMethod 748 | def VerifyAttribute(self, attribute, value, assertion='equal'): 749 | """ 750 | Example: 751 | NewClaim.Dates.ReminderDate.VerifyAttribute('ng-model', 'hello', assertion='equal') 752 | NewClaim.Dates.ReminderDate.VerifyAttribute('ng-model', 'hello', assertion='contain') 753 | NewClaim.Dates.ReminderDate.VerifyAttribute('ng-model', 'hello', assertion='in') 754 | NewClaim.Dates.ReminderDate.VerifyAttribute('ng-model', 'hello', assertion='not equal') 755 | 756 | :param assertion: 757 | in => Real value [in] 'hello'. For example: real_value is 'he' 758 | equal => Real value [equal] 'hello'. For example: real_value is 'hello' 759 | contain => Real value [contain] 'hello'. For example: real_value is 'hello world'. 760 | not equal => Real value [not equal] 'hello'. For example: real_value is 'hallow' 761 | not contain => Real value [not contain] 'hello'. For example: real_value is 'hi world'. 762 | """ 763 | logger.step_normal("Element [%s]: VerifyAttribute [%s] <%s> [%s]." % (self.__name__, attribute, assertion, value)) 764 | 765 | self.__wait() 766 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 767 | real_value = elements[self.index].get_attribute(attribute) 768 | 769 | if assertion.lower() == 'equal': 770 | if value == real_value: 771 | logger.step_pass("real value=[%s]" % real_value) 772 | else: 773 | logger.step_fail("real value=[%s]" % real_value) 774 | 775 | elif assertion.lower() == 'not equal': 776 | if not value == real_value: 777 | logger.step_pass("real value=[%s]" % real_value) 778 | else: 779 | logger.step_fail("real value=[%s]" % real_value) 780 | 781 | elif assertion.lower() == 'contain': 782 | if value in real_value: 783 | logger.step_pass("real value=[%s]" % real_value) 784 | else: 785 | logger.step_fail("real value=[%s]" % real_value) 786 | 787 | elif assertion.lower() == 'not contain': 788 | if not value in real_value: 789 | logger.step_pass("real value=[%s]" % real_value) 790 | else: 791 | logger.step_fail("real value=[%s]" % real_value) 792 | 793 | elif assertion.lower() == 'in': 794 | if real_value in value: 795 | logger.step_pass("real value=[%s]" % real_value) 796 | else: 797 | logger.step_fail("real value=[%s]" % real_value) 798 | 799 | else: 800 | logger.step_fail("code error.") 801 | 802 | @CompatibleMethod 803 | def VerifyEnabled(self, trueOrfalse): 804 | logger.step_normal("Element [%s]: Verify Enabled = [%s]" % (self.__name__, trueOrfalse)) 805 | 806 | self.__wait() 807 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 808 | 809 | is_disabled = elements[self.index].get_attribute("disabled") 810 | logger.step_normal("Element [%s]: attribute 'is_disabled' = [%s]" % (self.__name__, is_disabled)) 811 | 812 | if is_disabled == "true": 813 | if trueOrfalse is False: 814 | logger.step_pass("Pass...") 815 | else: 816 | logger.step_fail("Fail...") 817 | 818 | elif elements[self.index].is_enabled(): 819 | if trueOrfalse is True: 820 | logger.step_pass("Pass") 821 | else: 822 | logger.step_fail("Fail") 823 | 824 | else: 825 | logger.step_fail("Not verified.") 826 | 827 | @CompatibleMethod 828 | def VerifyExistence(self, trueORfalse): 829 | """ 830 | EXAMPLE 831 | Page.Element.VerifyExistence(True) 832 | 833 | :param trueORfalse: True or False 834 | :return: 835 | """ 836 | logger.step_normal("Element [%s]: Verify Existence = [%s]." % (self.__name__, trueORfalse)) 837 | 838 | if trueORfalse is True: 839 | self.__wait_for_appearing() 840 | else: 841 | self.__wait_for_disappearing() 842 | 843 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 844 | logger.step_normal("Element [%s]: Count = [%s]" % (self.__name__, len(elements))) 845 | 846 | 847 | 848 | if len(elements) > 0: 849 | if trueORfalse is True: 850 | logger.step_pass("Exist!") 851 | else: 852 | logger.step_fail("Exist!") 853 | else: 854 | if trueORfalse is False: 855 | logger.step_pass("Not Exist!") 856 | else: 857 | logger.step_fail("Not Exist!") 858 | 859 | @CompatibleMethod 860 | def VerifyInnerHTMLContains(self, content): 861 | self.VerifyAttribute("innerHTML", content, assertion="contain") 862 | 863 | @CompatibleMethod 864 | def VerifyVisible(self, trueORfalse): 865 | logger.step_normal("Element [%s]: Verify Visible = [%s]." % (self.__name__, trueORfalse)) 866 | 867 | self.__wait() 868 | 869 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 870 | logger.step_normal("Element [%s]: Count = [%s]" % (self.__name__, len(elements))) 871 | 872 | if elements[self.index].is_displayed(): 873 | if trueORfalse is True: 874 | logger.step_pass("Visible!") 875 | else: 876 | logger.step_fail("Visible!") 877 | else: 878 | if trueORfalse is False: 879 | logger.step_pass("Not Visible!") 880 | else: 881 | logger.step_fail("Not Visible!") 882 | 883 | @CompatibleMethod 884 | def WaitForAppearing(self): 885 | logger.step_normal("Element [%s]: WaitForAppearing..." % self.__name__) 886 | 887 | self.__wait_for_appearing() 888 | 889 | @CompatibleMethod 890 | def WaitForAttribute(self, attribute, value, assertion="equal"): 891 | """ 892 | Example: 893 | NewClaim.Dates.ReminderDate.WaitForAttribute('ng-model', 'hello', assertion='equal') 894 | NewClaim.Dates.ReminderDate.WaitForAttribute('ng-model', 'hello', assertion='contain') 895 | NewClaim.Dates.ReminderDate.WaitForAttribute('ng-model', 'hello', assertion='not contain') 896 | NewClaim.Dates.ReminderDate.WaitForAttribute('ng-model', 'hello', assertion='in') 897 | NewClaim.Dates.ReminderDate.WaitForAttribute('ng-model', 'hello', assertion='not equal') 898 | 899 | :param assertion: 900 | in => Real value [in] 'hello'. For example: real_value is 'he' 901 | equal => Real value [equal] 'hello'. For example: real_value is 'hello' 902 | contain => Real value [contain] 'hello'. For example: real_value is 'hello world'. 903 | not equal => Real value [not equal] 'hello'. For example: real_value is 'hallow' 904 | not contain => Real value [not contain] 'hello'. For example: real_value is 'hi world'. 905 | """ 906 | logger.step_normal("Element [%s]: WaitForAttribute [%s] <%s> [%s]." % (self.__name__, attribute, assertion, value)) 907 | 908 | i = 0 909 | while True: 910 | self.__wait() 911 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 912 | 913 | real_value = elements[self.index].get_attribute(attribute) 914 | 915 | if assertion.lower() == 'equal': 916 | if value.lower() == real_value.lower(): 917 | logger.step_normal("Yes! real value=[%s]" % real_value) 918 | break 919 | else: 920 | logger.step_normal("No! real value=[%s]" % real_value) 921 | 922 | elif assertion.lower() == 'contain': 923 | if value.lower() in real_value.lower(): 924 | logger.step_normal("Yes! real value=[%s]" % real_value[:150]) 925 | break 926 | else: 927 | logger.step_normal("No! real value=[%s]" % real_value[:150]) 928 | 929 | elif assertion.lower() == 'not contain': 930 | if value.lower() in real_value.lower(): 931 | logger.step_normal("Yes! real value=[%s]" % real_value[:150]) 932 | else: 933 | logger.step_normal("No! real value=[%s]" % real_value[:150]) 934 | break 935 | 936 | elif assertion.lower() == 'in': 937 | if real_value.lower() in value.lower(): 938 | logger.step_normal("Yes! real value=[%s]" % real_value[:150]) 939 | break 940 | else: 941 | logger.step_normal("No! real value=[%s]" % real_value[:150]) 942 | 943 | elif assertion.lower() == 'not equal': 944 | if value.lower() == real_value.lower(): 945 | logger.step_normal("No! real value=[%s]" % real_value) 946 | else: 947 | logger.step_normal("Yes! real value=[%s]" % real_value) 948 | break 949 | 950 | else: 951 | logger.step_fail("code error.") 952 | 953 | i = i + 1 954 | if i > 90: 955 | logger.step_fail("Not Found Expected Value! real value=[%s]" % real_value) 956 | break 957 | 958 | time.sleep(1) 959 | 960 | @CompatibleMethod 961 | def WaitForDisappearing(self): 962 | logger.step_normal("Element [%s]: WaitForDisappearing..." % self.__name__) 963 | 964 | self.__wait_for_disappearing() 965 | 966 | @CompatibleMethod 967 | def WaitForEnabled(self): 968 | logger.step_normal("Element [%s]: WaitForEnabled..." % self.__name__) 969 | 970 | self.__wait() 971 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 972 | 973 | t = 0 974 | while t < 90: 975 | if elements[self.index].is_enabled(): 976 | logger.step_normal("Element [%s]: is enabled now." % self.__name__) 977 | break 978 | else: 979 | logger.step_normal("Element [%s]: still NOT enabled, wait 1 second." % self.__name__) 980 | time.sleep(1) 981 | 982 | t = t + 1 983 | 984 | @CompatibleMethod 985 | def WaitForVisible(self): 986 | logger.step_normal("Element [%s]: WaitForVisible..." % self.__name__) 987 | 988 | self.__wait() 989 | elements = Browser.RunningBrowser.find_elements(self.by, self.value) 990 | 991 | t = 0 992 | while t < 90: 993 | if elements[self.index].is_displayed(): 994 | logger.step_normal("Element [%s]: IS visible now." % self.__name__) 995 | break 996 | else: 997 | logger.step_normal("Element [%s]: Still NOT visible, wait 1 second." % self.__name__) 998 | time.sleep(1) 999 | 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | --------------------------------------------------------------------------------