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 time

from 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 time

from 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 time

from 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 time

from 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 webdriver

driver = 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 webdriver

driver = 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 webdriver

driver = 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 webdriver

driver = 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 webdriver

driver = 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 webdriver

driver = 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 webdriver

driver = 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 webdriver

driver = 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
			//input
		CSS
			input
	通过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 By

driver = 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 By

driver = 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 webdriver

driver = 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 webdriver

driver = 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 time

from selenium import webdriver
from selenium.webdriver import ActionChains

driver = 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 ActionChains

driver = 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 ActionChains

driver = 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 ActionChains

driver = 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 Keys

driver = 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 webdriver

driver = 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 WebDriverWait

driver = 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 webdriver

driver = 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 Select

driver = 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 webdriver

driver = 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 webdriver

driver = 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 webdriver

driver = 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 webdriver

driver = 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 webdriver

driver = 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 webdriver

driver = 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 webdriver

driver = 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 + y

class TestPlus:

    # 判断 1+1 的结果等于 2
    def test_a(self):
        assert 2 == add(1, 1)

    # 调换表达式两个值的位置, 判断 1+1 的结果等于 2
    def test_b(self):
        assert add(1, 1) == 2

    # 判断 1+2 的结果不等于4
    def 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 time


def add(x, y):
    return x + y

class 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 pytest


class 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 pytest


class 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 pytest


