selenium-01原理分析

工具介绍

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

通过源码看原理

python + selenium 安装

  1. 首先安装python3。去官网下载所需版本、位数的python安装程序
  2. 将python安装好后,设置一下python的环境变量,这样在终端里能方便使用python
  3. 安装selenium的python库,可以直接通过pip install seleinum安装
  4. 下载对应版本的selenium浏览器驱动,下载地址:
type 地址
Chrome: https://sites.google.com/a/chromium.org/chromedriver/downloads
Edge: https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/
Firefox: https://github.com/mozilla/geckodriver/releases
Safari: https://webkit.org/blog/6900/webdriver-support-in-safari-10/

python示例

简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# -*- coding: UTF8 -*-
"""
File Name: selenium_test.py
Author : jynne
Date: 2019/04/01
"""
import time
from selenium import webdriver
from selenium.webdriver.common.keys import Keys

browser = webdriver.Chrome(executable_path="./chromedriver") # mac上
# 由于我将chromedriver和脚本放在同级目录,没有写入环境变量,所以这里需要传入driver的路径

browser.get('http://www.baidu.com') # 打开百度首页
assert '百度' in browser.title

elem = browser.find_element_by_name('wd') # 搜索框
elem.send_keys('seleniumhq' + Keys.RETURN) # 搜索

time.sleep(10)
browser.quit()

以上是一个简单的selenium在python中的使用示例,效果是 打开chrome浏览器,并打开百度首页,输入seleniumhq并回车搜索。

原理分析

  1. webdriver.Chrome(executable_path=”./chromedriver”)
    调试进函数内部:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    """
    ***\Lib\site-packages\selenium\webdriver\chrome\webdriver.py
    """
    class WebDriver(RemoteWebDriver):
    """
    Controls the ChromeDriver and allows you to drive the browser.

    You will need to download the ChromeDriver executable from
    http://chromedriver.storage.googleapis.com/index.html
    """

    def __init__(self, executable_path="chromedriver", port=0,
    options=None, service_args=None,
    desired_capabilities=None, service_log_path=None,
    chrome_options=None, keep_alive=True):
    """
    Creates a new instance of the chrome driver.

    Starts the service and then creates new instance of chrome driver.

    :Args:
    - executable_path - path to the executable. If the default is used it assumes the executable is in the $PATH
    - port - port you would like the service to run, if left as 0, a free port will be found.
    - options - this takes an instance of ChromeOptions
    - service_args - List of args to pass to the driver service
    - desired_capabilities - Dictionary object with non-browser specific
    capabilities only, such as "proxy" or "loggingPref".
    - service_log_path - Where to log information from the driver.
    - chrome_options - Deprecated argument for options
    - keep_alive - Whether to configure ChromeRemoteConnection to use HTTP keep-alive.
    """
    if chrome_options:
    warnings.warn('use options instead of chrome_options',
    DeprecationWarning, stacklevel=2)
    options = chrome_options

    if options is None:
    # desired_capabilities stays as passed in
    if desired_capabilities is None:
    desired_capabilities = self.create_options().to_capabilities()
    else:
    if desired_capabilities is None:
    desired_capabilities = options.to_capabilities()
    else:
    desired_capabilities.update(options.to_capabilities())

    self.service = Service(
    executable_path,
    port=port,
    service_args=service_args,
    log_path=service_log_path)
    self.service.start()

    try:
    RemoteWebDriver.__init__(
    self,
    command_executor=ChromeRemoteConnection(
    remote_server_addr=self.service.service_url,
    keep_alive=keep_alive),
    desired_capabilities=desired_capabilities)
    except Exception:
    self.quit()
    raise
    self._is_remote = False

    def launch_app(self, id):
    """Launches Chrome app specified by id."""
    return self.execute("launchApp", {'id': id})

    def get_network_conditions(self):
    """
    Gets Chrome network emulation settings.

    :Returns:
    A dict. For example:

    {'latency': 4, 'download_throughput': 2, 'upload_throughput': 2,
    'offline': False}

    """
    return self.execute("getNetworkConditions")['value']

    def set_network_conditions(self, **network_conditions):
    """
    Sets Chrome network emulation settings.

    :Args:
    - network_conditions: A dict with conditions specification.

    :Usage:
    driver.set_network_conditions(
    offline=False,
    latency=5, # additional latency (ms)
    download_throughput=500 * 1024, # maximal throughput
    upload_throughput=500 * 1024) # maximal throughput

    Note: 'throughput' can be used to set both (for download and upload).
    """
    self.execute("setNetworkConditions", {
    'network_conditions': network_conditions
    })

    def execute_cdp_cmd(self, cmd, cmd_args):
    """
    Execute Chrome Devtools Protocol command and get returned result

    The command and command args should follow chrome devtools protocol domains/commands, refer to link
    https://chromedevtools.github.io/devtools-protocol/

    :Args:
    - cmd: A str, command name
    - cmd_args: A dict, command args. empty dict {} if there is no command args

    :Usage:
    driver.execute_cdp_cmd('Network.getResponseBody', {'requestId': requestId})

    :Returns:
    A dict, empty dict {} if there is no result to return.
    For example to getResponseBody:

    {'base64Encoded': False, 'body': 'response body string'}

    """
    return self.execute("executeCdpCommand", {'cmd': cmd, 'params': cmd_args})['value']

    def quit(self):
    """
    Closes the browser and shuts down the ChromeDriver executable
    that is started when starting the ChromeDriver
    """
    try:
    RemoteWebDriver.quit(self)
    except Exception:
    # We don't care about the message because something probably has gone wrong
    pass
    finally:
    self.service.stop()

    def create_options(self):
    return Options()

