How can I `await` closing of window in Spectron? - testing

I'm working on Electron application that handles some logic in modal windows.
Those windows are waiting for async actions to resolve and then self-close. Now I am struggling to test this behaviour using Spectron and Jest: seems that there are no methods to catch window's closing and then proceed to another tests.
Currently my code is
it('doing its job', async () => {
// awaits and expects that aren't related
await app.client.click('button[data-role="close"]');
await new Promise(r => setTimeout(r, 1000));
expect(await client.getWindowCount()).toBe(1);
});
It works but I find it extremely anti-pattern. I wonder if there are any methods to do something like
it('doing its job', async () => {
// awaits and expects that aren't related
await app.client.click('button[data-role="close"]');
await app.client.waitUntilWindowCloses(windowIndex);
expect(await client.getWindowCount()).toBe(1);
});
Any help is appreciated.

await app.client.waitUntil(async () => (await app.client.getWindowCount()) === 1);

Related

Jasmine Spy called incorrectly

I'm running into an interesting problem. I have set up a jasmine spy on an event listener that I attach and detach during the lifecycle of the component (this is in lit element by the way). On the connected callback I attach it like this:
getPositionEvent = this.getPosition.bind(this);
connectedCallback() {
super.connectedCallback();
window.addEventListener('resize', this.getPositionEvent, true);
}
I later detach it like this:
disconnectedCallback() {
window.removeEventListener('resize', this.getPositionEvent, true);
}
I can see during testing of the code that the attachment works here:
let getPositionEventSpy: jasmine.Spy;
beforeAll(() => {
(code setting up component)
getPositionEventSpy = spyOn(component, 'getPositionEvent');
}
beforeEach(async () => {
component.connectedCallback();
await component.updateComplete;
getPositionEventSpy.calls.reset();
})
it('should include an event listener for "resize"', async () => {
window.dispatchEvent(new Event('resize'));
await component.updateComplete;
expect(getPositionEventSpy.calls.count()).toEqual(1);
})
The problem comes when I try to test that the event listener is detached. I put this test in a separate describe block where I initiate a the disconnectedCallback function which should remove the event listener and then test that the spy has not been called when I dispatch the event:
describe('disconnection', () => {
beforeAll(() => {
component.disconnectedCallback();
getPositionEventSpy.calls.reset();
})
it('should remove the "resize" event listener', async () => {
expect(getPositionEventSpy.calls.count()).toEqual(0);
window.dispatchEvent(new Event('resize'));
await component.updateComplete;
expect(getPositionEventSpy).not.toHaveBeenCalled();
})
})
In this case the test fails meaning the getPositionEventSpy has been called. In trying to understand what's happening I added a console.log("getting position") statement in the getPosition() function. When I run the test for removing the event listener the console log statement doesn't get run, so I believe that the removal of the eventlistener is actually successful. So why does the spy count increase? Does anybody know?
try spy.resetHistory()
beforeAll(() => {
component.disconnectedCallback();
getPositionEventSpy.resetHistory();
})

timer still in the queue despite flush in fakeAsync

I have this test that will result in the infamous "1 timer(s) still in the queue" error:
import {
discardPeriodicTasks,
fakeAsync,
flush,
flushMicrotasks,
tick
} from "#angular/core/testing";
describe("Sleep", () => {
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
it("should sleep async", async () => {
let slept = false;
await sleep(0).then(() => (slept = true));
expect(slept).toBeTruthy();
});
it("should sleep fakeAsync", fakeAsync(async () => {
let slept = false;
await sleep(0).then(() => (slept = true));
flush();
flushMicrotasks();
discardPeriodicTasks();
tick(1000);
expect(slept).toBeTruthy();
}));
});
No amount of flushing or ticking including the hints from this answer will get rid of the timer. What else can I do? The variant without fakeAsync() works fine.
Stackblitz: https://stackblitz.com/edit/test-jasmine-karma-fakeasync-timer?file=app/test.ts
For whatever reason, it works if you convert the sleep(0) Promise into an Observable instead.
it("should sleep fakeAsync", fakeAsync(async () => {
let slept = false;
//await sleep(0).then(() => (slept = true));
from(sleep(0)).subscribe(() => (slept = true));
expect(slept).toBeFalsy();
tick(0);
expect(slept).toBeTruthy();
}));
I ran into a similar problem with debounceTime from Rxjs where no amount of flush(), flushMicroTasks() or discardPeriodicTasks() would release the debounce. However in my case I was able to resolve my problem by making a call to tick() with a sufficiently large time value after my expectation had finished to allow the debounceTime to complete.
I was able to solve the problem in stackblitz removing the await in fakeAsync, because the point of fakeAsync is run synchronously.
So, the modified working test is:
it("should sleep fakeAsync", fakeAsync(() => {
let slept = false;
sleep(100).then(() => (slept = true));
flush();
expect(slept).toBeTruthy();
}));
You just need to use flush to process your timeout time synchronously and the test will pass as expected. Another answer to support my point about fakeAsync and async: Angular testing: using fakeAsync with async/await.
I was still getting this error in my test for nested timeout, a timeout inside a service that is inside a request subscribe. This solution doesn't solve my problem. So I dropped the fakeAsync approach and use the one suggested here: Test a function that contains a setTimeout() and finally I solved my problem.

Detox: "Cannot find UI element." when trying to scroll FlatList

This is the code for the tests:
//navigates to the new screen:
it("should show myFlatListScreen after tap", async () => {
await element(by.id("navigationButton")).tap();
await waitFor(element(by.id("myFlatListScreen"))).toBeVisible();
});
//Passes without issue:
it("FlatList should be visible", async () => {
await waitFor(element(by.id("myFlatList"))).toBeVisible();
});
//Fails with: "Cannot find UI element." error
it("FlatList should scroll", async () => {
await element(by.id('myFlatList')).scroll(100, 'down');
});
How is it that the element can pass the toBeVisible() test and then not exist for scrolling?
EDIT: I figured it out. there is some code before these that looks like this:
beforeEach(async () => {
await device.reloadReactNative();
});
The app is reloading from the start each time which is why that element is no longer available. It looks like I have to write all my tests so they run start to finish for each.
There is some code before these that looks like this:
beforeEach(async () => {
await device.reloadReactNative();
});
The app is reloading from the start each time which is why that element is no longer available. It looks like I have to write all my tests so they run start to finish for each.

Protractor.explore() and .pause() and .enterRepl() run into a TypeError

When I run a protractor test with one of those:
await browser.enterRepl();
await browser.pause();
await browser.debugger();
It runs into the following error:
TypeError: doneDeferred.fulfill is not a function
TypeError: doneDeferred.fulfill is not a function
at Socket.tester.once (C:\TSO-IP\tso-ip-ui\node_modules\protractor\built\debugger.js:212:34)
at Object.onceWrapper (events.js:273:13)
at Socket.emit (events.js:182:13)
at Socket.EventEmitter.emit (domain.js:442:20)
at TCP._handle.close (net.js:606:12)
I haven't really found anyone reporting an issue like this.
import 'jasmine';
import {browser} from 'protractor';
import {LoginPo} from '../login.po';
import {Process_adminPo} from "./process_admin.po";
describe ('Capacity register tests', () => {
let loginPage: LoginPo;
let process_adminPo: Process_adminPo;
beforeAll (async (done:DoneFn) => {
loginPage = new LoginPo ();
await loginPage.openApplication ();
await loginPage.changeLanguage ();
done();
});
beforeEach (async (done:DoneFn) => {
process_adminPo = new Process_adminPo ();
done();
});
it ('Add a new nomination group', async () => {
await process_adminPo.open ();
await browser.explore();
//await browser.enterRepl();
//await browser.pause();
//await browser.debugger();
await browser.sleep (5000);
await process_adminPo.clickOnNewButton ();
}
I expect to enter into an interactive shell (REPL) mode to deal better with locating the elements
Please try to not use the browser.repl(), browser.explore(), and browser.debugger(). These methods require the control flow; however, since you are using async / await (which is awesome), that means you are not using the control flow and these methods will not work.
These are all going away because the selenium-webdriver is no longer using the control flow. Protractor used to intercept the control flow and set a magical breakpoint.
You should alternatively use node --inspect-brk ./node_modules/.bin/protractor See youtu.be/6aPfHrSl0Qk?t=985 for an example video.

How to nest Vue.nextTick when testing?

I'm trying to test what happens in my app on two consecutive ticks. This is what I have so far (the tests fail in the karma devtools, but fail in the command line):
import { mount } from 'avoriaz';
import MyComponent from './MyComponent';
describe('testing', function() {
it('should do something', (done) => {
const wrapper = mount(MyComponent, { store });
wrapper.vm.changeData();
Vue.nextTick(() => {
expect(wrapper.vm.something).to.eql(somethingElse);
wrapper.vm.changeData();
Vue.nextTick(() => {
expect(wrapper.vm.something2).to.eql(somethingElse2);
done();
});
done();
});
});
});
I also tried using then() and catch(), but karma still thinks my failing tests are passing.
Should I only have one done() call? I'm not really sure what this callback is doing.
As indicated here, there is an even better solution, which prevents Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.:
it('should do something', (done) => {
const wrapper = mount(MyComponent, { store });
wrapper.vm.changeData();
Vue.nextTick(() => {
expect(wrapper.vm.something).to.eql(somethingElse);
wrapper.vm.changeData();
Vue.nextTick().then(() => {
expect(wrapper.vm.something2).to.eql(somethingElse2);
}).then(done, done);
});
});
I'd like to also use the async/await version, but I wasn't able to get it to work.