Python automated testing tool selenium usage guide

 

overview

seleniumIt is the most popular automated testing tool in web applications and can be used for automated testing or browser crawlers. The official website address is: selenium . Compared with another web automated testing tool QTP, it has the following advantages:

  • Free open source and lightweight, different languages ​​only need a small dependency package
  • Support multiple systems, including Windows, Mac, Linux
  • Support multiple browsers, including Chrome, FireFox, IE, safari, opera, etc.
  • Support multiple languages, including Java, C, python, c# and other mainstream languages
  • Support for distributed test case execution

python+selenium environment installation

First, you need to install python (recommended 3.7+) environment, and then directly use pip install seleniumthe installation dependency package.

In addition, you need to download the corresponding webdriverdriver of the browser. Note that the downloaded driver version must match the browser version .

 

After downloading, you can add the driver to the environment variable, so that you don't need to manually specify the driver path when using it.

Start the browser with selenium

ChromeYou can use the following code to start a browser in python , and then control the behavior of the browser or read data.

from selenium import webdriver
 
# 启动Chrome浏览器,要求chromedriver驱动程序已经配置到环境变量
# 将驱动程序和当前脚本放在同一个文件夹也可以
driver = webdriver.Chrome()
 
# 手动指定驱动程序路径
driver = webdriver.Chrome(r'D:/uusama/tools/chromedriver.exe')
 
driver = webdriver.Ie()        # Internet Explorer浏览器
driver = webdriver.Edge()      # Edge浏览器
driver = webdriver.Opera()     # Opera浏览器
driver = webdriver.PhantomJS()   # PhantomJS
 
driver.get('http://uusama.com')  # 打开指定路径的页面

You can also set startup parameters during startup. For example, the following code implements adding an agent at startup and ignoring httpscertificate verification.

from selenium import webdriver
 
# 创建chrome启动选项对象
options = webdriver.ChromeOptions()
 
 
options.add_argument("--proxy-server=127.0.0.1:16666")  # 设置代理
options.add_argument("---ignore-certificate-errors")  # 设置忽略https证书校验
options.add_experimental_option("excludeSwitches", ["enable-logging"])  # 启用日志
 
# 设置浏览器下载文件时保存的默认路径
prefs = {"download.default_directory": get_download_dir()}
options.add_experimental_option("prefs", prefs)
driver = webdriver.Chrome(options=options)

 Some very useful startup options, used below options = webdriver.ChromeOptions():

  • options.add_argument("--proxy-server=127.0.0.1:16666"): Set the proxy, which can be combined with mitmproxy to capture packets, etc.
  • option.add_experimental_option('excludeSwitches', ['enable-automation']): Set to bypass selenium detection
  • options.add_argument("---ignore-certificate-errors"): Set to ignore https certificate verification
  • options.add_experimental_option("prefs", {"profile.managed_default_content_settings.images": 2}): Set no request image mode to speed up page loading
  • chrome_options.add_argument('--headless'): set headless browser

selenium page load wait and detection

After using selenium to open the page, it cannot be operated immediately. It needs to wait until the loading of the pending page elements is completed. At this time, it is necessary to detect and wait for the loading of the page elements.

Use time.sleep() to wait

The easiest way is to use time.sleep() to wait for a certain period of time after opening the page. This method can only set a fixed time to wait. If the page is loaded in advance, it will be blocked in vain.

from time import sleep
from selenium import webdriver
 
driver = webdriver.Chrome()
driver.get('http://uusama.con')
time.sleep(10)
print('load finish')

 Use implicitly_wait to set the maximum waiting time
In addition, you can also use implicitly_wait to set the maximum waiting time. If the page is loaded within a given time or has timed out, the next step will be executed. This method will wait until all resources are loaded, that is, the loading chart in the browser tab bar does not turn before executing the next step. It is possible that the page elements have been loaded, but resources such as js or pictures have not been loaded yet, and you need to wait at this time.

It should also be noted that using implicitly_wait only needs to be set once, and it works for the entire driver life cycle. Whenever a page is loading, it will be blocked.

Examples are as follows:

from selenium import webdriver
 
driver = webdriver.Chrome()
driver.implicitly_wait(30)   # 设置最长等30秒
driver.get('http://uusama.com')
print(driver.current_url)
 
