web自动化

web自动化

selenium1-ide及webdriver

selenium IDE(浏览器插件)

安装

图片[1] - web自动化 - 宋马
图片[2] - web自动化 - 宋马
图片[3] - web自动化 - 宋马
图片[4] - web自动化 - 宋马

使用

图片[5] - web自动化 - 宋马
图片[6] - web自动化 - 宋马
图片[7] - web自动化 - 宋马
图片[8] - web自动化 - 宋马
图片[9] - web自动化 - 宋马
图片[10] - web自动化 - 宋马

selenium库

selenium webdriver工作原理

selenium工作原理详解

安装

pip install selenium
pip install -i 国内源 selenium

安装浏览器驱动

火狐浏览器:geckodriver

下载地址:https://github.com/mozilla/geckodriver/releases/tag/v0.27.0
国内镜像:https://npm.taobao.org/mirrors/geckodriver/
下载以后,解压出geckodriver.exe 文件
添加到环境变量中,如当前项目的Scripts目录下面

谷歌浏览器驱动:chromedriver

下载地址:http://npm.taobao.org/mirrors/chromedriver/
需要下载跟自己的谷歌浏览器版本相同的驱动

写代码

引入对应的库:from selenium import webdriver
实例化浏览器对象(火狐):browser = webdriver.Firefox()
打开相应的URL:browser.get(url)
浏览器最大化:browser.maximize_window()
查找节点:element = browser.find_element_by_xpath('xpath语句')
执行对应的操作
关闭标签页:browser.close()
退出浏览器:broser.quit()

浏览器对象的属性

获取当前窗口的url:browser.current_url
获取当前窗口的title:browser.title
前进:browser.forward()
后退:browser.back()
刷新:browser.refresh()
设置窗口大小:browser.set_window_size(500,800)
获取当前页面截图:driver.get_screenshot_as_file('截图.png')
获取当前页面源代码:driver.page_source
执行JS脚本:driver.execute_script(js)

selenium2-选择节点

8大方法

1. ID

# 1. 根据ID,最快最简洁,因为html里面ID是唯一的
driver.find_element_by_id('id属性的值')

2. class_name

# 2. 根据class属性的值去找,找到并返回第一个满足条件的节点
# 如果有多个class,用.隔开
driver.find_element_by_class_name('class属性的值')

3. name

# 3. 根据name属性的值去找
driver.find_element_by_name('name属性的值')

4. tag_name

# 4. 根据标签名去找
driver.find_element_by_tag_name('标签名')

5. link_text

# 5. 根据链接的文本去查找a标签,这里的文本要完全匹配
driver.find_element_by_link_text('链接的文本')

6. partial_link_text

# 6. 根据链接的文本查找a标签,这里的文本可以部分匹配
driver.find_element_by_partial_link_text('链接的文本')

7. css_selector

# 7. 根据css选择器来找
# 找class属性的值为bg s_btn的标签,不限定标签名,这里.表示class
driver.find_element_by_css_selector('.bg.s_btn')

# 找id属性的值为su的标签,这里#表示id
driver.find_element_by_css_selector('#su')

# 找包含name属性的标签,[]里面放属性
driver.find_element_by_css_selector('[name]')

# 找包含name属性的input标签
driver.find_element_by_css_selector('input[name]')

# 找name属性的值为f的input标签
driver.find_element_by_css_selector('input[name="f"]')

# 找name属性的值包含bp字母的input标签
driver.find_element_by_css_selector('input[name*="bp"]')

# 找name属性的值包含bp单词的input标签
driver.find_element_by_css_selector('input[name~="bp"]')

# 找name属性的值以bp开头的input标签
driver.find_element_by_css_selector('input[name^="bp"]')

# 找name属性的值以bp结尾的input标签
driver.find_element_by_css_selector('input[name$="bp"]')

# 找span标签内部,且name属性的值为wd的input标签
driver.find_element_by_css_selector('span input[name="wd"]')

# 找span标签为父标签,且name属性的值为wd的input标签
driver.find_element_by_css_selector('span>input[name="wd"]')

# 找span标签后面,且name属性的值为wd的input标签
driver.find_element_by_css_selector('span+input[name="wd"]')

8. xpath

# 8. 根据xpath表达式选择

# 查找class="bg s_btn"的input标签
driver.find_element_by_xpath('//input[@class="bg s_btn"]')

# 找id属性的值为su的标签,*表示不限制标签名
driver.find_element_by_xpath('//*[@id="su"]')

# 找包含name属性的标签
driver.find_element_by_xpath('//*[@name]')

# 找包含name属性的input标签
driver.find_element_by_xpath('//input[@name]')

# 找name属性的值为f的input标签
driver.find_element_by_xpath('//input[@name="f"]')

# 找name属性的值包含bp字母的input标签
driver.find_element_by_xpath('//input[contains(@name,"bp")]')

# 找span标签内部,且name属性的值为wd的input标签
driver.find_element_by_xpath('//span//input[@name="wd"]')

# 找span标签为父标签,且name属性的值为wd的input标签
driver.find_element_by_xpath('//span/input[@name="wd"]')

# 根据文本来找标签,找到标签文本等于zhangsan的p标签
driver.find_element_by_xpath('//p[text()="zhangsan"]')

# 根据文本来找标签,找到标签文本包含zhangsan的p标签
driver.find_element_by_xpath('//p[contains(text(),"zhangsan")]')

# 查找class属性为telA且type属性为textA的input标签
driver.find_element_by_xpath('//input[@class="telA" and @type="textA"]')

备注

1. find_element_by 与 find_elements_by
# find_element_by 系列,找到满足条件的第一个 返回的是一个element节点对象
# find_elements_by 系列,找到所有满足条件的  返回的是一个列表,列表里面的每一个元素是一个element节点对象

# 返回的是一个节点
element = find_element_by_xpath('//input') 
print(element.get_attribute('name'))
print(element.text)

# 返回的是一个列表
element_list = find_elements_by_xpath('//input') 
if element_list:
    for i in element_list:
        print(i.get_attribute('name'))
        print(i.text)
2. find_element 与 find_elements
# 以上8大方法,都是基于find_element()方法进行封装的
# 如果我们要直接使用find_element()方法,需要引入By
from selenium.webdriver.common.by import By
driver.find_element_by_id('su')
# 当执行上面的代码时,会使用下面的方法
driver.find_element(By.ID,'su')

# 以下两个方法等价
driver.find_element_by_xpath('//input[@name="abc"]')
driver.find_element(By.XPATH,'//input[@name="abc"]')

节点的属性和方法

a = driver.find_element(By.ID,'su')
a.text # 获取节点的文本
a.get_attribute('属性名') # 获取节点指定属性的值

# 节点对象同样可以是用find_element方法查找节点
span = driver.find_element_by_class_name('bg.s_btn_wr')
span.find_element_by_tag_name('input')

a.send_keys('需要填充的数据') # 给标签填充数据
a.click() #点击节点
a.clear() # 清除节点内容

a.is_displayed() # 判断节点是否可见
a.is_enabled() # 判断节点是否可用
a.is_selected() # 判断节点是否被选中

a.location # 输出节点的左上角的位置
a.size # 节点的尺寸

a.screenshot('节点截图.png') # 对节点进行截图

selenium3-键盘及鼠标

键盘操作

输入一些特殊按键
操作步骤

# 引入包
from selenium.webdriver.common.keys import Keys
# 通过send_keys进行输入
# 以下为执行ctrl+c
element.send_keys(Keys.CONTROL,'c')

鼠标操作-动作链

关于鼠标的一些操作

使用示例

# 1. 引入库
from selenium.webdriver import ActionChains
# from selenium.webdriver.common.action_chains import ActionChains

# 2. 实例化
driver = webdriver.Firefox()
# 有一个入参,为webdriver的实例化对象
action = ActionChains(driver)

# 3. 执行操作
# 对element节点对象执行双击操作,此时只是把对应的动作保存在action中,并未实际执行
action.double_click(element)
# 可以先添加多个动作,然后通过perform函数统一执行
action.perform()

方法


# 1. 移动鼠标到指定的节点
more_el = driver.find_element_by_name('tj_briicon')
ac.move_to_element(more_el)
# 以上动作只会添加到队列中,需要调用perform()函数进行执行
ac.perform()

# 2. 移动鼠标偏移当前位置100,20
ac.move_by_offset(100,20)

# 3. 移动鼠标到more_el节点的左上角并往右偏移100,往下偏移20
ac.move_to_element_with_offset(more_el,100,20)

# 4. 左键点击节点,并不放开
ac.click_and_hold(more_el)

# 5. 双击鼠标左键
ac.double_click(more_el)

# 6. 右键
ac.context_click(more_el)

# 7. 将节点从源位置拖动到目标位置
red_div = driver.find_element_by_id('div1')
blue_div = driver.find_element_by_id('div2')
ac.drag_and_drop(red_div,blue_div)

# 8. 将节点,拖动到偏移当前位置向右100,向下200的位置
ac.drag_and_drop_by_offset(red_div,100,200)

# 9. 按下指定按键
from selenium.webdriver.common.keys import Keys
# 按下Ctrl键,不松开
ac.key_down(Keys.CONTROL)
# 松开ctrl键
ac.key_up(Keys.CONTROL)
# key_down搭配ctrl、alt或者shift使用

注意事项

如果页面刷新,action对象需要重新实例化,动作执行所需的节点,也需要重新定位,可采用以下方法执行脚本

