使用Selenium打开PNETLab拓扑并开启所有节点

在玩PNETLab模拟器实验遇到的小问题,从登陆PNETLab到能登陆拓扑里的设备这一段时间是很无聊且每次都是重复动作

以前做测试就点点点没觉得是什么问题,但是设备数量多的大型拓扑,那是一个超长等待时间

想过的方法是查查API,无功而返,而且也不熟

最后使用Selenium实现功能

环境说明

  • Selenium==4.22.0
  • Python==Python 3.10.13
  • PNETLab== 5.3.13
  • ChromeDriver==126.0.6478.126
  • PNETLab关闭验证码验证

效果展示

显示窗口执行(控制台同时输出)

pnetlabstartall-2

无显示窗口执行(控制台输出)

pnetlabstartall-4

脚本代码

就不过多文字描述了,代码里的注释有说明

Note1:PNETLab的打开速度和拓扑文件的载入速度有很多影响因素(电脑硬件,拓扑文件大小,镜像等)

Note2:如果你在本地运行代码后提示错误,大概率可以通过调整显式等待时间和隐式等待时间解决(PNETLab的版本也要考虑,有可能定位元素有变动)

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
import time
import random


# 创建 ChromeOptions 对象
options = webdriver.ChromeOptions()
# 添加参数headless后不显示窗口
options.add_argument('--headless')
# 控制台不显示INFO:CONSOLE,默认0,显示与服务端的的交互内容
options.add_argument('--log-level=3')

# 4.6版本前需要指定路径,使用本地驱动
# 其实前后版本都可以使用本地驱动(本地好使,加载快,无须联网下载ChromeDriver)
# 指定浏览器驱动路径,以Chrome为例(与电脑安装的谷歌浏览器版本大版本号一致即可或添加参数忽略(自己查))
# 当前日期(20240704)使用最新版本selenium==4.22.0
# chromedriver_bin = 'D:/Plugs/ChromeDriver/126.0.6478.126/chrome-win64/chromedriver.exe'
chromedriver_bin = r'D:\Plugs\ChromeDriver\126.0.6478.126\chrome-win64\chromedriver.exe'
service = webdriver.ChromeService(executable_path=chromedriver_bin)
driver = webdriver.Chrome(service=service, options=options)

# 4.6版本之后使用默认的实例,使用如下代码(自动联网下载ChromeDriver)
# 当前日期(20240704)使用最新版本selenium==4.22.0
# service = Service()
# driver = webdriver.Chrome(options=options, service=service)

# selenium参数定义
# 显式等待设置,等待最多10秒钟,每0.5秒检查一次条件是否满足
# 该参数对需要控制的元素定位起作用
wait = WebDriverWait(driver, 10)

# 设置一个隐式等待时间,因为点击open后的loading时间不可预计
# 可能的因素有拓扑unl文件大小,电脑硬件配置
# 设置隐式等待时间为6秒,该参数全局作用,按需调整数值
driver.implicitly_wait(6)

# 本地PNETLab登陆参数定义
login_url = 'http://x.x.x.x/'
username = 'admin'
password = 'pnet'


# 自动登陆操作
def pnetlab_login():

    # 打开PNETLab登陆页面登陆
    driver.get(login_url)

    # 定位用户名输入框并输入用户名
    username_input = wait.until(EC.visibility_of_element_located((By.XPATH, '//input[@placeholder="Username"]')))
    username_input.clear()
    username_input.send_keys(username)

    # 定位密码输入框并输入密码
    password_input = wait.until(EC.visibility_of_element_located((By.XPATH, '//input[@placeholder="Password"]')))
    password_input.clear()
    password_input.send_keys(password)

    # 定位登录按钮并点击
    login_button = driver.find_element(By.XPATH, '//div[@class="button btn btn-info" and text()="Login"]')
    login_button.click()

    try:
        # 使用显式等待来等待搜索框元素出现
        search_element = wait.until(EC.visibility_of_element_located((By.XPATH,  '//input[@placeholder="Search Labs"]')))
        print("登录成功!")
        return True
    except Exception as e:
        print("登录失败或者无法找到搜索框元素.\n", str(e))
        return False


# 随机抽一个lab文件
def random_lab():
    lab_names = ('H1 CFG SPOTO Ver_1.unl',
                 'H1 Plus CFG SPOTO Ver_1.unl',
                 'H2 CFG SPOTO CCIE RS Ver_1.unl',
                 'H2 Plus CFG SPOTO Ver_1.unl',
                 "H3 SPOTO CCIE RS Ver_1.unl")
    chosen_lab = random.choice(lab_names)
    return chosen_lab


