Web的UI自动化基础知识

目录

  • 1 Web自动化入门基础
    • 1.1 自动化知识以及工具
    • 1.2 主流web自动化测试工具
    • 1.3 入门案例
  • 2 使用工具的API
    • 2.1 元素定位
      • 2.1.1 id选择器
      • 2.1.2 name
      • 2.1.3 class_name选择器
      • 2.1.4 tag_name选择器
      • 2.1.5 link_text选择器
      • 2.1.6 partial_link_text选择器
      • 2.1.7 xpath选择器
      • 2.1.8 CSS选择器
      • 2.1.9 Xpath和CSS区别
      • 2.1.10 元素定位分类
      • 2.1.11 元素定位的另一种写法
    • 2.2 元素操作
    • 2.3 浏览器操作
    • 2.4 获取元素信息
    • 2.5 鼠标操作
      • 2.5.1 常用方法
      • 2.5.2 执行的方法
      • 2.5.3 鼠标右击
      • 2.5.4 鼠标双击
      • 2.5.5 鼠标悬停
      • 2.5.6 鼠标拖动
    • 2.6 键盘操作
      • 2.6.1 常用操作
      • 2.6.2 键盘操作
      • 2.6.3 元素等待
      • 2.6.4 隐式等待
      • 2.6.5 显式等待
      • 2.6.6 隐式和显式区别
      • 2.6.7 下拉框
      • 2.6.8 弹出框分类
    • 2.7 滚动条实现方法
    • 2.8 frame切换
      • 2.8.1 多窗口切换
      • 2.8.2 窗口截图
    • 2.9 验证码处理
    • 2.10 cookie
  • 3 Pytest框架
    • 3.1 总体介绍
    • 3.2 断言方法
    • 3.3 setup和teardown
    • 3.4 配置文件
    • 3.5 测试报告插件
    • 3. 6 数据参数化
      • 3.6.1 单一参数
      • 3.6.2 多个参数
      • 3.6.3 推荐用法
  • 4 PO模式
    • 4.1 递进学习路线
    • 4.2 无模式
      • 4.2.1 案例说明
      • 4.2.2 选择测试用例
    • 4.3 V1版本
    • 4.4 V2版本
      • 4.4.1 方法封装
    • 4.5 V3版本
    • 4.6 PO模式
      • 4.6.1 概念
      • 4.6.2 PO模式分层
      • 4.6.3 PO模式优点
    • 4.7 V4版本
    • 4.8 v5版本
    • 4.9 v6版本
      • 4.9.1 示例代码
  • 5 数据驱动
    • 5.1 JSON基本介绍
    • 5.2 字典与JSON转换
    • 5.3 JSON文件读写
  • 6 项目实战
    • 6.1 项目结构
    • 6.2 base包
    • 6.3 data包
    • 6.4 page包
    • 6.5 scripts包
    • 6.6 utils包
  • 7 日志收集
    • 7.1 日志收集
    • 7.2 日志高级用法
    • 7.3 四大组件
      • 7.3.1 Logger类
      • 7.3.2 Handler类
      • 7.3.3 Formatter类
  • 8 面试题

1 Web自动化入门基础

1.1 自动化知识以及工具

自动化概念 :由机器设备代替人工自动完成指定目标的过程

优点:

  1. 减少人工劳动力
  2. 提高工作效率
  3. 产品规格统一标准
  4. 规模化
  5. 安全

自动化测试概念 :由程序代替人工去执行测试的过程

应用场景

  1. 解决回归测试
    • 已实现的功能需要回归
    • 已解决的bug需要回归
  2. 解决压力测试:例如使用Jmeter做接口自动化
  3. 解决兼容性测试:在不同浏览器上做兼容性测试
  4. 解决操作重复性问题

1.2 主流web自动化测试工具

  1. QTP :收费且支持web/桌面自动化测试
  2. selenium:开源web自动化测试工具(功能测试)【跨平台、支持多种浏览器、支持多种语言、稳定】
  3. robot framework :基于python的可扩展的关键字驱动的自动化测试框架

1.3 入门案例

# 导包
import timefrom selenium import webdriver# 创建浏览器驱动
driver = webdriver.Chrome()
# 打开百度首页
driver.get("http://www.taobao.com")
# 暂停3秒
time.sleep(10)
# 关闭浏览器
driver.quit()

在这里插入图片描述

2 使用工具的API

2.1 元素定位

八种定位方式

2.1.1 id选择器

案例:

打开https://parabank.parasoft.com/parabank/admin.htm网站首页,完成以下操作

  • 最大化页面

  • 使用ID定位,输入用户名:admin

  • 使用ID定位,输入密码:12345

  • 等待5s,关闭浏览器

import timefrom selenium import webdriver# 获取驱动对象
driver = webdriver.Chrome()
# 获取地址
driver.get("https://parabank.parasoft.com/parabank/admin.htm")
# 页面最大化
driver.maximize_window()
# 根据id查找元素
driver.find_element_by_id('username').send_key("admin")
driver.find_element_by_id('password').send_key("123456")
# 等待五秒
time.sleep(5)
# 关闭浏览器
driver.quit()

2.1.2 name

案例:

打开https://parabank.parasoft.com/parabank/admin.htm 网站首页,完成以下操作

  • 最大化页面

  • 使用name定位,输入用户名:admin

  • 使用name定位,输入密码:12345

  • 等待5s,关闭浏览器

import timefrom selenium import webdriver# 获取驱动对象
driver = webdriver.Chrome()
# 获取地址
driver.get("https://parabank.parasoft.com/parabank/admin.htm")
# 页面最大化
driver.maximize_window()
# 根据id查找元素
driver.find_element_by_name('username').send_key("admin")
driver.find_element_by_name('password').send_key("123456")
# 等待五秒
time.sleep(5)
# 关闭浏览器
driver.quit()

2.1.3 class_name选择器

案例:

打开https://parabank.parasoft.com/parabank/admin.htm网站首页,完成以下操作

  • 最大化页面

  • 使用class_name定位,输入用户名:admin

  • 使用class_name定位,输入密码:12345

  • 等待5s,关闭浏览器

import timefrom selenium import webdriver# 获取驱动对象
driver = webdriver.Chrome()
# 获取地址
driver.get("https://parabank.parasoft.com/parabank/admin.htm")
# 页面最大化
driver.maximize_window()
# 根据id查找元素
driver.find_element_by_class_name('username').send_key("admin")
driver.find_element_by_class_name('password').send_key("123456")
# 等待五秒
time.sleep(5)
# 关闭浏览器
driver.quit()

2.1.4 tag_name选择器

# 1.导包
import time
from selenium import webdriver# 2.创建浏览器驱动对象
driver = webdriver.Chrome()# 3.业务操作
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
driver.find_element_by_tag_name("input").send_keys("xxxxxx")# 4.暂停5秒
time.sleep(5)# 5.关闭驱动对象
driver.quit()

2.1.5 link_text选择器

# 1.导包
import time
from selenium import webdriver# 2.创建浏览器驱动对象
driver = webdriver.Chrome()# 3.业务操作
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
driver.find_element_by_link_text("访问 新浪 网站").click()# 4.暂停5秒
time.sleep(5)# 5.关闭驱动对象
driver.quit()

2.1.6 partial_link_text选择器

# 1.导包
import time
from selenium import webdriver# 2.创建浏览器驱动对象
driver = webdriver.Chrome()# 3.业务操作
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# driver.find_element_by_partial_link_text("访问 新浪 网站").click()    # 通过全部文本定位超链接
driver.find_element_by_partial_link_text("访问").click()    # 通过局部文本定位超链接# 4.暂停5秒
time.sleep(5)# 5.关闭驱动对象
driver.quit()

定位一组元素

# 1.导包
import time
from selenium import webdriver# 2.创建浏览器驱动对象
driver = webdriver.Chrome()# 3.业务操作
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
elements = driver.find_elements_by_tag_name("input")
elements[1].send_keys("123456")# 4.暂停5秒
time.sleep(5)# 5.关闭驱动对象
driver.quit()

2.1.7 xpath选择器

四种定位方式

  1. 路径
  2. 元素属性
  3. 属性与逻辑结合
  4. 层级与属性结合

方法

element = driver。find_element_by_xpath(xpath)

路径

  • 绝对路径:
    1. 从外层元素到指定元素之间所有经过元素层级的路径
    2. 绝对路径以/html根节点开始,使用/来分割元素层级,如:/html/body/div/fieldset/p[1]/input
    3. 绝对路径对页面要求严格,不建议使用
  • 相对路径
    1. 匹配任意层级的元素,不限制元素的位置
    2. 相对路径//开始
    3. 格式://input 或者 //*
import time
from selenium import webdriverdriver = webdriver.Chrome()driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 定位用户名输入框, 输入 admin
driver.find_element_by_xpath("/html/body/div/fieldset/form/p[1]/input").send_keys("admin")
# 暂停3s
time.sleep(3)
# 定位密码输入框, 输入 123
driver.find_element_by_xpath("//*[@id='passwordA']").send_keys("123")time.sleep(5)
driver.close()

使用谷歌浏览器获取 XPath 表达式的过程:

  1. 元素上右键 -> 检查

  2. 在F12对应的文档中的对应元素上 右键 -> Copy -> Copy XPath 或者 Copy full XPath

使用函数

不使用函数时:
//*[@id='xxx']使用函数后
//*[text()='xxx']   文本内容是 xxx 的元素
//*[contains(@attribute, 'xxx')] 属性中含有 xxx 值的元素
//*[starts-with(@attribute, 'xxx')] 属性以xxx开头的元素

案例