ActionChains(driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()

selenium4

执行JS语句

driver.execute_script(js)

# 以下脚本为从yya标签中,删除disabled属性
yya = driver.find_element_by_id('yyA')
driver.execute_script('arguments[0].removeAttribute(arguments[1])', yya,'disabled')

# 以下脚本为滚动页面,向右滚动100像素,向下滚动200像素
js = "window.scrollTo(100,200)"
driver.execute_script(js)

下拉框

通过Select类完成操作
引入

from selenium.webdriver.support.select import Select

找到要操作的下拉框

sel = driver.find_element_by_xxx('')

创建Select对象

sel_obj = Select(sel)

通过Select对象的方法进行选项选择

# 根据索引选择节点
sel_obj.select_by_index(1)

# 根据value属性的值选择节点
sel_obj.select_by_value('bj')

# 根据文本选择节点
sel_obj.select_by_visible_text('A广州')

元素等待

显示等待(了解)

当首次找不到元素时,会在设定的最大等待时间内,按照间隔时间不停的去找,直到找到该元素或者超出最大等待时间。找到了则返回该元素,否则抛出异常

针对单个元素,每个需要等待的元素都需要设置下等待条件

创建一个WebDriverWait对象

from selenium.webdriver.support.wait import WebDriverWait
from selenium import webdriver
driver = webdriver.Firefox()
# 需要传入driver对象及最大等待时间,第三个参数是时间间隔,默认为0.5秒
wait = WebDriverWait(driver,10,0.5)

设置等待条件

from selenium.webdriver.support import expected_conditions as EC

通过try…except结构去处理异常

from selenium.webdriver.common.by import By
try:
    userA = wait.until(EC.presence_of_element_located((By.ID,'userA')))
except:
    print('10秒过后依然没有找到userA')

其他等待条件

WebDriverWait(driver,10).until(EC.title_is(u"百度一下,你就知道"))
'''判断title,返回布尔值'''

WebDriverWait(driver,10).until(EC.title_contains(u"百度一下"))
'''判断title,返回布尔值'''

WebDriverWait(driver,10).until(EC.presence_of_element_located((By.ID,'kw')))
'''判断某个元素是否被加到了dom树里,并不代表该元素一定可见,如果定位到就返回WebElement'''

WebDriverWait(driver,10).until(EC.visibility_of_element_located((By.ID,'su')))
'''判断某个元素是否被添加到了dom里并且可见,可见代表元素可显示且宽和高都大于0'''

WebDriverWait(driver,10).until(EC.visibility_of(driver.find_element(by=By.ID,value='kw')))
'''判断元素是否可见,如果可见就返回这个元素'''

WebDriverWait(driver,10).until(EC.presence_of_all_elements_located((By.CSS_SELECTOR,'.mnav')))
'''判断是否至少有1个元素存在于dom树中,如果定位到就返回列表'''

WebDriverWait(driver,10).until(EC.visibility_of_any_elements_located((By.CSS_SELECTOR,'.mnav')))
'''判断是否至少有一个元素在页面中可见,如果定位到就返回列表'''

WebDriverWait(driver,10).until(EC.text_to_be_present_in_element((By.XPATH,"//*[@id='u1']/a[8]"),u'设置'))
'''判断指定的元素中是否包含了预期的字符串,返回布尔值'''

WebDriverWait(driver,10).until(EC.text_to_be_present_in_element_value((By.CSS_SELECTOR,'#su'),u'百度一下'))
'''判断指定元素的属性值中是否包含了预期的字符串,返回布尔值'''

#WebDriverWait(driver,10).until(EC.frame_to_be_available_and_switch_to_it(locator))
'''判断该frame是否可以switch进去,如果可以的话,返回True并且switch进去,否则返回False'''
#注意这里并没有一个frame可以切换进去

WebDriverWait(driver,10).until(EC.invisibility_of_element_located((By.CSS_SELECTOR,'#swfEveryCookieWrap')))
'''判断某个元素在是否存在于dom或不可见,如果可见返回False,不可见返回这个元素'''
#注意#swfEveryCookieWrap在此页面中是一个隐藏的元素

WebDriverWait(driver,10).until(EC.element_to_be_clickable((By.XPATH,"//*[@id='u1']/a[8]"))).click()
'''判断某个元素中是否可见并且是enable的,代表可点击'''
driver.find_element_by_xpath("//*[@id='wrapper']/div[6]/a[1]").click()
#WebDriverWait(driver,10).until(EC.element_to_be_clickable((By.XPATH,"//*[@id='wrapper']/div[6]/a[1]"))).click()

#WebDriverWait(driver,10).until(EC.staleness_of(driver.find_element(By.ID,'su')))
'''等待某个元素从dom树中移除'''
#这里没有找到合适的例子

WebDriverWait(driver,10).until(EC.element_to_be_selected(driver.find_element(By.XPATH,"//*[@id='nr']/option[1]")))
'''判断某个元素是否被选中了,一般用在下拉列表'''

WebDriverWait(driver,10).until(EC.element_selection_state_to_be(driver.find_element(By.XPATH,"//*[@id='nr']/option[1]"),True))
'''判断某个元素的选中状态是否符合预期'''

WebDriverWait(driver,10).until(EC.element_located_selection_state_to_be((By.XPATH,"//*[@id='nr']/option[1]"),True))
'''判断某个元素的选中状态是否符合预期'''
driver.find_element_by_xpath(".//*[@id='gxszButton']/a[1]").click()

instance = WebDriverWait(driver,10).until(EC.alert_is_present())
'''判断页面上是否存在alert,如果有就切换到alert并返回alert的内容'''

隐式等待(掌握)

当首次找不到元素时,会等待所设置的最大等待时间,等待时间结束后会再次查找,如果找到,则返回该元素,否则抛出异常

针对全局,代码中所有用到的节点,找不到时,都会进行等待

driver = webdriver.Firefox()
driver.implicitly_wait(10)

弹出框

三类弹出框alert、confirm、prompt
切换到弹出框alert = driver.switch_to.alert
操作

确认:alert.accept()
取消:alert.dismiss()
文本:alert.text
填写信息:alert.send_keys('内容')

切换frame/窗口

切换frame

driver.switch_to.frame('frame_name') # 通过name
driver.switch_to.frame(1) # 通过索引
driver.switch_to.frame(driver.find_elements_by_tag_name("iframe")[0]) # 通过节点

切换回去

driver.switch_to.default_content() # 切换回打开时的页面
driver.switch_to.parent_frame() # 切换回父级页面

切换窗口

主要根据句柄进行切换
查看句柄

driver.windows_handles # 查看当前所有标签页的句柄
driver.current_window_handle # 查看当前标签页的句柄

切换

switch_to.window(目标标签页的句柄)
# 一般格式
# 执行打开新的标签页的操作
  old_handles = driver.windows_handles
    
for i in driver.windows_handles:
    if i not in old_handles:
        driver.switch_to.window(i)

隐藏指纹

参考链接

找到chrome浏览器的目录,在cmd中通过以下命令打开浏览器

chrome.exe --remote-debugging-port=9222

通过以下方式启动浏览器

from selenium import webdriver

from selenium.webdriver.chrome.options import Options

chrome_options = Options()
chrome_options.add_argument('user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36')

chrome_options.add_experimental_option('debuggerAddress', '127.0.0.1:9222')

driver = webdriver.Chrome(options=chrome_options)

selenium工作原理详解

参考资料

非源码讲解

源码讲解

selenium简介

Selenium是一个用于Web应用程序自动化测试工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。支持的浏览器包括IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera等。

主要功能包括:测试与浏览器的兼容性——测试你的应用程序看是否能够很好得工作在不同浏览器和操作系统之上。

测试系统功能——创建回归测试检验软件功能和用户需求。支持自动录制动作和自动生成 .Net、Java、Perl等不同语言的测试脚本(这里主要是针对selenium ide)

selenium历程

04年,诞生了Selenium Core,Selenium Core是基于浏览器并且采用JavaScript编程语言的测试工具,运行在浏览器的安全沙箱中,设计理念是将待测试产品、Selenium Core和测试脚本均部署到同一台服务器上来完成自动化测试的工作。

05年,Selenium RC诞生,就是selenium1 ,这个时候,Selenium Core其实是Selenium RC的核心。

Selenium RC让待测试产品、Selenium Core和测试脚本三者分散在不同的服务器上。(测试脚本只关心将HTTP请求发送到指定的URL上,selenium本身不需要关心HTTP请求由于什么程序编程语言编写而成)

Selenium RC包括两部分:一个是Selenium RC Server,一个是提供各种编程语言的客户端驱动来编写测试脚本

07年,Webdriver诞生,WebDriver的设计理念是将端到端测试与底层具体的测试工具分隔离,并采用设计模式Adapter适配器来达到目标。WebDriver的API组织更多的是面向对象。

08/09年,selenium2诞生,selenium2其实是selenium rc和webdriver的合并,合并的根本原因是相互补充各自的缺点

09年,selenium3诞生,这个版本剔除了selenium rc , 主要由 selenium webdriver和selenium Grid组成, 我们日常使用的其实就是selenium webdriver,至于selenium grid是一个分布式实现自动化测试的工具

那么今天我们就要说说selenium3(selenium webdriver)的工作原理,下面简称selenium(以上具体时间可能不太准确,我也是通过网络资料了解到的,抛砖引玉-)

selenium原理

我们使用Selenium实现自动化测试,主要需要3个东西

1.测试脚本,可以是python,java编写的脚本程序(也可以叫做client端)

2.浏览器驱动, 这个驱动是根据不同的浏览器开发的,不同的浏览器使用不同的webdriver驱动程序且需要对应相应的浏览器版本,比如:geckodriver.exe(chrome)

3.浏览器,目前selenium支持市面上大多数浏览器,如:火狐,谷歌,IE等

selenium脚本

先看一个简单的代码


from selenium import webdriver


dr = webdriver.Chrome()  # 打开浏览器

执行上述代码,我们会发现程序打开了Chrome浏览器(前提:你已经正确配置了chrome的驱动和对应版本)

那么selenium是如何实现这个过程的呢?ok,我们今天就通过分析源码的方式来理解selenium的工作原理

源码分析

查看weddriver源码(按住Ctrl键,鼠标点击Chrome)

C:Python36Libsite-packagesseleniumwebdriverchromewebdriver.py

 1 class WebDriver(RemoteWebDriver):
 2     """
 3     Controls the ChromeDriver and allows you to drive the browser.
 4 
 5     You will need to download the ChromeDriver executable from
 6     http://chromedriver.storage.googleapis.com/index.html
 7     """
 8 
 9     def __init__(self, executable_path="chromedriver", port=0,
10                  options=None, service_args=None,
11                  desired_capabilities=None, service_log_path=None,
12                  chrome_options=None, keep_alive=True):
13         """
14         Creates a new instance of the chrome driver.
15 
16         Starts the service and then creates new instance of chrome driver.
17 
18         :Args:
19          - executable_path - path to the executable. If the default is used it assumes the executable is in the $PATH
20          - port - port you would like the service to run, if left as 0, a free port will be found.
21          - options - this takes an instance of ChromeOptions
22          - service_args - List of args to pass to the driver service
23          - desired_capabilities - Dictionary object with non-browser specific
24            capabilities only, such as "proxy" or "loggingPref".
25          - service_log_path - Where to log information from the driver.
26          - chrome_options - Deprecated argument for options
27          - keep_alive - Whether to configure ChromeRemoteConnection to use HTTP keep-alive.
28         """
29         if chrome_options:
30             warnings.warn('use options instead of chrome_options',
31                           DeprecationWarning, stacklevel=2)
32             options = chrome_options
33 
34         if options is None:
35             # desired_capabilities stays as passed in
36             if desired_capabilities is None:
37                 desired_capabilities = self.create_options().to_capabilities()
38         else:
39             if desired_capabilities is None:
40                 desired_capabilities = options.to_capabilities()
41             else:
42                 desired_capabilities.update(options.to_capabilities())
43 
44         self.service = Service(
45             executable_path,
46             port=port,
47             service_args=service_args,
48             log_path=service_log_path)
49         self.service.start()
50 
51         try:
52             RemoteWebDriver.__init__(
53                 self,
54                 command_executor=ChromeRemoteConnection(
55                     remote_server_addr=self.service.service_url,
56                     keep_alive=keep_alive),
57                 desired_capabilities=desired_capabilities)
58         except Exception:
59             self.quit()
60             raise
61         self._is_remote = False

通过源码中的44-49行发现,初始化了一个service对象,然后调用了start()方法,那么我们继续看下一49行的start()方法到底实现了什么功能?

C:Python36Libsite-packagesseleniumwebdrivercommonservice.py

 1  def start(self):
 2         """
 3         Starts the Service.
 4 
 5         :Exceptions:
 6          - WebDriverException : Raised either when it can't start the service
 7            or when it can't connect to the service
 8         """
 9         try:
10             cmd = [self.path]
11             cmd.extend(self.command_line_args())
12             self.process = subprocess.Popen(cmd, env=self.env,
13                                             close_fds=platform.system() != 'Windows',
14                                             stdout=self.log_file,
15                                             stderr=self.log_file,
16                                             stdin=PIPE)
17         except TypeError:
18             raise
19         except OSError as err:
20             if err.errno == errno.ENOENT:
21                 raise WebDriverException(
22                     "'%s' executable needs to be in PATH. %s" % (
23                         os.path.basename(self.path), self.start_error_message)
24                 )
25             elif err.errno == errno.EACCES:
26                 raise WebDriverException(
27                     "'%s' executable may have wrong permissions. %s" % (
28                         os.path.basename(self.path), self.start_error_message)
29                 )
30             else:
31                 raise
32         except Exception as e:
33             raise WebDriverException(
34                 "The executable %s needs to be available in the path. %s
%s" %
35                 (os.path.basename(self.path), self.start_error_message, str(e)))
36         count = 0
37         while True:
38             self.assert_process_still_running()
39             if self.is_connectable():
40                 break
41             count += 1
42             time.sleep(1)
43             if count == 30:
44                 raise WebDriverException("Can not connect to the Service %s" % self.path)

我们发现9-16行其实就是执行了一个cmd命令,命令的作用就是启动了chromedriver.exeChrome浏览器的驱动程序

这里我们需要注意一点: 下载的浏览器驱动一定要配置到环境变量中,或者放到python的根目录下,便于程序在执行驱动的时候查找

这个过程和我们手动启动浏览器驱动是一样的效果,类似下面的结果

启动驱动程序后,绑定端口号9515,且只允许本地访问这个服务,其实我们可以查看一下我们本地电脑任务管理器,确实开启了一个服务进程程序

第一步工作我们已经知道了执行测试脚本webdriver.Chrome()会自动执行chromedriver.exe驱动程序,然后开启一个进程

如何打开浏览器

我们继续看源码 C:Python36Libsite-packagesseleniumwebdriverchromewebdriver.py 的51-57行代码,调用了父类RemoteWebDriver 的初始化方法,我们看这个方法做了什么事?

C:Python36Libsite-packagesseleniumwebdriver
emotewebdriver.py

 1 class WebDriver(object):
 2     """
 3     Controls a browser by sending commands to a remote server.
 4     This server is expected to be running the WebDriver wire protocol
 5     as defined at
 6     https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
 7 
 8     :Attributes:
 9      - session_id - String ID of the browser session started and controlled by this WebDriver.
10      - capabilities - Dictionaty of effective capabilities of this browser session as returned
11          by the remote server. See https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities
12      - command_executor - remote_connection.RemoteConnection object used to execute commands.
13      - error_handler - errorhandler.ErrorHandler object used to handle errors.
14     """
15 
16     _web_element_cls = WebElement
17 
18     def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub',
19                  desired_capabilities=None, browser_profile=None, proxy=None,
20                  keep_alive=False, file_detector=None, options=None):
21         """
22         Create a new driver that will issue commands using the wire protocol.
23 
24         :Args:
25          - command_executor - Either a string representing URL of the remote server or a custom
26              remote_connection.RemoteConnection object. Defaults to 'http://127.0.0.1:4444/wd/hub'.
27          - desired_capabilities - A dictionary of capabilities to request when
28              starting the browser session. Required parameter.
29          - browser_profile - A selenium.webdriver.firefox.firefox_profile.FirefoxProfile object.
30              Only used if Firefox is requested. Optional.
31          - proxy - A selenium.webdriver.common.proxy.Proxy object. The browser session will
32              be started with given proxy settings, if possible. Optional.
33          - keep_alive - Whether to configure remote_connection.RemoteConnection to use
34              HTTP keep-alive. Defaults to False.
35          - file_detector - Pass custom file detector object during instantiation. If None,
36              then default LocalFileDetector() will be used.
37          - options - instance of a driver options.Options class
38         """
39         capabilities = {}
40         if options is not None:
41             capabilities = options.to_capabilities()
42         if desired_capabilities is not None:
43             if not isinstance(desired_capabilities, dict):
44                 raise WebDriverException("Desired Capabilities must be a dictionary")
45             else:
46                 capabilities.update(desired_capabilities)
47         if proxy is not None:
48             warnings.warn("Please use FirefoxOptions to set proxy",
49                           DeprecationWarning, stacklevel=2)
50             proxy.add_to_capabilities(capabilities)
51         self.command_executor = command_executor
52         if type(self.command_executor) is bytes or isinstance(self.command_executor, str):
53             self.command_executor = RemoteConnection(command_executor, keep_alive=keep_alive)
54         self._is_remote = True
55         self.session_id = None
56         self.capabilities = {}
57         self.error_handler = ErrorHandler()
58         self.start_client()
59         if browser_profile is not None:
60             warnings.warn("Please use FirefoxOptions to set browser profile",
61                           DeprecationWarning, stacklevel=2)
62         self.start_session(capabilities, browser_profile)
63         self._switch_to = SwitchTo(self)
64         self._mobile = Mobile(self)
65         self.file_detector = file_detector or LocalFileDetector()

