使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 30--开源电商商城系统项目实战--配置测试环境地址

测试学习记录,仅供参考!

项目实战 3(开源电商商城系统项目)

解析 .ini 配置文件的封装

通过配置文件读取测试地址

可以发现目前 登录页面类 和 注册页面类 中的 url 地址是固定的,当页面类有很多个时(几十几百几千个),每一个页面类中的 url 地址均写固定的话,后续每次修改起来会比较麻烦(例如:开发环境、测试环境、灰度环境、正式环境等等环境的 IP 地址是不一样的,若每一种环境都改的话,工作量又增加了),所以需要把服务器 IP 地址做到配置文件里面去,后面只需写每个页面的路径,不需要再写地址;

一、创建

1、在项目根目录 config 软件包文件目录下新建一个名称 config.ini 的配置文件;

复制代码
[HOST]
host = http://localhost:8088/ecshop

[MYSQL]
ip = 127.0.0.1
port = 3306
username = root
password = 123456
db = test
二、解析

2、专门写一个文件(configParse.py)用于解析读取配置文件(config.ini)的数据;

3、 在util_tools 软件包 handle_data 软件包文件目录下新建一个名称 configParse.py 的 python 文件;

使用 python 内置模块,导入第三方 configparser 库(专门解析 .ini 后缀配置文件)

复制代码
import configparser
from util_tools.logs_util.recordlog import logs

class ConfigParse:
    """解析.ini配置文件"""

    # 初始化函数--传一个需要解析的文件路径
    def __init__(self, file_path):
        # 调用 configparser 模块里面的 ConfigParser 类(调用方法去解析配置文件)
        self.config = configparser.ConfigParser()
        # self.file_path = 等于初始化传进来的文件路径
        self.file_path = file_path
        # 定义好 read_config 方法后,再调用
        self.read_config()

    # 定义一个方法--读取配置文件
    def read_config(self):
        # 通过 config 调用 configparser 模块 ConfigParser 类里面的对象,调用 read() 函数去读取 传进来的配置文件路径
        self.config.read(self.file_path)

    # 解析文件内容--第一个 section 参数是配置文件中的头参数"[HOST]、[MYSQL]",第二个 option 参数是所属头参数下面的键值参数
    def get_value(self, section, option):
        try:
            # 直接调用 config 这个对象 .get() 再把这两个"section, option"参数传进来--最后把结果返回出去
            value = self.config.get(section, option)
            return value
        except Exception as e:
            logs.error(f'解析配置文件出现异常,原因为{e}')

# 调试查看
if __name__ == '__main__':
    # 实例化 ConfigParse 类,里面传一个参数"需要解析的配置文件路径"
    conf = ConfigParse('../../config/config.ini')
    # 通过实例化类去调用读取 .get_value() 方法--需要解析哪个数据
    res = conf.get_value('HOST', 'host')
    print(res)

4、 运行调试查看结果是能够成功的解析 config.ini 配置文件;

复制代码
http://localhost:8088/ecshop

进程已结束,退出代码为 0
三、配置

5、若一个项目只有一个 config.ini 配置文件时,可用把"需要解析的配置文件路径 file_path "写到 config 软件包文件目录下 setting.py 文件中去;

优化 setting.py 文件;文件路径中增加 ini 选项,使用 os.path.join(DIR_PATH, 'config', 'config.ini') 把路径拼接一下,若是只有一个 config.ini 配置文件,可以把路径写为固定的;

通过根目录(DIR_PATH)+ 目录( config)+ 文件( config.ini)拼接成一个完整的目录文件路径;

调试打印测试 print(FILE_PATH['ini']) ,通过读取 FILE_PATH['ini'] 拿到项目中的 .ini 配置文件路径;

复制代码
# 导包
import os
import sys

DIR_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(DIR_PATH)

# 显示等待时间设置,默认为10秒
WAIT_TIME = 10

# 设置浏览器
browser_type = 'edge'

# 文件路径
FILE_PATH = {
    'log': os.path.join(DIR_PATH, 'log'),
    'screenshot': os.path.join(DIR_PATH, 'screenshot'),
    'ini': os.path.join(DIR_PATH, 'config', 'config.ini')
}

6、拿到 .ini 配置文件路径之后,优化 configParse.py 文件;

