Add test description in report - testing

Is there a way to add custom text in testcafe reports?
I would like to add a short description in the reports, so when somebody else checks it to be able to understand what the test does (especially when the test is passed, only the test name appears in the report and I cannot write too much text in the name of the test,it makes no sense). The problem is that I have different functions checking some menus and only 1 test scenario. I would like to add a text to know which functions were called.
console.log('custom text') will write the text only in the console
this.write('custom text') used inside a async function gives an error.
class goThroughAllMenus{
constructor(){
}
async f_CheckHomeMenus() {
//Description: This function is going through all submenus under Home page
// and checks that the pages are 'up and running'.
this.write(`Running test for Home menu`)
await t
//Hjem
.click(StartPage.HomeMenus.menuHome)
.expect(StartPage.StartPage.StartSubMenu.exists).ok()
}
}

There is no built-in capability to pass custom data to the reporter. You can ping the already existing issue in the TestCafe repo. However, if you just want to send common information about the whole test block, you can use meta and create a custom reporter to show this information.
//reporter.js
export default function () {
return {
async reportTaskStart (startTime, userAgents, testCount) {
},
async reportFixtureStart (name, path, meta) {
},
async reportTestStart (name, meta) {
},
async reportTestDone (name, testRunInfo, meta) {
this.write(meta );
},
async reportTaskDone (endTime, passed, warnings, result) {
}
};
}
Also, you can create a fork of any existing reporter and enhance one.

Related

how to make cypress wait for a async search with multiple result complete without causing the test to fail

I have a typical search where the user types some text in an input, async work is done and a table with the results is properly updated.
I have tests that must wait for this search step and then assert business rules regarding the results, like if the table records are eligible for edit.
Every time I ran a complete test battery (something like 80 test files), one or two of the tests involving that search inevitably fail. But if immediately after that, I run the same test alone, the test passes. It's excruciating and makes e2e testing in CI/CD pointless for the project.
I've read the Cypress documentation about flaky tests and searched questions in StackOverflow and GitHub with only complete failure. It's a drama.
Here is one of the tests:
import { searchList } from '../helpers';
import { createFluxoIniciado, randomFluxoNome } from './common';
import { fluxoSelectors } from './selectors';
describe('fluxos finish', () => {
it('can manually finish a fluxo INICIADO', () => {
// feed data to be searched
const fluxoNome = randomFluxoNome();
createFluxoIniciado({ fluxoNome });
// search
searchList(fluxoNome);
// do something with search results
fluxoSelectors.fluxos.view().click();
fluxoSelectors.finish().click();
fluxoSelectors.confirm().click();
// serach again
searchList(fluxoNome);
cy.contains('FINALIZADO');
});
});
The code in searchList is where trouble emerge sometimes. It uses the callback strategy recommended here. The code attempts to cause retries if not all rows have the searched text.
export function searchList (text) {
cy.get('#searchText')
.scrollIntoView()
.type(text)
.blur();
cy.get('tbody tr').should($trs => {
$trs.each((i, $tr) => {
expect($tr).to.contain(text);
});
}, { timeout: 15000 });
}
Here is an example of a test failure inside a run all test execution:
The problem is obviously caused by the async fetch between .blur() and testing the rows.
You are correctly trying to use Cypress retry with .should(callback), but if the callback is complex or there are multiple steps it may not retry the element that is changing (the table rows).
Ideally you want to keep the cy.get(...).should(...) as simple as possible, and start by testing that the table loading has completed.
// wait for expected number of rows
cy.get('tbody tr', {timeout: 15000}).should('have.length', 5)
cy.get('tbody tr').each($tr => {
expect($tr).to.contain(text);
})
But you have a randomizer there, so maybe it's not possible to test explicitly the row count.
Another approach, test the whole table for text (.contains() checks child text also)
// wait for text to appear somewhere
cy.get('tbody tr', {timeout: 15000}).should('contain', text)
cy.get('tbody tr').each($tr => {
expect($tr).to.contain(text);
})
You can also add an intercept between start and end of api call
export function searchList (text) {
cy.intercept('search/api/endpoint').as('search')
cy.get('#searchText')
.scrollIntoView()
.type(text)
.blur();
cy.wait('#search') // wait for api response
cy.get('tbody tr', {timeout: 15000}).should('contain', text)
cy.get('tbody tr').each($tr => {
expect($tr).to.contain(text);
})
}
I just noticed you have the {timeout} option on .should(), but that's the wrong place,
see Timeouts
cy.get('input', { timeout: 10000 }).should('have.value', '10')
// timeout here will be passed down to the '.should()'
// and it will retry for up to 10 secs
This may be successful
cy.get('tbody tr', { timeout: 15000 })
.should($trs => {
$trs.each((i, $tr) => {
expect($tr).to.contain(text);
});
})

