概述
Selenium是一款免费的分布式的自动化测试工具,支持多种开发语言,无论是C、 java、ruby、python、或是C# ,你都可以通过selenium完成自动化测试。本文以一个简单的小例子,简述C# 利用Selenium进行浏览器的模拟操作,仅供学习分享使用,如有不足之处,还请指正。
涉及知识点
要实现本例的功能,除了要掌握Html ,JavaScript,CSS等基础知识,还涉及以下知识点:.
-
log4net:主要用于日志的记录和存储,本例采用log4net进行日志记录,便于过程跟踪和问题排查,关于log4net的配置和介绍,之前已有说明,本文不做赘述。
-
Queue:队列,先进先出模式,本文主要用于将日志信息保存于队列中,然后再显示到页面上,其中Enqueue用于添加内容到结尾处,Dequeue用于返回并移除一个位置的对象。
-
IWebDriver:浏览器驱动接口,所有的关于浏览器的操作都可以通过此接口进行,不同浏览器有不同的实现类,如:IE浏览器(InternetExplorerDriver)Chrome浏览器(ChromeDriver)等。
-
BackgroundWorker:后台工作线程,区别于主线程,通过事件触发不同的状态。
Selenium安装
本例开发工具为VS2019,通过NuGet进行需要的软件包的安装与管理,如下所示:

示例效果图
本例采用Chrome浏览器,用于监控某一个网站并获取相应内容,如下所示:

