Web自动化测试Python实践

以Python开发语言为例,做一些web自动化的实践.

了解selenium

# 安装Python Binding, 最新版3.141.0
pip install selenium

代码示例

from selenium import webdriver
from selenium.webdriver import DesiredCapabilities

# 获取driver
driver = webdriver.Chrome()
# 忽略https检查
capabilities = DesiredCapabilities.CHROME.copy()
capabilities['acceptInsecureCerts'] = True
with webdriver.Chrome(desired_capabilities=capabilities) as driver:
    ...

# 打开页面
driver.get("https://xulizhao.com")

driver.maximize_window()  

下载/安装浏览器driver

国内下载不易,我上传了一份到云盘

把需要的driver解压缩,然后复制到C:\Windows目录.(Edge driver重命名为MicrosoftWebDriver.exe)

selenium代码结构

  • WebDriver: 表示浏览器
  • WebElement: 表示某个DOM节点

定位元素

定位元素的策略

  • id 或 name
  • class name : 只支持单一名称
  • tag name: 通常用来寻找一组元素
  • CSS selector
  • (partial) link text: 找到匹配的第一个链接 (底层使用xpath)
  • XPath: 尽量少用

Selenium 4.0 引入Relative Locators,支持找相邻元素,包含above/below/toLeftOf/toRightOf/near


# 查找元素
cheese = driver.find_element_by_id("cheese")
cheddar = cheese.find_elements_by_id("cheddar")   # 支持元素内查找

driver.find_element_by_id('login').value_of_css_property('color')

# 使用更简洁的CSS查找方式
cheddar = driver.find_element_by_css_selector("#cheese #cheddar")

# 返回多个元素的集合,可为空
mucho_cheese = driver.find_elements_by_css_selector("#cheese li")

# 说明
find_element_by_xpath("//h1")  # 等价于 
find_element(by=By.XPATH, value='//h1')


# 查找具有焦点的元素
attr = driver.switch_to.active_element.get_attribute("title")

常用操作

  • send_keys: 输入文本或上传文件(文件路径)
  • click
driver.current_url  # 获取当前 URL
# driver.back()   # driver.forward()  # driver.refresh()
driver.title  # 获取标题

# 发送键盘动作
el_name = driver.find_element_by_name("name")
el_name.send_keys("xulz")
el_name.clear()
# 输入并回车(提交表单)
el_name..send_keys("xulz" + Keys.RETURN)

# 组合键
action = webdriver.ActionChains(driver)
search = driver.find_element_by_name("q")
# 输入 QWERTYqwerty
action.key_down(Keys.SHIFT).send_keys_to_element(search, "qwerty").key_up(Keys.SHIFT).send_keys("qwerty").perform()

等待

  • 显式等待
    • 设置显式等待(wait.until),等待直到某条件满足. 等待将会吞没在没有找到元素时引发的 no such element 的错误.
    • 善用预期条件
  • 隐式等待/Implicit Wait Timeout
    • 在元素不可用时, 轮询DOM一段时间. 默认禁用,即超时时间为 0 (在整个会话期有效)
    • 注意:不要混合使用隐式和显式等待
  • 流畅等待:
# 预期条件
from selenium.webdriver.support.expected_conditions as EC

# 等待直到元素出现
wait = WebDriverWait(driver, 10)  # 支持超时时间设置
first_result = wait.until(presence_of_element_located((By.CSS_SELECTOR, "h3>div")))

el = WebDriverWait(driver).until(lambda d: d.find_element_by_tag_name("p"))


# 隐式等待
driver.implicitly_wait(10)


# 流畅等待
wait = WebDriverWait(driver, 10, poll_frequency=1, ignored_exceptions=[ElementNotVisibleException, ElementNotSelectableException])
element = wait.until(EC.element_to_be_clickable((By.XPATH, "//div")))


# 打开文件上传对话框时, Internet Explorer默认超时为 1s
options = webdriver.IeOptions
options.file_upload_dialog_timeout(2000)
driver = webdriver.Ie(ie_driver_path, options=options)

