升級至 Selenium 4

您還在使用 Selenium 3 嗎?本指南將協助您升級至最新版本!

如果您使用的是官方支援的語言 (Ruby、JavaScript、C#、Python 和 Java) 之一,升級至 Selenium 4 應該是一個輕鬆的過程。在某些情況下可能會發生一些問題,本指南將協助您解決這些問題。我們將逐步介紹升級專案依賴套件的步驟,並了解版本升級帶來的主要棄用和變更。

以下是我們升級至 Selenium 4 將遵循的步驟

  • 準備我們的測試程式碼
  • 升級依賴套件
  • 潛在的錯誤和棄用訊息

注意:在開發 Selenium 3.x 版本時,已實作對 W3C WebDriver 標準的支援。新的協定和舊版的 JSON Wire Protocol 都受到支援。大約在 3.11 版本時,Selenium 程式碼變得符合 W3C 1 級規範。最新版本 Selenium 3 中符合 W3C 規範的程式碼將在 Selenium 4 中如預期般運作。

準備我們的測試程式碼

Selenium 4 移除了對舊版協定的支援,並預設在底層使用 W3C WebDriver 標準。對於大多數情況,此實作不會影響終端使用者。主要例外是 CapabilitiesActions 類別。

Capabilities

如果測試 capabilities 未建構為符合 W3C 規範,可能會導致工作階段無法啟動。以下是 W3C WebDriver 標準 capabilities 的清單

  • browserName
  • browserVersion (取代 version)
  • platformName (取代 platform)
  • acceptInsecureCerts
  • pageLoadStrategy
  • proxy
  • timeouts
  • unhandledPromptBehavior

最新的標準 capabilities 清單可以在 W3C WebDriver 找到。

任何未包含在上述清單中的 capability,都需要包含供應商前綴。這適用於瀏覽器特定的 capabilities 以及雲端供應商特定的 capabilities。例如,如果您的雲端供應商針對您的測試使用 buildname capabilities,您需要將它們包裝在 cloud:options 區塊中 (請與您的雲端供應商確認適當的前綴)。

之前

移動程式碼

DesiredCapabilities caps = DesiredCapabilities.firefox();
caps.setCapability("platform", "Windows 10");
caps.setCapability("version", "92");
caps.setCapability("build", myTestBuild);
caps.setCapability("name", myTestName);
WebDriver driver = new RemoteWebDriver(new URL(cloudUrl), caps);
caps = {};
caps['browserName'] = 'Firefox';
caps['platform'] = 'Windows 10';
caps['version'] = '92';
caps['build'] = myTestBuild;
caps['name'] = myTestName;
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("browserName", "firefox");
caps.SetCapability("platform", "Windows 10");
caps.SetCapability("version", "92");
caps.SetCapability("build", myTestBuild);
caps.SetCapability("name", myTestName);
var driver = new RemoteWebDriver(new Uri(CloudURL), caps);
      caps = Selenium::WebDriver::Remote::Capabilities.firefox
      caps[:platform] = 'Windows 10'
      caps[:version] = '92'
      caps[:build] = my_test_build
      caps[:name] = my_test_name
      driver = Selenium::WebDriver.for :remote, url: cloud_url, desired_capabilities: caps
      driver.get(url)
      driver.quit
caps = {}
caps['browserName'] = 'firefox'
caps['platform'] = 'Windows 10'
caps['version'] = '92'
caps['build'] = my_test_build
caps['name'] = my_test_name
driver = webdriver.Remote(cloud_url, desired_capabilities=caps)

之後

移動程式碼

FirefoxOptions browserOptions = new FirefoxOptions();
browserOptions.setPlatformName("Windows 10");
browserOptions.setBrowserVersion("92");
Map<String, Object> cloudOptions = new HashMap<>();
cloudOptions.put("build", myTestBuild);
cloudOptions.put("name", myTestName);
browserOptions.setCapability("cloud:options", cloudOptions);
WebDriver driver = new RemoteWebDriver(new URL(cloudUrl), browserOptions);
capabilities = {
  browserName: 'firefox',
  browserVersion: '92',
  platformName: 'Windows 10',
  'cloud:options': {
     build: myTestBuild,
     name: myTestName,
  }
}
var browserOptions = new FirefoxOptions();
browserOptions.PlatformName = "Windows 10";
browserOptions.BrowserVersion = "92";
var cloudOptions = new Dictionary<string, object>();
cloudOptions.Add("build", myTestBuild);
cloudOptions.Add("name", myTestName);
browserOptions.AddAdditionalOption("cloud:options", cloudOptions);
var driver = new RemoteWebDriver(new Uri(CloudURL), browserOptions);
      options = Selenium::WebDriver::Options.firefox
      options.platform_name = 'Windows 10'
      options.browser_version = 'latest'
      cloud_options = {}
      cloud_options[:build] = my_test_build
      cloud_options[:name] = my_test_name
      options.add_option('cloud:options', cloud_options)
      driver = Selenium::WebDriver.for :remote, capabilities: options
      driver.get(url)
      driver.quit
from selenium.webdriver.firefox.options import Options as FirefoxOptions
options = FirefoxOptions()
options.browser_version = '92'
options.platform_name = 'Windows 10'
cloud_options = {}
cloud_options['build'] = my_test_build
cloud_options['name'] = my_test_name
options.set_capability('cloud:options', cloud_options)
driver = webdriver.Remote(cloud_url, options=options)

Java 中尋找元素 (element) 的實用方法

Java 綁定中尋找元素的實用方法 (FindsBy 介面) 已被移除,因為它們僅供內部使用。以下程式碼範例更清楚地說明了這一點。

使用 findElement* 尋找單個元素

之前

driver.findElementByClassName("className");
driver.findElementByCssSelector(".className");
driver.findElementById("elementId");
driver.findElementByLinkText("linkText");
driver.findElementByName("elementName");
driver.findElementByPartialLinkText("partialText");
driver.findElementByTagName("elementTagName");
driver.findElementByXPath("xPath");
之後

driver.findElement(By.className("className"));
driver.findElement(By.cssSelector(".className"));
driver.findElement(By.id("elementId"));
driver.findElement(By.linkText("linkText"));
driver.findElement(By.name("elementName"));
driver.findElement(By.partialLinkText("partialText"));
driver.findElement(By.tagName("elementTagName"));
driver.findElement(By.xpath("xPath"));

使用 findElements* 尋找多個元素

之前

driver.findElementsByClassName("className");
driver.findElementsByCssSelector(".className");
driver.findElementsById("elementId");
driver.findElementsByLinkText("linkText");
driver.findElementsByName("elementName");
driver.findElementsByPartialLinkText("partialText");
driver.findElementsByTagName("elementTagName");
driver.findElementsByXPath("xPath");
之後

driver.findElements(By.className("className"));
driver.findElements(By.cssSelector(".className"));
driver.findElements(By.id("elementId"));
driver.findElements(By.linkText("linkText"));
driver.findElements(By.name("elementName"));
driver.findElements(By.partialLinkText("partialText"));
driver.findElements(By.tagName("elementTagName"));
driver.findElements(By.xpath("xPath"));

升級依賴套件

查看以下小節,以安裝 Selenium 4 並升級您的專案依賴套件。

Java

升級 Selenium 的過程取決於使用的建置工具。我們將介紹 Java 最常用的工具,即 MavenGradle。所需的最低 Java 版本仍然是 8。

Maven

之前

<dependencies>
  <!-- more dependencies ... -->
  <dependency>
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>selenium-java</artifactId>
    <version>3.141.59</version>
  </dependency>
  <!-- more dependencies ... -->
</dependencies>
之後

<dependencies>
    <!-- more dependencies ... -->
    <dependency>
        <groupId>org.seleniumhq.selenium</groupId>
        <artifactId>selenium-java</artifactId>
        <version>4.4.0</version>
    </dependency>
    <!-- more dependencies ... -->
</dependencies>

進行變更後,您可以在 pom.xml 檔案所在的目錄中執行 mvn clean compile

Gradle

之前

plugins {
    id 'java'
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
    mavenCentral()
}
dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
    implementation group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '3.141.59'
}
test {
    useJUnitPlatform()
}
之後

