Saleor 1736 configuration channels (#966)

* Cypress tests for channels

Co-authored-by: Marcin Gębala <maarcin.gebala@gmail.com>
Co-authored-by: Krzysztof Wolski <krzysztof.k.wolski@gmail.com>
This commit is contained in:
Karolina 2021-02-11 14:58:05 +01:00 committed by GitHub
parent b36160edb1
commit 7bb3efa65d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 29232 additions and 192 deletions

View file

@ -90,11 +90,12 @@ jobs:
env: env:
API_URI: ${{ steps.api_uri.outputs.custom_api_uri || secrets.API_URI }} API_URI: ${{ steps.api_uri.outputs.custom_api_uri || secrets.API_URI }}
APP_MOUNT_URI: ${{ secrets.APP_MOUNT_URI }} APP_MOUNT_URI: ${{ secrets.APP_MOUNT_URI }}
CYPRESS_baseUrl: ${{ secrets.CYPRESS_BASEURL }}
CYPRESS_USER_NAME: ${{ secrets.CYPRESS_USER_NAME }} CYPRESS_USER_NAME: ${{ secrets.CYPRESS_USER_NAME }}
CYPRESS_USER_PASSWORD: ${{ secrets.CYPRESS_USER_PASSWORD }} CYPRESS_USER_PASSWORD: ${{ secrets.CYPRESS_USER_PASSWORD }}
with: with:
build: npm run build build: npm run build
start: npx http-server -a localhost -p 9000 build/dashboard start: npx local-web-server --spa index.html
wait-on: http://localhost:9000/ wait-on: http://localhost:9000/
wait-on-timeout: 120 wait-on-timeout: 120
- uses: actions/upload-artifact@v1 - uses: actions/upload-artifact@v1

View file

@ -1,5 +1,5 @@
{ {
"baseUrl": "http://localhost:9000", "baseUrl": "http://localhost:9000/dashboard/",
"defaultCommandTimeout": 15000, "defaultCommandTimeout": 15000,
"requestTimeout": 15000, "requestTimeout": 15000,
"viewportWidth": 1400, "viewportWidth": 1400,

View file

@ -0,0 +1,69 @@
class Channels {
createChannel(isActive, name, slug, currencyCode) {
const createChannelMutation = `mutation{
channelCreate(input: {
isActive: ${isActive}
name: "${name}"
slug: "${slug}"
currencyCode: "${currencyCode}"
}){
channel{
name
slug
}
channelErrors{
code
message
}
}
}`;
cy.sendRequestWithQuery(createChannelMutation);
}
deleteTestChannels(nameStartsWith) {
const getChannelsInfoQuery = `query{
channels{
name
id
isActive
slug
currencyCode
}
}
`;
cy.sendRequestWithQuery(getChannelsInfoQuery).then(resp => {
const channels = new Set(resp.body.data.channels);
channels.forEach(element => {
if (element.name.startsWith(nameStartsWith)) {
const targetChannels = Array.from(channels).filter(function(channel) {
return (
element.currencyCode === channel.currencyCode &&
element.id !== channel.id
);
});
if (targetChannels[0]) {
this.deleteChannel(element.id, targetChannels[0].id);
channels.delete(element);
}
}
});
});
}
deleteChannel(channelId, targetChennelId) {
const deleteChannelMutation = `mutation{
channelDelete(id: "${channelId}", input:{
targetChannel: "${targetChennelId}"
}){
channel{
name
}
channelErrors{
message
}
}
}`;
return cy.sendRequestWithQuery(deleteChannelMutation);
}
}
export default Channels;

View file

@ -1,4 +1,7 @@
/* eslint-disable sort-keys */
export const LEFT_MENU_SELECTORS = { export const LEFT_MENU_SELECTORS = {
catalog: "[data-test-id='catalogue']" catalog: "[data-test='menu-item-label'][data-test-id='catalogue']",
configuration: "[data-test='menu-item-label'][data-test-id='configure']",
home: "[data-test='menu-item-label'][data-test-id='home']",
orders: "[data-test='menu-item-label'][data-test-id=orders']",
products: "[data-test='submenu-item-label'][data-test-id='products']"
}; };

View file

@ -1,5 +1,6 @@
/* eslint-disable sort-keys */ /* eslint-disable sort-keys */
export const PRODUCTS_SELECTORS = { export const PRODUCTS_SELECTORS = {
productsList: "[data-test-id][data-test='id']",
products: "[data-test='submenu-item-label'][data-test-id='products']", products: "[data-test='submenu-item-label'][data-test-id='products']",
createProductBtn: "[data-test='add-product']", createProductBtn: "[data-test='add-product']",
productNameInput: "[name='name']", productNameInput: "[name='name']",
@ -11,5 +12,10 @@ export const PRODUCTS_SELECTORS = {
visibleRadioBtn: "[name='isPublished']", visibleRadioBtn: "[name='isPublished']",
saveBtn: "[data-test='button-bar-confirm']", saveBtn: "[data-test='button-bar-confirm']",
confirmationMsg: "[data-test='notification-success']", confirmationMsg: "[data-test='notification-success']",
channelAvailabilityItem: "[data-test='channel-availability-item']" channelAvailabilityItem: "[data-test='channel-availability-item']",
availableManageButton:
"[data-test-id='channels-availiability-manage-button']",
channelsAvailabilityForm:
"[data-test-id='manage-products-channels-availiability-list']",
emptyProductRow: "[class*='Skeleton']"
}; };

View file

@ -0,0 +1,12 @@
export const ADD_CHANNEL_FORM_SELECTORS = {
channelName: "[name='name']",
slug: "[name='slug']",
currency: "[data-test-id='channel-currency-select-input']",
currencyOptions: "[data-test='singleautocomplete-select-option']",
saveButton: "[data-test='button-bar-confirm']",
backToChannelsList: "[data-test-id='app-header-back-button']",
currencyValidationMessage: "[data-testid='currency-text-input-helper-text']",
slugValidationMessage: "[data-testid='slug-text-input-helper-text']",
currencyAutocompleteDropdown:
"[data-test='singleautocomplete-select-option'][data-test-type='custom']"
};

View file

@ -0,0 +1,5 @@
export const CHANNEL_FORM_SELECTORS = {
channelSelect: "[id='mui-component-select-channels']",
channelOption: "[data-test='selectFieldOption']",
confirmButton: "[data-test='submit']"
};

View file

@ -0,0 +1,5 @@
export const CHANNELS_SELECTORS = {
createChannelButton: "[data-test='add-channel']",
channelsTable: "[class='MuiTableBody-root']",
channelName: "[data-test='name']"
};

View file

@ -0,0 +1,4 @@
/* eslint-disable sort-keys */
export const CONFIGURATION_SELECTORS = {
channels: "[data-testid='channels']"
};

View file

@ -0,0 +1,4 @@
export const HEADER_SELECTORS = {
channelSelect: "[data-test-id='app-channel-select']",
channelSelectList: "[class*='MuiMenu-paper']"
};

View file

@ -0,0 +1,3 @@
export const DRAFT_ORDER_SELECTORS = {
addProducts: "[data-test-id='add-products-button']"
};

View file

@ -0,0 +1,4 @@
export const ORDERS_SELECTORS = {
orders: "[data-test='submenu-item-label'][data-test-id='orders']",
createOrder: "[data-test-id='create-order-button']"
};

View file

@ -0,0 +1,140 @@
// <reference types="cypress" />
import faker from "faker";
import Channels from "../apiRequests/Channels";
import { LEFT_MENU_SELECTORS } from "../elements/account/left-menu/left-menu-selectors";
import { PRODUCTS_SELECTORS } from "../elements/catalog/product-selectors";
import { ADD_CHANNEL_FORM_SELECTORS } from "../elements/channels/add-channel-form-selectors";
import { CHANNEL_FORM_SELECTORS } from "../elements/channels/channel-form-selectors";
import { CHANNELS_SELECTORS } from "../elements/channels/channels-selectors";
import { CONFIGURATION_SELECTORS } from "../elements/configuration/configuration-selectors";
import { HEADER_SELECTORS } from "../elements/header/header-selectors";
import { DRAFT_ORDER_SELECTORS } from "../elements/orders/draft-order-selectors";
import { ORDERS_SELECTORS } from "../elements/orders/orders-selectors";
import { BUTTON_SELECTORS } from "../elements/shared/button-selectors";
import ChannelsSteps from "../steps/channelsSteps";
import { urlList } from "../url/urlList";
describe("Channels", () => {
const channelStartsWith = "Cypress:";
const currency = "PLN";
const channels = new Channels();
const channelsSteps = new ChannelsSteps();
before(() => {
cy.clearSessionData().loginUserViaRequest();
channels.deleteTestChannels(channelStartsWith);
});
beforeEach(() => {
cy.clearSessionData().loginUserViaRequest();
});
it("should navigate to channels page", () => {
cy.visit(urlList.homePage)
.get(LEFT_MENU_SELECTORS.configuration)
.click()
.get(CONFIGURATION_SELECTORS.channels)
.click()
.location("pathname")
.should("contain", "channels");
});
it("should create new channel", () => {
const randomChannel = `${channelStartsWith} ${faker.random.number()}`;
cy.visit(urlList.channels).waitForGraph("Channels");
channelsSteps.createChannelByView(randomChannel, currency);
// New channel should be visible in channels list
cy.waitForGraph("Channel")
.get(ADD_CHANNEL_FORM_SELECTORS.backToChannelsList)
.click()
.get(CHANNELS_SELECTORS.channelsTable)
.contains(randomChannel)
// new channel should be visible in channel selector
cy.visit(urlList.homePage)
.get(HEADER_SELECTORS.channelSelect)
.click()
.get(HEADER_SELECTORS.channelSelectList)
.contains(randomChannel)
.click()
// new channel should be visible at product availability form
cy.visit(urlList.products)
.waitForGraph("InitialProductFilterData");
cy.get(PRODUCTS_SELECTORS.productsList)
.first()
.click()
.get(PRODUCTS_SELECTORS.availableManageButton)
.click()
.get(PRODUCTS_SELECTORS.channelsAvailabilityForm)
.contains(randomChannel);
});
it("should validate slug name", () => {
const randomChannel = `${channelStartsWith} ${faker.random.number()}`;
channels.createChannel(false, randomChannel, randomChannel, currency);
cy.visit(urlList.channels);
channelsSteps.createChannelByView(randomChannel, currency);
cy.get(ADD_CHANNEL_FORM_SELECTORS.slugValidationMessage).should(
"be.visible"
);
});
it("should validate currency", () => {
const randomChannel = `${channelStartsWith} ${faker.random.number()}`;
cy.visit(urlList.channels);
channelsSteps.createChannelByView(
randomChannel,
currency,
"notExistingCurrency"
);
cy.get(ADD_CHANNEL_FORM_SELECTORS.currencyValidationMessage).should(
"be.visible"
);
});
it("should delete channel", () => {
const randomChannelToDelete = `${channelStartsWith} ${faker.random.number()}`;
channels.createChannel(
false,
randomChannelToDelete,
randomChannelToDelete,
currency
);
cy.visit(urlList.channels).waitForGraph("Channels");
cy.get(CHANNELS_SELECTORS.channelName)
.contains(randomChannelToDelete)
.parentsUntil(CHANNELS_SELECTORS.channelsTable)
.find("button")
.click()
.get(BUTTON_SELECTORS.submit)
.click()
.waitForGraph("Channels");
cy.get(CHANNELS_SELECTORS.channelName)
.contains(randomChannelToDelete)
.should("not.exist");
});
it("should not be possible to add products to order with inactive channel", () => {
const randomChannel = `${channelStartsWith} ${faker.random.number()}`;
channels.createChannel(false, randomChannel, randomChannel, currency);
cy.visit(urlList.orders)
.get(ORDERS_SELECTORS.createOrder)
.click()
.get(CHANNEL_FORM_SELECTORS.channelSelect)
.click()
.get(CHANNEL_FORM_SELECTORS.channelOption)
.contains(randomChannel)
.click()
.get(CHANNEL_FORM_SELECTORS.confirmButton)
.click();
cy.location()
.should(loc => {
const urlRegex = new RegExp(`${urlList.orders}.+`, "g");
expect(loc.pathname).to.match(urlRegex);
})
.get(DRAFT_ORDER_SELECTORS.addProducts)
.should("not.exist");
});
});

View file

@ -1,19 +1,20 @@
import { LOGIN_SELECTORS } from "../elements/account/login-selectors";
// <reference types="cypress" /> // <reference types="cypress" />
import { LOGIN_SELECTORS } from "../elements/account/login-selectors";
import { urlList } from "../url/urlList";
describe("User authorization", () => { describe("User authorization", () => {
beforeEach(() => { beforeEach(() => {
cy.clearSessionData(); cy.clearSessionData();
}); });
it("should successfully log in an user", () => { it("should successfully log in an user", () => {
cy.visit("/"); cy.visit(urlList.homePage);
cy.loginUser(); cy.loginUser();
cy.get(LOGIN_SELECTORS.welcomePage); cy.get(LOGIN_SELECTORS.welcomePage);
}); });
it("should fail for wrong password", () => { it("should fail for wrong password", () => {
cy.visit("/") cy.visit(urlList.homePage)
.get(LOGIN_SELECTORS.emailAddressInput) .get(LOGIN_SELECTORS.emailAddressInput)
.type("admin@example.com") .type("admin@example.com")
.get(LOGIN_SELECTORS.emailPasswordInput) .get(LOGIN_SELECTORS.emailPasswordInput)
@ -27,7 +28,7 @@ describe("User authorization", () => {
cy.window().then(win => { cy.window().then(win => {
win.sessionStorage.clear(); win.sessionStorage.clear();
}); });
cy.visit("/"); cy.visit(urlList.homePage);
cy.loginUser(); cy.loginUser();
cy.get(LOGIN_SELECTORS.userMenu) cy.get(LOGIN_SELECTORS.userMenu)
.click() .click()

View file

@ -1,18 +1,25 @@
// <reference types="cypress" />
import { LEFT_MENU_SELECTORS } from "../elements/account/left-menu/left-menu-selectors"; import { LEFT_MENU_SELECTORS } from "../elements/account/left-menu/left-menu-selectors";
import { PRODUCTS_SELECTORS } from "../elements/catalog/product-selectors"; import { PRODUCTS_SELECTORS } from "../elements/catalog/product-selectors";
import { urlList } from "../url/urlList";
// <reference types="cypress" />
describe("Products", () => { describe("Products", () => {
beforeEach(() => { beforeEach(() => {
cy.clearSessionData().loginUserViaRequest(); cy.clearSessionData().loginUserViaRequest();
}); });
it("should add new visible product", () => { it("should navigate to channels page", () => {
cy.visit("/") cy.visit(urlList.homePage)
.get(LEFT_MENU_SELECTORS.catalog) .get(LEFT_MENU_SELECTORS.catalog)
.click() .click()
.get(PRODUCTS_SELECTORS.products) .get(LEFT_MENU_SELECTORS.products)
.click() .click()
.location("pathname")
.should("contain", "/products");
});
it("should add new visible product", () => {
cy.visit(urlList.products)
.get(PRODUCTS_SELECTORS.createProductBtn) .get(PRODUCTS_SELECTORS.createProductBtn)
.click() .click()
.get(PRODUCTS_SELECTORS.productNameInput) .get(PRODUCTS_SELECTORS.productNameInput)

View file

@ -1,11 +1,13 @@
// <reference types="cypress" /> // <reference types="cypress" />
import { urlList } from "../url/urlList";
describe("Warehouse settings", () => { describe("Warehouse settings", () => {
beforeEach(() => { beforeEach(() => {
cy.clearSessionData(); cy.clearSessionData();
}); });
xit("Warehouse section visible in the configuration", () => { xit("Warehouse section visible in the configuration", () => {
cy.visit("/configuration/") cy.visit(urlList.configuration)
.loginUser() .loginUser()
.get("[data-test-id=warehouses][data-test=settingsSubsection]") .get("[data-test-id=warehouses][data-test=settingsSubsection]")
.click(); .click();
@ -13,7 +15,7 @@ describe("Warehouse settings", () => {
}); });
xit("Editing warehouse is available", () => { xit("Editing warehouse is available", () => {
cy.visit(`/warehouses`) cy.visit(urlList.warehouses)
.loginUser() .loginUser()
.get("[data-test=editButton]") .get("[data-test=editButton]")
.first() .first()

View file

@ -16,7 +16,13 @@
* @type {Cypress.PluginConfig} * @type {Cypress.PluginConfig}
*/ */
module.exports = (on, config) => { module.exports = (on, config) => {
// make env variables visible for cypress
config.env.API_URI = process.env.API_URI; config.env.API_URI = process.env.API_URI;
config.env.APP_MOUNT_URI = process.env.APP_MOUNT_URI;
on("before:browser:launch", (browser = {}, launchOptions) => {
launchOptions.args.push("--proxy-bypass-list=<-loopback>");
return launchOptions;
});
return config; return config;
}; };

View file

@ -0,0 +1,26 @@
import { ADD_CHANNEL_FORM_SELECTORS } from "../elements/channels/add-channel-form-selectors";
import { CHANNELS_SELECTORS } from "../elements/channels/channels-selectors";
class ChannelsSteps {
createChannelByView(name, currency, otherCurrency, slug = name) {
cy.get(CHANNELS_SELECTORS.createChannelButton)
.click()
.get(ADD_CHANNEL_FORM_SELECTORS.channelName)
.type(name)
.get(ADD_CHANNEL_FORM_SELECTORS.slug)
.type(slug)
.get(ADD_CHANNEL_FORM_SELECTORS.currency)
.click();
if (!otherCurrency) {
cy.get(ADD_CHANNEL_FORM_SELECTORS.currency).type(currency);
cy.get(`[data-test-value=${currency}]`).click();
} else {
cy.get(ADD_CHANNEL_FORM_SELECTORS.currency)
.type(otherCurrency)
.get(ADD_CHANNEL_FORM_SELECTORS.currencyAutocompleteDropdown)
.click();
}
cy.get(ADD_CHANNEL_FORM_SELECTORS.saveButton).click();
}
}
export default ChannelsSteps;

View file

@ -1,19 +1,54 @@
import "./user"; import "./user";
import { urlList } from "../url/urlList";
Cypress.Commands.add("clearSessionData", () => { Cypress.Commands.add("clearSessionData", () => {
// Because of known cypress bug, not all local storage data are cleared. // Because of known cypress bug, not all local storage data are cleared.
// Here is workaround to ensure tests have no side effects. // Here is workaround to ensure tests have no side effects.
// Suggested usage: // Suggested usage:
// beforeEach(() => { // beforeEach(() => {
// cy.clearSessionData(); // cy.clearSessionData();
// }); // });
cy.clearCookies(); cy.clearCookies();
cy.clearLocalStorage(); cy.clearLocalStorage();
cy.visit("/", { cy.visit(urlList.homePage, {
onBeforeLoad: win => { onBeforeLoad: win => {
win.sessionStorage.clear(); win.sessionStorage.clear();
} }
}); });
}); });
Cypress.Commands.add("waitForGraph", operationName => {
cy.intercept("POST", Cypress.env("API_URI"), req => {
req.statusCode = 200;
const requestBody = req.body;
if (Array.isArray(requestBody)) {
requestBody.forEach(element => {
if (element.operationName === operationName) {
req.alias = operationName;
}
});
} else {
if (requestBody.operationName === operationName) {
req.alias = operationName;
}
}
});
cy.wait(`@${operationName}`);
});
Cypress.Commands.add("sendRequestWithQuery", query =>
cy.request({
body: {
method: "POST",
query,
url: urlList.apiUri,
},
headers: {
Authorization: `JWT ${window.sessionStorage.getItem("auth")}`
},
method: "POST",
url: urlList.apiUri
})
);

View file

@ -1,5 +1,5 @@
/* eslint-disable sort-keys */
import { LOGIN_SELECTORS } from "../../elements/account/login-selectors"; import { LOGIN_SELECTORS } from "../../elements/account/login-selectors";
import { urlList } from "../../url/urlList";
Cypress.Commands.add("loginUser", () => Cypress.Commands.add("loginUser", () =>
cy cy
@ -31,16 +31,16 @@ Cypress.Commands.add("loginUserViaRequest", () => {
return cy return cy
.request({ .request({
method: "POST",
url: Cypress.env("API_URI"),
body: { body: {
operationName: "TokenAuth", operationName: "TokenAuth",
query: logInMutationQuery,
variables: { variables: {
email: Cypress.env("USER_NAME"), email: Cypress.env("USER_NAME"),
password: Cypress.env("USER_PASSWORD") password: Cypress.env("USER_PASSWORD")
}, },
query: logInMutationQuery },
} method: "POST",
url: urlList.apiUri,
}) })
.then(resp => { .then(resp => {
window.sessionStorage.setItem("auth", resp.body.data.tokenCreate.token); window.sessionStorage.setItem("auth", resp.body.data.tokenCreate.token);

9
cypress/url/urlList.js Normal file
View file

@ -0,0 +1,9 @@
export const urlList = {
apiUri: Cypress.env("API_URI"),
channels: "channels/",
configuration: "configuration/",
homePage: "/",
orders: "orders/",
products: "products/",
warehouses: "warehouses/"
};

4
lws.config.js Normal file
View file

@ -0,0 +1,4 @@
module.exports = {
directory: 'build/dashboard/',
port: 9000,
}

29026
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{ {
"name": "saleor-dashboard", "name": "saleor-dashboard",
"version": "3.0.0-a.0", "version": "3.0.0-a.5",
"main": "src/index.tsx", "main": "src/index.tsx",
"repository": { "repository": {
"type": "git", "type": "git",
@ -42,7 +42,9 @@
"downshift": "^1.31.16", "downshift": "^1.31.16",
"editorjs-inline-tool": "^0.4.0", "editorjs-inline-tool": "^0.4.0",
"editorjs-undo": "^0.1.4", "editorjs-undo": "^0.1.4",
"faker": "^5.1.0",
"fast-array-diff": "^0.2.0", "fast-array-diff": "^0.2.0",
"fsevents": "^1.2.9",
"fuzzaldrin": "^2.1.0", "fuzzaldrin": "^2.1.0",
"graphql": "^14.4.2", "graphql": "^14.4.2",
"graphql-tag": "^2.11.0", "graphql-tag": "^2.11.0",
@ -130,7 +132,7 @@
"codecov": "^3.7.1", "codecov": "^3.7.1",
"core-js": "^3.7.0", "core-js": "^3.7.0",
"cross-env": "^6.0.3", "cross-env": "^6.0.3",
"cypress": "^4.9.0", "cypress": "^6.4.0",
"enzyme": "^3.11.0", "enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.5", "enzyme-adapter-react-16": "^1.15.5",
"enzyme-to-json": "^3.6.1", "enzyme-to-json": "^3.6.1",