helium源码分析

简介

前段时间在做web自动化测试方面的资料收集,无意间见到了一个selenium的封装库,使用下来真是要吹一波
官网:selenium-python-helium
文档:https://selenium-python-helium.readthedocs.io/en/latest/api.html

使用

启动浏览器

# A Helium function:
driver = start_chrome()
# A Selenium API:
driver.execute_script("alert('Hi!');")

获取selenium的driver

get_driver()

跳转

go_to(url)

写入

write(text, into=None)

点击

click(element)

具体的api可以参考文档来学习。在这里不再赘述。这个库最惊人的是代码量非常精简,只有几个文件:
helium源码分析_第1张图片

代码分析

那下面我们一点点地来看一下这个库是如何对selenium进行封装的:

  • util包是工具包,包含了系统判断,数据结构转换的工具等。python方法比较多,咋一看感觉c语言的痕迹还是挺重的。
  • webdrivers: 放了几款webdriver驱动,但是不见得能适配,或须根据自己的浏览器版本更新驱动。
  • match_type.py :
  • selenium_wrappers.py (核心)
  • _impl/init.py : 实现api的核心
  • init.py : 一般的库这个文件是做类或者方法导入,或者显示版本号。如:helium源码分析_第2张图片
    不过在helium包和_impl下都有方法实现,并没有把这部分代码放到单独的py文件中。
    helium源码分析_第3张图片
    如图所示,这个文件就包含了几乎所有的helium的api。当然具体的实现还是要移步APIImpl
def kill_browser():
	"""
	Closes the current browser with all associated windows and potentially open
	dialogs. Dialogs opened as a response to the browser closing (eg. "Are you
	sure you want to leave this page?") are also ignored and closed.

	This function is most commonly used to close the browser at the end of an
	automation run::

		start_chrome()
		...
		# Close Chrome:
		kill_browser()
	"""
	_get_api_impl().kill_browser_impl()

def highlight(element):
	"""
	:param element: The element to highlight.

	Highlights the given element on the webpage by drawing a red rectangle
	around it. This is useful for debugging purposes. For example::

		highlight("Helium")
		highlight(Button("Sign in"))
	"""
	_get_api_impl().highlight_impl(element)

def _get_api_impl():
	global _API_IMPL
	if _API_IMPL is None:
		_API_IMPL = APIImpl()  # 具体的实现类
	return _API_IMPL

helium源码分析_第4张图片

我们以chrome的驱动为例看一下这几个主要的api干了些什么:

	def _start_chrome_driver(self, headless, options):
		chrome_options = self._get_chrome_options(headless, options)
		try:  # 有驱动获得driver
			result = Chrome(options=chrome_options)
		except WebDriverException:
			# This usually happens when chromedriver is not on the PATH.
			# 没有搜索到驱动,使用内嵌的driver  无论是否发生异常,都将返回一个driver
			driver_path = self._use_included_web_driver('chromedriver')
			result = Chrome(options=chrome_options, executable_path=driver_path)
		atexit.register(self._kill_service, result.service)
		return result
  • python atexit 模块定义了一个 register 函数,用于在 python 解释器中注册一个退出函数,这个函数在解释器正常终止时自动执行,一般用来做一些资源清理的操作。Note:如果程序是非正常crash,比如异常抛出或者通过os._exit()退出,注册的退出函数将不会被调用。
    因此,我们在用ide,restart脚本的时候浏览器会自动退出。
  • 然后,启动浏览器(利用selenium)

其他的api利用了python的包装器模式
helium源码分析_第5张图片
在这里主要以write为例看看代码是如何设计的:
语法

		write("Hello World!")
		write("user12345", into="Username:")
		write("Michael", into=Alert("Please enter your name"))
def might_spawn_window(f):  # 或再建一个窗口
	def f_decorated(self, *args, **kwargs):
		driver = self.require_driver()
		if driver.is_ie() and AlertImpl(driver).exists():
			# Accessing .window_handles in IE when an alert is present raises an
			# UnexpectedAlertPresentException. When DesiredCapability
			# 'unexpectedAlertBehaviour' is not 'ignore' (the default is
			# 'dismiss'), this leads to the alert being closed. Since we don't
			# want to unintentionally close alert dialogs, we therefore do not
			# access .window_handles in IE when an alert is present.
			return f(self, *args, **kwargs)
		window_handles_before = driver.window_handles[:]
		result = f(self, *args, **kwargs)
		# As above, don't access .window_handles in IE if an alert is present:
		if not (driver.is_ie() and AlertImpl(driver).exists()):
			if driver.is_firefox():
				# Unlike Chrome, Firefox does not wait for new windows to open.
				# Give it a little time to do so:
				sleep(.2)
			new_window_handles = [
				h for h in driver.window_handles
				if h not in window_handles_before
			]
			if new_window_handles:
				driver.switch_to.window(new_window_handles[0])
		return result
	return f_decorated

使用过api,比如click的时候,就会发现这个接口极为简单

	def _click(self, selenium_elt, offset):
		self._move_to_element(selenium_elt, offset).click().perform()

	def _move_to_element(self, element, offset):
		result = self.require_driver().action()
		if offset is not None:
			result.move_to_element_with_offset(element, *offset)
		else:
			result.move_to_element(element)
		return result
		
class WebDriverWrapper(Wrapper):
	def __init__(self, target):
		super(WebDriverWrapper, self).__init__(target)
		self.last_manipulated_element = None
	def action(self):
		return ActionChains(self.target)

核心----move-to-element ----调用底层的ActionChain执行链

你可能感兴趣的:(python)