plugins {
    id 'java'
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
    mavenCentral()
}
dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
    implementation group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '4.4.0'
}
test {
    useJUnitPlatform()
}

進行變更後,您可以在 build.gradle 檔案所在的目錄中執行 ./gradlew clean build

若要查看所有 Java 版本,您可以前往 MVNRepository

C#

C# 中取得 Selenium 4 更新的地方是 NuGet。在 Selenium.WebDriver 套件下,您可以取得更新至最新版本的指示。在 Visual Studio 內,透過 NuGet 套件管理器,您可以執行

PM> Install-Package Selenium.WebDriver -Version 4.4.0

Python

使用 Python 最重要的變更是所需的最低版本。Selenium 4 將需要最低 Python 3.7 或更高版本。更多詳細資訊可以在 Python Package Index 找到。若要從命令列升級,您可以執行

pip install selenium==4.4.3

Ruby

Selenium 4 的更新詳細資訊可以在 RubyGems 中的 selenium-webdriver gem 中查看。若要安裝最新版本,您可以執行

gem install selenium-webdriver

若要將其新增至您的 Gemfile

gem 'selenium-webdriver', '~> 4.4.0'

JavaScript

selenium-webdriver 套件可以在 Node 套件管理器 npmjs 中找到。Selenium 4 可以在 這裡 找到。若要安裝它,您可以執行

npm install selenium-webdriver

或者,更新您的 package.json 並執行 npm install

{
  "name": "selenium-tests",
  "version": "1.0.0",
  "dependencies": {
    "selenium-webdriver": "^4.4.0"
  }
}

潛在的錯誤和棄用訊息

以下是一組程式碼範例,將協助您克服升級至 Selenium 4 後可能遇到的棄用訊息。