指定默认读取配置文件路径后实例化 ConfigParse 类时可以不传参数,若有需要别的配置文件时,可以传其他需要的配置文件路径;

复制代码
import configparser
from util_tools.logs_util.recordlog import logs
from config.setting import FILE_PATH

class ConfigParse:
    """解析.ini配置文件"""

    # 初始化函数--传一个需要解析的文件路径--指定默认读取配置文件路径
    def __init__(self, file_path=FILE_PATH['ini']):
        # 调用 configparser 模块里面的 ConfigParser 类(调用方法去解析配置文件)
        self.config = configparser.ConfigParser()
        # self.file_path = 等于初始化传进来的文件路径
        self.file_path = file_path
        # 定义好 read_config 方法后,再调用
        self.read_config()

    # 定义一个方法--读取配置文件
    def read_config(self):
        # 通过 config 调用 configparser 模块 ConfigParser 类里面的对象,调用 read() 函数去读取 传进来的配置文件路径
        self.config.read(self.file_path)

    # 解析文件内容--第一个 section 参数是配置文件中的头参数"[HOST]、[MYSQL]",第二个 option 参数是所属头参数下面的键值参数
    def get_value(self, section, option):
        try:
            # 直接调用 config 这个对象 .get() 再把这两个"section, option"参数传进来--最后把结果返回出去
            value = self.config.get(section, option)
            return value
        except Exception as e:
            logs.error(f'解析配置文件出现异常,原因为{e}')

# 调试查看
if __name__ == '__main__':
    # 实例化 ConfigParse 类时可以不传参数--若有需要别的配置文件时,可传其他需要的配置文件路径
    conf = ConfigParse()
    # 通过实例化类去调用读取 .get_value() 方法--需要解析哪个数据
    res = conf.get_value('HOST', 'host')
    print(res)


运行结果:
http://localhost:8088/ecshop

进程已结束,退出代码为 0

7、优化 configParse.py 文件;只获取 host;

复制代码
import configparser
from util_tools.logs_util.recordlog import logs
from config.setting import FILE_PATH

class ConfigParse:
    """解析.ini配置文件"""

    # 初始化函数--传一个需要解析的文件路径--指定默认读取配置文件路径
    def __init__(self, file_path=FILE_PATH['ini']):
        # 调用 configparser 模块里面的 ConfigParser 类(调用方法去解析配置文件)
        self.config = configparser.ConfigParser()
        # self.file_path = 等于初始化传进来的文件路径
        self.file_path = file_path
        # 定义好 read_config 方法后,再调用
        self.read_config()

    # 定义一个方法--读取配置文件
    def read_config(self):
        # 通过 config 调用 configparser 模块 ConfigParser 类里面的对象,调用 read() 函数去读取 传进来的配置文件路径
        self.config.read(self.file_path)

    # 解析文件内容--第一个 section 参数是配置文件中的头参数"[HOST]、[MYSQL]",第二个 option 参数是所属头参数下面的键值参数
    def get_value(self, section, option):
        try:
            # 直接调用 config 这个对象 .get() 再把这两个"section, option"参数传进来--最后把结果返回出去
            value = self.config.get(section, option)
            return value
        except Exception as e:
            logs.error(f'解析配置文件出现异常,原因为{e}')

    # 定义一个方法只用于获取 host--只传一个 option 参数
    def get_host(self, option):
        # 调用上面的 get_value 方法--第一个参数写固定,第二个参数才去传 option --最后直接返回
        return self.get_value('HOST', option)

# 调试查看
if __name__ == '__main__':
    # 实例化 ConfigParse 类时可以不传参数
    conf = ConfigParse()
    # 通过实例化类去调用读取 .get_host() 方法直接获取 host
    res = conf.get_host('host')
    print(res)


运行结果:
http://localhost:8088/ecshop

进程已结束,退出代码为 0
四、使用

8、完成配置文件之后,怎么去替换使用呢?

9、优化项目根目录 pageObject 软件包 login_page 目录下 login_page.py 文件;删除 url 中的前面一部分;

复制代码
from selenium.webdriver.common.by import By
from util_tools.basePage import BasePage