driver.get('http://baidu.com')
print(driver.current_url)

Use WebDriverWait to set waiting conditions
Use WebDriverWait (selenium.webdriver.support.wait.WebDriverWait) to set the waiting time more accurately and flexibly. WebDriverWait can detect whether a certain condition is met at regular intervals within the set time, and if the condition is met, proceed In the next step, if the set time is exceeded and it is not satisfied, a TimeoutException will be thrown, and the method declaration is as follows:

WebDriverWait(driver, timeout, poll_frequency=0.5, ignored_exceptions=None)

The meanings of the parameters are as follows:

  • driver: browser driver
  • timeout: The maximum timeout period, the default is in seconds
  • poll_frequency: detection interval (step size), the default is0.5秒
  • ignored_exceptions: Ignored exceptions that do not break even if the given exception is thrown during a call to until()oruntil_not()

WebDriverWait()Generally used with until()the or until_not()method, it means waiting to block until the return value Trueis or False. It should be noted that the parameters of these two methods must be callable objects, that is, the method name. You can use expected_conditionsthe method in the module or the method encapsulated by yourself.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
 
driver = webdriver.Chrome()
driver.get("http://baidu.com")
 
# 判断id为`input`的元素是否被加到了dom树里,并不代表该元素一定可见,如果定位到就返回WebElement
element = WebDriverWait(driver, 5, 0.5).until(expected_conditions.presence_of_element_located((By.ID, "s_btn_wr")))
 
# implicitly_wait和WebDriverWait都设置时,取二者中最大的等待时间
driver.implicitly_wait(5)
 
# 判断某个元素是否被添加到了dom里并且可见,可见代表元素可显示且宽和高都大于0
WebDriverWait(driver,10).until(EC.visibility_of_element_located((By.ID, 'su')))
 
# 判断元素是否可见,如果可见就返回这个元素
WebDriverWait(driver,10).until(EC.visibility_of(driver.find_element(by=By.ID, value='kw')))

expected_conditionsSome commonly used methods  are listed below :

  • title_is: Determine whether the title of the current page is exactly as expected
  • title_contains: Determine whether the title of the current page contains the expected string
  • presence_of_element_located: Judging whether an element is added to the dom tree does not mean that the element must be visible
  • visibility_of_element_located: Determine whether an element is visible (the element is not hidden, and the width and height of the element are not equal to 0)
  • visibility_of: Do the same thing as the above method, except that the above method needs to be passed to the locator, and this method can directly pass the positioned element
  • presence_of_all_elements_located: Determine whether at least one element exists in the dom tree. For example, if there are n elements on the page whose class is 'column-md-3', then as long as there is 1 element, this method will return True
  • text_to_be_present_in_element: Determine whether the text in an element contains the expected string
  • text_to_be_present_in_element_value: Determine whether the value attribute in an element contains the expected string
  • frame_to_be_available_and_switch_to_it: Determine whether the frame can be switched in, if so, return True and switch in, otherwise return False
  • invisibility_of_element_located: Determine whether an element does not exist in the dom tree or is invisible
  • element_to_be_clickable: Determine whether an element is visible and enabled, so it is called clickable
  • staleness_of: wait for an element to be removed from the dom tree, note that this method also returns True or False
  • element_to_be_selected: Determine whether an element is selected, generally used in drop-down lists
  • element_selection_state_to_be: Determine whether the selected state of an element meets expectations
  • element_located_selection_state_to_be: Same as the above method, except that the above method passes in the positioned element, and this method passes in the locator
     

Check if the document is loaded

In addition, you can use driver.execute_script('return document.readyState;') == 'complete' to detect whether the document is loaded.

Note that the document loading is completed, which does not include the asynchronous loading ajax request to dynamically render the dom. This requires using WebDriverWait to detect whether an element has been rendered.

Selenium element positioning and reading

find element

Selenium provides a series of APIs to facilitate access to elements in chrome. These APIs all return WebElementobjects or lists, such as:

  • find_element_by_id(id): Find the first element matching id
  • find_element_by_class_name(): Find the first element matching class
  • find_elements_by_xpath(): Find all elements matching xpath
  • find_elements_by_css_selector(): Find all elements that match the css selector

