TestCafé data driven tests with login - javascript

I had written some tests using for loops and recently discovered this handy doc page that describes how to write data-driven tests.
https://devexpress.github.io/testcafe/documentation/recipes/create-data-driven-tests.html
I'm now trying to refactor my tests but running into a problem. The purpose of the test is to log in as a bunch of different accounts, and then verify whether certain page elements exist. (I recognize that this is a heavy hammer to use, but our app has a huge number of permissions and the combinations often get forgotten when developing new features, so this seemed like the quickest way to get at the actual truth of what is on the screen for a real user).
My old tests look something like this:
test('Account manager', async (t) => {
const existingItems = [
[mediaSidePanel.customize, 'Customize'],
[mediaSidePanel.stats, 'Stats'],
[mediaSidePanel.download, 'Download'],
[mediaSidePanel.delete, 'Delete'],
];
const notExistingItems = [
[mediaSidePanel.adminTools, 'Admin Tools'],
];
await t
.useRole(advAccountManager)
.navigateTo(`https://${accountKey}.wistia.io/medias/${mediaHashedID}`);
await Promise.all(existingItems.map(async item => await t
.expect(item[0].exists).ok(`${item[1]} should exist for an Account Manager`)));
await Promise.all(notExistingItems.map(async item => await t
.expect(item[0].exists).notOk(`${item[1]} should not exist for an Account Manager`)));
});
The test works fine except for the obvious problems of having loops in tests: I need to have thorough diagnostic messages to ensure I know which element actually is failing, and worse, if something early in the loop fails, the test ends, and I have no way of knowing if there would have been subsequent failures.
I started trying to refactor this by pulling all of the existing/non-existing items into an array defined in a separate file and wrote this:
import * as dataSet from '../helpers/rolePermissions';
fixture `Advanced Account Manager`
.page `https://app.wistia.io/logout`
.beforeEach(async (t) => {
await t
.useRole(advAccountManager);
});
dataSet.advAccountManager.project.forEach(data => {
test.page `https://${accountKey}.wistia.io/projects/${projectHashedID}`(`Project - ${data.name}`, async t => {
if (data.present) {
await t
.expect(await data.selector.exists).ok(`${data.name} should exist for an Account Manager`);
}
else {
await t
.expect(await data.selector.exists).notOk(`${data.name} should not exist for an Account Manager`);
}
});
});
It works perfectly in that it gets rid of the biggest problem and keeps running the tests even if an earlier one fails. It introduces a much bigger problem, however. This is now considerably slower because it has to log in every single time it iterates through the test. I am already using Roles, as you can see, to try to speed things up, but it is still painfully slow comparatively. I don't want to continue down this refactoring path if it ultimately isn't going to pan out.
Is there a way to have the best of both worlds? Ideally, I would like to do the following:
log in once at the beginning of the fixture
stay on the page without reloading
iterate through all the relevant selectors
continue iterating even if an individual test fails

TestCafe reloads a page before every test to avoid indeterministic behavior caused by mutual influence of tests on one another. However, there is an experimental and undocumented feature that disables this mechanism. In your case, you can try using the fixture.disablePageReloads method as follows:
fixture `Advanced Account Manager`
.page `https://app.wistia.io/logout`
.disablePageReloads
.beforeEach(async (t) => {
await t
.useRole(advAccountManager);
});
Please, use it at your own risk.

Another way is to collect selectors of all non-existing elements into array and check its lenght.
For example:
import { Selector } from 'testcafe';
fixture `Log all requests`
.page`https://devexpress.github.io/testcafe/example/`;
test('Test 1', async t => {
const selectors = [
{
name: 'Remote testing',
selector: Selector('label').withText('Support for testing on remote devices')
},
{
name: 'Reuse JS code',
selector: Selector('label').withText('Re-using existing JavaScript code for testing')
},
{
name: 'Background parallel testing',
selector: Selector('label').withText('Running tests in background and/or in parallel')
}
];
const assertions = await Promise.all(selectors.map(async item => ({ name: item.name, exists: await item.selector.exists })));
const nonExistingItems = assertions.filter(item => !item.exists).map(item => item.name);
await t.expect(nonExistingItems.length).eql(0, `This items should exist: ${nonExistingItems.join(', ')}. `);
});

Related

Jest's `it.each` causes a 'Expected done to be called once, but it was called multiple times' error when used with 'getByTestId'