import time
from selenium import webdriverdriver = webdriver.Chrome()driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 利用元素属性通过XPath 定位用户名输入框, 并输入 admin
# driver.find_element_by_xpath("//*[@name='userA']").send_keys("admin")
# driver.find_element_by_xpath("//*[@id='userA']").send_keys("admin")
# driver.find_element_by_xpath("//*[@placeholder='请输入用户名']").send_keys("admin")
driver.find_element_by_xpath("//*[@type='text']").send_keys("admin")time.sleep(5)
driver.close()

2.1.8 CSS选择器

常用的定位方式

  • id选择器
  • class选择器
  • 元素选择器
  • 属性选择器
  • 层级选择器

方法

element = driver.find_element_by_css_selector(css表达式)

id

import time
from selenium import webdriverdriver = webdriver.Chrome()# 需求: 打开A页面, 使用CSS定位方式中的id选择器, 定位用户名输入框, 并输入 admin
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
driver.find_element_by_css_selector("#userA").send_keys("admin")time.sleep(5)
driver.close()

class

import time
from selenium import webdriverdriver = webdriver.Chrome()# 需求: 打开A页面, 使用CSS定位方式中的class选择器, 定位电话号码输入框, 并输入 13100000000
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
driver.find_element_by_css_selector(".telA").send_keys("13100000000")time.sleep(5)
driver.close()

元素选择器

import time
from selenium import webdriverdriver = webdriver.Chrome()# 需求: 打开A页面, 使用CSS定位方式中的元素选择器, 定位注册按钮, 并点击
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
driver.find_element_by_css_selector("button").click()time.sleep(5)
driver.close()

属性选择器

import time
from selenium import webdriverdriver = webdriver.Chrome()# 需求: 打开A页面, 使用CSS定位方式中的属性选择器, 定位密码输入框, 并输入 123456
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
driver.find_element_by_css_selector("[type='password']").send_keys("123456")time.sleep(5)
driver.close()

层级选择器

import time
from selenium import webdriverdriver = webdriver.Chrome()# 需求: 打开A页面, 使用CSS定位方式中的层级选择器, 定位用户名输入框, 并输入 admin
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# driver.find_element_by_css_selector("p[id='pa']>input").send_keys("admin")
driver.find_element_by_css_selector("div[class='zc'] input").send_keys("admin")time.sleep(5)
driver.close()

CSS扩展

  1. input[type^=‘p’] type属性以p字母开头的元素
  2. input[type$=‘d’] type属性以d字母结束的元素
  3. input[type*=‘w’] type属性包含w字母的元素
import time
from selenium import webdriverdriver = webdriver.Chrome()# 需求: 打开A页面, 使用CSS定位扩展的方式, 定位用户名输入框, 并输入 admin
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# driver.find_element_by_css_selector("input[type^='t']").send_keys("admin")
# driver.find_element_by_css_selector("input[name^='u']").send_keys("admin")
# driver.find_element_by_css_selector("input[type$='t']").send_keys("admin")
driver.find_element_by_css_selector("input[type*='ex']").send_keys("admin")time.sleep(5)
driver.close()

2.1.9 Xpath和CSS区别

XPath和CSS对比通过标签名定位XPath//inputCSSinput通过id属性定位XPath//*[@id='userA']CSS#userA通过class属性定位XPath//*[@class='telA']CSS.telA通过其他属性定位XPath//*[starts-with(@type,'x')]以x字母开头的type值的元素//*[contains(@type, 'x')]包含x字母的type值的元素//*[text()='x']文本内容为 x 的元素CSS[type^='x']以x字母开头的type值的元素[type*='x']包含x字母的type值的元素[type$='x']以x字母结尾的type值的元素

2.1.10 元素定位分类

  1. id, name, class_name: 元素属性定位
  2. tag_name: 元素标签名定位
  3. link_text, partial_link_text: 通过文本定位超链接
  4. XPath: 通过路径定位元素
  5. CSS: 使用CSS选择器定位

2.1.11 元素定位的另一种写法

方法

方法: driver.find_element(方式, 值)
备注:

  1. 需要2个参数, 第1个参数为定位的类型(由By提供), 第2个参数传入具体的值
  2. 如果要使用By, 需要导包

示例

import time
from selenium import webdriver
from selenium.webdriver.common.by import Bydriver = webdriver.Chrome()driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")# 八中定位方法都适用"另一种方法"
# driver.find_element(By.ID, "userA").send_keys("admin")
driver.find_element(By.XPATH, "//*[@placeholder='请输入电子邮箱']").send_keys("123456@qq.com")time.sleep(5)
driver.close()

2.2 元素操作

方法

click() 单击元素
send_keys() 模拟输入
clear() 清除文本

案例

需求:打开注册A页面,完成以下操作

  1. 通过脚本执行输入用户名:admin;密码:123456;电话号码:18611111111;电子邮件:123@qq.com

  2. 间隔3秒,修改电话号码为:18600000000

  3. 间隔3秒,点击‘注册’按钮

  4. 间隔3秒,关闭浏览器

    注意:元素定位方法不限

代码

import time
from selenium import webdriver
from selenium.webdriver.common.by import Bydriver = webdriver.Chrome()# 打开注册A页面,完成以下操作
# 1.通过脚本执行输入用户名:admin;密码:123456;电话号码:18611111111;电子邮件:123@qq.com
# 2.间隔3秒,修改电话号码为:18600000000
# 3.间隔3秒,点击‘注册’按钮
# 4.间隔3秒,关闭浏览器
# ps: 元素定位方法不限
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 1
driver.find_element_by_id("userA").send_keys("admin")
driver.find_element_by_id("passwordA").send_keys("123456")
driver.find_element_by_id("telA").send_keys("18611111111")
driver.find_element_by_name("emailA").send_keys("123@qq.com")
# 2
time.sleep(3)
driver.find_element_by_id("telA").clear()
driver.find_element_by_id("telA").send_keys("18600000000")
# 3
time.sleep(3)
driver.find_element_by_css_selector("body > div > fieldset > form > p:nth-child(5) > button").click()
# 4
time.sleep(3)
driver.close()

2.3 浏览器操作

import time
from selenium import webdriverdriver = webdriver.Chrome()driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")## maximize_window() 浏览器窗口最大化
driver.maximize_window()## set_window_size() 设置窗口大小(单位:像素点)   set_window_position()  设置窗口的位置
driver.set_window_size(300, 300)
driver.set_window_position(300, 300)## back() 后退 forward() 前进 refresh() 刷新
driver.back()
driver.forward()
time.sleep(3)
driver.refresh()## title 获取页面标题     current_url  获取当前页面url
print("页面标题:", driver.title)
print("当前页面地址:", driver.current_url)## driver.close()   关闭当前浏览器窗口  ==> 执行结果, 留下了新浪网站, 关闭了注册A页面
time.sleep(3)
driver.find_element_by_link_text("访问 新浪 网站").click()
time.sleep(3)
driver.close()### 序号 30~48 的脚本应该使用 driver.quit() 关闭浏览器驱动 而不是 driver.close()
## driver.quit()    关闭浏览器驱动对象(关闭浏览器)    ==> 执行结果, 关闭所有窗口, 关闭浏览器驱动
time.sleep(3)
driver.find_element_by_link_text("访问 新浪 网站").click()
time.sleep(3)
driver.quit()

2.4 获取元素信息

应用场景

用于校验, 判断定位的元素是否准确

常用方法

size 返回元素大小
text 获取元素文本
get_attribute("xxx") 获取属性值, 参数是元素的属性名
is_displayed() 判断元素是否可见
is_enabled() 判断元素是否可用
is_selected() 判断元素是否选中, 用来检查复选框或单选按钮

案例

import time
from selenium import webdriverdriver = webdriver.Chrome()driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")# 需求: 打开A页面, 完成以下操作:
# 1.获取用户名输入框的大小
print(driver.find_element_by_id("userA").size)
# 2.获取页面上第一个超链接的文本内容
print(driver.find_element_by_tag_name("a").text)
# 3.获取页面上第一个超链接的地址
print(driver.find_element_by_tag_name("a").get_attribute("href"))
# 4.判断页面中的span标签是否可见
print(driver.find_element_by_tag_name("span").is_displayed())
# 5.判断页面中的取消按钮是否可用
print(driver.find_element_by_id("cancelA").is_enabled())
# 6.判断页面中的'旅游'对应的复选框是否为选中状态
print(driver.find_element_by_id("lyA").is_selected())time.sleep(3)
driver.quit()

2.5 鼠标操作

什么是鼠标操作

单击, 右击, 双击, 悬停, 拖拽等

为什么要用到鼠标操作

现在web产品中存在丰富的鼠标交互方式, 作为一个web自动化测试框架, 需要应对这些鼠标操作的场景

2.5.1 常用方法

说明: 在Selenium中将鼠标操作的方法封装在 ActionChains 类中

实例化对象: action = ActionChains(driver)

方法:

  1. context_click(element) 右击
  2. double_click(element) 双击
  3. move_to_element(element) 悬停
  4. drag_and_drop(source, target) 拖拽
  5. perform() 执行

2.5.2 执行的方法

说明: 在 ActionChains 类中所有提供的鼠标事件方法, 在调用的时候, 所有行为都存储在 ActionChains 对象中, 而 perform() 方法就是真正去执行所有的鼠标事件

强调: 必须调用 perform() 方法才能执行鼠标事件

2.5.3 鼠标右击

说明: 对于点击鼠标右键, 如果弹出的是浏览器的默认菜单, Selenium并没有提供操作菜单的方法
如果是自定义的右键菜单, 则可以通过元素定位来操作菜单中的选项

需求: 打开A页面, 在用户名文本框上点击鼠标右键

