隐藏

C# 利用Selenium实现浏览器自动化操作的示例代码

发布:2020/9/25 11:14:37作者:管理员 来源:本站 浏览次数:1041

概述

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
3
//谷歌浏览器
 ChromeOptions options = new ChromeOptions();
 this.driver = new ChromeDriver(options);

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

1
2
3
4
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获取元素,如下所示:

1
2
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;

核心代码

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
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;
      }
    }
 
  }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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
  }
}