In fact, you can look at WebDriverthe implementation source code in the class. Its core implementation is to call two basic functions: 

  • find_element(self, by=By.ID, value=None): Find the first element of the matching strategy
  • find_elements(self, by=By.ID, value=None): Find all elements matching the policy

 The byparameters can be IDCSS_SELECTORCLASS_NAMEXPATHetc. Here are a few simple examples:

  • Query the first element containing the text login through xpath: find_element_by_xpath("//*[contains(text(),'login')]")
  • Query all elements containing the class name refresh: find_elements_by_class_name('refresh')
  • Query the second row of the table form: find_element_by_css_selector('table tbody > tr:nth(2)')

dom element interaction

For the element search result object introduced above WebElement, commonly used APIs are:

  • element.text: returns the text content of the element (including the content of all descendant nodes), note that if the element display=none, it will return an empty string
  • element.screenshot_as_png: Screenshot of the element
  • element.send_keys("input"): element input box input input string
  • element.get_attribute('data-v'): Get data-v name attribute value, in addition to custom node attributes, you can also get attributes such as textContent
  • element.is_displayed(): Determine whether the element is visible to the user
  • element.clear(): clear element text
  • element.click(): Click on the element, if the element is not clickable, an ElementNotInteractableException will be thrown
  • element.submit(): Simulate form submission

Find element failure handling

If the specified element cannot be found, NoSuchElementException will be thrown, and it should be noted that the element with display=none can be obtained, and all elements in the dom node can be obtained.

And when you actually use it, you should pay attention to some elements dynamically created by js code, which may need to be polled or monitored.

A method that checks for the existence of a specified element is as follows:

def check_element_exists(xpath):
    try:
        driver.find_element_by_xpath(xpath)
    except NoSuchElementException:
        return False
    return True

selenium interactive control

ActionChainsaction chain

Webdriver ActionChainssimulates user operations through objects, which represent an action link queue, and all operations will enter the queue in turn but will not be executed immediately until the perform()method is called. Its commonly used methods are as follows:

  • click(on_element=None): click the left mouse button
  • click_and_hold(on_element=None): Click the left mouse button without releasing it
  • context_click(on_element=None): click the right mouse button
  • double_click(on_element=None): Double click the left mouse button
  • send_keys(*keys_to_send): Send a key to the currently focused element
  • send_keys_to_element(element, *keys_to_send): Send a key to the specified element
  • key_down(value, element=None): Press a key on the keyboard
  • key_up(value, element=None): release a key
  • drag_and_drop(source, target): drag to an element and release
  • drag_and_drop_by_offset(source, xoffset, yoffset): drag to a certain coordinate and release
  • move_by_offset(xoffset, yoffset): Move the mouse from the current position to a certain coordinate
  • move_to_element(to_element): Move the mouse to an element
  • move_to_element_with_offset(to_element, xoffset, yoffset): How much distance to move to an element (upper left corner coordinates)
  • perform(): execute all actions in the chain
  • release(on_element=None): Release the left mouse button at an element position

Simulate mouse events

The following code simulates mouse movement, click, drag and other operations. Note that you need to wait for a certain period of time during the operation, otherwise the page will not have time to render

 

from time import sleep
from selenium import webdriver
# 引入 ActionChains 类
from selenium.webdriver.common.action_chains import ActionChains
 
driver = webdriver.Chrome()
driver.get("https://www.baidu.cn")
action_chains = ActionChains(driver)
 
target = driver.find_element_by_link_text("搜索")
# 移动鼠标到指定元素然后点击
action_chains.move_to_element(target).click(target).perform()
time.sleep(2)
 
# 也可以直接调用元素的点击方法
target.click()
time.sleep(2)
 
# 鼠标移动到(10, 50)坐标处
action_chains.move_by_offset(10, 50).perform()
time.sleep(2)
 
# 鼠标移动到距离元素target(10, 50)处
action_chains.move_to_element_with_offset(target, 10, 50).perform()
time.sleep(2)
 
# 鼠标拖拽,将一个元素拖动到另一个元素
dragger = driver.find_element_by_id('dragger')
action.drag_and_drop(dragger, target).perform()
time.sleep(2)
 
# 也可以使用点击 -> 移动来实现拖拽
action.click_and_hold(dragger).release(target).perform()
time.sleep(2)
action.click_and_hold(dragger).move_to_element(target).release().perform()

