使用异步 forEach 循环磁带“测试退出而没有结束”错误

Posted

技术标签:

【中文标题】使用异步 forEach 循环磁带“测试退出而没有结束”错误【英文标题】:Tape "test exited without ending" error with asynchronous forEach loops 【发布时间】:2019-04-12 20:23:42 【问题描述】:

我在做什么

编辑:我创建了一个repo,其中包含我的问题的简化版本,重现了该问题。

我正在尝试使用 browserstack、selenium-webdriver 和 tape 设置自动化前端测试。

More about tape

这个想法是定义多个浏览器和设备,它们必须通过 X 数量的给定测试一个接一个地进行测试。在下面的示例中,我在 OSX 上只定义了一个测试和两个浏览器。

为了只定义一次浏览器并处理测试,我创建了一个 repo test-runner,它应该作为 dev-dependency 添加到需要在给定设备和浏览器上测试的 repo 中。 test-runner 获得所有需要的测试通过,启动第一个浏览器,在该浏览器上运行测试,一旦所有测试完成,浏览器将关闭quit(),下一个浏览器将启动并再次测试。

测试运行器

/index.js

const webdriver = require( 'selenium-webdriver' )

// ---
// default browser configs
// ---
const defaults = 
  "os" : "OS X",
  "os_version" : "Mojave",
  "resolution" : "1024x768",
  "browserstack.user" : "username",
  "browserstack.key" : "key",
  "browserstack.console": "errors",
  "browserstack.local" : "true",
  "project" : "element"


// ---
// browsers to test
// ---
const browsers = [
  
    "browserName" : "Chrome",
    "browser_version" : "41.0"
  ,
  
    "browserName" : "Safari",
    "browser_version" : "10.0",
    "os_version" : "Sierra"
  
]

module.exports = ( tests, url ) => 

  // ---
  // Asynchronous forEach loop
  // helper function
  // ---
  async function asyncForEach(array, callback) 
    for (let index = 0; index < array.length; index++) 
      await callback(array[index], index, array)
    
  

  // ---
  // runner
  // ---
  const run = async () => 

    // ---
    // Iterate through all browsers and run the tests on them
    // ---
    await asyncForEach( browsers, async ( b ) => 

      // ---
      // Merge default configs with current browser
      // ---
      const capabilities = Object.assign( , defaults, b )

      // ---
      // Start and connect to remote browser
      // ---
      console.info( '-- Starting remote browser hang on --', capabilities.browserName )
      const browser = await new webdriver.Builder().
        usingServer( 'http://hub-cloud.browserstack.com/wd/hub' ).
        withCapabilities( capabilities ).
        build()

      // ---
      // Navigate to page which needs to be checked (url)
      // ---
      console.log('-- Navigate to URL --')
      await browser.get( url )

      // ---
      // Run the tests asynchronously
      // ---
      console.log( '-- Run tests --- ' )
      await asyncForEach( tests, async ( test ) => 
        await test( browser, url, capabilities, webdriver )
       )

      // ---
      // Quit the remote browser when all tests for this browser are done
      // and move on to next browser
      // Important: if the browser is quit before the tests are done
      // the test will throw an error beacause there is no connection
      //  anymore to the browser session
      // ---
      browser.quit()

     )

  

  // ---
  // Start the tests
  // ---
  run()


如果你想知道这个 asyncForEach 函数是如何工作的,我是从 here 那里得到的。

我的仓库

/test/front/index.js

const testRunner = require( 'test-runner' )
const url = ( process.env.NODE_ENV == 'development' ) ? 'http://localhost:8888/element/...' : 'https://staging-url/element/...'

// tests to run
const tests = [
  require('./test.js')
]

testRunner( tests, url )

/test/front/test.js

const tape = require( 'tape' )

module.exports = async ( browser, url, capabilities, driver ) => 

  return new Promise( resolve => 

    tape( `Frontend test $capabilities.browserName $capabilities.browser_version`, async ( t ) => 

      const myButton = await browser.wait( driver.until.elementLocated( driver.By.css( 'my-button:first-of-type' ) ) )

      myButton.click()

      const marked = await myButton.getAttribute( 'marked' )
      t.ok(marked == "true", 'Button marked')

      //---
      // Test should end now
      //---
      t.end()

      resolve()

     )

  )


/package.json


  ...
  "scripts": 
    "test": "NODE_ENV=development node test/front/ | tap-spec",
    "travis": "NODE_ENV=travis node test/front/ | tap-spec"
  
  ...

当我想运行测试时,我在 my-repo 中执行 npm run test

请记住,我们只有一个测试(但也可以是多个测试)并定义了两个浏览器,因此行为应该是:

    启动浏览器 1 并导航 (Chrome) 在浏览器 1 (Chrome) 上进行一次测试 关闭浏览器 1 (Chrome) 启动浏览器 2 并导航 (Safari) 在浏览器 2 (Safari) 上进行一次测试 关闭浏览器 2 (Safari) 完成

问题

异步的东西似乎工作得很好,浏览器按预期一个接一个地启动。 问题是,即使我调用t.end(),第一个测试也没有完成,并且我没有进行第二个测试(在 4 之后失败。)

我尝试了什么

我尝试使用 t.pass() 并使用 NODE_ENV=development tape test/front/ | tap-spec 运行 CLI,但没有帮助。 我还注意到,当我在test.js 中没有resolve() 时,测试结束得很好,但当然我不会进入下一个测试。

