python使用splinter实现SDP组件自动化发布

#

背景

  • 在ND项目开发中,每码完一个版本的代码或是改完bug都需要发布到SDP上,以便打包测试。
  • 发布代码涉及打标签、检查podfilepodspec一致性(appfactory已提供验证方法)、发布等过程。发布流程较为麻烦。
  • 开发者可以使用SDP组件自动化发布工具更方便的发布组件。

设计思路

使用pythonsplinter库模拟组件发布流程,实现自动化发布:

  • 下载Browser驱动(本文使用Chrome)
  • 提取podSpec文件中的版本号和homepage
  • 打开组件标签页面,打上标签。
  • 进入SDP对应的组件发布页面进行发布。

使用方法

一、安装脚本

  1. 确保安装了Chrome

  2. 确保安装python2.X

  3. 确保安装pip

  4. 安装SDP工具: pip install SDPPublishTool ( pip install SDPPublishTool-0.0.3-py2-none-any.whl )

  5. 移动执行脚本到根目录: mv /usr/local/lib/python2.7/site-packages/SDP.* ~/

二、发布命令:

python ~/sdp.py(确保 podspec 文件内 s.homepage 为对应组件 gitlab 地址,且s.version为要发布的版本号)

  • userName : gitlab用户名(工号)
  • password : 工号密码
  • project dir : 要发布的组件目录
  • code-branch(dev or test or release):发布分支分别对应开发、测试、正式环境
  • publish content:发布内容(可为空)

demo

实现

Splinter

  • Splinter 是一个基于python的开源测试工具,可以进行基于浏览器的的自动化操作。
  • 可以通过安装特定驱动(Firefox、Chrome)模拟浏览器行为,访问指定的URL。
  • 支持模拟鼠标的动作,比如滑动到某个按钮上,焦点离开某个按钮等等。
  • 支持模拟键盘的输入操作,对input等控件的输入可以模拟用户的输入。
  • 最重要的,splinter的API非常简单,配合官方的文档学习成本几乎是0。
  • 支持cookie操作,可以很方便的添加和删除cookie。
  • 支持直接运行js或者调用页面的js。
  • 支持模拟上传文件。
  • 对radio和checkbox有专门的api支持,非常方便。
  • 支持快速的获取页面的元素或者判断是否存在文本,用于开发判断页面提示信息是否准确非常方便。

Splinter 的使用

  • 指定浏览器驱动

    b = Browser("chrome")
    b.visit('http://www.baidu.com')   
    
  • 查找页面元素

    browser.find_by_css('h1')
    browser.find_by_xpath('//h1')
    browser.find_by_tag('h1')
    browser.find_by_name('name')
    browser.find_by_text('Hello World!')
    browser.find_by_id('firstheader')
    browser.find_by_value('query')
    
    first_found = browser.find_by_name('name').first
    last_found = browser.find_by_name('name').last
    second_found = browser.find_by_name('name')[1]
    
    //链接
    links_found = browser.find_link_by_text('Link for Example.com')
    links_found = browser.find_link_by_partial_text('for Example')
    links_found = browser.find_link_by_href('http://example.com')
    links_found = browser.find_link_by_partial_href('example')
    
  • 填充点击事件

    b.fill('username', input_name)
    b.fill('password', input_pwd)
    b.find_by_id('login').click()
    

实现源码

下载驱动

packagePath = os.path.dirname(os.__file__)
file_path = os.path.join('/', packagePath, 'site-packages/chromedriver')

if not os.path.isfile(file_path):
    logging.info("Downloading chromedriver...")
    # replace with url you need
    url = 'http://npm.taobao.org/mirrors/chromedriver/70.0.3538.16/chromedriver_mac64.zip'

    def down(_save_path, _url):
        try:
            urllib.urlretrieve(_url, _save_path)
        except:
            print '\nError when retrieving the URL:', _save_path

    down(file_path, url)
    logging.info("Download finish") 

输入用户信息

  • 用户信息数据库存储与读取 (暂未加密)

    import sqlite3
    
    # 建一个数据库
    def create_sql():
        sql = sqlite3.connect("user_data.db")
        sql.execute("CREATE TABLE IF NOT EXISTS \
                Writers(username, password)")
        sql.close()
    
    def get_cursor():
    
        create_sql()
    
        conn = sqlite3.connect("user_data.db")
    
        return conn.cursor()
    
    def insert_user(username, pwd):
        if (fetch_username() is None) | (fetch_pwd is None):
        sql = sqlite3.connect("user_data.db")
        sql.execute("insert into Writers(username,password) values(?,?)",
                  (username, pwd))
        sql.commit()
        # print "添加成功"
        sql.close()
    
    def fetch_username():
    
          conn = sqlite3.connect("user_data.db")
    
          c = conn.cursor()
    
          return c.execute("select username from Writers").fetchone()
    
      def fetch_pwd():
    
          conn = sqlite3.connect("user_data.db")
    
          c = conn.cursor()
    
          return c.execute("select password from Writers").fetchone()
    def fetch_username():
    
          conn = sqlite3.connect("user_data.db")
    
          c = conn.cursor()
    
          return c.execute("select username from Writers").fetchone()
    
      def fetch_pwd():
    
          conn = sqlite3.connect("user_data.db")
    
          c = conn.cursor()
    
          return c.execute("select password from Writers").fetchone()
    
    
  • 输入用户信息、发布环境、发布内容

    from dataBase.dataBase import get_cursor
    from dataBase.dataBase import insert_user
    from dataBase.dataBase import fetch_pwd
    from dataBase.dataBase import fetch_username
    
    # 用户信息
    c = get_cursor()
    username = fetch_username()
    pwd = fetch_pwd()
    
    if (username is None) | (pwd is None):
        # 输入信息
        username = raw_input('please input userName:')
        pwd = getpass.getpass('please input password:')
    
    env = raw_input('please input code-branch(dev or test or release):')
    detail = raw_input('please input publish content:')
    