这里有一行最重要的代码,62行self.start_session(capabilities, browser_profile) 这个方法,继续看一下这个方法的源码做了什么工作

 1     def start_session(self, capabilities, browser_profile=None):
 2         """
 3         Creates a new session with the desired capabilities.
 4 
 5         :Args:
 6          - browser_name - The name of the browser to request.
 7          - version - Which browser version to request.
 8          - platform - Which platform to request the browser on.
 9          - javascript_enabled - Whether the new session should support JavaScript.
10          - browser_profile - A selenium.webdriver.firefox.firefox_profile.FirefoxProfile object. Only used if Firefox is requested.
11         """
12         if not isinstance(capabilities, dict):
13             raise InvalidArgumentException("Capabilities must be a dictionary")
14         if browser_profile:
15             if "moz:firefoxOptions" in capabilities:
16                 capabilities["moz:firefoxOptions"]["profile"] = browser_profile.encoded
17             else:
18                 capabilities.update({'firefox_profile': browser_profile.encoded})
19         w3c_caps = _make_w3c_caps(capabilities)
20         parameters = {"capabilities": w3c_caps,
21                       "desiredCapabilities": capabilities}
22         response = self.execute(Command.NEW_SESSION, parameters)
23         if 'sessionId' not in response:
24             response = response['value']
25         self.session_id = response['sessionId']
26         self.capabilities = response.get('value')
27 
28         # if capabilities is none we are probably speaking to
29         # a W3C endpoint
30         if self.capabilities is None:
31             self.capabilities = response.get('capabilities')
32 
33         # Double check to see if we have a W3C Compliant browser
34         self.w3c = response.get('status') is None
35         self.command_executor.w3c = self.w3c

