selene介绍

背景

Selenium想必大家都知道,一个偶然的机会看到孙高飞很久之前的一篇文章聊聊自动化的打开方式中提到了java系的一款UI自动化工具Selenide(UI Testing Framework powered by Selenium WebDriver)
Selenide其实是基于Selenium WebDriver的封装,封装了更好用的API,更稳定的控件搜索机制,更好的异常处理机制等
受到Selenide及其他优秀的Selenium项目的启发,在Python系就出现了Selene

  • 面向用户的 Selenium WebDriver API(写起代码来像在说简单英语)
  • 支持Ajax(智能的隐式等待和重试机制)
  • 支持PageObjects(所有元素都是延迟计算的对象)
  • 可自动管理driver(不需要安装和设置驱动,可实现快速本地运行)

相关地址

安装

Python版本 >= 3.7

  • 预发布 alpha 版本
$ pip install selene --pre

具有改进的API和速度

  • 最新稳定版
$ pip install selene

selene API

selene api主要由以下四个部分组成:

  1. 浏览器操作(包含元素查找)
  2. 自定义的选择器
  3. 断言条件
  4. 自定义配置

修改chromedriver为淘宝镜像

因为selene可以自动管理driver,其实是跟你当前chrome版本来匹配,然后去下载对应的driver,但由于众所周知的原因,这里我们可以修改下源码,把下载chromedriver的地址修改为淘宝镜像,修改源码webdriver_manager/chrome.py如下:

import os

from webdriver_manager.core.manager import DriverManager
from webdriver_manager.core.utils import ChromeType
from webdriver_manager.drivers.chrome import ChromeDriver


class ChromeDriverManager(DriverManager):
    def __init__(
            self,
            version="latest",
            os_type=None,
            path=None,
            name="chromedriver",
            # 可通过触发报错,根据报错调用栈来定位到该处,下载地址如如https://chromedriver.storage.googleapis.com/103.0.5060.53/chromedriver_mac64.zip
            # url="https://chromedriver.storage.googleapis.com", 
            # 因为众所周知的原因,这里我们修改源码,下载chromedriver修改为淘宝镜像
            url="https://registry.npmmirror.com/-/binary/chromedriver", 
            # latest_release_url="https://chromedriver.storage.googleapis.com/LATEST_RELEASE",
            latest_release_url="https://registry.npmmirror.com/-/binary/chromedriver/LATEST_RELEASE",
            chrome_type=ChromeType.GOOGLE,
            cache_valid_range=1,
            download_manager=None,
    ):
        super().__init__(
            path,
            cache_valid_range=cache_valid_range,
            download_manager=download_manager)

        self.driver = ChromeDriver(
            name=name,
            version=version,
            os_type=os_type,
            url=url,
            latest_release_url=latest_release_url,
            chrome_type=chrome_type,
            http_client=self.http_client,
        )

    def install(self):
        driver_path = self._get_driver_path(self.driver)
        os.chmod(driver_path, 0o755)
        return driver_path
  • quick start
from selene.support.shared import browser
from selene import by, be, have

browser.open('https://google.com/ncr')
browser.element(by.name('q')).should(be.blank)\
    .type('selenium').press_enter()
browser.all('.srg .g').should(have.size(10))\
    .first.should(have.text('Selenium automates browsers'))
from selene.support.shared import browser
from selene import by, be, have

browser.config.browser_name = 'firefox'
browser.config.base_url = 'https://google.com'
browser.config.timeout = 2
# browser.config.* = ...

browser.open('/ncr')
browser.element(by.name('q')).should(be.blank)\
    .type('selenium').press_enter()
browser.all('.srg .g').should(have.size(10))\
    .first.should(have.text('Selenium automates browsers'))

常用API

  • 配置
from selene import Browser, Config

browser = Browser(Config(
    driver=Chrome(),
    base_url='https://google.com',
    timeout=2))
# import atexit
# atexit.register(browser.quit)
browser.open('/ncr')
  • 元素定位
query = browser.element(by.name('q'))  # actual search doesn't start here, the element is "lazy"          
     # here the actual webelement is found
query.type('selenium').press_enter()       
                      # and here it's located again, i.e. the element is "dynamic"
from selene import be

results = browser.all('.srg .g').filtered_by(be.visible)
  • should
from selene import have

# results.should(have.size(10))
# results.first.should(have.text('Selenium automates browsers'))
# OR...

results.should(have.size(10))\
    .first.should(have.text('Selenium automates browsers'))

browser.quit()

高级API

  • 执行js
from selene import command

browser.element('#not-in-view').perform(command.js.scroll_into_view)

from selene import query

product_text = browser.element('#to-assert-something-non-standard').get(query.text)
price = my_int_from(product_text)
assert price > 100
  • 自定义条件
browser.element('#to-assert-something-non-standard').should(have_in_text_the_int_number_more_than(100))

have_in_text_the_int_number_more_than为自定义条件函数

  • wait until
if browser.element('#i-might-say-yes-or-no').wait_until(have.text('yes')):
    # do something

# ...

if browser.all('.i-will-appear').wait_until(have.size_greater_than_or_equal(2)):
    # do something
if browser.element('#i-might-say-yes-or-no').matching(have.text('yes')):
    # do something

# ...

if browser.all('.i-will-appear').matching(have.size_greater_than_or_equal(2)):
    # do something

参考及扩展阅读