
Selenium可以自动化操作浏览器,例如:选择元素,输入,点击等,可以用于软件自动化测试,爬虫等工作,也可以做你想做的任何事情。
本文环境: Python3.12,Windows10,Selenium 4.15.2,Chrome 119.0.6045.160
原理
WebDriver是一套标准API协议,由Selenium提出,W3C将其作为推荐标准,用于描述对UI界面的操作指令,使得客户端使用同样的函数就能操作不同的浏览器。自动化框架Appium也是基于WebDriver协议传输指令。

Selenium各种编程语言的lib库(客户端)使用WebDriver协议,向Driver程序传输控制指令,各种浏览器都有自己对应的Driver程序,Driver程序实际上就是把WebDriver 指令翻译成实际的浏览器控制命令。
安装
一、安装Chrome
方法1.安装标准版Chrome(如果系统中已安装可跳过)
官网下载安装标准版(https://www.google.com/chrome/),Selenium会自动在PATH路径寻找系统安装的浏览器,并自动下载对应的Driver程序。
方法2.安装测试专用版
去chromiun官网(https://chromedriver.chromium.org/downloads) 下载各平台对应的测试专用版本Chrome和对应驱动,解压后放到项目路径。此种方式需要初始化驱动时配置浏览器和驱动的路径,后文会有介绍。
二、安装Python
去官网下载安装即可 https://www.python.org/downloads/windows/
可以自定义安装位置和特性。

注:安装给所有人,默认安装到系统路径(C:\Program Files\)下,有些库文件会分散安装到系统其他路径。
安装给当前用户,默认安装到路径(C:\Users\username\AppData\Local\Programs\)下。所有文件都会安装到一个文件夹下,把这个目录复制到别处,只需自己配好PATH路径就能继续使用。
注:由于Python3.12的基础库有些变动,如果你使用Pycharm请升级其到最新版,否则可能管理不了虚拟环境。
三、安装selenium库
pip install selenium示例:访问百度
import timefrom selenium import webdriverfrom selenium.webdriver.common.by import Bydriver = webdriver.Chrome()driver.get("https://www.baidu.com/")print(driver.title)input = driver.find_element(by=By.ID, value="kw")input.send_keys('abc')submit = driver.find_element(by=By.XPATH, value='//*[@id="su"]')submit.click()time.sleep(300)
第一次运行可能要等一段挺长的时间,之后会自动打开页面输入内容,点击搜索:

初始化driver
以下初始化参数请按需选择
from selenium import webdriveroptions = webdriver.ChromeOptions()options.page_load_strategy = 'normal' # 网页加载模式:等待所有资源都被下载options.binary_location = r".\chrome-win64\chrome.exe" # 指定被操作的浏览器程序路径# headless模式需要的三个配置,不需要可以注释掉options.add_argument("--headless") # 无界面模式options.add_argument("--disable-gpu") # headless模式下,windows系统可能需要options.add_argument('--window-size=1920,1080') # 缺少此配置,headless模式可能点击不到元素options.add_argument("--start-maximized") # 启动时最大化窗口options.add_argument(f"--force-device-scale-factor={1}") # 缩放后的高分屏截屏模糊,需要恢复options.add_argument("--test-type=gpu") # 关掉提示:“Chrome测试版 仅适用于自动测试”options.add_experimental_option("detach", True) # driver退出,但保留浏览器(注意不要执行driver.quit())switches = ['enable-automation', # 关掉提示:“Chrome正受到自动测试软件的控制”'enable-logging', # 关闭浏览器日志]options.add_experimental_option('excludeSwitches', switches)prefs = {"credentials_enable_service": False, # 禁止保存密码弹窗"profile.password_manager_enabled": False, # 禁止保存密码弹窗}options.add_experimental_option("prefs", prefs)service = webdriver.ChromeService(service_args=["--verbose", "--log-path=.\\qc1.log"], # chromedriver的日志executable_path=r".\chromedriver-win64\chromedriver.exe" # chromedriver程序路径)driver = webdriver.Chrome(options=options, service=service)driver.implicitly_wait(50) # 查找元素标签时,等待时长
注:浏览器启动后,可以在地址栏访问:chrome://version/ 查看启动参数,需要屏蔽的默认参数可以放到excludeSwitches中。
注:如果想用detach True参数,在Pycharm中不要直接Run,要在Terminal标签,或系统cmd命令行运行。或者excludeSwitches中包含'enable-logging'确保浏览器不输出日志。
注:关于指定所用的浏览器和web驱动
在4.10.0版本后,selenium包含了一个Selenium Manager工具,会自动探测系统上安装的浏览器位置,自动下载对应的web驱动程序,这种情况下也可以删除上文中这两个配置:上文的百度示例就是没有这两个配置,第一次运行自动下载驱动消耗了一段时间。
# 指定浏览器options.binary_location = r".\chrome-win64\chrome.exe"# 指定web驱动service = webdriver.ChromeService(executable_path=r".\chromedriver-win64\chromedriver.exe")
现在我也找了很多测试的朋友,做了一个分享技术的交流群,共享了很多我们收集的技术文档和视频教程。
如果你不想再体验自学时找不到资源,没人解答问题,坚持几天便放弃的感受
可以加入我们一起交流。而且还有很多在自动化,性能,安全,测试开发等等方面有一定建树的技术大牛
分享他们的经验,还会分享很多直播讲座和技术沙龙
可以免费学习!划重点!开源的!!!
qq群号:691998057【暗号:csdn999】
打开网页
打开网页,selenium只有get方法,其他方法只能操作浏览器,由浏览器或js脚本发送。
driver.get('https://www.w3schools.com/js/default.asp')# driver.refresh() 刷新页面,如果链接未变,重复get无用,可以刷新。
网页操作简单来说就分两步:1. 定位元素 2.执行操作。
from selenium.webdriver.common.by import Byaccept_button = driver.find_element(by=By.XPATH,value='//*[@id="accept-choices"]')accept_button.click()
元素定位
主要通过find_element()函数进行,有两个参数,by参数选择定位方式;value参数输入定位方式对应的值。
其他定位方式:
By.XPATH:使用XPATH定位: '//*[@id="accept-choices"]'。
By.TAG_NAME:使用tag名定位: 即h1,div,a等。
By.ID: 使用ID属性:<div id="content"></div>
By.NAME: 使用name属性:<div name="content"></div>
By.CSS_SELECTOR: css选择器:"#book"
By.CLASS_NAME:类名:<div class="content"></div>
By.LINK_TEXT:通过链接文字:<a href="continue.html">Continue</a>
By.PARTIAL_LINK_TEXT:部分链接文字:上例中只写Cont
  
定位元素不只针对窗口,也可以从已经定位的元素开始,driver.find_element() 返回的是一个 WebElement,它也有find_element()方法
table = driver.find_element(by=By.ID, value="table")t_body = table.find_element(by=By.TAG_NAME, value="tbody")t_tr = t_body.find_elements(by=By.TAG_NAME, value='tr')
元素操作
# 按钮链接等button.click()# 文本框username_box.send_keys(username)# select元素from selenium.webdriver.support.ui import Selectselect_elm = driver.find_element(by=By.TAG_NAME, value='select')selecter = Select(select_elm)selecter.select_by_value("2023-12")
切换iframe和窗口
有时候你会发现,怎么也定位不到元素,这也许是因为他们处于不同的iframe或窗口,必须先切换过去才能操作。
切换iframe
<iframe id="buttonframe" name="myframe" src="https://seleniumhq.github.io"><button>Click here</button></iframe>------------------------------------# Store iframe web elementiframe = driver.find_element(By.CSS_SELECTOR, "#modal > iframe")# switch to selected iframedriver.switch_to.frame(iframe)# Switch frame by iddriver.switch_to.frame('buttonframe')# Now click on buttondriver.find_element(By.TAG_NAME, 'button').click()# switch back to default contentdriver.switch_to.default_content()
切换窗口
driver.current_window_handle # 当前窗口句柄driver.window_handles # 所有的窗口列表,可以从中取出handle# Opens a new tab and switches to new tabdriver.switch_to.new_window('tab')# Opens a new window and switches to new windowdriver.switch_to.new_window('window')driver.switch_to.window(handle) # 切换到一个窗口,driver.close() # 关闭窗口
等待(Wait)
点击了按钮或执行了某个操作后,浏览器需要一定的时间加载资源或进行计算,js也会改变DOM内容。
因此,很多时候我们需要等待一定的条件才能继续操作。
手工等待
最简单的等待可以添加time.sleep()硬性等待,短时间的等待(一两秒)是可以使用的,对于等待时长不固定,有较大概率等待时间较长的情况一般不要使用,但在遇到问题时可以临时用这种方法定位问题。
隐式等待
在初始化好driver后,使用如下语句配置
driver.implicitly_wait(20)如果没有配置默认值为0,即如果元素没有就位会立刻返回错误;如果配置了则等待对用时长后元素还没有就位才返回错误;如果元素就位会立即返回,不会浪费时间。
显式等待(最重要)
显式等待,是等待某个或某些条件的成立,条件是否成立由一个函数判断,这个函数作为参数传寄给Wait机制的until()或until_not()函数:
from selenium.webdriver.support.ui import WebDriverWaitwait = WebDriverWait(driver, timeout=20)element = wait.until(lambda d : revealed.is_displayed())wait.until_not(lambda d : revealed.is_displayed())# 还可以配置其探测间隔,忽略的异常等errors = [NoSuchElementException, ElementNotInteractableException]wait = WebDriverWait(driver, timeout=2, poll_frequency=.2, ignored_exceptions=errors)wait.until(lambda d : revealed.send_keys("Displayed") or True)
until()函数直到回调函数返回"真"值,否则抛出超时异常;until_not()函数相反,直到回调函数返回"假"值就返回否则抛出异常,另外如果遇到被忽略的异常,会返回True。
回调函数接收一个参数,即初始化wait时的driver。
def condition_fun(driver):if time.time() > 2524579200: # 2050-01-01后满足条件return Trueresp = wait.until(condition_fun)
系统预定义条件
系统预置了一些expected conditions函数,他们通过闭包的方式,生成一个符合until()函数需要的单参数函数:
from selenium.webdriver.support import expected_conditions as ECf_condition = EC.element_to_be_clickable(submit_button)wait.until(f_condition) # f_condition是一个函数
EC.element_to_be_clickable()函数会帮我们生成需要的回调函数。这个函数等待指定的元素可点击。
函数的参数指定要等待的元素,有两种类型,一种是之前见过的使用find_element找到的元素,另一种是使用一个元组,传入定位方式和定位值,被称为locator:
例:
# 传入元素submit_button = driver.find_element(by=By.XPATH, value='//*[@id="submit"]')submit_button = wait.until(EC.element_to_be_clickable(submit_button))if submit_button :submit_button.click()# 传入locatorsubmit_button = wait.until(EC.element_to_be_clickable((By.XPATH, '//*[@id="submit"]')))if submit_button :submit_button.click()
预定义的条件很多,详情见官方文档,这里简单介绍几个:
EC.alert_is_present() # 当前正弹出告警框,并返回告警框EC.element_attribute_to_include() # 元素包含某个属性EC.element_to_be_clickable() # 元素可被点击EC.visibility_of() # 元素可见EC.staleness_of() # 元素消失EC.text_to_be_present_in_element() # 元素种出现某个字符EC.title_contains(title) # 页面标题包含某字符串# 还有些条件的组合函数EC.all_of() # 所有条件都满足EC.any_of() # 任何一个条件满足EC.none_of() # 所有条件都不满足
文档:https://www.selenium.dev/selenium/docs/api/py/webdriver_support/selenium.webdriver.support.expected_conditions.html
补充:等待AJAX请求结束,并获取其内容
由于前后端分离技术,越来越多的页面并不是后端渲染完再返回显示,而是先返回一个框架,通过AJAX请求异步获取数据,再用js渲染到页面。Selenium对js的这些请求没有处理。
网络波动会对下载产生很大影响,时不时会有一个很长的等待(概率低但不是没有),如果使用sleep()手动等待,就需要等待很长时间,才能保证每一次请求都能完成。
所以如果能准确地等待一个数据请求结束,将会节省很多时间。
# 首先记得在初始化时打开performance日志options.set_capability("goog:loggingPrefs", {"performance": "ALL"})
这样就可以通过日志获得链接即将发送请求,响应以及响应结束三种状态。
def ec_request_finish(url, timestamp):"""等待ajax发起的GET或POST请求结束:param url: 请求前缀,:param timestamp: 请求开始前记录的时间,可帮助排除页面自动发起的请求,或减少处理的日志数量:return: request_id, 可以通过它获取文件内容"""request_id = ''def check_fish(driver):for log in driver.get_log('performance'):if timestamp * 1000 > log['timestamp'] or 'message' not in log:continuelog_entry = json.loads(log['message'])try:method = log_entry['message']['method']params = log_entry['message']['params']if not request_id and method in ['Network.requestWillBeSent', 'Network.responseReceived']:if method == 'Network.requestWillBeSent' and \params['request']['method'] in ['POST', 'GET'] and \url in params['request']['url']:request_id = params['requestId']if method == 'Network.responseReceived' and \params['response']['method'] in ['POST', 'GET'] and \url in params['response']['url']:request_id = params['requestId']if request_id and method in ['Network.loadingFinished'] and \request_id == params['requestId']:return request_idexcept Exception as e:passreturn check_fish# 等待请求结束request_id = wait.until(ec_request_finish('https://www.abc.com/x/y/z',0))# 获取返回的内容,此处以返回json格式字符串为例result = self.driver.execute_cdp_cmd("Network.getResponseBody", {"requestId": request_id})data = json.loads(result['body'])
浏览器操作
# 导航driver.back()driver.forward()driver.refresh()# 获取信息driver.title # 标题driver.current_url # 当前链接driver.capabilities # 查看driver的一些配置# Cookiesdriver.add_cookie({"name": "foo", "value": "bar"})driver.get_cookie("foo")driver.get_cookies() #获取所有域名的cookiesdriver.delete_cookie("foo")driver.delete_all_cookies()# 执行js脚本,有些信息可以通过js获取,当然也可以做其他事情driver.execute_script('return arguments[0].innerText', header)size = driver.get_window_size() # 获取窗口尺寸driver.set_window_size(1024, 768) # 设置窗口尺寸driver.get_window_position() # 获取窗口位置driver.set_window_position(0, 0) # 设置窗口位置driver.maximize_window() # 最大化driver.minimize_window() # 最小化driver.fullscreen_window() # 全屏显示,相当于F11
截屏
# 对窗口截屏driver.get_screenshot_as_file('./image.png') # 截屏当前窗口,gng格式driver.save_screenshot('./image.png') # 对get_screenshot_as_file()的封装driver.get_screenshot_as_png() # 对窗口截屏,返回二进制png数据driver.get_screenshot_as_base64() # 对窗口截屏,返回base64数据# 对目标元素截屏element = driver.find_element(...)element.screenshot('./image.png')element.screenshot_as_base64 # 注意不是函数,是property属性element.screenshot_as_png # 注意不是函数,是property属性
补充:截长屏的方法
一般的截屏工具截长屏是通过滚轮操作滚动条,然后不断拼接完成的。Selenium应该也可以这样操作,不断的滚动、切图、拼接。
这里分享一个更简单些的操作:设置窗口大小,使滚动条消失内容全部显示。但要注意,这种方式一般需要在headless模式下才能正常工作。
截屏浏览器:
original_size = driver.get_window_size()height = driver.execute_script('return document.documentElement.scrollHeight')width = driver.execute_script('return document.documentElement.scrollWidth')driver.set_window_size(width, height) # the trick, and must in headless modescreenshot = form.screenshot_as_base64form.screenshot('image.png')driver.set_window_size(original_size['width'], original_size['height'])
截取带滚动条的元素:
content = driver.find_element(by=By.XPATH, value='//div[@id="app-content"]')original_size = driver.get_window_size()height = content.size['height']width = content.size['width']driver.set_window_size(width, height+100) # the trick, and must in headless mode# screenshot = content.screenshot_as_base64content.screenshot('image.png')driver.set_window_size(original_size['width'], original_size['height'])
下载文件
有些网页有文档或数据导出功能,点击按钮会下载文件,可以使用的相关参数有:
prefs = {"download.default_directory": 'D:\\', # 下载文件的保存地址,一定要是绝对地址"download.prompt_for_download": False,"download.directory_upgrade": True,"profile.default_content_settings.popups": 0,}options.add_experimental_option("prefs", prefs)
default_directory一定要注意写绝对地址,其他参数是屏蔽弹窗什么的,遇到了可以试试。
有时候想直接指定文件名,暂时看是不可以的,可以下载回来后再给文件改名。
警告框操作
警告框是通过js的 alert()、 confirm()、 prompt()函数弹出的
# 使用wait等待警告框出现,并保存它alert = wait.until(EC.alert_is_present())# 或者直接获取警告框alert = driver.switch_to.alert# 警告框的文字内容text = alert.text# 按确定按钮alert.accept()# 按取消按钮alert.dismiss()# 向prompt()输入内容,作为其返回值alert.send_keys("Selenium")
设备操作
能模拟键盘,鼠标,滚轮等操作。主要通过ActionChains,ActionBuilder 操作。
通用的一些操作
.pause(3) # 暂停3秒.perform() # 执行action chain
键盘操作
from selenium.webdriver import Keys, ActionChains# 直接输入到当前活跃元素,支持链式调用ActionChains(driver)\.key_down(Keys.SHIFT)\.send_keys("abc")\.key_up(Keys.SHIFT)\.send_keys("def")\.perform()# 发送到特定元素(会自动点击传入的元素)text_input = driver.find_element(By.ID, "textInput")ActionChains(driver)\.send_keys_to_element(text_input, "abc")\.perform()# 剪切复制粘贴cmd_ctrl = Keys.COMMAND if sys.platform == 'darwin' else Keys.CONTROLActionChains(driver)\.send_keys("Selenium!")\.send_keys(Keys.ARROW_LEFT)\.key_down(Keys.SHIFT)\.send_keys(Keys.ARROW_UP)\.key_up(Keys.SHIFT)\.key_down(cmd_ctrl)\.send_keys("xvv")\.key_up(cmd_ctrl)\.perform()
鼠标操作
from selenium.webdriver import Keys, ActionChainsbutton= driver.find_element(By.ID, "button")ActionChains(driver)\.move_to_element(button) \.click() \.perform()# 移动鼠标.move_to_element(to_element).move_by_offset(xoffset, yoffset).move_to_element_with_offset(to_element, xoffset, yoffset)# 点击相关.click() # 单击.double_click() # 双击.context_click() # 右击.click_and_hold() # 单击不释放.release() # 释放按钮# 拖动.drag_and_drop(source, target) # 一个元素拖动到另一个元素的位置.drag_and_drop_by_offset(source, xoffset, yoffset) # 一个元素按像素拖动# 滚轮滚动条,如果没效果尝试用一个新的ActionChains.scroll_by_amount(delta_x, delta_y).scroll_to_element(element).scroll_from_origin(origin, delta_x, delta_y)
补充:显示鼠标指针
鼠标指针是操作系统管理的,我们可以移动浏览器的指针焦点位置,但是操做系统不会让鼠标跟着动。所以我们要想直观些看到指针(debug用,正常时候不要用),需要自己画一个:
def enable_cursor(driver):enable_cursor = """(function() {var seleniumFollowerImg = document.createElement("img");seleniumFollowerImg.setAttribute('src', 'data:image/png;base64,'+ 'iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAQAAACGG/bgAAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAA'+ 'HsYAAB7GAZEt8iwAAAAHdElNRQfgAwgMIwdxU/i7AAABZklEQVQ4y43TsU4UURSH8W+XmYwkS2I0'+ '9CRKpKGhsvIJjG9giQmliHFZlkUIGnEF7KTiCagpsYHWhoTQaiUUxLixYZb5KAAZZhbunu7O/PKf'+ 'e+fcA+/pqwb4DuximEqXhT4iI8dMpBWEsWsuGYdpZFttiLSSgTvhZ1W/SvfO1CvYdV1kPghV68a3'+ '0zzUWZH5pBqEui7dnqlFmLoq0gxC1XfGZdoLal2kea8ahLoqKXNAJQBT2yJzwUTVt0bS6ANqy1ga'+ 'VCEq/oVTtjji4hQVhhnlYBH4WIJV9vlkXLm+10R8oJb79Jl1j9UdazJRGpkrmNkSF9SOz2T71s7M'+ 'SIfD2lmmfjGSRz3hK8l4w1P+bah/HJLN0sys2JSMZQB+jKo6KSc8vLlLn5ikzF4268Wg2+pPOWW6'+ 'ONcpr3PrXy9VfS473M/D7H+TLmrqsXtOGctvxvMv2oVNP+Av0uHbzbxyJaywyUjx8TlnPY2YxqkD'+ 'dAAAAABJRU5ErkJggg==');seleniumFollowerImg.setAttribute('id', 'selenium_mouse_follower');seleniumFollowerImg.setAttribute('style', 'position: absolute; z-index: 99999999999; pointer-events: none; left:0; top:0');document.body.appendChild(seleniumFollowerImg);document.onmousemove = function (e) {document.getElementById("selenium_mouse_follower").style.left = e.pageX + 'px';document.getElementById("selenium_mouse_follower").style.top = e.pageY + 'px';};})();// enableCursor();"""driver.execute_script(enable_cursor)
鼠标除了常用的按键还有一些特殊的,比如“中键”。这些按键需要用更底层的action机制:ActionBuilder
from selenium.webdriver.common.actions.action_builder import ActionBuilderfrom selenium.webdriver.common.actions.mouse_button import MouseButton# 按鼠标按键action = ActionBuilder(driver)action.pointer_action.pointer_down(MouseButton.MIDDLE)action.pointer_action.pointer_up(MouseButton.MIDDLE)action.perform()
注:ActionChains 底层也是使用ActionBuilder创建的action对象:
class ActionChains:def __init__(self, driver: WebDriver, duration: int = 250, devices: list[AnyDevice] | None = None) -> None:...self.w3c_actions = ActionBuilder(driver, mouse=mouse, keyboard=keyboard, wheel=wheel, duration=duration)def click(self, on_element: WebElement | None = None) -> ActionChains:if on_element:self.move_to_element(on_element)self.w3c_actions.pointer_action.click()self.w3c_actions.key_action.pause()self.w3c_actions.key_action.pause()return self
因此,学习ActionBuilder可以通过学习ActionChains源码进行。
指点设备
触控屏,触摸板,指点笔之类的
pointer_area = driver.find_element(By.ID, "pointerArea")pen_input = PointerInput(POINTER_PEN, "default pen")# 关键是替换默认的mouseaction = ActionBuilder(driver, mouse=pen_input)action.pointer_action\.move_to(pointer_area)\.pointer_down()\.move_by(2, 2, tilt_x=-72, tilt_y=9, twist=86)\.pointer_up(0)action.perform()
颜色支持
Selenium有个操作颜色的类,帮助判定颜色,配置颜色
from selenium.webdriver.support.color import Color# 创建颜色BLACK = Color.from_string('black')CHOCOLATE = Color.from_string('chocolate')HOTPINK = Color.from_string('hotpink')TRANSPARENT = Color.from_string('transparent')HEX_COLOUR = Color.from_string('#2F7ED8')RGB_COLOUR = Color.from_string('rgb(255, 255, 255)')RGB_COLOUR = Color.from_string('rgb(40%, 20%, 40%)')RGBA_COLOUR = Color.from_string('rgba(255, 255, 255, 0.5)')RGBA_COLOUR = Color.from_string('rgba(40%, 20%, 40%, 0.5)')HSL_COLOUR = Color.from_string('hsl(100, 0%, 50%)')HSLA_COLOUR = Color.from_string('hsla(100, 0%, 50%, 0.5)')# 从页面元素获取颜色login_button = driver.find_element(By.ID,'login')login_button_colour = Color.from_string(login_button.value_of_css_property('color'))login_button_background_colour = Color.from_string(login_button.value_of_css_property('background-color'))# 从颜色生成hex,rgb,rgba颜色login_button_background_colour.hex # '#ff69b4'login_button_background_colour.rgba # 'rgba(255, 105, 180, 1)'login_button_background_colour.rgb # 'rgb(255, 105, 180)'# 判断元素的颜色assert login_button_background_colour == HOTPINK
总结:
本文描述了Selenium自动化操控浏览器的原理,环境安装,及其支持的各种操作,并补充了一些很有用的扩展功能,希望能帮助读者对Selenium有个整体的认识,更多操作请参考官方资料。
下面是配套资料,对于做【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!

最后: 可以在公众号:自动化测试老司机 ! 免费领取一份216页软件测试工程师面试宝典文档资料。以及相对应的视频学习教程免费分享!,其中包括了有基础知识、Linux必备、Shell、互联网程序原理、Mysql数据库、抓包工具专题、接口测试工具、测试进阶-Python编程、Web自动化测试、APP自动化测试、接口自动化测试、测试高级持续集成、测试架构开发测试框架、性能测试、安全测试等。
如果我的博客对你有帮助、如果你喜欢我的博客内容,请 “点赞” “评论” “收藏” 一键三连哦!