class 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").text
        print(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").text
        print(msg)
        # 断言
        assert "密码错误!" == msg

存在问题

依然是代码冗余

4.4.1 方法封装

概念

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

好处

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

目的

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

4.5 V3版本

驱动工具类

# 获取/关闭浏览器驱动的类
from selenium import webdriver


class DriverUtils:
    __driver = None

    # 获取浏览器驱动
    @classmethod
    def get_driver(cls):
        if cls.__driver is None:
            cls.__driver = webdriver.Chrome()
            cls.__driver.maximize_window()
            cls.__driver.implicitly_wait(10)
        return cls.__driver

    # 关闭浏览器驱动
    @classmethod
    def 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").text
        print(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").text
        print(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").text
        return 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").text
        return msg

4.8 v5版本

介绍

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

优化内容

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

示例代码

login_page.py

from selenium.webdriver.common.by import By


class 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 = driver

    def 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 = 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()

注意: 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 By

from base.base_action import BaseAction


class 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 webdriver


class DriverUtils:
    __driver = None

    # 获取浏览器驱动
    @classmethod
    def get_driver(cls):
        if cls.__driver is None:
            cls.__driver = webdriver.Chrome()
            cls.__driver.maximize_window()
            cls.__driver.implicitly_wait(10)
        return cls.__driver

    # 关闭浏览器驱动
    @classmethod
    def quit_driver(cls):
        if cls.__driver is not None:
            cls.__driver.quit()
            cls.__driver = None

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

# 读取data数据文件
import json


def 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自动化一个什么时候开始?

①手工测试结束后

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++ 58 之 计算器案例

虚函数,vitual function C动态多态性是通过虚函数来实现的&#xff0c;虚函数允许子类&#xff08;派生类&#xff09;重新定义父类&#xff08;基类&#xff09;成员函数&#xff0c;而子类&#xff08;派生类&#xff09;重新定义父类&#xff08;基类&#xff09;虚函数的做…

国产MCU芯片(2):东软MCU概览

前言: 国产芯片替代的一个主战场之一就是mcu,可以说很多国内芯片设计公司都打算或者已经在设计甚至有了一款或多款的量产产品了,这也是国际大背景决定的。过去的家电市场、过去的汽车电子市场,的确国产芯片的身影不是很常见,如今不同了,很多fabless投身这个行业,一种是…

一文带你精通Android中的Activity

本文将会从活动的生命周期、启动模式、Intent数据传输、最佳实践等多维度来讲解Activity&#xff0c;希望对你有用 生命周期 深入理解活动的生命周期&#xff0c;可以帮助我们更加流畅地编程&#xff0c;并在管理系统资源方面更加游刃有余 活动状态 每个活动在生命周期中最…

等保一体机:多种防护机制,让等保合规简单高效!

自1994年国务院颁布《中华人民共和国计算机信息系统安全保护条例》规定计算机信息系统实行安全等级保护以来&#xff0c;等级保护工作经过了近25年的发展历程&#xff0c;成为了我国网络安全保护的重要举措之一。 2019年12月1日等保2.0正式开始实施&#xff0c;我国网络安全行业…

C++ virtual public(虚继承类)

这个"virtual"有什么作用&#xff1f; 由于C支持多重继承&#xff0c;所以对于一个派生类中有几个直接父类&#xff0c;而几个直接父类中有几个可能分别继承自某一个基类&#xff08;就是父类的父类&#xff09;&#xff0c;这样在构造最终派生类时&#xff0c;会出现…

15.docker-compose(单机版的容器编排工具)

docker-compose(单机版的容器编排工具) 类似ansible剧本 安装docker-compose编排工具 yum install -y docker-compose #&#xff08;需要epel源&#xff09;##docker-compose配置文件详细指令详解&#xff0c;参考如下链接 http://www.jianshu.com/p/2217cfed29d7 上传两个d…

路由器虚拟服务器有什么作用

现如今在IPv4时代&#xff0c;由于公网IP地址的匮乏&#xff0c;约有70%的电脑都处于内网中&#xff0c;上网需要通过路由器。如果反过来想要访问身处内网的电脑&#xff0c;我们就需要在路由器里开放相应的端口才能实现。而这开放端口的功能&#xff0c;在路由器里就叫做虚拟服…

Vue54-浏览器的本地存储webStorage

一、本地存储localStorage的作用 二、本地存储的代码实现 2-1、存储数据 注意&#xff1a; localStorage是window上的函数&#xff0c;所以&#xff0c;可以把window.localStorage直接写成localStorage&#xff08;直接调用&#xff01;&#xff09; 默认调了p.toString()方…

导出本地服务到Public Network,需有密码才能访问,7天有效时间

导出服务到Public Network&#xff0c;7天有效时间&#xff0c;需有密码才能访问 npm install -g localtunnellt --port 8000详细文档 https://localtunnel.github.io/www/

树状数组练习

先看一下最后一题&#xff0c;这是一个树状数组的题目&#xff0c;那就水一下吧,但是由于没有注意问题&#xff0c;wa了很多次 const int N (int)1e5 5; int n; int flag[N]; int dp[N]; class Solution { public:vector<int> countOfPeaks(vector<int>& num…

贪心算法学习五

例题一 解法&#xff08;贪⼼&#xff09;&#xff1a; 贪⼼策略&#xff1a; 我们的任何选择&#xff0c;应该让这个数尽可能快的变成 1 。 对于偶数&#xff1a;只能执⾏除 2 操作&#xff0c;没有什么分析的&#xff1b; 对于奇数&#xff1a; i. 当 n 1 的时候…

Prometheus之图形化界面grafana与服务发现

前言 上一篇文章中我们介绍了Prometheus的组件&#xff0c;监控作用&#xff0c;部署方式&#xff0c;以及如何通过在客户机安装exporter再添加监控项的操作。 但是不免会发现原生的Prometheus的图像化界面对于监控数据并不能其他很好的展示效果。所以本次我们将介绍一…

DP:01背包问题

一、背包问题的概述 背包问题是⼀种组合优化的NP完全问题。 本质上是为了找出“带有限制条件的组合最优解” 1、根据物品的个数&#xff0c;分为如下几类&#xff1a; • 01背包问题&#xff1a;每个物品只有⼀个&#xff08;重点掌握&#xff09;• 完全背包问题&#xff1…

【如何保持专注】

今日&#x1f4d2;&#xff0c;看博主分享下保持专注的新方法&#xff0c;有点意思 &#xff0c; 怎么保持专注&#xff0c;给大家分享两个极客的方法啊。 第一个呢是来自于一个非常著名的程序员啊&#xff0c;叫做这个尼克温特&#xff0c;大家有兴趣可以查一下&#xff0c;就…

探究肥胖致血糖异常的原因与运动的意义

肥胖对身体血糖存在影响&#xff0c;原因主要在于以下两方面。 首先&#xff0c;肥胖者体内的脂肪组织大量积聚&#xff0c;会释放诸多有害物&#xff0c;对胰岛素的正常功能形成干扰&#xff0c;致使胰岛素抵抗加剧&#xff0c;从而造成血糖调节失常。 其次&#xff0c;肥胖往…

Spring AI探索

Spring AI概述 该Spring AI项目旨在简化包含人工智能功能的应用程序的开发&#xff0c;避免不必要的复杂性。 该项目从著名的 Python 项目&#xff08;例如 LangChain 和 LlamaIndex&#xff09;中汲取灵感&#xff0c;但 Spring AI 并非这些项目的直接移植。该项目的成立基于…

Day 24:100301. 构成整天的下标对数目II

Leetcode 100301. 构成整天的下标对数目II 给你一个整数数组 hours&#xff0c;表示以 **小时 **为单位的时间&#xff0c;返回一个整数&#xff0c;表示满足 i < j 且 hours[i] hours[j] 构成 **整天 **的下标对 i, j 的数目。 **整天 **定义为时间持续时间是 24 小时的 *…

Electron+vite+vuetify项目搭建

最近想用Electron来进行跨平台的桌面应用开发。同时想用vuetify作为组件&#xff0c;于是想搭建一个这样的开发环境。其中踩了不少坑&#xff0c;总是会出现各种的编译错误和问题&#xff0c;依赖的各种问题&#xff0c;搞了好久最终环境终于弄好可正常开发了。这里分享下快速搭…

C语言王国——深入自定义类型(结构体)

目录 一、引言 二、结构体 1. 结构体类型的声明 2. 结构体变量的创建和初始化 2.1 创建 2.2 初始化 2.3 typedef 2.4 特殊声明 2.5 自引用 3. 结构成员访问操作符 4. 结构体内存对齐 4.1 对齐规则 4.2 offsetof 4.3 为什么存在内存对齐 5. 结构体传参 6. 结构体实现…

力扣191. 位1的个数

Problem: 191. 位1的个数 文章目录 题目描述思路复杂度Code 题目描述 思路 题目规定数值的范围不会超过32位整形数 1.定义统计个数的变量oneCount&#xff1b;由于每次与给定数字求与的变量mask初始化为1 2.for循环从0~32&#xff0c;每一次拿mask与给定数字求与运算&#xff…