分析这部分源码可以发现22行是向地址localhost:9515/session发送了一个post请求,参数是json格式的,然后返回特定的响应信息给程序(这里主要就是新建了一个sessionid),最终打开了浏览器

ok,打开浏览器的操作完成了

如何执行对应操作

查看C:Python36Libsite-packagesseleniumwebdriverchromewebdriver.py源码(第一个源码中的51-57行)

51         try:
52             RemoteWebDriver.__init__(
53                 self,
54                 command_executor=ChromeRemoteConnection(
55                     remote_server_addr=self.service.service_url,
56                     keep_alive=keep_alive),
57                 desired_capabilities=desired_capabilities)

点击ChromeRemoteConnection查看一下源码

 1 from selenium.webdriver.remote.remote_connection import RemoteConnection
 2 
 3 
 4 class ChromeRemoteConnection(RemoteConnection):
 5 
 6     def __init__(self, remote_server_addr, keep_alive=True):
 7         RemoteConnection.__init__(self, remote_server_addr, keep_alive)
 8         self._commands["launchApp"] = ('POST', '/session/$sessionId/chromium/launch_app')
 9         self._commands["setNetworkConditions"] = ('POST', '/session/$sessionId/chromium/network_conditions')
10         self._commands["getNetworkConditions"] = ('GET', '/session/$sessionId/chromium/network_conditions')
11         self._commands['executeCdpCommand'] = ('POST', '/session/$sessionId/goog/cdp/execute')

第7行访问的是localhost:9515/session地址,第8-11行,定义了一些和我们使用的浏览器(chrome)特有的接口地址,我们再看一下父类RemoteConnection里面源码

C:Python36Libsite-packagesseleniumwebdriver
emote
emote_connection.py:RemoteConnection

  1 self._commands = {
  2             Command.STATUS: ('GET', '/status'),
  3             Command.NEW_SESSION: ('POST', '/session'),
  4             Command.GET_ALL_SESSIONS: ('GET', '/sessions'),
  5             Command.QUIT: ('DELETE', '/session/$sessionId'),
  6             Command.GET_CURRENT_WINDOW_HANDLE:
  7                 ('GET', '/session/$sessionId/window_handle'),
  8             Command.W3C_GET_CURRENT_WINDOW_HANDLE:
  9                 ('GET', '/session/$sessionId/window'),
 10             Command.GET_WINDOW_HANDLES:
 11                 ('GET', '/session/$sessionId/window_handles'),
 12             Command.W3C_GET_WINDOW_HANDLES:
 13                 ('GET', '/session/$sessionId/window/handles'),
 14             Command.GET: ('POST', '/session/$sessionId/url'),
 15             Command.GO_FORWARD: ('POST', '/session/$sessionId/forward'),
 16             Command.GO_BACK: ('POST', '/session/$sessionId/back'),
 17             Command.REFRESH: ('POST', '/session/$sessionId/refresh'),
 18             Command.EXECUTE_SCRIPT: ('POST', '/session/$sessionId/execute'),
 19             Command.W3C_EXECUTE_SCRIPT:
 20                 ('POST', '/session/$sessionId/execute/sync'),
 21             Command.W3C_EXECUTE_SCRIPT_ASYNC:
 22                 ('POST', '/session/$sessionId/execute/async'),
 23             Command.GET_CURRENT_URL: ('GET', '/session/$sessionId/url'),
 24             Command.GET_TITLE: ('GET', '/session/$sessionId/title'),
 25             Command.GET_PAGE_SOURCE: ('GET', '/session/$sessionId/source'),
 26             Command.SCREENSHOT: ('GET', '/session/$sessionId/screenshot'),
 27             Command.ELEMENT_SCREENSHOT: ('GET', '/session/$sessionId/element/$id/screenshot'),
 28             Command.FIND_ELEMENT: ('POST', '/session/$sessionId/element'),
 29             Command.FIND_ELEMENTS: ('POST', '/session/$sessionId/elements'),
 30             Command.W3C_GET_ACTIVE_ELEMENT: ('GET', '/session/$sessionId/element/active'),
 31             Command.GET_ACTIVE_ELEMENT:
 32                 ('POST', '/session/$sessionId/element/active'),
 33             Command.FIND_CHILD_ELEMENT:
 34                 ('POST', '/session/$sessionId/element/$id/element'),
 35             Command.FIND_CHILD_ELEMENTS:
 36                 ('POST', '/session/$sessionId/element/$id/elements'),
 37             Command.CLICK_ELEMENT: ('POST', '/session/$sessionId/element/$id/click'),
 38             Command.CLEAR_ELEMENT: ('POST', '/session/$sessionId/element/$id/clear'),
 39             Command.SUBMIT_ELEMENT: ('POST', '/session/$sessionId/element/$id/submit'),
 40             Command.GET_ELEMENT_TEXT: ('GET', '/session/$sessionId/element/$id/text'),
 41             Command.SEND_KEYS_TO_ELEMENT:
 42                 ('POST', '/session/$sessionId/element/$id/value'),
 43             Command.SEND_KEYS_TO_ACTIVE_ELEMENT:
 44                 ('POST', '/session/$sessionId/keys'),
 45             Command.UPLOAD_FILE: ('POST', "/session/$sessionId/file"),
 46             Command.GET_ELEMENT_VALUE:
 47                 ('GET', '/session/$sessionId/element/$id/value'),
 48             Command.GET_ELEMENT_TAG_NAME:
 49                 ('GET', '/session/$sessionId/element/$id/name'),
 50             Command.IS_ELEMENT_SELECTED:
 51                 ('GET', '/session/$sessionId/element/$id/selected'),
 52             Command.SET_ELEMENT_SELECTED:
 53                 ('POST', '/session/$sessionId/element/$id/selected'),
 54             Command.IS_ELEMENT_ENABLED:
 55                 ('GET', '/session/$sessionId/element/$id/enabled'),
 56             Command.IS_ELEMENT_DISPLAYED:
 57                 ('GET', '/session/$sessionId/element/$id/displayed'),
 58             Command.GET_ELEMENT_LOCATION:
 59                 ('GET', '/session/$sessionId/element/$id/location'),
 60             Command.GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW:
 61                 ('GET', '/session/$sessionId/element/$id/location_in_view'),
 62             Command.GET_ELEMENT_SIZE:
 63                 ('GET', '/session/$sessionId/element/$id/size'),
 64             Command.GET_ELEMENT_RECT:
 65                 ('GET', '/session/$sessionId/element/$id/rect'),
 66             Command.GET_ELEMENT_ATTRIBUTE:
 67                 ('GET', '/session/$sessionId/element/$id/attribute/$name'),
 68             Command.GET_ELEMENT_PROPERTY:
 69                 ('GET', '/session/$sessionId/element/$id/property/$name'),
 70             Command.GET_ALL_COOKIES: ('GET', '/session/$sessionId/cookie'),
 71             Command.ADD_COOKIE: ('POST', '/session/$sessionId/cookie'),
 72             Command.GET_COOKIE: ('GET', '/session/$sessionId/cookie/$name'),
 73             Command.DELETE_ALL_COOKIES:
 74                 ('DELETE', '/session/$sessionId/cookie'),
 75             Command.DELETE_COOKIE:
 76                 ('DELETE', '/session/$sessionId/cookie/$name'),
 77             Command.SWITCH_TO_FRAME: ('POST', '/session/$sessionId/frame'),
 78             Command.SWITCH_TO_PARENT_FRAME: ('POST', '/session/$sessionId/frame/parent'),
 79             Command.SWITCH_TO_WINDOW: ('POST', '/session/$sessionId/window'),
 80             Command.CLOSE: ('DELETE', '/session/$sessionId/window'),
 81             Command.GET_ELEMENT_VALUE_OF_CSS_PROPERTY:
 82                 ('GET', '/session/$sessionId/element/$id/css/$propertyName'),
 83             Command.IMPLICIT_WAIT:
 84                 ('POST', '/session/$sessionId/timeouts/implicit_wait'),
 85             Command.EXECUTE_ASYNC_SCRIPT: ('POST', '/session/$sessionId/execute_async'),
 86             Command.SET_SCRIPT_TIMEOUT:
 87                 ('POST', '/session/$sessionId/timeouts/async_script'),
 88             Command.SET_TIMEOUTS:
 89                 ('POST', '/session/$sessionId/timeouts'),
 90             Command.DISMISS_ALERT:
 91                 ('POST', '/session/$sessionId/dismiss_alert'),
 92             Command.W3C_DISMISS_ALERT:
 93                 ('POST', '/session/$sessionId/alert/dismiss'),
 94             Command.ACCEPT_ALERT:
 95                 ('POST', '/session/$sessionId/accept_alert'),
 96             Command.W3C_ACCEPT_ALERT:
 97                 ('POST', '/session/$sessionId/alert/accept'),
 98             Command.SET_ALERT_VALUE:
 99                 ('POST', '/session/$sessionId/alert_text'),
100             Command.W3C_SET_ALERT_VALUE:
101                 ('POST', '/session/$sessionId/alert/text'),
102             Command.GET_ALERT_TEXT:
103                 ('GET', '/session/$sessionId/alert_text'),
104             Command.W3C_GET_ALERT_TEXT:
105                 ('GET', '/session/$sessionId/alert/text'),
106             Command.SET_ALERT_CREDENTIALS:
107                 ('POST', '/session/$sessionId/alert/credentials'),
108             Command.CLICK:
109                 ('POST', '/session/$sessionId/click'),
110             Command.W3C_ACTIONS:
111                 ('POST', '/session/$sessionId/actions'),
112             Command.W3C_CLEAR_ACTIONS:
113                 ('DELETE', '/session/$sessionId/actions'),
114             Command.DOUBLE_CLICK:
115                 ('POST', '/session/$sessionId/doubleclick'),
116             Command.MOUSE_DOWN:
117                 ('POST', '/session/$sessionId/buttondown'),
118             Command.MOUSE_UP:
119                 ('POST', '/session/$sessionId/buttonup'),
120             Command.MOVE_TO:
121                 ('POST', '/session/$sessionId/moveto'),
122             Command.GET_WINDOW_SIZE:
123                 ('GET', '/session/$sessionId/window/$windowHandle/size'),
124             Command.SET_WINDOW_SIZE:
125                 ('POST', '/session/$sessionId/window/$windowHandle/size'),
126             Command.GET_WINDOW_POSITION:
127                 ('GET', '/session/$sessionId/window/$windowHandle/position'),
128             Command.SET_WINDOW_POSITION:
129                 ('POST', '/session/$sessionId/window/$windowHandle/position'),
130             Command.SET_WINDOW_RECT:
131                 ('POST', '/session/$sessionId/window/rect'),
132             Command.GET_WINDOW_RECT:
133                 ('GET', '/session/$sessionId/window/rect'),
134             Command.MAXIMIZE_WINDOW:
135                 ('POST', '/session/$sessionId/window/$windowHandle/maximize'),
136             Command.W3C_MAXIMIZE_WINDOW:
137                 ('POST', '/session/$sessionId/window/maximize'),
138             Command.SET_SCREEN_ORIENTATION:
139                 ('POST', '/session/$sessionId/orientation'),
140             Command.GET_SCREEN_ORIENTATION:
141                 ('GET', '/session/$sessionId/orientation'),
142             Command.SINGLE_TAP:
143                 ('POST', '/session/$sessionId/touch/click'),
144             Command.TOUCH_DOWN:
145                 ('POST', '/session/$sessionId/touch/down'),
146             Command.TOUCH_UP:
147                 ('POST', '/session/$sessionId/touch/up'),
148             Command.TOUCH_MOVE:
149                 ('POST', '/session/$sessionId/touch/move'),
150             Command.TOUCH_SCROLL:
151                 ('POST', '/session/$sessionId/touch/scroll'),
152             Command.DOUBLE_TAP:
153                 ('POST', '/session/$sessionId/touch/doubleclick'),
154             Command.LONG_PRESS:
155                 ('POST', '/session/$sessionId/touch/longclick'),
156             Command.FLICK:
157                 ('POST', '/session/$sessionId/touch/flick'),
158             Command.EXECUTE_SQL:
159                 ('POST', '/session/$sessionId/execute_sql'),
160             Command.GET_LOCATION:
161                 ('GET', '/session/$sessionId/location'),
162             Command.SET_LOCATION:
163                 ('POST', '/session/$sessionId/location'),
164             Command.GET_APP_CACHE:
165                 ('GET', '/session/$sessionId/application_cache'),
166             Command.GET_APP_CACHE_STATUS:
167                 ('GET', '/session/$sessionId/application_cache/status'),
168             Command.CLEAR_APP_CACHE:
169                 ('DELETE', '/session/$sessionId/application_cache/clear'),
170             Command.GET_NETWORK_CONNECTION:
171                 ('GET', '/session/$sessionId/network_connection'),
172             Command.SET_NETWORK_CONNECTION:
173                 ('POST', '/session/$sessionId/network_connection'),
174             Command.GET_LOCAL_STORAGE_ITEM:
175                 ('GET', '/session/$sessionId/local_storage/key/$key'),
176             Command.REMOVE_LOCAL_STORAGE_ITEM:
177                 ('DELETE', '/session/$sessionId/local_storage/key/$key'),
178             Command.GET_LOCAL_STORAGE_KEYS:
179                 ('GET', '/session/$sessionId/local_storage'),
180             Command.SET_LOCAL_STORAGE_ITEM:
181                 ('POST', '/session/$sessionId/local_storage'),
182             Command.CLEAR_LOCAL_STORAGE:
183                 ('DELETE', '/session/$sessionId/local_storage'),
184             Command.GET_LOCAL_STORAGE_SIZE:
185                 ('GET', '/session/$sessionId/local_storage/size'),
186             Command.GET_SESSION_STORAGE_ITEM:
187                 ('GET', '/session/$sessionId/session_storage/key/$key'),
188             Command.REMOVE_SESSION_STORAGE_ITEM:
189                 ('DELETE', '/session/$sessionId/session_storage/key/$key'),
190             Command.GET_SESSION_STORAGE_KEYS:
191                 ('GET', '/session/$sessionId/session_storage'),
192             Command.SET_SESSION_STORAGE_ITEM:
193                 ('POST', '/session/$sessionId/session_storage'),
194             Command.CLEAR_SESSION_STORAGE:
195                 ('DELETE', '/session/$sessionId/session_storage'),
196             Command.GET_SESSION_STORAGE_SIZE:
197                 ('GET', '/session/$sessionId/session_storage/size'),
198             Command.GET_LOG:
199                 ('POST', '/session/$sessionId/log'),
200             Command.GET_AVAILABLE_LOG_TYPES:
201                 ('GET', '/session/$sessionId/log/types'),
202             Command.CURRENT_CONTEXT_HANDLE:
203                 ('GET', '/session/$sessionId/context'),
204             Command.CONTEXT_HANDLES:
205                 ('GET', '/session/$sessionId/contexts'),
206             Command.SWITCH_TO_CONTEXT:
207                 ('POST', '/session/$sessionId/context'),
208             Command.FULLSCREEN_WINDOW:
209                 ('POST', '/session/$sessionId/window/fullscreen'),
210             Command.MINIMIZE_WINDOW:
211                 ('POST', '/session/$sessionId/window/minimize')
212         }

