天天看點

軟體測試丨Selenium 自動偵測浏覽器版本并下載下傳對應的浏覽器驅動

作者:霍格沃茲軟體測試

公衆号搜尋:TestingStudio 霍格沃茲測試開發的幹貨都很硬核

本文為霍格沃茲測試學院學員 @felix 測試開發實用技能分享。

在學院學員群交流時,有同學說 Appium 官方支援自動下載下傳相容的浏覽器驅動,想來 Selenium 也有類似的方法,于是在網上搜尋一番,并參考了 Medium 上的一篇文章所介紹的方法進行嘗試,對相關步驟進行了改進,增加了對多浏覽器的支援。本文就總結下整體過程與實作代碼。

首先,想明白大緻的幾個步驟:

  1. 識别本地浏覽器版本
  2. 下載下傳對應浏覽器版本的驅動
  3. 解壓到對應檔案夾
  4. 記錄到 mapping.json 檔案中

接下來就是撸起袖子開幹了。

定義好目錄結構

|— config

 |— mapping.json: 浏覽器驅動配置資訊

|— driver: 存放浏覽器驅動

|— utils

 |— driver_util.py: 封裝的工具包

|— test_search.py: 測試腳本           

資料準備

導入第三方庫,定義好路徑名稱等常量。

import json
import os
import zipfile
import shutil
import requests
import pathlib
from win32com import client as win_client


# 工作目錄(目前路徑調試時需加上.parent)
BASE_DIR = str(pathlib.Path.cwd())
# BASE_DIR = str(pathlib.Path.cwd().parent)

CHROME_DRIVER_BASE_URL = "https://chromedriver.storage.googleapis.com"
EDGE_DRIVER_BASE_URL = "https://msedgedriver.azureedge.net"
CHROME_DRIVER_ZIP = "chromedriver_win32.zip"
EDGE_DRIVER_ZIP = "edgedriver_win64.zip"
CHROME_DRIVER = "chromedriver.exe"
EDGE_DRIVER = "msedgedriver.exe"

BROWSER_DRIVER_DIR = str(pathlib.PurePath(BASE_DIR, "driver"))
DRIVER_MAPPING_FILE = os.path.join(BASE_DIR, "config", "mapping.json")           

第一步:擷取浏覽器的版本

Chrome 浏覽器有些小版本沒有對應版本号的浏覽器驅動,需要借助 Query API 查詢對應大版本LATEST RELEASE版本,再根據查詢對應的浏覽器驅動。

新版 Edge 浏覽器每個版本号官網都有對應的驅動下載下傳。

Latest Version API
https://chromedriver.storage.googleapis.com/LATEST_RELEASE_{version}Download Chrome Driver 
API
https://chromedriver.storage.googleapis.com/{version}/chromedriver_win32.zip
https://msedgedriver.azureedge.net/{version}/edgedriver_win64.zip           

代碼如下:

def get_browser_version(file_path):
    """
    擷取浏覽器版本
    :param file_path: 浏覽器檔案路徑
    :return: 浏覽器大版本号
    """
    # 判斷路徑檔案是否存在
    if not os.path.isfile(file_path):
        raise FileNotFoundError(f"{file_path} is not found.")
    win_obj = win_client.Dispatch('Scripting.FileSystemObject')
    version = win_obj.GetFileVersion(file_path)

    return version.strip()


def get_browser_major_version(file_path):
    """
    擷取浏覽器大版本号
    :param file_path: 浏覽器檔案路徑
    :return: 浏覽器大版本号
    """
    browser_ver = get_browser_version(file_path)
    browser_major_ver = browser_ver.split(".")[0]

    return browser_major_ver


def get_latest_browser_version(browser_major_ver):
    """
    擷取比對大版本的最新release版本
    :param browser_major_ver: 浏覽器大版本号
    :return: 最新release版本号
    """
    latest_api = f"{CHROME_DRIVER_BASE_URL}/LATEST_RELEASE_{browser_major_ver}"
    resp = requests.get(latest_api)
    latest_driver_version = resp.text.strip()

    return latest_driver_version           

