我們很高興宣布 Nightwatch 的下一個主要版本 已在 NPM 上以 alpha 預發布版形式提供。它包含了大量用於編寫和執行測試的新功能和改進,以及對符合 W3C WebDriver 標準的瀏覽器的完整跨瀏覽器測試支援。

底層架構已完全重寫,改為使用官方的 selenium-webdriver Node.js 函式庫與瀏覽器驅動程式進行通訊。這意味著更好的跨瀏覽器整合、更可靠的 DOM 元素命令處理,以及整體更穩定、更快速的測試。

alpha 版本的目標是收集一些回饋、識別並修復主要錯誤,同時完成新的 API 並更新文件。因此,如果您遇到任何重大問題,請務必讓我們知道,以便我們可以讓從 v1.x 升級的過程盡可能順利。本文末尾提到了幾個重大變更,但它們應該相對較小。

我們也會繼續發布現有 v1.7 版本的修補程式和重要修正。以下是 v2.0 中新功能、改進和其他變更的概述。

若要安裝 alpha 版本,請執行以下指令:

npm i nightwatch@alpha

支援 WebDriver Actions API

WebDriver 提供了一個全面的 API 來產生複雜的使用者手勢,稱為 Actions API。這已透過現有的 .perform() 命令在 Nightwatch 中提供並可隨時使用。 perform() 命令的先前功能仍然存在,並且與之前的工作方式相同。

以下是如何使用新的 actions api 的基本範例


try {
  const performResult = await browser.perform(function() {
    const actions = this.actions({async: true});

    return actions
       .keyDown(Key.SHIFT)
       .keyUp(Key.SHIFT);
  });

  console.log('perform', performResult)
} catch (err) {
  console.error(err)
}

API 文件中的更多範例,請參閱 Selenium 文件網站。在上面的範例中, Actions 類別的實例是使用 this.actions(<options>) 建立的。結尾的 .perform() (在 selenium 文件中是必要的) 應在 Nightwatch 中省略,因為它會自動呼叫。

支援 Chrome DevTools 協定

ChromeDriverEdgeDriver 都公開了一些用於處理各自瀏覽器的特定命令。

當使用 ChromeDriver 或 EdgeDriver 時,現在可以透過 Chrome DevTools 協定執行命令。以下是 Nightwatch browser 物件上 chrome 命名空間中可用的完整命令列表

browser.chrome

更多資訊

新的 Firefox 特定命令

FirefoxDriver 公開了一些特定命令,例如設定執行「特權」javascript 程式碼的內容,或是處理外掛程式。這些現在直接在 Nightwatch 中的 firefox 命名空間中提供。

browser.firefox

更多資訊

新的 .ensure 斷言

新的 .ensure 命名空間基於 until 模組,該模組來自 selenium-webdriver

範例


describe('demo test for .ensure', function() {
  test('basic test', function(browser) {
    browser
      .url('https://nightwatch.dev.org.tw')
      .ensure.titleMatches(/Nightwatch.js/)
      .ensure.elementIsVisible('#index-container')  });
});

新的 element() 全域變數和支援使用 WebElements

新加入的 element() 全域變數可用於在測試案例之外預先建構元素物件。也可以使用新加入的 by() 全域實用程式,它相當於使用 By() 類別 (來自 selenium-webdriver) 來建立元素定位器。

此外,browser 物件也作為全域變數提供,因此不必像 Nightwatch v1.x 中那樣將其作為引數傳遞給測試。

也可以透過在 nightwatch 設定檔中將 disable_global_apis 設定為 true 來停用全域 api。

範例


const assert = require('assert');
const {WebElement} = require('selenium-webdriver');

describe('demo element() global', function() {
  const signupEl = element(by.css('#signupSection'));
  const loginEl = element('#weblogin');

  test('element globals command',  async function() {
    const tagName = await browser.waitForElementPresent(loginEl, 100).getTagName(loginEl);
    assert.strictEqual(tagName, 'div');

    // use elements created with element() to regular nightwatch assertions
    browser.assert.visible(loginEl);

    // use elements created with element() to expect assertions
    browser.expect.element(loginEl).to.be.visible;

    // retrieve the WebElement instance
    const loginWebElement = await loginEl.getWebElement();
    assert.ok(loginWebElement instanceof WebElement);
  });
});