import timefrom selenium import webdriver
from selenium.webdriver import ActionChainsdriver = webdriver.Chrome()# 需求: 打开A页面, 在用户名文本框上点击鼠标右键
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")# 定位用户名输入框
element = driver.find_element_by_id("userA")
# 执行右键点击操作
action = ActionChains(driver)
action.context_click(element).perform()time.sleep(3)
driver.quit()

2.5.4 鼠标双击

说明: 模拟鼠标双击左键的操作

需求: 打开A页面, 输入用户名 admin, 暂停3s, 双击鼠标左键(选中admin)

import time
from selenium import webdriver
from selenium.webdriver import ActionChainsdriver = webdriver.Chrome()# 需求: 打开A页面, 输入用户名 admin, 暂停3s, 双击鼠标左键(选中admin)
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
element = driver.find_element_by_id("userA")
element.send_keys("admin")
time.sleep(3)
action = ActionChains(driver)
action.double_click(element).perform()time.sleep(3)
driver.quit()

2.5.5 鼠标悬停

说明: 模拟鼠标悬停在指定元素上

需求: 打开A页面, 模拟鼠标悬停在 注册 按钮上

import time
from selenium import webdriver
from selenium.webdriver import ActionChainsdriver = webdriver.Chrome()# 需求: 打开A页面, 模拟鼠标悬停在 注册 按钮上
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
element = driver.find_element_by_tag_name("button")
action = ActionChains(driver)
action.move_to_element(element).perform()time.sleep(3)
driver.quit()

2.5.6 鼠标拖动

说明: 模拟鼠标拖动动作, 选定拖动源元素释放到目标元素

  1. 源元素 source = driver.find_element_by_xxx("xxx")
  2. 目标元素 target = driver.find_element_by_xxx("xxx")
  3. 调用方法 action.drag_and_drop(source, target).perform()

需求: 打开 drag.html 页面, 把红色方框拖动到蓝色方框上

import time
from selenium import webdriver
from selenium.webdriver import ActionChainsdriver = webdriver.Chrome()# 需求: 打开 drag.html 页面, 把红色方框拖动到蓝色方框上
driver.get("file:///C:/Users/57769/Desktop/pagetest/drag.html")
red = driver.find_element_by_id("div1")
blue = driver.find_element_by_id("div2")
ActionChains(driver).drag_and_drop(red, blue).perform()time.sleep(3)
driver.quit()

2.6 键盘操作

说明:

  1. 模拟键盘上的一些按键或者组合键的输入, 如: 复制/粘贴
  2. Selenium中把键盘的按键都封装在 Keys 类中

2.6.1 常用操作

导包

  1. send_keys(Keys.BACK_SPACE) 删除键(Backspace)
  2. send_keys(Keys.SPACE) 空格键(Space)
  3. send_keys(Keys.TAB) 制表键(Tab)
  4. send_keys(Keys.ESCAPE) 回退键(ESC)
  5. send_keys(Keys.ENTER) 回车键(Enter)
  6. send_keys(Keys.CONTROL, 'a') 全选(Ctrl + A)
  7. send_keys(Keys.CONTROL, 'c') 复制(Ctrl + C)

提示: 以上方法很多, 不会逐一讲解, 因为调用方法都一样

2.6.2 键盘操作

需求

打开 A 页面, 完成以下操作

  1. 输入用户名 admin1, 暂停2s, 删除1
  2. 全选用户名 admin 暂停2s
  3. 复制用户名 admin 暂停2s
  4. 粘贴到电话输入框

代码

import time
from selenium import webdriver
from selenium.webdriver.common.keys import Keysdriver = webdriver.Chrome()driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 1. 输入用户名 admin1, 暂停2s, 删除1
element = driver.find_element_by_id("userA")
element.send_keys("admin1")
time.sleep(2)
element.send_keys(Keys.BACK_SPACE)
# 2. 全选用户名 admin 暂停2s
element.send_keys(Keys.CONTROL, "a")
time.sleep(2)
# 3. 复制用户名 admin 暂停2s
element.send_keys(Keys.CONTROL, "c")
time.sleep(2)
# 4. 粘贴到电话输入框
driver.find_element_by_id("telA").send_keys(Keys.CONTROL, "v")time.sleep(5)
driver.quit()

2.6.3 元素等待

概念

定位页面元素, 如果未找到, 在指定时间内一直等待的过程

分类

  • 隐式等待
  • 显式等待

由于一些原因, 我们想找的元素并没有立刻出来, 此时直接定位会报错, 场景如下:

  1. 网络速度慢
  2. 服务器计算慢
  3. 硬件配置差

思考: 是否定位每个元素时, 都需要元素等待?

2.6.4 隐式等待

方法

隐式等待为全局设置 (只需要设置1次,会作用于所有元素)

参数:

timeout: 超时的时长, 单位: 秒

driver.implicitly_wait(timeout)

案例

import time
from selenium import webdriverdriver = webdriver.Chrome()driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")# 需求: 打开A页面, 使用隐式等待定位 "延时加载的输入框", 并输入 admin
driver.implicitly_wait(10)
driver.find_element_by_css_selector("input[placeholder='延时加载的输入框']").send_keys("admin")time.sleep(3)
driver.quit()# 不使用元素等待时, 如果找不到元素会报 NoSuchElementException 异常
# 使用隐式等待时, 如果找不到元素会报 NoSuchElementException 异常

注意点

单个元素定位超时会报 NoSuchElementException

2.6.5 显式等待

说明: 在Selenium中把显式等待的相关方法封装在 WebDriverWait 类中

方法 :

显式等待, 为定位不同的元素的超时时间设置不同的值

  1. 导包

  2. WebDriverWait(driver, timeout, poll_frequency=0.5)

    1. driver: 浏览器驱动对象
    2. timeout: 超时时长, 单位: 秒
    3. poll_frequency: 检测的间隔时间, 默认为0.5s
  3. 调用 until(method)

    1. method: 函数名称, 该函数用来实现元素定位
    2. 一般使用匿名来实现: lambda x: x.find_element_by_xxx("xxx")

    如:element = WebDriverWait(driver,10,1).until(lambda x: x.find_element_by_xxx("xxx"))

案例

import time
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWaitdriver = webdriver.Chrome()driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")# 需求: 打开A页面, 使用显式等待定位 "延时加载的输入框", 并输入 admin
wait = WebDriverWait(driver, 10, 1)
element = wait.until(lambda x: x.find_element_by_css_selector("input[placeholder='延时加载的输入框']"))
element.send_keys("admin")time.sleep(3)
driver.quit()# 单个元素定位超时会报错 TimeoutException

注意点

单个元素定位超时会报错 TimeoutException

2.6.6 隐式和显式区别

  1. 作用域: 隐式等待为全局有效, 显式等待为单个元素有效
  2. 使用方法: 隐式等待直接通过驱动对象调用, 而显式等待方法封装在 WebDriverWait 类中
  3. 达到最大超时时长后抛出异常不同: 隐式等待为 NoSuchElementException, 显式等待为 TimeoutException

2.6.7 下拉框

案例

import time
from selenium import webdriverdriver = webdriver.Chrome()# 需求: 打开A页面,完成以下下拉框操作
# 1. 暂停2s, 选择广州
# 2. 暂停2s, 选择上海
# 3. 暂停2s, 选择北京
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 1
time.sleep(2)
driver.find_element_by_xpath("//*[@id='selectA']/option[3]").click()
# 2
time.sleep(2)
driver.find_element_by_xpath("//*[@id='selectA']/option[2]").click()
# 3
time.sleep(2)
driver.find_element_by_xpath("//*[@id='selectA']/option[1]").click()time.sleep(3)
driver.quit()

案例

说明: Select类是Selenium为操作select标签封装的

实例化对象:
select = Select(element)
element: <select>标签对应的元素, 通过元素定位方式获取
例如: driver.find_element_by_id("selectA")

操作方法:

  1. select_by_index(index) 根据option索引来定位, 从0开始
  2. select_by_value(value) 根据option属性 value值来定位
  3. select_by_visible_text(text) 根据option显示文本内容来定位

步骤分析

  1. 导包
  2. 实例化Select类 select = Select(driver.find_element_by_id("selectA"))
  3. 调用方法

案例

import time
from selenium import webdriver
from selenium.webdriver.support.select import Selectdriver = webdriver.Chrome()# 需求: 打开A页面,完成以下下拉框操作
# 1. 暂停2s, 选择广州
# 2. 暂停2s, 选择上海
# 3. 暂停2s, 选择北京
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
select = Select(driver.find_element_by_id("selectA"))
# 1
time.sleep(2)
select.select_by_index(2)
# 2
time.sleep(2)
select.select_by_value("sh")
# 3
time.sleep(2)
select.select_by_visible_text("北京")time.sleep(3)
driver.quit()

2.6.8 弹出框分类

  1. alert 警告框
  2. confirm 确认框
  3. prompt 提示框

弹出框的错误示范

错误代码

import time
from selenium import webdriverdriver = webdriver.Chrome()# 需求: 打开A页面,完成以下弹出框操作
# 1.点击 alert 按钮
# 2.暂停2s, 输入用户名 admin
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 1
driver.find_element_by_id("alerta").click()
# 2
time.sleep(2)
driver.find_element_by_id("userA").send_keys("admin")time.sleep(3)
driver.quit()# 思考
# 1.什么问题导致的?
# driver的焦点在弹出框页面, 并不在A页面, 无法为你输入admin(找不到用户名输入框)
# 2.如何处理弹出框?

弹出框方法

说明: Selenium中对弹出框的处理, 有专用的方法, 且处理的方法都一样(alert/confirm/prompt)

1.获取弹出框对象
alert = driver.switch_to.alert
2.调用
alert.text 返回alert/confirm/prompt文字信息
alert.accept() 接受对话框选项(确认)
alert.dismiss() 取消对话框选项(取消)