# 登录页面类
class LoginPage(BasePage):
    # url = 'http://localhost:8088/ecshop/user.php'
    # 页面地址
    url = '/user.php'
    # 用户名
    username = (By.NAME, 'username')
    # 密码
    password = (By.NAME, 'password')
    # 登录按钮
    submit = (By.NAME, 'submit')
    # 断言结果
    assert_result = (By.XPATH, '//*[@id="ECS_MEMBERZONE"]/font/font')

    # 登录操作
    def login(self, user_name, pass_word):
        # 打开网址
        self.open_url(self.url)
        # 输入用户名
        self.send_keys(self.username, user_name)
        # 输入密码
        self.send_keys(self.password, pass_word)
        # 点击登录按钮
        self.click(self.submit)

10、优化项目根目录 pageObject 软件包 register_page 目录下 register_page.py 文件;同样删除 url 中的前面一部分;

复制代码
from selenium.webdriver.common.by import By
from util_tools.basePage import BasePage

# 注册页面类
class RegisterPage(BasePage):
    # url = 'http://localhost:8088/ecshop/user.php?act=register'
    # 页面地址
    url = '/user.php?act=register'
    # 用户名
    username = (By.NAME, 'username')
    # email
    email = (By.NAME, 'email')
    # 密码
    password = (By.NAME, 'password')
    # 确认密码
    confirm_password = (By.ID, 'conform_password')
    # MSN
    msn = (By.NAME, 'extend_field1')
    # QQ
    qq = (By.NAME, 'extend_field2')
    # 办公电话
    office_phone = (By.NAME, 'extend_field3')
    # 家庭电话
    family_phone = (By.NAME, 'extend_field4')
    # 手机
    phone = (By.NAME, 'extend_field5')
    # 密码提示问题
    select = (By.NAME, 'sel_question')
    # 密码问题答案
    password_answer = (By.NAME, 'passwd_answer')
    # 立即注册按钮
    submit_button = (By.NAME, 'Submit')

    # 断言结果定位
    assert_result = (By.XPATH, '/html/body/div[7]/div[2]/div/div/div/font')
    # 断言用户已存在的结果
    assert_user_exist = (By.XPATH, '//*[@id="username_notice"]')

    # 跳转登录按钮--部分超链接 PARTIAL_LINK_TEXT 文本定位方式
    skip_login_button = (By.PARTIAL_LINK_TEXT, '我要登录')
    # 完整的超链接 LINK_TEXT 文本定位方式
    skip_login_button1 = (By.LINK_TEXT, '我已有账号,我要登录')

    # 注册操作--若不怕搞混淆,定位表达式的变量名和输入传参数据名一样也行
    def click_register(self, username, email, password, confirm_password, msn, qq, office_phone, family_phone, phone,
                       password_answer):
        # 打开被测页面
        self.open_url(self.url)
        # 依次输入内容
        self.send_keys(self.username, username)
        self.send_keys(self.email, email)
        self.send_keys(self.password, password)
        self.send_keys(self.confirm_password, confirm_password)
        self.send_keys(self.msn, msn)
        self.send_keys(self.qq, qq)
        self.send_keys(self.office_phone, office_phone)
        self.send_keys(self.family_phone, family_phone)
        self.send_keys(self.phone, phone)
        # 下拉菜单选择--若不固定索引值请在上面方法自行加参数
        self.selects(self.select, 3)
        self.send_keys(self.password_answer, password_answer)
        # 点击立即注册按钮
        self.click(self.submit_button)

    # 跳转操作
    def skip_login_page(self):
        # 打开被测页面
        self.open_url(self.url)
        # 直接点击即可
        self.click(self.skip_login_button)
五、优化改造方法

11、 可以在页面类中看到打开被测页面 self.open_url(self.url) 方法 通过"open_url()"方法打开页面 url;

而 open_url() 方法是在公共模块 basePage.py 文件中 def open_url(self, url): 封装的方法;

所以说只需改造 def open_url(self, url): 方法,通过配置文件和页面类中的做一个拼接,

1)、引入 ini 文件解析 ConfigParse 类;

2)、引入之后要使用里面的解析方法得先实例化它,在初始化改造函数中实例化;

3)、改造 def open_url(self, url): 方法;自动去判断 url 是不是完整的,若是完整的 url 就直接打开,否则就通过配置文件去读取加拼接一个完整的 url 地址;

