我们很高兴地宣布,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 的复杂用户手势。此 API 可在 Nightwatch 中使用,并通过现有的 .perform() 命令随时可以使用。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)
}

Selenium 文档网站上的 API 文档 中有更多示例。在上面的示例中,使用 this.actions(<options>) 创建了 Actions 类实例。.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.node.org.cn')
      .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.node.org.cn')
      .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.node.org.cn').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 中提供了早期支持,它已在 ChromeDriver 中通过 Chrome Developer Tools 提供。

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.node.org.cn/');

    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 问题 上提交错误,或在我们 讨论 页面上提供一般性反馈。感谢您的帮助!