提交 ec796de8 authored 作者: 陈泽健's avatar 陈泽健

1. 2024-10-21

   - 修改了自动化测试脚本架构,使用PO模式,将共有方法封装在base目录下,任何模块都可以进行调用。
   - 将测试数据csv为文件统一放在测试数据目录下,并标注所属模块功能。
   - 将驱动加载方式改为更加灵活的自动下载方式,避免其他人员使用时手动下载驱动。
   - 补充安卓信息模块的Mqtt主题上报以及接收脚本,但暂时还未与实际mqtt主题进行调试,需要先整理出所有的mqtt主题,再进行代码调试。
上级 a17198d1
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.10 (ubains-module-test)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (ubains-module-test)" project-jdk-type="Python SDK" />
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/ubains-module-test.iml" filepath="$PROJECT_DIR$/.idea/ubains-module-test.iml" />
</modules>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.10 (ubains-module-test)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="1b298f03-b3a2-4f3f-9fa2-7f833ec35924" name="更改" comment="" />
<list default="true" id="1b298f03-b3a2-4f3f-9fa2-7f833ec35924" name="更改" comment="">
<change afterPath="$PROJECT_DIR$/预定系统/README.md" afterDir="false" />
<change afterPath="$PROJECT_DIR$/预定系统/base/Mqtt.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/预定系统/安卓信息模块/安卓信息上报.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/预定系统/登录模块/Page/LoginBy_ActAndPwd_PyTest.py" beforeDir="false" afterPath="$PROJECT_DIR$/预定系统/登录模块/账号密码登录.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/预定系统/登录模块/Page/Login_By_Sms_Pytest.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/预定系统/登录模块/Page/Login_Test.csv" beforeDir="false" afterPath="$PROJECT_DIR$/预定系统/测试数据/Login_Test.csv" afterDir="false" />
<change beforePath="$PROJECT_DIR$/预定系统/登录模块/Page/Login_Test_UnitTest.py" beforeDir="false" afterPath="$PROJECT_DIR$/预定系统/登录模块/Login_Test_UnitTest.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/预定系统/登录模块/base/Base.py" beforeDir="false" afterPath="$PROJECT_DIR$/预定系统/base/Base.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/预定系统/登录模块/base/BasePage.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/预定系统/登录模块/base/Broswer_driver.py" beforeDir="false" afterPath="$PROJECT_DIR$/预定系统/base/Broswer_driver.py" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
......@@ -25,18 +40,26 @@
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"RunOnceActivity.ShowReadmeOnStart": "true",
"git-widget-placeholder": "master",
"last_opened_file_path": "D:/GithubData/ubains-module-test",
"nodejs_package_manager_path": "npm",
"settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable",
"vue.rearranger.settings.migration": "true"
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;Python 测试.Python 测试 (LoginBy_ActAndPwd_PyTest.py 内).executor&quot;: &quot;Run&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;develop&quot;,
&quot;last_opened_file_path&quot;: &quot;D:/GithubData/ubains-module-test/ubains-module-test&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
}
}]]></component>
}</component>
<component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS">
<recent name="D:\GithubData\ubains-module-test\ubains-module-test\预定系统\登录模块" />
<recent name="D:\GithubData\ubains-module-test\ubains-module-test\预定系统\测试数据" />
<recent name="D:\GithubData\ubains-module-test\ubains-module-test\预定系统" />
<recent name="D:\GithubData\ubains-module-test\预定系统" />
</key>
</component>
......@@ -58,6 +81,8 @@
<updated>1728379882087</updated>
<workItem from="1728379883141" duration="132000" />
<workItem from="1728380017193" duration="171000" />
<workItem from="1729481397357" duration="2465000" />
<workItem from="1729519480013" duration="2092000" />
</task>
<servers />
</component>
......@@ -74,4 +99,7 @@
</breakpoints>
</breakpoint-manager>
</component>
<component name="com.intellij.coverage.CoverageDataManagerImpl">
<SUITE FILE_PATH="coverage/ubains_module_test$.coverage" NAME=" 覆盖结果" MODIFIED="1729517965711" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/预定系统/登录模块" />
</component>
</project>
\ No newline at end of file
## 更新记录
1. 2024-10-21
- 修改了自动化测试脚本架构,使用PO模式,将共有方法封装在base目录下,任何模块都可以进行调用。
- 将测试数据csv为文件统一放在测试数据目录下,并标注所属模块功能。
- 将驱动加载方式改为更加灵活的自动下载方式,避免其他人员使用时手动下载驱动。
- 补充安卓信息模块的Mqtt主题上报以及接收脚本,但暂时还未与实际mqtt主题进行调试,需要先整理出所有的mqtt主题,再进行代码调试。
import os
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager
import logging
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
class Browser_driver(object):
def browser_init(self):
# logging.info('初始化开始!')
# 动态获取 chromedriver 路径
driver_path = os.getenv('CHROMEDRIVER_PATH', "C:\\Program Files\\Google\\Chrome\\Application\\chromedriver.exe")
try:
# 尝试使用指定路径初始化 WebDriver
self.driver = webdriver.Chrome(service=Service(driver_path))
# 自动下载并使用 ChromeDriver
self.driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()))
# 设置隐式等待时间
self.driver.implicitly_wait(5) # 延长至5秒
# logging.info('初始化成功,驱动正常!')
except Exception as e:
logging.error(f'初始化失败: {e}')
raise
return self.driver
\ No newline at end of file
return self.driver
# Mqtt.py
# -*- coding: utf-8 -*-
# cython: language_level=3
import json
import paho.mqtt.client as mqtt
class MQTTClient:
def __init__(self, broker_address, port):
"""
MQTT客户端初始化函数
:param broker_address: MQTT代理服务器地址
:param port: MQTT代理服务器端口
"""
# 保存MQTT代理服务器地址和端口作为实例变量
self.broker_address = broker_address
self.port = port
# 创建MQTT客户端实例,使用MQTTv311协议版本
self.client = mqtt.Client(protocol=mqtt.MQTTv311)
# 初始化接收到的消息变量
self.received_message = None
# 设置回调函数
self.client.on_connect = self.on_connect # 当客户端连接到代理时调用
self.client.on_message = self.on_message # 当客户端接收到消息时调用
def on_connect(self, client, userdata, flags, rc):
"""
当客户端连接到MQTT代理时调用的回调函数。
参数:
- client: 客户端实例,用于操作MQTT代理。
- userdata: 在客户端初始化时定义的任意类型的数据。
- flags: 连接应答的标志位。
- rc: 连接结果的返回码,用于判断连接是否成功。
此函数没有返回值。
功能描述:
- 如果连接成功(rc == 0),则打印成功连接的消息。
- 如果连接失败,则打印失败的消息,并显示返回码。
"""
if rc == 0:
print("Connected to MQTT Broker!")
else:
print(f"Failed to connect, return code {rc}")
def on_message(self, client, userdata, msg):
"""
当接收到消息时调用的回调函数。
参数:
- client: MQTT客户端实例。
- userdata: 用户定义的数据,未使用。
- msg: 消息数据,包含主题(topic)和负载(payload)。
此函数将解码消息负载,并将其存储为实例属性`received_message`,然后打印出接收到的消息和其对应的主题。
"""
# 解码消息负载并存储为实例属性
self.received_message = msg.payload.decode()
# 打印接收到的消息和其对应的主题
print(f"Received message: {self.received_message} on topic {msg.topic}")
def connect(self):
"""
Establishes a connection to the MQTT broker.
This method calls the connect method of the self.client instance to establish a network connection to the MQTT broker,
using the broker address and port number that were set during class initialization. After the connection is established,
it starts the network loop using loop_start to keep the connection alive and handle MQTT protocol communications.
"""
self.client.connect(self.broker_address, self.port)
self.client.loop_start()
def disconnect(self):
"""
从服务器断开连接。
该方法首先停止客户端的循环,然后断开与服务器的连接。
"""
# 停止客户端的循环
self.client.loop_stop()
# 断开与服务器的连接
self.client.disconnect()
def publish(self, topic, message, qos=0):
"""
发布消息到指定主题。
参数:
- topic: 消息发布的目标主题。
- message: 要发布的消息内容,将被转换为JSON格式。
- qos: 消息的服务质量等级,默认为0。
此方法将消息内容序列化为JSON格式,并通过客户端发布到指定的主题上。
"""
# 将消息内容转换为JSON格式并发布到指定主题
self.client.publish(topic, json.dumps(message), qos=qos)
def subscribe(self, topic, qos=0):
"""
订阅指定的主题。
参数:
- topic (str): 要订阅的主题名称。
- qos (int): 消息的质量服务等级,可选参数,默认为0。
此方法调用client实例的subscribe方法来执行MQTT协议的订阅操作。
"""
self.client.subscribe(topic, qos=qos)
def wait_for_message(self, timeout=5):
"""
等待接收消息,直到达到超时时间。
本函数会持续检查是否接收到消息,直到消息到达或超出指定的超时时间。
这种等待机制适用于需要同步处理消息的场景,通过轮询方式检查消息状态。
参数:
timeout (int): 超时时间(秒),默认为5秒。表示在没有接收到消息的情况下,函数将等待的最大时间。
返回:
接收到的消息内容,如果没有消息在指定时间内到达,则返回None。
"""
# 导入time模块用于计算时间差和延时
import time
# 记录开始等待的时间
start_time = time.time()
# 当没有接收到消息且未超出超时时间时,持续等待
while self.received_message is None and time.time() - start_time < timeout:
# 短暂暂停,避免过高CPU占用
time.sleep(0.1)
# 返回接收到的消息,如果没有消息则返回None
return self.received_message
# main.py
# -*- coding: utf-8 -*-
# cython: language_level=3
from 预定系统.base.Mqtt import MQTTClient
if __name__ == "__main__":
broker_address = "192.168.5.218"
port = 1883
topic = "/maintain/room/master/client/"
client_udid = '79ac2430-6a98-5509-9e99-65b974eb70a1'
message = {
"action": "_updatemaster",
"client_udid": client_udid,
"data": [
{
"item": "environmental",
"pm25": 0,
"co2": 0,
"temp": 0,
"tvoc": 0,
"humi": 0,
"hcho": 0
},
{
"item": "conference",
"power": 0,
"exist": 1,
"run": "hello world"
}
]
}
# 创建客户端实例
mqtt_client = MQTTClient(broker_address, port)
# 连接到MQTT服务器
mqtt_client.connect()
# 订阅主题
mqtt_client.subscribe(topic)
# 发布消息
mqtt_client.publish(topic, message)
# 等待接收消息
received_message = mqtt_client.wait_for_message(timeout=5)
# 检查接收到的消息
if received_message:
print("Message received successfully!")
else:
print("No message received within the timeout period.")
# 断开连接
mqtt_client.disconnect()
import pytest
import logging
from selenium import webdriver
from selenium.common import StaleElementReferenceException
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import csv
from 登录模块.base.Broswer_driver import Browser_driver
from 预定系统.base.Broswer_driver import Browser_driver
def read_csv_data():
"""
......@@ -15,7 +13,7 @@ def read_csv_data():
"""
data = []
try:
with open('Login_Test.csv', mode='r', encoding='utf-8') as csvfile:
with open('../测试数据/Login_Test.csv', mode='r', encoding='utf-8') as csvfile:
csv_reader = csv.DictReader(csvfile)
for row in csv_reader:
data.append((row['username'], row['password'], row['verification_code']))
......
import pytest
import logging
import os
import csv
import configparser
from selenium.common import StaleElementReferenceException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from 登录模块.base.Broswer_driver import Browser_driver
# 初始化日志记录器
logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def read_csv_data(file_path=None):
"""
读取 CSV 文件数据
"""
data = []
if file_path is None:
file_path = os.getenv('CSV_FILE_PATH', 'Login_Test.csv')
try:
with open(file_path, mode='r', encoding='utf-8') as csvfile:
csv_reader = csv.DictReader(csvfile)
for row in csv_reader:
data.append((row['username'], row['password'], row['verification_code']))
except FileNotFoundError:
logger.error("CSV 文件未找到,请检查文件路径")
pytest.exit("CSV 文件未找到,请检查文件路径")
except csv.Error as e:
logger.error(f"CSV 格式错误: {e}")
pytest.exit(f"CSV 格式错误: {e}")
except Exception as e:
logger.error(f"出现错误: {e}")
pytest.exit(f"出现错误: {e}")
return data
@pytest.fixture(scope='session')
def driver():
"""
初始化浏览器并打开指定URL
"""
url = 'https://nat.ubainsyun.com:11046'
logging.info("'----------' 正在初始化浏览器 '----------'")
try:
driver = Browser_driver().browser_init()
logging.info("'----------' 浏览器初始化成功 '----------'")
except Exception as e:
logging.error(f"浏览器初始化失败: {e}")
raise
driver.implicitly_wait(10)
driver.maximize_window()
logging.info("'----------' 正在打开URL '----------'")
try:
driver.get(url)
logging.info(f"成功打开URL: {url}")
except Exception as e:
logging.error(f"打开URL失败: {e}")
raise
handle_ssl_warning(driver)
yield driver
driver.quit()
def handle_ssl_warning(driver):
"""
处理 SSL 证书警告
"""
try:
logging.info("'----------' 正在处理SSL证书警告 '----------'")
WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.XPATH, '//*[@id="details-button"]'))
).click()
WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.XPATH, '//*[@id="proceed-link"]'))
).click()
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.TAG_NAME, 'body'))
)
logging.info("'----------' SSL证书警告处理完成 '----------'")
except Exception as e:
logging.error(f"处理 SSL 证书警告遇到错误: {e}")
raise
def _find_element(driver, by, value):
"""
查找元素,并处理可能的异常
"""
try:
return WebDriverWait(driver, 20).until(
EC.presence_of_element_located((by, value))
)
except StaleElementReferenceException:
return WebDriverWait(driver, 20).until(
EC.presence_of_element_located((by, value))
)
def send_sms_verification_code(driver, phone_number):
"""
模拟发送短信验证码
"""
# 假设有一个发送短信验证码的API接口
# 这里只是一个示例,实际应用中需要调用真实的API
logging.info(f"模拟发送短信验证码至 {phone_number}")
def perform_login(driver, username, password, verification_code):
"""
执行登录操作
"""
driver.implicitly_wait(5)
driver.refresh()
try:
username_input = _find_element(driver, By.XPATH, '//*[@id="app"]/div/div[3]/div[1]/div[2]/div/form/div[1]/div/input')
username_input.clear()
username_input.send_keys(username)
password_input = _find_element(driver, By.XPATH, '//*[@id="app"]/div/div[3]/div[1]/div[2]/div/form/div[2]/div/input')
password_input.clear()
password_input.send_keys(password)
# 假设验证码已经发送并接收
send_sms_verification_code(driver, username) # 发送验证码
verification_input = _find_element(driver, By.XPATH, '//*[@id="app"]/div/div[3]/div[1]/div[2]/div/form/div[3]/div[1]/div/input')
verification_input.clear()
verification_input.send_keys(verification_code)
login_button = _find_element(driver, By.XPATH, '//*[@id="app"]/div/div[3]/div[1]/div[2]/div/form/div[4]/input')
login_button.click()
if check_login_null(driver):
logger.info("登录失败,必填项不能为空!断言成功!")
elif check_login_success(driver):
logger.info("登录成功,断言成功!")
return_to_login_page(driver)
elif check_login_failure(driver):
logger.info("登录失败, 必填项错误!断言成功!")
else:
logger.error("登录状态未知,断言失败!")
pytest.fail("登录状态未知,断言失败!")
except StaleElementReferenceException as e:
logger.error(f"元素已失效,重新查找... {e}")
pytest.fail(f"元素已失效,重新查找... {e}")
except Exception as e:
logger.error(f"登录操作失败: {e}")
pytest.fail(f"登录操作失败: {e}")
def return_to_login_page(driver):
"""
返回登录页面
"""
try:
logout_button = _find_element(driver, By.XPATH, '//*[@id="app"]/div/div[1]/div/img[3]')
logout_button.click()
logging.info("已成功返回登录页面")
driver.refresh()
driver.implicitly_wait(5)
except Exception as e:
logger.error(f"返回登录页面失败: {e}")
pytest.fail(f"返回登录页面失败: {e}")
def check_login_null(driver):
"""
检查登录失败,必填项为空的情况
"""
try:
login_element_text = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.XPATH, '/html/body/div[2]/p'))
).text
assert "请输入" in login_element_text, "登录失败后未找到'请输入'提示语"
return True
except Exception as e:
logger.error(f"登录失败检查失败: {e}")
return False
def check_login_success(driver):
"""
检查登录成功的情况
"""
try:
message_element_text = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.XPATH, "//*[contains(text(), '欢迎')]"))
).text
assert "欢迎 预定标准版测试" == message_element_text, "登录成功后找到'欢迎 预定标准版测试'提示语"
return True
except Exception as e:
logger.error(f"登录成功检查失败: {e}")
return False
def check_login_failure(driver):
"""
检查登录失败的情况
"""
try:
login_element_text = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.XPATH, '//*[@id="app"]/div/div[3]/div[1]/div[1]/div[1]'))
).text
assert "账号登录" == login_element_text, "登录失败后未找到'账号登录'提示语"
return True
except Exception as e:
logger.error(f"登录失败检查失败: {e}")
return False
@pytest.mark.parametrize("username,password,verification_code", read_csv_data())
def test_login_from_csv(driver, username, password, verification_code):
"""
从 CSV 文件读取并测试登录情况
"""
logger.info(f"'----------' 开始执行测试 '----------'")
perform_login(driver, username, password, verification_code)
logger.info(f"'----------' 执行完成测试 '----------'")
driver.implicitly_wait(10)
if __name__ == '__main__':
pytest.main()
from time import sleep
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager
from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class BasePage(object):
def __init__(self):
print('初始化开始!')
# 自动下载驱动
# self.driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()))
# 直接指定下载好的 ChromeDriver 路径
# # try:
self.driver_path = "C:\Program Files\Google\Chrome\Application\chromedriver.exe" # 替换为实际路径
self.driver = webdriver.Chrome(service=ChromeService(self.driver_path))
sleep(5)
print('初始化成功,驱动正常!')
# except Exception as e:
# print(f"初始化失败: {e}")
......@@ -6,7 +6,7 @@ from selenium.common import StaleElementReferenceException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from 登录模块.base.Broswer_driver import Browser_driver
from 预定系统.base.Broswer_driver import Browser_driver
# 初始化日志记录器
logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')
......@@ -21,7 +21,7 @@ def read_csv_data(file_path=None):
# 使用环境变量或参数指定文件路径
if file_path is None:
file_path = os.getenv('CSV_FILE_PATH', 'Login_Test.csv')
file_path = os.getenv('CSV_FILE_PATH', '../测试数据/Login_Test.csv')
try:
# 打开 CSV 文件,使用 utf-8 编码读取
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论