做个判断,这样不管地址是在配置文件中或者在页面类中书写固定的完整地址时均不会报错;

复制代码
# 导包
from selenium import webdriver
from selenium.common import NoSuchElementException, TimeoutException
from selenium.webdriver.common.by import By
from time import sleep
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.wait import WebDriverWait
from config import setting
from util_tools.logs_util.recordlog import logs
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
from datetime import datetime
import pytesseract
from PIL import Image
from selenium.webdriver.support.ui import Select
from util_tools.handle_data.configParse import ConfigParse


class BasePage(object):


    def __init__(self, driver):
        self.__driver = driver
        self.__wait = WebDriverWait(self.__driver, setting.WAIT_TIME)
        self.conf = ConfigParse()

    def window_max(self):
        self.__driver.maximize_window()

    def window_full(self):
        self.__driver.fullscreen_window()

    def screenshot(self):
        self.__driver.get_screenshot_as_png()

    def open_url(self, url):
        if url.startswith('http') or url.startswith('https'):
            self.__driver.get(url)
        else:
            new_url = self.conf.get_host('host') + url
            self.__driver.get(new_url)

    @property
    def current_url(self):
        return self.__driver.current_url

    @property
    def title(self):
        return self.__driver.title

    def refresh(self):
        self.__driver.refresh()

    @property
    def switch_to(self):
        return self.__driver.switch_to

    def iframe(self, frame):
        self.switch_to.frame(frame)

    def exit_iframe(self):
        self.switch_to.default_content()

    @property
    def alert(self):
        return self.__wait.until(ec.alert_is_present())

    def alert_confirm(self):
        self.alert.accept()

    def alert_cancel(self):
        self.alert.dismiss()

    def location_element(self, by, value):
        try:
            element = self.__wait.until(ec.presence_of_element_located((by, value)))
            logs.info(f"找到元素:{by}={value}")
            return element
        except Exception as e:
            logs.error(f"未找到元素:{by}={value}")
            raise e

    def location_elements(self, by, value):
        try:
            self.__wait.until(ec.presence_of_all_elements_located((by, value)))
            elements = self.__driver.find_elements(by, value)
            logs.info(f"找到元素列表:{by}={value}")
            return elements
        except Exception as e:
            logs.error(f"未找到元素列表:{by}={value}")
            raise e

    def click(self, locator: tuple, force=False):
        try:
            element = self.location_element(*locator)
            if not force:
                self.__driver.execute_script("arguments[0].click()", element)
            else:
                self.__driver.execute_script("arguments[0].click({force:true})", element)
            logs.info(f"元素被点击:{locator}")
        except NoSuchElementException as e:
            logs.error(f"元素无法定位:{e}")
            raise e

    def send_keys(self, locator: tuple, data):
        try:
            element = self.location_element(*locator)
            element.send_keys(data)
            logs.info(f"元素被输入内容:{locator},输入的内容为:{data}")
        except NoSuchElementException as e:
            logs.error(f"元素无法定位:{e}")
            raise e

    def selects(self, locator: tuple, index):
        try:
            select = Select(self.location_element(*locator))
            select.select_by_index(index)
            logs.info(f'选择第{index}个数据')
        except NoSuchElementException as e:
            logs.error(f'元素无法定位:{e}')
            raise e

    def enter(self):
        try:
            ActionChains(self.__driver).send_keys(Keys.ENTER).perform()
            logs.info("按下回车键")
        except NoSuchElementException as e:
            logs.error(f"元素无法定位:{e}")
            raise e

    def right_click(self, locator: tuple):
        try:
            element = self.location_element(*locator)
            ActionChains(driver).context_click(element).perform()
            logs.info("执行鼠标右键点击操作")
        except NoSuchElementException as e:
            logs.error(f"元素无法定位:{e}")
            raise e

    def double_click(self, locator: tuple):
        try:
            element = self.location_element(*locator)
            ActionChains(driver).double_click(element).perform()
            logs.info("执行鼠标双击操作")
        except NoSuchElementException as e:
            logs.error(f"元素无法定位:{e}")
            raise e

    def screenshots(self, image_name):
        import os
        current_time = datetime.now().strftime("%Y%m%d%H%M%S")
        file_name = f"{image_name}-{current_time}.png"
        file_path = os.path.join(setting.FILE_PATH.get('screenshot'), file_name)
        self.__driver.get_screenshot_as_file(file_path)

    def clear(self, locator: tuple):
        try:
            element = self.location_element(*locator)
            element.clear()
            logs.info("清空文本")
        except NoSuchElementException as e:
            logs.error(f"元素无法定位:{e}")
            raise e

    def ocr_captcha(self, locator: tuple):
        captcha_element = self.location_element(*locator)
        captcha_path = setting.FILE_PATH['screenshot'] + '/captcha.png'
        captcha_element.screenshot(captcha_path)
        captcha_image = Image.open(captcha_path)
        try:
            captcha_text = pytesseract.image_to_string(captcha_image)
            logs.info(f"识别到的验证码为:{captcha_text}")
            return captcha_text
        except pytesseract.pytesseract.TesseractNotFoundError:
            logs.error("找不到tesseract,这是因为pytesseract模块依赖于TesseractOCR引擎来进行图像识别!")

    def assert_is_element_present(self, locator: tuple):
        try:
            element = self.__driver.find_element(*locator)
            assert element.is_displayed(), '元素不存在'
        except NoSuchElementException as e:
            logs.error(f'元素未找到:{e}')
            raise AssertionError('元素不存在')

    def assert_element_not_visible(self, locator: tuple):
        try:
            self.__wait.until(ec.invisibility_of_element_located(locator))
        except TimeoutException:
            logs.error('元素可见')

    def assert_title(self, expect_title):
        assert expect_title in self.title