第二步:下載下傳浏覽器驅動

def download_browser_driver(latest_driver_version, browser_name):
    """
    下載下傳浏覽器驅動壓縮包
    :param browser_name: 浏覽器名稱
    :param latest_driver_version: 浏覽器的版本号
    """
    download_api = None
    if browser_name == "Chrome":
        download_api = f"{CHROME_DRIVER_BASE_URL}/{latest_driver_version}/{CHROME_DRIVER_ZIP}"
    elif browser_name == "Edge":
        download_api = f"{EDGE_DRIVER_BASE_URL}/{latest_driver_version}/{EDGE_DRIVER_ZIP}"

    download_dir = os.path.join(str(BROWSER_DRIVER_DIR), os.path.basename(download_api))
    # 下載下傳,設定逾時時間20s
    resp = requests.get(download_api, stream=True, timeout=20)

    if resp.status_code == 200:
        with open(download_dir, 'wb') as fo:
            fo.write(resp.content)
    else:
        raise Exception("Download chrome driver failed")            

第三步:解驅動壓縮包

解壓後将原壓縮包删除。

def unzip_driver(browser_major_ver, browser_name):
    """
    解壓驅動壓縮包
    :param browser_name: 浏覽器名稱
    :param browser_major_ver: 浏覽器大版本号
    :return: 驅動檔案路徑
    """
    file_path = None
    driver_path = None

    if browser_name == "Chrome":
        file_path = os.path.join(BROWSER_DRIVER_DIR, os.path.basename(CHROME_DRIVER_ZIP))
        driver_path = os.path.join(BROWSER_DRIVER_DIR, browser_major_ver, CHROME_DRIVER)
    elif browser_name == "Edge":
        file_path = os.path.join(BROWSER_DRIVER_DIR, os.path.basename(EDGE_DRIVER_ZIP))
        driver_path = os.path.join(BROWSER_DRIVER_DIR, browser_major_ver, EDGE_DRIVER)
    browser_driver_dir = os.path.join(BROWSER_DRIVER_DIR, browser_major_ver)

    # 解壓到指定目錄
    with zipfile.ZipFile(file_path, 'r') as zip_ref:
        zip_ref.extractall(browser_driver_dir)

    return driver_path


def remove_driver_zip(browser_name):
    """
    删除下載下傳的驅動壓縮包
    :param browser_name: 浏覽器名稱
    """
    file_path = None
    if browser_name == "Chrome":
        file_path = os.path.join(BROWSER_DRIVER_DIR, os.path.basename(CHROME_DRIVER_ZIP))
    elif browser_name == "Edge":
        file_path = os.path.join(BROWSER_DRIVER_DIR, os.path.basename(EDGE_DRIVER_ZIP))
    os.remove(file_path)           

第四步,讀寫配置檔案資訊

def read_driver_mapping_json():
    """
    讀取 mapping_json
    :return: 字典格式
    """
    if os.path.exists(DRIVER_MAPPING_FILE):
        with open(DRIVER_MAPPING_FILE) as fo:
            try:
                driver_mapping_dict = json.load(fo)
            # mapping.json内容為空時,傳回空字典
            except json.decoder.JSONDecodeError:
                driver_mapping_dict = {}
    else:
        raise FileNotFoundError(f"{DRIVER_MAPPING_FILE} is not found")

    return driver_mapping_dict