def main():
    try:
        # 登陆
        sign_in = pnetlab_login()
        if not sign_in:
            print('>>>登陆有问题')
            return

        # 随机抽
        chosen_lab = random_lab()
        print('-' * 50 + f'\n本次抽中的lab是:{chosen_lab[:-4]}')
        # 搜索框输入lab文件名
        search_element = wait.until(EC.visibility_of_element_located((By.XPATH,  '//input[@placeholder="Search Labs"]')))
        search_element.clear()
        search_element.send_keys(chosen_lab)

        # 等待第一个搜索结果可见并点击
        time.sleep(1)
        first_button = wait.until(
            EC.visibility_of_element_located((By.XPATH, "//div[@id='search_lab_suggest']/div[@class='button'][1]")))
        first_button.click()

        # 等待第二个搜索结果可见并点击
        # second_button = wait.until(
        #     EC.visibility_of_element_located((By.XPATH, "//div[@id='search_lab_suggest']/div[@class='button'][2]")))
        # second_button.click()

        # 点击open按钮
        open_button = driver.find_element(By.CSS_SELECTOR, "div.box_flex > div.btn-info:nth-child(1)")
        open_button.click()

        # 等不到加载完成页面报错,调大隐式等待时间
        # 触发左侧菜单的元素,左下角log图片class类
        leftmenu_trigger = driver.find_element(By.CLASS_NAME, "logo_img")

        # 创建 ActionChains 对象
        actions = ActionChains(driver)

        # 移动鼠标到左侧菜单触发元素上,触发菜单显示
        actions.move_to_element(leftmenu_trigger).perform()

        # 点击Setup Nodes
        setup_nodes = driver.find_element(By.XPATH, '//span[text()="Setup Nodes"]')
        setup_nodes.click()

        # Start All Nodes
        start_all = driver.find_element(By.CLASS_NAME,"action-nodesstart")
        start_all.click()

        # 查看已启动的IOL Nodes 的数值
        # 移动鼠标到左侧菜单触发元素上,触发菜单显示
        actions.move_to_element(leftmenu_trigger).perform()
        # 点击System status
        system_status = driver.find_element(By.XPATH, '//span[text()="System Status"]')
        system_status.click()

        print('-' * 50 + '\n正在启动设备\n')

        # 30秒后获取已启动设备数值
        # time.sleep(30)

        # 循环获取启动的设备数值,间隔10秒获取一次,共5分钟
        x = 1
        while x < 30:
            time.sleep(10)
            started = wait.until(EC.presence_of_element_located((By.XPATH, "//td/strong[@style='color: red;']")))

            # 打印已启动设备数值
            print("已启动设备数:", started.text)
            if int(started.text) >= 40:
                break
            x += 1
        time.sleep(5)
        print(f'设备启动完成,打开链接{login_url},开始实验\n' * 3 + '-' * 50)

         # //////////////////////////////////////////////////////////////////////////////////
        # # destroy lab 退出
        # # 测试用,随后可能用不到
        # # time.sleep(10)
        # print('正在关闭设备\n')
        # # 移动鼠标到左侧菜单触发元素上,触发菜单显示
        # actions.move_to_element(leftmenu_trigger).perform()
        # # 点击Destroy Lab
        # destroy_lab = driver.find_element(By.XPATH, '//span[text()="Destroy Lab"]')
        # destroy_lab.click()
        # 
        # # 定位弹窗的 Destroy 按钮
        # destroy_button = driver.find_element(By.CLASS_NAME, "swal2-confirm")
        # 
        # # 定位弹窗的 Cancel 按钮
        # # cancel_button = driver.find_element(By.CLASS_NAME, "swal2-cancel")
        # 
        # # 二次确认点击
        # destroy_button.click()
        # 
        # # 循环获取启用的设备数值,间隔10秒获取一次,共5分钟
        # x = 600
        # while x > 0:
        #     time.sleep(10)
        #     started = WebDriverWait(driver, 10).until(
        #         EC.presence_of_element_located((By.XPATH, "//td/strong[@style='color: red;']"))
        #     )
        # 
        #     # 打印剩余启动设备数值
        #     print("剩余启动设备数:", started.text)
        #     if int(started.text) < 10:
        #         break
        #     x -= 1
        # 
        # print('设备已关闭\n' * 3 + '-' * 50)
        # time.sleep(10)
        # //////////////////////////////////////////////////////////////////////////////////

    except Exception as e:
        print(e)
        print('>>>OPS,FUCKED UP')
    finally:
        # 关闭浏览器驱动
        driver.quit()


if __name__ == '__main__':
    main()

最后

  • 忽略代码中变量名的命名拼写错误,词汇量紧张
  • 部分代码来自GPT,官网示例(注意看版本对应的示例)
  • 大部分时间都耗在定位元素了,pnet为了玩花样或是抄来不想改这html前端也是无限套壳
  • 有些是前端渲染代码是通过java函数生成的,这就有点不好玩了
  • 欢迎“来电”来函探讨。