这个类里面定义了所有的selenium操作需要的接口地址(这些接口地址全部封装在浏览器驱动程序中),那么所有的浏览器操作就是通过访问这些接口来实现的

其中 Command.GET: ('POST', '/session/$sessionId/url') 这个地址就是实现访问一个网址的url ,我们先记录一下后面有用

ok,所有的操作对应接口地址我们知道了,那么又怎样执行这些接口来达到在浏览器上实现各种操作呢?继续看紧接着接口地址定义下面的源码

 1     def execute(self, command, params):
 2         """
 3         Send a command to the remote server.
 4 
 5         Any path subtitutions required for the URL mapped to the command should be
 6         included in the command parameters.
 7 
 8         :Args:
 9          - command - A string specifying the command to execute.
10          - params - A dictionary of named parameters to send with the command as
11            its JSON payload.
12         """
13         command_info = self._commands[command]
14         assert command_info is not None, 'Unrecognised command %s' % command
15         path = string.Template(command_info[1]).substitute(params)
16         if hasattr(self, 'w3c') and self.w3c and isinstance(params, dict) and 'sessionId' in params:
17             del params['sessionId']
18         data = utils.dump_json(params)
19         url = '%s%s' % (self._url, path)
20         return self._request(command_info[0], url, body=data)
21 
22     def _request(self, method, url, body=None):
23         """
24         Send an HTTP request to the remote server.
25 
26         :Args:
27          - method - A string for the HTTP method to send the request with.
28          - url - A string for the URL to send the request to.
29          - body - A string for request body. Ignored unless method is POST or PUT.
30 
31         :Returns:
32           A dictionary with the server's parsed JSON response.
33         """
34         LOGGER.debug('%s %s %s' % (method, url, body))
35 
36         parsed_url = parse.urlparse(url)
37         headers = self.get_remote_connection_headers(parsed_url, self.keep_alive)
38         resp = None
39         if body and method != 'POST' and method != 'PUT':
40             body = None
41 
42         if self.keep_alive:
43             resp = self._conn.request(method, url, body=body, headers=headers)
44 
45             statuscode = resp.status
46         else:
47             http = urllib3.PoolManager(timeout=self._timeout)
48             resp = http.request(method, url, body=body, headers=headers)
49 
50             statuscode = resp.status
51             if not hasattr(resp, 'getheader'):
52                 if hasattr(resp.headers, 'getheader'):
53                     resp.getheader = lambda x: resp.headers.getheader(x)
54                 elif hasattr(resp.headers, 'get'):
55                     resp.getheader = lambda x: resp.headers.get(x)
56 
57         data = resp.data.decode('UTF-8')
58         try:
59             if 300 <= statuscode < 304:
60                 return self._request('GET', resp.getheader('location'))
61             if 399 < statuscode <= 500:
62                 return {'status': statuscode, 'value': data}
63             content_type = []
64             if resp.getheader('Content-Type') is not None:
65                 content_type = resp.getheader('Content-Type').split(';')
66             if not any([x.startswith('image/png') for x in content_type]):
67 
68                 try:
69                     data = utils.load_json(data.strip())
70                 except ValueError:
71                     if 199 < statuscode < 300:
72                         status = ErrorCode.SUCCESS
73                     else:
74                         status = ErrorCode.UNKNOWN_ERROR
75                     return {'status': status, 'value': data.strip()}
76 
77                 # Some of the drivers incorrectly return a response
78                 # with no 'value' field when they should return null.
79                 if 'value' not in data:
80                     data['value'] = None
81                 return data
82             else:
83                 data = {'status': 0, 'value': data}
84                 return data
85         finally:
86             LOGGER.debug("Finished Request")
87             resp.close()

可以看到主要是通过execute方法调用_request方法通过urilib3标准库向服务器发送对应操作请求地址,进而实现了浏览器各种操作

有人会问打开浏览器和操作浏览器实现各种动作是怎么关联的呢?

