Selenium二次封装Java版本

   1 package com.yanfuchang.selenium.utils;
   2 
   3 import java.awt.AWTException;
   4 import java.awt.Robot;
   5 import java.awt.event.KeyEvent;
   6 import java.io.File;
   7 import java.io.IOException;
   8 import java.net.MalformedURLException;
   9 import java.net.URL;
  10 import java.text.SimpleDateFormat;
  11 import java.util.Date;
  12 import java.util.HashMap;
  13 import java.util.List;
  14 import java.util.Map;
  15 import java.util.Set;
  16 import java.util.concurrent.TimeUnit;
  17 import org.apache.commons.io.FileUtils;
  18 import org.openqa.selenium.Alert;
  19 import org.openqa.selenium.By;
  20 import org.openqa.selenium.Cookie;
  21 import org.openqa.selenium.Dimension;
  22 import org.openqa.selenium.JavascriptExecutor;
  23 import org.openqa.selenium.Keys;
  24 import org.openqa.selenium.NoAlertPresentException;
  25 import org.openqa.selenium.NoSuchElementException;
  26 import org.openqa.selenium.OutputType;
  27 import org.openqa.selenium.TakesScreenshot;
  28 import org.openqa.selenium.WebDriver;
  29 import org.openqa.selenium.WebDriverException;
  30 import org.openqa.selenium.WebElement;
  31 import org.openqa.selenium.chrome.ChromeDriver;
  32 import org.openqa.selenium.firefox.FirefoxDriver;
  33 import org.openqa.selenium.firefox.FirefoxProfile;
  34 import org.openqa.selenium.ie.InternetExplorerDriver;
  35 import org.openqa.selenium.interactions.Actions;
  36 import org.openqa.selenium.interactions.HasInputDevices;
  37 import org.openqa.selenium.interactions.Keyboard;
  38 import org.openqa.selenium.remote.DesiredCapabilities;
  39 import org.openqa.selenium.remote.RemoteWebDriver;
  40 import org.openqa.selenium.support.ui.ExpectedConditions;
  41 import org.openqa.selenium.support.ui.Select;
  42 import org.openqa.selenium.support.ui.WebDriverWait;
  43 
  44 /**
  45  *    基于selenium的二次封装
  46  */
  47 public class WebDriverUtil {
  48     private static WebDriver driver = null;
  49     private static Select select = null;
  50     private static Alert alert = null;
  51     private static WebElement element = null;
  52     private static List<WebElement> elementList = null;
  53     private static long timeOutInSeconds = 10;
  54     //--------------------自定义常量------------------------
  55     public final String LINE = "\r\n";
  56     public final String smile = "^_^";
  57     public final String sad = "*o*";
  58 
  59     public WebDriverUtil(long timeOutInSeconds) {
  60         WebDriverUtil.timeOutInSeconds = timeOutInSeconds;
  61     }
  62 
  63     public WebDriverUtil() {}
  64     
  65     /**
  66      *     指定浏览器打开URL
  67      */
  68     public static void openBrowser(String url, String browser) {
  69         driver = initBrowser(browser);
  70         driver.manage().timeouts().implicitlyWait(timeOutInSeconds, TimeUnit.SECONDS);
  71         driver.get(url);
  72     }
  73     /**
  74      *     指定浏览器打开URL
  75      */
  76     public static void openBrowser(String url) {
  77         driver = initBrowser();
  78         driver.get(url);
  79     }
  80     
  81     /**
  82      *    初始化浏览器,方式1
  83      *        Firefox
  84      */
  85     public static WebDriver initBrowser() {
  86         /*
  87          * 谷歌浏览器 System.setProperty("webdriver.chrome.driver",
  88          * "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chromedriver.exe");
  89          * WebDriver driver = new ChromeDriver(); driver.get("http://www.baidu.com/");
  90          */
  91         FirefoxProfile profile = new FirefoxProfile();
  92         profile.setPreference("browser.download.manager.showWhenStarting", false);// 是否显示下载进度框
  93         profile.setPreference("browser.offline-apps.notify", false);// 网站保存离线数据时不通知我
  94         profile.setPreference("browser.helperApps.alwaysAsk.force", false);// 应用程序设置不询问
  95         profile.setPreference("browser.download.folderList", 0);// 设置下载地址0是桌面;1是“我的下载”;2是自定义
  96         profile.setPreference("browser.helperApps.neverAsk.saveToDisk",
  97                 "application/octet-stream, application/vnd.ms-excel, text/csv, application/zip, application/msword");
  98         profile.setPreference("dom.webnotifications.enabled", false);// 允许通知
  99         WebDriver driver = new FirefoxDriver(profile);// 启动火狐浏览器
 100         driver.manage().window().maximize();// 设置窗口大小
 101         driver.manage().timeouts().pageLoadTimeout(20, TimeUnit.SECONDS);// 设置页面加载超时
 102         driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);// 设置查询组件等待时间
 103         return driver;
 104     }
 105     
 106     /**
 107      *     初始化浏览器,方式2
 108      *         (ie, ff, chrome)
 109      */
 110     private static WebDriver initBrowser(String browser) {
 111         switch (browser) {
 112         case "ie":
 113             System.setProperty("webdriver.ie.driver", ".\\Tools\\IEDriverServer.exe");
 114             driver = new InternetExplorerDriver();
 115             break;
 116         case "ff":
 117         case "firefox":
 118         case "Firefox":
 119         case "FireFox":
 120             /**
 121              *     FireFox安装方式为默认安装:
 122              *     FireFox版本小于48
 123              *         System.setProperty("webdriver.firefox.marionette", ".\\Tools\\geckodriver.exe");
 124              *     FireFox版本大于48,默认安装时可以试试,应该可以
 125              *         System.setProperty("webdriver.gecko.driver", ".\\Tools\\geckodriver.exe");
 126              */
 127             // FireFox安装方式为自定义安装
 128             System.setProperty("webdriver.firefox.bin", "D:\\ProgramFiles\\Mozilla Firefox\\firefox.exe");
 129             driver = new FirefoxDriver();
 130             break;
 131         case "chrome":
 132         case "Chrome":
 133             System.setProperty("webdriver.chrome.driver", ".\\Tools\\chromedriver.exe");
 134             driver = new ChromeDriver();
 135             break;
 136         default:
 137             try {
 138                 throw new Exception("浏览器错误!");
 139             } catch (Exception e) {
 140                 e.printStackTrace();
 141             }
 142         }
 143         return driver;
 144     }
 145 
 146     //----------------------------------------------------元素相关-----------------------------------------------------------------------------
 147     /**
 148      *     查找元素
 149      * @param by      传入一个类型        例如:name
 150      * @param byValue 传入一个类型值       例如:username
 151      */
 152     public WebElement findElement(String by, String byValue) {
 153         try {
 154             switch (by) {
 155             case "id":
 156                 element = driver.findElement(By.id(byValue));
 157                 break;
 158             case "name":
 159                 element = driver.findElement(By.name(byValue));
 160                 break;
 161             case "class":
 162                 element = driver.findElement(By.className(byValue));
 163                 break;
 164             case "tag":
 165                 element = driver.findElement(By.tagName(byValue));
 166             case "link":
 167                 element = driver.findElement(By.linkText(byValue));
 168                 break;
 169             case "partiallinktext":
 170                 element = driver.findElement(By.partialLinkText(byValue));
 171             case "css":
 172                 element = driver.findElement(By.cssSelector(byValue));
 173                 break;
 174             case "xpath":
 175                 element = driver.findElement(By.xpath(byValue));
 176                 break;
 177             default:
 178                 throw new RuntimeException("输入的定位类型未在程序中定义,类型为:" + byValue);
 179             }
 180         } catch (Exception e) {
 181             System.out.println("没有找到元素:" + byValue);
 182         }
 183         return element;
 184     }
 185     /**
 186      *     查找一组元素
 187      * @param by      传入一个类型        例如:name
 188      * @param byValue 传入一个类型值       例如:username
 189      */
 190     public List<WebElement> findElements(String by, String byValue) {
 191         try {
 192             switch (by) {
 193             case "id":
 194                 elementList = driver.findElements(By.id(byValue));
 195                 break;
 196             case "name":
 197                 elementList = driver.findElements(By.name(byValue));
 198                 break;
 199             case "class":
 200                 elementList = driver.findElements(By.className(byValue));
 201                 break;
 202             case "tag":
 203                 elementList = driver.findElements(By.tagName(byValue));
 204             case "link":
 205                 elementList = driver.findElements(By.linkText(byValue));
 206                 break;
 207             case "partiallinktext":
 208                 elementList = driver.findElements(By.partialLinkText(byValue));
 209             case "css":
 210                 elementList = driver.findElements(By.cssSelector(byValue));
 211                 break;
 212             case "xpath":
 213                 elementList = driver.findElements(By.xpath(byValue));
 214                 break;
 215             default:
 216                 throw new RuntimeException("输入的定位类型未在程序中定义,类型为:" + byValue);
 217             }
 218         } catch (Exception e) {
 219             System.out.println("没有找到元素:" + byValue);
 220         }
 221         return elementList;
 222     }
 223     
 224     /**
 225      *     获取单个元素
 226      */
 227     public WebElement findElementByXpath(String xpath) {
 228         return driver.findElement(By.xpath(xpath));
 229     }
 230     public WebElement findElementByTag(String tag) {
 231         return driver.findElement(By.tagName(tag));
 232     }
 233     public WebElement findElementById(String id) {
 234         return driver.findElement(By.id(id));
 235     }
 236     public WebElement findElementByClassName(String name) {
 237         return driver.findElement(By.className(name));
 238     }
 239     public WebElement findElementByText(String text) {
 240         return driver.findElement(By.linkText(text));
 241     }
 242     public WebElement findElementByPartialText(String text) {
 243         return driver.findElement(By.partialLinkText(text));
 244     }
 245     public WebElement findElementByName(String name) {
 246         return driver.findElement(By.name(name));
 247     }
 248     
 249     /**
 250      *    获取多个元素
 251      */
 252     public List<WebElement> findElementsByClassName(String className) {
 253         return driver.findElements(By.className(className));
 254     }
 255     public List<WebElement> findElementsByText(String text) {
 256         return driver.findElements(By.linkText(text));
 257     }
 258     public List<WebElement> findElementsByPartialText(String text) {
 259         return driver.findElements(By.partialLinkText(text));
 260     }
 261     public List<WebElement> findElementsById(String id) {
 262         return driver.findElements(By.id(id));
 263     }
 264     public List<WebElement> findElementsByTag(String tag) {
 265         return driver.findElements(By.tagName(tag));
 266     }
 267     
 268     /**
 269      *    获取一组元素中的指定元素
 270      */
 271     public WebElement FindByElements(By by, int index) {
 272         WebElement element = null;
 273         if (this.elementsExists(by)) {
 274             element = driver.findElements(by).get(index);
 275         }
 276         return element;
 277     }
 278     
 279     /**
 280      *     查找元素并点击
 281      * @param by      传入一个类型        例如:name
 282      * @param byValue 传入一个类型值       例如:username
 283      */
 284     public boolean findElementClick(String by, String byValue) {
 285         try {
 286             switch (by) {
 287             case "id":
 288                 driver.findElement(By.id(byValue)).click();
 289                 return true;
 290             case "name":
 291                 driver.findElement(By.name(byValue)).click();
 292                 return true;
 293             case "class":
 294                 driver.findElement(By.className(byValue)).click();
 295                 return true;
 296             case "tag":
 297                 driver.findElement(By.tagName(byValue)).click();
 298             case "link":
 299                 driver.findElement(By.linkText(byValue)).click();
 300                 return true;
 301             case "partiallinktext":
 302                 driver.findElement(By.partialLinkText(byValue)).click();
 303             case "css":
 304                 driver.findElement(By.cssSelector(byValue)).click();
 305                 return true;
 306             case "xpath":
 307                 driver.findElement(By.xpath(byValue)).click();
 308                 return true;
 309             default:
 310                 throw new RuntimeException("输入的定位类型未在程序中定义,类型为:" + byValue);
 311             }
 312         } catch (Exception e) {
 313             System.out.println("*****没有找到元素,类型为::" + by + "属性值为:" + byValue + "  的元素或者该元素无法点击****");
 314             return false;
 315         }
 316     }
 317     
 318     /**
 319      *    定位元素并点击
 320      */
 321     public void findElementByIdAndClick(String id) {
 322         driver.findElement(By.id(id)).click();
 323     }
 324     public void findElementByNameAndClick(String name) {
 325         driver.findElement(By.name(name)).click();
 326     }
 327     public void findElementByTextAndClick(String text) {
 328         driver.findElement(By.linkText(text)).click();
 329     }
 330     public void findElementByPartiaTextAndClick(String text) {
 331         driver.findElement(By.partialLinkText(text)).click();
 332     }
 333     public void findElementByXpathAndClick(String xpath) {
 334         driver.findElement(By.xpath(xpath)).click();
 335     }
 336     public void findElementByClassNameAndClick(String name) {
 337         driver.findElement(By.className(name)).click();
 338     }
 339     
 340     /**
 341      *     查找元素并清除文本内容
 342      * @param by      传入一个类型        例如:name
 343      * @param byValue 传入一个类型值       例如:username
 344      */
 345     public boolean findElementClear(String by, String byValue) {
 346         try {
 347             switch (by) {
 348             case "id":
 349                 driver.findElement(By.id(byValue)).clear();
 350                 return true;
 351             case "name":
 352                 driver.findElement(By.name(byValue)).clear();
 353                 return true;
 354             case "class":
 355                 driver.findElement(By.className(byValue)).clear();
 356                 return true;
 357             case "tag":
 358                 driver.findElement(By.tagName(byValue)).clear();
 359                 return true;
 360             case "link":
 361                 driver.findElement(By.linkText(byValue)).clear();
 362                 return true;
 363             case "partiallinktext":
 364                 driver.findElement(By.partialLinkText(byValue)).clear();
 365                 return true;
 366             case "css":
 367                 driver.findElement(By.cssSelector(byValue)).clear();
 368                 return true;
 369             case "xpath":
 370                 driver.findElement(By.xpath(byValue)).clear();
 371                 return true;
 372             default:
 373                 throw new RuntimeException("输入的定位类型未在程序中定义,类型为:" + byValue);
 374             }
 375         } catch (Exception e) {
 376             System.out.println("*****没有找到元素,类型为::" + by + "属性值为:" + byValue + "  的元素或者该元素没有输入值****");
 377             return false;
 378         }
 379     }
 380     
 381     /**
 382      *     查找元素并输入值
 383      * @param by      传入一个类型        例如:name
 384      * @param byValue 传入一个类型值       例如:username
 385      * @param key     填写要输入的值        例如:zhangsan
 386      */
 387     public boolean findElementSendKeys(String by, String byValue, String key) {
 388         try {
 389             switch (by) {
 390             case "id":
 391                 driver.findElement(By.id(byValue)).sendKeys(key);
 392                 return true;
 393             case "name":
 394                 driver.findElement(By.name(byValue)).sendKeys(key);
 395                 return true;
 396             case "class":
 397                 driver.findElement(By.className(byValue)).sendKeys(key);
 398                 return true;
 399             case "tag":
 400                 driver.findElement(By.tagName(byValue)).sendKeys(key);
 401                 return true;
 402             case "link":
 403                 driver.findElement(By.linkText(byValue)).sendKeys(key);
 404                 return true;
 405             case "partiallinktext":
 406                 driver.findElement(By.partialLinkText(byValue)).sendKeys(key);
 407             case "css":
 408                 driver.findElement(By.cssSelector(byValue)).sendKeys(key);
 409                 return true;
 410             case "xpath":
 411                 driver.findElement(By.xpath(byValue)).sendKeys(key);
 412                 return true;
 413             default:
 414                 throw new RuntimeException("输入的定位类型未在程序中定义,类型为:" + byValue);
 415             }
 416         } catch (Exception e) {
 417             System.out.println("*****没有找到元素,类型为::" + by + "属性值为:" + byValue + "    的元素或者该元素无法输入****");
 418             return false;
 419         }
 420     }
 421     /**
 422      *     查找元素并输入值
 423      * @param by      传入一个类型        例如:name
 424      * @param byValue 传入一个类型值       例如:username
 425      * @param key     填写要输入的值        例如:zhangsan
 426      */
 427     public boolean findElementClearAndSendKeys(String by, String byValue, String key) {
 428         try {
 429             switch (by) {
 430             case "id":
 431                 findElementClear(by,byValue);
 432                 driver.findElement(By.id(byValue)).sendKeys(key);
 433                 return true;
 434             case "name":
 435                 findElementClear(by,byValue);
 436                 driver.findElement(By.name(byValue)).sendKeys(key);
 437                 return true;
 438             case "class":
 439                 findElementClear(by,byValue);
 440                 driver.findElement(By.className(byValue)).sendKeys(key);
 441                 return true;
 442             case "tag":
 443                 findElementClear(by,byValue);
 444                 driver.findElement(By.tagName(byValue)).sendKeys(key);
 445                 return true;
 446             case "link":
 447                 findElementClear(by,byValue);
 448                 driver.findElement(By.linkText(byValue)).sendKeys(key);
 449                 return true;
 450             case "partiallinktext":
 451                 findElementClear(by,byValue);
 452                 driver.findElement(By.partialLinkText(byValue)).sendKeys(key);
 453             case "css":
 454                 findElementClear(by,byValue);
 455                 driver.findElement(By.cssSelector(byValue)).sendKeys(key);
 456                 return true;
 457             case "xpath":
 458                 findElementClear(by,byValue);
 459                 driver.findElement(By.xpath(byValue)).sendKeys(key);
 460                 return true;
 461             default:
 462                 throw new RuntimeException("输入的定位类型未在程序中定义,类型为:" + byValue);
 463             }
 464         } catch (Exception e) {
 465             System.out.println("*****没有找到元素,类型为::" + by + "属性值为:" + byValue + "    的元素或者该元素无法输入****");
 466             return false;
 467         }
 468     }
 469     
 470     /**
 471      *    定位元素并清空文本内容,输入相应的值
 472      */
 473     public void findElementByIdAndClearSendkeys(String id, String text) {
 474         driver.findElement(By.id(id)).clear();
 475         driver.findElement(By.id(id)).sendKeys(text);
 476     }
 477     public void findElementByIdAndClearSendkeys(String id, int num) {
 478         driver.findElement(By.id(id)).clear();
 479         driver.findElement(By.id(id)).sendKeys(num + "");
 480     }
 481     public void findElementByNameAndClearSendkeys(String name, String text) {
 482         driver.findElement(By.name(name)).clear();
 483         driver.findElement(By.name(name)).sendKeys(text);
 484     }
 485     public void findElementByNameAndClearSendkeys(String name, int num) {
 486         driver.findElement(By.name(name)).clear();
 487         driver.findElement(By.name(name)).sendKeys(num + "");
 488     }
 489     public void findElementByXpathAndClearSendkeys(String xpath, String text) {
 490         findElementByXpath(xpath).clear();
 491         findElementByXpath(xpath).sendKeys(text);
 492     }
 493     public void findElementByXpathAndClearSendkeys(String xpath, int num) {
 494         findElementByXpath(xpath).clear();
 495         findElementByXpath(xpath).sendKeys(num + "");
 496     }
 497     public void findElementByClassnameAndClearSendkeys(String classname, String text) {
 498         driver.findElement(By.className(classname)).clear();
 499         driver.findElement(By.className(classname)).sendKeys(text);
 500     }
 501     public void findElementByClassnameAndClearSendkeys(String classname, int num) {
 502         driver.findElement(By.className(classname)).clear();
 503         driver.findElement(By.className(classname)).sendKeys(num + "");
 504     }
 505 
 506     /**
 507      *    定位元素,并获取其文本内容
 508      */
 509     public String getTextByXpath(String xpath) {
 510         return findElementByXpath(xpath).getText();
 511     }
 512     public String getTextByClassName(String name) {
 513         return findElementByClassName(name).getText();
 514     }
 515     public String getTextById(String id) {
 516         return findElementById(id).getText();
 517     }
 518     public String getTextByName(String name) {
 519         return findElementByName(name).getText();
 520     }
 521     
 522     /**
 523      *     定位元素,并指定点击次数(连续点击)
 524      */
 525     public boolean clickById(String id, int clickCount) {
 526         try {
 527             for (int i = 0; i < clickCount; i++) {
 528                 driver.findElement(By.id(id)).click();
 529             }
 530             return true;
 531         } catch (Exception e) {
 532             System.out.println(e.getMessage());
 533             return false;
 534         }
 535     }
 536     public boolean clickByXpath(String xpath, int clickCount) {
 537         try {
 538             for (int i = 0; i < clickCount; i++) {
 539                 driver.findElement(By.xpath(xpath)).click();
 540             }
 541             return true;
 542         } catch (Exception e) {
 543             System.out.println(e.getMessage());
 544             return false;
 545         }
 546     }
 547     public boolean clickByCss(String css, int clickCount) {
 548         try {
 549             for (int i = 0; i < clickCount; i++) {
 550                 driver.findElement(By.cssSelector(css)).click();
 551             }
 552             return true;
 553         } catch (Exception e) {
 554             System.out.println(e.getMessage());
 555             return false;
 556         }
 557     }
 558     
 559     // 判断元素是否存在
 560     public boolean exists(By selector) {
 561         driver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);// 设置查询组件等待时间
 562         try {
 563             driver.findElement(selector);
 564             driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);// 设置查询组件等待时间
 565             return true;
 566         } catch (Exception e) {
 567             driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);// 设置查询组件等待时间
 568             return false;
 569         }
 570     }
 571     /**
 572      *     判断一个元素是否存在
 573      */
 574     public boolean isElementExist(By by) {
 575         try {
 576             driver.findElement(by);
 577             return true;
 578         } catch (NoSuchElementException e) {
 579             return false;
 580         }
 581     }
 582     
 583     /**
 584      *    判断一组元素是否存在
 585      */
 586     public boolean elementsExists(By by) {
 587         return (driver.findElements(by).size() > 0) ? true : false;
 588     }
 589     
 590 //---------------------------------------判断页面是否包含指定文本---------------------------------------------------------
 591     /**
 592      *     1、指定时间内等待直到页面包含文本字符串
 593      * @param text        期望出现的文本
 594      * @param seconds     超时时间
 595      * @return Boolean     检查给定文本是否存在于指定元素中, 超时则捕获抛出异常TimeoutException并返回false
 596      * @see org.openqa.selenium.support.ui.ExpectedConditions.textToBePresentInElement(WebElement
 597      *      element, String text)
 598      */
 599     public static Boolean waitUntilPageContainText(String text, long seconds) {
 600         try {
 601             return new WebDriverWait(driver, seconds)
 602                     .until(ExpectedConditions.textToBePresentInElement(driver.findElement(By.tagName("body")), text));
 603         } catch (Exception e) {
 604             e.printStackTrace();
 605             return false;
 606         }
 607     }
 608 
 609     /**
 610      *     2、默认时间等待直到页面包含文本字符串
 611      * @param text 期望出现的文本
 612      * @return Boolean 检查给定文本是否存在于指定元素中, 超时则捕获抛出异常TimeoutException并返回false
 613      * @see org.openqa.selenium.support.ui.ExpectedConditions.textToBePresentInElement(WebElement
 614      *      element, String text)
 615      */
 616     public static Boolean waitUntilPageContainText(String text) {
 617         try {
 618             return new WebDriverWait(driver, timeOutInSeconds)
 619                     .until(ExpectedConditions.textToBePresentInElement(driver.findElement(By.tagName("body")), text));
 620         } catch (Exception e) {
 621             e.printStackTrace();
 622             return false;
 623         }
 624     }
 625     
 626 //---------------------------------------元素判断---------------------------------------------------------
 627     /**
 628      *     1、指定时间内等待直到元素存在于页面的DOM上并可见, 可见性意味着该元素不仅被显示, 而且具有大于0的高度和宽度
 629      * @param locator 元素定位器
 630      * @param seconds 超时时间
 631      * @return Boolean 检查给定元素的定位器是否出现, 超时则捕获抛出异常TimeoutException并返回false
 632      * @see org.openqa.selenium.support.ui.ExpectedConditions.visibilityOfElementLocated(By
 633      *      locator)
 634      */
 635     public static Boolean waitUntilElementVisible(By locator, int seconds) {
 636         try {
 637             new WebDriverWait(driver, seconds).until(ExpectedConditions.visibilityOfElementLocated(locator));
 638             return true;
 639         } catch (Exception e) {
 640             e.printStackTrace();
 641             return false;
 642         }
 643     }
 644 
 645     /**
 646      *     2、默认时间内等待直到元素存在于页面的DOM上并可见, 可见性意味着该元素不仅被显示, 而且具有大于0的高度和宽度
 647      * @param locator 元素定位器
 648      * @return Boolean 检查给定元素的定位器是否出现, 超时则捕获抛出异常TimeoutException并返回false
 649      * @see org.openqa.selenium.support.ui.ExpectedConditions.visibilityOfElementLocated(By
 650      *      locator)
 651      */
 652     public static Boolean waitUntilElementVisible(By locator) {
 653         try {
 654             new WebDriverWait(driver, timeOutInSeconds).until(ExpectedConditions.visibilityOfElementLocated(locator));
 655             return true;
 656         } catch (Exception e) {
 657             e.printStackTrace();
 658             return false;
 659         }
 660     }
 661     
 662     /**
 663      *    判断元素是否显示
 664      */
 665     public boolean getDisplayStatById(String id) {
 666             return driver.findElement(By.id(id)).isDisplayed();
 667     }
 668     public boolean getDisplayStatByXpath(String xpath) {
 669             return driver.findElement(By.xpath(xpath)).isDisplayed();
 670     }
 671     public boolean getDisplayStatByCss(String css) {
 672         return driver.findElement(By.cssSelector(css)).isDisplayed();
 673     }
 674     /**
 675      *    判断元素是否可写
 676      */
 677     public boolean getEnableStatById(String id) {
 678         return driver.findElement(By.id(id)).isEnabled();
 679     }
 680     public boolean getEnableStatByXpath(String xpath) {
 681         return driver.findElement(By.xpath(xpath)).isEnabled();
 682     }
 683     public boolean getEnableStatByCss(String css) {
 684         return driver.findElement(By.cssSelector(css)).isEnabled();
 685     }
 686     /**
 687      *    判断元素是否选中
 688      */
 689     public boolean getSelectStatById(String id) {
 690         return driver.findElement(By.id(id)).isSelected();
 691     }
 692     public boolean getSelectStatByXpath(String xpath) {
 693         return driver.findElement(By.xpath(xpath)).isSelected();
 694     }
 695     public boolean getSelectStatByCss(String css) {
 696         return driver.findElement(By.cssSelector(css)).isSelected();
 697     }
 698 
 699     /**
 700      *     获取当前焦点所在页面元素的属性值(name,value,id,src等等)
 701      */
 702     public String getFocusAttributeValue(String attribute) {
 703         String value = "";
 704         try {
 705             Thread.sleep(333);
 706         } catch (Exception e) {
 707             e.printStackTrace();
 708         }
 709         value = driver.switchTo().activeElement().getAttribute(attribute);
 710         System.out.println("The focus Element's " + attribute + "attribute value is>>" + value);
 711         return value;
 712     }
 713     
 714     // 等待元素可用再点击
 715     public void waitForEnabledByXpathAndClick(String xpath) throws InterruptedException {
 716         boolean key = true;
 717         while (key) {
 718             if (findElementByXpath(xpath).isEnabled() && findElementByXpath(xpath).isDisplayed()) {
 719                 clickByJsByXpath(xpath);
 720                 key = false;
 721             } else {
 722                 sleep(0);
 723             }
 724         }
 725     }
 726     // 自定义等待时间
 727     public static void sleep(int key) throws InterruptedException {
 728         switch (key) {
 729         case 0:
 730             Thread.sleep(500);
 731             break;
 732         case 1:
 733             Thread.sleep(2000);
 734             break;
 735         case 2:
 736             Thread.sleep(5000);
 737             break;
 738         default:
 739             System.out.println("错误");
 740             break;
 741         }
 742     }
 743 
 744 //---------------------------------------下拉列表操作---------------------------------------------------------
 745     // 根据id获取下拉框,根据index选择选项
 746     public void findSelectByIdAndSelectByIndex(String id, int index) {
 747         Select select = new Select(findElementById(id));
 748         select.selectByIndex(index);
 749     }
 750     // 根据id获取下拉框,根据value选择选项
 751     public void findSelectByIdAndSelectByValue(String id, String value) {
 752         Select select = new Select(findElementById(id));
 753         select.selectByValue(value);
 754     }
 755     // 根据id获取下拉框,根据text选择选项
 756     public void findSelectByIdAndSelectByText(String id, String text) {
 757         Select select = new Select(findElementById(id));
 758         select.selectByVisibleText(text);
 759     }
 760 
 761     // 根据classname获取下拉框,根据text选择选项
 762     public void findSelectByClassNameAndSelectByText(String name, String text) {
 763         Select select = new Select(findElementByClassName(name));
 764         select.selectByVisibleText(text);
 765     }
 766     // 根据classname获取下拉框,根据Value选择选项
 767     public void findSelectByClassNameAndSelectByValue(String name, String value) {
 768         Select select = new Select(findElementByClassName(name));
 769         select.selectByValue(value);
 770     }
 771     // 根据classname获取下拉框,根据index选择选项
 772     public void findSelectByClassNameAndSelectByIndex(String name, int index) {
 773         Select select = new Select(findElementByClassName(name));
 774         select.selectByIndex(index);
 775     }
 776 
 777     // 根据name获取下拉框,根据text选择选项
 778     public void findSelectByNameAndSelectByText(String name, String text) {
 779         Select select = new Select(findElementByName(name));
 780         select.selectByVisibleText(text);
 781     }
 782     // 根据name获取下拉框,根据Value选择选项
 783     public void findSelectByNameAndSelectByValue(String name, String value) {
 784         Select select = new Select(findElementByName(name));
 785         select.selectByValue(value);
 786     }
 787     // 根据name获取下拉框,根据index选择选项
 788     public void findSelectByNameAndSelectByIndex(String name, int index) {
 789         Select select = new Select(findElementByName(name));
 790         select.selectByIndex(index);
 791     }
 792     
 793     /**
 794      *     定位select并选中对应text的option
 795      * @param locator
 796      * @param text
 797      * @see org.openqa.selenium.support.ui.Select.selectByVisibleText(String text)
 798      */
 799     public static void selectByText(By locator, String text) {
 800         select = new Select(driver.findElement(locator));
 801         select.selectByVisibleText(text);
 802     }
 803 
 804     /**
 805      *     定位select并选中对应index的option
 806      * @param locator
 807      * @param index
 808      * @see org.openqa.selenium.support.ui.Select.selectByIndex(int index)
 809      */
 810     public static void selectByIndex(By locator, int index) {
 811         select = new Select(driver.findElement(locator));
 812         select.selectByIndex(index);
 813     }
 814 
 815     /**
 816      *    定位select并选中对应value值的option
 817      * @param locator  定位select的选择器
 818      * @param value       option 中的value值
 819      * @see org.openqa.selenium.support.ui.Select.selectByValue(String value)
 820      */
 821     public static void selectByValue(By locator, String value) {
 822         select = new Select(driver.findElement(locator));
 823         select.selectByValue(value);
 824     }
 825     
 826 //---------------------------------------弹框操作---------------------------------------------------------
 827     // 判断是否有弹框
 828     public boolean isAlertPresent() {
 829         try {
 830             alert = driver.switchTo().alert();
 831             return true;
 832         } catch (NoAlertPresentException Ex) {
 833             return false;
 834         }
 835     }
 836     // 接受弹出框
 837     public void acceptAlert() {
 838         if (this.isAlertPresent()) {
 839             alert.accept();
 840         }
 841     }
 842     // 取消弹出框
 843     public void dimissAlert() {
 844         if (this.isAlertPresent()) {
 845             alert.dismiss();
 846         }
 847     }
 848     // 获取弹出内容
 849     public String getAlertText() {
 850         String text = null;
 851         if (this.isAlertPresent()) {
 852             text = alert.getText();
 853         } else {
 854             // todo:log;
 855         }
 856         return text;
 857     }
 858     // 弹出对话框输入文本字符串
 859     public void inputTextToAlert(String text) {
 860         if (this.isAlertPresent()) {
 861             alert.sendKeys(text);
 862         } else {
 863             // todo:log;
 864         }
 865     }
 866     
 867 //---------------------------------------窗口和iframe---------------------------------------------------------
 868     /**
 869      *     切换到当前页面
 870      */
 871     public static void switchToCurrentPage() {
 872         String handle = driver.getWindowHandle();
 873         for (String tempHandle : driver.getWindowHandles()) {
 874             if (tempHandle.equals(handle)) {
 875                 driver.close();
 876             } else {
 877                 driver.switchTo().window(tempHandle);
 878             }
 879         }
 880     }
 881     /**
 882      *     切换到指定title的窗口
 883      */
 884     public void switchToWindow(String windowTtitle) {
 885         Set<String> windowHandles = driver.getWindowHandles();
 886         for (String handler : windowHandles) {
 887             driver.switchTo().window(handler);
 888             String title = driver.getTitle();
 889             if (windowTtitle.equals(title)) {
 890                 break;
 891             }
 892         }
 893     }
 894 
 895     /**
 896      *     切换至父级frame
 897      * @see org.openqa.selenium.WebDriver.TargetLocator.parentFrame()
 898      */
 899     public static void switchToParentFrame() {
 900         driver.switchTo().parentFrame();
 901     }
 902     
 903     /**
 904      *     切换默认最外层frame或者窗口
 905      * @return 这个驱动程序聚焦在顶部窗口/第一个frame上
 906      * @see org.openqa.selenium.WebDriver.TargetLocator.defaultContent()
 907      */
 908     public static void switchToDefault() {
 909         driver.switchTo().defaultContent();
 910     }
 911     
 912     /**
 913      *    切换到指定iframe
 914      */
 915     public void switchToFrameById(String frameId) {
 916         driver.switchTo().frame(frameId);
 917     }
 918     public void switchToFrameByIndex(int index) {
 919         driver.switchTo().frame(index);
 920     }
 921     public void switchToframeByElement(By locator) {
 922         driver.switchTo().frame(driver.findElement(locator));
 923     }
 924     
 925     /**
 926      *     提交表单
 927      * @see org.openqa.selenium.WebElement.submit()
 928      */
 929     public static void submitForm(By locator) {
 930         driver.findElement(locator).submit();
 931     }
 932 
 933     /**
 934      *     上传文件
 935      */
 936     public static void uploadFile(By locator, String filePath) {
 937         driver.findElement(locator).sendKeys(filePath);
 938     }
 939     
 940 //---------------------------------------JS操作---------------------------------------------------------
 941     // JS点击指定元素
 942     public void clickByJs(WebElement element) {
 943          ((JavascriptExecutor) driver).executeScript("arguments[0].click()", element);
 944     }
 945     
 946     // 定位元素触发JS点击事件
 947     public void clickByJsByXpath(String xpath) {
 948         clickByJs(driver.findElement(By.xpath(xpath)));
 949     }
 950     public void clickByJsByText(String text) {
 951         clickByJs(findElementByText(text));
 952     }
 953     public void clickByJsById(String id) {
 954         clickByJs(findElementById(id));
 955     }
 956     public void clickByJsByClassName(String name) {
 957         clickByJs(findElementByClassName(name));
 958     }
 959     public void clickByJsByName(String name) {
 960         clickByJs(findElementByName(name));
 961     }
 962     
 963     // 滚动到窗口最上方
 964     public void scrollToTop() {
 965         ((JavascriptExecutor) driver).executeScript("window.scrollTo(0,0);");
 966     }
 967     // 滚动到页面底部
 968     public void scrollToBottom(String id) {
 969         ((JavascriptExecutor) driver).executeScript("window.scrollTo(0,10000);");
 970     }
 971     // 滚动到某个元素
 972     public void scrollToElement(WebElement element) {
 973         JavascriptExecutor js = (JavascriptExecutor) driver;
 974         js.executeScript("arguments[0].scrollIntoView(true);", element);
 975     }
 976     // js给指定元素value赋值
 977     public void inputTextByJs(String text, WebElement element) {
 978         JavascriptExecutor js = (JavascriptExecutor) driver;
 979         js.executeScript("arguments[0].value=" + text + "\"", element);
 980     }
 981     // js使元素隐藏元素显示
 982     public void makeElementDisplay(WebElement element) {
 983         JavascriptExecutor js = (JavascriptExecutor) driver;
 984         js.executeScript("arguments[0].style=arguments[1]", element, "display: block;");
 985     }
 986     
 987     
 988 //---------------------------------------浏览器操作---------------------------------------------------------
 989     /**
 990      *     关闭当前浏览器
 991      */
 992     public static void closeCurrentBrowser() {
 993         driver.close();
 994     }
 995     /**
 996      *     关闭所有selenium驱动打开的浏览器
 997      */
 998     public static void closeAllBrowser() {
 999         driver.quit();
1000     }
1001     /**
1002      *     最大化浏览器
1003      */
1004     public static void maxBrowser() {
1005         driver.manage().window().maximize();
1006     }
1007 
1008     /**
1009      *     自定义设置浏览器尺寸
1010      */
1011     public static void setBrowserSize(int width, int heigth) {
1012         driver.manage().window().setSize(new Dimension(width, heigth));
1013     }
1014     
1015     /**
1016      *     获取网页的title值
1017      */
1018     public String getTitle() {
1019         return driver.getTitle();
1020     }
1021 
1022     /**
1023      *     获取当前url字符串
1024      */
1025     public static String getURL() {
1026         return driver.getCurrentUrl();
1027     }
1028 
1029     /**
1030      *    上一个页面(点击浏览器返回)
1031      */
1032     public static void returnToPreviousPage() {
1033         driver.navigate().back();
1034     }
1035 
1036     /**
1037      *     下一个页面(如果没有下一个页面则什么都不做)
1038      *     浏览器上的前进
1039      */
1040     public static void forwardToNextPage() {
1041         driver.navigate().forward();
1042     }
1043 
1044     /**
1045      *     刷新页面
1046      */
1047     public static void refreshPage() {
1048         driver.navigate().refresh();
1049     }
1050     
1051     // 强制刷新页面
1052     public void refresh() {
1053         Actions ctrl = new Actions(driver);
1054         ctrl.keyDown(Keys.CONTROL).perform();
1055         try {
1056             pressKeyEvent(KeyEvent.VK_F5);
1057         } catch (AWTException e) {
1058             e.printStackTrace();
1059         }
1060         ctrl.keyUp(Keys.CONTROL).perform();
1061     }
1062     
1063     /**
1064      *     判断是否加载有JQuery
1065      */
1066     public Boolean JQueryLoaded() {
1067         Boolean loaded;
1068         JavascriptExecutor js = (JavascriptExecutor) driver;
1069         try {
1070             loaded = (Boolean) js.executeScript("return" + "JQuery()!=null");
1071         } catch (WebDriverException e) {
1072             loaded = false;
1073         }
1074         return loaded;
1075     }
1076 //---------------------------------------屏幕截图---------------------------------------------------------
1077     public void screenShot(WebDriver driver) {
1078         String dir_name = "screenshot";
1079         if (!(new File(dir_name).isDirectory())) {
1080             new File(dir_name).mkdir();
1081         }
1082         SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss");
1083         String time = sdf.format(new Date());
1084         try {
1085             File source_file = (((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE));// 执行截屏
1086             FileUtils.copyFile(source_file, new File(dir_name + File.separator + time + ".png"));
1087         } catch (IOException e) {
1088             e.printStackTrace();
1089         }
1090     }
1091     // 截图命名为当前时间保存桌面
1092     public void takeScreenshotByNow() throws IOException {
1093         File srcFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
1094         SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss");
1095         String time = sdf.format(new Date());
1096         String file = "C:\\Users\\zhangsan\\Desktop\\picture\\" + time + ".png";
1097         FileUtils.copyFile(srcFile, new File(file));
1098     }
1099     // 截图重命名保存至桌面
1100     public void takeScreenshotByName(String name) throws IOException {
1101         File srcFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
1102         String file = "C:\\Users\\zhangsan\\Desktop\\picture\\" + name + ".png";
1103         FileUtils.copyFile(srcFile, new File(file));
1104     }
1105     
1106 //---------------------------------------键盘操作---------------------------------------------------------
1107     // 获取键盘
1108     public Keyboard getKeyboard() {
1109         return ((HasInputDevices) driver).getKeyboard();
1110     }
1111     // 模拟crtrl+F5
1112     public void refreshWithCtrlF5() {
1113         getKeyboard().sendKeys(Keys.CONTROL, Keys.F5);
1114     }
1115     /**
1116      *     按物理按键(KeyEvent类中查找相关的常量)
1117      *     例子:
1118      *        Robot robot = new Robot();
1119      *         robot.keyPress(KeyEvent.VK_ENTER);//按下enter键
1120      */
1121     public void pressKeyEvent(int keycode) throws AWTException {
1122         Robot robot = new Robot();
1123         robot.keyPress(keycode);
1124     }
1125     
1126 //---------------------------------------鼠标操作---------------------------------------------------------
1127     // 鼠标悬浮指定元素并点击
1128     public void moveToElementById(String id) {
1129         Actions actions = new Actions(driver);
1130         actions.moveToElement(findElementById(id)).perform();
1131     }
1132     public void moveToElementByClassName(String name) {
1133         Actions actions = new Actions(driver);
1134         actions.moveToElement(findElementByClassName(name)).perform();
1135     }
1136     
1137     // 鼠标右键点击
1138     public void RightClickWebElement(String id) {
1139         Actions actions = new Actions(driver);
1140         actions.contextClick(findElementById(id)).perform();
1141     }
1142     // 鼠标双击
1143     public void DoubleClickWebElement(String id) {
1144         Actions actions = new Actions(driver);
1145         actions.doubleClick(findElementById(id)).perform();
1146     }
1147     
1148     /**
1149      *    模拟点击键盘上的键:
1150      *        keyDown()按下
1151      *        keyUp()抬起,松开
1152      *
1153      *    常见的键:
1154      *        Keys.SHIFT    Keys.ALT   Keys.Tab
1155      */
1156     public void ClickCtrl(String id) {
1157         Actions actions = new Actions(driver);
1158         actions.keyDown(Keys.CONTROL);//按下control键
1159         actions.keyUp(Keys.CONTROL);//松开control键
1160     }
1161     
1162     /**
1163      *    模拟键盘输入关键字到输入框
1164      */
1165     public void sendText(By by,String text) {
1166         Actions actions = new Actions(driver);
1167         actions.sendKeys(driver.findElement(by),text).perform();
1168     }
1169     
1170     /**
1171      *    模拟鼠标移动到指定元素,并点击
1172      */
1173     public void moveToElementAndClick(By by,String text) {
1174         Actions actions = new Actions(driver);
1175         actions.moveToElement(driver.findElement(by)).click().perform();
1176     }
1177     
1178     /**
1179      *    模拟鼠标点击和释放
1180      */
1181     public void clickHoldAndRelease(By by) {
1182         Actions actions = new Actions(driver);
1183         actions.clickAndHold(driver.findElement(by)).perform();
1184         try {
1185             Thread.sleep(1000);
1186         } catch (InterruptedException e) {
1187             e.printStackTrace();
1188         }
1189         actions.release(driver.findElement(by)).perform();
1190     }
1191     
1192 //---------------------------------------Cookie操作---------------------------------------------------------
1193     /**
1194      *     获取当前域所有的cookies
1195      * @return Set&lt;Cookie> 当前的cookies集合
1196      * @see org.openqa.selenium.WebDriver.Options.getCookies()
1197      */
1198     public static Set<Cookie> getAllCookies() {
1199         return driver.manage().getCookies();
1200     }
1201     // 输出cookies信息
1202     public void outputCookie() {
1203         Set<Cookie> cookie = driver.manage().getCookies();
1204         System.out.println(cookie);
1205     }
1206     //添加cookie信息
1207     public void addCookie(Map<String, String> args) {
1208         Set<String> keys = args.keySet();
1209         for (String key : keys) {
1210             driver.manage().addCookie(new Cookie(key, args.get(key)));
1211         }
1212     }
1213     /**
1214      *     用给定的name和value创建默认路径的Cookie并添加, 永久有效
1215      * @param name
1216      * @param value
1217      * @see org.openqa.selenium.WebDriver.Options.addCookie(Cookie cookie)
1218      * @see org.openqa.selenium.Cookie.Cookie(String name, String value)
1219      */
1220     public static void addCookie(String name, String value) {
1221         driver.manage().addCookie(new Cookie(name, value));
1222     }
1223 
1224     /**
1225      *     用给定的name和value创建指定路径的Cookie并添加, 永久有效
1226      * @param name  cookie名称
1227      * @param value cookie值
1228      * @param path  cookie路径
1229      */
1230     public static void addCookie(String name, String value, String path) {
1231         driver.manage().addCookie(new Cookie(name, value, path));
1232     }
1233     /**
1234      *     根据cookie名称删除cookie
1235      * @param name cookie的name值
1236      * @see org.openqa.selenium.WebDriver.Options.deleteCookieNamed(String name)
1237      */
1238     public static void deleteCookie(String name) {
1239         driver.manage().deleteCookieNamed(name);
1240     }
1241     /**
1242      *     删除当前域的所有Cookie
1243      * @see org.openqa.selenium.WebDriver.Options.deleteAllCookies()
1244      */
1245     public static void deleteAllCookies() {
1246         driver.manage().deleteAllCookies();
1247     }
1248     
1249     /**
1250      *     根据名称获取指定cookie
1251      * @param name cookie名称
1252      * @return Map&lt;String, String>, 如果没有cookie则返回空, 返回的Map的key值如下:
1253      *         <ul>
1254      *         <li><tt>name</tt> <tt>cookie名称</tt>
1255      *         <li><tt>value</tt> <tt>cookie值</tt>
1256      *         <li><tt>path</tt> <tt>cookie路径</tt>
1257      *         <li><tt>domain</tt> <tt>cookie域</tt>
1258      *         <li><tt>expiry</tt> <tt>cookie有效期</tt>
1259      *         </ul>
1260      * @see org.openqa.selenium.WebDriver.Options.getCookieNamed(String name)
1261      */
1262     public static Map<String, String> getCookieByName(String name) {
1263         Cookie cookie = driver.manage().getCookieNamed(name);
1264         if (cookie != null) {
1265             Map<String, String> map = new HashMap<String, String>();
1266             map.put("name", cookie.getName());
1267             map.put("value", cookie.getValue());
1268             map.put("path", cookie.getPath());
1269             map.put("domain", cookie.getDomain());
1270             map.put("expiry", cookie.getExpiry().toString());
1271             return map;
1272         }
1273         return null;
1274     }
1275     
1276 //---------------------------------------远程---------------------------------------------------------
1277     /**
1278      *     进入测试,打开浏览器,输入网址,打开网页
1279      *
1280      * @param remoteUrl 远程服务器地址
1281      * @param pageUrl   测试页面地址
1282      */
1283     public boolean startTest(String remoteUrl, String pageUrl) {
1284         try {
1285             try {
1286                 driver = new RemoteWebDriver(new URL(remoteUrl), DesiredCapabilities.firefox());
1287             } catch (MalformedURLException e) {
1288                 e.printStackTrace();
1289             }
1290             driver.get(pageUrl);
1291             return true;
1292         } catch (Exception e) {
1293             e.printStackTrace();
1294             System.out.println(e.getMessage());
1295             return false;
1296         }
1297     }
1298 
1299     /**
1300      *     进入测试,打开浏览器,输入网址,打开网页
1301      * @param explore   调用的浏览器,需要启动不同的server
1302      *                     如:firefox,需要运行selenium-server-standalone-2.33.0.jar。
1303      *                        IE,则需运行IEDriverServer.exe
1304      * @param remoteUrl 远程服务器地址
1305      * @param pageUrl   测试页面地址
1306      */
1307     public boolean startTest(String explore, String remoteUrl, String pageUrl) {
1308         try {
1309             try {
1310                 if ("f".equals(explore)) {
1311                     System.out.println("firefox");
1312                     driver = new RemoteWebDriver(new URL(remoteUrl), DesiredCapabilities.firefox());
1313                 } else if ("ie".equals(explore)) {
1314                     System.out.println("internet explorer");
1315                     DesiredCapabilities cap = DesiredCapabilities.internetExplorer();
1316                     driver = new RemoteWebDriver(new URL(remoteUrl), cap);
1317                 } else {
1318                     System.out.println("firefox");
1319                     driver = new RemoteWebDriver(new URL(remoteUrl), DesiredCapabilities.firefox());
1320                 }
1321             } catch (Exception e) {
1322                 e.printStackTrace();
1323             }
1324             driver.get(pageUrl);
1325             return true;
1326         } catch (Exception e) {
1327             System.out.println(e.getMessage());
1328             return false;
1329         }
1330     }
1331 }
原文地址:https://www.cnblogs.com/wang1001/p/9556158.html