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的工作原理
源码分析
查看webdriver源码(按住Ctrl键,鼠标点击Chrome)
C:\Python36\Lib\site-packages\selenium\webdriver\chrome\webdriver.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:\Python36\Lib\site-packages\selenium\webdriver\common\service.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\n%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:\Python36\Lib\site-packages\selenium\webdriver\chrome\webdriver.py 的51-57行代码,调用了父类RemoteWebDriver 的初始化方法,我们看这个方法做了什么事?
C:\Python36\Lib\site-packages\selenium\webdriver\remote\webdriver.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:\Python36\Lib\site-packages\selenium\webdriver\chrome\webdriver.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:\Python36\Lib\site-packages\selenium\webdriver\remote\remote_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 72
DEBUG: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的
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.浏览器再把执行的动作结果通过浏览器驱动程序返回给测试脚本
相关文章:
Selenium原理
我们使用Selenium实现自动化测试,主要需要3个东西1.测试脚本,可以是python,java编写的脚本程序(也可以叫做client端)2.浏览器驱动, 这个驱动是根据不同的浏览器开发的,不同的浏览器使用不同的webdriver驱动…...
Disconf、Apollo和Nacos分布式配置框架差异对比
差异对比表格: 功能点DisconfApolloNacos依赖高可用框架完全依赖于Zookeeper来实现监听拉取,向外提供了HTTP拉取数据接口依赖于Eureka实现内部服务发现注册,提供HTTP接口给Client SDK拉取监听数据内部自研实现框架高可用CAP理论偏重点Zookee…...
高新技术企业认定条件条件 高企认定要求
高新技术企业认定条件 一、成立年限:申报企业须注册成立365个日历天数,而非一个会计年度。 二、知识产权 (1)申报企业必须拥有在中国境内授权或审批审定的知识产权,且知识产权在有效保护期内。知识产权权属人应为申请企…...
华为OD机试 - 新学校选址(JavaScript) | 机试题+算法思路+考点+代码解析 【2023】
新学校选址 题目 为了解新学期学生暴涨的问题,小乐村要建立所新学校 考虑到学生上学安全问题,需要所有学生家到学校的距离最短. 假设学校和所有学生家都走在一条直线之上,请问学校建立在什么位置, 能使得到学校到各个学生家的距离和最短 输入 第一行: 整数 n 取值范围 [1,1…...

二进制部署K8S
目录 一、环境准备 1、常见的k8s部署方式 2、关闭防火墙 3、关闭selinux 4、关闭swap 5、根据规划设置主机名 6、在master添加hosts 7、将桥接的IPv4流量传递到iptables的链 8、时间同步 二、部署etcd集群 1、master节点部署 2、查看证书的信息 2.1 创建k8s工作目…...
高效获知Activity的生命周期
Activity生命周期监听 使用 Instrumentation 对 Activity 生命周期进行监听。 优点: 全局仅一次反射,性能影响极小所有Activity的生命周期都能够被监听到由于Java的单继承,为了拓展性,可以使用装饰器模式对Instrumentation进行功…...

分析现货黄金价格一般有什么方法
分析现货黄金价格一般有什么方法呢?我相信很多投资者都会说,是技术分析。很多人并不知道技术分析是什么,并且技术分析是如何去分析现货黄金价格的,那么本文就介绍一下技术分析的主要分类。可以说,小编的其他文章都是以…...

Spring中的拦截器
这里写目录标题基本概念HandlerInterceptor拦截器HandlerInterceptor讲解MethodInterceptor拦截器二者的区别基本概念 在web开发中,拦截器是经常用到的功能。它可以帮我们预先设置数据以及统计方法的执行效率等等。 Spring中拦截器主要分两种,一个是Han…...
【Linux操作系统】【综合实验四 Linux的编译环境及线程编程】
文章目录一、实验目的二、实验要求三、实验内容四、实验报告要求一、实验目的 要求熟悉Linux环境中的程序编译、调试与项目管理过程并能实现具体操作;熟练使用基础函数库中与线程库中的管理函数,实现用户线程编程过程,并深入了解Linux的线程…...
Switch 如何使用NSCB 转换XCI NSP NSZ教程
很多小白经常碰到Switch游戏文件格式和预期不符的情况,比如碰到nsz自己不会安装(安装NSZ格式文件教程);或者是碰到xci格式的,想转换为nsp;抑或想将nsz格式文件还原回nsp格式。本文对此提供了解决方案。 文中…...

JVM12 字节码指令集
1. 概述 2. 加载与存储指令 2.1. 局部变量压栈指令 iload 从局部变量中装载int类型值 lload 从局部变量中装载long类型值 fload 从局部变量中装载float类型值 dload 从局部变量中装载double类型值 aload 从局部变量中装载引用类型值(refernce) iload_0 从…...
centos之python安装与多版本python之间的共存
一、背景 随着python版本迭代加快,有写python模块再低版本无法运行,此时需要我们在进行安装一个python版本 例如:uvloop 在python3.7上运行;python 3.6官方不再维护与更新 有些模块或不支持较低版本、有些模块支持较高版本python…...
SpringBoot学习笔记(一)
Idea中隐藏指定文件或指定类型文件 setting->File Types->Ignored Files and Folders输入要隐藏的文件名,支持*号通配符回车确认添加 SpringBoot概述 parent 小结: 开发SpringBoot程序要继承spring-boot-starter-parentspring-boot-starter-pa…...

美国原装KEYSIGHT E4981A(安捷伦) E4981A电容计
KEYSIGHT E4981A(安捷伦) Keysight E4981A(安捷伦)电容计为生产线中的陶瓷电容器测试提供了高速、可靠的测量。E4981A 实现了电容从小到大的测量能力,测量准确。Agilent E4981A 电容计有助于提高测试吞吐量࿰…...

K8S的基础概念
目录 一、k8s概述 1、k8s简介 1.1 k8s的作用 1.2 k8s的由来 1.3 k8s的含义 1.4 k8s的官网 1.5 GitHub 2、为什么要用 K8S? 2.1 K8s的目标 2.2 K8s解决了裸跑Docker 的若干痛点: 2.3 K8s的主要功能 3、K8s的特性 二、Kubernetes 集群架构与组件 1、工作流程 2、…...

【数据结构】——环形队列
文章目录一.环形队列的定义及其特点二.使用数组来实现环形队列1.创建一个队列2.初始化队列3. 判断环形队列是否为空4.判断环形队列是否已满5. 向循环队列插入元素,插入成功返回真6.删除环形链表的数据7. 取队头元素8.取队尾元素8.释放空间总结一.环形队列的定义及其…...

windows 安装Qt
下载 下载地址https://download.qt.io/,此文已5.7.0为例子。 根据图片依次选择即可。 安装 安装过程参考另一篇文章Ubuntu 安装 Qt5.7.0即可 配置环境变量 ps:我就是之前没配置环境变量,直接使用创建项目,项目源码直接运行是…...

spring cloud gateway集成sentinel并扩展支持restful api进行url粒度的流量治理
sentinel集成网关支持restful接口进行url粒度的流量治理前言使用网关进行总体流量治理(sentinel版本:1.8.6)1、cloud gateway添加依赖:2、sentinel配置3、网关类型项目配置4、通过zk事件监听刷新上报api分组信息1、非网关项目上报api分组信息…...

wafw00f工具
wafw00f Web应用程序防火墙指纹识别工具 github地址:https://github.com/EnableSecurity/wafw00f 安装环境:python3环境 —>使用 pip install wafw00f 进行安装 安装成功后目录:python安装目录中的Lib\site-packages\wafw00f 本机为&a…...

论文阅读笔记-DiffusionInst: Diffusion Model for Instance Segmentation
文章目录DiffusionInst: Diffusion Model for Instance Segmentation摘要介绍任务介绍实例分割的几种方法想法来源贡献方法整体结构Mask RepresentationDiffusionInst组成TrainingInference不足之处感悟DiffusionInst: Diffusion Model for Instance Segmentation 代码&#x…...

K8S认证|CKS题库+答案| 4. RBAC - RoleBinding
目录 4. RBAC - RoleBinding 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、查看SA和role 3)、编辑 role-1 权限 4)、检查role 5)、创建 role和 rolebinding 6࿰…...
力扣-131.分割回文串
题目描述 给你一个字符串 s,请你将 s 分割成一些 子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。 class Solution {List<List<String>> res new ArrayList<>();List<String> path new ArrayList<>();void…...

在WPS中如何启用宏VBA wps.vba.exe下载和安装
首先我们点击导航栏中的【工具】,点击左侧 运行宏,根据提示 点击 立即加载。加载卡在50%时间比较长,耐心等待。 关闭wps重新打开后, word和xls表格都可以使用了。 如果电脑无法联网,需要提前下载 WPS VBA插件 WPS VB…...

ORACLE 修改端口号之后无法启动?
Oracle数据库更改端口后出现监听器无法启动的问题确实较为常见,但并非必然发生。这一问题通常源于配置错误或环境冲突,而非端口修改本身。以下是系统性解决方案: 🔍 一、问题根源分析 配置文件语法错误 修…...
DQN算法(详细注释版)
DQN算法 DQN算法使用的常见问题 Q1: 为什么用目标网络而非Q网络直接计算? 答案:避免“移动目标”问题(训练中Q网络频繁变化导致目标不稳定),提高收敛性。 Q2: 为什么用 max 而不是像SARSA那样采样动作?…...

大模型如何选型?嵌入模型如何选型?
欢迎来到啾啾的博客🐱。 记录学习点滴。分享工作思考和实用技巧,偶尔也分享一些杂谈💬。 有很多很多不足的地方,欢迎评论交流,感谢您的阅读和评论😄。 目录 引言模型优劣认知与模型选择大模型(L…...

(LeetCode 动态规划(基础版))96. 不同的二叉搜索树 (递推 || 递归)
题目:96. 不同的二叉搜索树 思路:二叉树长度为n时,枚举每个点u作为根节点root,那么root左边的数构成左子树种数left,root右边的数构成右子树种数right,那么当前u为根节点下,二叉树的种数为left*…...

阿里云域名怎么绑定
阿里云服务器绑定域名全攻略:一步步轻松实现网站“零”障碍上线! 域名,您网站在云端的“身份证”! 在数字化浪潮中,拥有一个属于自己的网站或应用,是个人展示、企业运营不可或缺的一环。而云服务器&#x…...

紫光同创FPGA系列实现Aurora 8b/10b协议
特性 1.兼容XILINX aurora IP核 2.支持X1、X2、X4、X8模式(根据硬件条件选择模式) 3.支持FRAMING和STREAMING 用户接口 4.自动初始化和维护链路状态 5.支持热插拔 6.支持扰码、解扰 7.支持流量控制 8.支持crc用户数据 9.支持全双工或者半双工模式 10.最…...
二元函数可微 切平面逼近 线性函数逼近
二元函数 f ( x , y ) f(x, y) f(x,y) 在某点可微 的含义,可以从几何直观、严格数学定义、与一阶偏导数的关系三个层面来理解: 🔹1. 几何直观上的含义(最易理解) 二元函数 f ( x , y ) f(x, y) f(x,y) 在点 ( x 0 …...