52pjdana 发表于 2024-9-2 17:55

c#和python通用,使用Selenium自动化测试库单点登录、自动修改密码等各种自动化操作

接上个帖子,之前使用的是油猴脚本tampermonkey+js代码,通过在浏览器安装插件执行js脚本的方式自动填充账号密码登录框,实现另类的单点登录。

后续实施起来比较麻烦,需要帮用户安装脚本文件,并且脚本更新比较麻烦,需要手动一个个替换js代码,另外油猴脚本插件存在浏览器不兼容或者被删除的可能。
于是便找到了Selenium这个库,通过学习和编写测试代码也比较完善的实现了这个功能,记录和分享一下代码和解决方案。

Selenium在Python和c#中均可直接引入并使用,由于我的业务是做来后台单点登录和一键修改密码的操作,所以通过后台API调用的方式来实现是最完美的,而.net程序
的打包和更新更方便,所以便从python更改成了c#程序

话不多说,上代码和实现步骤:

[*]使用vs或者Rider创建一个.net core解决方案(不需要写web api项目的话创建一个简单的桌面解决方案也行)
[*]引入Selenium库,大概就这些:
using OpenQA.Selenium.Interactions;
using SSO.WEBAPI.Controllers;

namespace SSO.WEBAPI.Services;

using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
using SeleniumExtras.WaitHelpers;
    3.使用这个库执行自动化测试。Selenium的大概功能就是可以控制打开浏览器服务窗口,然后这个浏览器窗口是完全由Selenium控制的,你可以通过代码来控制浏览器打开指定的web页面,然后获取页面上的所有元素,通过调试将需要的元素赋值、点击按钮、点击链接、前往新的页面、在新的页面继续控制各个元素的事件和值,很多网站很少会更新改这个代码,所以大概写好一次就能一直用了。我这边以登录修改密码为例:
try
      {
            // 配置ChromeDriver服务
            _service = ChromeDriverService.CreateDefaultService();
            // _service.HideCommandPromptWindow = true; // 隐藏命令行窗口

            // 启动Chrome浏览器
            _options = new ChromeOptions();
            _options.AddArgument("--start-maximized");
            _options.AddArgument("--headless"); // 无头模式

            // 启动ChromeDriver时传入service和options
            IWebDriver driver = new ChromeDriver(_service, _options, TimeSpan.FromSeconds(5));

            // 导航到GitLab登录页面
            driver.Navigate().GoToUrl(pwdRequest.Url);

            // 等待页面加载(可以使用显式等待,简化为线程休眠)
            Thread.Sleep(100);

            // 查找并填充用户名字段
            var usernameField = driver.FindElement(By.Id("user_login"));
            usernameField.SendKeys(pwdRequest.Username); // 替换为你的GitLab用户名

            // 查找并填充密码字段
            var passwordField = driver.FindElement(By.Id("user_password"));
            passwordField.SendKeys(pwdRequest.Password); // 替换为你的GitLab密码

            // 查找并点击登录按钮
            var loginButton = driver.FindElement(By.CssSelector("button"));
            loginButton.Click();

            // 等待登录过程完成(可以根据页面变化调整等待时间)
            // System.Threading.Thread.Sleep(1000);

            // 检查是否登录成功(可选,验证是否跳转到登录后的页面)
            var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(2));

            // 等待特定ID的元素出现
            var sidebarHeading = wait.Until(ExpectedConditions.ElementIsVisible(By.Id("super-sidebar-heading")));

            //特定元素出现,前往新页面
            if (sidebarHeading.Displayed)
            {
                driver.Navigate().GoToUrl(pwdRequest.PwdUrl);
                Thread.Sleep(100);
                //找到元素并填充
                var oldPwd = driver.FindElement(By.Id("user_password"));
                oldPwd.SendKeys(pwdRequest.Password);
                var newPwd = driver.FindElement(By.Id("user_new_password"));
                newPwd.SendKeys(pwdRequest.NewPassword);
                var passPwd = driver.FindElement(By.Id("user_password_confirmation"));
                passPwd.SendKeys(pwdRequest.NewPassword);
                var saveButton = driver.FindElement(By.CssSelector("button"));
                saveButton.Click();
                return true;
            }

            return false;
      }
      catch (Exception e)
      {
            // Log.Error("An error occurred: " + e.Message);
            return false;
      }

