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

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.

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();
})

How to share response data across multiple suites?

Let's say we have the following suite:
describe('Devices', () => {
describe('Master Data Set-Up', () => {
it('should create the device if necessary', () => {
cy.createDevice()
its('body.id')
.as('deviceId');
});
});
describe('Test Suite 1', () => {
it('should allow to send data to device', () => {
cy.get('#deviceId').then((deviceId) => {
cy.sendData(deviceId, 'Some Data');
});
});
});
});
So, we have a set up suite that creates master data. This is a simplified version, actually it contains a couple of it specs and I'd like to keep it like that because it's better to read in the Cypress output.
Then, there is the actual test suite that want's to use data that has previously been created. In this case a server generated id that should be used for another REST call.
This is assuming, that cy.createDevice and cy.sendData are custom commands available that internally use cy.request.
When running that, cy.get('#deviceId') fails because aliases are not shared across describe blocks AFAIK. I tried to use let deviceId but it's undefined as it is not yet available when the test specs are processed.
What is a proper way to do this?
I believe this will be better solution, as cypress is asynchronous so it's better to write it on file and read it
describe('Devices', () => {
describe('Master Data Set-Up', () => {
it('should create the device if necessary', () => {
cy.createDevice()
......
cy.writeFile('deviceId.txt', body.id)
});
});
describe('Test Suite 1', () => {
it('should allow to send data to device', () => {
cy.readFile('deviceId.txt').then((device_id) => {
cy.sendData(device_id, 'Some Data');
})
});
});
});
Upvote for #ArekKhatry's idea, but to be safe I would obtain the the id in a before(). If you ever run tests in parallel, grabbing data from one test to use in another would be flaky.
Note that running cy.createDevice().its('body.id') in the before() still gives you the same test coverage as running inside it(), i.e it tests that the request succeeds and the return value has an id.
The file should be written to cypress/fixtures, otherwise it will write to the project root causing untidy pollution of the file structure.
Also, the id is returned from cy.request() as a number, but must be stringifyed in order to write to a text file.
Here's my variant
describe('Devices', () => {
before(() => {
cy.createDevice()
.its('body.id')
.then(id => {
cy.writeFile('cypress/fixtures/deviceId.txt', id.toString());
cy.log(`Created device: ${id}`);
});
});
describe('Test Suite 1', () => {
it('should allow to send data to device', () => {
cy.fixture('deviceId') // can use simpler cy.fixture here
.then(device_id => { // returned as a string here
const id = parseInt(device_id); // may need to parse to number?
cy.sendData(id, 'Some Data');
})
});
});
});
Ok, so first thanks to Aloysius and Arek for their answers. But I had the gut feeling that there must be some easier way to do this that writing an Id to a file.
As I mentioned before, I had issues with my first attempt to use a global variable:
I tried to use let deviceId but it's undefined as it is not yet
available when the test specs are processed.
I really wanted to understand, why this did not work and did some console debugging.
I added a console log:
describe('Devices', () => {
console.log('Loading test suites...')
(...)
});
When running the tests, I saw the log output twice, once after the first describe block where the device id was stored and then a second time after the master data was written.
Actually, I found out that this issue was cause by the following known Cypress issue:
https://github.com/cypress-io/cypress/issues/2777
After setting the baseUrl, it actually works:
describe('Devices', () => {
let deviceId;
before( () => {
Cypress.config('baseUrl', Cypress.env('system_url'))
cy.visit('/');
})
describe('Master Data Set-Up', () => {
it('should create the device if necessary', () => {
cy.createDevice()
.its('body.id')
.then((id) => {
deviceId = id;
});
});
});
describe('Test Suite 1', () => {
it('should allow to send data to device', () => {
cy.sendData(deviceId, 'Some Data');
});
});
});

Does detox tests have access to code in my react-native app?

So inside my firstTest.e2e.js file I have stuff like:
describe('All tests', () => {
beforeEach(async () => {
//can add code here to do something before each it test
});
it('Take screenshot', async () => {
await device.takeScreenshot('home');
});
});
My question is if from this file do I have access to any of code in my actual app or is it working outside of my app? Can I call a helper class inside my app/src folder?

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

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);

Overriding Cypress' before and after methods

We are using Cypress.io to build our automation suite. We have a requirement to seed our database before every test and to clear the data afterward. This could be done like below.
describe('Our test suite', function() {
before(function () {
//loadDbSeed is a custom command that will load the seed file based on the spec file
seed = cy.loadDbSeed()
cy.task('seed:db', seed)
})
it('should be true', function() {
//Some test with some action followed by an assertion
cy.visit('/some-page')
cy.get('[data-cy="identifier"]')
.click()
expect(true).to.equal(true)
})
after(function () {
// clearDb is a custom command that will clear out the DB.
// We are still debating if we must clear the DB after the tests.
// But we might still need to do some common actions for the entire suite after
cy.clearDb()
})
})
The problem we see is that the same before and after operations will be required for all our test suites. So we would like to override these methods so that our tests are something like this.
describe('Our test suite', function() {
before(function () {
// DB seeding is done automatically
// Write only custom before steps required for this test
})
it('should be true', function() {
//Some test with some action followed by an assertion
cy.visit('/some-page')
cy.get('[data-cy="identifier"]')
.click()
expect(true).to.equal(true)
})
after(function () {
// DB clearing is done automatically
// Write only custom after steps required for this test
})
})
How do we achieve this? I have been digging around in the Cypress code and haven't found anything obvious.
If you look at the Cypress docs, using after isn't recommended - Cypress Docs. I'd caution against setting data globally, will you really need it for every test? If you need to enter data on a per-test basis at some point, will that conflict with this global data? You could do something like this on a per test basis:
describe('Our test suite', function() {
beforeEach(function () {
cy.then(async () => {
await MyDatabaseService.resetdata()
await MyDatabaseService.createSomeData()
});
});
it('should be true', function() {
//Some test with some action followed by an assertion
})
})
I've also had to nest some tests as follows when specific tests needed specific data (sorry if some of the formatting here is a bit out, hopefully it'll make sense!):
describe('Our test suite', function() {
beforeEach(function () {
cy.then(async () => {
await MyDatabaseService.resetdata()
await MyDatabaseService.createSomeData()
});
});
it('should be true', function() {
//Some test with some action followed by an assertion
});
describe('These tests need more data, this is a nested describe', function () {
before(function () {
cy.then(async () => {
await MyDatabaseService.addSomeMoreData()
});
it('this test uses the extra data', function () {
// Do some funky tests here
});
});
});
})
For the second tests above, the test will run all three database actions.
Upshot is, if you clear data before you run your tests then it makes things much clearer.
I hope that helps. I'm new to Cypress myself and it can be hard to drop that (bad?) habits we've used for some time in Selenium!