鼠标动作

  • clickAndHold: 单击(不释放)
  • contextClick: 上下文点击(右键单击)
  • doubleClick
  • moveToElement
  • moveByOffset
  • dragAndDrop
  • dragAndDropBy
  • release
# 鼠标点击
driver.find_element_by_css_selector("input[type='submit']").click()

# 拖放
ActionChains(driver).drag_and_drop(source, target).perform()

Select元素

from selenium.webdriver.support.select import Select

select_element = driver.find_element_by_id('selectElementID')
select_object = Select(select_element)

# 选择选项
# Select an <option> based upon the <select> element's internal index
select_object.select_by_index(1)
# Select an <option> based upon its value attribute
select_object.select_by_value('value1')
# Select an <option> based upon its text
select_object.select_by_visible_text('Bread')

# 取消选择,也支持其他方式
select_object.deselect_by_index(1)


# 被选择项
# Return a list[WebElement] of options that have been selected
all_selected_options = select_object.all_selected_options

# Return a WebElement referencing the first selection option found by walking down the DOM
first_selected_option = select_object.first_selected_option

# 是否允许多选
does_this_allow_multiple_selections = select_object.is_multiple

Cookie操作

Cookies主要用于识别用户并加载存储的信息.

# 添加cookie
driver.add_cookie({"name": "test1", "value": "cookie1"})
driver.add_cookie({"name": "test2", "value": "cookie2"})

# 获取命名cookie
print driver.get_cookie("foo")
# 获取全部cookies
print driver.get_cookies()

# 删除
driver.delete_cookie("test1")
driver.delete_all_cookies()

# 控制cookie是否与第三方站点发起的请求一起发送. 引入其是为了防止CSRF(跨站请求伪造)攻击
# Adds the cookie into current browser context with sameSite 'Strict' (or) 'Lax'
driver.add_cookie({"name": "foo", "value": "value", 'sameSite': 'Strict'})
driver.add_cookie({"name": "foo1", "value": "value", 'sameSite': 'Lax'})  # cookie将与第三方网站发起的GET请求一起发送

其他

element.get_attribute('innerHTML')
source_code = elem.get_attribute("outerHTML")

# xpath
_locator = (By.XPATH, f"//div[text()='{mobile}']")
driver.find_element_by_xpath("//p[@id='one']/following-sibling::p")

# 截图
driver.save_screenshot('sample_screenshot_1.png')
driver.get_screenshot_as_file('sample_screenshot_2.png')

POM/页面对象模型

# 使用PyPOM
pip install PyPOM[splinter]

源码分析:

最常使用的是Page和Region类,分别代表页面对象和页面的一部分。

  • Page:
    • 初始化参数:driver,base_url,timeout
    • seed_url: 即实际访问的页面地址
    • open(): 访问seed_url并自动调用wait_for_page_to_load()方法
    • loaded: 一个代表页面是否加载完毕的bool值,将被wait_for_page_to_load()调用
  • Region: 共享的页面部分(Header,Footer)或重复的页面部分(Result)
    • 初始化参数: page(区域所在的页面对象), root(区别的根页面元素,用户缩小查找范围)
    • root: 传入或使用_root_locator,会在区域内查找元素
    • wait_for_region_to_load():等待到区域加载完毕
    • find_element()/find_elements(): 根据策略和定位符查找元素
    • is_element_present()/is_element_displayed():元素是否出现或可见
    • loaded: 区域是否已加载完毕
  • WebView: 基类
    • 初始化参数: driver, timeout
    • driver/find_element()/find_elements()/is_element_present()/is_element_displayed()

Selenium Grid

from selenium.webdriver.remote.file_detector import LocalFileDetector

# 支持文件上传
driver.file_detector = LocalFileDetector()

# UselessFileDetector 是程序员的黑色幽默

调试

  • 增加等待: time.sleep(3)
  • 打印页面元素HTML代码: print(element.get_attribute(‘innerHTML’))
  • 直接测试部分节点
import urllib
from selenium import webdriver


def inline(doc):
    return "data:text/html;charset=utf-8,{}".format(urllib.parse.quote(doc))


session = webdriver.Chrome()
session.get(inline('<p>成功</p>'))
p = session.find_element_by_tag_name("p")
print(p.text)

扩展阅读