代码看不清可以看图片附件,每一步都有注释分析的,查找页面的元素直接浏览器f12打开控制台找即可,演示图也在最后

另外调试获取页面元素的方式很多很多,通过id,class,name,甚至文本内容,这个如果有学前段的朋友应该挺了解的。
注意如果页面中有iframe标签的话,需要切换到iframe标签内才能获取iframe标签内的元素,我被这个坑了好久:
// 切换到 iframe
var iframeElement = driver.FindElement(By.ClassName("lui_widget_iframe"));
driver.SwitchTo().Frame(iframeElement);

52pjdana 发表于 2024-9-2 17:59

如果需要调用源码的话我后续再整理分享一下

yanggo 发表于 2024-9-2 22:09

谢谢分享!收藏先

jellycici 发表于 2024-9-3 23:51

自动化测试学习中,谢谢分享

rsy1479 发表于 2024-9-6 08:07

学习中,希望自己越来越强大💪('ω'💪)!

tzq001 发表于 2024-9-6 10:00

求PYthon源码案例

52pjdana 发表于 2024-9-6 11:19

tzq001 发表于 2024-9-6 10:00
求PYthon源码案例

这是python的示例,用的djiango写的web案例
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
import json
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.common.exceptions import NoSuchElementException, TimeoutException
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


# 初始化 WebDriver
def initialize_login_driver():
    chrome_options = Options()
    chrome_options.add_argument("--start-maximized")
    chrome_options.add_argument("--disable-extensions")
    chrome_options.add_argument("--disable-popup-blocking")
    chrome_options.add_argument("--disable-infobars")
    chrome_options.add_argument("--disable-notifications")
    chrome_options.add_experimental_option("useAutomationExtension", False)
    chrome_options.add_experimental_option("detach", True)# 保持浏览器打开
    # chrome_options.add_argument("--headless")# Uncomment if you want headless mode

    driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=chrome_options)
    return driver

def initialize_password_driver():
    chrome_options = Options()
    chrome_options.add_argument("--start-maximized")
    chrome_options.add_argument("--disable-extensions")
    chrome_options.add_argument("--disable-popup-blocking")
    chrome_options.add_argument("--disable-infobars")
    chrome_options.add_argument("--disable-notifications")
    chrome_options.add_experimental_option("useAutomationExtension", False)
    chrome_options.add_experimental_option("detach", True)# 保持浏览器打开
    # chrome_options.add_argument("--headless")# Uncomment if you want headless mode

    driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=chrome_options)
    return driver



def login_to_gitlab(driver, url, username, password):
    try:
      driver.get(url)
      wait = WebDriverWait(driver, 10)
      username_input = wait.until(EC.presence_of_element_located((By.ID, 'user_login')))
      password_input = wait.until(EC.presence_of_element_located((By.ID, 'user_password')))
      username_input.clear()
      username_input.send_keys(username)
      password_input.clear()
      password_input.send_keys(password)
      login_button = wait.until(EC.element_to_be_clickable((By.XPATH, '//button[@type="submit"]')))
      login_button.click()
      wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'mobile-overlay')))
      return {"message": "Login successful"}
    except TimeoutException:
      return {"error": "Timeout waiting for elements"}
    except NoSuchElementException:
      return {"error": "Element not found"}
    except Exception as e:
      return {"error": str(e)}
    finally:
      return {"message": "Login successful"}
      # driver.quit()