I have a component with some parts that are conditionally rendered based on some config on the window object. This all works, I can confirm this manually.
I have this sort of test setup, but I have simplified it somewhat:
it('renders both by default', () => { // PASS
const Comp = getComponent();
render(<Comp />);
expect(screen.getByTestId('one')).toBeInTheDocument();
expect(screen.getByTestId('two')).toBeInTheDocument();
})
it.each([
{ testId: 'one', otherTestId: 'two'},
{ testId: 'two', otherTestId: 'one' },
])('renders $testId, not $otherTestId', (testId, otherTestId) => { // FAIL
delete window.config[otherTestId]; // this works
const Comp = getComponent();
render(<Comp />);
expect(screen.getByTestId(testId)).toBeInTheDocument();
expect(screen.getByTestId(otherTestId)).not.toBeInTheDocument();
})
But I am getting this error:
Expected done to be called once, but it was called multiples times. Reason 'two'.
Which is not something I've ever seen before. None of my tests that are running here are async. The component isn't async. I'm not using done anywhere near this.
What's happening?
You need to destructure your test variables
Jest is expecting testId to be an object containing all your test variables, and otherTestId to be the function done.
I'm not sure why it thinks it's called multiple times, but making the first argument:
{ testId, otherTestId }
Works around this, and matches the documentation correctly.
Alternatively, follow this answer for replacing jest's for loop with the native one, which is a better approach in many ways.

How to create and call selectors from a separate file in Cypress?

I am new to Cypress and struggling to get it work. I need to create a file with selectors (I suppose in 'support' folder) to use them in my project file.
Here is an example
describe('Test_spec_1', () => {
it.only('Visits the site & verifies elements', () => {
cy.get('[type=text]').should('be.visible')
cy.get('[type=password]').should('be.visible')
cy.get('[type=submit]').should('be.visible')
cy.get('[routerlink="/login"]').should('be.visible')
cy.get('[routerlink="/reset-password"]').should('be.visible')
cy.get('[routerlink="/support"]').should('be.visible')
cy.get('[routerlink="/reset-password"]').should('be.visible')
})
})
Basically, I need to have all selectors in a separate file, so I can call them and update their values easily.
I have experimented a bit with export/import, but it did not work. Could't find anywhere how to use it properly. It would be great if you can give me some hints how to do it. Thank you very much.
You can create a folder called page-objects inside your integration folder. Inside that you can create one js file for each screen like login.js. Now inside that you can write your locators like:
class login {
usernameInput() {
return cy.get('[type=text]')
}
passwordInput() {
return cy.get('[type=password]')
}
submitButton() {
return cy.get('[type=submit]')
}
}
export default login
Inside your tests you can use them as:
import login from '/page-objects/login.js'
const loginPage = new login();
describe('Test_spec_1', function() {
it('Visits the site & verifies elements', function() {
loginPage.usernameInput().should('be.visible')
loginPage.passwordInput().should('be.visible')
loginPage.submitButton().should('be.visible')
})
})
Please don't use page objects in Cypress, see this tutorial Stop using Page Objects
Page objects problems
Page objects are hard to maintain and take away time from actual application development. I have never seen PageObjects documented well enough to actually help one write tests.
Page objects introduce additional state into the tests, which is separate from the application’s internal state. This makes understanding the tests and failures harder.
Page objects try to fit multiple cases into a uniform interface, falling back to conditional logic - a huge anti-pattern in our opinion.
Page objects make tests slow because they force the tests to always go through the application user interface.
Number 3) killed it for me. You get to a point where you try to figure out more convoluted methods in the page object to cater for different scenarios.
The easiest way to store your selector text in one place is given in this question Where to store selectors in Cypress.io
// cypress/support/selectors.js
export default {
mySelector: '.my-selector',
mySelector2: '.my-selector-2'
};
// cypress/integration/one.spec.js
import selectors from '../support/selectors.js';
describe('test', () => {
it('test', () => {
cy.get(selectors.mySelector);
});
});

Gatsby source plugin only showing last item in array in GraphQL

When I console log after I run build 6 objects show up in my drinks array. They also show up when I run develop. But when I query graphQL only the last object in my array is available. I am new to gatsby and graphQL so included image just in case my query was off.
Code is from my gatsby-node.js file:
exports.sourceNodes = async (
{ actions, createContentDigest, createNodeId,}
) => {
const NODE_TYPE = "CocktailRecipes";
try{
const response = await fetch(`https://www.thecocktaildb.com/api/json/v1/1/search.php?s=vodka`)
const data = await response.json();
const {drinks} = data;
console.log(drinks)
drinks.forEach((drink) => {
actions.createNode({
...drink,
id: createNodeId(`${NODE_TYPE }-${drink.id}`),
parent:null,
children:[],
internal:{
type:NODE_TYPE,
content:JSON.stringify(drink),
contentDigest: createContentDigest(drink)
}
})
})
}
catch(error){
console.log("ERROR")
console.log(error)
}
}
only one object showing in graphQL
If anyone could help it would be very much appreciated as I've been banging my head on a wall for awhile now. I've done a gatsby clean. I've tried map instead of forEach
I've faced exactly the same issue as you a few months ago and, in my case was that I needed to set a valid internal id for each element to allow GraphQL to create a proper schema for each node if not, the id is overridden in each element and it only takes the last one.
In your case, it seems that some field is wrong, making the following expression invalid:
id: createNodeId(`${NODE_TYPE }-${drink.id}`),
Try debugging more what's receiving and changing it to some hardcoded value. Something like:
id: drink.id,
Keep in mind that, if the ids are different for each node, you don't need to use createNodeId API for debugging purposes (but it's recommended).