案例

正确代码

import time
from selenium import webdriverdriver = webdriver.Chrome()# 需求: 打开A页面,完成以下弹出框操作
# 1.点击 alert 按钮
# 2.暂停2s, 输入用户名 admin
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 1
driver.find_element_by_id("alerta").click()
time.sleep(2)
alert = driver.switch_to.alert
print(alert.text)
time.sleep(2)
alert.accept()
# 2
time.sleep(2)
driver.find_element_by_id("userA").send_keys("admin")time.sleep(3)
driver.quit()

2.7 滚动条实现方法

方法

说明: Selenium中没有提供滚动条的操作方法, 但是它提供了执行 JS 的方法, 所有我们可以通过 JS脚本来操作滚动条

  1. 设置 JS 脚本控制滚动条
    js = "window.scrollTO(0,1000)"
    (0:左边距, 1000:上边距 单位:像素(px))
  2. Selenium 调用执行 JS 脚本的方法
    driver.execute_script(js)

案例

import time
from selenium import webdriverdriver = webdriver.Chrome()# 需求: 打开A页面
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# js1 滚动到最底部
js1 = "window.scrollTo(0, 10000)"
# js2 滚动到最顶部
js2 = "window.scrollTo(0, 0)"
# 执行第一个脚本
time.sleep(2)
driver.execute_script(js1)
# 执行第二个脚本
time.sleep(2)
driver.execute_script(js2)time.sleep(3)
driver.quit()

2.8 frame切换

概念

frame : html页面中的一种框架, 主要作用是在当前页面指定区域显示另一个页面元素

形式一:
<frameset cols="25%,75%">
<frame src="a.html">
<frame src="b.html">
</frameset>
形式二:
<iframe name="iframe_a" src="demo.html" width="200" height="200"></iframe>

方法

说明: 在Selenium中封装了如何切换frame框架的方法

步骤:
1.driver.switch_to.frame(frame_reference) 切换到指定frame
frame_reference: 可以传frame框架的id,name,定位的frame元素
2.driver.switch_to.default_content() 恢复默认页面
必须回到默认页面才能进一步操作

解决方案

  1. 在主页面输入用户名 admin
  2. 切换到A页面, 再输入用户名 adminA
  3. 恢复默认页面
  4. 切换到B页面, 再输入用户名 adminB

正确代码

import time
from selenium import webdriverdriver = webdriver.Chrome()# 需求: 打开"注册实例"页面
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8C%E5%AE%9E%E4%BE%8B.html")
# 1.填写主页面的用户名 admin
time.sleep(2)
driver.find_element_by_id("userA").send_keys("admin")
# 2.填写注册页面A中的用户名 adminA
time.sleep(2)
# driver.switch_to.frame("idframe1")  # 从主页面, 切换到了A页面, 通过 id
# driver.switch_to.frame("myframe1")  # 从主页面, 切换到了A页面, 通过 name
driver.switch_to.frame(driver.find_element_by_id("idframe1"))  # 从主页面, 切换到了A页面, 通过 定位到的元素
driver.find_element_by_id("userA").send_keys("adminA")
# 3.回到主页面
time.sleep(1)
driver.switch_to.default_content()
# 4.填写注册页面B中的用户名 adminB
time.sleep(1)
driver.switch_to.frame("idframe2")  # 从主页面, 切换到B页面
driver.find_element_by_id("userA").send_keys("adminB")time.sleep(3)
driver.quit()

2.8.1 多窗口切换

概念

什么是窗口? 窗口类似于浏览器中的标签页, 每个窗口就对应了一个标签页

为什么要切换窗口? 在html页面中, 当点击按钮或超链接时, 有的会在新窗口打开页面

如果点击按钮或超链接在当前窗口打开新页面, 就不需要切换窗口

需求

打开A页面

  1. 在新窗口打开新浪页面
  2. 在新浪的搜索框输入"新浪搜索"
  3. 在A页面输入用户名 admin

方法

说明: 在Selenium中封装了获取当前窗口句柄,获取所有窗口句柄和切换到指定句柄窗口的方法
句柄: 英文handle, 窗口的唯一识别码
方法:

1. `driver.current_window_handle`		获取当前窗口句柄
2. `driver.window_handles`				获取所有窗口句柄
3. `driver.switch_to.window(handle)`	切换到指定句柄的窗口

对于需求的解决方案:

  1. 打开A页面, 获取当前窗口句柄(拿到的是A页面的句柄)
  2. 在A页面点击"访问 新浪 网站" 这个超链接, 获取所有窗口句柄
  3. 根据句柄, 切换到新浪窗口, 对输入框输入 “新浪搜索”
  4. 切换回原本窗口(A页面), 输入用户名 admin

注意: 新浪页面需要访问网络, 可能加载慢, 可能需要用到元素等待

代码

import time
from selenium import webdriverdriver = webdriver.Chrome()
# 隐式等待10秒, 以防新浪窗口加载慢, 定位不到输入框
driver.implicitly_wait(10)# 1. 打开A页面, 获取当前窗口句柄(拿到的是A页面的句柄)
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
print("当前A页面窗口句柄:", driver.current_window_handle)
# 2. 在A页面点击"访问 新浪 网站" 这个超链接, 获取所有窗口句柄
driver.find_element_by_id("fw").click()
handles = driver.window_handles
print("所有窗口句柄:", handles)
# 3. 根据句柄, 切换到新浪窗口, 对输入框输入 "新浪搜索"
driver.switch_to.window(handles[1])
time.sleep(1)
driver.find_element_by_class_name("inp-txt").clear()
time.sleep(1)
driver.find_element_by_class_name("inp-txt").send_keys("新浪搜索")
time.sleep(2)
# 4. 切换回原本窗口(A页面), 输入用户名 admin
driver.switch_to.window(handles[0])
driver.find_element_by_id("userA").send_keys("admin")time.sleep(3)
driver.quit()

2.8.2 窗口截图

概念

什么是窗口截图?

把当前操作的页面, 截图保存到指定的位置

为什么要窗口截图?

有时候打印的错误信息不十分准确, 需要窗口截图辅助定位错误

方法

说明: 在Selenium中提供了截图方法, 我们只需要调用即可

方法:
driver.get_screenshot_as_file(imgpath)
imgpath: 图片保存路径 + 图片名

案例

import time
from selenium import webdriverdriver = webdriver.Chrome()# 需求: 打开 A 页面, 完成以下操作
# 1.输入用户名 admin
# 2.截图保存
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")# 1
driver.find_element_by_id("userA").send_keys("admin")
# 2
time.sleep(1)
# 每次都是用固定文件名, 会股改上一次生成的图片文件
# driver.get_screenshot_as_file("./png/123.png")  # 需要提前创建 png 目录# 使用时间去格式化文件名, 可以使每次截图保存的文件名都不同, 不会覆盖之前保存的文件, 更有效
imgpath = "./png/test_{}.png".format(time.strftime("%Y%m%d%H%M%S"))
driver.get_screenshot_as_file(imgpath)time.sleep(3)
driver.quit()

2.9 验证码处理

概念

什么是验证码?

一种随机生成的信息 (数字, 字母, 汉字, 图片, 算术题…) 等为了防止恶意的请求行为, 增加应用的安全性

为什么要学习验证码?

在web应用中, 大部分系统在用户登录注册的时候都需要输入验证码, 而我们自动化脚本也要面临处理验证码的问题

常用方法

说明: Selenium中并没有对验证码处理的方法, 在这里我们介绍几种常用的处理方式

方法:
1.去掉验证码
(测试环境下采用)
2.设置万能验证码
(生产和测试环境下采用)
3.验证码识别技术
(通过 python-tesseract 来识别图片类型的验证码: 识别率很难达到100%)
4.记录 cookie
(通过记录 cookie 进行跳过登录)

注意

1 和 2, 都是开发人员来完成
3 验证码识别技术成功率不高, 不太合适
4 记录cookie 比较实用, 推荐

2.10 cookie

概念

  1. cookie是由web服务器生成的, 并且保存在用户浏览器上的小文本文件, 它可以包含用户信息
  2. cookie数据格式: 键值对 (python中的字典)
  3. cookie产生: 客户端请求服务器, 如果服务器需要记录该用户状态, 就向客户端浏览器颁发一个cookie数据
  4. cookie使用: 当浏览器再次请求该网站时, 浏览器把请求的数据和cookie数据一同提交给服务器, 服务器检查该cookie, 以此来辨认用户

应用场景

  1. 实现会话跟踪, 记录用户登录状态
  2. 实现记住密码和自动登录的功能
  3. 用户未登录状态下, 记录购物车中的商品

方法

说明: Selenium中对cookie操作提供相应的方法

方法:
1.driver.get_cookies() 获取本网站所有本地cookies
2.driver.get_cookie(name) 获取指定cookie
name: 为cookie中键值对数据的 键名
3.driver.add_cookie(cookie_dict) 添加cookie
cookie_dict: 一个字典对象, 必选的内容包括: “name” 和 “value”

案例需求

使用cookie 实现跳过百度登录
1.手动登录百度, 获取cookie
2.请求百度, 并且带上cookie

步骤分析

BDUSS是登录百度后的唯一身份凭证, 拿到BDUSS就等于拿到了账号的控制权,通行贴吧,知道,文库…主要产品

  1. 登录百度, 抓取BDUSS
  2. 添加 BDUSS 的键值对
  3. 调用刷新的方法

代码