其实,打开浏览器也是发送请求,请求会返回一个sessionid,后面操作的各种接口地址,你也会发现接口地址中存在一个变量$sessionid,那么不难猜测打开浏览器和操作浏览器就是用过sessionid关联到一起,达到在同一个浏览器中做操作

第二步在浏览其上实现各种操作原理也完成了

模拟selenium

现在我们可以通过下面的一段代码查看一下打开浏览器和访问我的博客首页的请求参数是什么样子的


from selenium import webdriver
import logging


logging.basicConfig(level=logging.DEBUG)  # 打印源码中的日志
dr = webdriver.Chrome() # 打开浏览器driver.get("https://www.cnblogs.com/linuxchao/") # 访问我的博客首页

输出日志信息

DEBUG:selenium.webdriver.remote.remote_connection:POST http://127.0.0.1:55695/session {"capabilities": {"firstMatch": [{}], "alwaysMatch": {"browserName": "chrome", "platformName": "any", "goog:chromeOptions": {"extensions": [], "args": []}}}, "desiredCapabilities": {"browserName": "chrome", "version": "", "platform": "ANY", "goog:chromeOptions": {"extensions": [], "args": []}}}
DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): 127.0.0.1
DEBUG:urllib3.connectionpool:http://127.0.0.1:55695 "POST /session HTTP/1.1" 200 830
DEBUG:selenium.webdriver.remote.remote_connection:Finished Request
DEBUG:selenium.webdriver.remote.remote_connection:POST http://127.0.0.1:51006/session/09d52393b7dfcb45b8bb9101885ce206/url {"url": "https://www.cnblogs.com/linuxchao/", "sessionId": "09d52393b7dfcb45b8bb9101885ce206"}DEBUG:urllib3.connectionpool:http://127.0.0.1:51006 "POST /session/09d52393b7dfcb45b8bb9101885ce206/url HTTP/1.1" 200 72DEBUG:selenium.webdriver.remote.remote_connection:Finished Request
Process finished with exit code 0

通过执行结果就很明显明白selenium执行的过程了,程序告诉RemoteWebDriver打开一个浏览器(发送post请求,带上请求参数),然后再向remote server发送执行浏览器动作的请求

那么为了更加深入理解selenium实现自动化测试的过程,我们可以自己编写程序模拟一下打开浏览器然后控制浏览器访问我的博客地址的操作过程

首先我们需要保持浏览器的驱动程序打开状态,然后编写如下代码并执行


import requests


# 请求地址(打开浏览器)
driver_url = 'http://localhost:9515/session'
# 打开浏览器的请求参数
driver_value = {"capabilities":
                    {"firstMatch": [{}],
                     "alwaysMatch":
                         {"browserName":
                              "chrome",
                          "platformName": "any",
                          "goog:chromeOptions":
                              {"extensions": [], "args": []}}},
                "desiredCapabilities":
                    {"browserName":
                         "chrome",
                     "version": "",
                     "platform": "ANY",
                     "goog:chromeOptions": {"extensions": [],
                                            "args": []}}}
# 发送求清
response_session = requests.post(driver_url, json = driver_value)
print(response_session.json())
# 访问我的博客的请求地址 (这个地址是我们上面记录的地址)
url = 'http://localhost:9515/session/'+response_session.json()['sessionId']+'/url'
# 访问我的博客的请求参数
value = {"url": "https://www.cnblogs.com/linuxchao/", "sessionId": response_session.json()['sessionId']}
response_blog = requests.post(url = url,json = value)
print(response_blog.json())

执行结果

{'sessionId': '25144efef880dcce53e4e6f60c342e9d', 'status': 0, 'value': {'acceptInsecureCerts': False, 'acceptSslCerts': False, 'applicationCacheEnabled': False, 'browserConnectionEnabled': False, 'browserName': 'chrome', 'chrome': {'chromedriverVersion': '2.39.562718 (9a2698cba08cf5a471a29d30c8b3e12becabb0e9)', 'userDataDir': 'C:\Users\v-xug\AppData\Local\Temp\scoped_dir9944_25238'}, 'cssSelectorsEnabled': True, 'databaseEnabled': False, 'handlesAlerts': True, 'hasTouchScreen': False, 'javascriptEnabled': True, 'locationContextEnabled': True, 'mobileEmulationEnabled': False, 'nativeEvents': True, 'networkConnectionEnabled': False, 'pageLoadStrategy': 'normal', 'platform': 'Windows NT', 'rotatable': False, 'setWindowRect': True,'takesHeapSnapshot': True, 'takesScreenshot': True, 'unexpectedAlertBehaviour': '', 'version': '75.0.3770.100', 'webStorageEnabled': True}}
{'sessionId': '25144efef880dcce53e4e6f60c342e9d', 'status': 0, 'value': None}

Process finished with exit code 0

上面的返回信息中最重要的信息是'sessionId': '25144efef880dcce53e4e6f60c342e9d',从代码中你也可以看到访问我的博客地址的url是使用这个参数拼接的,因为打开浏览器后,后面所有的操作都是基于这个sessionid的

你还会看到Chrome浏览器被打开,且打开了我的博客地址https://www.cnblogs.com/linuxchao/,这就是selenium原理的一个过程了

最后

前面的代码你看不懂,也没关系,我们再来叙述一下selenium工作的过程

1.selenium client(python等语言编写的自动化测试脚本)初始化一个service服务,通过Webdriver启动浏览器驱动程序chromedriver.exe

2.通过RemoteWebDriver向浏览器驱动程序发送HTTP请求,浏览器驱动程序解析请求,打开浏览器,并获得sessionid,如果再次对浏览器操作需携带此id

3.打开浏览器,绑定特定的端口,把启动后的浏览器作为webdriver的remote server

3.打开浏览器后,所有的selenium的操作(访问地址,查找元素等)均通过RemoteConnection链接到remote server,然后使用execute方法调用_request方法通过urlib3向remote server发送请求

4.浏览器通过请求的内容执行对应动作

5.浏览器再把执行的动作结果通过浏览器驱动程序返回给测试脚本

phpstudy启动

MySQL启动不了

右下角海豚图标,右键,选择第二个功能,选择STOP

Apache启动不了

点击其他选项菜单
选择打开文件位置
选择Apache
进入bin目录
在地址栏输入cmd,回车
输入httpd.exe回车
根据报错信息修改对应的文件

Apache启动后无页面

点击其他选项菜单
选择phpstudy设置
选择端口常规设置
调整网站目录为WWW目录(确保里面有内容)
点击应用
重启服务

unittest

使用

定义测试用例

# 引入模块
import unittest

#定义一个类,继承unittest.TestCase
class TestLogin(unittest.TestCase):
    # 定义测试用例函数,函数以test开头
    def test_login(self):
        assert 1 == 2

运行测试用例

通过unittest.main()函数运行

if __name__ == '__main__':
    unittest.main()

在终端通过命令运行

python -m unittest demo_test.py

pycharm中,会自动识别unittest的测试用例,右键会有使用unittest运行

注意:当右键使用unittests运行时,非测试用例类的代码不会运行,如if _ name_ =='__main__'中的代码

运行的顺序

默认根据测试用例函数名的ASCII码排序运行
可通过suite,按照添加顺序指定运行测试用例

运行的结果

unittest的四大组件

test fixture:测试脚手架,测试代码的运行环境,指测试准备前和执行后要做的工作,包括setUp和tearDown
TestCase:测试案例,所有测试用例的基类,它是软件测试中最基本的组成单元
TestSuite:测试套件,测试案例的集合
test runner:执行测试,测试用例的执行,TextTestRunner

unittest的属性

['BaseTestSuite', 'FunctionTestCase', 'SkipTest', 'TestCase', 'TestLoader', 'TestProgram', 'TestResult', 'TestSuite', 'TextTestResult', 'TextTestRunner', '_TextTestResult', '__all__', '__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', '__unittest', 'case', 'defaultTestLoader', 'expectedFailure', 'findTestCases', 'getTestCaseNames', 'installHandler', 'loader', 'main', 'makeSuite', 'registerResult', 'removeHandler', 'removeResult', 'result', 'runner', 'signals', 'skip', 'skipIf', 'skipUnless', 'suite', 'util']

unittest.TestCase

TestCase类,所有测试用例类继承的基本类

class BaiduTest(unittest.TestCase):

unittest.main()

运行单元测试模块
使用TestLoader类来搜索所有包含在该模块中,以test命名开头的测试方法,并执行
执行顺序:根据ASCII码的顺序执行

unittest.TestSuite()

创建测试套件

import unittest

class BaiduTest(unittest.TestCase):
    def test_login(self):
        pass

# 实例化测试套件
suite = unittest.TestSuite()
# 添加测试用例,添加BaiduTest测试用类里面的test_login测试用例
suite.addTest(BaiduTest('test_login'))
suite.addTests([BaiduTest('test_login')])

unittest.TextTestRunner()

通过调用该类的run()方法运行suite所组装的测试用例
入参是suit测试套件

# 实例化一个对象
runner = unittest.TextTestRunner()
# 通过run方法执行测试套件
runner.run(suite)

unittest.defaultTestLoader()

通过该类下面的discover()方法,可以自动查找测试目录start_dir下所有指定的测试用例文件pattern = 'test_*.py',将查找到测试用例添加到测试套件

discover = unittest.defaultTestLoader.discover(test_dir,pattern='test_*.py')

可直接通过TextTestRunner()run()方法直接执行discover

unittest.skip()

装饰器,可用来屏蔽测试用例
unittest.skip(reason) :无条件跳过测试用例,并说明跳过原因
unittest.skipIf(condition,reason):条件为真时,跳过测试用例,并说明跳过原因
unittest.skipUnless(condition,reason) :条件为假是,跳过测试用例,并说明跳过原因
unittest.expectedFailure():将测试用例标记为失败

