Saleor 3391 tests for staff members (#1122)

* invite staff

* staff members

* all tests for staff members

* remove unused import

* update snap

* remove /dashboard/

* uptade test.yml
This commit is contained in:
Karolina Rakoczy 2021-06-07 13:35:53 +02:00 committed by GitHub
parent 839c4f5afe
commit 7df850bcb1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 359 additions and 14 deletions

View file

@ -103,6 +103,7 @@ jobs:
wait-on: http://localhost:9000/
wait-on-timeout: 120
spec: cypress/integration/allEnv/**/*.js
- name: Cypress run
if: ${{ steps.api_uri.outputs.custom_api_uri}} == 'https://qa.staging.saleor.cloud/graphql/'
uses: cypress-io/github-action@v2
@ -114,6 +115,7 @@ jobs:
CYPRESS_SECOND_USER_NAME: ${{ secrets.CYPRESS_SECOND_USER_NAME }}
CYPRESS_USER_PASSWORD: ${{ secrets.CYPRESS_USER_PASSWORD }}
CYPRESS_PERMISSIONS_USERS_PASSWORD: ${{ secrets.CYPRESS_PERMISSIONS_USERS_PASSWORD }}
CYPRESS_MAILHOG: ${{ secrets.CYPRESS_MAILHOG }}
with:
build: npm run build
start: npx local-web-server --spa index.html
@ -124,7 +126,7 @@ jobs:
with:
name: cypress-videos
path: cypress/videos
translation-messages:
runs-on: ubuntu-latest
steps:

View file

@ -0,0 +1,12 @@
export function getPermissionsArray(first) {
const query = `query{
permissionGroups(first:${first}){
edges{
node{
id
}
}
}
}`;
return cy.sendRequestWithQuery(query).its("body.data.permissionGroups.edges");
}

View file

@ -0,0 +1,88 @@
import { urlList } from "../url/urlList";
export function getStaffMembersStartsWith(startsWith) {
const query = `query{
staffUsers(first:100 filter:{
search:"${startsWith}"
}){
edges{
node{
id
email
}
}
}
}`;
return cy.sendRequestWithQuery(query);
}
export function inviteStaffMember({
email,
isActive = true,
firstName = "",
lastName = "",
permissionId
}) {
const mutation = `mutation createStaff{
staffCreate(input:{
firstName: "${firstName}"
lastName: "${lastName}"
email: "${email}",
isActive: ${isActive},
addGroups:"${permissionId}"
redirectUrl: "${Cypress.config().baseUrl}${urlList.newPassword}"
}){
user{
id
}
errors{
field
message
}
}
}
`;
return cy.sendRequestWithQuery(mutation).its("body.data.staffCreate");
}
export function updateStaffMember({ userId, isActive }) {
const mutation = `mutation{
staffUpdate(id:"${userId}", input:{
isActive:${isActive}
}){
user{
id
}
errors{
field
message
}
}
}`;
return cy.sendRequestWithQuery(mutation).its("body.data.staffUpdate");
}
export function deleteStaffMember(staffId) {
const mutation = `mutation{
staffDelete(id:"${staffId}"){
errors{
field
message
}
}
}`;
return cy.sendRequestWithQuery(mutation).its("body.data.staffDelete");
}
export function deleteStaffMembersStartsWith(startsWith) {
getStaffMembersStartsWith(startsWith).then(resp => {
if (resp.body.data.staffUsers) {
const staffMembers = resp.body.data.staffUsers.edges;
staffMembers.forEach(element => {
if (element.node.email.includes(startsWith)) {
deleteStaffMember(element.node.id);
}
});
}
});
}

View file

@ -0,0 +1,4 @@
export const SET_PASSWORD = {
passwordInput: '[data-test="password"]',
confirmPasswordInput: '[data-test="confirm-password"]'
};

View file

@ -0,0 +1,5 @@
export const INVITE_STAFF_MEMBER_FORM = {
firstNameInput: '[name="firstName"]',
lastNameInput: '[name="lastName"]',
emailInput: '[name="email"]'
};

View file

@ -0,0 +1,5 @@
export const STAFF_MEMBER_DETAILS = {
permissionsSelect: '[data-test="permissionGroups"]',
isActiveCheckBox: '[name="isActive"]',
removePermissionButton: '[data-test-id="remove"]'
};

View file

@ -0,0 +1,3 @@
export const STAFF_MEMBERS_LIST = {
inviteStaffMemberButton: '[data-test-id="inviteStaffMember"]'
};

View file

@ -1,27 +1,19 @@
import { TEST_ADMIN_USER, USER_WITHOUT_NAME } from "../../../Data/users";
import { HOMEPAGE_SELECTORS } from "../../../elements/homePage/homePage-selectors";
import { expectWelcomeMessageIncludes } from "../../../steps/homePageSteps";
import { urlList } from "../../../url/urlList";
describe("Displaying welcome message on home page", () => {
it("should display user name on home page", () => {
cy.loginUserViaRequest();
cy.visit(urlList.homePage);
cy.get(HOMEPAGE_SELECTORS.welcomeMessage)
.invoke("text")
.then(text => {
expect(text).to.contains(
`${TEST_ADMIN_USER.name} ${TEST_ADMIN_USER.lastName}`
);
});
expectWelcomeMessageIncludes(
`${TEST_ADMIN_USER.name} ${TEST_ADMIN_USER.lastName}`
);
});
it("should display user email on home page", () => {
cy.loginUserViaRequest("auth", USER_WITHOUT_NAME);
cy.visit(urlList.homePage);
cy.get(HOMEPAGE_SELECTORS.welcomeMessage)
.invoke("text")
.then(text => {
expect(text).to.contains(`${USER_WITHOUT_NAME.email}`);
});
expectWelcomeMessageIncludes(`${USER_WITHOUT_NAME.email}`);
});
});

View file

@ -0,0 +1,111 @@
import faker from "faker";
import {
deleteStaffMembersStartsWith,
updateStaffMember
} from "../../apiRequests/staffMember";
import { LEFT_MENU_SELECTORS } from "../../elements/account/left-menu/left-menu-selectors";
import { BUTTON_SELECTORS } from "../../elements/shared/button-selectors";
import { STAFF_MEMBER_DETAILS } from "../../elements/staffMembers/staffMemberDetails";
import { STAFF_MEMBERS_LIST } from "../../elements/staffMembers/staffMembersList";
import { expectWelcomeMessageIncludes } from "../../steps/homePageSteps";
import { getDisplayedSelectors } from "../../steps/permissions";
import {
fillUpSetPassword,
fillUpUserDetails,
updateUserActiveFlag
} from "../../steps/user";
import { urlList, userDetailsUrl } from "../../url/urlList";
import {
getMailActivationLinkForUser,
inviteStaffMemberWithFirstPermission
} from "../../utils/users";
describe("Staff members", () => {
const startsWith = "Cypress";
const password = Cypress.env("USER_PASSWORD");
const lastName = faker.name.lastName();
const email = `${startsWith}${lastName}@example.com`;
let user;
before(() => {
cy.clearSessionData().loginUserViaRequest();
deleteStaffMembersStartsWith(startsWith);
inviteStaffMemberWithFirstPermission({ email })
.then(({ user: userResp }) => {
user = userResp;
getMailActivationLinkForUser(email);
})
.then(urlLink => {
cy.clearSessionData().visit(urlLink);
fillUpSetPassword(password);
cy.clearSessionData();
});
});
beforeEach(() => {
cy.clearSessionData().loginUserViaRequest();
});
it("should invite user", () => {
const firstName = faker.name.firstName();
const emailInvite = `${startsWith}${firstName}@example.com`;
cy.visit(urlList.staffMembers)
.get(STAFF_MEMBERS_LIST.inviteStaffMemberButton)
.click();
fillUpUserDetails(firstName, lastName, emailInvite);
getMailActivationLinkForUser(emailInvite).then(urlLink => {
cy.clearSessionData().visit(urlLink);
fillUpSetPassword(password);
expectWelcomeMessageIncludes(`${firstName} ${lastName}`);
});
});
it("should deactivate user", () => {
updateStaffMember({ userId: user.id, isActive: true });
updateUserActiveFlag(user.id);
cy.clearSessionData()
.loginUserViaRequest("auth", { email, password })
.its("body.data.tokenCreate")
.then(tokenCreate => {
chai
.softExpect(
tokenCreate.errors[0].code,
"logging in should return error"
)
.to.be.eq("INVALID_CREDENTIALS");
expect(tokenCreate.token).to.be.not.ok;
});
});
it("should activate user", () => {
updateStaffMember({ userId: user.id, isActive: false });
updateUserActiveFlag(user.id);
cy.clearSessionData()
.loginUserViaRequest("auth", { email, password })
.visit(urlList.homePage);
expectWelcomeMessageIncludes(email);
});
it("should remove user permissions", () => {
cy.visit(userDetailsUrl(user.id))
.get(STAFF_MEMBER_DETAILS.removePermissionButton)
.click()
.addAliasToGraphRequest("StaffMemberUpdate")
.get(BUTTON_SELECTORS.confirm)
.click()
.wait("@StaffMemberUpdate")
.clearSessionData()
.loginUserViaRequest("auth", { email, password })
.visit(urlList.homePage);
expectWelcomeMessageIncludes(email);
getDisplayedSelectors().then(displayedSelectors => {
expect(Object.values(displayedSelectors)).to.have.length(1);
expect(Object.values(displayedSelectors)[0]).to.eq(
LEFT_MENU_SELECTORS.home
);
});
});
});

View file

@ -19,6 +19,7 @@ module.exports = (on, config) => {
// make env variables visible for cypress
config.env.API_URI = process.env.API_URI;
config.env.APP_MOUNT_URI = process.env.APP_MOUNT_URI;
config.env.mailHogUrl = process.env.CYPRESS_MAILHOG;
on("before:browser:launch", (browser = {}, launchOptions) => {
launchOptions.args.push("--proxy-bypass-list=<-loopback>");

View file

@ -1,4 +1,6 @@
import { HEADER_SELECTORS } from "../elements/header/header-selectors";
import { HOMEPAGE_SELECTORS } from "../elements/homePage/homePage-selectors";
export function changeChannel(channelName) {
cy.get(HEADER_SELECTORS.channelSelect).click();
cy.addAliasToGraphRequest("Home");
@ -7,3 +9,11 @@ export function changeChannel(channelName) {
.click();
cy.wait("@Home");
}
export function expectWelcomeMessageIncludes(name) {
cy.get(HOMEPAGE_SELECTORS.welcomeMessage)
.invoke("text")
.then(text => {
expect(text, `welcome message should contains ${name}`).to.contains(name);
});
}

46
cypress/steps/user.js Normal file
View file

@ -0,0 +1,46 @@
import { SET_PASSWORD } from "../elements/account/setPassword";
import { BUTTON_SELECTORS } from "../elements/shared/button-selectors";
import { SHARED_ELEMENTS } from "../elements/shared/sharedElements";
import { INVITE_STAFF_MEMBER_FORM } from "../elements/staffMembers/inviteStaffMemberForm";
import { STAFF_MEMBER_DETAILS } from "../elements/staffMembers/staffMemberDetails";
import { userDetailsUrl } from "../url/urlList";
import { fillAutocompleteSelect } from "./shared/autocompleteSelect";
export function fillUpSetPassword(password) {
cy.get(SET_PASSWORD.confirmPasswordInput)
.type(password)
.get(SET_PASSWORD.passwordInput)
.type(password)
.addAliasToGraphRequest("SetPassword")
.get(BUTTON_SELECTORS.confirm)
.click()
.wait("@SetPassword");
}
export function fillUpUserDetails(firstName, lastName, email) {
cy.get(INVITE_STAFF_MEMBER_FORM.firstNameInput)
.type(firstName)
.get(INVITE_STAFF_MEMBER_FORM.lastNameInput)
.type(lastName)
.get(INVITE_STAFF_MEMBER_FORM.emailInput)
.type(email)
.get(BUTTON_SELECTORS.submit)
.click();
fillAutocompleteSelect(STAFF_MEMBER_DETAILS.permissionsSelect);
cy.addAliasToGraphRequest("StaffMemberUpdate");
cy.get(BUTTON_SELECTORS.confirm)
.click()
.wait("@StaffMemberUpdate");
}
export function updateUserActiveFlag(userId) {
cy.visit(userDetailsUrl(userId))
.get(SHARED_ELEMENTS.progressBar)
.should("not.be.visible")
.get(STAFF_MEMBER_DETAILS.isActiveCheckBox)
.click()
.addAliasToGraphRequest("StaffMemberUpdate");
cy.get(BUTTON_SELECTORS.confirm)
.click()
.wait("@StaffMemberUpdate");
}

View file

@ -2,6 +2,7 @@ import "./user";
import "./softAssertions";
import "./deleteElement/index.js";
import "./elements/index";
import "cypress-mailhog";
import { urlList } from "../url/urlList";

View file

@ -12,10 +12,15 @@ export const urlList = {
collections: "collections/",
vouchers: "discounts/vouchers/",
staffMembers: "staff/",
newPassword: "new-password/",
permissionsGroups: "permission-groups/"
};
export const productDetailsUrl = productId => `${urlList.products}${productId}`;
export const userDetailsUrl = userId => `${urlList.staffMembers}${userId}`;
export const staffMemberDetailsUrl = staffMemberId =>
`${urlList.staffMembers}${staffMemberId}`;
export const permissionGroupDetails = permissionGroupId =>
`${urlList.permissionsGroups}${permissionGroupId}`;

44
cypress/utils/users.js Normal file
View file

@ -0,0 +1,44 @@
import { getPermissionsArray } from "../apiRequests/permissions";
import { inviteStaffMember } from "../apiRequests/staffMember";
export function inviteStaffMemberWithFirstPermission({
email,
isActive = true,
firstName = "",
lastName = ""
}) {
return getPermissionsArray(1).then(permissions => {
inviteStaffMember({
firstName,
lastName,
email,
isActive,
permissionId: permissions[0].node.id
});
});
}
/**
* Function mhGetMailsByRecipient first get all emails from mailhog with a timeout, and after that it finds email from recipient.
* It cloud happened that invite email from saleor has not been received yet, so in this case the action should be retried.
*/
export function getMailActivationLinkForUser(email, i = 0) {
if (i > 3) {
throw new Error(`There is no email invitation for user ${email}`);
}
return cy.mhGetMailsByRecipient(email).should(mails => {
if (!mails.length) {
getMailActivationLinkForUser(email, i + 1);
} else {
cy.wrap(mails)
.mhFirst()
.should("not.eq", undefined)
.mhGetBody()
.then(body => {
const urlRegex = /\[([^\]]*)\]/;
const bodyWithoutWhiteSpaces = body.replace(/(\r\n|\n|\r|\s)/gm, "");
return urlRegex.exec(bodyWithoutWhiteSpaces)[1];
});
}
});
}

5
package-lock.json generated
View file

@ -11133,6 +11133,11 @@
}
}
},
"cypress-mailhog": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/cypress-mailhog/-/cypress-mailhog-1.3.0.tgz",
"integrity": "sha512-SS2dK+ea/3APLmWcoUrnCxNtFbrTfVFsESnnEUq7qnwQYKs52tnCFzhlpN9akRgcCZ7V4ihpxYYeJFkBG1rg2g=="
},
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",