import time
from selenium import webdriverdriver = webdriver.Chrome()
driver.maximize_window()# 需求: 使用cookie 实现跳过百度登录
# 1.手动登录百度, 获取cookie
# 2.请求百度, 并且带上cookie# 没有cookie的时候
driver.get("http://www.baidu.com")
# 添加cookie操作
driver.add_cookie({"name": "BDUSS", "value": "VZMUEl0WFJQYkxNSXk0c0VMUk5ZNGYteWVYNG01aVJtZXFCV056alk5M3V3SUZlSVFBQUFBJCQAAAAAAAAAAAEAAAC2KUFmTFhKX0pheQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO4zWl7uM1peQ"})
time.sleep(3)
# 刷新, 再次请求百度首页, 验证是否带上身份信息
driver.refresh()time.sleep(3)
driver.quit()

3 Pytest框架

3.1 总体介绍

什么是断言

让程序代替人工去判断测试程序的执行结果是否符合预期的过程

为什么学习断言

自动化脚本在执行的时候一般都是无人值守的状态, 我们不知道执行结果是否符合预期, 所以我们需要让程序代替人工去检测程序的执行结果是否符合预期, 这就需要断言

3.2 断言方法

assert xx 判断 xx 为真
assert not xx 判断 xx 不为真
assert a in b 判断 b 包含 a
assert a == b 判断 a 等于 b
assert a != b 判断 a 不等于 b

代码案例

def add(x, y):return x + yclass TestPlus:# 判断 1+1 的结果等于 2def test_a(self):assert 2 == add(1, 1)# 调换表达式两个值的位置, 判断 1+1 的结果等于 2def test_b(self):assert add(1, 1) == 2# 判断 1+2 的结果不等于4def test_c(self):assert 4 != add(1, 2)# 误判: 1+2 等于 4 了def test_d(self):assert 4 == add(1, 2)

3.3 setup和teardown

应用场景

pytest 在运行自动化脚本的前后会执行两个特殊的方法, 分别是"前置"和"后置"方法
在脚本执行前会执行"前置"方法,在脚本执行后会执行"后置"方法

概念和方法

1.初始化(前置处理方法):
def setup(self)
2.销毁(后置处理方法):
def teardown(self)
3.运行于测试方法的始末, 即:运行一次测试方法就会运行一次 setup 和 teardown

案例

import timedef add(x, y):return x + yclass TestPlus:# 获取并打印开始时间, 每个测试函数执行前都打印一次def setup(self):print("start-time:", time.time())# 获取并打印结束时间, 每个测试函数执行后都打印一次def teardown(self):print("end-time:", time.time())def test_a(self):assert 2 == add(1, 1)def test_b(self):assert add(1, 1) == 2

3.4 配置文件

应用场景

使用配置文件, 可以通过配置项来选择执行哪些目录下的哪些测试模块

用法

步骤:

  1. 新建 scripts 模块, 测试脚本放到模块中
  2. 新建 pytest.ini 文件, 名称为 pytest.ini, 第一行为 [pytest], 并且补全配置项
  3. 命令行运行 pytest 即可

示例

pytest.ini

[pytest]
addopts = -s
testpaths = ./scripts
python_files = test_*.py
python_classes = Test*
python_functions = test_*

3.5 测试报告插件

应用场景

需要测试报告来体现自动化脚本测试是否通过

安装

pip install pytest-html==1.21.1

使用

在配置文件中的命令行参数中, 增加 --html=用户路径/report.html

生成报告

步骤:

  1. 命令行输入 pytest 运行脚本
  2. 在项目目录下会有一个 report文件夹, 里面有个 report.html 就是测试报告

3. 6 数据参数化

应用场景

需要测试多组值得时候, 使用数据参数化可以使代码更简洁, 可读性更好

方法

数据参数化, 装饰器需要放在要传多组值的函数上

@pytest.mark.parametrize(argnames, argvalues)

参数:

argnames: 参数名
argvalues: 参数对应值, 类型必须是可迭代类型, 一般使用 list

3.6.1 单一参数

代码

import pytestclass TestDemo:# 需求: 不使用数据参数化, 分别打印用户名 "zhangsan" 和 "lisi"def test_a(self):print("zhangsan")def test_b(self):print("lisi")# 需求: 使用数据参数化 (单一参数), 修改上面的代码@pytest.mark.parametrize("name", ["zhangsan", "lisi"])def test_c(self, name):print(name)

3.6.2 多个参数

代码

import pytestclass TestDemo:# 需求: 使用数据参数化 (多个参数), 分别打印2组账号和密码: zhangsan / 111111 和 lisi / 222222@pytest.mark.parametrize(("username", "password"), [("zhangsan", "111111"), ("lisi", "222222")])def test_c(self, username, password):print(username + "-----" + password)# 使用元组可以传多个值  ("zhangsan", "111111"),   列表行不行?

3.6.3 推荐用法

代码

import pytestclass TestDemo:# 需求: 使用数据参数化 (推荐用法), 分别打印2组账号和密码: zhangsan / 111111 和 lisi / 222222
#     @pytest.mark.parametrize(("username", "password"), [("zhangsan", "111111"), ("lisi", "222222")])
#     def test_c(self, username, password):
#         print(username + "-----" + password)@pytest.mark.parametrize("dict", [{"username": "zhangsan", "password": "111111"}, {"username": "lisi", "password": "222222"}])def test_d(self, dict):print(dict)print(dict["username"])print(dict["password"])
#("zhangsan", "111111", "13000000000", "1", "1", "30", "......")
#("lisi", "222222", "13100000000", ??????)
# 推荐的用法是用字典表示参数值
#  {"username": "zhangsan", "password": "111111"}

4 PO模式

4.1 递进学习路线

  • v1: 不使用任何设计模式和单元测试框架
  • v2: 使用 pytest 管理用例
  • v3: 使用方法封装的思想, 对代码进行优化
  • v4: 采用PO模式的分层思想对代码进行拆分, 分离page
  • v5: 对PO分层后的代码继续优化, 分离page中的元素和操作
  • v6: PO模式深入封装, 把共同操作提取封装

4.2 无模式

4.2.1 案例说明

对 TPshop 项目的登录模块进行自动化测试

登录模块包含了很多测试用例, 如: 账号不存在, 密码错误, 验证码错误, 登录成功等等

为了节省时间, 我们只选取几个有代表性的用例来演示: 账号不存在, 密码错误

4.2.2 选择测试用例

  • 账号不存在

    1. 点击首页的"登录"链接, 进入登录页面
    2. 输入一个不存在的用户名
    3. 输入密码
    4. 输入验证码
    5. 点击登录按钮
    6. 获取错误提示信息
  • 密码错误

    1. 点击首页的"登录"链接, 进入登录页面

    2. 输入用户名

    3. 输入一个错误的密码

    4. 输入验证码

    5. 点击登录按钮

    6. 获取错误提示信息

4.3 V1版本

  • 不使用任何设计模式和单元测试框架
  • 每个文件对应编写一个测试用例, 完全的面向过程的编程方式

示例代码

  1. 登录功能, 账号不存在
# 账号不存在
import time
from selenium import webdriver# 实例化浏览器驱动
driver = webdriver.Chrome()
driver.maximize_window()
driver.implicitly_wait(10)
driver.get("http://localhost/")# 1. 点击首页的"登录"链接, 进入登录页面
driver.find_element_by_class_name("red").click()
# 2. 输入一个不存在的用户名
driver.find_element_by_id("username").send_keys("18800000000")
# 3. 输入密码
driver.find_element_by_id("password").send_keys("123456")
# 4. 输入验证码
driver.find_element_by_id("verify_code").send_keys("8888")
# 5. 点击登录按钮
driver.find_element_by_name("sbtbutton").click()
# 6. 获取错误提示信息
msg = driver.find_element_by_css_selector(".layui-layer-content").text
print(msg)# 关闭浏览器驱动
time.sleep(5)
driver.quit()
  1. 登录功能, 密码错误
# 密码错误
import time
from selenium import webdriver# 实例化浏览器驱动
driver = webdriver.Chrome()
driver.maximize_window()
driver.implicitly_wait(10)
driver.get("http://localhost/")# 1. 点击首页的"登录"链接, 进入登录页面
driver.find_element_by_class_name("red").click()
# 2. 输入用户名
driver.find_element_by_id("username").send_keys("17150312012")
# 3. 输入一个错误密码
driver.find_element_by_id("password").send_keys("error")
# 4. 输入验证码
driver.find_element_by_id("verify_code").send_keys("8888")
# 5. 点击登录按钮
driver.find_element_by_name("sbtbutton").click()
# 6. 获取错误提示信息
msg = driver.find_element_by_css_selector(".layui-layer-content").text
print(msg)# 关闭浏览器驱动
time.sleep(5)
driver.quit()

存在的问题

  • 一条测试用例对应一个文件, 用例多时, 不方便维护管理
  • 代码高度冗余

4.4 V2版本

引入pytest管理测试用例, 并断言用例的执行结果

好处

  • 方便组织和管理多个测试用例
  • 提供了丰富的断言方法
  • 方便生成测试报告
  • 减少了代码冗余

示例代码

