Are Loops Possible in Detox testing - detox

Is it possible to loop through items in Detox? For example if you have many testid’s of the same type that simply increment by 1 in name, can it be looped in way that wouldn’t require the statements to be fully typed out each time?

Yes, its possible. Here you can see the code of a test that:
loads first screen
loops through a small list of items
taps nth item
navigates to second screen
grabs a screenshot
Hope it helps
const { takeScreenshot } = require ('./helpers')
describe('Example', () => {
let artistList, artistListItem
let tagsList
beforeEach(async () => {
await device.reloadReactNative();
})
afterEach(async () => {
takeScreenshot() //https://medium.com/async-la/e2e-testing-react-native-with-detox-screenshots-595146073863
})
var fnTest=(i)=>{
it('should navigate to '+i+'nth artist', async () => {
await expect(element(by.id('artistList'))).toBeVisible()
await element(by.id('artistListItem'+i)).tap()
})
}
for (var i=0; i<3; i++){
fnTest(i)
}
})

Related

Cypress spy on multiple calls of the same method

I'm trying to check that a method was not called again after a certain action.
My test:
it('if query is less than 3 symbols, api call is not made', () => {
cy.spy(foo, 'bar').as('bar');
cy.get('input').type('12').then(() => {
cy.get('#bar').its('callCount').then(res => {
expect(res).to.eq(1); // a basic check after mounted hook
});
});
});
My component:
async mounted(): Promise<void> {
await this.foo.bar();
}
async getSearchResults(): Promise<void> {
if (this.searchQuery.length < 3) {
return;
}
await this.foo.bar();
}
The problem is that bar was already called on mount, and it could have been called multiple times before, if query length was valid. I was thinking about saving bar's callCount to a variable and checking it after call, but that looks ugly. Kinda stuck here, any ideas are welcome.
It's not an issue. The call count is started at the point you set up the spy, not when the component is mounted.
Try this:
const foo = {
bar: () => console.log('bar called')
}
it('starts with a clean callcount', () => {
foo.bar() // make a call
cy.spy(foo, 'bar').as('bar'); // callCount === 0 on setup
cy.get('#bar')
.its('callCount')
.should('eq', 0) // passes
});
Even if you have some callcount from another test, you can always reset it before the current test:
it('allows reset of spy callCount', () => {
cy.spy(foo, 'bar').as('bar'); // callCount === 0 on setup
foo.bar() // make a call, count is now 1
cy.get('#bar').invoke('resetHistory') // remove prior calls
cy.get('#bar')
.its('callCount')
.should('eq', 0) // passes
});
I believe you can get the initial call count, and then wrap your test in that.
it('if query is less than 3 symbols, api call is not made', () => {
cy.spy(foo, 'bar').as('bar');
cy.get('#bar').its('callCount').then((initRes) => {
cy.get('input').type('12').then(() => {
cy.get('#bar').its('callCount').then(res => {
expect(res).to.eq(initRes); // a basic check after mounted hook
});
});
});
});
You would probably want to do a test that this would fail, to make sure that Cypress is getting '#bar' again.

Using API to fecth the next list and display for users

I am making a react-native app, I am fetching a list of movies from an API, and every time I press next I'm to supposed to get fetch the next list of movies, however, my code doesn't work correctly.
At first, you have to click on the button to fetch the first list like this:
<Button mode="contained" onPress={() => getMovieList()}>
Get Movies
</Button>
const getMovieList= async () => {
setLoading(true);
await fetchMovies(url)
.then(async (data) => {
setData(data);
// more code
})
.catch((error) => {
console.log(error);
});
};
The URL is:
const url = `https://api.themoviedb.org/4/list/${listID}?page=1&api_key=${api_key}`;
I have written a function that I can use to fetch the list using the URL above,
const [listID, setListID] = useState(1);
After I fetch the first list I show them in a child component, like this:
<MyCompanyCard
name={data.companyName}
desc={data.desc}
loadNextCompany={loadNextCompany}
loadPrevCompany={loadPrevCompany}
setListID={setListID}
listID={listID}
/>
And also:
const loadNextCompany = async () => {
setListID(listID + 1);
await getMovieCompany();
};
const loadPrevCompany = async () => {
setListID(listID - 1);
await getMovieCompany();
};
In my child component, I call the getNextOne function and the problem is, although the URL changes but the content doesn't change and I have to press next, then I can see the next list and so on, the same applies for the getPrevOne. The problem is that every time I press next/prev I make an API call but I am not sure how to set the content to change accordingly.
=================
I was able to solve it by adding a useeffet like this:
useEffect(async () => {
await getMovieCompany();
}, [listID]);
so now every time I add to listID then I fetch the url again and immdedialtly represnt the current items.
try this
const getMovieList = useCallback(() => {
const url = `https://api.themoviedb.org/4/list/${listID}?page=1&api_key=${api_key}`;
setLoading(true);
await fetchMovies(url)
.then(async (data) => {
setData(data);
// more code
})
.catch((error) => {
console.log(error);
});
}, [listID]);
I was able to solve it by adding a useeffet like this:
useEffect(async () => {
await getMovieCompany();
}, [listID]);
so now every time I add to listID then I fetch the url again and immdedialtly represnt the current items.

How to get state in Nuxt js with composition api?

