API自动化说明

工程路径说明

<Project>
├README.md              #工程说明
├requirements.txt       #pip install -r requirements.txt 生成工程依赖包信息
├run.py                 #启动工程文件
├<bases>                #自定义模块路径
│  ├__init__.py
│  ├api.py              #定义总的api请求接口
│  ├auth.py             #获取token;操作数据库方法
│  ├code.py             #系统中定义的返回code码值
│  ├handle_yaml.py      #读取yaml,将接口返回参数回写到yaml
│  ├logger.py           #生成日志
│  ├request.py          #不同接口协议(get、post、put、delete、visit)
│  ├sha.py              #计算文件哈希
├<configs>
│  ├common_config.ini   #服务器连接信息
│  ├ip_config           #请求服务的ip+端口
│  ├path_config.py      #获取文件的绝对路径,以及对工程下各个模块的不同路径进行定义
├<dataSource>
│  ├__init__.py   
│  └001.txt             #准备的测试数据
├<outputs>                            #工程生成的日志及报告
│  ├<htmlreports>                     #生成报告的路径
│  │  ├2021-12-21_08-55-43.html       #生成的测试报告
│  ├<logs>                            #生成的日志路径
│  │  ├2021-12-23_13-19-01.log        #执行测试过程的所有信息的记录
│  │  └error.log                      #直接测试过程的错误日志
├<test_cases>                         #测试case
│  ├__init__.py
│  ├test_00_get_aksk.py
│  ├test_00_get_magazines_list.py
│  ├test_00_get_users.py
│  ├test_00_login.py
├<test_datas>                        #测试case的参数化数据
│  ├roles_login.yaml
│  ├Test_Datas_00_Get_AkSk.py
│  ├Test_Datas_00_Get_AkSk.yaml
│  ├test_datas_00_login.py
│  ├test_datas_15_login.py
│  ├test_datas_15_login.yaml
├<tools>                             #工具文件夹
│  ├HTMLTestRunner.py                #生成测试报告的方法

@staticmethod装饰器--python静态方法

在python中, 通过obj.method()调用一个方法默认会传入实例本身作为参数. 在类中定义方法时, 第一个参数也总是self. 如果想要通过实例调用一个不需要接收self参数的方法, 需要用到@staticmethod装饰器.

api.py

    def api_name(method, url, data, content_type, refer):  
#定义接口方法 method=接口协议,url=请求链接,data=测试数据,content_type=上传时,内容类型,refer=发起连接
        headers = Auth().set_headers(content_type, refer)  
#获取Auth中定义的set_headers方法中的content_type, refer
        res = Request().visit(method, url, headers=headers, data=json.dumps(data), verify=False)  
#结合上述参数,发起请求
        return res  
#请求返回

auth.py

    def set_headers(self, content_type, refer):
        logger.info(" START set_headers().")
        headers = {'Content-Type': content_type, 'referer': referer + refer}
        # add stime,nonce,sign
        auth_result = self.get_auth()
        headers['stime'] = auth_result[0]
        headers['nonce'] = auth_result[1]
        headers['signature'] = auth_result[2]
        headers['token'] = auth_result[3]
        # print(headers)
        logger.info('------ finished to set_headers! ------')
        return headers

handle_yaml.py

    def __init__(self, file_name=None):  
        if file_name:
            self.file_path = os.path.join(path_project, "test_datas", file_name)  #参数化数据路径文件拼接
            print(self.file_path)
    def get_data(self):
        if os.path.exists(self.file_path):
            with open(self.file_path) as fp:                       #打开yaml文件
                data = yaml.safe_load(fp)                          #加载yaml文件夹中的数据
                return data
        else:
            return None

