zoukankan      html  css  js  c++  java
  • 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进行需要的软件包的安装与管理,如下所示:

    示例效果图

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

    Selenium示例介绍

    定义一个webDriver,如下所示:

    1 //谷歌浏览器
    2 ChromeOptions options = new ChromeOptions();
    3 this.driver = new ChromeDriver(options);

    通过ID获取元素并填充内容和触发事件,如下所示:

    1 this.driver.FindElement(By.Id("email")).SendKeys(username);
    2 this.driver.FindElement(By.Id("password")).SendKeys(password);
    3 //# 7. 点击登录按钮
    4 this.driver.FindElement(By.Id("sign-in")).Click();

    通过XPath获取元素,如下所示:

    1 string xpath1 = "//div[@class="product-list"]/div[@class="product"]/div[@class="price-and-detail"]/div[@class="price"]/span[@class="noStock"]";
    2 string txt = this.driver.FindElement(By.XPath(xpath1)).Text;

    核心代码

    主要的核心代码,就是浏览器的元素定位查找和事件触发,如下所示:

      1 using OpenQA.Selenium;
      2 using OpenQA.Selenium.IE;
      3 using OpenQA.Selenium.Chrome;
      4 using System;
      5 using System.Collections.Generic;
      6 using System.Linq;
      7 using System.Text;
      8 using System.Threading;
      9 using System.Threading.Tasks;
     10 
     11 namespace AiSmoking.Core
     12 {
     13     public class Smoking
     14     {
     15         /// <summary>
     16         /// 是否正在运行
     17         /// </summary>
     18         private bool running = false;
     19 
     20         /// <summary>
     21         /// 驱动
     22         /// </summary>
     23         private IWebDriver driver = null;
     24 
     25 
     26         /// <summary>
     27         /// # 无货
     28         /// </summary>
     29         private string no_stock = "Currently Out of Stock";
     30 
     31 
     32         /// <summary>
     33         ///   # 线程等待秒数
     34         /// </summary>
     35         private int wait_sec = 2;
     36 
     37         private Dictionary<string, string> cfg_info;
     38 
     39         private string work_path = string.Empty;
     40 
     41         /// <summary>
     42         /// 构造函数
     43         /// </summary>
     44         public Smoking()
     45         {
     46 
     47         }
     48 
     49         /// <summary>
     50         /// 带参构造函数
     51         /// </summary>
     52         /// <param name="cfg_info"></param>
     53         /// <param name="work_path"></param>
     54         public Smoking(Dictionary<string, string> cfg_info,string work_path)
     55         {
     56             this.cfg_info = cfg_info;
     57             this.work_path = work_path;
     58             this.wait_sec = int.Parse(cfg_info["wait_sec"]);
     59             //# 如果小于2,则等于2
     60             this.wait_sec = (this.wait_sec < 2 ? 2 : this.wait_sec);
     61             this.wait_sec = this.wait_sec * 1000;
     62         }
     63 
     64         /// <summary>
     65         /// 开始跑
     66         /// </summary>
     67         public void startRun()
     68         {
     69             //"""运行起来"""
     70             try
     71             {
     72                 this.running = true;
     73                 string url = this.cfg_info["url"];
     74                 string username = this.cfg_info["username"];
     75                 string password = this.cfg_info["password"];
     76                 string item_id = this.cfg_info["item_id"];
     77                 if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(item_id))
     78                 {
     79                     LogHelper.put("配置信息不全,请检查config.cfg文件是否为空,然后再重启");
     80                     return;
     81                 }
     82                 if (this.driver == null)
     83                 {
     84                     string explorer = this.cfg_info["explorer"];
     85                     if (explorer == "Chrome")
     86                     {
     87                         //谷歌浏览器
     88                         ChromeOptions options = new ChromeOptions();
     89                         this.driver = new ChromeDriver(options);
     90                     }
     91                     else
     92                     {
     93                         //默认IE
     94                         var options = new InternetExplorerOptions();
     95                         //options.AddAdditionalCapability.('encoding=UTF-8')
     96                         //options.add_argument('Accept= text / css, * / *')
     97                         //options.add_argument('Accept - Language= zh - Hans - CN, zh - Hans;q = 0.5')
     98                         //options.add_argument('Accept - Encoding= gzip, deflate')
     99                         //options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko')
    100                         //# 2. 定义浏览器驱动对象
    101                         this.driver = new InternetExplorerDriver(options);
    102                     }
    103                 }
    104                 this.run(url, username, password, item_id);
    105             }
    106             catch (Exception e)
    107             {
    108                 LogHelper.put("运行过程中出错,请重新打开再试"+e.StackTrace);
    109             }
    110         }
    111 
    112 
    113         /// <summary>
    114         /// 运行
    115         /// </summary>
    116         /// <param name="url"></param>
    117         /// <param name="username"></param>
    118         /// <param name="password"></param>
    119         /// <param name="item_id"></param>
    120         private void run(string url, string username, string password, string item_id)
    121         {
    122             //"""运行起来"""
    123             //# 3. 访问网站
    124             this.driver.Navigate().GoToUrl(url);
    125             //# 4. 最大化窗口
    126             this.driver.Manage().Window.Maximize();
    127             if (this.checkIsExists(By.LinkText("账户登录")))
    128             {
    129                 //# 判断是否登录:未登录
    130                 this.login(username, password);
    131             }
    132             if (this.checkIsExists(By.PartialLinkText("欢迎回来")))
    133             {
    134                 //# 判断是否登录:已登录
    135                 LogHelper.put("登录成功,下一步开始工作了");
    136                 this.working(item_id);
    137             }
    138             else
    139             {
    140                 LogHelper.put("登录失败,请设置账号密码");
    141             }
    142         }
    143 
    144         /// <summary>
    145         /// 停止运行
    146         /// </summary>
    147         public void stopRun()
    148         {
    149             //"""停止"""
    150             try
    151             {
    152                 this.running = false;
    153                 //# 如果驱动不为空,则关闭
    154                 //self.close_browser_nicely(self.__driver)
    155                 if (this.driver != null)
    156                 {
    157                     this.driver.Quit();
    158                     //# 关闭后切要为None,否则启动报错
    159                     this.driver = null;
    160                 }
    161             }
    162             catch (Exception e)
    163             {
    164                 //print('Stop Failure')
    165             }
    166             finally
    167             {
    168                 this.driver = null;
    169             }
    170         }
    171 
    172 
    173         private void login(string username, string password)
    174         {
    175             //# 5. 点击链接跳转到登录页面
    176             this.driver.FindElement(By.LinkText("账户登录")).Click();
    177             //# 6. 输入账号密码
    178             //# 判断是否加载完成
    179             if (this.checkIsExists(By.Id("email")))
    180             {
    181                 this.driver.FindElement(By.Id("email")).SendKeys(username);
    182                 this.driver.FindElement(By.Id("password")).SendKeys(password);
    183                 //# 7. 点击登录按钮
    184                 this.driver.FindElement(By.Id("sign-in")).Click();
    185             }
    186         }
    187 
    188         /// <summary>
    189         /// 工作状态
    190         /// </summary>
    191         /// <param name="item_id"></param>
    192         private void working(string item_id)
    193         {
    194             while (this.running)
    195             {
    196                 try
    197                 {
    198                     //# 正常获取信息
    199                     if (this.checkIsExists(By.Id("string")))
    200                     {
    201                         this.driver.FindElement(By.Id("string")).Clear();
    202                         this.driver.FindElement(By.Id("string")).SendKeys(item_id);
    203                         this.driver.FindElement(By.Id("string")).SendKeys(Keys.Enter);
    204                     }
    205                     //# 判断是否查询到商品
    206                     string xpath = "//div[@class="specialty-header search"]/div[@class="specialty-description"]/div[@class="gt-450"]/span[2] ";
    207                     if (this.checkIsExists(By.XPath(xpath)))
    208                     {
    209                         int count = int.Parse(this.driver.FindElement(By.XPath(xpath)).Text);
    210                         if (count < 1)
    211                         {
    212                             Thread.Sleep(this.wait_sec);
    213                             LogHelper.put("没有查询到item id =" + item_id + "对应的信息");
    214                             continue;
    215                         }
    216                     }
    217                     else
    218                     {
    219                         Thread.Sleep(this.wait_sec);
    220                         LogHelper.put("没有查询到item id2 =" + item_id + "对应的信息");
    221                         continue;
    222                     }
    223                     //# 判断当前库存是否有货
    224 
    225                     string xpath1 = "//div[@class="product-list"]/div[@class="product"]/div[@class="price-and-detail"]/div[@class="price"]/span[@class="noStock"]";
    226                     if (this.checkIsExists(By.XPath(xpath1)))
    227                     {
    228                         string txt = this.driver.FindElement(By.XPath(xpath1)).Text;
    229                         if (txt == this.no_stock)
    230                         {
    231                             //# 当前无货
    232                             Thread.Sleep(this.wait_sec);
    233                             LogHelper.put("查询一次" + item_id + ",无货");
    234                             continue;
    235                         }
    236                     }
    237                     //# 链接path1
    238                     string xpath2 = "//div[@class="product-list"]/div[@class="product"]/div[@class="imgDiv"]/a";
    239                     //# 判断是否加载完毕
    240                     //# this.waiting((By.CLASS_NAME, "imgDiv"))
    241                     if (this.checkIsExists(By.XPath(xpath2)))
    242                     {
    243                         this.driver.FindElement(By.XPath(xpath2)).Click();
    244                         Thread.Sleep(this.wait_sec);
    245                         //# 加入购物车
    246                         if (this.checkIsExists(By.ClassName("add-to-cart")))
    247                         {
    248                             this.driver.FindElement(By.ClassName("add-to-cart")).Click();
    249                             LogHelper.put("加入购物车成功,商品item-id:" + item_id);
    250                             break;
    251                         }
    252                         else
    253                         {
    254                             LogHelper.put("未找到加入购物车按钮");
    255                         }
    256                     }
    257                     else
    258                     {
    259                         LogHelper.put("没有查询到,可能是商品编码不对,或者已下架");
    260                     }
    261                     Thread.Sleep(this.wait_sec);
    262                 }
    263                 catch (Exception e)
    264                 {
    265                     Thread.Sleep(this.wait_sec);
    266                     LogHelper.put(e);
    267                 }
    268             }
    269         }
    270 
    271         /// <summary>
    272         /// 判断是否存在
    273         /// </summary>
    274         /// <param name="by"></param>
    275         /// <returns></returns>
    276         private bool checkIsExists(By by)
    277         {
    278             try
    279             {
    280                 int i = 0;
    281                 while (this.running && i < 3)
    282                 {
    283                     if (this.driver.FindElements(by).Count > 0)
    284                     {
    285                         break;
    286                     }
    287                     else
    288                     {
    289                         Thread.Sleep(this.wait_sec);
    290                         i = i + 1;
    291                     }
    292                 }
    293                 return this.driver.FindElements(by).Count > 0;
    294             }
    295             catch (Exception e)
    296             {
    297                 LogHelper.put(e);
    298                 return false;
    299             }
    300         }
    301 
    302     }
    303 }
    View Code

    关于日志帮助类,代码如下:

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.Threading.Tasks;
     6 using log4net;
     7 
     8 [assembly: log4net.Config.XmlConfigurator(Watch = true)]
     9 namespace AiSmoking.Core
    10 {
    11     /// <summary>
    12     /// 日志帮助类
    13     /// </summary>
    14     public static class LogHelper
    15     {
    16         /// <summary>
    17         /// 日志实例
    18         /// </summary>
    19         private static ILog logInstance = LogManager.GetLogger("smoking");
    20 
    21         private static Queue<string> queue = new Queue<string>(2000);
    22 
    23         public static void put(string msg)
    24         {
    25             queue.Enqueue(msg);
    26             WriteLog(msg, LogLevel.Info);
    27         }
    28 
    29         public static void put(Exception ex)
    30         {
    31             WriteLog(ex.StackTrace, LogLevel.Error);
    32         }
    33 
    34         public static string get()
    35         {
    36             if (queue.Count > 0)
    37             {
    38                 return queue.Dequeue();
    39             }
    40             else
    41             {
    42                 return string.Empty;
    43             }
    44         }
    45 
    46         public static void WriteLog(string message, LogLevel level)
    47         {
    48             switch (level)
    49             {
    50                 case LogLevel.Debug:
    51                     logInstance.Debug(message);
    52                     break;
    53                 case LogLevel.Error:
    54                     logInstance.Error(message);
    55                     break;
    56                 case LogLevel.Fatal:
    57                     logInstance.Fatal(message);
    58                     break;
    59                 case LogLevel.Info:
    60                     logInstance.Info(message);
    61                     break;
    62                 case LogLevel.Warn:
    63                     logInstance.Warn(message);
    64                     break;
    65                 default:
    66                     logInstance.Info(message);
    67                     break;
    68             }
    69         }
    70 
    71 
    72     }
    73 
    74 
    75     public enum LogLevel
    76     {
    77         Debug = 0,
    78         Error = 1,
    79         Fatal = 2,
    80         Info = 3,
    81         Warn = 4
    82     }
    83 }
    View Code

    关于log4net的实例定义,需要由配置文件【Log4NetConfig.xml】支撑,如下所示:

     1 <?xml version="1.0" encoding="utf-8" ?>
     2 <log4net>
     3     <root>
     4         <level value="DEBUG" />
     5         <appender-ref ref="LogFileAppender" />
     6         <appender-ref ref="ConsoleAppender" />
     7     </root>
     8     <logger name="smoking">
     9         <level value="ALL" />
    10     </logger>
    11     <appender name="LogFileAppender" type="log4net.Appender.FileAppender" >
    12         <param name="File" value="logs/${TMO}log-file.txt" />
    13         <StaticLogFileName value="false"/>
    14         <param name="AppendToFile" value="true" />
    15         <layout type="log4net.Layout.PatternLayout">
    16             <param name="Header" value="[Header]"/>
    17             <param name="Footer" value="[Footer]"/>
    18             <param name="ConversionPattern" value="%d [%t] %-5p %c [%x]  - %m%n"/>
    19         </layout>
    20         <filter type="log4net.Filter.LevelRangeFilter">
    21             <param name="LevelMin" value="DEBUG" />
    22             <param name="LevelMax" value="ERROR" />
    23         </filter>
    24     </appender>
    25     <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
    26         <layout type="log4net.Layout.PatternLayout">
    27             <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n" />
    28         </layout>
    29     </appender>
    30 </log4net>
    View Code

     还需要在AssemblyInfo.cs中添加声明,如下所示:

    1 [assembly: log4net.Config.DOMConfigurator(ConfigFile = "Log4NetConfig.xml", ConfigFileExtension = "xml", Watch = true)]

    备注

    行路难·其一

    【作者】李白 【朝代】唐

    金樽清酒斗十千,玉盘珍羞直万钱。

    停杯投箸不能食,拔剑四顾心茫然。

    欲渡黄河冰塞川,将登太行雪满山。

    闲来垂钓碧溪上,忽复乘舟梦日边。

    行路难,行路难,多歧路,今安在?

    长风破浪会有时,直挂云帆济沧海。 

  • 相关阅读:
    Socket 的网络编程
    《Python 3.5从零开始学》笔记-第8章 面向对象编程
    Python 的8个关键要素
    分布式发布订阅模型网络的实现有哪些
    MongoDB知识整理
    C++模板类与Qt信号槽混用
    C++中 =default,=delete用法
    QT知识整理
    Python题整理
    STL库的应用
  • 原文地址:https://www.cnblogs.com/hsiang/p/13643016.html
Copyright © 2011-2022 走看看