View file

@ -41,6 +41,7 @@
"classnames": "^2.2.6",
"crc-32": "^1.2.0",
"currency-codes": "^2.1.0",
"cypress-mailhog": "^1.3.0",
"downshift": "^1.31.16",
"editorjs-inline-tool": "^0.4.0",
"editorjs-undo": "^0.1.4",

View file

@ -112,6 +112,7 @@ const NewPasswordPage: React.FC<NewPasswordPageProps> = props => {
/>
<FormSpacer />
<Button
data-test="button-bar-confirm"
className={classes.submit}
color="primary"
disabled={(passwordError && data.password.length > 0) || disabled}

View file

@ -139,6 +139,7 @@ const StaffAddMemberDialog: React.FC<StaffAddMemberDialogProps> = props => {
<FormattedMessage {...buttonMessages.back} />
</Button>
<ConfirmButton
data-test="submit"
color="primary"
disabled={!hasChanged}
variant="contained"

View file

@ -73,6 +73,7 @@ const StaffListPage: React.FC<StaffListPageProps> = ({
}
>
<Button
data-test-id="inviteStaffMember"
color="primary"
disabled={reachedLimit}
variant="contained"

View file

@ -42047,6 +42047,7 @@ exports[`Storyshots Views / Authentication / Set up a new password default 1`] =
/>
<button
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-contained-id NewPasswordPage-submit-id MuiButton-containedPrimary-id"
data-test="button-bar-confirm"
tabindex="0"
type="submit"
>
@ -42163,6 +42164,7 @@ exports[`Storyshots Views / Authentication / Set up a new password loading 1`] =
/>
<button
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-contained-id NewPasswordPage-submit-id MuiButton-containedPrimary-id MuiButton-disabled-id MuiButtonBase-disabled-id"
data-test="button-bar-confirm"
disabled=""
tabindex="-1"
type="submit"
@ -42287,6 +42289,7 @@ exports[`Storyshots Views / Authentication / Set up a new password too short err
/>
<button
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-contained-id NewPasswordPage-submit-id MuiButton-containedPrimary-id"
data-test="button-bar-confirm"
tabindex="0"
type="submit"
>
@ -238914,6 +238917,7 @@ exports[`Storyshots Views / Staff / Staff members default 1`] = `
</div>
<button
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-contained-id MuiButton-containedPrimary-id"
data-test-id="inviteStaffMember"
tabindex="0"
type="button"
>
@ -239596,6 +239600,7 @@ exports[`Storyshots Views / Staff / Staff members limits reached 1`] = `
</div>
<button
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-contained-id MuiButton-containedPrimary-id MuiButton-disabled-id MuiButtonBase-disabled-id"
data-test-id="inviteStaffMember"
disabled=""
tabindex="-1"
type="button"
@ -240319,6 +240324,7 @@ exports[`Storyshots Views / Staff / Staff members no limits 1`] = `
>
<button
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-contained-id MuiButton-containedPrimary-id"
data-test-id="inviteStaffMember"
tabindex="0"
type="button"
>
@ -241001,6 +241007,7 @@ exports[`Storyshots Views / Staff / Staff members when loading 1`] = `
</div>
<button
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-contained-id MuiButton-containedPrimary-id"
data-test-id="inviteStaffMember"
tabindex="0"
type="button"
>