request.py

    def get(self, url, params=None, data=None, headers=None, **kwargs):
        try:
            response = requests.get(url=url, params=params, data=data, headers=headers, **kwargs)
            res = response.json()
            self.api_log('get', url, headers=headers, data=data, params=params,
                         res_code=response.status_code, res_text=response.json(), res_header=response.headers)
            print('response:', response)
            print('res=', json.dumps(res, indent=4, ensure_ascii=False))
            res_code = res['result']
            if res_code in code:
                print("'" + res_code + "'" + " : " + "'" + code[res_code] + "'")
                logger.info(" '{}' : '{}'".format(res_code, code[res_code]))
            return res
        except Exception as e:
            print(e)
            logger.error("接口请求异常,原因:{}".format(e))
            raise e

testcase.py

class Test_00_Login(unittest.TestCase):
    def setUp(self):
        pass
    def tearDown(self):
        pass
    def test_00_login_success(self):
        logger.info('------ TestCase START : {} ------'.format(sys._getframe().f_code.co_name))
        res = API.login()
        actual = res['result']
        try:
            if actual == "0000":
                logger.info("pass")
            else:
                co = "Test_00_Login-" + actual + "-" + code[actual] + "\n"
                errorFile = open(path_logs + '/' + 'error.log', 'w')
                errorFile.write(co)
                errorFile.close()
                self.assertEqual(actual, '0000')
        except AssertionError as e:
            logger.error("failed!  actual : {} .".format(actual))
            raise e
if __name__ == '__main__':
    unittest.main()

test_data.py

method = 'post'
file = open(path_configs + '/ip_config')
a = file.read()
a = a.strip()
host = a
userName = '########'
passWord = "@@@@@@@@"
# data = data  将json数据存入列表中
body = {
    'userName': userName,
    'passWord': passWord
}
path = '/v1/login'
url = host + path
referer = host + '/gdas/'

run.py

suites = unittest.defaultTestLoader.discover(path_test_cases, pattern='test_*.py')
rq = time.strftime('%Y-%m-%d_%H-%M-%S', time.localtime(time.time()))
filename = path_htmlreports + '/' + rq + '.html'
with open(filename, 'wb') as file:
    runner = HTMLTestRunner(stream=file,
                            verbosity=2,
                            title=None,
                            description='',
                            tester='ghq')
    runner.run(suites)

discover()方法

discover(start_dir, pattern ='test *.py', top_level_dir = None )

start_dir:要测试的模块名或测试用例目录;

pattern='test*.py':表示用例文件名的匹配原则,下面的例子中匹配文件名为以“test”开头的“.py”文件,星号“*”表示任意多个字符;

top_level_dir=None:测试模块的顶层目录,如果没有顶层目录,默认为None;

该方法通过从指定的开始目录递归到子目录中查找所有测试模块,并返回包含它们的TestSuite对象,只有与模式匹配测试文件和可导入的模块名称才会被加载。

所有测试模块必须可以从项目的顶层导入,如果起始目录不是顶层目录,则顶层目录必须单独指定。

如果一个测试文件的名称符合pattern,将检查该文件是否包含 load_tests() 函数,如果 load_tests() 函数存在,则由该函数负责加载本文件中的测试用例。

如果不存在,就会执行loadTestsFromModule(),查找该文件中派生自TestCase 的类包含的 test 开头的方法。

UI自动化说明

工程路径说明

