TestCafe chrome:headless intermittently fails with Vue event emit - vue.js

Chrome: Version 91.0.4472.114 (Official Build) (x86_64)
TestCafe: 1.14.2
Vue codes
Template
<input
id="amount"
v-model="amount"
min="0"
name="amount"
class="input-field"
:placeholder="labelPlaceholder"
#input="validateAmount"
>
Event emitter
validateAmount() {
...
this.$root.$emit('new-amount', this.id, this.valid, this.amount);
},
Event receiver
this.$root.$on('new-amount', (id, valid, amount) => {
if (this.id === id) {
this.amount = amount;
this.validAmount = valid; // In the screenshot below
}
});
Test codes
TestCafe with chrome:headless (browser mode works) intermittently fails for tests with Vue event emit. No matter test waits for 2 seconds or not, the result is same but when it enables Quarantine mode it passes (I don't want to use this though)
test('Should be enabled when a valid value is entered', async (t) => {
await t
.typeText(amount, '1')
.expect(amount.value)
.eql('1');
await t.wait(2000);
const after = button.withAttribute('disabled', 'disabled').exists;
await t.expect(after).notOk();
});
Result TestCafe test (screenshot on error)
1) AssertionError: expected true to be falsy
Result by user (MUST BE)

Since enabling the quarantine mode fixes the problem, I assume that the issue occurs because sometimes the event is not fired within 2 seconds.
Try to rewrite your code in the following way:
test('Should be enabled when a valid value is entered', async (t) => {
await t
.typeText(amount, '1')
.expect(amount.value).eql('1');
.expect(button.hasAttribute('disabled')).notOk();
});

Related

Cypress - check test failure in global afterEach

Is there a possibility to check in global afterEach if test (it) failed?
Such global afterEach is located in support/index.js:
afterEach(() => {
// check if test failed and perform some actions
})
You can use afterEach hook and remain in the scope of the Cypress context (where cy commands are available), for example:
afterEach(function() {
if (this.currentTest.state === 'failed') {
// your code
}
});
Reference: https://github.com/cypress-io/cypress/discussions/15047
Or you can use test:after:run event and switch to node context (where any kind of node code can be executed outside of the scope of Cypress, like accessing database or file system), for example:
Cypress.on('test:after:run', (test, runnable) => {
if (test.state === 'failed') {
// your code
}
});
Reference: https://docs.cypress.io/api/events/catalog-of-events#Cypress-Events
What you seek is the after:spec and is exposed via plug-ins. You'll have access to the spec and the results.
https://docs.cypress.io/api/plugins/after-spec-api#Syntax

Why does an expectation in $nextTick never fail?

I need to use $nextTick in order to test some parts of my program. Somehow, it breaks my tests and make them success all the time - even when they should fail.
A minimal test sample would look like this:
import App from "./App";
import { shallowMount } from "#vue/test-utils";
it("should fail", () => {
const wrapper = shallowMount(App);
wrapper.vm.$nextTick(() => {
expect(1).toBe(3);
done();
});
});
You can find a sandbox example here
If you open the console, you should find the following error messages:
[Vue warn]: Error in nextTick: "Error: expect(received).toBe(expected)
Error: expect(received).toBe(expected)
Why does the test success? Why are the errors ignored? How do I use $nextTick properly if note like so?
In order to wait until Vue.js has finished updating the DOM after a
data change, you can use Vue.nextTick(callback) immediately after the
data is changed. The callback will be called after the DOM has been
updated.
I can not see any trigger that change DOM in your test. And you missed done argument in test callback
For example in the following this is wrapper.find('button').trigger('click')
it('fetches async when a button is clicked', (done) => {
const wrapper = shallowMount(Foo)
wrapper.find('button').trigger('click')
wrapper.vm.$nextTick(() => {
expect(wrapper.vm.value).toBe('value')
done()
})
})

What is the best way to wait for 'WebComponentsReady' event in TestCafe?

I want to wait for web components in the page to upgrade before running any TestCafe tests (In other words, wait for WebComponentsReady event before running tests). What is the best way to do this?
TestCafe starts to execute any action on the page after the DOMContentReady event is raised. As I see, the WebComponentsReady event can be raised before DOMContentReady.
TestCafe allows you to wait for some events in the browser by using ClientFunction:
const waitForWebComponentsReady = ClientFunction(() => {
return new Promise(resolve => {
window.addEventListener('WebComponentsReady', resolve);
});
});
await waitForWebComponentsReady();
However, note that TestCafe can't guarantee that this code will be executed before the WebComponentReady event is raised. As a result, this Promise will not be resolved.
As a solution, you can find another way to identify if the required Web Component is loaded. For example, you can check that some element is visible on the page:
await t.expect(Selector('some-element').visible).ok();
Meanwhile, TestCafe has a feature suggestion to add the capability to execute a custom script before page initialization scripts. You will be able to use code like this when the feature is implemented:
import { ClientFunction } from 'testcafe';
const initScript = `window.addEventListener('WebComponentsReady', () => window.WebComponentsLoaded = true);`;
fixture `My Fixture`
.page `http://example.com`
.addScriptToEachPage(initScript)
.beforeEach(async t => {
await t.expect(ClientFunction(() => window.WebComponentsLoaded)()).ok();
});

How to test promises in Mongo(ose)/Express app?