我还尝试像 this issue 中的解决方案一样调整我的代码,但没能成功。

同时,我还在磁带 github 页面上打开了 issue。

所以我希望这个问题阅读起来不会太痛苦,任何帮助都将不胜感激。

【问题讨论】:

尝试用 IE、Firefox 或任何其他浏览器替换 Safari 并运行测试。问题可能是因为 Safari 没有直接解析 localhost 导致问题。 这个问题写得很好,但这是一个非常具体的问题,很难重现,也不容易得到答案 @MukeshTiwari 问题不是因为 safari 我认为@mihai 是在正确的道路上,但我还没有设法让它工作:-( 【参考方案1】:

似乎tape 不适用于异步代码。在他们的 Github 问题页面上查看这些讨论:

https://github.com/substack/tape/issues/223https://github.com/substack/tape/issues/160

解决方案似乎是在调用任何异步代码之前,在开头使用 tape.add 声明您的测试。

如果您只是按顺序打开浏览器,我也会尝试重构一些可能不需要的异步代码。

【讨论】:

您好,感谢您的回答,我已阅读您分享的问题,但不了解如何将其应用到我的设置中。有时间可以给我写个例子吗? 我现在尝试使用 .add() 函数等创建一个类。但我总是不得不做一些异步的事情,这让我更加困惑。现在尝试了 100 种不同的方法,但都没有成功。让它以某种方式工作真的很酷,在另一种情况下,我必须在每个测试中定义浏览器,我认为这不是必需的。 按顺序打开浏览器的问题是,我必须等待测试才能退出浏览器并启动下一个(我认为) 我会帮忙,但是这个设置对我来说有点重,我尝试安装。我会看看我是否可以用 puppeteer 而不是 selenium 运行一些东西 我明白了。问题是,我们曾经使用 puppeteer 并希望迁移到 selenium,因为它已被 browserstack 显式集成。 puppeteer 也有异步问题。我创建了一个重现该问题的 repo,请参阅上面的编辑。【参考方案2】:

不幸的是,我还没有对现有设置做出任何回答,并设法让事情以稍微不同的方式工作。

我发现,只要任何其他进程正在运行,tape() 进程就不能.end()。就我而言,它是browser。所以只要浏览器运行,我想tape就不能结束。

在我的example repo 中没有browser,但必须有其他东西仍在运行以防止tape 结束。

所以我只需要在一个tape 进程中定义测试。由于我设法按顺序打开浏览器并进行测试,现在完全没问题。

如果有很多不同的东西要测试,我会把这些东西分成不同的文件,然后导入到主测试文件中。

我还从dependency 导入浏览器capabilities,以便只定义一次。

代码如下:

依赖主文件


  "browsers": [
      "browserName": "Chrome",
      "browser_version": "41",
      "os": "Windows",
      "os_version": "10",
      "resolution": "1024x768",
      "browserstack.user": "username",
      "browserstack.key": "key"
    ,
    
      "browserName": "Safari",
      "browser_version": "10.0",
      "os": "OS X",
      "os_version": "Sierra",
      "resolution": "1024x768",
      "browserstack.user": "username",
      "browserstack.key": "key"
    
  ]

test.js

const tape = require( "tape" )
const  Builder, By, until  = require( 'selenium-webdriver' );
const  browsers  = require( "dependency" )
const browserStack = 'http://hub-cloud.browserstack.com/wd/hub'

tape( "Browsers", async ( t ) => 

  await Promise.all( browsers.map( async ( capa ) => 

    const  browserName, browser_version, os  = capa

    const browser = new Builder().usingServer( browserStack ).withCapabilities( capa ).build();

    await browser.get( 'http://someurl.com' )

    const myButton = await browser.wait( until.elementLocated( By.css( 'my-button:first-of-type' ) ) )

    myButton.click()

    const marked = await myButton.getAttribute( 'marked' )

    t.ok(marked == "true", `$browserName $browser_version $os`)

    await browser.quit()

   ) )

  t.end()

 )

【讨论】:

【参考方案3】:

我会先尝试简化测试的编写和执行方式:

    您是否尝试过使用磁带二进制文件运行测试?例如tape test/front/test.js 同时简化test/front/test/js(你必须弄清楚如何以其他方式传递参数;也许你可以硬编码它们只是为了调试?)
const tape = require( 'tape' )

tape( `your test outline`, ( t ) => 

  const alwaysEnd = () => t.end();

  new Promise((resolve, reject) => 
    // your async stuff here...
    // resolve() or reject() at the end
  ).then(alwaysEnd, alwaysEnd);
)

【讨论】:

您好,谢谢您的回答,是的,我确实尝试了二进制文件(没有帮助),并且从测试内部硬编码和启动浏览器工作得很好。但我想声明浏览器只是来自测试之外的浏览器,这就是整个想法。

以上是关于使用异步 forEach 循环磁带“测试退出而没有结束”错误的主要内容,如果未能解决你的问题,请参考以下文章

node.js:如何在 forEach 循环中使用异步调用实现路由方法?

@foreach 循环中的多种形式。如何使用 javascript 异步提交一个。 C# 核心剃刀

JavaScript 循环中调用异步函数的三种方法,及为什么 forEach 无法工作的分析

JavaScript 循环中调用异步函数的三种方法,及为什么 forEach 无法工作的分析

如何正确调用 Parallel.ForEach 循环中的调用异步方法[重复]

如何等待来自 forEach 循环的多个异步调用?