Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Fix asynctree from getting cleared in between test case. #4325

Merged
merged 6 commits into from
Dec 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions lib/core/asynctree.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,15 @@ class AsyncTree extends EventEmitter{
}

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);
this.empty();
this.createRootNode();
Expand Down
3 changes: 2 additions & 1 deletion lib/core/queue.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ class CommandQueue extends EventEmitter {
return Promise.resolve();
}

run() {
run(currentTestCaseResult) {
this.tree.currentTestCaseResult = currentTestCaseResult;
if (this.tree.started) {
return this;
}
Expand Down
14 changes: 12 additions & 2 deletions lib/testsuite/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,12 @@ class Context extends EventEmitter {
args[0] = client.api;
}

return this.testsuite[fnName].apply(context, args);
const result = this.testsuite[fnName].apply(context, args);
if (this.currentRunnable) {
this.currentRunnable.currentTestCaseResult = result;
}

return result;
}

/**
Expand All @@ -541,7 +546,12 @@ class Context extends EventEmitter {
const fnAsync = Utils.makeFnAsync(expectedArgs, this.testsuite[fnName], context);
const args = this.getHookFnArgs(done, api, expectedArgs);

return fnAsync.apply(context, args);
const result = fnAsync.apply(context, args);
if (this.currentRunnable) {
this.currentRunnable.currentTestCaseResult = result;
}

return result;
}

////////////////////////////////////////////////////////////////
Expand Down
31 changes: 28 additions & 3 deletions lib/testsuite/runnable.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ 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() {};
this.deffered.rejectFn = function() {};

Expand Down Expand Up @@ -159,7 +159,32 @@ class Runnable {
}
}

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) {
return this.deffered.rejectFn(err);
}
Expand Down
80 changes: 80 additions & 0 deletions test/sampletests/withdescribe/basic2/sample.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
const assert = require('assert');

describe('basic describe test', async function () {
const nwClient = this['[client]'];

const asynctreeEventListener = () => {
this.globals.asynctreeFinishedEventCount++;
};

const queueEventListener = () => {
this.globals.queueFinishedEventCount++;
};

this.desiredCapabilities = {
name: 'basicDescribe'
};

test('with awaited JS command bw Nightwatch commands', async function (client) {
nwClient.queue.tree.on('asynctree:finished', asynctreeEventListener);

// queue event listeners are automatically removed after
// every (test case + afterEach + after (if applicable))
// so, no need to remove this listener manually before next test case run.
nwClient.queue.on('queue:finished', queueEventListener);

await client.assert.strictEqual(client.options.desiredCapabilities.name, 'basicDescribe');
assert.equal(nwClient.queue.tree.rootNode.childNodes.length, 1);

await client.url('http://localhost');
assert.equal(nwClient.queue.tree.rootNode.childNodes.length, 2);

await new Promise((resolve) => {
setTimeout(function() {
resolve(null);
}, 50);
});
assert.equal(nwClient.queue.tree.rootNode.childNodes.length, 2);

await client.assert.elementPresent('#weblogin');
assert.equal(nwClient.queue.tree.rootNode.childNodes.length, 3);

// queue:finished event shouldn't be emitted in between the test.
assert.equal(this.globals.queueFinishedEventCount, 0);
});

test('with await in last command', async function (client) {
nwClient.queue.tree.on('asynctree:finished', asynctreeEventListener);

await client.assert.strictEqual(client.options.desiredCapabilities.name, 'basicDescribe');
assert.equal(nwClient.queue.tree.rootNode.childNodes.length, 1);

await client.url('http://localhost');
assert.equal(nwClient.queue.tree.rootNode.childNodes.length, 2);

await client.assert.elementPresent('#weblogin');
assert.equal(nwClient.queue.tree.rootNode.childNodes.length, 3);
});

test('without await in last command', async function (client) {
nwClient.queue.tree.on('asynctree:finished', asynctreeEventListener);

await client.assert.strictEqual(client.options.desiredCapabilities.name, 'basicDescribe');
assert.equal(nwClient.queue.tree.rootNode.childNodes.length, 1);

await client.url('http://localhost');
assert.equal(nwClient.queue.tree.rootNode.childNodes.length, 2);

client.assert.elementPresent('#weblogin');
assert.equal(nwClient.queue.tree.rootNode.childNodes.length, 3);
});

afterEach(function () {
nwClient.queue.tree.removeListener('asynctree:finished', asynctreeEventListener);
assert.equal(nwClient.queue.tree.rootNode.childNodes.length, 0);
});

after(function (client) {
client.end();
});
});
47 changes: 47 additions & 0 deletions test/src/core/testAsyncTree.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const assert = require('assert');
const path = require('path');
const CommandGlobals = require('../../lib/globals/commands-w3c.js');
const common = require('../../common.js');
const {runTests} = common.require('index.js');
const {settings} = common;

describe('element().isPresent() command', function() {
before(function (done) {
CommandGlobals.beforeEach.call(this, done);
});

after(function (done) {
CommandGlobals.afterEach.call(this, done);
});

it('test runner with multiple test interfaces - exports/describe', function () {
const srcFolders = [
path.join(__dirname, '../../sampletests/withdescribe/basic2')
];

const globals = {
asynctreeFinishedEventCount: 0,
queueFinishedEventCount: 0,
reporter(results, cb) {
if (results.lastError) {
throw results.lastError;
}

// should equal the number of test cases.
assert.equal(globals.asynctreeFinishedEventCount, 3);

// only counts for the first test case + afterEach
assert.equal(globals.queueFinishedEventCount, 2);

cb();
}
};

return runTests(settings({
globals,
src_folders: srcFolders,
output: true,
silent: false
}));
});
});
Loading