webdriver.Chrome()定位到__init__(),为什类名不是Chrome
因为在/site-packages/selenium/webdriver/__init__.py中,

通过源码可以看见(上述代码的12-64行),webdriver.Chrome()主要是创建了一个Service,并且启动Service,然后创建RemoteWebDriver。
(1) self.service = Service()
进入源码:site-packages/selenium/webdriver/chrome/service.py

继续通过父类创建Service:
site-packages/selenium/webdriver/common/service.py

作用就是创建了一个Service
(2) service.start()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
""" site-packages/selenium/webdriver/common/service.py """
class Service(object):
""" ………… """
def start(self):
"""
Starts the Service.

:Exceptions:
- WebDriverException : Raised either when it can't start the service
or when it can't connect to the service
"""
try:
cmd = [self.path]
cmd.extend(self.command_line_args())
self.process = subprocess.Popen(cmd, env=self.env,
close_fds=platform.system() != 'Windows',
stdout=self.log_file,
stderr=self.log_file,
stdin=PIPE)
except TypeError:
raise
except OSError as err:
if err.errno == errno.ENOENT:
raise WebDriverException(
"'%s' executable needs to be in PATH. %s" % (
os.path.basename(self.path), self.start_error_message)
)
elif err.errno == errno.EACCES:
raise WebDriverException(
"'%s' executable may have wrong permissions. %s" % (
os.path.basename(self.path), self.start_error_message)
)
else:
raise
except Exception as e:
raise WebDriverException(
"The executable %s needs to be available in the path. %s\n%s" %
(os.path.basename(self.path), self.start_error_message, str(e)))
count = 0
while True:
self.assert_process_still_running()
if self.is_connectable():
break
count += 1
time.sleep(1)
if count == 30:
raise WebDriverException("Can not connect to the Service %s" % self.path)

本质是执行了一个cmd命令: ./chromedriver --port=53393',作用是启动chromedriver,端口号是53393

(3) RemoteWebDriver.init()
首先看一下传入的参数:

1
2
3
4
5
6
RemoteWebDriver.__init__(
self,
command_executor=ChromeRemoteConnection(
remote_server_addr=self.service.service_url,
keep_alive=keep_alive),
desired_capabilities=desired_capabilities)

command_executor的值是一个ChromeRemoteConnection实例,

实际上就是一个RemoteConnection,和Remote WebDriver server进行通信的连接,所有的命令存在self._command中。

再看RemoteWebDriver.init()的源码:site-packages/selenium/webdriver/remote/webdriver.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class WebDriver(object):
"""
Controls a browser by sending commands to a remote server.
This server is expected to be running the WebDriver wire protocol
as defined at
https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol

:Attributes:
- session_id - String ID of the browser session started and controlled by this WebDriver.
- capabilities - Dictionaty of effective capabilities of this browser session as returned
by the remote server. See https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities
- command_executor - remote_connection.RemoteConnection object used to execute commands.
- error_handler - errorhandler.ErrorHandler object used to handle errors.
"""

_web_element_cls = WebElement

def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub',
desired_capabilities=None, browser_profile=None, proxy=None,
keep_alive=False, file_detector=None, options=None):
"""
Create a new driver that will issue commands using the wire protocol.

:Args:
- command_executor - Either a string representing URL of the remote server or a custom
remote_connection.RemoteConnection object. Defaults to 'http://127.0.0.1:4444/wd/hub'.
- desired_capabilities - A dictionary of capabilities to request when
starting the browser session. Required parameter.
- browser_profile - A selenium.webdriver.firefox.firefox_profile.FirefoxProfile object.
Only used if Firefox is requested. Optional.
- proxy - A selenium.webdriver.common.proxy.Proxy object. The browser session will
be started with given proxy settings, if possible. Optional.
- keep_alive - Whether to configure remote_connection.RemoteConnection to use
HTTP keep-alive. Defaults to False.
- file_detector - Pass custom file detector object during instantiation. If None,
then default LocalFileDetector() will be used.
- options - instance of a driver options.Options class
"""
capabilities = {}
if options is not None:
capabilities = options.to_capabilities()
if desired_capabilities is not None:
if not isinstance(desired_capabilities, dict):
raise WebDriverException("Desired Capabilities must be a dictionary")
else:
capabilities.update(desired_capabilities)
if proxy is not None:
warnings.warn("Please use FirefoxOptions to set proxy",
DeprecationWarning, stacklevel=2)
proxy.add_to_capabilities(capabilities)
self.command_executor = command_executor
if type(self.command_executor) is bytes or isinstance(self.command_executor, str):
self.command_executor = RemoteConnection(command_executor, keep_alive=keep_alive)
self._is_remote = True
self.session_id = None
self.capabilities = {}
self.error_handler = ErrorHandler()
self.start_client()
if browser_profile is not None:
warnings.warn("Please use FirefoxOptions to set browser profile",
DeprecationWarning, stacklevel=2)
self.start_session(capabilities, browser_profile)
self._switch_to = SwitchTo(self)
self._mobile = Mobile(self)
self.file_detector = file_detector or LocalFileDetector()

比较关键的是62行的self.start_session(capabilities, browser_profile)

作用是执行了newSession的命令,发送了一个post请求,参数是json格式的,返回response
当web driver接收到请求后,浏览器驱动程序解析请求,打开浏览器,并获得sessionid,在response中返回

后续再执行各类操作时,都会在请求中拼接上session id

------------- The End -------------