Selenium示例介绍
定义一个webDriver,如下所示:
//谷歌浏览器ChromeOptions options = new ChromeOptions();this.driver = new ChromeDriver(options);
通过ID获取元素并填充内容和触发事件,如下所示:
this.driver.FindElement(By.Id("email")).SendKeys(username);this.driver.FindElement(By.Id("password")).SendKeys(password);//# 7. 点击登录按钮this.driver.FindElement(By.Id("sign-in")).Click();
通过XPath获取元素,如下所示:
string xpath1 = "//div[@class=\"product-list\"]/div[@class=\"product\"]/div[@class=\"price-and-detail\"]/div[@class=\"price\"]/span[@class=\"noStock\"]";string txt = this.driver.FindElement(By.XPath(xpath1)).Text;
核心代码
主要的核心代码,就是浏览器的元素定位查找和事件触发,如下所示:
using OpenQA.Selenium;using OpenQA.Selenium.IE;using OpenQA.Selenium.Chrome;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading;using System.Threading.Tasks;namespace AiSmoking.Core{public class Smoking{/// <summary>/// 是否正在运行/// </summary>private bool running = false;/// <summary>/// 驱动/// </summary>private IWebDriver driver = null;/// <summary>/// # 无货/// </summary>private string no_stock = "Currently Out of Stock";/// <summary>/// # 线程等待秒数/// </summary>private int wait_sec = 2;private Dictionary<string, string> cfg_info;private string work_path = string.Empty;/// <summary>/// 构造函数/// </summary>public Smoking(){}/// <summary>/// 带参构造函数/// </summary>/// <param name="cfg_info"></param>/// <param name="work_path"></param>public Smoking(Dictionary<string, string> cfg_info,string work_path){this.cfg_info = cfg_info;this.work_path = work_path;this.wait_sec = int.Parse(cfg_info["wait_sec"]);//# 如果小于2,则等于2this.wait_sec = (this.wait_sec < 2 ? 2 : this.wait_sec);this.wait_sec = this.wait_sec * 1000;}/// <summary>/// 开始跑/// </summary>public void startRun(){//"""运行起来"""try{this.running = true;string url = this.cfg_info["url"];string username = this.cfg_info["username"];string password = this.cfg_info["password"];string item_id = this.cfg_info["item_id"];if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(item_id)){LogHelper.put("配置信息不全,请检查config.cfg文件是否为空,然后再重启");return;}if (this.driver == null){string explorer = this.cfg_info["explorer"];if (explorer == "Chrome"){//谷歌浏览器ChromeOptions options = new ChromeOptions();this.driver = new ChromeDriver(options);}else{//默认IEvar options = new InternetExplorerOptions();//options.AddAdditionalCapability.('encoding=UTF-8')//options.add_argument('Accept= text / css, * / *')//options.add_argument('Accept - Language= zh - Hans - CN, zh - Hans;q = 0.5')//options.add_argument('Accept - Encoding= gzip, deflate')//options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko')//# 2. 定义浏览器驱动对象this.driver = new InternetExplorerDriver(options);}}this.run(url, username, password, item_id);}catch (Exception e){LogHelper.put("运行过程中出错,请重新打开再试"+e.StackTrace);}}/// <summary>/// 运行/// </summary>/// <param name="url"></param>/// <param name="username"></param>/// <param name="password"></param>/// <param name="item_id"></param>private void run(string url, string username, string password, string item_id){//"""运行起来"""//# 3. 访问网站this.driver.Navigate().GoToUrl(url);//# 4. 最大化窗口this.driver.Manage().Window.Maximize();if (this.checkIsExists(By.LinkText("账户登录"))){//# 判断是否登录:未登录this.login(username, password);}if (this.checkIsExists(By.PartialLinkText("欢迎回来"))){//# 判断是否登录:已登录LogHelper.put("登录成功,下一步开始工作了");this.working(item_id);}else{LogHelper.put("登录失败,请设置账号密码");}}/// <summary>/// 停止运行/// </summary>public void stopRun(){//"""停止"""try{this.running = false;//# 如果驱动不为空,则关闭//self.close_browser_nicely(self.__driver)if (this.driver != null){this.driver.Quit();//# 关闭后切要为None,否则启动报错this.driver = null;}}catch (Exception e){//print('Stop Failure')}finally{this.driver = null;}}private void login(string username, string password){//# 5. 点击链接跳转到登录页面this.driver.FindElement(By.LinkText("账户登录")).Click();//# 6. 输入账号密码//# 判断是否加载完成if (this.checkIsExists(By.Id("email"))){this.driver.FindElement(By.Id("email")).SendKeys(username);this.driver.FindElement(By.Id("password")).SendKeys(password);//# 7. 点击登录按钮this.driver.FindElement(By.Id("sign-in")).Click();}}/// <summary>/// 工作状态/// </summary>/// <param name="item_id"></param>private void working(string item_id){while (this.running){try{//# 正常获取信息if (this.checkIsExists(By.Id("string"))){this.driver.FindElement(By.Id("string")).Clear();this.driver.FindElement(By.Id("string")).SendKeys(item_id);this.driver.FindElement(By.Id("string")).SendKeys(Keys.Enter);}//# 判断是否查询到商品string xpath = "//div[@class=\"specialty-header search\"]/div[@class=\"specialty-description\"]/div[@class=\"gt-450\"]/span[2] ";if (this.checkIsExists(By.XPath(xpath))){int count = int.Parse(this.driver.FindElement(By.XPath(xpath)).Text);if (count < 1){Thread.Sleep(this.wait_sec);LogHelper.put("没有查询到item id =" + item_id + "对应的信息");continue;}}else{Thread.Sleep(this.wait_sec);LogHelper.put("没有查询到item id2 =" + item_id + "对应的信息");continue;}//# 判断当前库存是否有货string xpath1 = "//div[@class=\"product-list\"]/div[@class=\"product\"]/div[@class=\"price-and-detail\"]/div[@class=\"price\"]/span[@class=\"noStock\"]";if (this.checkIsExists(By.XPath(xpath1))){string txt = this.driver.FindElement(By.XPath(xpath1)).Text;if (txt == this.no_stock){//# 当前无货Thread.Sleep(this.wait_sec);LogHelper.put("查询一次" + item_id + ",无货");continue;}}//# 链接path1string xpath2 = "//div[@class=\"product-list\"]/div[@class=\"product\"]/div[@class=\"imgDiv\"]/a";//# 判断是否加载完毕//# this.waiting((By.CLASS_NAME, "imgDiv"))if (this.checkIsExists(By.XPath(xpath2))){this.driver.FindElement(By.XPath(xpath2)).Click();Thread.Sleep(this.wait_sec);//# 加入购物车if (this.checkIsExists(By.ClassName("add-to-cart"))){this.driver.FindElement(By.ClassName("add-to-cart")).Click();LogHelper.put("加入购物车成功,商品item-id:" + item_id);break;}else{LogHelper.put("未找到加入购物车按钮");}}else{LogHelper.put("没有查询到,可能是商品编码不对,或者已下架");}Thread.Sleep(this.wait_sec);}catch (Exception e){Thread.Sleep(this.wait_sec);LogHelper.put(e);}}}/// <summary>/// 判断是否存在/// </summary>/// <param name="by"></param>/// <returns></returns>private bool checkIsExists(By by){try{int i = 0;while (this.running && i < 3){if (this.driver.FindElements(by).Count > 0){break;}else{Thread.Sleep(this.wait_sec);i = i + 1;}}return this.driver.FindElements(by).Count > 0;}catch (Exception e){LogHelper.put(e);return false;}}}}
关于日志帮助类,代码如下:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using log4net;[assembly: log4net.Config.XmlConfigurator(Watch = true)]namespace AiSmoking.Core{/// <summary>/// 日志帮助类/// </summary>public static class LogHelper{/// <summary>/// 日志实例/// </summary>private static ILog logInstance = LogManager.GetLogger("smoking");private static Queue<string> queue = new Queue<string>(2000);public static void put(string msg){queue.Enqueue(msg);WriteLog(msg, LogLevel.Info);}public static void put(Exception ex){WriteLog(ex.StackTrace, LogLevel.Error);}public static string get(){if (queue.Count > 0){return queue.Dequeue();}else{return string.Empty;}}public static void WriteLog(string message, LogLevel level){switch (level){case LogLevel.Debug:logInstance.Debug(message);break;case LogLevel.Error:logInstance.Error(message);break;case LogLevel.Fatal:logInstance.Fatal(message);break;case LogLevel.Info:logInstance.Info(message);break;case LogLevel.Warn:logInstance.Warn(message);break;default:logInstance.Info(message);break;}}}public enum LogLevel{Debug = 0,Error = 1,Fatal = 2,Info = 3,Warn = 4}}