直接使用 Selenium WebDriver 物件

WebDriver 實例也可以在 Nightwatch api 物件上以 driver 屬性提供。

如果您想要串連 WebDriver 特定的命令,您需要將它們包裝在 perform()waitUntil() 命令中。

範例


describe('demo driver object', function() {

  it('get browser logs – classic',  function() {
    browser
      .url('https://nightwatch.dev.org.tw')
      .waitForElementVisible('body')
      .perform(function() {
        this.driver.manage().logs().get('browser').then(result => {
          console.log('Browser logs:', result)
        })
      });
  });

  it('get browser logs – with ES6 async/await', async function() {
    await browser.url('https://nightwatch.dev.org.tw').waitForElementVisible('body');
    const logs = await browser.driver.manage().logs().get('browser');

    console.log('Browser logs:', logs)
  });
});

在 Nightwatch 中使用 WebDriver BiDi

以 Selenium WebDriver 為基礎,意味著 WebDriver 的最新功能和功能將直接在 Nightwatch 中提供,例如即將推出的 Webdriver BiDi 協定,該協定被視為「跨瀏覽器自動化的未來」。

WebDriver BiDi 是用於與瀏覽器通訊的新協定,定義為新的 W3C 規範,目前正在開發中。

Selenium 4 中提供了早期支援,並且已透過 Chrome 開發人員工具在 ChromeDriver 中提供。

WebDriver Bidi 允許使用者在瀏覽器發生事件時捕獲事件,而不是使用 WebDriver 用於其他 API 的傳統請求/回應方法。

在內部,WebDriver 將建立一個 WebSocket 連線到瀏覽器,以便傳輸事件和命令。

範例

以下範例透過 WebSocket 上的 WebDriver 雙向連線從 CDP 呼叫 Page.getLayoutMetrics 方法。


describe('demo webdriver bidirectional', function() {

  it('samepl test bidi', async function(browser) {
    await browser.url('https://nightwatch.dev.org.tw/');

    const cdpConnection = await browser.driver.createCDPConnection('page');
    browser.assert.ok(cdpConnection._wsConnection && cdpConnection._wsConnection._url.startsWith('ws://'),
            CDP connection is successful to: ${cdpConnection._wsConnection._url});

    const layoutMetrics = await browser.perform(function(callback) {
      const id = 99;
      cdpConnection._wsConnection.on('message', function getLayoutMetrics(message) {
        const params = JSON.parse(message)
        if (params.id === 99) {
          cdpConnection._wsConnection.off('message', getLayoutMetrics);
          callback(params.result);
        }
      });

      cdpConnection.execute('Page.getLayoutMetrics', id, {});
    });

    console.log('Layout Metrics:', layoutMetrics)
  });
});

新的 API 命令

已新增幾個新命令,並且也改進了幾個現有命令的相容性。

browser.getAccessibleName(<selector> | <WebElement>)

傳回元素的計算 WAI-ARIA 標籤。

 const result = await browser.getAccessibleName('input[type=search]');

browser.getAriaRole(<selector> | <WebElement>)

傳回元素的計算 WAI-ARIA 角色。

 const result = await browser.getAriaRole('input[type=search]');
 

browser.takeElementScreenshot(<selector> | <WebElement>)

拍攝元素邊界矩形所包含的可見區域的螢幕快照。

 const data = await browser.takeElementScreenshot('#container');
 require('fs').writeFile('out.png', data, 'base64');
 

browser.uploadFile(<selector> | <WebElement>)

使用絕對檔案路徑將檔案上傳到元素。

 await browser.uploadFile('#myFile', '/path/to/file.pdf');
 

browser.waitUntil(<conditionFunction>, [optionalMaxTimeout], [optionalRetryInterval], [optionalCallback])

一個通用命令,可以使測試執行器等待條件評估為「真值」。條件可以由任何傳回要評估的值或要等待的 Promise 的函式指定。如果條件不滿足,將會拋出 TimeoutError,並且測試將會失敗。

 let conditionValue;
 await browser.waitUntil(function() {
    return conditionValue === true;
 });

 await browser.waitUntil(async function() {
   const title = await this.execute(function() {
      return document.title;
   });
   return title === 'Nightwatch.js';
 }, 1000);