# 导包
import time
from selenium import webdriver# 定义测试类
class TestLogin:def setup(self):self.driver = webdriver.Chrome()self.driver.maximize_window()self.driver.implicitly_wait(10)self.driver.get("http://localhost/")def teardown(self):time.sleep(5)self.driver.quit()# 定义用户不存在的测试方法def test_login_account_not_exist(self):# 1. 点击首页的"登录"链接, 进入登录页面self.driver.find_element_by_class_name("red").click()# 2. 输入一个不存在的用户名self.driver.find_element_by_id("username").send_keys("18800000000")# 3. 输入密码self.driver.find_element_by_id("password").send_keys("123456")# 4. 输入验证码self.driver.find_element_by_id("verify_code").send_keys("8888")# 5. 点击登录按钮self.driver.find_element_by_name("sbtbutton").click()# 6. 获取错误提示信息msg = self.driver.find_element_by_css_selector(".layui-layer-content").textprint(msg)# 断言assert "账号不存在!" == msg# 定义密码错误的测试方法def test_login_password_error(self):# 1. 点击首页的"登录"链接, 进入登录页面self.driver.find_element_by_class_name("red").click()# 2. 输入用户名self.driver.find_element_by_id("username").send_keys("17150312012")# 3. 输入一个错误密码self.driver.find_element_by_id("password").send_keys("error")# 4. 输入验证码self.driver.find_element_by_id("verify_code").send_keys("8888")# 5. 点击登录按钮self.driver.find_element_by_name("sbtbutton").click()# 6. 获取错误提示信息msg = self.driver.find_element_by_css_selector(".layui-layer-content").textprint(msg)# 断言assert "密码错误!" == msg

存在问题

依然是代码冗余

4.4.1 方法封装

概念

是将一些有共性的或多次被使用的代码提取到一个方法中, 供其他地方调用

好处

  • 避免代码冗余
  • 容易维护
  • 隐藏代码实现的细节

目的

用最少的代码实现最多的功能

4.5 V3版本

驱动工具类

# 获取/关闭浏览器驱动的类
from selenium import webdriverclass DriverUtils:__driver = None# 获取浏览器驱动@classmethoddef get_driver(cls):if cls.__driver is None:cls.__driver = webdriver.Chrome()cls.__driver.maximize_window()cls.__driver.implicitly_wait(10)return cls.__driver# 关闭浏览器驱动@classmethoddef quit_driver(cls):if cls.__driver is not None:cls.__driver.quit()cls.__driver = None

测试类

# 导包
import time
from v3.driver_utils_121 import DriverUtils# 定义测试类
class TestLogin:def setup(self):self.driver = DriverUtils.get_driver()self.driver.get("http://localhost/")def teardown(self):time.sleep(5)DriverUtils.quit_driver()# 定义用户不存在的测试方法def test_login_account_not_exist(self):# 1. 点击首页的"登录"链接, 进入登录页面self.driver.find_element_by_class_name("red").click()# 2. 输入一个不存在的用户名self.driver.find_element_by_id("username").send_keys("18800000000")# 3. 输入密码self.driver.find_element_by_id("password").send_keys("123456")# 4. 输入验证码self.driver.find_element_by_id("verify_code").send_keys("8888")# 5. 点击登录按钮self.driver.find_element_by_name("sbtbutton").click()# 6. 获取错误提示信息msg = self.driver.find_element_by_css_selector(".layui-layer-content").textprint(msg)# 断言assert "账号不存在!" == msg# 定义密码错误的测试方法def test_login_password_error(self):# 1. 点击首页的"登录"链接, 进入登录页面self.driver.find_element_by_class_name("red").click()# 2. 输入用户名self.driver.find_element_by_id("username").send_keys("17150312012")# 3. 输入一个错误密码self.driver.find_element_by_id("password").send_keys("error")# 4. 输入验证码self.driver.find_element_by_id("verify_code").send_keys("8888")# 5. 点击登录按钮self.driver.find_element_by_name("sbtbutton").click()# 6. 获取错误提示信息msg = self.driver.find_element_by_css_selector(".layui-layer-content").textprint(msg)# 断言assert "密码错误!" == msg

注意: 如果想要引用其他类, 那么被引用的类的文件名要符合要求, 比如不能出现 []

存在的问题

代码冗余

4.6 PO模式

在做UI自动化时, 元素定位特别依赖页面, 如果页面变更, 自动化脚本就需要被修改

存在的问题

  • 如果前端工程师改了某个元素, 你就得修改所有对应的代码
  • 存在大量的冗余

如果解决?

答案就是 PO模式

4.6.1 概念

PO是Page Object的缩写, PO模式是自动化测试开发的最佳设计模式之一

核心思想:

  • 通过对页面元素的封装减少冗余代码, 同时在后期维护中, 若元素发生变化, 只需要调整页面元素封装的代码即可, 提高了测试用例的可维护性, 可读性
  • 页面和测试脚本分离

4.6.2 PO模式分层

分层机制, 让不同层去做不同类型的事情, 让代码结构清晰, 增加复用性

分层方式

  1. 两层: 对象操作层 + 业务数据层

    • 对象操作层: 封装页面信息, 包括元素以及元素的操作
    • 业务数据层: 封装多种操作组合的业务以及测试数据
  2. 三层: 对象库 + 操作层 + 业务数据层 / 对象操作层 + 业务层 + 数据层

  3. 四层: 对象库 + 操作层 + 业务层 + 数据层

4.6.3 PO模式优点

  • 引入PO模式前
    • 存在大量冗余代码
    • 业务流程不清晰
    • 后期维护成大
  • 引入PO模式后
    • 减少冗余代码
    • 业务代码和测试数据被分开, 降低耦合性
    • 维护成本低

4.7 V4版本

介绍

采用PO模式的分层思想对代码进行拆分

PO封装

对登录页面进行封装: 封装到类 LoginPage

对测试用例进行封装: 封装到类 TestLogin

代码结构

  • utils包

    • driver_utils.py
  • page包

    • login_page.py
  • scripts包

    • test_login.py
  • pytest.ini

PO封装

login_page.py

class LoginPage:def __init__(self, driver):self.driver = driver# 点击首页的"登录"链接, 进入登录页面def click_login_link(self):return self.driver.find_element_by_class_name("red").click()# 输入用户名def input_username(self, username):return self.driver.find_element_by_id("username").send_keys(username)# 输入密码def input_password(self, password):return self.driver.find_element_by_id("password").send_keys(password)# 输入验证码def input_verify_code(self, code):return self.driver.find_element_by_id("verify_code").send_keys(code)# 点击登录按钮def click_login_btn(self):return self.driver.find_element_by_name("sbtbutton").click()# 获取提示信息def get_msg(self):msg = self.driver.find_element_by_css_selector(".layui-layer-content").textreturn msg

test_login.py

class LoginPage:def __init__(self, driver):self.driver = driver# 点击首页的"登录"链接, 进入登录页面def click_login_link(self):return self.driver.find_element_by_class_name("red").click()# 输入用户名def input_username(self, username):return self.driver.find_element_by_id("username").send_keys(username)# 输入密码def input_password(self, password):return self.driver.find_element_by_id("password").send_keys(password)# 输入验证码def input_verify_code(self, code):return self.driver.find_element_by_id("verify_code").send_keys(code)# 点击登录按钮def click_login_btn(self):return self.driver.find_element_by_name("sbtbutton").click()# 获取提示信息def get_msg(self):msg = self.driver.find_element_by_css_selector(".layui-layer-content").textreturn msg

4.8 v5版本

介绍

对PO分层后的代码继续优化

优化内容

  • 分离page页面中的元素和操作
  • 优化元素定位方式

示例代码

login_page.py

from selenium.webdriver.common.by import Byclass LoginPage:# 登录链接 按钮login_link_btn = By.CLASS_NAME, "red"# 用户名 输入框username_input = By.ID, "username"# 密码 输入框password_input = By.ID, "password"# 验证码 输入框verify_code_input = By.ID, "verify_code"# 登录 按钮login_btn = By.NAME, "sbtbutton"# 提示信息msg_info = By.CSS_SELECTOR, ".layui-layer-content"def __init__(self, driver):self.driver = driverdef find_el(self, feature):return self.driver.find_element(*feature)# return self.driver.find_elment(feature[0], feature[1])# 点击首页的"登录"链接, 进入登录页面def click_login_link(self):return self.find_el(self.login_link_btn).click()# return self.driver.find_elment(self.login_link_btn[0], self.login_link_btn[1]).click()# return self.driver.find_element_by_class_name("red").click()# 输入用户名def input_username(self, username):return self.find_el(self.username_input).send_keys(username)# return self.driver.find_element_by_id("username").send_keys(username)# 输入密码def input_password(self, password):return self.find_el(self.password_input).send_keys(password)# return self.driver.find_element_by_id("password").send_keys(password)# 输入验证码def input_verify_code(self, code):return self.find_el(self.verify_code_input).send_keys(code)# return self.driver.find_element_by_id("verify_code").send_keys(code)# 点击登录按钮def click_login_btn(self):return self.find_el(self.login_btn).click()# return self.driver.find_element_by_name("sbtbutton").click()# 获取提示信息def get_msg(self):return self.find_el(self.msg_info).text# msg = self.driver.find_element_by_css_selector(".layui-layer-content").text# return msg

4.9 v6版本

介绍

把共同的方法进行封装

优化内容

  • 封装操作基类
    • 封装查找元素的方法
    • 封装基本操作方法: 点击/ 清空/ 输入等等
  • page继承操作基类

结构

  • utils包

    • driver_utils.py
  • page包

    • login_page.py
  • scripts包

    • test_login.py
  • pytest.ini

  • base包

    • base_action.py

4.9.1 示例代码

base_action.py

class BaseAction:def __init__(self, driver):self.driver = driverdef find_el(self, feature):return self.driver.find_element(*feature)def find_els(self, feature):return self.driver.find_elements(*feature)def click(self, feature):return self.find_el(feature).click()def input(self, feature, content):return self.find_el(feature).send_keys(content)def clear(self, feature):return self.find_el(feature).clear()

注意: page页面要继承 BaseAction

5 数据驱动

概念

是以数据来驱动整个测试用例的执行, 也就是测试数据决定测试结果

特点

  • 可以把数据驱动理解为一种模式或者一种思想
  • 数据驱动技术可以让用户把关注点放在测试数据的构建和维护上, 而不是直接维护脚本, 可以利用同样的过程, 对不同的输入数据进行测试
  • 数据驱动要依赖参数化技术