TestCase类的属性

['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__eq__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_addSkip', '_baseAssertEqual', '_classSetupFailed', '_deprecate', '_diffThreshold', '_formatMessage', '_getAssertEqualityFunc', '_truncateMessage', 'addCleanup', 'addTypeEqualityFunc', 'assertAlmostEqual', 'assertAlmostEquals', 'assertDictContainsSubset', 'assertDictEqual', 'assertEqual', 'assertEquals', 'assertFalse', 'assertGreater', 'assertGreaterEqual', 'assertIn', 'assertIs', 'assertIsInstance', 'assertIsNone', 'assertIsNot', 'assertIsNotNone', 'assertItemsEqual', 'assertLess', 'assertLessEqual', 'assertListEqual', 'assertMultiLineEqual', 'assertNotAlmostEqual', 'assertNotAlmostEquals', 'assertNotEqual', 'assertNotEquals', 'assertNotIn', 'assertNotIsInstance', 'assertNotRegexpMatches', 'assertRaises', 'assertRaisesRegexp', 'assertRegexpMatches', 'assertSequenceEqual', 'assertSetEqual', 'assertTrue', 'assertTupleEqual', 'assert_', 'countTestCases', 'debug', 'defaultTestResult', 'doCleanups', 'fail', 'failIf', 'failIfAlmostEqual', 'failIfEqual', 'failUnless', 'failUnlessAlmostEqual', 'failUnlessEqual', 'failUnlessRaises', 'failureException', 'id', 'longMessage', 'maxDiff', 'run', 'setUp', 'setUpClass', 'shortDescription', 'skipTest', 'tearDown', 'tearDownClass']

setUp()

用于测试用例执行前的初始化工作,比如连接数据库,实例化浏览器等

tearDown()

用于测试用例执行后的善后工作,如关闭数据库连接,关闭浏览器

import unittest

class Baidu(unittest.TestCase):
    # setUpClass的作用域是类,在测试用例开始执行前执行,同一个类,只会执行一次
    @classmethod
    def setUpClass(cls):
        pass
    # tearDownClass的作用域是类,在测试用例全部执行结束后执行,同一个类,只会执行一次
    @classmethod
    def tearDownClass(cls):
        pass
    
    # setUp的作用域是单个测试用例,每个测试用例执行前,都先执行它
    def setUp(self):
        pass
    
    # tearDown的作用域是单个测试用例,每个测试用执行结束后执行
    def tearDown(self):
        pass

assert*()系列

断言方法,用来判断用例的实际结果时候与预期结果相符合

方法

说明

assertEqual(a,b[,msg])

断言a和b是否相等,相等则通过,不相等则失败,并展示失败信息msg

assertNotEqual(a,b[,msg])

assertTrue(x[,msg])

断言x是否为True

assertFalse(x,[,msg])

assertIs(a,b[,msg])

断言a是否是b

assertNotIs(a,b[,msg])

assertIsNone(x[,msg])

断言x是否是None

assertIsNotNone(x[,msg])

assertIn(a,b[,msg])

断言a是否在b中

assertNotIn(a,b[,msg])

assertIsInstance(a,b[,msg])

断言a是否是b的一个实例

assertIsInstance(a,b[,msg])

TestSuite类的属性

['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__eq__', '__format__', '__getattribute__', '__hash__', '__init__', '__iter__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_addClassOrModuleLevelException', '_get_previous_module', '_handleClassSetUp', '_handleModuleFixture', '_handleModuleTearDown', '_tearDownPreviousClass', '_tests', 'addTest', 'addTests', 'countTestCases', 'debug', 'run']

addTest()

一次添加单个测试用例到测试套件中,按添加顺序执行测试用例

#实例化测试套件
suite = unittest.TestSUite()
#将test_baidu模块下BaiduTest类中的test_baidu测试用例添加到测试套件中
suite.addTest(test_baidu.BaiduTest('test_baidu'))

addTests()

一次添加多个测试用例到测试套件中

suite.addTests([BaiduTest('test_baidu1'),BaiduTest('test_baidu2')])

TextTestRunner类的属性

['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_makeResult', 'buffer', 'descriptions', 'failfast', 'resultclass', 'run', 'stream', 'verbosity']

run()

运行测试套件

runner = unittest.TextTestRunner()
runner.run(suite)

编写测试用例思路

编写用例

#1.导入unittest模块
import unittest
#2.定义测试类,父类为unittest.TestCase。
#可继承unittest.TestCase的方法,如setUp和tearDown方法,不过此方法可以在子类重写,覆盖父类方法。
#可继承unittest.TestCase的各种断言方法。
class Test(unittest.TestCase): 
    
#3.定义setUp()方法用于测试用例执行前的初始化工作。
#注意,所有类中方法的入参为self,定义方法的变量也要“self.变量”
#注意,输入的值为字符型的需要转为int型
    def setUp(self):
        self.number=raw_input('Enter a number:')
        self.number=int(self.number)
#4.定义测试用例,以“test_”开头命名的方法
#注意,方法的入参为self
#可使用unittest.TestCase类下面的各种断言方法用于对测试结果的判断
#可定义多个测试用例
#最重要的就是该部分
    def test_case1(self):
        print self.number
        self.assertEqual(self.number,10,msg='Your input is not 10')
        
    def test_case2(self):
        print self.number
        self.assertEqual(self.number,20,msg='Your input is not 20')
    @unittest.skip('暂时跳过用例3的测试')
    def test_case3(self):
        print self.number
        self.assertEqual(self.number,30,msg='Your input is not 30')
#5.定义tearDown()方法用于测试用例执行之后的善后工作。
#注意,方法的入参为self
    def tearDown(self):
        print 'Test over'

执行用例示例1

#如果直接运行该文件(__name__值为__main__),则执行以下语句,常用于测试脚本是否能够正常运行
if __name__=='__main__':
#1执行测试用例方案一如下:
#unittest.main()方法会搜索该模块下所有以test开头的测试用例方法,并自动执行它们。
#执行顺序是命名顺序:先执行test_case1,再执行test_case2
    unittest.main()

执行用例示例2

#2执行测试用例方案二如下:
#2.1先构造测试集
#2.1.1实例化测试套件
    suite=unittest.TestSuite()
#2.1.2将测试用例加载到测试套件中。
#执行顺序是安装加载顺序:先执行test_case2,再执行test_case1
    suite.addTest(Test('test_case2'))
    suite.addTest(Test('test_case1'))
#2.2执行测试用例
#2.2.1实例化TextTestRunner类
    runner=unittest.TextTestRunner()
#2.2.2使用run()方法运行测试套件(即运行测试套件中的所有用例)
    runner.run(suite)

执行用例示例3

#3执行测试用例方案三如下:
#3.1构造测试集(简化了方案二中先要创建测试套件然后再依次加载测试用例)
#执行顺序同方案一:执行顺序是命名顺序:先执行test_case1,再执行test_case2
    test_dir = './'
    discover = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py')
#3.2执行测试用例
#3.2.1实例化TextTestRunner类
    runner=unittest.TextTestRunner()
#3.2.2使用run()方法运行测试套件(即运行测试套件中的所有用例)
    runner.run(discover)

HTMLTestRunner报告

from HTMLTestReportCN import HTMLTestRunner
f = open('result.html','w',encoding='utf-8')
runner = HTMLTestRunner(stream=f,title='测试报告',tester='lisi',description='这是描述')
runner.run(suit)
f.close()

Web自动化测试框架

数据驱动

安装ddt

pip install ddt

使用

import ddt
import unittest
@ddt.ddt
class LoginTest(unittest.TestCase):
    def setUp(self):
        pass
    
    def tearDown(self):
        pass
    
    @ddt.data((username1,password1,msg1),(username2,password2,msg2))
    @ddt.unpack  # 如果测试用例接收的是一个入参,则不需要解包
    def test_login(self,username,password,msg):
        pass

读取数据

数据存放在数据库

利用pymysql读取存放在mysql数据库的数据
pip install pymysql

import pymysql

# 连接数据库
con = pymysql.connect(host='127.0.0.1',port=3306,user='root',password='root',db='test',charset='utf8')

# 获取游标,后续的操作都是通过游标
cursor = con.cursor()

# result,只会展示执行的结果数
result = cursor.execute('show tables')

# 读取具体的数据
data = cursor.fetchall() #输出所有数据
data = cursor.fetchone() # 输出一条数据
data = cursor.fetchmany(2) # 输出指定条数的数据

try:
    cursor.execute('insert into ceshi values(1,"zhangsan",20)')
    # 提交操作,涉及到对数据库的数据进行改动时,需要提交
    con.commit()
except:
    # 如果发生错误,数据库回滚
    con.rollback()

con.close()

数据存放在yaml文件中

yaml数据的格式,参见

此处为语雀内容卡片,点击链接查看:yaml · 语雀

POM模型

page object module 把页面当做类的对象进行使用,实现页面和测试用例的分离

1. page

页面文件夹,需要测试的页面,都做成类,一个页面一个.py文件,同时对应一个类
类里面,实现页面的功能函数,页面所有需要测试的功能,都定义成函数
其他地方如果需要用到,导入过去

# login_page.py
class LoginPage():
    # 要实现的页面的功能,需要查找节点,节点需要通过webdriver对象查找,
    # 因此在示例化页面对象时,需要传一个webdriver对象
    def __init__(self,driver):
        self.driver = driver
    
    def login(self,user,pwd):
        '''
        登录页面的登录功能,主要就是实现登录
        '''
        pass
    
    def register(self,user,pwd,tel,code):
        '''
        登录页面的注册功能,主要就是实现注册
        '''
        pass
    
    def find_element_by_text(self,text):
        try:
            self.driver.find_element_by_xpath(f'//*[text()="{text}"]')
            return True
        except:
            self.driver.get_screenshot_as_file(f'{text}.png')
            return False
        
  