改進了對使用 async/await 的支援

我們已變更 Nightwatch 命令的結果格式,以便在使用 await 運算子時直接傳回值。

傳遞給回呼的值與 v1.x 中相同。可以透過在 nightwatch 設定中將 backwards_compatibility_mode 設定為 true 來停用此行為。

範例

使用 await 時取得值


const value = await browser.getText('#weblogin');
console.log('Value is:', value);

使用回呼時取得值


browser.getText('#weblogin', function(result) {
  console.log('Value is:', result.value);});

定義 WebDriver 功能的更多方式

現在可以透過在 nightwatch.conf.js 檔案中將 Selenium Capabilities 物件的實例設定為 capabilities 值來定義工作階段功能。

您可以參考 Selenium 文件,瞭解所有可用的功能。以下是在 nightwatch.conf.js 中定義 Chrome 無頭模式的 capabilities 物件的範例

範例


// nightwatch.conf.js
const chrome = require('selenium-webdriver/chrome');
const capabilities = new chrome.Options();
capabilities.headless();

module.exports = {
  test_settings: {
    chrome: {
      capabilities,
      webdriver: {
        start_process: true,
        server_path: require('chromedriver').path,
        cli_args: [
          // --verbose
        ]
      }
    }
  }
};

新的設定

以下是在 v2.0 中引入的新設定及其預設值

module.exports = {
  // Set this to true to use the v1.x response format for commands when using ES6 async/await

  backwards_compatibility_mode: false,

  // Set this to true to disable the global objects such as element(), browser, by(), expect()
  disable_global_apis: false,

  // Ignore network errors (e.g. ECONNRESET errors)

  report_network_errors: true,

  // Interactive element commands such as "click" or "setValue" can be retried if an error occurred (such as an "element not interactable" error)
  element_command_retries: 2,

  // Sets the initial window size, defined as an object with "width" and "height" numerical properties
  window_size: null

}

新的 WebDriver 設定

以下是為各種瀏覽器驅動程式在 v2.0 中引入的新的 webdriver 設定

module.exports = {
  webdriver: {
    // Sets the path to the Chrome binary to use. On Mac OS X, this path should reference the actual Chrome executable, not just the application binary (e.g. "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome").
    chrome_binary: '',

    // Sets the path to Chrome's log file. This path should exist on the machine that will launch Chrome.
    chrome_log_file: '',

    // Configures the ChromeDriver to launch Chrome on Android via adb.
    android_chrome: false,

    // Sets the path to the Edge binary to use.

    edge_binary: '',

    // Sets the path to the Edge binary to use.
    edge_log_file: '',

    // Sets the binary to use. The binary may be specified as the path to a Firefox executable or a desired release Channel.
    firefox_binary: '',

    // Sets the path to an existing profile to use as a template for new browser sessions. This profile will be copied for each new session - changes will not be applied to the profile itself.
    firefox_profile: ''
  }
}

重大變更

我們已盡可能地減少重大變更的數量,但有些變更難以避免。也移除了一些已棄用的功能。

以下是摘要。如果您從 1.5 或更高版本升級後有其他問題,請在 Github 上告訴我們。

  • 當使用 ES6 async/await 測試案例時,Nightwatch 命令的結果值不包含 statusvalue 屬性,而只是值 (這可以透過在 nightwatch 設定中將 backwards_compatibility_mode 設定為 true 來還原)
  • setValue 現在會在傳送按鍵之前清除值
  • sendKeys 不再是 setValue 的別名,因為它不會清除值,而是直接傳送按鍵

元素定位錯誤時,結果物件的變更

  • 包含一個 error 屬性,該屬性是一個 Error 物件實例
  • 不再包含 httpStatusCode 屬性
  • 不再包含 value 屬性
  • 移除 proxy-agent 作為相依性,因為它經常導致相依性問題;可以從 NPM 單獨安裝 proxy-agent 套件,並以相同的方式使用。

回饋

請隨時在我們的 Github Issue 上提交錯誤,或在我們的 討論區頁面上提交一般回饋。感謝您的協助!