def write_driver_mapping_json(browser_major_ver, latest_driver_version, driver_path, browser_name):
    """
    寫入 mapping_json
    :param browser_major_ver: 浏覽器大版本号
    :param latest_driver_version: 浏覽器驅動版本号
    :param driver_path: 驅動存放路徑
    :param browser_name: 浏覽器名稱
    """
    mapping_dict = read_driver_mapping_json()
    # 版本号在dict中(浏覽器名不在dict中)
    if browser_major_ver in mapping_dict:

        mapping_dict[browser_major_ver][browser_name] = {
                            "driver_path": driver_path,
                            "driver_version": latest_driver_version
                }
    # 大版本号不在dict中,且字典不為空
    elif browser_major_ver not in mapping_dict and mapping_dict:
        mapping_dict[browser_major_ver] = {
            browser_name:
                {
                    "driver_path": driver_path,
                    "driver_version": latest_driver_version
                }
        }
    # 字典為空
    else:
        mapping_dict = {
            browser_major_ver:
                {
                    browser_name:
                        {
                            "driver_path": driver_path,
                            "driver_version": latest_driver_version
                        }
                }
        }
        mapping_dict.update(mapping_dict)

    with open(DRIVER_MAPPING_FILE, 'w') as fo:
        json.dump(mapping_dict, fo)           

綜合

将以上步驟整合到 automatic_discover_driver 函數中,通過調用該函數傳回浏覽器驅動路徑。

def automatic_discover_driver(browser_path, browser_name="Chrome"):
    """
    偵測浏覽器驅動是否在mapping.json有記錄,否則下載下傳該驅動
    :param browser_path: 浏覽器路徑
    :param browser_name: 浏覽器名稱
    """
    browser_maj_ver = get_browser_major_version(browser_path)
    # Chrome需要擷取大版本号對應的latest release version
    # Edge 可直接用目前浏覽器版本号
    if browser_name == "Chrome":
        latest_browser_ver = get_latest_browser_version(browser_maj_ver)
    elif browser_name == "Edge":
        latest_browser_ver = get_browser_version(browser_path)
    else:
        raise Exception(f"{browser_name} is not found")

    # 讀取mapping.json内容
    mapping_dict = read_driver_mapping_json()

    # json為空 或版本号不在mapping_dict中 或浏覽器名不在mapping_dict中
    if not mapping_dict or \
            browser_maj_ver not in mapping_dict or \
            browser_name not in mapping_dict[browser_maj_ver]:

        # 下載下傳浏覽器驅動壓縮包
        download_browser_driver(latest_browser_ver, browser_name)
        # 解壓浏覽器驅動壓縮包,并傳回驅動路徑
        driver_path = unzip_driver(browser_maj_ver, browser_name)
        # 将浏覽器大版本号、浏覽器名、驅動路徑、對應的浏覽器版本号資訊寫入到mapping.json中
        write_driver_mapping_json(browser_maj_ver, latest_browser_ver, driver_path, browser_name)

        # 删除浏覽器驅動壓縮包
        remove_driver_zip(browser_name)

    # 傳回浏覽器驅動的路徑
    mapping_dict = read_driver_mapping_json()
    return mapping_dict[browser_maj_ver][browser_name]["driver_path"]           

測試

建立一個 test_search.py 檔案驗證是否可以自動下載下傳對應的浏覽器驅動。

import pytest
from time import sleep
from selenium import webdriver
from utils.driver_util import automatic_discover_driver as automatic


class TestSearch:
    _CHROME_PATH = r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
    _EDGE_PATH = r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"
    _browser = "Edge"

    def setup(self):
        driver_path = automatic(self._EDGE_PATH, self._browser)
        if self._browser == "Chrome":
            self.driver = webdriver.Chrome(driver_path)
        elif self._browser == "Edge":
            self.driver = webdriver.Edge(driver_path)

    def teardown(self):
        self.driver.close()
        self.driver.quit()

    def test_search_bing(self):
        self.driver.get("https://cn.bing.com/")
        self.driver.find_element_by_id("sb_form_q").send_keys("selenium")
        self.driver.find_element_by_id("sb_go_par").click()
        sleep(3)


if __name__ == '__main__':
    pytest.main()           

實測,成功打開浏覽器!

詳細代碼可參考:

  • https://github.com/felixzfq/AutomaticDiscoverBrowserDriver