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:\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的

你还会看到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

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

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

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

最后作为一位过来人也是希望大家少走一些弯路,如果你不想再体验一次学习时找不到资料,没人解答问题,坚持几天便放弃的感受的话,在这里我给大家分享一些软件测试的学习资源,希望能给你前进的路上带来帮助。

视频文档获取方式:
这份文档和视频资料,对于想从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!以上均可以分享,点下方小卡片即可自行领取。

<think>首先,用户的问题是:“我想了解HTML5的工作原理 请问HTML5工作原理详解”。这基于之前的上下文,其中提供了多个引用,我需要参考这些引用。 回顾引用: - 引用[1]:讨论了HTML5 canvas游戏的工作原理,但重点是自动化测试。提到了游戏的基本工作原理,但没有详细解释HTML5本身。 - 引用[2]:简要说明HTML的原理,说HTML5使用HTTP作为请求/响应协议,客户端和服务器之间的通信涉及TCP连接和HTTP请求。还提到了WebSocket的使用减少HTTP头。 - 引用[3]:详细描述了浏览器如何加载HTML页面,包括HTML、CSS、JS的加载顺序,渲染过程,以及动态改变时的重新渲染。 用户明确想了解“HTML5的工作原理”,所以我需要提供一个详细的解释。HTML5是HTML的第五版,包括HTML语义、表单控件、多媒体(如音频、视频)、canvas、Web存储等新特性。工作原理通常涉及客户端(浏览器)和服务器的交互。 根据系统级指令: 1. 所有行内数学表达式使用$...$格式。但在这个主题中,可能没有太多数学内容,除非涉及计算或变量。 2. 独立公式用$$...$$,如果需要。 3. LaTeX语法正确。 4. 使用中文回答。 5. 生成相关问题。 6. 回答中引用的段落末尾自然地添加引用标识,如[^1]。 此外,回答必须结构清晰,逐步解决问题,保证真实可靠。 结构化我的回答: 1. **引言**:简要概述HTML5是什么及其工作原理的核心。 2. **详细工作原理**:基于引用和知识解释。 - 参考引用[2]:HTTP请求/响应模型。 - 参考引用[3]:浏览器加载和渲染过程。 - 整合HTML5特定特性,如Canvas、WebSocket等。 3. **关键组件**:解释HTML5的新特性如何工作,例如: - Canvas绘图。 - WebSocket用于实时通信。 - 本地存储(如LocalStorage)。 - 多媒体元素。 4. **总结**:重申工作原理。 5. **相关问题**:生成相关问题。 确保引用自然添加。例如,当解释HTTP模型时,添加[^2];当解释浏览器渲染时,添加[^3]。 最终,回答问题:"HTML5工作原理详解"。 大纲: - **什么是HTML5**:HTML5是超文本标记语言的最新标准,用于构建网页,添加了新元素和API支持丰富媒体和交互。 - **工作原理的核心**: - 客户端-服务器模型:浏览器(客户端)向服务器发送请求,服务器响应资源(HTML、CSS、JS文件)。这基于HTTP(S)协议[^2]。 - 浏览器渲染过程:加载HTML → 解析 → 加载外部资源(CSS, JS) → 构建DOM树 → 布局 → 绘制 → 执行JavaScript可能修改DOM导致重新渲染[^3]。 - HTML5特定:例如,Canvas元素允许JavaScript绘图;WebSocket提供全双工通信减少开销;Geolocation API等。 - **详细步骤**: 1. **请求阶段**:用户输入URL,浏览器发起HTTP GET请求,服务器返回HTML文档。 2. **解析和渲染**:浏览器解析HTML构建DOM树;遇到CSS加载并构建CSSOM;结合DOM和CSSOM形成渲染树;布局(布局计算);绘制到屏幕。 3. **JavaScript执行**:JS可以阻塞渲染;HTML5支持async/defer属性优化加载。 4. **HTML5特性工作**:如video元素自动处理视频播放;localStorage用于本地数据存储;Web Workers在后台运行脚本。 - **优化和挑战**:提到性能优化,如减少重绘。 - **相关问题**:生成相关问题。 相关问题应基于主题,如: 1. HTML5新特性有哪些? 2. 如何优化HTML5应用的性能? 3. Canvas的工作原理是什么? 确保回答真实可靠:基于标准Web开发知识,参考W3C规范或权威来源。 </think>### HTML5工作原理详解 HTML5 是 HTML(超文本标记语言)的第五个主要版本,它不仅继承了 HTML 的核心功能,还引入了许多新特性,如 Canvas 绘图、WebSocket 实时通信、本地存储(localStorage)和多媒体元素(video、audio)。HTML5 的工作原理基于客户端-服务器模型,涉及 HTTP 协议、浏览器渲染过程和 JavaScript 执行。下面我从以下几个方面逐步详解其工作原理,确保结构清晰、真实可靠。 #### 1. **客户端-服务器交互模型** HTML5 依赖于 HTTP(超文本传输协议)作为基础通信机制。当用户在浏览器中输入 URL 时,浏览器(客户端)会发起一个 TCP 连接,并向服务器发送 HTTP 请求(如 GET 请求)。服务器响应请求后,返回 HTML 文件及其他资源(如 CSS、JS)。整个过程遵循请求/响应模式: - **请求阶段**:客户端发送 HTTP 请求头,指定资源路径(如一个 HTML5 页面)。 - **响应阶段**:服务器返回状态码(如 200 OK)和资源内容。HTML5 通过 HTTP 或基于 WebSocket 的轻量级通信减少开销。例如,WebSocket 建立后只需少量标头,避免频繁 HTTP 请求,提升实时性[^2]。 - **关键点**:HTML5 增强了这一模型,支持更高效的资源加载(如通过 CDN 加速),但核心仍是基于 TCP/IP 的网络通信。 #### 2. **浏览器加载和渲染过程** 浏览器处理 HTML5 页面时,会逐步解析并渲染内容。这个过程是异步和非阻塞的,以优化性能。参考用户打开的页面加载流程: - **步骤1: HTML 加载**:浏览器下载 HTML 文件后,开始解析并构建 DOM(文档对象模型)树。DOM 树表示页面的结构元素(如 `<div>`, `<canvas>`)。 - **步骤2: 外部资源加载**:当遇到外部资源链接时: - `<link>` 引用 CSS 文件:浏览器发起 CSS 请求,服务器返回后构建 CSSOM(CSS 对象模型)。CSSOM 与 DOM 结合形成渲染树(Render Tree),用于布局计算。 - `<img>` 或 `<video>` 引用多媒体资源:浏览器异步下载资源(如图片或视频),而不阻塞渲染。如果资源加载延迟,可能导致后续布局重新计算(例如图片加载后重新调整页面布局)。 - `<script>` 引用 JavaScript:浏览器执行 JS 前会暂停渲染(除非使用 `async` 或 `defer` 属性优化)。JS 可以动态修改 DOM 或 CSS,触发重新渲染(如隐藏元素或更改样式)[^3]。 - **步骤3: 渲染和绘制**:渲染树确定每个元素的几何位置后,浏览器进行布局(Layout)和绘制(Painting)。HTML5 的新元素(如 Canvas)允许 JavaScript 直接操作像素数据,实现动态绘图。 - **步骤4: 事件处理和更新**:页面加载完成后,用户交互(如点击“换肤”按钮)可能触发 JavaScript 更新资源(如切换 CSS 文件),导致整个渲染过程重新开始。浏览器通过增量渲染优化性能,但频繁修改仍可能引发重绘(Repaint)或回流(Reflow)。 - **关键优化**:HTML5 提供特性如 Web Workers(在后台线程运行 JS,避免阻塞主线程)和 Service Workers(离线缓存),提升响应速度。 #### 3. **HTML5 核心特性的工作原理** HTML5 引入了多种 API 和新元素,扩展了网页功能: - **Canvas 绘图**:Canvas 元素(`<canvas>`)提供一个像素画布,JavaScript 可以直接绘制图形、动画或游戏。例如,在游戏中,JS 通过 Canvas API(如 `context.fillRect()`)动态更新图像帧,无需重新加载页面。这基于浏览器的渲染引擎(如 Chromium 的 Blink)实时处理像素数据[^1]。 - **WebSocket 实时通信**:不同于传统 HTTP 的轮询,WebSocket 建立持久连接后,客户端和服务器可双向传输数据(如聊天应用),减少延迟和开销。握手阶段使用 HTTP,之后仅需要轻量级的 WebSocket 帧[^2]。 - **本地存储**:localStorage 和 sessionStorage 允许在客户端存储数据(如用户偏好)。数据以键值对形式保存,即使页面刷新也不会丢失,提升离线用户体验。 - **多媒体元素**:`<video>` 和 `<audio>` 元素内置解码器,浏览器自动处理媒体流加载和播放。例如,视频文件通过 HTTP 请求获取,浏览器解码后绘制到屏幕。 - **其他特性**:Geolocation API 使用 GPS 或 IP 定位;Drag and Drop 允许用户交互元素移动;这些特性通过 JavaScript 事件驱动,与 DOM 紧密集成。 #### 4. **整体工作流程总结** HTML5 的工作原理可概括为:用户触发请求 → 服务器响应资源 → 浏览器解析、渲染并执行交互逻辑。关键优势在于其模块化设计: - **优势**:标准化跨浏览器支持、丰富的 API 支持响应式设计、高效通信减少页面刷新。 - **挑战**:渲染过程复杂可能导致性能问题(如频繁重绘),需通过代码优化(如使用 CSS3 动画代替 JS)和工具(如 Selenium 自动化测试)管理[^1]。 总之,HTML5 通过结合 HTTP、DOM 和 JavaScript,实现动态、交互式网页,支撑现代 Web 应用。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值