<Project>
├README.md              #工程说明
├requirements.txt       #pip install -r requirements.txt 生成工程依赖包信息
├run.py                 #启动工程文件
├<bases>                #自定义模块路径
│  ├__init__.py
│  ├base_page.py        #页面基类
│  ├database.py         #数据库操作
│  ├handle_yaml.py      #读取yaml,将接口返回参数回写到yaml
│  ├init_browser.py     #获取driver路径信息,通过driver调用本地或者远程服务器中的浏览器
│  ├logger.py           #生成日志
│  ├paging_counts.py    #获取token,接口请求组合方法
│  ├ssh.py              #获取命令执行时长
├<configs>  
│  ├basic_config.py     #账户、服务器等连接信息
│  ├path_config.py      #工程路径信息
├<outputs>
│  ├<htmlreports>                  #报告生成路径
│  │  └2021-12-20_12-51-41.html
│  ├<logs>                         #日志生成路径
│  │  └2021-12-20.log  
│  ├<screenshots>                  #截图生成路径
│  │  └2021-12-20  12-52-07.png
├<page_objects>  
│  ├__init__.py
│  ├login_page.py                  #页面元素信息;针对页面操作定义的方法
├<resources>                       #测试数据路径,没有对文件上传的可以不要
│  ├<correct_file>
│  │  ├001.txt
│  │  ├<aaaaa>
│  │  │  ├<bbb>
│  │  │  │  ├<ccc>
│  │  │  │  │  └新建文本文档.txt
│  ├<err_file>
│  │  ├#.txt
│  ├<exist_file>
│  │  ├200M.zip
│  │  ├<001>
│  │  │  └111.txt
├<test_cases>                      #测试case路径
│  ├__init__.py
│  ├test_001_login_page.py         #调用page_objects定义的方法执行测试
├<test_datas>
│  ├login_datas.py                 #测试的参数化数据
│  ├login_datas.yaml
├<tools>
│  ├HTMLTestRunner.py              #报告的前端代码
│  ├Chromedriver.exe               #浏览器driver

base_page.py

上述API自动化中已经说明了各个模块的功能,UI自动化中的区别主要体现在base_page.py,该文件中是对webdriver.driver中的基类进行的统一的声明,方便后续测试中的调用,减少冗余

#定义一个页面基类,让所有页面都继承这个类,封装一些常用的页面操作方法到这个类   
 def __init__(self, driver):
        self.driver = driver
    # 退出浏览器
    def quit_f(self):
        self.driver.quit()
        logger.info("****** Quit browser ******.")
        # logger.info("============ END ============")
    # 关闭当前window
    def close_f(self):
        try:
            self.driver.close()
            logger.info("Close window or browser.")
        except NameError as e:
            logger.error("Failed to close window or browser! {}".format(e))
