python3+tkinter实践历程(三)——基于requests与tkinter的模拟web登录工具
系列文章目录
python3+tkinter实践历程(一)——基于requests与tkinter的API工具
python3+tkinter实践历程(二)——基于tkinter的日志检索工具
python3+tkinter实践历程(三)——基于requests与tkinter的模拟web登录工具
python3+tkinter实践历程(四)——模仿CRT完成基于socket通信与tkinter的TCP串口客户端
分享背景
①分享意图在于帮助新入门的朋友,提供思路,里面详细的注释多多少少能解决一些问题。欢迎大佬指点跟交流。
②2021年8月,开始陆续有需求制作一些工具,因为python语言跟tkinter工具相对简单,所以就基于这些做了好几个不同用处的工具。
③分享从完成的第一个工具开始分享,分享到最新完成的工具,对于tkinter的理解也从一开始的摸索入门,到后来逐渐熟练,完成速度也越来越快,所用到的tk的功能点也越来越多。
④这是直至发布前做的倒数第二个工具了,从“python3+tkinter实践历程(二)——基于tkinter的日志检索工具”开始到这个工具,中间还有3个工具,但都过于偏产品功能的调度,所以难以展示。
制作背景
① 产品有允许登录的最大客户端数,测试人员需要打开n个浏览器来登录设备,数量过于庞大,所以使用接口登录的方式来代替浏览器登录。
② 并非所有测试人员都会使用jmeter等工具来进行接口登录,又或者对http没有了解,无法掌握设备的登录流程。
③ 此工具就把设备的登录流程封装好,使用者只需填写IP账号密码、登录个数等信息即可达到同样的登录效果。
最终功能
① 使用者无需接触http,无需接触接口,即可完成对设备的n个登录数量的测试
代码详解
代码中关于产品登录流程及加密方式不展示,不同的设备将登录模块修改成相应的登录流程即可。
import tkinter as tk
import threading
import time
import requests
# HTTP类
class Http(object):
def __init__(self, url, username, password):
# 必要信息,用于连接设备
self.url = 'http://' + url
self.username = username
self.password = password
# 登录所需的参数,根据登录方式的不同而变化
self.sessionId = ""
self.nonce = ""
self.token = ""
self.sessionKey = ""
# 用于记录是否登录成功
self.is_logined = False
def send_requests(self, api, reqhead=None, reqbody="", port="80", returnAll=False):
"""发送请求"""
try:
# 发送某个请求
resp = requests.post(api, headers=reqhead, data=reqbody, timeout=60)
if returnAll:
return resp
# 返回报文
return resp.text
except Exception as e:
print('发送接口异常:%s' % e)
def check_is_login(self):
"""查询是否登录状态"""
# 设备本身某个能检查是否已登录的接口,成功则是已登录状态,失败则是未登录状态
try:
if a:
return True
else:
return False
except Exception as e:
print('查询是否登录状态异常:%s' % e)
return False
def make_login(self):
"""做一次登录操作,并记录相关数据"""
# 此函数封装登录操作,
try:
# 若登录操作完成,则self.is_logined = True,原默认为False
self.is_logined = True
return True
else:
return res
except Exception as e:
print('登录异常:%s' % e)
def get_req_result(self):
"""
解析相应报文数据
try:
xx
except Exception as e:
print('解析相应xml数据异常:%s' % e)
return ''
def make_req_body(self):
"""
拼接Request Body,根据具体接口的要求带上相应的参数:
"""
try:
if encode:
return req_body.encode()
return req_body
except Exception as e:
print('拼接Request Body异常:%s' % e)
def get_psd_hash(self):
"""
密码加密
"""
try:
return psd_hash
except Exception as e:
print('密码加密异常:%s' % e)
def make_res_head(self):
"""拼接Request headers"""
try:
return req_head
except Exception as e:
print('拼接res_head异常:%s' % e)
class Application(tk.Frame):
# 继承于tkinter的Frame类,可使用Frame的布局方式
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.grid()
self.create_widgets() # 构造界面
self.is_running = False # 记录是否已正在运行
self.re_dict = dict() # 记录创建的HTTP类的对象
self.LOG_LINE_NUM = 0 # 记录日志打印框的数量
def create_widgets(self):
"""创建界面"""
# 一行先定义一个Frame,该行的其他控件就已该Frame为主。类似html开发中的一行一个div
# 第一行(用于IP、账号、密码)
row1 = tk.Frame(self)
row1.grid(row=0, column=0, sticky='w')
# 设备IP端口
dev_url = tk.Label(row1, text="设备IP及端口(xx:xx)")
dev_url.grid(row=0, column=0)
dev_url = tk.StringVar()
dev_url.set("10.100.10.183:80") # set的作用的预置的文字(在标签内可变的文本)
self.dev_url = tk.Entry(row1, textvariable=dev_url, width=20)
self.dev_url.grid(row=0, column=1)
# 账号
user_name = tk.Label(row1, text="账号")
user_name.grid(row=0, column=2)
user_name = tk.StringVar()
user_name.set("xxx") # 预设账号xxx
self.user_name = tk.Entry(row1, textvariable=user_name, width=20)
self.user_name.grid(row=0, column=3)
# 密码
password = tk.Label(row1, text="密码")
password.grid(row=0, column=4)
password = tk.StringVar()
password.set("123456") # 预设密码123456
self.password = tk.Entry(row1, textvariable=password, width=20)
self.password.grid(row=0, column=5)
# 第二行(用于登录数量框、开始按钮、成功失败数量的显示)
row2 = tk.Frame(self)
row2.grid(row=1, column=0, sticky='w')
# 登录数量框,提供给使用者输入
tk.Label(row2, text="登录数量").grid(row=0, column=0)
login_num = tk.StringVar()
login_num.set("0")
self.login_num = tk.Entry(row2, textvariable=login_num, width=10)
self.login_num.grid(row=0, column=1)
# 开始登录按钮,绑定登录函数,点击即触发
tk.Button(row2, text="开始登录", command=self.start_to_login).grid(row=0, column=2, padx=10, pady=10)
# 新建一个Label,用于成功数量的实时显示
self.success_text = tk.StringVar()
self.success_text.set('成功:')
tk.Label(row2, textvariable=self.success_text, width=10, anchor='w').grid(row=0, column=3, padx=(20, 10))
# 新建一个Label,用于成功数量的实时显示
self.fail_text = tk.StringVar()
self.fail_text.set('失败:')
tk.Label(row2, textvariable=self.fail_text).grid(row=0, column=4)
# 第三行(用于日志打印框、注意事项)
row3 = tk.Frame(self)
row3.grid(row=2, column=0, sticky='w')
# 日志打印框,用于打印程序运行的相关信息
self.log_data = tk.Text(row3, width=60, height=20)
# 滚动条,与日志打印框绑定
respone_slide_bar = tk.Scrollbar(row3, command=self.log_data.yview, orient="vertical")
respone_slide_bar.grid(row=0, column=1, sticky='ns')
self.log_data.grid(row=0, column=0)
self.log_data.config(yscrollcommand=respone_slide_bar.set)
# 注意事项的Label
note = """注意事项:\n1、测试前请先重启设备,使设备登录数量清0\n2、经过多台设备测试,最大登录数量一般为xxx\n3、只支持xxxxx版本及以上的设备"""
tk.Label(row3, text=note, anchor='w', justify='left', width=50, font=('', 11, '')).grid(row=0, column=2, padx=10, sticky='nw')
def login(self):
# 开始登录按钮绑定的函数--开始执行登录操作
url = self.dev_url.get() # 获取IP地址
username = self.user_name.get() # 获取账号
password = self.password.get() # 获取密码
times = int(self.login_num.get()) # 获取登录个数
if not url or not username or not password or not times:
self.write_log_to_Text('请先填写必要的信息')
return
if self.is_running: # 当登录开始时,self.is_running为True,此时再次点击开始按钮会返回
self.write_log_to_Text('测试已开始,无需重复开始') # self.write_log_to_Text函数用于打印日志
return
self.write_log_to_Text('开始登录')
self.is_running = True # 当登录开始时,self.is_running为True
self.success = 0 # 记录成功个数
self.fail = 0 # 记录失败个数
for i in range(1, times + 1): # 进入循环,从第1个到第times个
print('第%d个登录开始' % i)
s = str(i) # s为当前第几个设备
self.re_dict[s] = Http(url, username, password) # self.re_dict字典中记录创建的HTTP对象
result = self.re_dict[s].make_login() # 该对象执行make_login(),即登录操作
if result: # 如果返回为True,则登录操作完成,默认登录成功,self.success计数器+1,否则self.fail + 1
self.success = self.success + 1
print('第%s个登录成功,token为:%s,sessionId为:%s' % (s, self.re_dict[s].token, self.re_dict[s].sessionId))
self.success_text.set('成功:%d个' % self.success) # 利用self.success_text容器实时打印成功个数
else:
self.fail = self.fail + 1
print('第%s个登录失败' % s)
self.fail_text.set('失败:%d个' % self.fail) # 利用self.fail_text容器实时打印成功个数
self.write_log_to_Text('设备登录完成,登录成功:%d,登录失败:%d,等待30秒后检查是否仍在线' % (self.success, self.fail))
self.write_log_to_Text('等待30秒后检查是否仍在线')
time.sleep(30)
self.online = 0 # 记录设备在线个数
for s in self.re_dict:
result = self.re_dict[s].check_is_login() # 通过遍历self.re_dict中记录的HTTP对象,调用check_is_login()方法,确定每一个对象是否能完成该动作
if result:
self.online = self.online + 1
print('第%s个仍在线' % s)
if not result:
print('第%s个不在线' % s)
self.write_log_to_Text('检查结果:%d个登录成功,其中%d个仍在线' % (self.success, self.online))
self.is_running = False
def thread_it(self, func, *args):
"""将函数打包进线程"""
# 当程序在进行登录操作时,工具主界面不会卡死
# 创建
t = threading.Thread(target=func, args=args)
# 守护 !!!
t.setDaemon(True)
# 启动
t.start()
def start_to_login(self, *args):
# 将登录函数self.login放进线程运行
self.thread_it(self.login)
def write_log_to_Text(self, logmsg):
"""日志动态打印"""
self.LOG_LINE_NUM = int(self.log_data.index('end').split('.')[0]) - 1 # 记录日志的总行数
if self.LOG_LINE_NUM > 500: # 当日志行数大于500,将行数从最上方开始清除到剩余500行
del_target = float(self.LOG_LINE_NUM - 500 - 1)
self.log_data.delete(1.0, del_target)
logmsg_in = "%s\n" % logmsg # 为日志添加换行符
self.log_data.insert('end', logmsg_in) # 打印日志
self.log_data.see('end') # 滚动条自动跳转跟随日志的最新打印
root = tk.Tk() # 创建Tk对象
app = Application(master=root) # 创建Application类的对象app,主界面为root
root.title("模拟web登录工具") # 设置app标题
root.resizable(False, False) # 禁止调整窗口大小
sw = root.winfo_screenwidth() # 得到屏幕宽度
sh = root.winfo_screenheight() # 得到屏幕高度
# 窗口宽高为800x400
ww = 800
wh = 400
x = (sw-ww) / 2
y = (sh-wh) / 2
root.geometry("%dx%d+%d+%d" % (ww, wh, x, y)) # 设置窗口宽高为800x400,且自适应居中
root.deiconify() # 显示窗口
app.mainloop() # 进入消息循环