数据来源

  • 直接定义在测试脚本中 (简单直观, 但测试方法和测试数据未分离, 不方便后期维护)
  • 从文件中读取数据, 如 txt, excel, xml, JSON等格式文件
  • 从数据库读取数据

5.1 JSON基本介绍

概念

JSON全称是" JavaScript Object Notation", 是JavaScript 对象表示法, 它是一种基于文本, 独立于语言的轻量级数据交换格式

特点

  • JSON是纯文本
  • JSON具有良好的自我描述性, 便于阅读和编写
  • JSON具有清晰的层级结构
  • 有效的提升网络传输效率

对比XML

  • XML指可扩展标记语言, 被设计用来传输数据
  • 如果使用XML, 需要读取XML, 然后通过标签结点来遍历文档, 并读取对应的值, 然后传输
  • 使用JSON, 只需要读取JSON字符串

JSON语法规格

  • 大括号保存对象
  • 中括号保存数组
  • 对象和数组可以相互嵌套
  • 数据采用键值对来表示
  • 多个数据用逗号分隔

JSON值

  • 数字 (整数或者浮点数)
  • 字符串 (在双引号中)
  • 逻辑值 (true 或者 false)
  • 数组 (在中括号中)
  • 对象 (在大括号中)
  • null
    • JSON中空值用 null 表示
    • python中对应的用 None 表示

JSON基本操作

操作内容

  • python字典与JSON之间的转换

  • JSON文件读写

    在python中想要操作 JSON, 需要先导入依赖包

    import json
    

5.2 字典与JSON转换

代码

import json# 把python字典类型转换为JSON字符串
dict1 = {"name": "zhangsan","age": 18,"is_man": True,"school": None
}
# 使用 dumps 方法, 得到的结果是 json 字符串
json_str1 = json.dumps(dict1)
print(json_str1)# 把JSON字符串转换为python字典
json_str2 = '{"name": "zhangsan", "age": 18, "is_man": true, "school": null}'
# 使用 loads 方法, 得到的结果是 python字典
dict2 = json.loads(json_str2)
print(dict2)

把python字典类型转换为JSON字符串: 使用 dumps 方法

把JSON字符串转换为python字典: 使用 loads 方法

5.3 JSON文件读写

代码

import json# 读取 data.json 文件
with open("data.json", "r", encoding="utf-8") as f:data1 = json.load(f)print(data1)# 把字典写入json文件 "data2.json"
data2 = data1
with open("data2.json", "w", encoding="utf-8") as f:json.dump(data2, f)# 把字典写入json文件 "data3.json"  ------解决写入中文的问题
data3 = data1
with open("data3.json", "w", encoding="utf-8") as f:json.dump(data2, f, ensure_ascii=False)

实现步骤

  1. 编写测试用例
  2. 敲代码
    1. 采用PO模式的分层思想对页面进行封装
    2. 编写测试脚本
    3. 定义数据文件, 实现参数化

6 项目实战

在线计算器项目 http://cal.apple886.com/

6.1 项目结构

  • base ----> 存储页面对象的父类(便于子类调用)
  • data ----> 存储测试用例数据
  • page ----> 存储页面对象
  • scripts ----> 存储测试脚本
  • utils ----> 存储经常使用的工具类
  • pytest.ini ----> 运行项目的配置

6.2 base包

新建base_action.py

class BaseAction:# 初始化驱动def __init__(self, driver):self.driver = driver# 查找单个元素def find_el(self, feature):return self.driver.find_element(*feature)# 查找多个元素def find_els(self, feature):return self.driver.find_elements(*feature)# 查找按钮元素def click(self, feature):return self.find_el(feature).click()# 查找输入元素def input(self, feature, content):return self.find_el(feature).send_keys(content)# 清空def clear(self, feature):return self.find_el(feature).clear()# 定位数字按钮def find_el_num(self, feature, num):# 将num格式化为字符串return self.driver.find_element(feature[0], feature[1].format(str(num)))

6.3 data包

新建cal_data.json

{"cal_001": {"data": [1, 3],"result": 4},"cal_002": {"data": [1, 2],"result": 3},"cal_002": {"data": [1, 2, 3],"result": 6}
}

6.4 page包

新建cal_page.py

from selenium.webdriver.common.by import Byfrom base.base_action import BaseActionclass CalPage(BaseAction):# 数字按钮number_btn = By.ID, "simple{}"# 加号按钮add_btn = By.ID, "simpleAdd"# 等号按钮equal_btn = By.ID, "simpleEqual"# 结果result = By.ID, "resultIpt"# 点击数字def click_number_btn(self, num):return self.find_el_num(self.number_btn, num).click()# 点击加号def click_add_btn(self):return self.click(self.add_btn)# 点击等于号def click_equal_btn(self):return self.click(self.equal_btn)# 显示结果def get_result(self):return self.find_el(self.result).get_attribute("value")

6.5 scripts包

新建tesst_cal.py

# 导包
import time
import pytest
from page.cal_page import CalPage
from utils.driver_utils import DriverUtils
from utils.read_data import read_data# 定义测试类
class TestCal:def setup_method(self):self.driver = DriverUtils.get_driver()self.cal_page = CalPage(self.driver)self.driver.get("http://cal.apple886.com/")def teardown_method(self):time.sleep(5)DriverUtils.quit_driver()# 加法算数@pytest.mark.parametrize("params", read_data("cal_data.json"))def test_2_add(self, params):for i in params["data"]:self.cal_page.click_number_btn(i)self.cal_page.click_add_btn()time.sleep(5)# 4. 点击等于号按钮self.cal_page.click_equal_btn()time.sleep(5)# 断言assert str(params["result"]) == self.cal_page.get_result()

6.6 utils包

新建driver_utils.py

# 获取/关闭浏览器驱动的类
from selenium import webdriverclass DriverUtils:__driver = None# 获取浏览器驱动@classmethoddef get_driver(cls):if cls.__driver is None:cls.__driver = webdriver.Chrome()cls.__driver.maximize_window()cls.__driver.implicitly_wait(10)return cls.__driver# 关闭浏览器驱动@classmethoddef quit_driver(cls):if cls.__driver is not None:cls.__driver.quit()cls.__driver = None

新建read_data.py用于读取data包的数据

# 读取data数据文件
import jsondef read_data(filename):with open("./data/" + filename, "r", encoding="utf-8") as f:list_data = []dict_list = json.load(f)for value in dict_list.values():list_data.append(value)return list_data

在这里插入图片描述

7 日志收集

7.1 日志收集

概念 : 日志就是用于记录系统运行时的信息, 也称为Log

作用:

  • 调试程序
    • 旧的方式: print(“xxxx”)
      • low
    • 新的方式: 通过日志
  • 了解程序运行的情况, 是否正常
  • 程序运行故障分析与问题定位
  • 用来做用户行为分析和数据统计
    • 需要学好 sql

级别:

  • 思考
    • 是否记录的所有日志信息重要性都一样?
  • 日志级别, 指日志信息的重要性

常见日志级别:

  1. DEBUG === 调试
  2. INFO === 信息
  3. WARNING === 警告
  4. ERROR === 错误
  5. CRITICAL === 严重错误

日志基本用法:

  • logging:python中有一个标准库, logging模块可以直接记录日志

  • 使用

    1. 导入 logging 包
    2. 输出日志
    3. 默认的日志级别被设置为 warning
  • 设置日志级别

    • 方法
      • logging.basicConfig(level=logging.DEBUG)
  • 设置日志格式

    • 默认格式
      • 日志级别 : Logger名称 : 日志内容
    • 自定义格式
      • logging.basicConfig(format="xxxxxx")
  • 将日志信息输出到文件

    • 默认
      • python的logging模块将日志打印到了标准输出中(控制台)
    • 将日志输出到文件的方法
      • logging.basicConfig(filename=“xxx.log”)

7.2 日志高级用法

思考

  • 如何将日志信息同时输出到控制台和日志文件中?
  • 如何将不同级别的日志输出到不同的日志文件?
  • 如何解决日志文件过大的问题?

7.3 四大组件

  1. 日志器(Logger):提供了程序使用日志的入口
  2. 处理器(Handler):将logger创建的日志记录发送到合适的输出
  3. 格式器(Formatter):决定日志的输出格式
  4. 过滤器(Filter): 提供了更细粒度的控制工具来决定输出哪条日志记录, 丢弃哪条日志记录

组件之间的关系

  • 日志器 (Logger) 是入口,
  • 真正干活的是处理器 (Handler),
  • 处理器还可以通过格式器 (Formatter)
  • 过滤器 (Filter) 对输出的日志内容做格式化和过滤

7.3.1 Logger类

  • 如何创建Logger对象

    • logger = logging.getLogger(name)

    • 可选参数 name

      • 如果不写name, 日志器名称默认为 root

      • 如果写了name, 如, logger = logging.getLogger(“myLogger”) 那么日志器的名称为 myLogger

Logger常用方法

  • 打印日志

    • logger.debug()
    • logger.info()
    • logger.warning
    • logger.error
    • logger.critical()

    设置日志级别

    • logger.setLevel()
    • 为logger对象添加一个handler对象
      • logger.addHandler()

    为logger对象添加一个filter对象

    • logger.addFilter

7.3.2 Handler类

  • 如何创建Handler对象
    • 在程序中不应该直接实例化和使用Handler实例, 因为Handler是一个基类, 它只定义了Handler应该有的接口, 应该使用Handler实现类来创建对象
    • 创建方式
      • 输出日志到控制台
        • logging.StreamHandler
      • 输出到磁盘文件, 默认文件大小会无限增长
      • 输出到文件, 按文件大小切割
      • 输出到文件, 按时间切割
        • logging.hanlders.TimedRotatingFileHandler
      • 将日志消息以get或post的方式发送给http服务器
      • 将日志消息发送给一个指定的email地址
    • 常用方法
      • 为handler设置格式器对象
        • handler.setFormatter()