#清空cookies
    def clear_cookies_f(self):
        self.driver.delete_all_cookies()
        logger.info("Clear cookies.")
    # 刷新浏览器
    def refresh_f(self):
        self.driver.refresh()
        self.wait_sleep_f(3)
        logger.info("Sleep for browser refresh.")
    # 浏览器前进操作
    def forward_f(self):
        self.driver.forward()
        logger.info("Browser forward.")
    # 浏览器后退操作
    def back_f(self):
        self.driver.back()
        logger.info("Browser back.")
    # 截图
    def save_screenshot_f(self):

        rq = time.strftime('%Y-%m-%d  %H-%M-%S', time.localtime(time.time()))
        screenshot_name = path_screenshots + '/' + rq + '.png'
        try:
            self.driver.get_screenshot_as_file(screenshot_name)
            logger.info("Save a screenshot.  {}".format(screenshot_name))
        except NameError as e:
            logger.error("Failed to save a screenshot! {}".format(e))
            self.save_screenshot_f()
    # sleep强制等待
    @staticmethod
    def wait_sleep_f(seconds):
        if seconds >= 5:
            logger.info("Sleep {}s .".format(seconds))
        time.sleep(seconds)
    # 隐式等待
    def wait_implicitly_f(self, seconds):
        self.driver.implicitly_wait(seconds)
        logger.info("Set implicitly_wait for {} seconds.".format(seconds))
    # 显示等待 元素可见
    def wait_ele_f(self, locator):
        try:
            start = datetime.datetime.now()
            WebDriverWait(self.driver, timeout=10, poll_frequency=0.5).until(EC.visibility_of_element_located(locator))
            end = datetime.datetime.now()
            wait_time = (end - start).seconds
            logger.info("Wait element visible : {} . {}s".format(locator, wait_time))
            return True
        except Exception as e:
            logger.error("Failed to wait element visible : {} ! {} .".format(locator, e))
            self.save_screenshot_f()
    # 元素定位
    def find_element_f(self, locator):
        """
        传入元组 *locator   (By.XPATH, '//*[@id="pageLogIn"]/div[3]/div')
        """
        try:
            self.wait_ele_f(locator)
            element = self.driver.find_element(*locator)
            # logger.info("Find element : {} .".format(locator))
            return element
        except NoSuchElementException as e:
            logger.error("Failed to find element : {} ! {} .".format(locator, e))
            self.save_screenshot_f()
            raise e
    # 多元素定位
    def find_elements_f(self, locator):
        """
        传入元组 *locator   (By.XPATH, '//*[@id="pageLogIn"]/div[3]/div')
        """
        try:
            self.wait_ele_f(locator)
            element = self.driver.find_elements(*locator)
            logger.info("Find element : {} .".format(locator))
            return element
        except NoSuchElementException as e:
            logger.error("Failed to find element : {} ! {} .".format(locator, e))
            self.save_screenshot_f()
            raise e
    # 鼠标移动到指定位置
    def move_to_f(self, locator):
        action_chains = ActionChains(self.driver)
        el = self.find_element_f(locator)
        try:
            action_chains.move_to_element(el).perform()
            logger.info("Move to : {}".format(locator))
        except NameError as e:
            logger.error("Failed to Move to : {} ! {} .".format(locator, e))
    # 清空内容并输入数据
    def send_f(self, locator, text):

        el = self.find_element_f(locator)
        el.clear()
        try:
            el.send_keys(text)
            logger.info("Send_keys '{}' to : {}".format(text, locator))
        except NameError as e:
            logger.error("Failed to send_keys : {} ! {} .".format(locator, e))
            self.save_screenshot_f()
    # 清空内容
    def clear_f(self, locator):

        el = self.find_element_f(locator)
        try:
            el.clear()
            logger.info("Clear content : {}".format(locator))
        except NameError as e:
            logger.error("Failed to clear content : {} ! {} .".format(locator, e))
            self.save_screenshot_f()
    # 单击
    def click_f(self, locator):

        el = self.find_element_f(locator)
        try:
            el.click()
            self.wait_sleep_f(0.5)
            logger.info("Click : {}".format(locator))
        except NameError as e:
            logger.error("Failed to click : {} ! {} .".format(locator, e))
    # 双击
    def double_click_f(self, locator):
        action_chains = ActionChains(self.driver)
        el = self.find_element_f(locator)
        try:
            action_chains.double_click(el).perform()
            logger.info("Click : {}".format(locator))
        except NameError as e:
            logger.error("Failed to click : {} ! {} .".format(locator, e))
    # js点击
    def click_js_f(self, locator):

        el = self.find_element_f(locator)
        try:
            self.driver.execute_script("arguments[0].click();", el)
            logger.info("JS_Click : {}".format(locator))
        except NameError as e:
            logger.error("Failed to js_click : {} ! {} .".format(locator, e))
    # 获取网页标题
    def get_page_title_f(self):
        logger.info("Page title is : {}".format(self.driver.title))
        return self.driver.title
    # 获取浏览器位置
    def get_pos_f(self):
        self.driver.get_window_position()
        logger.info("Window position is : {}".format(self.driver.title))
        return self.driver.title
    # 设置浏览器位置
    def set_pos_f(self, x, y):
        self.driver.set_window_position(x, y)
        logger.info("Set window position : {},{}".format(x, y))
    # 高亮元素
    def highlight_ele(self, locator):
        el = self.find_element_f(locator)
        self.driver.execute_script("arguments[0].setAttribute('style',arguments[1]);", el,
                                   "border:2px solid red;")
        """
        封装设置页面对象的属性值的方法
        调用JS代码修改页面元素的属性值,arguments[0]~arguments[1]分别
        会用后面的element,attributeName和value参数进行替换
        """
    def set_attribute_f(self, locator, attribute_name, value):
        element = self.find_element_f(locator)
        self.driver.execute_script("arguments[0].setAttribute(arguments[1],arguments[2])",
                                   element, attribute_name, value)