12、测试调试完成后删除多余不需要的内容;

复制代码
import configparser
from util_tools.logs_util.recordlog import logs
from config.setting import FILE_PATH

class ConfigParse:
    """解析.ini配置文件"""

    # 初始化函数--传一个需要解析的文件路径--指定默认读取配置文件路径
    def __init__(self, file_path=FILE_PATH['ini']):
        # 调用 configparser 模块里面的 ConfigParser 类(调用方法去解析配置文件)
        self.config = configparser.ConfigParser()
        # self.file_path = 等于初始化传进来的文件路径
        self.file_path = file_path
        # 定义好 read_config 方法后,再调用
        self.read_config()

    # 定义一个方法--读取配置文件
    def read_config(self):
        # 通过 config 调用 configparser 模块 ConfigParser 类里面的对象,调用 read() 函数去读取 传进来的配置文件路径
        self.config.read(self.file_path)

    # 解析文件内容--第一个 section 参数是配置文件中的头参数"[HOST]、[MYSQL]",第二个 option 参数是所属头参数下面的键值参数
    def get_value(self, section, option):
        try:
            # 直接调用 config 这个对象 .get() 再把这两个"section, option"参数传进来--最后把结果返回出去
            value = self.config.get(section, option)
            return value
        except Exception as e:
            logs.error(f'解析配置文件出现异常,原因为{e}')

    # 定义一个方法只用于获取 host--只传一个 option 参数
    def get_host(self, option):
        # 调用上面的 get_value 方法--第一个参数写固定,第二个参数才去传 option --最后直接返回
        return self.get_value('HOST', option)

未完待续。。。

相关推荐
小霖家的混江龙2 小时前
Token 到底怎么来的? 一文读懂大模型分词的核心逻辑, 看完秒懂!
人工智能·python·llm
曲幽2 小时前
Flask进阶必备:掌握中间件、钩子和扩展
python·flask·web·request·cors·wsgi
学编程的闹钟2 小时前
83【html的换行规则】
学习
伯明翰java2 小时前
Redis学习笔记-Set集合(2)
redis·笔记·学习
jennychary12 小时前
网工学习笔记:loopback 和route id
网络·笔记·学习
承渊政道2 小时前
C++学习之旅【C++基础知识介绍】
c语言·c++·学习·程序人生
nwsuaf_huasir2 小时前
深度学习2-pyTorch学习-第一个神经网络
pytorch·深度学习·学习
hiber9872 小时前
SLM-Lab安装过程(踩坑过程)
python·slm-lab
YJlio2 小时前
Active Directory 工具学习笔记(10.2):AdExplorer 实战(二)— 对象 / 属性 / 搜索 / 快照
java·笔记·学习