nightwatchjs / nightwatch

Integrated end-to-end testing framework written in Node.js and using W3C Webdriver API. Developed at @browserstack
https://nightwatchjs.org
MIT License
11.8k stars 1.32k forks source link

verify.textContains() ends process if the element was not found #3026

Open gizmodus opened 2 years ago

gizmodus commented 2 years ago

I wrote a custom test runner with the new version 2.0.4 which is really cool! Love the new architecture of the programmatic API. The test runner sequentally runs multiple tests. If a test fails, it should continues with the next one.

I have the following scenario: browser.verify.textContains('my-element', 'test')

-> If the element is found but the text "test" not present, it works as intended and my test runner continues with the next test. -> However, if the element is not found, the whole process is killed.

I found out, that the process is killed via unhandled() function in process-listener.js. Is this the intended behavior? What can I do to keep my process alive?

Not sure if this is related to https://github.com/nightwatchjs/nightwatch/issues/3019

Thanks for your help!

beatfactor commented 2 years ago

Sounds good, but we can't really help without you providing more details or a reproducible scenario. One thing to mention though is that when using a custom runner, .verify assertions won't work properly because they rely on the Nightwatch runner and reporter to fail the test at the end.

It sounds like the problem you're facing is caused by an unhandled rejection so I will need to debug that to see why that happens and how we can fix it.

gizmodus commented 2 years ago

Thanks for your response, @beatfactor!

I tried to narrow it down a bit. My basic setup is this:

import test = require('./tests/test-001');

const client = Nightwatch.createClient({ ... });
const browser = await client.launchBrowser();

for (const key in test) {
      try {
        await this.test[key](browser);
      } catch(error) {
         console.error(error);
      }
}

The file 'test-001.ts' is a normal Nightwatch.js test:

export = {
  'My Test': async (browser) => {
    // This works
    await browser
    .assert.textContains('body', '123')
    .assert.textContains('inexistent-element', '123');

    // This kills the process
    await browser
      .assert.textContains('inexistent-element', '123')
      .assert.textContains('body', '123');

    // This kills the process, too
    await browser
      .assert.textContains('body', '123')
      .assert.textContains('inexistent-element', '123')
      .assert.textContains('body', '123');
  }

It seems to me that the problem only occurs if another assertion is chained after the one with the inexistent element. When I log the argument array that are passed to the callback function in 'textContains.js' -> this.command(), I get this:

[
  {
    error: NoSuchElementError: Timed out while waiting for element "inexistent-element-123" with "css selector" to be present for 10000 milliseconds.
        at LocateElement.sendElementsAction (D:\Data\git\test-application\node_modules\nightwatch\lib\element\locator.js:255:15)
        at runMicrotasks (<anonymous>)
        at processTicksAndRejections (internal/process/task_queues.js:95:5)
        at async LocateElement.findElement (D:\Data\git\test-application\node_modules\nightwatch\lib\element\locator.js:143:20)
        at async GetText.findElement (D:\Data\git\test-application\node_modules\nightwatch\lib\element\command.js:215:14)
        at async PeriodicPromise.perform (D:\Data\git\test-application\node_modules\nightwatch\lib\utils\periodic-promise.js:81:23)
        at async PeriodicPromise.runAction (D:\Data\git\test-application\node_modules\nightwatch\lib\utils\periodic-promise.js:48:22) {
      selector: 'inexistent-element-123',
      abortOnFailure: undefined,
      retries: undefined,
      strategy: 'css selector'
    },
    status: -1,
    value: null
  },
  GetText {
    _events: [Object: null prototype] {},
    _eventsCount: 0,
    _maxListeners: undefined,
    nightwatchInstance: NightwatchClient {
      _events: [Object: null prototype] {},
      _eventsCount: 0,
      _maxListeners: 0,
      settings: [Object],
      isES6AsyncTestcase: false,
      isES6AsyncCommand: false,
      options: [Object],
      __sessionId: 'a54f462fe84e998bb5ccb547c968fbeb',
      __argv: [Object],
      __locateStrategy: 'css selector',
      __httpOpts: [HttpOptions],
      __transport: [ChromeDriver],
      __commandQueue: [CommandQueue],
      __elementLocator: [LocateElement],
      __reporter: [Object],
      __api: [NightwatchAPI],
      __configLocateStrategy: 'css selector',
      [Symbol(kCapture)]: false
    },
    __commandName: 'getText',
    __strategy: 'css selector',
    __suppressNotFoundErrors: true,
    __abortOnFailure: false,
    throwOnMultipleElementsReturned: false,
    elementId: null,
    args: [],
    __timeoutMs: 10000,
    __rescheduleIntervalMs: 30,
    __selector: {
      selector: 'inexistent-element-123',
      suppressNotFoundErrors: true
    },
    message: null,
    __callback: [Function (anonymous)],
    __element: Element {
      name: undefined,
      __index: undefined,
      __selector: 'inexistent-element-123',
      locateStrategy: 'css selector',
      pseudoSelector: null,
      suppressNotFoundErrors: true,
      parent: undefined,
      resolvedElement: null
    },
    executor: PeriodicPromise {
      _events: [Object: null prototype] {},
      _eventsCount: 0,
      _maxListeners: undefined,
      __queue: [Array],
      __rescheduleIntervalMs: 30,
      __timeoutMs: 10000,
      retries: 0,
      startTime: 1643906325643,
      [Symbol(kCapture)]: false
    },
    elementCommandRetries: 0,
    maxElementCommandRetries: 3,
    stackTrace: 'Error: \n' +
      '    at AssertionInstance.command (D:\\Data\\git\\test-application\\node_modules\\nightwatch\\lib\\api\\assertions\\textContains.js:99:14)\n' +
      '    at AssertionScheduler.schedule (D:\\Data\\git\\test-application\\node_modules\\nightwatch\\lib\\api\\_loaders\\assertion-scheduler.js:83:19)\n' +
      '    at AssertionScheduler.start (D:\\Data\\git\\test-application\\node_modules\\nightwatch\\lib\\api\\_loaders\\assertion-scheduler.js:77:10)\n' +
      '    at Object.module.exports.create (D:\\Data\\git\\test-application\\node_modules\\nightwatch\\lib\\api\\_loaders\\assertion-scheduler.js:180:20)\n' +
      '    at D:\\Data\\git\\test-application\\node_modules\\nightwatch\\lib\\api\\_loaders\\assertion.js:122:30',
    [Symbol(kCapture)]: false
  }
]

The funny thing is that when I do the following change in 'textContains.js', the problem is solved and the process stays alive (I did this by coincidence when I was logging the error):

// Original
this.command = function(callback) {
    this.api.getText(setElementSelectorProps(definition, {
      suppressNotFoundErrors: true
    }), callback);
  };

// Magic fix
this.command = function(callback) {
    this.api.getText(setElementSelectorProps(definition, {
      suppressNotFoundErrors: true
    }), function(...args) {
      callback(...args)
    });
  };

Could this be a problem with the scope of the callback function?