2. testcase

测试用例文件夹,里面主要存放测试用例
有一个要测的页面,就定义一个测试用例
页面跟测试用例一一对应

# test_login.py
import unittest
from selenium import webdriver
import ddt

from page.login_page import LoginPage

@ddt.ddt
class TestLogin(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.driver = webdriver.Firefox()
        cls.driver.maximize_window()
        cls.driver.get(url)
        cls.driver.implicitly_wait(10)
        cls.login_page = LoginPage(cls.driver)
        
    @classmethod
    def tearDownClass(cls):
        cls.driver.quit()
    
    @ddt.data((user1,pwd1,msg1),(user2,pwd2,msg2))
    # @ddt.file_data('yaml数据文件所在路径')
    @ddt.unpack
    def test_login(self,user,pwd,msg):
        # 测试登录功能
        self.login_page.login(user,pwd)
        # 对结果断言
        result = self.login_page.find_element_by_text(msg)
        self.assertTrue(result,f'未出现预期的【msg】标签,测试用例未通过')   
    
    def test_register(self):
        pass

3. main.py

项目入口
里面运行项目

# main.py
import unittest

if __name__ == '__main__':
    # 加载testcase目录下所有满足以test开头的.py文件里面所有的测试用例
    discover = unittest.defaultTestLoader.discover(r'./testcase','test*.py')
    runner = unittets.TextTestRunner()
    runner.run(discover)

4. common

公共方法目录
里面存放一些公用的方法或工具

# tools.py
import pymysql
import yaml
import os

def get_data_from_sql(table):
    con = pymysql.connect(host='127.0.0.1',user='root',password='root',db='test',charset='utf8')
    cursor = con.cursor()
    cursor.execute(f'select * from {table}')
    data = cursor.fetchall()
    return map(lambda x:x[1:],data)

def get_data_from_yaml(filename):
    with open(filename,encoding='utf-8') as f:
        return yaml.load(f,Loader=yaml.FullLoader)

PATH = lambda file:os.path.abspath(os.path.join(os.path.dirname(__file__),file))

5. data

里面存放着数据驱动需要的数据

数据可以是yaml格式、或者是excel等等

如果yaml,且数据驱动采用的是ddt.file_data,需要留意数据格式,列表里面嵌套字典

- user: admin1
  pwd: admin1
  msg: 密码错误
- user: admin
  pwd: admin
  msg: 退出1

6. conf

里面存放着配置文件,可以是yaml格式,也可以是ini格式

全局的账号密码,成功登录的用户名和密码
mysql的一些属性,mysql服务器的基本信息
测试用例的一些运行状态,控制测试用例是跳过还是运行
使用的浏览器,通过关键字设定实例化哪个浏览器

BaseTest:
  browser: firefox
  url: http://127.0.0.1/admin.php
TestLogin:
  module: False
  test_login: True
TestClassify:
  module: True
  test_add_menu: True

7. result

存放测试用例运行报告、日志等信息

HTMLTestReportCN
用法

from HTMLTestReportCN import HTMLTestRunner
with open('result.html','w',encoding='utf-8') as f:
    runner = HTMLTestRunner(stream=f,title='标题',tester='作者',descript='描述')
    runner.run(suite)

loggin

import logging

# 通过level调整日志的级别,默认为WARNING级别
# 通过format 调整日志格式
# 通过datefmt 调整时间格式
# 通过filename 指定日志写入的文件
# 通过filemode 指定日志写入文件的格式
logging.basicConfig(level=logging.DEBUG,
                    filename='rizhi.log',
                    datefmt='%m-%d %H:%M:%S',
                    filemode='w',
                    format='%(asctime)s  %(filename)s  %(lineno)d  %(levelname)s  %(message)s')

logging.debug('这是一条debug级别的日志')
logging.info('这是一条info级别的日志')
logging.warning('这是一条warining级别的日志')
logging.error('这是一条error级别的日志')
logging.critical('这是一条critical级别的日志')


'''
format 参数
%(levelno)s:    打印日志级别的数值
%(levelname)s:  打印日志级别名称
%(pathname)s:   打印当前执行程序的路径,其实就是sys.argv[0]
%(filename)s:   打印当前执行程序名
%(module)s:     模块名
%(funcName)s:   打印日志的当前函数
%(lineno)d:     打印日志的当前行号
%(asctime)s:    打印日志的时间
%(thread)d:     打印线程ID
%(threadName)s: 打印线程名称
%(process)d:    打印进程ID
%(message)s:    打印日志信息
'''

pymysql

1. 安装pymysql及基本使用

# 安装  pip install pymysql

# 基本使用
import pymysql
# 连接数据库
db = pymysql.connect(host='localhost',user='root',password='123',port=3306,charset='utf8',db='ceshi') #链接数据库
cursor = db.cursor()  #获得数据库的游标
cursor.execute('select version();') #执行sql语句,查看版本
data = cursor.fetchone() #查看第一条数据
print(data)
cursor.execute('create database spiders default character set utf8;') #执行sql语句,创建数据库
db.close() #关闭链接


#优化,设置通用格式
db_config = {
    'host':'localhost',
    'user':'root',
    'password':'123',
    'port':3306,
    'db':'spiders',
    'charset':'utf8'
}
db = pymysql.connect(**db_config)
cursor = db.cursor()

2. 创建表

createsql = '''
create table zengchao2(
id int not null,
name varchar(50),
age int,
primary key (id))
'''

db = pymysql.connect(host='localhost',user='root',password='123',port=3306,db='spiders',charset='utf8')
cursor = db.cursor()
sql = "create table if not exists ceshi (id int not null,name varchar(255),age int ,primary key (id))"
cursor.execute(sql)
cursor.close()

3. 插入数据


#插入数据
db = pymysql.connect(host='localhost',user='root',password='123',port=3306,db='spiders',charset='utf8')
cursor = db.cursor()
id = '2007';user='bob';age=20 #要插入的数据
sql = "insert into ceshi(id,name,age) values(%s,%s,%s) "
try:
    cursor.execute(sql,(id,user,age))
    db.commit() #更新表
except:
    db.rollback() #数据库回滚
db.close()

# 插入数据
data = {
    'id':'2009',
    'name':'李四',
    'age':23
}
table='ceshi'
keys = ','.join(data.keys())
values = ','.join(['%s']*len(data))
insertsql = "insert into {}({}) values({})".format(table,keys,values)
print(insertsql)
try:
    if cursor.execute(insertsql,tuple(data.values())):
        print('Successful')
        db.commit()
except:
    print('Failed')
    db.rollback()
db.close()

4. 更新数据


#更新数据
db = pymysql.connect(**db_config)
cursor = db.cursor()
sql = 'update ceshi set age = %s where name = %s'
try:
    cursor.execute(sql,(21,'张三'))
    print('Successful')
    db.commit()
except:
    print('Failed')
    db.rollback()

#优化更新
db = pymysql.connect(**db_config)
cursor = db.cursor()
data = {
    'id':'2012',
    'name':'李四',
    'age':25
}
table='ceshi'
keys = ','.join(data.keys())
values = ','.join(['%s']*len(data))
sql = "insert into {}({}) values({}) on duplicate key update ".format(table,keys,values)
update = ','.join(["{}=%s".format(key) for key in data.keys()])
sql += update
try:
    if cursor.execute(sql,tuple(data.values())*2):
        print('Successful')
        db.commit()
except:
    print('Failed')
    db.rollback()

5. 删除数据

#删除数据
condition = "age = 21"
sql = "delete from {} where {}".format(table,condition)
try:
    if cursor.execute(sql):
        print('Successful')
        db.commit()
except:
    print('Failed')
    db.rollback()
db.close()

6. 查询数据

#查询数据
sql = "select * from ceshi where age=25"
try:
    cursor.execute(sql)
    print(cursor.rowcount)
    one = cursor.fetchone()
    print(one)
    results = cursor.fetchall() #数据量大的时候,不建议直接使用,可以用while fetchone
    print(results)
    print(type(results))
    for row in results:
        print(row)
except:
    print('Error')

'''
row = cursor.fetchone()
while row:
    row = cursor.fetchone()
'''

yaml

安装:pip install pyyaml
新建一个yaml类型文件:.yaml .yml
写数据:

整型,直接写:5
浮点型,直接写:5.0
字符串,数值型,用引号包起来,文本可以直接写:'5'、zhangsan
布尔,直接写:True False
空值:null
列表,用 – 符号声明,每个元素前面都需要一个 – :

#[1,2,3]
- 1
- 2
- 3
# [1,2,3]
[1,2,3]

字典,用 :符号分隔键和值,key: value,注意value前面有个空格:
数据采用缩进表示层级

#{'a':1,'b':2}
a: 1
b: 2
# {'a':[1,2],'b':{'c':3}}
a:
  - 1
  - 2
b:
  c: 3
# 还可以直接按照python的格式写

读数据

import yaml
f = open('abc.yaml')
data = yaml.load(f,Loader=yaml.FullLoader)
f.close()

存数据

data = {'a':[1,2,3]}
f = open('abc.yaml','w')
yaml.dump(data,f)
f.close()

ddt使用yaml

使用yaml.load()把数据加载出来,然后ddt.data()
用ddt.file_data(yaml文件的路径)
需要注意yaml数据的格式列表配字典

# 键的个数和名称要与测试用例的入参相对应
- a: 1
  b: 2
- a: 3
  b: 4

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容