@csrf_exempt
def login_view(request):
    if request.method == 'POST':
      try:
            data = json.loads(request.body)
            login_type = data.get('type')
            url = data.get('url')
            username = data.get('username')
            password = data.get('password')

            if not url or not username or not password:
                return JsonResponse({"error": "Missing URL, username, or password"}, status=400)

            driver = initialize_login_driver()
            if login_type == "gitlab":
                result = login_to_gitlab(driver, url, username, password)
            return JsonResponse(result)
      except json.JSONDecodeError:
            return JsonResponse({"error": "Invalid JSON"}, status=400)
    else:
      return JsonResponse({"error": "Only POST method allowed"}, status=400)


@csrf_exempt
def reset_password(request):
    if request.method == 'POST':
      try:
            data = json.loads(request.body)
            reset_type = data.get('type')
            login_url = data.get('url')
            update_password_url = data.get("password_url")
            username = data.get('username')
            password = data.get('password')
            new_password = data.get("new_password")

            if not login_url or not username or not password:
                return JsonResponse({"error": "Missing URL, username, or password"}, status=400)

            driver = initialize_password_driver()
            if reset_type == "gitlab":
                result = reset_to_gitlab(driver, login_url, username, password,update_password_url,new_password)

            return JsonResponse(result)
      except json.JSONDecodeError:
            return JsonResponse({"error": "Invalid JSON"}, status=400)
    else:
      return JsonResponse({"error": "Only POST method allowed"}, status=400)


def reset_to_gitlab(driver, url, username, password, update_url, new_pwd):
    try:
      print("Navigating to login page...")
      driver.get(url)
      wait = WebDriverWait(driver, 10)

      # 登录操作
      print("Looking for username input field...")
      username_input = wait.until(EC.presence_of_element_located((By.ID, 'user_login')))
      print("Found username input field.")
      print("Looking for password input field...")
      password_input = wait.until(EC.presence_of_element_located((By.ID, 'user_password')))
      print("Found password input field.")

      username_input.clear()
      username_input.send_keys(username)
      password_input.clear()
      password_input.send_keys(password)

      print("Looking for login button...")
      login_button = wait.until(EC.element_to_be_clickable((By.XPATH, '//button[@type="submit"]')))
      print("Found login button, clicking...")
      login_button.click()

      print("Waiting for page to load after login...")
      wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'mobile-overlay')))
      print("Login successful, navigating to update password page...")

      # 跳转到修改密码页面
      driver.get(update_url)
      print(f"Navigated to {update_url}, waiting for password fields...")

      # 等待密码修改页面元素加载
      wait.until(EC.presence_of_element_located((By.ID, 'user_password')))
      print("Found old password field.")

      # 修改密码
      old_pwd_input = driver.find_element(By.ID, 'user_password')
      print("Found old password input field.")
      new_password_input = driver.find_element(By.ID, 'user_new_password')
      print("Found new password input field.")
      user_password_confirmation = driver.find_element(By.ID, 'user_password_confirmation')
      print("Found password confirmation input field.")

      old_pwd_input.clear()
      old_pwd_input.send_keys(password)
      new_password_input.clear()
      new_password_input.send_keys(new_pwd)
      user_password_confirmation.clear()
      user_password_confirmation.send_keys(new_pwd)

      print("Password updated successfully.")
      return {"message": "Password updated successfully"}

    except TimeoutException:
      print("Timeout waiting for elements")
      return {"error": "Timeout waiting for elements"}
    except NoSuchElementException:
      print("Element not found")
      return {"error": "Element not found"}
    except Exception as e:
      print(f"An error occurred: {str(e)}")
      return {"error": str(e)}
    # finally:
    #   driver.get(update_url)
      # driver.quit()

tzq001 发表于 2024-9-7 15:40

52pjdana 发表于 2024-9-6 11:19
这是python的示例,用的djiango写的web案例
from django.http import JsonRes ...

十分感谢分享
页: [1]
查看完整版本: c#和python通用,使用Selenium自动化测试库单点登录、自动修改密码等各种自动化操作