#回到顶部
    def scroll_top(self):
        js_top = "var q=document.body.scrollTop=0"
        js = "window.scrollTo(0,0);"
        self.driver.execute_script(js)
#回到底部
    def scroll_bottom(self):
        # js_bottom = "var q=document.documentElement.scrollTop=10000"
        js_bottom = "var q=document.body.scrollTop=10000"
        self.driver.execute_script(js_bottom)
    # 移动到元素element对象的“顶端”与当前窗口的“顶部”对齐
    def scroll_element(self, locator):
        element = self.find_element_f(locator)
        self.driver.execute_script("arguments[0].scrollIntoView();", element)
    # 显示等待 元素从dom中消失
    def wait_ele_staleness_f(self, locator):
        start = datetime.datetime.now()
        WebDriverWait(self.driver, timeout=20, poll_frequency=0.5).until(EC.staleness_of(self.find_element_f(locator)))
        end = datetime.datetime.now()
        wait_time = (end - start).seconds
        logger.info("Wait element visible : {} . {}s".format(locator, wait_time))
        return True
    # 显示等待 元素从页面中消失
    def wait_ele_invisibility_f(self, locator):
        start = datetime.datetime.now()
        WebDriverWait(self.driver, timeout=20, poll_frequency=0.5).until(EC.invisibility_of_element_located(self.find_element_f(locator)))
        end = datetime.datetime.now()
        wait_time = (end - start).seconds
        logger.info("Wait element visible : {} . {}s".format(locator, wait_time))
        return True
    # 显示等待 元素可见
    def wait_ele_not_clickable_f(self, locator):
        try:
            start = datetime.datetime.now()
            WebDriverWait(self.driver, timeout=10, poll_frequency=0.5).until_not(EC.element_to_be_clickable(locator))
            end = datetime.datetime.now()
            wait_time = (end - start).seconds
            logger.info("Wait element visible : {} . {}s".format(locator, wait_time))
            return True
        except Exception as e:
            logger.error("Failed to wait element visible : {} ! {} .".format(locator, e))
            self.save_screenshot_f()
    # 获取文本内容
    def get_text_f(self, locator):
        el = self.find_element_f(locator)
        try:
            text = el.text
            logger.info("get_text '{}' to : {}".format(text, locator))
            return text
        except NameError as e:
            logger.error("Failed to get_text : {} ! {} .".format(locator, e))
            self.save_screenshot_f()
    # 查找元素是否存在,若消失,返回None
    def check_ele_f(self, locator):
        try:
            ele = WebDriverWait(self.driver, timeout=2).until(EC.visibility_of_element_located(locator))
            logger.info("Wait element visible : {} ".format(locator))
            return ele
        except Exception as e:
            pass
    # 获取用户名密码:
    @staticmethod
    def get_user_login_datas(usertype):
        role_data = {
            'manager': {
                'username': manager_username,
                'password': manager_password
            },
            'general': {
                'username': 'general_username',
                'password': 'general_password'
            }
        }
        user_handle = HandleYaml('roles_login.yaml')  # 从yaml文件中取出该接口的参数
        role_dic = user_handle.get_data()
        if role_dic:
            try:
                user_dic = role_dic[usertype]
                return user_dic['username'], user_dic['password']
            except KeyError:
                username = role_data[usertype]['username']
                password = role_data[usertype]['password']
                user_handle.write_data(usertype, username, password)
                return username, password
        else:
            username = role_data[usertype]['username']
            password = role_data[usertype]['password']
            user_handle.write_data(usertype, username, password)
            return username, password