Java

等待和逾時

Timeout 中接收的參數已從預期 (long time, TimeUnit unit) 切換為預期 (Duration duration)

之前

driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
driver.manage().timeouts().setScriptTimeout(2, TimeUnit.MINUTES);
driver.manage().timeouts().pageLoadTimeout(10, TimeUnit.SECONDS);
之後

driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
driver.manage().timeouts().scriptTimeout(Duration.ofMinutes(2));
driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(10));

等待現在也預期不同的參數。WebDriverWait 現在預期 Duration 而不是以秒和毫秒為單位的 long 作為逾時。FluentWait 中的 withTimeoutpollingEvery 實用方法已從預期 (long time, TimeUnit unit) 切換為預期 (Duration duration)

之前

new WebDriverWait(driver, 3)
.until(ExpectedConditions.elementToBeClickable(By.cssSelector("#id")));

Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
  .withTimeout(30, TimeUnit.SECONDS)
  .pollingEvery(5, TimeUnit.SECONDS)
  .ignoring(NoSuchElementException.class);
之後

new WebDriverWait(driver, Duration.ofSeconds(3))
  .until(ExpectedConditions.elementToBeClickable(By.cssSelector("#id")));

  Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
  .withTimeout(Duration.ofSeconds(30))
  .pollingEvery(Duration.ofSeconds(5))
  .ignoring(NoSuchElementException.class);

合併 capabilities 不再變更呼叫物件

可以將不同的 capabilities 集合併到另一個集合中,並且它會變更呼叫物件。現在,合併操作的結果需要被賦值。

之前

MutableCapabilities capabilities = new MutableCapabilities();
capabilities.setCapability("platformVersion", "Windows 10");
FirefoxOptions options = new FirefoxOptions();
options.setHeadless(true);
options.merge(capabilities);

// As a result, the `options` object was getting modified.
之後

MutableCapabilities capabilities = new MutableCapabilities();
capabilities.setCapability("platformVersion", "Windows 10");
FirefoxOptions options = new FirefoxOptions();
options.setHeadless(true);
options = options.merge(capabilities);

// The result of the `merge` call needs to be assigned to an object.

Firefox Legacy

在 GeckoDriver 出現之前,Selenium 專案有一個驅動程式實作來自動化 Firefox (版本 <48)。但是,由於此實作在最新版本的 Firefox 中無法運作,因此不再需要它。為了避免升級至 Selenium 4 時出現重大問題,setLegacy 選項將顯示為已棄用。建議停止使用舊的實作,而僅依賴 GeckoDriver。以下程式碼將在升級後顯示 setLegacy 行已棄用。

FirefoxOptions options = new FirefoxOptions();
options.setLegacy(true);

BrowserType

BrowserType 介面已存在很長時間,但它即將被棄用,轉而支持新的 Browser 介面。

之前

MutableCapabilities capabilities = new MutableCapabilities();
capabilities.setCapability("browserVersion", "92");
capabilities.setCapability("browserName", BrowserType.FIREFOX);
之後

MutableCapabilities capabilities = new MutableCapabilities();
capabilities.setCapability("browserVersion", "92");
capabilities.setCapability("browserName", Browser.FIREFOX);

C#

AddAdditionalCapability 已棄用

建議改用 AddAdditionalOption。以下範例顯示了這一點

之前

var browserOptions = new ChromeOptions();
browserOptions.PlatformName = "Windows 10";
browserOptions.BrowserVersion = "latest";
var cloudOptions = new Dictionary<string, object>();
browserOptions.AddAdditionalCapability("cloud:options", cloudOptions, true);
之後

var browserOptions = new ChromeOptions();
browserOptions.PlatformName = "Windows 10";
browserOptions.BrowserVersion = "latest";
var cloudOptions = new Dictionary<string, object>();
browserOptions.AddAdditionalOption("cloud:options", cloudOptions);

Python

executable_path 已棄用,請傳入 Service 物件

在 Selenium 4 中,您需要從 Service 物件設定驅動程式的 executable_path,以防止棄用警告。(或者不要設定路徑,而是確保您需要的驅動程式位於系統 PATH 中。)

之前

from selenium import webdriver
options = webdriver.ChromeOptions()
driver = webdriver.Chrome(
    executable_path=CHROMEDRIVER_PATH, 
    options=options
)
之後

from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
options = webdriver.ChromeOptions()
service = ChromeService(executable_path=CHROMEDRIVER_PATH)
driver = webdriver.Chrome(service=service, options=options)

總結

我們介紹了升級至 Selenium 4 時需要考慮的主要變更。涵蓋了為升級準備測試程式碼時需要涵蓋的不同方面,包括關於如何預防在使用新版本 Selenium 時可能出現的潛在問題的建議。最後,我們也涵蓋了一組您在升級後可能會遇到的可能問題,並分享了這些問題的潛在修復方案。

這最初發佈於 https://saucelabs.com/resources/articles/how-to-upgrade-to-selenium-4