testcafe - how to assert text contains in html body

I am using testcafe for api testing however our api requires login. Below is my code. I can see json response fine. But i am not sure how to assert on the page.
import { Selector } from 'testcafe';
import Page from './page';
// Page model
const page = new Page();
const url = 'https://myexample.com';
const elementWithIdOrClassName = Selector(value => {
return document.getElementById(value) || document.getElementsByTagName(value);
});
fixture `Test`
.page(url + '/talent/career')
.beforeEach( async t => {
await t
.typeText(page.username, 'gvp50')
.typeText(page.password, 'password')
.click(page.login_button)
});
// Tests
test('Text typing basics', async t => {
await t
.navigateTo(url+'/api/learner/learning_items')
.expect(Selector('html')).contains('learning_items');
});
Testcafe just hangs after i run this code. I tried Selector('body') as well but it doesn't work.
You need to specify what element property (state) you'd like to obtain (verify).
After you selected the entire 'html' element (Selector('html')), specify what property (state) you'd like to access (attributes, childNodes, style, size, etc.). See the DOM Node State topic to learn more.
It looks like you wanted to access the text content as follows:
.expect(Selector('html').textContent).contains('learning_items');
However, such selector usage is unlikely to be the cause of the hang as TestCafe will properly display a message about invalid selector usage. You might want to simplify your test and/or debug it to find what causes the hang.
const cellcomparedata =await Selector('[role="gridcell"]').textContent;
console.log("cellcomparedata is",cellcomparedata);
either you can use this.

Testing Complex Asynchronous Redux Actions

So, let's say I have the next action:
export function login({ email, password, redirectTo, doNotRedirect }) {
return ({ dispatch }) => {
const getPromise = async () => {
const basicToken = Base64.encode(`${email}:${password}`);
const authHeaders = { Authorization: `Basic ${basicToken}` };
const { payload, error } = await dispatch(sendAuthentication(authHeaders));
if (error) throw payload;
const { username, token, fromTemporaryPassword } = payload;
const encodedToken = Base64.encode(`${username}:${token}`);
dispatch(persistence.set('authorizationToken', encodedToken));
dispatch(postGlobalId({ username }));
dispatch(setIsLoggedIn(true));
dispatch(setIsFromTemporaryPassword(fromTemporaryPassword));
await dispatch(clientActions.fetchClient);
if (doNotRedirect) return;
if (fromTemporaryPassword)
dispatch(updatePath('/profile/change-password'));
else
dispatch(updatePath(redirectTo || '/dashboard'));
};
return {
type: AUTHENTICATION_LOGIN,
payload: getPromise()
};
};
}
And I want to add tests for it, to add reliability to the code.
So, here are few things:
We send authentication headers and get data as a response
We throw an error if some error is present in the response
We set up all needed tokens, dispatch all needed actions to show that we are logged in now
Fetching client data
Based on params and received data, we redirect to needed route / don't redirect
The question is that it is really too hard to test and we need to stub literally everything, which is bad due to brittle tests, fragility and too much of implementation knowing (not to mention that it is pretty challenging to stub dispatch to work properly).
Therefore, should I test all of these 5 points, or to focus only on the most important stuff, like sending authorization request, throw error and check redirects? I mean, the problem with all flags that they can be changed, so it is not that reliable.
Another solution is just to separate these activities into something like following:
auth
setLoginInfo
handleRedirects
And to pass all needed functions to invoke through dependency injection (here just with params, basically)? With this approach I can spy only invoking of this functions, without going into much details.
I am quite comfortable with unit testing of pure functions and handling different edge-cases for them (without testing too much implementation, just the result), but testing complex functions with side-effects is really hard for me.
If you have very complex actions like that, I think an alternative (better?) approach is to have simple synchronous actions instead (you can even just dispatch payloads directly, and drop action creators if you like, reducing boiler-plate), and handle the asynchronous side using redux-saga: https://github.com/yelouafi/redux-saga
Redux Saga makes it very simple to factor out your business logic code into multiple simple generator functions that can be tested in isolation. They can also be tested without the underlying API methods even being called, due to the 'call' function in that library: http://yelouafi.github.io/redux-saga/docs/api/index.html#callfn-args. Due to the use of generators, your test can 'feed' values to the saga using the standard iterator.next method. Finally, they make it much easier for reducers to have their say, since you can check something from store state (e.g. using a selector) to see what to do next in your saga.
If Redux + Redux Saga had existed before I started on my app (about 100,000 JS(X) LOC so far), I would definitely have used them.

Categories

Resources