I'm using promises to wrap asynchronous (Mongo) DB ops at the end of an (expressJS) route.
I want to try and figure out how to test the following code.
userService
userService.findOne = function (id) {
var deferred = q.defer();
User.findOne({"_id" : id})
.exec(function (error, user) {
if (error) {
deferred.reject(error);
} else {
deferred.resolve(user);
}
});
return deferred.promise;
};
userRoute
var user = function (req, res) {
var userId = req.params.id
, userService = req.load("userService");
// custom middleware that enables me to inject mocks
return userService.findOne(id)
.then(function (user) {
console.log("called then");
res.json({
msg: "foo"
});
}).catch(function (error) {
console.log("called catch");
res.json({
error: error
});
}).done();
};
Here's an attempt to test the above with mocha
userTest
it("when resolved", function (done) {
var jsonSpy = sinon.spy(httpMock.res, "json")
, httpMock = require("/path/to/mock/http/object")
, serviceMock = require("/path/to/mock/service"),
, deferred = q.defer()
, findStub = sinon.stub(serviceMock, "findOne")
.returns(deferred.promise)
, loadStub = sinon.stub(httpMock.req, "load")
.returns(serviceMock),
retPromise;
// trigger route
routes.user(httpMock.req, httpMock.res);
// force promise to resolve?
deferred.resolve();
expect(jsonSpy.called).to.be.true; // fails
// chai as promised
retPromise = findStub.returnValues[0];
expect(retPromise).to.be.fulfilled; // passes
});
the http mock is just an empty object with no-ops where expressJS would normally start rendering stuff. I've added some logging inside those no-ops to get an idea on how this is hanging together.
This isn't really working out. I want to verify how the whole is integrated, to establish some sort of regression suite - but I've effectively mocked it to smithereens and I'm just testing my mocks (not entirely successfully at that).
I'm also noticing that the console logs inside my http mocks triggered by then and catch are firing twice - but the jsonSpy that is invoked inside the actual code (verified by logging out the sinon spy within the userRoute code) is not called in test.
Has anyone got some advice on integration testing strategies for express apps backed by Mongo?
It looks to me like you're not giving your promise an opportunity to fire before you check if the result has been called. You need to wait asynchronously for userService.findOne()'s promise chain to complete before jsonSpy.called will be set. Try this instead:
// start of code as normal
q.when(
routes.user(httpMock.req, httpMock.res),
function() { expect(jsonSpy.called).to.be.true; }
);
deferred.resolve();
// rest of code as normal
That should chain off the routes.user() promise and pass as expected.
One word of caution: I'm not familiar with your framework, so I don't know if it will wait patiently for all async events to go off. If it's giving you problems calling back into your defer chain, you may want to try nodeunit instead, which handles async tests very well (IMO).

How to set jasmine for karma e2e for testing angular app?

I try to create e2e tests with karma and jasmine with yeoman. In my karma-e2e.conf.js I add jasmine:
files = [
JASMINE,
JASMINE_ADAPTER,
ANGULAR_SCENARIO,
ANGULAR_SCENARIO_ADAPTER,
'test/e2e/**/*.js'
];
A need async testing so I need to use runs, waits, waitsFor (https://github.com/pivotal/jasmine/wiki/Asynchronous-specs)
But if I try to use it:
it('test', function () {
runs(function () {
...
});
});
Scenatio test runner returns this:
TypeError: Cannot call method 'runs' of null
at runs (http://localhost:8080/adapter/lib/jasmine.js:562:32)
at Object.<anonymous> (http://localhost:8080/base/test/e2e/eduUser.js:42:3)
at Object.angular.scenario.SpecRunner.run (http://localhost:8080/adapter/lib/angular-scenario.js:27057:15)
at Object.run (http://localhost:8080/adapter/lib/angular-scenario.js:10169:18)
I don't know where the problem is. Can you help me please?
Angular e2e tests with Karma don't and can't use the JASMINE adapter. Instead you have the ANGULAR_SCENARIO_ADAPTER which has a similar feel to writing Jasmine tests.
All commands in the adapter's API are asynchronous anyway. For example element('#nav-items').count() doesn't return a number, it returns a Future object. Future objects are placed in a queue and executed asynchronously as the runner progresses. To quote the API docs:
expect(future).{matcher}:
[...] All API statements return a future object, which get a value assigned after they are executed.
If you need to run your own asynchronous test code, you can extend the adapter's DSL, this is easier than it might sound. The idea is that you return your own Future which can be evaluated by a matcher such as toBe(). There are some examples on how to do this in the e2e-tests.js Gist from Vojta. Just remember to call done(null, myRetrunValue); when your test code is successful (myRetrunValue is the value evaluated by your matcher). Or done('Your own error message'); if you want the test to fail.
UPDATE: In response to question below. To simulate a login, first add a function called login to the dsl:
angular.scenario.dsl('login', function() {
return function(selector) {
// #param {DOMWindow} appWindow The window object of the iframe (the application)
// #param {jQuery} $document jQuery wrapped document of the application
// #param {function(error, value)} done Callback that should be called when done
// (will basically call the next item in the queuue)
return this.addFutureAction('Logging in', function(appWindow, $document, done) {
// You can do normal jQuery/jqLite stuff here on $document, just call done() when your asynchronous tasks have completed
// Create some kind of listener to handle when your login is complete
$document.one('loginComplete', function(e){
done(null, true);
}).one('loginError', function(e){
done('Login error', false);
});
// Simulate the button click
var loginButton = $document.find(selector || 'button.login');
loginButton.click();
})
};
});
And then call:
beforeEach( function()
{
expect( login('button.login') ).toBeTruthy();
});