nightwatch
Advanced tools
Comparing version 3.9.0 to 3.10.0
@@ -154,2 +154,14 @@ const EventEmitter = require('events'); | ||
if (this.module) { | ||
// this place is only reached by client-commands, protocol commands and custom-commands (no assertions or element-commands). | ||
if (this.isUserDefined) { | ||
// only custom-commands will reach here. | ||
// later extend this to client-commands and protocol commands as well. | ||
Object.defineProperty(this.module, 'rejectNodeOnAbortFailure', { | ||
configurable: true, | ||
get() { | ||
return true; | ||
} | ||
}); | ||
} | ||
this.commandFn = function commandFn({args, stackTrace}) { | ||
@@ -187,3 +199,12 @@ const instance = CommandLoader.createInstance(this.nightwatchInstance, this.module, { | ||
if (instance instanceof EventEmitter) { | ||
instance.emit('error', err); | ||
if (instance.needsPromise) { | ||
// if the instance has `needsPromise` set to `true`, the `error` event is listened | ||
// on the `context` object, not on the `instance` object (in `treenode.js`). | ||
this.emit('error', err); | ||
} else { | ||
// for class-based commands that inherit from EventEmitter. | ||
// Since the `needsPromise` is set to `false` in this case, the `complete` and `error` | ||
// events are listened on the `instance` object. | ||
instance.emit('error', err); | ||
} | ||
@@ -190,0 +211,0 @@ return; |
@@ -93,3 +93,3 @@ const {WebElement} = require('selenium-webdriver'); | ||
const el = await element; | ||
const id = await el.getId(); | ||
const id = await el?.getId(); | ||
@@ -96,0 +96,0 @@ return assertApi.ok(el instanceof WebElement, message || `Testing if the element <WebElement: ${id}> is present.`); |
@@ -183,7 +183,10 @@ const fs = require('fs'); | ||
} catch (error) { | ||
if (this.suppressNotFoundErrors) { | ||
const narrowedError = createNarrowedError({error, condition, timeout}); | ||
if ( | ||
this.suppressNotFoundErrors && | ||
narrowedError.name === 'NoSuchElementError' | ||
) { | ||
return null; | ||
} | ||
const narrowedError = createNarrowedError({error, condition, timeout}); | ||
Logger.error(narrowedError); | ||
@@ -360,2 +363,4 @@ | ||
error.message = `Error occurred while trying to locate element "${condition}": ${error.message || 'unknown error'}`; | ||
return error; | ||
@@ -362,0 +367,0 @@ } |
@@ -109,3 +109,3 @@ const EventEmitter = require('events'); | ||
if ((err.isExpect || node.namespace === 'assert' || (abortOnFailure && rejectNodeOnAbortFailure)) && this.currentNode.isES6Async) { | ||
if ((err.isExpect || node.namespace === 'assert' || (abortOnFailure && rejectNodeOnAbortFailure)) && node.isES6Async) { | ||
return true; | ||
@@ -138,3 +138,2 @@ } | ||
const result = await node.run(); | ||
const {parent} = this.currentNode; | ||
@@ -160,3 +159,3 @@ let abortOnFailure = false; | ||
if (this.shouldRejectParentNodePromise(err, node)) { | ||
parent.reject(err); | ||
node.parent.reject(err); | ||
} | ||
@@ -218,2 +217,11 @@ } else { | ||
async done(err = null) { | ||
// `this.currentTestCaseResult` represents the return value of the | ||
// currently executing `it` test case or hook. | ||
// We do not want to clear the tree if the test case is still running. | ||
// In case of an error, the tree will be cleared in the `runChildNode` | ||
// method itself. | ||
if (this.currentTestCaseResult instanceof Promise && !this.currentTestCaseResult.settled) { | ||
return err; | ||
} | ||
this.emit('asynctree:finished', this); | ||
@@ -220,0 +228,0 @@ this.empty(); |
@@ -148,3 +148,4 @@ const EventEmitter = require('events'); | ||
run() { | ||
run(currentTestCaseResult) { | ||
this.tree.currentTestCaseResult = currentTestCaseResult; | ||
if (this.tree.started) { | ||
@@ -151,0 +152,0 @@ return this; |
@@ -209,2 +209,5 @@ const EventEmitter = require('events'); | ||
this.needsPromise = true; | ||
// this change was done because the only way function-styled custom commands could be resolved | ||
// is if they contain another NW API command, which could call `node.context.emit('complete')` | ||
// inside `asynctree.js > resolveNode` method for the custom command node. | ||
commandResult = this.context; | ||
@@ -211,0 +214,0 @@ } |
@@ -319,3 +319,16 @@ const path = require('path'); | ||
// TODO: warn if test name already exists | ||
if (this.allScreenedTests.includes(testName)) { | ||
const {Logger} = Utils; | ||
const err = new Error( | ||
'An error occurred while loading the testsuite:\n' + | ||
`A testcase with name "${testName}" already exists. Testcases must have unique names inside the test suite, ` + | ||
'otherwise testcases with duplicate names might not run at all.\n\n' + | ||
'This testsuite has been disabled, please fix the error to run it again properly.' | ||
); | ||
Logger.error(err); | ||
this.setAttribute('@disabled', true); | ||
} | ||
if (!skipTest) { | ||
@@ -516,3 +529,8 @@ this.tests.push(testName); | ||
return this.testsuite[fnName].apply(context, args); | ||
const result = this.testsuite[fnName].apply(context, args); | ||
if (this.currentRunnable) { | ||
this.currentRunnable.currentTestCaseResult = result; | ||
} | ||
return result; | ||
} | ||
@@ -533,3 +551,8 @@ | ||
return fnAsync.apply(context, args); | ||
const result = fnAsync.apply(context, args); | ||
if (this.currentRunnable) { | ||
this.currentRunnable.currentTestCaseResult = result; | ||
} | ||
return result; | ||
} | ||
@@ -536,0 +559,0 @@ |
@@ -232,4 +232,6 @@ const Utils = require('../../utils'); | ||
this.instance.once('module-loaded', () => { | ||
// in case tests have been declared using other interfaces (e.g. exports) | ||
// in case tests have been declared using other interfaces (e.g. exports), | ||
// we do not want to disable the suite. | ||
if (this.instance.tests.length === 0) { | ||
// if no tests are added after all interfaces are loaded, disable the suite. | ||
this.instance.setAttribute('@disabled', true); | ||
@@ -236,0 +238,0 @@ } |
@@ -106,4 +106,4 @@ class Runnable { | ||
setDoneCallback(cb) { | ||
let originalResolve = this.deffered.resolveFn; | ||
let originalReject = this.deffered.rejectFn; | ||
const originalResolve = this.deffered.resolveFn; | ||
const originalReject = this.deffered.rejectFn; | ||
this.deffered.resolveFn = function() {}; | ||
@@ -163,3 +163,28 @@ this.deffered.rejectFn = function() {}; | ||
this.queue.run().then(err => { | ||
// `this.currentTestCaseResult` represents the return value of the currently | ||
// running test case or hook. | ||
// in case the runnable is executing something other than a test case/hook, | ||
// `this.currentTestCaseResult` will be `undefined`. | ||
if (this.currentTestCaseResult instanceof Promise) { | ||
this.currentTestCaseResult | ||
.catch(() => { | ||
// to avoid unhandledRejections | ||
// although the test case promises are already handled for rejections | ||
// above (`result.catch()`), if we don't use `.catch()` here again, | ||
// `.finally` will return a new promise that will be rejected without | ||
// any error handling. | ||
}) | ||
.finally(() => { | ||
// mark the promise as settled as a cue to the asynctree so that it | ||
// can get cleared out and subsequently call the queue `done` method. | ||
this.currentTestCaseResult.settled = true; | ||
// sometimes this promise is settled after the last call of | ||
// asynctree `done` method, so we need schedule a tree traversal | ||
// again to clear out the tree and call the queue `done` method. | ||
this.queue.scheduleTraverse(); | ||
}); | ||
} | ||
this.queue.run(this.currentTestCaseResult).then(err => { | ||
if (err) { | ||
@@ -166,0 +191,0 @@ return this.deffered.rejectFn(err); |
{ | ||
"name": "nightwatch", | ||
"description": "Easy to use Node.js based end-to-end testing solution for web applications using the W3C WebDriver API.", | ||
"version": "3.9.0", | ||
"version": "3.10.0", | ||
"author": "Andrei Rusu", | ||
@@ -46,3 +46,3 @@ "homepage": "https://nightwatchjs.org", | ||
"piscina": "^4.3.1", | ||
"selenium-webdriver": "4.26.0", | ||
"selenium-webdriver": "4.27.0", | ||
"semver": "7.5.4", | ||
@@ -49,0 +49,0 @@ "stacktrace-parser": "0.1.10", |
@@ -0,1 +1,5 @@ | ||
import {CommandInstance} from './index'; | ||
export interface CustomCommandInstance extends CommandInstance {} | ||
export interface NightwatchCustomCommandsModel { | ||
@@ -8,3 +12,3 @@ /** | ||
* command() { | ||
* | ||
* | ||
* return Promise.resolve(); | ||
@@ -16,3 +20,3 @@ * } | ||
*/ | ||
command: (...args: any) => unknown | Promise<unknown>; | ||
command: (...args: any[]) => unknown | Promise<unknown>; | ||
} | ||
@@ -19,0 +23,0 @@ |
@@ -194,3 +194,4 @@ import {expectAssignable, expectError, expectType} from "tsd"; | ||
expectType<Promise<WebElement>>(elem.setAttribute('role', 'button')); | ||
expectType<Promise<WebElement>>(elem.dragAndDrop({xOffset: 150, yOffset: 500})); | ||
expectType<Promise<WebElement>>(elem.dragAndDrop({x: 150, y: 500})); | ||
expectType<Promise<WebElement>>(elem.dragAndDrop(elem.webElement)); | ||
expectType<Promise<WebElement>>(elem.moveTo(100, 100)); | ||
@@ -197,0 +198,0 @@ expectType<Promise<WebElement>>(elem.clickAndHold()); |
@@ -363,6 +363,5 @@ import { | ||
export type DragAndDropDestination = { | ||
readonly xOffset: number; | ||
readonly yOffset: number; | ||
}; | ||
export type DragAndDropDestination = | ||
| {readonly x: number; readonly y: number;} | ||
| WebElement | ||
@@ -369,0 +368,0 @@ export interface ElementFunction |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
1911275
562
54275
+ Addedselenium-webdriver@4.27.0(transitive)
- Removedselenium-webdriver@4.26.0(transitive)
Updatedselenium-webdriver@4.27.0