How to write Testcafe selector('Withoutvalue').withText('Valid Text')?

I am facing a scenario where the element tag name and attribute is changing from env to env, but the text content alone is unique.
Therefore I am not able to define any value for Selector('could not define anything here').
How could I write a path to locate the element ?
I don't see a direct solution for this, but a workaround that could solve your issue. You could have an object that holds environments specific data for you and which helps you for specific cases as the one that you seem to be confronted with. In this object, you could also store environment specific Selectors. This could, written in TypeScript, look as follows:
import { Selector } from "testcafe";
interface EnvironmentData {
envName: string;
myVariableSelector: Selector;
}
// Set up a list that contains environment specific data objects
const CONFIGS: EnvironmentData[] = [
{
envName: "MyEnv1",
myVariableSelector: Selector("my css selector 1").withText("my text 1")
},
{
envName: "MyEnv2",
myVariableSelector: Selector("my css selector 2").withText("my text 2")
},
{
envName: "MyEnv3",
myVariableSelector: Selector("my css selector 3").withText("my text 3")
}
]
// Assuming that you're CI for instance sets a environment variable ENVIRONMENT_NAME to
// any of the specific environments MyEnv1, MyEnv2 or MyEnv3
function getConfigForEnvironment(envDataSets: EnvironmentData[]): EnvironmentData {
const envData = envDataSets.find((c) => c.envName === process.env.ENVIRONMENT_NAME);
if (envData === undefined) {
console.error(`No suitable data for environment '${process.env.ENVIRONMENT_NAME}' found!`);
process.exit(1);
}
return envData;
}
// Determine the right object before the tests start
const envData = getConfigForEnvironment(CONFIGS);
fixture`My awesome tests`.page("myTestUrl");
test("My test", async (t) => {
// Make use of the object that holds the data for the desired environment
await t.expect(envData.myVariableSelector.exists).ok("Should be fine for any environment!");
});
I did it in a simpler way using the or operator (,)
i,e I wrote the selector with Selector('div,span').withText('Value')
The attribute div and span are changing with environment so I used , to pass both and it worked.

How to use ".contains" assertion to match one of the values

As per my application, clicking on one of the links can open one of the URLs from two URLs.
Eg: Clicking on Link - X, it can open one of the below URLs :
http://example.com/value1 or http://example.com/value2
I have to write an .contains assertions for this which can look something like this:
expect(currentUrl).contains(value1 or value2)
As per the TestCafe documentation, contains does not have support for a regular expression and I do not want to use Match as I have to pass incomplete URL there.
Please let me know how this can be done.
Thanks.
I have solved it using match assertion as below but still it would be good if this can be somehow done with contain assertion as well.
expect(currentUrl).match(/value1|value2$/)
Check the following "current location" example test:
import { ClientFunction } from 'testcafe';
fixture `Fixture`
.page `https://google.com`;
test('Check location', async t => {
// Some actions and assertions...
await t
.navigateTo(/*...*/)
.click(/*...*/);
// Then check our location
const getLocation = ClientFunction(() => document.location.href);
const location = await getLocation();
await t
.expect(location.includes('microsoft') || location.includes('google')).ok();
});

Vuex: Best Way To Handle actions