Simulate keyboard input events

Simulate keyboard events through send_keys, commonly used are:

  • send_keys(Keys.BACK_SPACE): delete key (BackSpace)
  • send_keys(Keys.SPACE): space bar (Space)
  • send_keys(Keys.TAB): tab key (Tab)
  • send_keys(Keys.ESCAPE): Escape Key (Esc)
  • send_keys(Keys.ENTER): Enter key (Enter)
  • send_keys(Keys.F1): keyboard F1
  • send_keys(Keys.CONTROL,'a'): select all (Ctrl+A)
  • send_keys(Keys.CONTROL,'c'): copy (Ctrl+C)
  • send_keys(Keys.CONTROL,'x'): Cut (Ctrl+X)
  • send_keys(Keys.CONTROL,'v'): Paste (Ctrl+V)

Example: Locate the input box and enter content

# 输入框输入内容
driver.find_element_by_id("kw").send_keys("uusamaa")
 
# 模拟回车删除多输入的一个字符a
driver.find_element_by_id("kw").send_keys(Keys.BACK_SPACE)

Alert Box Handling

Used to handle alertthe alert box popped up by the call.

  • driver.switch_to_alert(): Switch to the alert box
  • text: returns the text information in alert/confirm/prompt, for example, if js calls alert('failed'), it will get the failed string
  • accept(): accept the existing alert box
  • dismiss(): close the existing warning box
  • send_keys(keysToSend): Send text to the alert box

selenium browser control

Basic common api

Some very useful browser control APIs are listed below:

  • driver.current_url: Get the url of the current active window
  • driver.switch_to_window("windowName"): Move to the specified tab window
  • driver.switch_to_frame("frameName"): Move to the iframe with the specified name
  • driver.switch_to_default_content(): Move to the default text content area
  • driver.maximize_window(): maximize the browser display
  • driver.set_window_size(480, 800): set the browser width to 480 and height to 800 for display
  • driver.forword(), driver.back(): browser forward and back
  • driver.refresh(): Refresh the page
  • driver.close(): close the current tab
  • driver.quiit(): close the entire browser
  • driver.save_screenshot('screen.png'): Save screenshot of the page
  • driver.maximize_window(): maximize the browser display
  • browser.execute_script('return document.readyState;'): execute js script
     

selenium reads and loads cookies

Using get_cookiesand add_cookiecan cache the cookie locally, and then load it at startup, so that the login state can be preserved. The implementation is as follows

import os
import json
from selenium import webdriver
 
driver = webdriver.Chrome()
driver.get("https://www.baidu.cn")
 
# 读取所有cookie并保存到文件
cookies = driver.get_cookies()
cookie_save_path = 'cookie.json'
with open(cookie_save_path, 'w', encoding='utf-8') as file_handle:
    json.dump(cookies, file_handle, ensure_ascii=False, indent=4)
 
# 从文件读取cookie并加载到浏览器
with open(cookie_save_path, 'r', encoding='utf-8') as file_handle:
    cookies = json.load(file_handle)
    for cookie in cookies:
        driver.add_cookie(cookie)

 

selenium opens a new tab window

Using driver.get(url) will open the specified connection in the first tab window by default, and a new tab window will also be opened when clicking the _blank link in the page.

You can also use the following method to manually open a tab window of a specified page. Note that after opening a new window or closing it, you need to manually call switch_to.window to switch the currently active tab window, otherwise NoSuchWindowException will be thrown.

from selenium import webdriver
 
driver = webdriver.Chrome()
driver.get("https://www.baidu.cn")
 
new_tab_url = 'http://uusama.com'
driver.execute_script(f'window.open("{new_tab_url}", "_blank");')
time.sleep(1)
 
# 注意:必须调用switch_to.window手动切换window,否则会找不到tab view
# 聚焦到新打开的tab页面,然后关闭
driver.switch_to.window(driver.window_handles[1])
time.sleep(2)
driver.close()   # 关闭当前窗口
 
# 手动回到原来的tab页面
driver.switch_to.window(driver.window_handles[0])
time.sleep(1)

 In addition to using execute_script, you can also create a new tab window by simulating the button to open a new tab page:

  • driver.find_element_by_tag_name('body').send_keys(Keys.CONTROL + 't')
  • ActionChains(driver).key_down(Keys.CONTROL).send_keys('t').key_up(Keys.CONTROL).perform()

