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)]

备注

行路难·其一

【作者】李白 【朝代】唐

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

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

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

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

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

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

posted @ 2020-09-09 23:38  老码识途呀  阅读(8062)  评论(4编辑  收藏  举报