C# 利用Selenium实现浏览器自动化操作

概述

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进行需要的软件包的安装与管理,如下所示:

C# 利用Selenium实现浏览器自动化操作

示例效果图

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

C# 利用Selenium实现浏览器自动化操作

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,则等于2            this.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                    {                        //默认IE                        var 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;                        }                    }                    //# 链接path1                    string 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     } }