Some problem records of selenium

Get the text content of hidden elements

If an element is hidden, that is display=none, although find_elementthe element can be found by searching, but element.textthe text content of the element cannot be obtained by using attributes, and its value is an empty string, then the following method can be used to obtain it:

element = driver.find_element_by_id('uusama')
driver.execute_script("return arguments[0].textContent", element)
driver.execute_script("return arguments[0].innerHTML", element)
 
# 相应的也可以把隐藏的元素设置为非隐藏
driver.execute_script("arguments[0].style.display = 'block';", element)

 

Browser crash WebDriverException exception handling

For example, if a page runs for a long time in Chrome, an Out Of Memory error will occur. At this time, WebDriver will throw a WebDriverException. Basically all APIs will throw this exception. At this time, it needs to be caught and handled specially.

My processing method is to record some basic information of the page, such as url, cookie, etc., and write it to the file regularly. If the abnormality is detected, restart the browser and load data such as url and cookie.

Selenium crawls page request data

There are ways to obtain page requests through driver.requests or by parsing logs on the Internet, but I don't think it works very well. Finally, use the mitmproxy proxy to capture packets, and then fill in the proxy when starting selenium to achieve.

proxy.py is a custom proxy request processing packaged on the basis of mitmproxy, and its code is as follows:

import os
import gzip
from mitmproxy.options import Options
from mitmproxy.proxy.config import ProxyConfig
from mitmproxy.proxy.server import ProxyServer
from mitmproxy.tools.dump import DumpMaster
from mitmproxy.http import HTTPFlow
from mitmproxy.websocket import WebSocketFlow
 
 
class ProxyMaster(DumpMaster):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
 
    def run(self, func=None):
        try:
            DumpMaster.run(self, func)
        except KeyboardInterrupt:
            self.shutdown()
 
 
def process(url: str, request_body: str, response_content: str):
    # 抓包请求处理,可以在这儿转存和解析数据
    pass
 
 
class Addon(object):
    def websocket_message(self, flow: WebSocketFlow):
        # 监听websockt请求
        pass
 
    def response(self, flow: HTTPFlow):
        # 避免一直保存flow流,导致内存占用飙升
        # flow.request.headers["Connection"] = "close"
        # 监听http请求响应,并获取请求体和响应内容
        url = flow.request.url
        request_body = flow.request
        response_content = flow.response
 
        # 如果返回值是压缩的内容需要进行解压缩
        if response_content.data.content.startswith(b'\x1f\x8b\x08'):
            response_content = gzip.decompress(response_content.data.content).decode('utf-8')
        Addon.EXECUTOR.submit(process, url, request_body, response_content)
 
 
def run_proxy_server():
    options = Options(listen_host='0.0.0.0', listen_port=16666)
    config = ProxyConfig(options)
    master = ProxyMaster(options, with_termlog=False, with_dumper=False)
    master.server = ProxyServer(config)
    master.addons.add(Addon())
    master.run()
 
 
if __name__ == '__main__':
    with open('proxy.pid', mode='w') as fin:
        fin.write(os.getpid().__str__())
    run_proxy_server()

In the process of using mitmproxy, proxy.py will have a problem of soaring memory usage as time goes by. Some people have encountered it in the issue area of ​​github. It is said that the http connection keep-alive=true request will always be saved and will not be released. , resulting in more requests taking up more memory, and then manually closing the connection by adding flow.request.headers["Connection"] = "close". After I added it, it was relieved to some extent, but it still couldn't be solved fundamentally.

Finally, record the proxy process by writing proxy.pid, and then restart proxy.py regularly with another program to solve the memory leak problem.

Finally, I would like to thank everyone who has read my article carefully. Reciprocity is always necessary. Although it is not a very valuable thing, you can take it away if you need it:

These materials should be the most comprehensive and complete preparation warehouse for [software testing] friends. This warehouse has also accompanied tens of thousands of test engineers through the most difficult journey, and I hope it can help you! Partners can click the small card below to receive   

Guess you like

Origin blog.csdn.net/hlsxjh/article/details/131838197