检查获取组件的podspec

# 判断目录路径是否存在
def file_name(file_dir):
    for roots, dirs, files in os.walk(file_dir):

        for child_file in files:
            if os.path.splitext(child_file)[1] == '.podspec':
                return os.path.join(roots, child_file)


filename = file_name(dir)

while filename is None:
    print 'invalid path'
    dir = raw_input('please input project dir:')
    filename = file_name(dir)

print(filename)
  • 提取组件名、版本号(标签)、homePage

    f = open(filename, 'r')
    
    lines = f.readlines()
    # 版本号
    versionNo = ''
    # 组件名
    component_name = ''
    
    for line in lines:
        if ("s.name" in line) & (line.find("{") == -1):
            tempList = line.split('"')
            component_name = tempList[1]
            print component_name
    
        if ("s.version" in line) & (line.find("{") == -1):
            tempList = line.split('"')
            for str2 in tempList:
                if ("." in str2) & (str2.find('=') == -1):
                    versionNo = str2
                    print versionNo
    
        if "s.homepage" in line:
            tempList = line.split('"')
            for str1 in tempList:
                if "http://" in str1:
                    url = str1
                    print url
    
    f.close()
    

打Tag

# 版本标签
b = Browser("chrome")

def add_version_tag(input_name, input_pwd):
    b.visit(url + '/tags/new')
    time.sleep(1)
    b.fill('username', input_name)
    b.fill('password', input_pwd)
    b.find_by_name('remember_me').click()
    b.find_by_name('button').click()
    if b.find_by_id('tag_name').__len__() == 0:
        print 'invalid userName or password'
        input_name = raw_input('please input userName:')
        input_pwd = getpass.getpass('please input password:')
        add_version_tag(input_name, input_pwd)
        print 'login fail'
        return
    else:
        insert_user(input_name, input_pwd)
        return 1


login_success = add_version_tag(username, pwd)

b.fill('tag_name', versionNo)
b.find_by_name('button').click()

发布

# 发布
b.visit('http://sdp.nd/main.html')
time.sleep(0.5)
b.find_by_id('login').click()

username = fetch_username()
pwd = fetch_pwd()

b.find_by_id('object_id').fill(username)
b.find_by_id('password').fill(pwd)
b.find_by_name('keep_pwd')
b.find_by_id('confirmLogin').click()
time.sleep(1)
b.visit('http://sdp.nd/main.html')

b.find_by_id('appSearchTxt').fill(component_name)


while b.find_link_by_partial_href('/modules/mobileComponent/detail.html?appId') is []:
    print '.'

b.find_link_by_partial_href('/modules/mobileComponent/detail.html?appId').click()


# 选择分支
time.sleep(0.5)
b.find_by_xpath('/html/body/div[4]/a[2]').first.click()

b.find_by_xpath('//*[@id="appDeatil"]/div[1]/span[2]/span').first.click()
b.find_by_id(env_id).first.find_by_tag('a').click()
b.find_by_id('publish').first.click()

# 版本号
b.find_by_id('version').fill(versionNo)
b.find_by_id('versionDesc').fill(detail)

# b.find_by_xpath('//*[@id="gitlist"]/div').first.click()
# b.find_by_id('publishConfirm').click()

python打包发布pip

  • 注册PyPi

  • setup.py

    #!/usr/bin/env python
    #-*- coding:utf-8 -*-
    
    #############################################
    # File Name: setup.py
    # Author: tanzou
    # Mail: 'tanzou34@gmail.com'
    # Created Time:  2018-10-10 19:17:34
    #############################################
     from setuptools import setup, find_packages
    
      setup(
    
            name='SDPPublishTool',
    
            version = "0.0.4",
    
            keywords = ("pip", "SDP"),
    
            zip_safe = False,
    
             description = "SDP publish",
            long_description = "SDP publish for NetDragon",
            license = "MIT Licence",
    
            url = "https://github.com/ZouMac/SDPPublishTool",
            author = "tanzou",
            author_email = "tanzou34@gmail.com",
    
            packages = find_packages(),
            include_package_data = True,
            platforms = "any",
            install_requires = ["splinter", "getpass2"]
          }
    
  • 打包上传
python setup.py sdist     #会生成SDPPublishTool-0.0.3.tar.gz文件用于上传到PyPi上

twine upload dist/magetool-0.1.0.tar.gz  #如果报权限问题,可以在前面加上python -m

参考文献


 上一篇
FBKVOController源码分析 FBKVOController源码分析
前言FBKVOController 是 Facebook 开源的接口设计优雅的 KVO 框架。研读源码有助于加深对其框架和模式的理解,将其中的一些代码技巧运用到开发工作中,以提升自身开发水平。 一、FBKVOController 简介K
2018-08-22
下一篇 
load 耗时检测 load 耗时检测
背景目前部分产品反馈启动时间还是较慢。但目前启动时间耗时统计方案无法统计到 main 方法之前的 load 方法耗时,无法定位耗时长的组件代码。 第三方方案:Hook所有+load方法(包括Category)该方案通过 Hook 所有 Cl
2018-06-14
  目录