前言
在进行UI自动化过程会遇到滚动条下拉、隐藏元素定位、只读属性元素的编辑、富文本处理等,此时可以使用JS执行器简化我们的一些处理操作。
具体应用
JS执行器的使用步骤:
1.先写个JS脚本,如果需要获取操作后的值,JS脚本前面需要加return
2.使用webdriver的execute_script()方法执行脚本;方法中传递2个参数或1个参数;传递2个参数时,分别传入JS脚本、要操作的页面元素;传递1个参数时,传入JS脚本,对整个webdriver做操作。
3.操作通过JS执行器注入了JS脚本的页面元素或webdriver对象
接下来我们以百度、12306网站为例,演绎具体用法
1.通过JS脚本,获取页面元素的属性值
from selenium import webdriverdriver = webdriver.Chrome()#创建浏览器对象
driver.maximize_window()#窗口最大化driver.get(r'https://www.baidu.com')#访问百度
el = driver.find_element('id','su') #页面的百度一下元素
'''
写JS脚本,写JS脚本的步骤:
js:定义一个变量名js,用于存放所写的JS季报表
= :赋值
JS脚本全部用引号引起来,整个脚本就是一个字符串
arguments[0]:理解为占位符,代值被操作的元素或对象
.:连接被操作的元素对象与所做的具体操作
getAttribute():我们这里的举例的操作为:获取元素的某个属性值
JS脚本最前面加上return,是因为我们要获取对元素操作后的值
'''
js = "return arguments[0].getAttribute('value')" #JS脚本的意思为:返回某个元素的value属性的值
value = driver.execute_script(js,el)#执行JS脚本,传入的2个参数依次为:要执行的脚本、要操作的对象
print(value)#打印获取到的值
driver.quit()#关闭浏览器
执行的结果:
2.通过JS脚本,获取页面元素的文本值
from selenium import webdriverdriver = webdriver.Chrome()#创建浏览器对象
driver.maximize_window()#窗口最大化driver.get(r'https://www.12306.cn/index/')#访问12306#页面的出发日期元素
el = driver.find_element('xpath','/html/body/div[3]/div[2]/div/div[1]/div/div[2]/div[1]/div[1]/div[2]/label') #
js = "return arguments[0].innerHTML" #JS脚本的意思为:返回某个元素的文本值
value = driver.execute_script(js,el)#执行JS脚本,传入的2个参数依次为:要执行的脚本、要操作的对象
print(value)#打印获取到的值
driver.quit()#关闭浏览器
运行结果
3.通过JS脚本,删除页面元素的属性值,然后对页面元素进行操作
我们发现出发日期输入框因为有readonly属性,导致这个输入框我们无法直接输入日期,在实际的自动化操作中我们就只能通过点击右边的日历控件,从而选择日期,这样操作就变得非常的复杂,现在我们通过执行JS脚本,删除中个输入框的readonly属性,实现对这个输入框直接输入日期
from selenium import webdriver
from time import sleep
driver = webdriver.Chrome()#创建浏览器对象
driver.maximize_window()#窗口最大化driver.get(r'https://www.12306.cn/index/')#访问12306#页面的出发日期输入框
el = driver.find_element('xpath','//input[@id="train_date" and @class="input"]')
js = "arguments[0].removeAttribute('readonly')" #JS脚本的意思为:移除某个元素的readonly属性
driver.execute_script(js,el)#执行JS脚本,传入的2个参数依次为:要执行的脚本、要操作的对象
sleep(1)#强制等待1秒
el.clear() #清空输入框内容
el.send_keys("2021-07-11")#对输入框进行输入操作
sleep(3)
driver.quit()#关闭浏览器
执行结果
4.通过JS脚本,修改页面元素的属性值
from selenium import webdriver
from time import sleepdriver = webdriver.Chrome()#创建浏览器对象
driver.maximize_window()#窗口最大化driver.get(r'https://www.baidu.com')#访问百度
el = driver.find_element('id','su') #页面的百度一下元素js = "return arguments[0].setAttribute('value','AAAAA')" #JS脚本的意思为:返回某个元素的value属性的值
driver.execute_script(js,el)#执行JS脚本,传入的2个参数依次为:要执行的脚本、要操作的对象
sleep(10)
driver.quit()#关闭浏览器
执行结果
5.执行JS脚本,进行滚动条操作
from selenium import webdriver
from time import sleepdriver = webdriver.Chrome()#创建浏览器对象
driver.maximize_window()#窗口最大化
driver.implicitly_wait(30)driver.get(r'https://www.baidu.com')#访问百度
driver.find_element('id','kw').send_keys("它石科技")
driver.find_element('id','su').click()
sleep(3)
js = "document.documentElement.scrollTop=1000" #向下滚动
driver.execute_script(js)#执行JS脚本
sleep(3)
js1 = "document.documentElement.scrollTop=0" #先上滑动
driver.execute_script(js1)#执行JS脚本
sleep(3)
js2 = "window.scrollTo(100,200)" #左右滑动
driver.execute_script(js2)
sleep(3)
js3 = "arguments[0].scrollIntoView()"
el = driver.find_element('xpath','//span[text()="百度热搜"]')
sleep(1)
driver.execute_script(js3,el)
sleep(3)
driver.quit()#关闭浏览器
执行结果:
由于是动态的图,这里就不截图了,大家可以按照代码自己尝试
总结:
selenium底层就方式通过执行JS脚本来操作页面元素的,所以当我们使用普通封装的常规操作方法无法进行相关操作时,可以使用JS执行器来达到我们的操作目的。
3.logging模块的封装处理
具体封装好的log.py文件代码如下:
import logging
import os
import time
class Logging():
def make_log_dir(self,dirname='logs'): #创建存放日志的目录,并返回目录的路径
now_dir = os.path.dirname(__file__)
father_path = os.path.split(now_dir)[0]
path = os.path.join(father_path,dirname)
path = os.path.normpath(path)
if not os.path.exists(path):
os.mkdir(path)
return path
def get_log_filename(self):#创建日志文件的文件名格式,便于区分每天的日志
filename = "{}.log".format(time.strftime("%Y-%m-%d",time.localtime()))
filename = os.path.join(self.make_log_dir(),filename)
filename = os.path.normpath(filename)
return filename
def log(self):#生成日志的主方法
logger = logging.getLogger()
logger.setLevel(logging.INFO)
if not logger.handlers: #作用:防止日志重打
sh = logging.StreamHandler()
fh =
logging.FileHandler(filename=self.get_log_filename(),mode='a',encoding="utf-8")
fmt = logging.Formatter("%(asctime)s-%(levelname)s-%(filename)s-
Line:%(lineno)d-Message:%(message)s")
sh.setFormatter(fmt=fmt)
fh.setFormatter(fmt=fmt)
logger.addHandler(sh)
logger.addHandler(fh)
return logger
if __name__ == '__main__':
#Logging().log().debug("1111111111111111111111")
Logging().log().info("222222222222222222222222")
Logging().log().error("附件为IP飞机外婆家二分IP文件放")
selenium中的非input型上传
我们在之前的文章中介绍了有四种方式可以实现,接下来我们展开说明一下实现
win32gui
废话不多说,上代码先:
示例网址:http://www.sahitest.com/demo/php/fileUpload.htm
# -*- coding: utf-8 -*-
from selenium import webdriver
import win32gui
import win32con
import timedr = webdriver.Firefox()
dr.get('http://sahitest.com/demo/php/fileUpload.htm')
upload = dr.find_element_by_id('file')
upload.click()
time.sleep(1)# win32gui
dialog = win32gui.FindWindow('#32770', u'文件上传') # 对话框
ComboBoxEx32 = win32gui.FindWindowEx(dialog, 0, 'ComboBoxEx32', None)
ComboBox = win32gui.FindWindowEx(ComboBoxEx32, 0, 'ComboBox', None)
Edit = win32gui.FindWindowEx(ComboBox, 0, 'Edit', None) # 上面三句依次寻找对象,直到找到输入框Edit对象的句柄
button = win32gui.FindWindowEx(dialog, 0, 'Button', None) # 确定按钮Buttonwin32gui.SendMessage(Edit, win32con.WM_SETTEXT, None, 'd:\\baidu.py') # 往输入框输入绝对地址
win32gui.SendMessage(dialog, win32con.WM_COMMAND, 1, button) # 按buttonprint upload.get_attribute('value')
dr.quit()
结果:
baidu.py
在这里你需要一个非常重要的小工具:Spy++,百度一下有很多,当然你也可以用autoIT自带的工具,不过没有这个好用,建议去下一个吧。
而且,你得安装pywin32的库,你可以到这里找到对应你Python版本的库,注意32位还是64位一定要和你安装的Python版本对应。
安装完成之后在【开始菜单Python的文件夹】里看到PyWin32的文档【Python for GitCode - 全球开发者的开源社区,开源代码托管平台 Documentation】,你能从中找到对应的方法API。
简单介绍几个用到的:
win32gui.FindWindow(lpClassName=None, lpWindowName=None):
自顶层窗口开始寻找匹配条件的窗口,并返回这个窗口的句柄。
lpClassName:类名,在Spy++里能够看到
lpWindowName:窗口名,标题栏上能看到的名字
代码示例里我们用来寻找上传窗口,你可以只用其中的一个,用classname定位容易被其他东西干扰,用windowname定位不稳定,不同的上传对话框可能window_name不同,怎么定位取决于你的情况。
win32gui.FindWindowEx(hwndParent=0, hwndChildAfter=0, lpszClass=None, lpszWindow=None)
搜索类名和窗体名匹配的窗体,并返回这个窗体的句柄。找不到就返回0。
hwndParent:若不为0,则搜索句柄为hwndParent窗体的子窗体。
hwndChildAfter:若不为0,则按照z-index的顺序从hwndChildAfter向后开始搜索子窗体,否则从第一个子窗体开始搜索。
lpClassName:字符型,是窗体的类名,这个可以在Spy++里找到。
lpWindowName:字符型,是窗口名,也就是标题栏上你能看见的那个标题。
代码示例里我们用来层层寻找输入框和寻找确定按钮
win32gui.SendMessage(hWnd, Msg, wParam, lParam)
hWnd:整型,接收消息的窗体句柄
Msg:整型,要发送的消息,这些消息都是windows预先定义好的,可以参见系统定义消息(System-Defined Messages)
wParam:整型,消息的wParam参数
lParam:整型,消息的lParam参数
代码示例里我们用来向输入框输入文件地址以及点击确定按钮
至于win32api模块以及其他的方法,这里不进行更多描述,想要了解的自行百度或看pywin32文档。
SendKeys
首先要安装SendKeys库,可以用pip安装
pip install SendKeys
代码示例:
示例网址:http://www.sahitest.com/demo/php/fileUpload.htm
# -*- coding: utf-8 -*-
from selenium import webdriver
import win32gui
import win32con
import timedr = webdriver.Firefox()
dr.get('http://sahitest.com/demo/php/fileUpload.htm')
upload = dr.find_element_by_id('file')
upload.click()
time.sleep(1)# SendKeys
SendKeys.SendKeys('D:\\baidu.py') # 发送文件地址
SendKeys.SendKeys("{ENTER}") # 发送回车键print upload.get_attribute('value')
dr.quit()
结果:
baidu.py
1
通过SendKeys库可以直接向焦点里输入信息,不过要注意在打开窗口是略微加一点等待时间,否则容易第一个字母send不进去(或者你可以在地址之前加一个无用字符),不过我觉得这种方法很不稳定,不推荐。
keybd_event
win32api提供了一个keybd_event()方法模拟按键,不过此方法比较麻烦,也不稳定,所以很不推荐,下面给出部分代码示例,如果想要研究,可以自己研究研究
...# 先找一个input框,输入想要上传的文件的地址,剪切到剪贴板
video.send_keys('C:\\Users\\Administrator\\Pictures\\04b20919fc78baf41fc993fd8ee2c5c9.jpg')
video.send_keys(Keys.CONTROL, 'a') # selenium的send_keys(ctrl+a)
video.send_keys(Keys.CONTROL, 'x') # (ctrl+x)
driver.find_element_by_id('uploadImage').click() # 点击上传按钮,打开上传框# 粘贴(ctrl + v)
win32api.keybd_event(17, 0, 0, 0) # 按下按键 ctrl
win32api.keybd_event(86, 0, 0, 0) # 按下按键 v
win32api.keybd_event(86, 0, win32con.KEYEVENTF_KEYUP, 0) # 升起按键 v
win32api.keybd_event(17, 0, win32con.KEYEVENTF_KEYUP, 0) # 升起按键 ctrl
time.sleep(1)# 回车(enter)
win32api.keybd_event(13, 0, 0, 0) # 按下按键 enter
win32api.keybd_event(13, 0, win32con.KEYEVENTF_KEYUP, 0) # 升起按键 enter...
是不是很麻烦,当然,你甚至可以用按键把整个路径输入进去,不过,我想没人愿意这么做的。而且在此过程中你不能随意移动鼠标,不能使用剪贴板,太不稳定了,所以非常不建议你用这种办法。
多文件上传
接下来还有一种情况值得我们考虑,那就是多文件上传。如何上传多个文件,当然我们还是往输入框里输入文件路径,所以唯一要搞清楚的就是多文件上传时,文件路径是怎么写的。
我来告诉你吧,多文件上传就是在文件路径框里用引号括起单个路径,然后用逗号隔开多个路径,就是这么简单,例如:
“D:\a.txt” “D:\b.txt”
但需要注意的是:只有多个文件在同一路径下,才能这样用,否则是会失败的(下面的写法是不可以的):
“C:\a.txt” “D:\b.txt”
接下里找一个例子试试:
# -*- coding: utf-8 -*-from selenium import webdriver
import win32gui
import win32con
import timedr = webdriver.Firefox()
dr.get('http://www.sucaijiayuan.com/api/demo.php?url=/demo/20150128-1')dr.switch_to.frame('iframe') # 一定要注意frame
dr.find_element_by_class_name('filePicker').click()
time.sleep(1)dialog = win32gui.FindWindow('#32770', None)
ComboBoxEx32 = win32gui.FindWindowEx(dialog, 0, 'ComboBoxEx32', None)
ComboBox = win32gui.FindWindowEx(ComboBoxEx32, 0, 'ComboBox', None)
Edit = win32gui.FindWindowEx(ComboBox, 0, 'Edit', None)
button = win32gui.FindWindowEx(dialog, 0, 'Button', None)# 跟上面示例的代码是一样的,只是这里传入的参数不同,如果愿意可以写一个上传函数把上传功能封装起来
win32gui.SendMessage(Edit, win32con.WM_SETTEXT, 0, '"d:\\baidu.py" "d:\\upload.py" "d:\\1.html"')
win32gui.SendMessage(dialog, win32con.WM_COMMAND, 1, button)print dr.find_element_by_id('status_info').text
dr.quit()
结果:
选中3张文件,共1.17KB。
可见,多文件上传并没有那么复杂,也很简单,唯一的区别就是输入的参数不同而已。autoIT也可以实现,有兴趣可以自己试试。
而且我们可以发现一点,就是上面的这个窗口的代码跟之前示例中的基本是一样,说明我们可以把上传的部分抽出来,写一个函数,这样每次要上传,直接去调用函数,传入参数即可。