7.3.3 Formatter类

**作用:**Formatter对象用于配置日志信息的格式

  • 如何创建Formatter对象

    • logging.Formatter(fmt=None, datefmt=None) fmt: 消息格式化字符串, 如果不指定该参数则默认使用message的原始值 datefmt: 日期格式化字符串, 如果不指定该参数则默认使用 “%Y-%m-%d %H:%M:%S”
  • 案例

    • 说明

      • 可读性好的日志需要具备一些特征

        • 在控制台和文件都能输出
        • 文件输出能够按时间切割

步骤

  1. 导包
  2. 创建日志器对象 / 设置日志级别
  3. 创建处理器对象: 输出到控制台 + 文件(按时间切割)
  4. 创建格式器对象
  5. 将格式器添加到处理器
  6. 将处理器添加到日志器
  7. 打印日志

代码

# 1. 导包
import logging
import logging.handlers
# 2. 创建日志器对象 / 设置日志级别
logger = logging.getLogger()      # 默认日志器名称为 root
# logger = logging.getLogger("An")   # 自定义日志器名称为 An
logger.setLevel(level=logging.DEBUG)
# 3. 创建处理器对象: 输出到控制台 + 文件(按时间切割)
ls = logging.StreamHandler()
lf = logging.handlers.TimedRotatingFileHandler(filename="172.log", when="s", backupCount=3)
# 4. 创建格式器对象
fmt = "%(asctime)s %(levelname)s [%(name)s] [%(filename)s:%(funcName)s:%(lineno)d] - %(message)s"
formatter = logging.Formatter(fmt=fmt)
# 5. 将格式器添加到处理器
ls.setFormatter(formatter)
lf.setFormatter(formatter)
# 6. 将处理器添加到日志器
logger.addHandler(ls)
logger.addHandler(lf)
# 7. 打印日志
while 1:logger.debug("===================================================================")

8 面试题

  1. 说明样的项目适合做web自动化?

①需求变动不频繁

②项目周期长

③项目需要回归测试

  1. web自动化一个什么时候开始?

①手工测试结束后

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/28441.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mediamtx流媒体服务器测试

MediaMTX简介 在web页面中直接播放rtsp视频流&#xff0c;重点推荐&#xff1a;mediamtx&#xff0c;不仅仅是rtsp-CSDN博客 mediamtx github MediaMTX(以前的rtsp-simple-server)是一个现成的和零依赖的实时媒体服务器和媒体代理&#xff0c;允许发布&#xff0c;读取&…

可视化大屏开发系列——页面布局

页面布局是可视化大屏的基础&#xff0c;想要拥有一个基本美观的大屏&#xff0c;就得考虑页面整体模块的宽高自适应&#xff0c;我们自然就会想到具有强大灵活性flex布局&#xff0c;再借助百分比布局来辅助。至此&#xff0c;大屏页面布局问题即可得到解决。 写在前面&#x…

哪些数据管理知识领域需要做到数据全生命周期管理

一、数据生命周期 数据管理、数据治理、数据安全、元数据管理、数据治理等知识领域,都需要按照数据的生命周期开展管理工作。数据生命周期包括计划、设计/启用、创建/获取、存储/维护、使用、增强和处置。详见下图。 1.数据治理生命周期 1)规划:将数据要求与业务战略连接起…

PTA 6 - 20 汉诺塔问题(py 递归)

这道题是一道比较典型的递归问题&#xff0c;他跟斐波那契数列的本质是一样的&#xff0c;大家自己动手推理一下&#xff0c;非常好推 参考代码&#xff1a; def hanoi(n,a,b,c):global stepif n 1:print(a,"->",c)step 1else:hanoi(n-1,a,c,b)print(a,"…

查看npm版本异常,更新nvm版本解决问题

首先说说遇见的问题&#xff0c;基本上把nvm&#xff0c;npm的坑都排了一遍 nvm版本导致npm install报错 Unexpected token ‘.‘install和查看node版本都正确&#xff0c;结果查看npm版本时候报错 首先就是降低node版本… 可以说基本没用&#xff0c;如果要降低版本的话&…

用python纯手写一个日历

一、代码 # 月份名称数组 months ["January", "February", "March", "April", "May", "June","July", "August", "September", "October", "November", &qu…

深度解析RocketMq源码-持久化组件(二) MappedFileQueue

1.绪论 MappedFileQueue是commitLog中最核心的主组件。前面讲解commitLog的时候也曾说过&#xff0c;MappedFileQueue本质上就是一个MappedFile队列&#xff0c;而commitLog操纵Mmapped读写的时候&#xff0c;也是通过MappedFileQueue来实现的。 commitlog和mappedfilequeue和…

git下载路径

第一步 1进入官网&#xff1a;Git - Downloading Package 第二步 根据自己的系统选择对应版本下载

局域网内怎么访问另一台电脑?(2种方法)

案例&#xff1a;需要在局域网内远程电脑 “当我使用笔记本电脑时&#xff0c;有时需要获取保存在台式机上的文件&#xff0c;而两者都连接在同一个局域网上。我的台式机使用的是Windows 10企业版&#xff0c;而笔记本电脑则是Windows 10专业版。我想知道是否可以通过网络远程…

OpenCV计算形状之间的相似度ShapeContextDistanceExtractor类的使用

操作系统&#xff1a;ubuntu22.04OpenCV版本&#xff1a;OpenCV4.9IDE:Visual Studio Code编程语言&#xff1a;C11 1.功能描述 ShapeContextDistanceExtractor是OpenCV库中的一个类&#xff0c;主要用于计算形状之间的相似度或距离。它是基于形状上下文&#xff08;Shape Co…

26.1 WEB框架介绍

1. Web应用程序 1.1 应用程序有两种模式 应用程序的架构模式主要分为两种: C/S (客户端/服务器端)和B/S(浏览器/服务器端). * 1. C/S模式, 即客户端/服务器模式(Client/Server Model): 是一种分布式计算模式.它将应用程序的功能划分为客户端和服务器端两部分.在这种模式下, 客…

码住!详解时序数据库不同分类与性能对比

加速发展中的时序数据库&#xff0c;基于不同架构&#xff0c;最流行的类别是&#xff1f; 作为管理工业场景时序数据的新兴数据库品类&#xff0c;时序数据库凭借着对海量时序数据的高效存储、高可扩展性、时序分析计算等特性&#xff0c;一跃成为物联网时代工业领域颇受欢迎的…

C++升级软件时删除老版本软件的桌面快捷方式(附源码)

删除桌面快捷方式其实是删除桌面上的快捷方式文件,那我们如何去删除桌面快捷方式文件呢?软件可能已经发布过多个版本,其中的一些版本的快捷方式文件名称可能做了多次改动,程序中不可能记录每个版本的快捷方式名称,没法直接去删除快捷方式文件。本文就给出一种有效的处理办…

【GO-OpenCV】go-cv快速配置

最近对golang实现目标检测心血来潮&#xff0c;尝试在没有sudo权限的平台配置go-cv,有所发现&#xff0c;索性多个平台都做尝试 安装Go语言&#xff08;Golang&#xff09; 通过包管理器安装&#xff08;适用于Debian/Ubuntu&#xff09;(有点慢) 更新包列表&#xff1a; sud…

Linux命令2

文章目录 移动文件或目录mv格式 查找命令/文件存放位目录置which格式 查找文件或目录find格式查找类型多个查找条件逻辑运算符 移动文件或目录 mv 将文件或者目录移动到指定的位置 如果目标的位置和源位置相同&#xff0c;相当于改名操作 跨目录移动相当于window的剪切 格式…

C++ 算法教程

归并排序 #include<iostream> using namespace std; template <class T> void Merge(T data[],int start,int mid,int end) {int len1 mid - start 1, len2 end - mid;int i, j, k;T* left new int[len1];T* right new int[len2];for (i 0; i < len1; i)…

TF-IDF(Term Frequency-Inverse Document Frequency)

TF-IDF&#xff08;Term Frequency-Inverse Document Frequency&#xff09;是一种常用于信息检索和文本挖掘的统计方法&#xff0c;用以评估一个词语对于一个文件集或一个语料库中的其中一份文件的重要程度。它的重要性随着词语在文本中出现的次数成正比增加&#xff0c;但同时…

【SOEM主站】EtherCAT主站时钟偏移补偿

在进行EtherCAT主从通讯测试时&#xff0c;比较容易在DC配置出现错误&#xff0c;特别是使用到从站DC模式时&#xff0c;有时会报同步错误&#xff0c;有时即使没报错误伺服从站运行过程中也会出现电机轴的抖动。引起同步错误其中一个原因就是主站发送数据帧时间存在较大的抖动…

Hadoop+Spark大数据技术(微课版)总复习

图1 Hadoop开发环境 图2 HDFS 图3 MapReduce 图4 HBase 图5 Scala 图6 Spark 图7 Spark RDD 图8 &#xff08;不考&#xff09; 图9 Spark SQL 图10 Spark Streaming 图11 Spark GraphX 第一章 Hadoop大数据开发环境 hadoop是什么&#xff1f; &#xff08;判断题&#…

二维数组与指针【C语言】

二维数组与指针 一维数组一维数组与指针二维数组二维数组与指针总结补充判断以下方式是否正确打印二维数组一维数组 int arr[] = {11, 22, 33, 44};arr:首地址(第一个元素的地址) 一维数组与指针 int arr[] = {11, 22, 33, 44};因为,arr表示的是首地址,等价于 int* p =…