setup(){
const columns = computed(()=>store.state['subCategory'].subCategoryColumnsData[subCategoryName.value]);
const { fetch } = useFetch(async () => {
await store.dispatch('subCategory/getColumnsQuery', {
categories: subCategoryId.value,
page: 1,
subCategoryName: subCategoryName.value,
})
});
fetch();
}
I want to switch between pages in my project. Whenever I switched another page, I send request to get data with latest updates. This code works well for the first time when page was loaded, but it doesn't work when I switched from one page to another page. But if I check store state, I can see it in store. If I visit same page second time , I can see data this time.
But if I change my code like this, it works well. I did not get why it does not work true in the first sample
setup(){
const columns = ref([])
const { fetch } = useFetch(async () => {
await store.dispatch('subCategory/getColumnsQuery', {
categories: subCategoryId.value,
page: 1,
subCategoryName: subCategoryName.value,
})
}).then(() => (columns.value = store.state['subCategory'].subCategoryColumnsData[subCategoryName.value]));
fetch();
}
Can you test it? sample:
const state = reactive({ columns: computed(() => yourstore })
// do not need to call fetch because this hook is a function
useFetch(async () => { await store.dispatch(url) })
return {
...toRefs(state),
}

cypress not executing "then" portion

I am trying to write a custom cypress command but the code in my then portion is not executing. any help will be appreciated thanks
my command looks similar to this:
Cypress.Commands.add("ex", () => {
const links=[]
cy.get("*[class='link post']").each((link)=>{
links.push(link.href)
}).then(() => {
var i=0;
while (links[i]) {
cy.visit(link)
i++
}
})
})
There are a few things going on here we should step through.
In your each() block, link.href will return an undefined value, so when you get to your then method, you have no links in your array to visit. Instead of links.push(link.href), try links.push(links.attr('href') to grab the value of your href attribute.
In your then method, your while loop isn't the most efficient way of looping through your array (and it will most likely error out for you with an undefined value). You should instead use a .forEach(), like so:
links.forEach((link)=>{
cy.visit(link)
)
If you do not need to persist the links array, then your entire command can be majorly simplified:
Cypress.Commands.add("ex", () => {
cy.get("*[class='link post']")
.then((links) => {
links.each((link)=>{
cy.visit(link.attr('href'))
})
})
});
To add to Kerry's answer,
The parameter given to a .then() callback is a jQuery object, containing one or more elements found by cy.get(...)
To iterate over the elements, you need to de-structure the jQuery object with the spread operator,
Cypress.Commands.add("visitLinks", () => {
cy.get("*[class='link post']")
.then($links => { // $links is a jQuery wrapper
[...$links].forEach(link => { // link is a raw element
const url = link.getAttribute('href') // apply DOM method
cy.visit(url)
})
})
});
or if you want to use the Cypress iterator .each() instead of .then(),
Cypress.Commands.add("visitLinks", () => {
cy.get("*[class='link post']")
.each($link => { // jQuery wrapped element
const href = $link.attr('href') // apply jQuery method
cy.visit(href)
})
});
However
It's going to break.
cy.visit() navigates the page, which changes the DOM in the page, so on the 2nd iteration of .each(), Cypress sees things have changed and crashes (probably a "detached element" error).
You should separate the query (grabbing the links) from the action (visiting them).
Cypress.Commands.add("getLinks", () => {
const found = [];
cy.get("*[class='link post']")
.each($link => { // jQuery wrapped element
const href = $link.attr('href') // apply jQuery method
found.push(href)
})
.then(() => found) // wait until iteration finishes
// then return the array of links
});
Use it like this
cy.getLinks()
.then(links => {
links.forEach(link => cy.visit(link))
})

Click() is not working though there's no any error but element not opening up - (Cypress automation)

last one cy.get('[data-cy=impact-area-table]').contains(impactareas.name).should('be.visible').click({force: true}); is not working though there's no any error ,it shows that it's fine and test pass but it doesnot open up the impact area ??
import { fillImpactAreaForm } from './utils';
import {contact, campaign, impactArea,impactareas} from '../support/commands.js';
describe('Fundraising test suite', function () {
beforeEach(() => {
cy.resetDb();
cy.visit('/');
});
it('should allow the user to create transactions', () => {
cy.seedOrgAndLogin().then(() => {
return cy.factoryCreate('PersonContacts', contact);
}).then(() => {
cy.factoryCreate('Campaigns', campaign);
}).then(() => {
cy.factoryCreate('ImpactAreas', impactArea);
}).then(() => {
cy.get('[data-cy="sidebar-Impact Areas"]').click({force: true});
cy.reload(true);
cy.get('[data-cy=create-impactarea]').click();
cy.get('[data-cy=impact-area-form]').contains('Close').click();
cy.get('[data-cy=create-impactarea]').click();
fillImpactAreaForm(impactareas);
cy. wait(2000);
cy.get('[data-cy=impact-area-table]').contains(impactareas.name).should('be.visible').click({force: true});
//cy.get('.content-scroll-wrapper.block-content').find('.content-scroll-body').contains(impactArea.name).click({force: true});
});
});
});
It's happening in 2 situations:
you don't have that item in your page or the dictation is different. (mention that cypress is case sensitive for .containt) or maybe your item is not visible.
you have more than one of this item. for example, you have 2 close in your page. it makes ambition to click on witch one. try to make it clear by adding more detail.