I am developing a CRUD application to show user-list. As an admin, the user list is shown with edit & delete icons. Once the user is deleted by dispatching deleteUser action, the user-list should be re-fetched to reflect updated list of users.
actions: {
fetchUsers(context) {
axios.get("/get/user").then(function(response) {
context.commit("SET_USERS", response.data);
});
},
deleteUser(context,payload) {
axios.delete("/delete/user",).then(function(response) {
if(response.status == 200) {
//Refresh the user list.
//HOW TO call "fetchUsers" from here.?
}
})
}
}
So I need to call fetchUsers action after delete user successful.
What is the best way to achieve this? Copying the code would help but it's against DRY principle. Delete is an example but there could be several actions like edit which would need to call fetchUsers again.
Kindly provide inputs.
Regards
Robin.
You seem to be missing a critical feature of Vuex which is actions. If you need to perform an async operation regarding a mutation, the async operations should be performed from an action. Within the action, you'll be able to perform other actions, as this answer shows.
You can use the dispatch() of the context object like this
deleteUser(context,payload) {
axios.delete("/delete/user",).then(function(response) {
if(response.status == 200) {
// payload is optional and used if you want to send specific datas to your fetchUsers method
context.dispatch('fetchUsers', payload)
}
})
}
cf: https://vuex.vuejs.org/api/#commit and https://vuex.vuejs.org/api/#actions

WinJS Listview shows undefined when navigating quickly

I have a WinJS application with listviews in which if quickly navigate between pages before the listview is fully loaded, the next page shows the listview with all elements in it bound as "undefined".
So say I have a hub page with a "to do" that is filtered to only show 6 items, and there is a header that navigates to the full "to do" page, when the hub page is displayed but before it is fully loaded I click on the header link to the "to do" page, the app then goes to the "to do" page, but the items show up with all the properties in the tile as "undefined".
I am using IndexedDB as my data store.
My home page code looks like this:
WinJS.UI.Pages.define("/pages/home/home.html", {
ready: function (element, options) {
WinJS.Utilities.query("a").listen("click", function (e) {
e.preventDefault();
WinJS.Navigation.navigate(e.currentTarget.href);
}, false);
viewModel = new HomeViewModel(element);
viewModel.load(); //loads from indexed db
},
//etc...
To Do Page:
WinJS.UI.Pages.define("/pages/ToDo/ToDo.html", {
ready: function (element, options) {
viewModel = new ToDoViewModel(element);
viewModel.load();
},
etc//
I know there isn't much to go off, but any ideas would be appreciated.
Also tips on how to debug something like this would be great.
Update
I narrowed it down to this one line from the Hub Page:
myLib.GetData(todaysDate, function (result) {
that.trendsModel.today = result;
WinJS.Binding.processAll(that.el.querySelector("#dataPanel"), that.trendsModel); //<--Right Here
});
If I remove that, then when I load the second page the data doesn't show as undefined. What is interesting is the data initially shows correctly on the second page and then it changes to "undefined".
Solution
My fix:
myLib.GetData(todaysDate, function (result) {
var element = that.el.querySelector("#dataPanel");
that.trendsModel.today = result;
if(element) {
WinJS.Binding.processAll(element, that.trendsModel);
}
});
At the point when when the callback returns, I am already on the second page. So the selector was not found returning null. If you pass null to processAll it tries to bind the whole page which is why I was able to see the correct data for a second then it changes to undefined...Wow, what a doozy. I guess it makes sense but what a pain to find.
Hope it helps someone in the future :)
Your ToDoViewModel, and HomeViewModel need to be observable. This means they need to mix in from WinJS.Binding.mixin, and for the properties that you pull in asynchronously, they need to call this.notify("propertyName", newVal, oldVal) from the property setter.
Note that you need to have getter/setter properties. e.g.
var bindingBase = WinJS.Class.mix(function() {}, WinJS.Binding.mixin);
WinJS.Namespace.define("YourNamespace", {
ToDoViewModel: WinJS.Class.derive(bindingBase, function constructor() {
}, {
_titleStorage: "",
title: {
get: function() { return this._titleStorage; },
set: function(newValue) {
if(newValue === this._titleStorage) {
return;
}
var old = this._titleStorage;
this._titleStorage = newValue;
this.notify("title", newValue, old);
}
}
}),
});
myLib.GetData(todaysDate, function (result) {
var element = that.el.querySelector("#dataPanel");
that.trendsModel.today = result;
if(element) {
WinJS.Binding.processAll(element, that.trendsModel);
}
});
At the point when when the callback returns, I am already on the second page. So the selector was not found returning null. If you pass null to processAll it tries to bind the whole page which is why I was able to see the correct data for a second then it change to undefined...Wow, what doozy. I guess it makes sense but what a pain to find.