From f22f4f1398b009965cd90244b5cc22d68e8bd69a Mon Sep 17 00:00:00 2001 From: Beniamin Sinca Date: Fri, 15 Jul 2022 11:17:40 +0100 Subject: [PATCH 01/24] Fix bug when media_gallery array is empty In `getGallery` rather check the length of an array to get an accurate bool value than simple the array which will always return true. --- .../theme/modules/catalog/product/getters/productGetters.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/theme/modules/catalog/product/getters/productGetters.ts b/packages/theme/modules/catalog/product/getters/productGetters.ts index 524255fee..209f52e94 100644 --- a/packages/theme/modules/catalog/product/getters/productGetters.ts +++ b/packages/theme/modules/catalog/product/getters/productGetters.ts @@ -92,11 +92,11 @@ export const getPrice = (product: ProductInterface): Price => { export const getGallery = (product: Product, maxGallerySize = 4): MediaGalleryItem[] => { const images = []; - if (!product?.media_gallery && !product?.configurable_product_options_selection?.media_gallery) { + if (!product?.media_gallery.length && !product?.configurable_product_options_selection?.media_gallery.length) { return images; } - const selectedGallery = product.configurable_product_options_selection?.media_gallery + const selectedGallery = product.configurable_product_options_selection?.media_gallery.length ? product.configurable_product_options_selection.media_gallery : product.media_gallery; From 29c0dee21bbacc387923cca1042b3b6326e5a23a Mon Sep 17 00:00:00 2001 From: Artur Tagisow Date: Mon, 18 Jul 2022 11:31:10 +0200 Subject: [PATCH 02/24] ci: poll farmer for pod deployment status ci: add polling farmer for deploy status ci: fix extra parentheses --- .github/workflows/deployment-template.yml | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deployment-template.yml b/.github/workflows/deployment-template.yml index ded9778b4..c869c15c0 100644 --- a/.github/workflows/deployment-template.yml +++ b/.github/workflows/deployment-template.yml @@ -28,9 +28,17 @@ jobs: exit 1 fi - # the above curl only *asks* for the container to be deployed - # we don't know when the newly built Docker image replaces the old one - # but it takes less than 5 minutes - - name: 'Wait for container deployed on VSF Cloud to come online' - run: 'sleep 300' - shell: 'bash' + - name: 'Poll for Farmer pod deployment status' + timeout-minutes: 5 + run: | + query_deploy_check_endpoint () { + NAMESPACE=${{ inputs.environment-code }}-europe-west1-gcp-storefrontcloud-io + curl -s \ + -H 'X-User-Id: ${{ secrets.cloud-username }}' \ + -H 'X-Api-Key: ${{ secrets.cloud-password }}' \ + -H 'Content-Type: application/json' \ + https://farmer.storefrontcloud.io/deploy_check/$NAMESPACE/${{ github.sha }} + } + until $( query_deploy_check_endpoint | tee /dev/stderr | grep -q '{"code":200,"ready":"1","deployed":"1"}' ); do + sleep 10; + done; From 3e04b98ba8228cf78fbb7043c1d6c0368451adc5 Mon Sep 17 00:00:00 2001 From: Marcin Kwiatkowski Date: Thu, 14 Jul 2022 15:57:44 +0200 Subject: [PATCH 03/24] docs: addd 1.0.0 release notes --- docs/index.md | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/docs/index.md b/docs/index.md index 383e13057..e2148b546 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,22 +13,4 @@ To get started, see the following guides: ## Demo -If you want to see the integration in action, we have three demo environments where you can see and test Magento 2 integration for Vue Storefront in action. If you find a bug in any of them, please [create an issue](https://github.com/vuestorefront/magento2/issues/new/choose) in our repository to let us know about it. - -### Production environment - -In the production environment, you can see the latest **released and stable** version of the integration. It should be visually and feature-wise identical to the new project installed using our CLI. - -[Production demo](https://demo-magento2.europe-west1.gcp.vuestorefront.cloud) - -### Staging environment - -In the staging environment, we are testing new releases. It should be **relatively stable**, but you might still encounter some bugs. - -[Staging demo](https://demo-magento2-canary.europe-west1.gcp.storefrontcloud.io) - -### Development environment - -In the development environment, we are testing the `develop` branch, which might contain unfinished or experimental features. It might be **unstable** both visually and feature-wise. - -[Development environment](https://demo-magento2-dev.europe-west1.gcp.storefrontcloud.io) +If you want to see the integration in action, check out our [demo environments](/guide/environments.html). From 6be7d3e42935639e524e19d94d0c561f045bb665 Mon Sep 17 00:00:00 2001 From: Bartosz Herba Date: Thu, 21 Jul 2022 11:22:18 +0200 Subject: [PATCH 04/24] fix: images are not loaded properly with ipx - add images extensions for a proper rendering --- packages/theme/pages/Home.vue | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/theme/pages/Home.vue b/packages/theme/pages/Home.vue index 26a782f55..826b28ea3 100644 --- a/packages/theme/pages/Home.vue +++ b/packages/theme/pages/Home.vue @@ -100,7 +100,7 @@ export default defineComponent({ title: app.i18n.t('Colorful summer dresses are already in store'), subtitle: app.i18n.t('SUMMER COLLECTION {year}', { year }), buttonText: app.i18n.t('Learn more'), - imageSrc: '/homepage/bannerB', + imageSrc: '/homepage/bannerB.webp', imageWidth: isDesktop ? 1240 : 328, imageHeight: isDesktop ? 400 : 224, imageConfig: { @@ -120,9 +120,9 @@ export default defineComponent({ buttonText: app.i18n.t('Shop now'), image: { mobile: - '/homepage/bannerB', + '/homepage/bannerB.webp', desktop: - '/homepage/bannerF', + '/homepage/bannerF.webp', }, imageConfig: { fit: 'cover', @@ -141,7 +141,7 @@ export default defineComponent({ 'Find stunning women\'s cocktail dresses and party dresses. Stand out in lace and metallic cocktail dresses from all your favorite brands.', ), buttonText: app.i18n.t('Shop now'), - image: '/homepage/bannerE', + image: '/homepage/bannerE.webp', imageConfig: { fit: 'cover', width: isDesktop ? 496 : 328, @@ -155,7 +155,7 @@ export default defineComponent({ slot: 'banner-C', subtitle: app.i18n.t('T-Shirts'), title: app.i18n.t('The Office Life'), - image: '/homepage/bannerC', + image: '/homepage/bannerC.webp', imageConfig: { fit: 'cover', width: isDesktop ? 332 : 328, @@ -169,7 +169,7 @@ export default defineComponent({ slot: 'banner-D', subtitle: app.i18n.t('Summer Sandals'), title: app.i18n.t('Eco Sandals'), - image: '/homepage/bannerG', + image: '/homepage/bannerG.webp', imageConfig: { fit: 'cover', width: isDesktop ? 332 : 328, @@ -184,7 +184,7 @@ export default defineComponent({ title: app.i18n.t('Subscribe to Newsletters'), description: app.i18n.t('Be aware of upcoming sales and events. Receive gifts and special offers!'), buttonText: app.i18n.t('Subscribe'), - imageSrc: '/homepage/newsletter', + imageSrc: '/homepage/newsletter.webp', imageWidth: isDesktop ? 1240 : 400, imageHeight: isDesktop ? 202 : 200, imageConfig: { From cb8590c02138fa2a317d6d9b3a0c178410e4f0bc Mon Sep 17 00:00:00 2001 From: Bartosz Herba Date: Wed, 20 Jul 2022 11:37:08 +0200 Subject: [PATCH 05/24] fix: env export for windows os environments --- packages/theme/.env.example | 2 ++ packages/theme/nuxt.config.js | 2 +- packages/theme/package.json | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/theme/.env.example b/packages/theme/.env.example index d1fc42c14..a9f09937a 100644 --- a/packages/theme/.env.example +++ b/packages/theme/.env.example @@ -27,3 +27,5 @@ VSF_RECAPTCHA_HIDE_BADGE= VSF_RECAPTCHA_SIZE=invisible VSF_RECAPTCHA_MIN_SCORE=0.5 VSF_RECAPTCHA_VERSION=3 + +NODE_TLS_REJECT_UNAUTHORIZED=0 diff --git a/packages/theme/nuxt.config.js b/packages/theme/nuxt.config.js index f72d586c8..2a305497f 100755 --- a/packages/theme/nuxt.config.js +++ b/packages/theme/nuxt.config.js @@ -285,7 +285,7 @@ export default () => { }; } - if (process.env.NODE_ENV === 'development' || process.env.IS_ENV_LOCAL) { + if (process.env.NODE_ENV === 'development' || process.env.VSF_NUXT_APP_ENV === 'development') { baseConfig.server = { https: { key: fs.readFileSync(path.resolve(__dirname, 'localhost-key.pem')), diff --git a/packages/theme/package.json b/packages/theme/package.json index 5e86d3d98..82d6c8839 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -10,14 +10,14 @@ "scripts": { "build": "nuxt build --modern=client", "build:analyze": "nuxt build -a --modern=client", - "dev": "export NODE_TLS_REJECT_UNAUTHORIZED=0 && nuxt --env.NODE_TLS_REJECT_UNAUTHORIZED=0", + "dev": "nuxt --env.NODE_TLS_REJECT_UNAUTHORIZED=0", "dev:debug": "node --inspect ../../node_modules/.bin/nuxt dev", "generate": "nuxt generate", "lint": "eslint . --ext .ts,.vue", "lint:fix": "eslint . --ext .ts,.vue --fix", "precommit": "lint-staged", "start": "nuxt start --modern=client", - "start:local": "export NODE_TLS_REJECT_UNAUTHORIZED=0 && export IS_ENV_LOCAL=1 && nuxt start --modern=client --env.NODE_TLS_REJECT_UNAUTHORIZED=0", + "start:local": "nuxt start --modern=client --env.NODE_TLS_REJECT_UNAUTHORIZED=0", "test": "jest", "test:watch": "jest --watch", "test:coverage": "jest --coverage", From 133c8367477b1cad8d984d4ed340f45b2f510bb1 Mon Sep 17 00:00:00 2001 From: Bartosz Herba Date: Mon, 18 Jul 2022 10:01:49 +0200 Subject: [PATCH 06/24] feat: load tests - add magento store config load test - add create empty guest cart load test - add magento create customer --- .../src/mutation/addProductsToCart.js | 191 ++++++++++++++++++ .../load-tests/src/mutation/createCustomer.js | 45 +++++ .../src/mutation/createEmptyCart.js | 7 + .../load-tests/src/query/storeConfig.gql.js | 17 ++ .../tests/magento/addSimpleProductToCart.js | 52 +++++ .../src/tests/magento/createCustomer.js | 51 +++++ .../src/tests/magento/createEmptyGuestCart.js | 46 +++++ .../src/tests/magento/storeConfig.js | 41 ++++ .../src/tests/magento/utils/customerData.js | 16 ++ .../src/tests/magento/utils/getHeaders.js | 5 + .../src/tests/middleware/createCustomer.js | 48 +++++ .../tests/middleware/createEmptyGuestCart.js | 47 +++++ .../src/tests/middleware/storeConfig.js | 40 ++++ .../tests/middleware/utils/customerData.js | 16 ++ .../src/tests/middleware/utils/getHeaders.js | 7 + packages/load-tests/src/utils/getCookies.js | 7 + 16 files changed, 636 insertions(+) create mode 100644 packages/load-tests/src/mutation/addProductsToCart.js create mode 100644 packages/load-tests/src/mutation/createCustomer.js create mode 100644 packages/load-tests/src/mutation/createEmptyCart.js create mode 100644 packages/load-tests/src/query/storeConfig.gql.js create mode 100644 packages/load-tests/src/tests/magento/addSimpleProductToCart.js create mode 100644 packages/load-tests/src/tests/magento/createCustomer.js create mode 100644 packages/load-tests/src/tests/magento/createEmptyGuestCart.js create mode 100644 packages/load-tests/src/tests/magento/storeConfig.js create mode 100644 packages/load-tests/src/tests/magento/utils/customerData.js create mode 100644 packages/load-tests/src/tests/magento/utils/getHeaders.js create mode 100644 packages/load-tests/src/tests/middleware/createCustomer.js create mode 100644 packages/load-tests/src/tests/middleware/createEmptyGuestCart.js create mode 100644 packages/load-tests/src/tests/middleware/storeConfig.js create mode 100644 packages/load-tests/src/tests/middleware/utils/customerData.js create mode 100644 packages/load-tests/src/tests/middleware/utils/getHeaders.js create mode 100644 packages/load-tests/src/utils/getCookies.js diff --git a/packages/load-tests/src/mutation/addProductsToCart.js b/packages/load-tests/src/mutation/addProductsToCart.js new file mode 100644 index 000000000..97b581093 --- /dev/null +++ b/packages/load-tests/src/mutation/addProductsToCart.js @@ -0,0 +1,191 @@ +export const AddProductsToCart = ` +mutation addProductsToCart($cartId: String!, $cartItems: [CartItemInput!]!) { + addProductsToCart(cartId,: $cartId, cartItems,: $cartItems) { + cart { + id + email + is_virtual + applied_coupons { + code + } + prices { + subtotal_excluding_tax { + value + }, + subtotal_including_tax { + value + }, + applied_taxes { + amount { + value + }, + label + } + discounts { + amount { + value + }, + label + } + grand_total { + value + } + } + items { + uid + product { + uid + __typename + sku + name + stock_status + only_x_left_in_stock + rating_summary + thumbnail { + url + position + disabled + label + } + url_key + url_rewrites { + url + } + price_range { + maximum_price { + final_price { + currency + value + } + regular_price { + currency + value + } + } + minimum_price { + final_price { + currency + value + } + regular_price { + currency + value + } + } + } + categories { + uid + name + url_suffix + url_path + breadcrumbs { + category_name, + category_url_path + } + } + review_count + reviews { + items { + average_rating + ratings_breakdown { + name + value + } + } + } + } + prices { + row_total { + value + } + row_total_including_tax { + value + } + total_item_discount { + value + } + } + quantity + ... on ConfigurableCartItem { + configurable_options { + configurable_product_option_uid + option_label + configurable_product_option_value_uid + value_label + } + configured_variant { + sku + thumbnail { + url + } + } + } + ... on BundleCartItem { + bundle_options { + uid + label + type + values { + id + label + price + quantity + } + } + } + } + total_quantity + shipping_addresses { + firstname + lastname + street + city + company + region { + code + region_id + label + } + postcode + telephone + country { + code + label + } + selected_shipping_method { + carrier_code + carrier_title + method_code + method_title + amount { + value + currency + } + } + } + billing_address { + firstname + lastname + street + city + company + region { + code + region_id + label + } + postcode + telephone + country { + code + label + } + } + } + user_errors { + code + message + } + } + } +`; diff --git a/packages/load-tests/src/mutation/createCustomer.js b/packages/load-tests/src/mutation/createCustomer.js new file mode 100644 index 000000000..aab1d7a13 --- /dev/null +++ b/packages/load-tests/src/mutation/createCustomer.js @@ -0,0 +1,45 @@ +export const CreateCustomer = ` + mutation createCustomer($input: CustomerCreateInput!) { + createCustomerV2(input: $input) { + customer { + date_of_birth + default_billing + default_shipping + email + firstname + is_subscribed + lastname + middlename + prefix + suffix + taxvat + addresses { + city + country_code + default_billing + default_shipping + extension_attributes { + attribute_code + value + } + firstname + id + lastname + postcode + prefix + region { + region_code + region_id + region + } + street + suffix + telephone + vat_id + } + } + } + } +`; + +export default CreateCustomer; diff --git a/packages/load-tests/src/mutation/createEmptyCart.js b/packages/load-tests/src/mutation/createEmptyCart.js new file mode 100644 index 000000000..28d399306 --- /dev/null +++ b/packages/load-tests/src/mutation/createEmptyCart.js @@ -0,0 +1,7 @@ +export const CreateEmptyCartMutation = ` + mutation { + createEmptyCart + } +`; + +export default CreateEmptyCartMutation; diff --git a/packages/load-tests/src/query/storeConfig.gql.js b/packages/load-tests/src/query/storeConfig.gql.js new file mode 100644 index 000000000..367c7886a --- /dev/null +++ b/packages/load-tests/src/query/storeConfig.gql.js @@ -0,0 +1,17 @@ +export const StoreConfigQuery = ` + query storeConfig { + storeConfig { + store_code, + default_title, + store_name, + default_display_currency_code, + locale, + header_logo_src, + logo_width, + logo_height, + logo_alt + } + } +`; + +export const StoreConfigQueryString = 'query=query%20storeConfig%20%7B%0A%20%20%20%20storeConfig%20%7B%0A%20%20%20%20%20%20%20%20store_code,%0A%20%20%20%20%20%20%20%20default_title,%0A%20%20%20%20%20%20%20%20store_name,%0A%20%20%20%20%20%20%20%20default_display_currency_code,%0A%20%20%20%20%20%20%20%20locale,%0A%20%20%20%20%20%20%20%20header_logo_src,%0A%20%20%20%20%20%20%20%20logo_width,%0A%20%20%20%20%20%20%20%20logo_height,%0A%20%20%20%20%20%20%20%20logo_alt%0A%20%20%20%20%7D%0A%20%20%7D&variables=%7B%7D&operationName=storeConfig'; diff --git a/packages/load-tests/src/tests/magento/addSimpleProductToCart.js b/packages/load-tests/src/tests/magento/addSimpleProductToCart.js new file mode 100644 index 000000000..9f6de3f65 --- /dev/null +++ b/packages/load-tests/src/tests/magento/addSimpleProductToCart.js @@ -0,0 +1,52 @@ +// Creator: k6 Browser Recorder 0.6.2 (+ handmade cleanups) +import { group, check } from 'k6'; +import http from 'k6/http'; + +import getHeaders from './utils/getHeaders.js'; +import { AddProductsToCart } from '../../mutation/addProductsToCart.js'; +import CreateEmptyGuestCart from './createEmptyGuestCart.js'; + +/** + * @type {import('k6/options').Options} + */ +export const options = { + vus: 150, + duration: '1m', +}; + +const { BASE_URL } = __ENV; +let response; + +if (BASE_URL === undefined) { + throw new Error('BASE_URL is not set'); +} + +export default function main() { + const params = { + headers: getHeaders(), + }; + + group('[Magento] addSimpleProductToGuestCart', () => { + const { cartId } = CreateEmptyGuestCart(); + + group('add a simple product to the cart', () => { + response = http.post( + `${BASE_URL}`, + JSON.stringify({ + query: AddProductsToCart, + variables: { + cartId, + cartItems: [{ quantity: Math.floor(Math.random() * 10), sku: '24-WG02' }], + }, + }), + params, + ); + + check(response, { + 'is status 200': (r) => r.status === 200, + 'does not have errors': (r) => !r.json().errors, + 'includes cart data': (r) => r.body.includes('addProductsToCart'), + }); + }); + }); +} diff --git a/packages/load-tests/src/tests/magento/createCustomer.js b/packages/load-tests/src/tests/magento/createCustomer.js new file mode 100644 index 000000000..b4d7c831a --- /dev/null +++ b/packages/load-tests/src/tests/magento/createCustomer.js @@ -0,0 +1,51 @@ +// Creator: k6 Browser Recorder 0.6.2 (+ handmade cleanups) +import { group, check } from 'k6'; +import http from 'k6/http'; + +import getHeaders from './utils/getHeaders.js'; +import { CreateCustomer } from '../../mutation/createCustomer.js'; +import { customerData } from './utils/customerData.js'; + +/** + * @type {import('k6/options').Options} + */ +export const options = { + vus: 150, + duration: '1m', +}; + +const { BASE_URL } = __ENV; +let response; + +if (BASE_URL === undefined) { + throw new Error('BASE_URL is not set'); +} + +export default function main() { + const params = { + headers: getHeaders(), + }; + + group('[Magento] create a customer account', () => { + response = http.post( + `${BASE_URL}`, + JSON.stringify({ + query: CreateCustomer, + variables: { + input: { + email: customerData.getEmail(), + password: 'Abcd123!', + firstname: 'Load', + lastname: 'Test', + }, + }, + }), + params, + ); + + check(response, { + 'is status 200': (r) => r.status === 200, + 'does not have errors': (r) => !r.json().errors, + }); + }); +} diff --git a/packages/load-tests/src/tests/magento/createEmptyGuestCart.js b/packages/load-tests/src/tests/magento/createEmptyGuestCart.js new file mode 100644 index 000000000..64e850e4c --- /dev/null +++ b/packages/load-tests/src/tests/magento/createEmptyGuestCart.js @@ -0,0 +1,46 @@ +// Creator: k6 Browser Recorder 0.6.2 (+ handmade cleanups) +import { sleep, group, check } from 'k6'; +import http from 'k6/http'; + +import jsonpath from 'https://jslib.k6.io/jsonpath/1.0.2/index.js'; +import getHeaders from './utils/getHeaders.js'; +import { CreateEmptyCartMutation } from '../../mutation/createEmptyCart.js'; + +/** + * @type {import('k6/options').Options} + */ +export const options = { + vus: 150, + duration: '1m', +}; + +const { BASE_URL } = __ENV; +const vars = {}; +let response; +if (BASE_URL === undefined) { + throw new Error('BASE_URL is not set'); +} + +export default function main() { + const params = { + headers: getHeaders(), + }; + + group('[Magento] create an empty cart', () => { + response = http.post( + `${BASE_URL}`, + JSON.stringify({ query: CreateEmptyCartMutation }), + params, + ); + sleep(0.2); + [vars.cartId] = jsonpath.query(response.json(), '$.data.createEmptyCart'); + + check(response, { + 'is status 200': (r) => r.status === 200, + 'does not have errors': (r) => !r.json().errors, + 'includes cart id': () => vars.cartId, + }); + }); + + return { cartId: vars.cartId }; +} diff --git a/packages/load-tests/src/tests/magento/storeConfig.js b/packages/load-tests/src/tests/magento/storeConfig.js new file mode 100644 index 000000000..1e1285d92 --- /dev/null +++ b/packages/load-tests/src/tests/magento/storeConfig.js @@ -0,0 +1,41 @@ +// Creator: k6 Browser Recorder 0.6.2 (+ handmade cleanups) +import { sleep, check, group } from 'k6'; +import http from 'k6/http'; + +import getHeaders from './utils/getHeaders.js'; +import { StoreConfigQueryString } from '../../query/storeConfig.gql.js'; + +/** + * @type {import('k6/options').Options} + */ +export const options = { + vus: 150, + duration: '1m', +}; + +const { BASE_URL } = __ENV; + +if (BASE_URL === undefined) { + throw new Error('BASE_URL is not set'); +} + +export default function main() { + const params = { + headers: getHeaders(), + }; + + group('[Magento] storeConfig', () => { + const response = http.get( + `${BASE_URL}/graphql?${StoreConfigQueryString}`, + params, + ); + + sleep(0.5); + + check(response, { + 'is status 200': (r) => r.status === 200, + 'does not have errors': (r) => !r.json().errors, + 'includes storeConfig data': (r) => r.body.includes('storeConfig'), + }); + }); +} diff --git a/packages/load-tests/src/tests/magento/utils/customerData.js b/packages/load-tests/src/tests/magento/utils/customerData.js new file mode 100644 index 000000000..59ed2defb --- /dev/null +++ b/packages/load-tests/src/tests/magento/utils/customerData.js @@ -0,0 +1,16 @@ +const chars = 'abcdefghijklmnopqrstuvwxyz1234567890'; + +export const getEmail = (length = 15) => { + let string = ''; + for (let i = 0; i < length; i++) { + string += chars[Math.floor(Math.random() * chars.length)]; + } + + return `${string}@gmail.com`; +}; + +export const customerData = { + getEmail, +}; + +export default customerData; diff --git a/packages/load-tests/src/tests/magento/utils/getHeaders.js b/packages/load-tests/src/tests/magento/utils/getHeaders.js new file mode 100644 index 000000000..7f21f7703 --- /dev/null +++ b/packages/load-tests/src/tests/magento/utils/getHeaders.js @@ -0,0 +1,5 @@ +const getHeaders = () => ({ + 'Content-Type': 'application/json', +}); + +export default getHeaders; diff --git a/packages/load-tests/src/tests/middleware/createCustomer.js b/packages/load-tests/src/tests/middleware/createCustomer.js new file mode 100644 index 000000000..c85a174ce --- /dev/null +++ b/packages/load-tests/src/tests/middleware/createCustomer.js @@ -0,0 +1,48 @@ +// Creator: k6 Browser Recorder 0.6.2 (+ handmade cleanups) +import { sleep, group, check } from 'k6'; +import http from 'k6/http'; + +import getHeaders from './utils/getHeaders.js'; +import { customerData } from './utils/customerData.js'; + +/** + * @type {import('k6/options').Options} + */ +export const options = { + vus: 150, + duration: '1m', +}; + +const { BASE_URL } = __ENV; +let response; + +if (BASE_URL === undefined) { + throw new Error('BASE_URL is not set'); +} + +export default function main() { + const params = { + headers: getHeaders(), + }; + + group('[Middleware] create a customer account', () => { + response = http.post( + `${BASE_URL}/magento/createCustomer`, + JSON.stringify([{ + email: customerData.getEmail(), + password: 'Abcd123!', + firstname: 'Load', + lastname: 'Test', + recaptchaToken: '12345', + }]), + params, + ); + + sleep(2); + + check(response, { + 'is status 200': (r) => r.status === 200, + 'does not have errors': (r) => !r.json().errors, + }); + }); +} diff --git a/packages/load-tests/src/tests/middleware/createEmptyGuestCart.js b/packages/load-tests/src/tests/middleware/createEmptyGuestCart.js new file mode 100644 index 000000000..6eae6a2f0 --- /dev/null +++ b/packages/load-tests/src/tests/middleware/createEmptyGuestCart.js @@ -0,0 +1,47 @@ +// Creator: k6 Browser Recorder 0.6.2 (+ handmade cleanups) +import { sleep, group, check } from 'k6'; +import http from 'k6/http'; + +import jsonpath from 'https://jslib.k6.io/jsonpath/1.0.2/index.js'; +import getHeaders from './utils/getHeaders.js'; + +/** + * @type {import('k6/options').Options} + */ +export const options = { + vus: 1, + duration: '1s', +}; + +const { BASE_URL } = __ENV; +const vars = {}; +let response; +if (BASE_URL === undefined) { + throw new Error('BASE_URL is not set'); +} + +export default function main() { + const params = { + headers: getHeaders(), + }; + + group('[Middleware] create an empty cart', () => { + response = http.post( + `${BASE_URL}/magento/createEmptyCart`, + '[]', + params, + ); + + sleep(1); + + [vars.cartId] = jsonpath.query(response.json(), '$.data.createEmptyCart'); + + check(response, { + 'is status 200': (r) => r.status === 200, + 'does not have errors': (r) => !r.json().errors, + 'includes cart id': () => vars.cartId, + }); + }); + + return { cartId: vars.cartId }; +} diff --git a/packages/load-tests/src/tests/middleware/storeConfig.js b/packages/load-tests/src/tests/middleware/storeConfig.js new file mode 100644 index 000000000..96a128a89 --- /dev/null +++ b/packages/load-tests/src/tests/middleware/storeConfig.js @@ -0,0 +1,40 @@ +// Creator: k6 Browser Recorder 0.6.2 (+ handmade cleanups) +import { sleep, check, group } from 'k6'; +import http from 'k6/http'; +import getHeaders from './utils/getHeaders.js'; + +/** + * @type {import('k6/options').Options} + */ +export const options = { + vus: 150, + duration: '1m', +}; + +const { BASE_URL } = __ENV; + +if (BASE_URL === undefined) { + throw new Error('BASE_URL is not set'); +} + +export default function main() { + const params = { + headers: getHeaders(), + }; + + group('[Middleware] storeConfig', () => { + const response = http.post( + `${BASE_URL}/git magento/customQuery`, + '[{"query":"\\n query storeConfig {\\n storeConfig {\\n store_code,\\n default_title,\\n store_name,\\n default_display_currency_code,\\n locale,\\n header_logo_src,\\n logo_width,\\n logo_height,\\n logo_alt\\n }\\n }\\n"}]', + params, + ); + + sleep(0.5); + + check(response, { + 'is status 200': (r) => r.status === 200, + 'does not have errors': (r) => !r.json().errors, + 'includes storeConfig data': (r) => r.body.includes('storeConfig'), + }); + }); +} diff --git a/packages/load-tests/src/tests/middleware/utils/customerData.js b/packages/load-tests/src/tests/middleware/utils/customerData.js new file mode 100644 index 000000000..59ed2defb --- /dev/null +++ b/packages/load-tests/src/tests/middleware/utils/customerData.js @@ -0,0 +1,16 @@ +const chars = 'abcdefghijklmnopqrstuvwxyz1234567890'; + +export const getEmail = (length = 15) => { + let string = ''; + for (let i = 0; i < length; i++) { + string += chars[Math.floor(Math.random() * chars.length)]; + } + + return `${string}@gmail.com`; +}; + +export const customerData = { + getEmail, +}; + +export default customerData; diff --git a/packages/load-tests/src/tests/middleware/utils/getHeaders.js b/packages/load-tests/src/tests/middleware/utils/getHeaders.js new file mode 100644 index 000000000..b70f2919c --- /dev/null +++ b/packages/load-tests/src/tests/middleware/utils/getHeaders.js @@ -0,0 +1,7 @@ +const getHeaders = () => ({ + 'content-type': 'application/json', + accept: 'application/json, text/plain, */*', + cookie: 'vsf-store=default; vsf-locale=default; vsf-currency=USD', +}); + +export default getHeaders; diff --git a/packages/load-tests/src/utils/getCookies.js b/packages/load-tests/src/utils/getCookies.js new file mode 100644 index 000000000..fefd5d14f --- /dev/null +++ b/packages/load-tests/src/utils/getCookies.js @@ -0,0 +1,7 @@ +const getCookies = () => ({ + 'vsf-store': 'default', + 'vsf-locale': 'default', + 'vsf-currency': 'USD', +}); + +export default getCookies; From 2d700df2a9617516838be75a70005aa7152c095b Mon Sep 17 00:00:00 2001 From: Bartosz Herba Date: Mon, 18 Jul 2022 10:01:49 +0200 Subject: [PATCH 07/24] fix: product review form offscreen issue --- .../catalog/product/components/ProductAddReviewForm.vue | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/theme/modules/catalog/product/components/ProductAddReviewForm.vue b/packages/theme/modules/catalog/product/components/ProductAddReviewForm.vue index 92bedc9b8..f04bc17fb 100644 --- a/packages/theme/modules/catalog/product/components/ProductAddReviewForm.vue +++ b/packages/theme/modules/catalog/product/components/ProductAddReviewForm.vue @@ -308,6 +308,12 @@ export default defineComponent({ margin-right: 0; margin-bottom: 0; } + textarea { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 100%; + } } } } From 821c16ceb293c450419546f36c6fdf2261a4868a Mon Sep 17 00:00:00 2001 From: Shankar Konar Date: Mon, 25 Jul 2022 14:17:57 +0530 Subject: [PATCH 08/24] fix: feedback changes --- .../modules/catalog/product/components/ProductAddReviewForm.vue | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/theme/modules/catalog/product/components/ProductAddReviewForm.vue b/packages/theme/modules/catalog/product/components/ProductAddReviewForm.vue index f04bc17fb..173aa0613 100644 --- a/packages/theme/modules/catalog/product/components/ProductAddReviewForm.vue +++ b/packages/theme/modules/catalog/product/components/ProductAddReviewForm.vue @@ -309,8 +309,6 @@ export default defineComponent({ margin-bottom: 0; } textarea { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; box-sizing: border-box; width: 100%; } From 9cb6f8f0a72da3693ac10652f89dee4ae205037b Mon Sep 17 00:00:00 2001 From: Shankar Konar Date: Fri, 22 Jul 2022 20:42:27 +0530 Subject: [PATCH 09/24] fix: command correction according to markshust script --- docs/installation-setup/configure-magento.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/installation-setup/configure-magento.md b/docs/installation-setup/configure-magento.md index f394b7eff..5fb5d71a2 100644 --- a/docs/installation-setup/configure-magento.md +++ b/docs/installation-setup/configure-magento.md @@ -70,11 +70,11 @@ For security reasons, Magento 2, by default, allows maximum GraphQL query comple To install the Magento 2 GraphQL Config module, run the following commands on your Magento installation: ```bash -composer require caravelx/module-graphql-config -php bin/magento module:enable Caravel_GraphQlConfig -php bin/magento setup:upgrade -php bin/magento setup:di:compile -php bin/magento setup:static-content:deploy +bin/composer require caravelx/module-graphql-config +bin/magento module:enable Caravel_GraphQlConfig +bin/magento setup:upgrade +bin/magento setup:di:compile +bin/magento setup:static-content:deploy -f ``` Then go to the admin panel, find the configuration panel of the `GraphQL CustomConfig` module, and set: From 62b1f1fab3cc166011e902171bd52bcbbcb1301d Mon Sep 17 00:00:00 2001 From: Bartosz Herba Date: Mon, 25 Jul 2022 13:45:21 +0200 Subject: [PATCH 10/24] test: browsing product load test --- .eslintignore | 1 + .../src/scenarios/browsingProduct.js | 298 ++++++++++++++ .../load-tests/src/scenarios/searchProduct.js | 370 ------------------ .../src/scenarios/utils/setDefaultCookies.js | 8 + 4 files changed, 307 insertions(+), 370 deletions(-) create mode 100644 packages/load-tests/src/scenarios/browsingProduct.js delete mode 100644 packages/load-tests/src/scenarios/searchProduct.js create mode 100644 packages/load-tests/src/scenarios/utils/setDefaultCookies.js diff --git a/.eslintignore b/.eslintignore index 0fc6ca764..2a24330f7 100755 --- a/.eslintignore +++ b/.eslintignore @@ -5,4 +5,5 @@ packages/api-client/server packages/composables packages/api-client/lib packages/theme/static/sw.js +packages/load-tests/* .eslintrc.js diff --git a/packages/load-tests/src/scenarios/browsingProduct.js b/packages/load-tests/src/scenarios/browsingProduct.js new file mode 100644 index 000000000..da6aa49f6 --- /dev/null +++ b/packages/load-tests/src/scenarios/browsingProduct.js @@ -0,0 +1,298 @@ +// Creator: k6 Browser Recorder 0.6.2 + +import { check, sleep, group } from 'k6'; +import http from 'k6/http'; + +import jsonpath from 'https://jslib.k6.io/jsonpath/1.0.2/index.js'; +import { setDefaultCookies } from './utils/setDefaultCookies.js'; + +export const options = { + vus: 200, + duration: '5m', + thresholds: { + http_req_failed: ['rate<0.01'], // http errors should be less than 1% + http_req_duration: ['p(80)<250'], // 80% of requests should be below 250ms + }, +}; + +const { BASE_URL } = __ENV; + +if (BASE_URL === undefined) { + throw new Error('BASE_URL is not set'); +} + +const headers = { + accept: 'application/json, text/plain, */*', + 'content-type': 'application/json', + 'upgrade-insecure-requests': '1', + 'sec-ch-ua': '".Not/A)Brand";v="99", "Google Chrome";v="103", "Chromium";v="103"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"macOS"', +}; + +const executeCommonChecks = (res) => { + check(res, { + 'is status 200': (r) => r.status === 200, + 'has no errors': (r) => !r.json().errors, + }); +}; + +export default function main() { + let res; + + const vars = {}; + setDefaultCookies(BASE_URL); + + group('Browsing for a product', () => { + group('Visit the homepage', () => { + res = http.get( + `${BASE_URL}/default`, + { + headers, + }, + ); + + check(res, { + 'is status 200': (r) => r.status === 200, + }); + + sleep(0.9); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + '[{"query":"\\n query storeConfig {\\n storeConfig {\\n store_code,\\n default_title,\\n store_name,\\n default_display_currency_code,\\n locale,\\n header_logo_src,\\n logo_width,\\n logo_height,\\n logo_alt\\n }\\n }\\n"}]', + { + headers, + }, + ); + + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + '[{"query":"\\n query getStoresAndCurrencies {\\n availableStores {\\n store_code\\n }\\n currency {\\n available_currency_codes\\n }\\n }\\n"}]', + { + headers, + }, + ); + + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + '[{"query":"\\n query getStoresAndCurrencies {\\n availableStores {\\n store_code\\n }\\n currency {\\n available_currency_codes\\n }\\n }\\n"}]', + { + headers, + }, + ); + + executeCommonChecks(res); + + sleep(2.2); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + '[{"query":"\\n query categoryList {\\n categories {\\n items {\\n ...CategoryFields\\n children {\\n ...CategoryFields\\n children {\\n ...CategoryFields\\n children {\\n ...CategoryFields\\n }\\n }\\n }\\n }\\n }\\n }\\n \\n fragment CategoryFields on CategoryTree {\\n is_anchor\\n name\\n position\\n product_count\\n uid\\n url_path\\n url_suffix\\n include_in_menu\\n }\\n\\n"}]', + { + headers, + }, + ); + + executeCommonChecks(res); + }); + + group('Go to the Men category page', () => { + res = http.post( + `${BASE_URL}/api/magento/route`, + '["/men.html",null]', + { + headers, + }, + ); + check(res, { + 'is status 200': (r) => r.status === 200, + }); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + '[{"query":"\\n query categoryList {\\n categories {\\n items {\\n ...CategoryFields\\n children {\\n ...CategoryFields\\n children {\\n ...CategoryFields\\n children {\\n ...CategoryFields\\n }\\n }\\n }\\n }\\n }\\n }\\n \\n fragment CategoryFields on CategoryTree {\\n is_anchor\\n name\\n position\\n product_count\\n uid\\n url_path\\n url_suffix\\n include_in_menu\\n }\\n\\n"}]', + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + '[{"query":"\\n query getCategoryContentData($filters: CategoryFilterInput) {\\n categoryList(filters: $filters) {\\n uid\\n display_mode\\n landing_page\\n cms_block {\\n identifier\\n content\\n }\\n }\\n }\\n","queryVariables":{"filters":{"category_uid":{"eq":"MTE="}}}}]', + { + headers, + }, + ); + executeCommonChecks(res); + + [vars.uid1] = jsonpath.query(res.json(), '$.data.categoryList[0].uid'); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + `[{"query":"\\n query getFacetData($search: String = \\"\\", $filter: ProductAttributeFilterInput, $pageSize: Int = 10, $currentPage: Int = 1, $sort: ProductAttributeSortInput) {\\n products(search: $search, filter: $filter, pageSize: $pageSize, currentPage: $currentPage, sort: $sort) {\\n items {\\n __typename\\n uid\\n sku\\n name\\n stock_status\\n only_x_left_in_stock\\n thumbnail {\\n url\\n position\\n disabled\\n label\\n }\\n url_key\\n url_rewrites {\\n url\\n }\\n price_range {\\n maximum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n minimum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n }\\n }\\n page_info {\\n current_page\\n page_size\\n total_pages\\n }\\n total_count\\n }\\n }\\n","queryVariables":{"pageSize":10,"search":"","filter":{"category_uid":{"in":["${vars.uid1}"]}},"sort":{},"currentPage":1}}]`, + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + `[{"query":"\\n query getProductFiltersByCategory($categoryIdFilter: FilterEqualTypeInput!) {\\n products(filter: { category_uid: $categoryIdFilter }) {\\n aggregations {\\n label\\n count\\n attribute_code\\n options {\\n count\\n label\\n value\\n __typename\\n }\\n position\\n __typename\\n }\\n __typename\\n }\\n }\\n","queryVariables":{"categoryIdFilter":{"eq":"${vars.uid1}"}}}]`, + { + headers, + }, + ); + executeCommonChecks(res); + sleep(3); + }); + + group('Sort by name ASC', () => { + res = http.post( + `${BASE_URL}/api/magento/route`, + '["/men.html",null]', + { + headers, + }, + ); + check(res, { + 'is status 200': (r) => r.status === 200, + }); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + `[{"query":"\\n query getCategoryContentData($filters: CategoryFilterInput) {\\n categoryList(filters: $filters) {\\n uid\\n display_mode\\n landing_page\\n cms_block {\\n identifier\\n content\\n }\\n }\\n }\\n","queryVariables":{"filters":{"category_uid":{"eq":"${vars.uid1}"}}}}]`, + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + `[{"query":"\\n query getFacetData($search: String = \\"\\", $filter: ProductAttributeFilterInput, $pageSize: Int = 10, $currentPage: Int = 1, $sort: ProductAttributeSortInput) {\\n products(search: $search, filter: $filter, pageSize: $pageSize, currentPage: $currentPage, sort: $sort) {\\n items {\\n __typename\\n uid\\n sku\\n name\\n stock_status\\n only_x_left_in_stock\\n thumbnail {\\n url\\n position\\n disabled\\n label\\n }\\n url_key\\n url_rewrites {\\n url\\n }\\n price_range {\\n maximum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n minimum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n }\\n }\\n page_info {\\n current_page\\n page_size\\n total_pages\\n }\\n total_count\\n }\\n }\\n","queryVariables":{"pageSize":10,"search":"","filter":{"category_uid":{"in":["${vars.uid1}"]}},"sort":{"name":"DESC"},"currentPage":1}}]`, + { + headers, + }, + ); + executeCommonChecks(res); + sleep(3); + }); + + group('Sort by Price ASC', () => { + res = http.post( + `${BASE_URL}/api/magento/route`, + '["/men.html",null]', + { + headers, + }, + ); + check(res, { + 'is status 200': (r) => r.status === 200, + }); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + `[{"query":"\\n query getCategoryContentData($filters: CategoryFilterInput) {\\n categoryList(filters: $filters) {\\n uid\\n display_mode\\n landing_page\\n cms_block {\\n identifier\\n content\\n }\\n }\\n }\\n","queryVariables":{"filters":{"category_uid":{"eq":"${vars.uid1}"}}}}]`, + { + headers, + }, + ); + executeCommonChecks(res); + [vars.uid2] = jsonpath.query(res.json(), '$.data.categoryList[0].uid'); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + `[{"query":"\\n query getFacetData($search: String = \\"\\", $filter: ProductAttributeFilterInput, $pageSize: Int = 10, $currentPage: Int = 1, $sort: ProductAttributeSortInput) {\\n products(search: $search, filter: $filter, pageSize: $pageSize, currentPage: $currentPage, sort: $sort) {\\n items {\\n __typename\\n uid\\n sku\\n name\\n stock_status\\n only_x_left_in_stock\\n thumbnail {\\n url\\n position\\n disabled\\n label\\n }\\n url_key\\n url_rewrites {\\n url\\n }\\n price_range {\\n maximum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n minimum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n }\\n }\\n page_info {\\n current_page\\n page_size\\n total_pages\\n }\\n total_count\\n }\\n }\\n","queryVariables":{"pageSize":10,"search":"","filter":{"category_uid":{"in":["${vars.uid1}"]}},"sort":{"price":"ASC"},"currentPage":1}}]`, + { + headers, + }, + ); + executeCommonChecks(res); + sleep(3); + }); + + group('Filter list by the price', () => { + res = http.post( + `${BASE_URL}/api/magento/route`, + '["/men.html",null]', + { + headers, + }, + ); + check(res, { + 'is status 200': (r) => r.status === 200, + }); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + `[{"query":"\\n query getCategoryContentData($filters: CategoryFilterInput) {\\n categoryList(filters: $filters) {\\n uid\\n display_mode\\n landing_page\\n cms_block {\\n identifier\\n content\\n }\\n }\\n }\\n","queryVariables":{"filters":{"category_uid":{"eq":"${vars.uid2}"}}}}]`, + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + `[{"query":"\\n query getFacetData($search: String = \\"\\", $filter: ProductAttributeFilterInput, $pageSize: Int = 10, $currentPage: Int = 1, $sort: ProductAttributeSortInput) {\\n products(search: $search, filter: $filter, pageSize: $pageSize, currentPage: $currentPage, sort: $sort) {\\n items {\\n __typename\\n uid\\n sku\\n name\\n stock_status\\n only_x_left_in_stock\\n thumbnail {\\n url\\n position\\n disabled\\n label\\n }\\n url_key\\n url_rewrites {\\n url\\n }\\n price_range {\\n maximum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n minimum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n }\\n }\\n page_info {\\n current_page\\n page_size\\n total_pages\\n }\\n total_count\\n }\\n }\\n","queryVariables":{"pageSize":10,"search":"","filter":{"price":{"from":40,"to":50},"category_uid":{"in":["${vars.uid2}"]}},"sort":{"price":"ASC"},"currentPage":1}}]`, + { + headers, + }, + ); + executeCommonChecks(res); + sleep(1.6); + }); + + group('Visit selected product', () => { + res = http.post( + `${BASE_URL}/api/magento/productDetail`, + '[{"filter":{"sku":{"eq":"MH06"}},"configurations":[]},{"productDetail":"productDetail"}]', + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + '[{"query":"\\n query getProductPriceBySku(\\n $filter: ProductAttributeFilterInput,\\n $configurations: [ID!]\\n ) {\\n products(filter: $filter) {\\n items {\\n price_range {\\n ...PriceRangeFields\\n }\\n\\n ... on ConfigurableProduct {\\n price_range {\\n maximum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n minimum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n }\\n\\n configurable_product_options_selection(configurableOptionValueUids: $configurations) {\\n options_available_for_selection {\\n attribute_code\\n option_value_uids\\n }\\n media_gallery {\\n disabled\\n label\\n position\\n url\\n }\\n variant {\\n uid\\n sku\\n name\\n price_range {\\n ...PriceRangeFields\\n }\\n }\\n }\\n }\\n\\n ... on BundleProduct {\\n items {\\n position\\n required\\n sku\\n title\\n type\\n uid\\n options {\\n can_change_quantity\\n is_default\\n position\\n uid\\n quantity\\n product {\\n uid\\n sku\\n name\\n price_range {\\n ...PriceRangeFields\\n }\\n }\\n }\\n }\\n }\\n\\n ... on GroupedProduct {\\n items {\\n position\\n qty\\n product {\\n uid\\n sku\\n name\\n stock_status\\n only_x_left_in_stock\\n price_range {\\n maximum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n minimum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n }\\n thumbnail {\\n url\\n position\\n disabled\\n label\\n }\\n }\\n }\\n }\\n\\n }\\n }\\n }\\n \\n fragment PriceRangeFields on PriceRange {\\n maximum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n minimum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n }\\n\\n","queryVariables":{"filter":{"sku":{"eq":"MH06"}},"configurations":[]}}]', + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/upsellProduct`, + '[{"filter":{"sku":{"eq":"MH06"}}},null]', + { + headers, + }, + ); + + res = http.post( + `${BASE_URL}/api/magento/relatedProduct`, + '[{"filter":{"sku":{"eq":"MH06"}}},null]', + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/productReviewRatingsMetadata`, + '[null]', + { + headers, + }, + ); + }); + }); +} diff --git a/packages/load-tests/src/scenarios/searchProduct.js b/packages/load-tests/src/scenarios/searchProduct.js deleted file mode 100644 index c7384d1fc..000000000 --- a/packages/load-tests/src/scenarios/searchProduct.js +++ /dev/null @@ -1,370 +0,0 @@ -// Creator: k6 Browser Recorder 0.6.2 (+ handmade cleanups) -import { sleep, group } from 'k6'; -import http from 'k6/http'; - -import jsonpath from 'https://jslib.k6.io/jsonpath/1.0.2/index.js'; - -/** - * @type {import('k6/options').Options} - */ -export const options = { - vus: 200, - duration: '5m', - ext: { - loadimpact: { - name: 'Search products', - note: 'Guest user browsing through a product', - // eslint-disable-next-line unicorn/numeric-separators-style - projectID: 3591701, - }, - }, -}; - -const { BASE_URL } = __ENV; - -if (BASE_URL === undefined) { - throw new Error('BASE_URL is not set'); -} - -export default function main() { - let response; - - const vars = {}; - - group(`page_1 - ${BASE_URL}/default`, () => { - response = http.get(`${BASE_URL}/default`); - sleep(0.5); - - response = http.post( - `${BASE_URL}/api/magento/customQuery`, - `[{"query":" - query storeConfig { - storeConfig { - store_code, - default_title, - store_name, - default_display_currency_code, - locale, - header_logo_src, - logo_width, - logo_height, - logo_alt - } - } -"}]`, - ); - - response = http.post( - `${BASE_URL}/api/magento/customQuery`, - `[{"query":" - query getStoresAndCurrencies { - availableStores { - store_code - } - currency { - available_currency_codes - } - } -"}]`, - ); - sleep(2.6); - - response = http.post( - `${BASE_URL}/api/magento/route`, - '["/women/tops-women/jackets-women.html",null]', - ); - - [vars.uid1] = jsonpath.query(response.json(), '$.data.route.uid'); - - response = http.post( - `${BASE_URL}/api/magento/customQuery`, - `[{"query":" - query categoryList { - categories { - items { - ...CategoryFields - children { - ...CategoryFields - children { - ...CategoryFields - children { - ...CategoryFields - } - } - } - } - } - } - - fragment CategoryFields on CategoryTree { - is_anchor - name - position - product_count - uid - url_path - url_suffix - include_in_menu - } - -"}]`, - ); - - response = http.post( - `${BASE_URL}/api/magento/customQuery`, - `[{"query":" - query getCategoryContentData($filters: CategoryFilterInput) { - categoryList(filters: $filters) { - uid - display_mode - landing_page - cms_block { - identifier - content - } - } - } -","queryVariables":{"filters":{"category_uid":{"eq":"${vars.uid1}"}}}}]`, - ); - - response = http.post( - `${BASE_URL}/api/magento/customQuery`, - `[{"query":" - query getFacetData($search: String = \\"\\", $filter: ProductAttributeFilterInput, $pageSize: Int = 10, $currentPage: Int = 1, $sort: ProductAttributeSortInput) { - products(search: $search, filter: $filter, pageSize: $pageSize, currentPage: $currentPage, sort: $sort) { - items { - __typename - uid - sku - name - stock_status - only_x_left_in_stock - thumbnail { - url - position - disabled - label - } - url_key - url_rewrites { - url - } - price_range { - maximum_price { - final_price { - currency - value - } - regular_price { - currency - value - } - } - minimum_price { - final_price { - currency - value - } - regular_price { - currency - value - } - } - } - } - page_info { - current_page - page_size - total_pages - } - total_count - } - } -","queryVariables":{"pageSize":10,"search":"","filter":{"category_uid":{"in":["${vars.uid1}"]}},"sort":{},"currentPage":1}}]`, - ); - sleep(0.5); - - response = http.post( - `${BASE_URL}/api/magento/customQuery`, - `[{"query":" - query getProductFiltersByCategory($categoryIdFilter: FilterEqualTypeInput!) { - products(filter: { category_uid: $categoryIdFilter }) { - aggregations { - label - count - attribute_code - options { - count - label - value - __typename - } - position - __typename - } - __typename - } - } -","queryVariables":{"categoryIdFilter":{"eq":"${vars.uid1}"}}}]`, - ); - sleep(1.8); - - response = http.post( - `${BASE_URL}/api/magento/productDetail`, - '[{"filter":{"sku":{"eq":"WJ12"}},"configurations":[]},{"productDetail":"productDetail"}]', - ); - - response = http.post( - `${BASE_URL}/api/magento/customQuery`, - `[{"query":" - query getProductPriceBySku($sku: String) { - products(filter: {sku: {eq: $sku}}) { - items { - price_range { - ...PriceRangeFields - } - - ... on BundleProduct { - items { - position - required - sku - title - type - uid - options { - can_change_quantity - is_default - position - uid - quantity - product { - uid - sku - name - price_range { - maximum_price { - final_price { - currency - value - } - regular_price { - currency - value - } - } - minimum_price { - final_price { - currency - value - } - regular_price { - currency - value - } - } - } - } - } - } - } - - ... on GroupedProduct { - items { - position - qty - product { - uid - sku - name - stock_status - only_x_left_in_stock - price_range { - maximum_price { - final_price { - currency - value - } - regular_price { - currency - value - } - } - minimum_price { - final_price { - currency - value - } - regular_price { - currency - value - } - } - } - thumbnail { - url - position - disabled - label - } - } - } - } - - } - } - } - - fragment PriceRangeFields on PriceRange { - maximum_price { - final_price { - currency - value - } - regular_price { - currency - value - } - } - minimum_price { - final_price { - currency - value - } - regular_price { - currency - value - } - } - } - -","queryVariables":{"sku":"WJ12"}}]`, - ); - - response = http.post( - `${BASE_URL}/api/magento/upsellProduct`, - '[{"filter":{"sku":{"eq":"WJ12"}}},null]', - ); - - response = http.post( - `${BASE_URL}/api/magento/relatedProduct`, - '[{"filter":{"sku":{"eq":"WJ12"}}},null]', - ); - - response = http.post( - `${BASE_URL}/api/magento/productReviewRatingsMetadata`, - '[null]', - ); - sleep(2.3); - - response = http.post( - `${BASE_URL}/api/magento/productReview`, - '[{"filter":{"sku":{"eq":"WJ12"}}},null]', - ); - - response = http.post( - `${BASE_URL}/api/magento/productReviewRatingsMetadata`, - '[null]', - ); - }); -} diff --git a/packages/load-tests/src/scenarios/utils/setDefaultCookies.js b/packages/load-tests/src/scenarios/utils/setDefaultCookies.js new file mode 100644 index 000000000..328461ac0 --- /dev/null +++ b/packages/load-tests/src/scenarios/utils/setDefaultCookies.js @@ -0,0 +1,8 @@ +import http from 'k6/http'; + +export const setDefaultCookies = (url) => { + const jar = http.cookieJar(); + jar.set(url, 'vsf-store', 'default'); + jar.set(url, 'vsf-locale', 'default'); + jar.set(url, 'vsf-currency', 'USD'); +}; From 849bc6664bebf613e65f178f0f1df507eb0c7610 Mon Sep 17 00:00:00 2001 From: Bartosz Herba Date: Tue, 26 Jul 2022 08:43:58 +0200 Subject: [PATCH 11/24] test: add search product load tests --- .eslintignore | 1 - .../{ => tests}/scenarios/browsingProduct.js | 3 +- .../src/tests/scenarios/searchProduct.js | 125 ++++++++++++++++++ .../scenarios/utils/setDefaultCookies.js | 1 + 4 files changed, 127 insertions(+), 3 deletions(-) rename packages/load-tests/src/{ => tests}/scenarios/browsingProduct.js (99%) create mode 100644 packages/load-tests/src/tests/scenarios/searchProduct.js rename packages/load-tests/src/{ => tests}/scenarios/utils/setDefaultCookies.js (91%) diff --git a/.eslintignore b/.eslintignore index 2a24330f7..0fc6ca764 100755 --- a/.eslintignore +++ b/.eslintignore @@ -5,5 +5,4 @@ packages/api-client/server packages/composables packages/api-client/lib packages/theme/static/sw.js -packages/load-tests/* .eslintrc.js diff --git a/packages/load-tests/src/scenarios/browsingProduct.js b/packages/load-tests/src/tests/scenarios/browsingProduct.js similarity index 99% rename from packages/load-tests/src/scenarios/browsingProduct.js rename to packages/load-tests/src/tests/scenarios/browsingProduct.js index da6aa49f6..264bb2f0b 100644 --- a/packages/load-tests/src/scenarios/browsingProduct.js +++ b/packages/load-tests/src/tests/scenarios/browsingProduct.js @@ -42,8 +42,7 @@ export default function main() { const vars = {}; setDefaultCookies(BASE_URL); - - group('Browsing for a product', () => { + group(`Browsing for a product - page ${BASE_URL}`, () => { group('Visit the homepage', () => { res = http.get( `${BASE_URL}/default`, diff --git a/packages/load-tests/src/tests/scenarios/searchProduct.js b/packages/load-tests/src/tests/scenarios/searchProduct.js new file mode 100644 index 000000000..80fba2341 --- /dev/null +++ b/packages/load-tests/src/tests/scenarios/searchProduct.js @@ -0,0 +1,125 @@ +// Creator: k6 Browser Recorder 0.6.2 + +import { check, sleep, group } from 'k6'; +import http from 'k6/http'; +import { setDefaultCookies } from './utils/setDefaultCookies.js'; + +export const options = { + vus: 200, + duration: '5m', + thresholds: { + http_req_failed: ['rate<0.01'], // http errors should be less than 1% + http_req_duration: ['p(80)<250'], // 80% of requests should be below 250ms + }, +}; + +const { BASE_URL } = __ENV; + +if (BASE_URL === undefined) { + throw new Error('BASE_URL is not set'); +} + +const headers = { + accept: 'application/json, text/plain, */*', + 'content-type': 'application/json', + 'upgrade-insecure-requests': '1', + 'sec-ch-ua': '".Not/A)Brand";v="99", "Google Chrome";v="103", "Chromium";v="103"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"macOS"', +}; + +const executeCommonChecks = (res) => { + check(res, { + 'is status 200': (r) => r.status === 200, + 'has no errors': (r) => !r.json().errors, + }); +}; + +export default function main() { + let res; + setDefaultCookies(BASE_URL); + + group( + `Search Product - page ${BASE_URL}`, + () => { + group('Visit the homepage', () => { + res = http.get( + `${BASE_URL}/default`, + { headers }, + ); + check(res, { + 'is status 200': (r) => r.status === 200, + }); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + '[{"query":"\\n query storeConfig {\\n storeConfig {\\n store_code,\\n default_title,\\n store_name,\\n default_display_currency_code,\\n locale,\\n header_logo_src,\\n logo_width,\\n logo_height,\\n logo_alt\\n }\\n }\\n"}]', + { headers }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + '[{"query":"\\n query getStoresAndCurrencies {\\n availableStores {\\n store_code\\n }\\n currency {\\n available_currency_codes\\n }\\n }\\n"}]', + { headers }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + '[{"query":"\\n query getStoresAndCurrencies {\\n availableStores {\\n store_code\\n }\\n currency {\\n available_currency_codes\\n }\\n }\\n"}]', + { headers }, + ); + executeCommonChecks(res); + sleep(4.1); + }); + + group('Search for a product', () => { + res = http.post( + `${BASE_URL}/api/magento/products`, + '[{"pageSize":12,"search":"erika"},{"products":"products"}]', + { headers }, + ); + executeCommonChecks(res); + sleep(1.9); + }); + + group('Visit the found product', () => { + res = http.post( + `${BASE_URL}/api/magento/productDetail`, + '[{"filter":{"sku":{"eq":"WSH12"}},"configurations":[]},{"productDetail":"productDetail"}]', + { headers }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + '[{"query":"\\n query getProductPriceBySku(\\n $filter: ProductAttributeFilterInput,\\n $configurations: [ID!]\\n ) {\\n products(filter: $filter) {\\n items {\\n price_range {\\n ...PriceRangeFields\\n }\\n\\n ... on ConfigurableProduct {\\n price_range {\\n maximum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n minimum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n }\\n\\n configurable_product_options_selection(configurableOptionValueUids: $configurations) {\\n options_available_for_selection {\\n attribute_code\\n option_value_uids\\n }\\n media_gallery {\\n disabled\\n label\\n position\\n url\\n }\\n variant {\\n uid\\n sku\\n name\\n price_range {\\n ...PriceRangeFields\\n }\\n }\\n }\\n }\\n\\n ... on BundleProduct {\\n items {\\n position\\n required\\n sku\\n title\\n type\\n uid\\n options {\\n can_change_quantity\\n is_default\\n position\\n uid\\n quantity\\n product {\\n uid\\n sku\\n name\\n price_range {\\n ...PriceRangeFields\\n }\\n }\\n }\\n }\\n }\\n\\n ... on GroupedProduct {\\n items {\\n position\\n qty\\n product {\\n uid\\n sku\\n name\\n stock_status\\n only_x_left_in_stock\\n price_range {\\n maximum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n minimum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n }\\n thumbnail {\\n url\\n position\\n disabled\\n label\\n }\\n }\\n }\\n }\\n\\n }\\n }\\n }\\n \\n fragment PriceRangeFields on PriceRange {\\n maximum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n minimum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n }\\n\\n","queryVariables":{"filter":{"sku":{"eq":"WSH12"}},"configurations":[]}}]', + { headers }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/relatedProduct`, + '[{"filter":{"sku":{"eq":"WSH12"}}},null]', + { headers }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/upsellProduct`, + '[{"filter":{"sku":{"eq":"WSH12"}}},null]', + { headers }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/productReviewRatingsMetadata`, + '[null]', + { headers }, + ); + executeCommonChecks(res); + }); + }, + ); +} diff --git a/packages/load-tests/src/scenarios/utils/setDefaultCookies.js b/packages/load-tests/src/tests/scenarios/utils/setDefaultCookies.js similarity index 91% rename from packages/load-tests/src/scenarios/utils/setDefaultCookies.js rename to packages/load-tests/src/tests/scenarios/utils/setDefaultCookies.js index 328461ac0..2b143868d 100644 --- a/packages/load-tests/src/scenarios/utils/setDefaultCookies.js +++ b/packages/load-tests/src/tests/scenarios/utils/setDefaultCookies.js @@ -1,3 +1,4 @@ +/* eslint-disable */ import http from 'k6/http'; export const setDefaultCookies = (url) => { From 51906bfe1aa922bcd6e0a2597da179a28576a46c Mon Sep 17 00:00:00 2001 From: Bartosz Herba Date: Tue, 26 Jul 2022 11:35:53 +0200 Subject: [PATCH 12/24] test: add buy product as a guest user load test --- .../src/tests/magento/createCustomer.js | 2 +- .../tests/scenarios/buyProductAsAGuestUser.js | 448 ++++++++++++++++++ .../{tests/magento => }/utils/customerData.js | 0 3 files changed, 449 insertions(+), 1 deletion(-) create mode 100644 packages/load-tests/src/tests/scenarios/buyProductAsAGuestUser.js rename packages/load-tests/src/{tests/magento => }/utils/customerData.js (100%) diff --git a/packages/load-tests/src/tests/magento/createCustomer.js b/packages/load-tests/src/tests/magento/createCustomer.js index b4d7c831a..a382e3ab9 100644 --- a/packages/load-tests/src/tests/magento/createCustomer.js +++ b/packages/load-tests/src/tests/magento/createCustomer.js @@ -4,7 +4,7 @@ import http from 'k6/http'; import getHeaders from './utils/getHeaders.js'; import { CreateCustomer } from '../../mutation/createCustomer.js'; -import { customerData } from './utils/customerData.js'; +import { customerData } from '../../utils/customerData.js'; /** * @type {import('k6/options').Options} diff --git a/packages/load-tests/src/tests/scenarios/buyProductAsAGuestUser.js b/packages/load-tests/src/tests/scenarios/buyProductAsAGuestUser.js new file mode 100644 index 000000000..bf9437d8b --- /dev/null +++ b/packages/load-tests/src/tests/scenarios/buyProductAsAGuestUser.js @@ -0,0 +1,448 @@ +// Creator: k6 Browser Recorder 0.6.2 +import { check, sleep, group } from 'k6'; +import http from 'k6/http'; +import jsonpath from 'https://jslib.k6.io/jsonpath/1.0.2/index.js'; +import { setDefaultCookies } from './utils/setDefaultCookies.js'; + +export const options = { + vus: 200, + duration: '5m', + thresholds: { + http_req_failed: ['rate<0.01'], // http errors should be less than 1% + http_req_duration: ['p(80)<250'], // 80% of requests should be below 250ms + }, +}; + +const { BASE_URL } = __ENV; + +if (BASE_URL === undefined) { + throw new Error('BASE_URL is not set'); +} + +const headers = { + accept: 'application/json, text/plain, */*', + 'content-type': 'application/json', + 'upgrade-insecure-requests': '1', + 'sec-ch-ua': '".Not/A)Brand";v="99", "Google Chrome";v="103", "Chromium";v="103"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"macOS"', +}; + +const executeCommonChecks = (res) => { + check(res, { + 'is status 200': (r) => r.status === 200, + 'has no errors': (r) => !r.json().errors, + }); +}; + +export default function main() { + let res; + + const vars = {}; + setDefaultCookies(BASE_URL); + + group(`Buy product as a guest user - page ${BASE_URL}`, () => { + group('Visit the homepage', () => { + res = http.get( + `${BASE_URL}/default`, + { + headers, + }, + ); + sleep(0.8); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + '[{"query":"\\n query storeConfig {\\n storeConfig {\\n store_code,\\n default_title,\\n store_name,\\n default_display_currency_code,\\n locale,\\n header_logo_src,\\n logo_width,\\n logo_height,\\n logo_alt\\n }\\n }\\n"}]', + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + '[{"query":"\\n query getStoresAndCurrencies {\\n availableStores {\\n store_code\\n }\\n currency {\\n available_currency_codes\\n }\\n }\\n"}]', + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + '[{"query":"\\n query getStoresAndCurrencies {\\n availableStores {\\n store_code\\n }\\n currency {\\n available_currency_codes\\n }\\n }\\n"}]', + { + headers, + }, + ); + executeCommonChecks(res); + sleep(2.7); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + '[{"query":"\\n query categoryList {\\n categories {\\n items {\\n ...CategoryFields\\n children {\\n ...CategoryFields\\n children {\\n ...CategoryFields\\n children {\\n ...CategoryFields\\n }\\n }\\n }\\n }\\n }\\n }\\n \\n fragment CategoryFields on CategoryTree {\\n is_anchor\\n name\\n position\\n product_count\\n uid\\n url_path\\n url_suffix\\n include_in_menu\\n }\\n\\n"}]', + { + headers, + }, + ); + executeCommonChecks(res); + }); + + group('Select the a category', () => { + res = http.post( + `${BASE_URL}/api/magento/route`, + '["/women.html",null]', + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + '[{"query":"\\n query categoryList {\\n categories {\\n items {\\n ...CategoryFields\\n children {\\n ...CategoryFields\\n children {\\n ...CategoryFields\\n children {\\n ...CategoryFields\\n }\\n }\\n }\\n }\\n }\\n }\\n \\n fragment CategoryFields on CategoryTree {\\n is_anchor\\n name\\n position\\n product_count\\n uid\\n url_path\\n url_suffix\\n include_in_menu\\n }\\n\\n"}]', + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + '[{"query":"\\n query getCategoryContentData($filters: CategoryFilterInput) {\\n categoryList(filters: $filters) {\\n uid\\n display_mode\\n landing_page\\n cms_block {\\n identifier\\n content\\n }\\n }\\n }\\n","queryVariables":{"filters":{"category_uid":{"eq":"MjA="}}}}]', + { + headers, + }, + ); + executeCommonChecks(res); + [vars.uid1] = jsonpath.query(res.json(), '$.data.categoryList[0].uid'); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + `[{"query":"\\n query getFacetData($search: String = \\"\\", $filter: ProductAttributeFilterInput, $pageSize: Int = 10, $currentPage: Int = 1, $sort: ProductAttributeSortInput) {\\n products(search: $search, filter: $filter, pageSize: $pageSize, currentPage: $currentPage, sort: $sort) {\\n items {\\n __typename\\n uid\\n sku\\n name\\n stock_status\\n only_x_left_in_stock\\n thumbnail {\\n url\\n position\\n disabled\\n label\\n }\\n url_key\\n url_rewrites {\\n url\\n }\\n price_range {\\n maximum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n minimum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n }\\n }\\n page_info {\\n current_page\\n page_size\\n total_pages\\n }\\n total_count\\n }\\n }\\n","queryVariables":{"pageSize":10,"search":"","filter":{"category_uid":{"in":["${vars.uid1}"]}},"sort":{},"currentPage":1}}]`, + { + headers, + }, + ); + executeCommonChecks(res); + sleep(0.5); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + `[{"query":"\\n query getProductFiltersByCategory($categoryIdFilter: FilterEqualTypeInput!) {\\n products(filter: { category_uid: $categoryIdFilter }) {\\n aggregations {\\n label\\n count\\n attribute_code\\n options {\\n count\\n label\\n value\\n __typename\\n }\\n position\\n __typename\\n }\\n __typename\\n }\\n }\\n","queryVariables":{"categoryIdFilter":{"eq":"${vars.uid1}"}}}]`, + { + headers, + }, + ); + executeCommonChecks(res); + sleep(2.1); + }); + + group('configure the product', () => { + res = http.post( + `${BASE_URL}/api/magento/productDetail`, + '[{"filter":{"sku":{"eq":"WSH11"}},"configurations":[]},{"productDetail":"productDetail"}]', + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + '[{"query":"\\n query getProductPriceBySku(\\n $filter: ProductAttributeFilterInput,\\n $configurations: [ID!]\\n ) {\\n products(filter: $filter) {\\n items {\\n price_range {\\n ...PriceRangeFields\\n }\\n\\n ... on ConfigurableProduct {\\n price_range {\\n maximum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n minimum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n }\\n\\n configurable_product_options_selection(configurableOptionValueUids: $configurations) {\\n options_available_for_selection {\\n attribute_code\\n option_value_uids\\n }\\n media_gallery {\\n disabled\\n label\\n position\\n url\\n }\\n variant {\\n uid\\n sku\\n name\\n price_range {\\n ...PriceRangeFields\\n }\\n }\\n }\\n }\\n\\n ... on BundleProduct {\\n items {\\n position\\n required\\n sku\\n title\\n type\\n uid\\n options {\\n can_change_quantity\\n is_default\\n position\\n uid\\n quantity\\n product {\\n uid\\n sku\\n name\\n price_range {\\n ...PriceRangeFields\\n }\\n }\\n }\\n }\\n }\\n\\n ... on GroupedProduct {\\n items {\\n position\\n qty\\n product {\\n uid\\n sku\\n name\\n stock_status\\n only_x_left_in_stock\\n price_range {\\n maximum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n minimum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n }\\n thumbnail {\\n url\\n position\\n disabled\\n label\\n }\\n }\\n }\\n }\\n\\n }\\n }\\n }\\n \\n fragment PriceRangeFields on PriceRange {\\n maximum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n minimum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n }\\n\\n","queryVariables":{"filter":{"sku":{"eq":"WSH11"}},"configurations":[]}}]', + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/upsellProduct`, + '[{"filter":{"sku":{"eq":"WSH11"}}},null]', + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/relatedProduct`, + '[{"filter":{"sku":{"eq":"WSH11"}}},null]', + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/productReviewRatingsMetadata`, + '[null]', + { + headers, + }, + ); + executeCommonChecks(res); + sleep(1.1); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + '[{"query":"\\n query getProductPriceBySku(\\n $filter: ProductAttributeFilterInput,\\n $configurations: [ID!]\\n ) {\\n products(filter: $filter) {\\n items {\\n price_range {\\n ...PriceRangeFields\\n }\\n\\n ... on ConfigurableProduct {\\n price_range {\\n maximum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n minimum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n }\\n\\n configurable_product_options_selection(configurableOptionValueUids: $configurations) {\\n options_available_for_selection {\\n attribute_code\\n option_value_uids\\n }\\n media_gallery {\\n disabled\\n label\\n position\\n url\\n }\\n variant {\\n uid\\n sku\\n name\\n price_range {\\n ...PriceRangeFields\\n }\\n }\\n }\\n }\\n\\n ... on BundleProduct {\\n items {\\n position\\n required\\n sku\\n title\\n type\\n uid\\n options {\\n can_change_quantity\\n is_default\\n position\\n uid\\n quantity\\n product {\\n uid\\n sku\\n name\\n price_range {\\n ...PriceRangeFields\\n }\\n }\\n }\\n }\\n }\\n\\n ... on GroupedProduct {\\n items {\\n position\\n qty\\n product {\\n uid\\n sku\\n name\\n stock_status\\n only_x_left_in_stock\\n price_range {\\n maximum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n minimum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n }\\n thumbnail {\\n url\\n position\\n disabled\\n label\\n }\\n }\\n }\\n }\\n\\n }\\n }\\n }\\n \\n fragment PriceRangeFields on PriceRange {\\n maximum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n minimum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n }\\n\\n","queryVariables":{"filter":{"sku":{"eq":"WSH11"}},"configurations":["Y29uZmlndXJhYmxlLzkzLzU4"]}}]', + { + headers, + }, + ); + executeCommonChecks(res); + + [vars.index01] = jsonpath.query( + res.json(), + '$.data.products.items[0].configurable_product_options_selection.options_available_for_selection[1].option_value_uids[0]', + ); + + [vars.index11] = jsonpath.query( + res.json(), + '$.data.products.items[0].configurable_product_options_selection.options_available_for_selection[0].option_value_uids[1]', + ); + + sleep(1.6); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + `[{"query":"\\n query getProductPriceBySku(\\n $filter: ProductAttributeFilterInput,\\n $configurations: [ID!]\\n ) {\\n products(filter: $filter) {\\n items {\\n price_range {\\n ...PriceRangeFields\\n }\\n\\n ... on ConfigurableProduct {\\n price_range {\\n maximum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n minimum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n }\\n\\n configurable_product_options_selection(configurableOptionValueUids: $configurations) {\\n options_available_for_selection {\\n attribute_code\\n option_value_uids\\n }\\n media_gallery {\\n disabled\\n label\\n position\\n url\\n }\\n variant {\\n uid\\n sku\\n name\\n price_range {\\n ...PriceRangeFields\\n }\\n }\\n }\\n }\\n\\n ... on BundleProduct {\\n items {\\n position\\n required\\n sku\\n title\\n type\\n uid\\n options {\\n can_change_quantity\\n is_default\\n position\\n uid\\n quantity\\n product {\\n uid\\n sku\\n name\\n price_range {\\n ...PriceRangeFields\\n }\\n }\\n }\\n }\\n }\\n\\n ... on GroupedProduct {\\n items {\\n position\\n qty\\n product {\\n uid\\n sku\\n name\\n stock_status\\n only_x_left_in_stock\\n price_range {\\n maximum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n minimum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n }\\n thumbnail {\\n url\\n position\\n disabled\\n label\\n }\\n }\\n }\\n }\\n\\n }\\n }\\n }\\n \\n fragment PriceRangeFields on PriceRange {\\n maximum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n minimum_price {\\n final_price {\\n currency\\n value\\n }\\n regular_price {\\n currency\\n value\\n }\\n }\\n }\\n\\n","queryVariables":{"filter":{"sku":{"eq":"WSH11"}},"configurations":["${vars.index01}","${vars.index11}"]}}]`, + { + headers, + }, + ); + executeCommonChecks(res); + sleep(0.5); + }); + + group('Add product to the cart', () => { + res = http.post( + `${BASE_URL}/api/magento/createEmptyCart`, + '[]', + { + headers, + }, + ); + executeCommonChecks(res); + + [vars.cart_id] = jsonpath.query( + res.json(), + '$.data.createEmptyCart', + ); + + res = http.post( + `${BASE_URL}/api/magento/cart`, + `["${vars.cart_id}"]`, + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/addProductsToCart`, + `[{"cartId":"${vars.cart_id}","cartItems":[{"quantity":1,"sku":"WSH11","selected_options":["${vars.index01}","${vars.index11}"]}]},null]`, + { + headers, + }, + ); + executeCommonChecks(res); + sleep(3); + }); + + group('Go through the checkout', () => { + res = http.post( + `${BASE_URL}/api/magento/cart`, + `["${vars.cart_id}"]`, + { + headers, + }, + ); + executeCommonChecks(res); + sleep(5.3); + + res = http.post( + `${BASE_URL}/api/magento/setGuestEmailOnCart`, + `[{"email":"john.loadtest@gmail.com","cart_id":"${vars.cart_id}"},null]`, + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/cart`, + `["${vars.cart_id}"]`, + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/cart`, + `["${vars.cart_id}"]`, + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/countries`, + '[]', + { + headers, + }, + ); + executeCommonChecks(res); + sleep(2.2); + + res = http.post( + `${BASE_URL}/api/magento/country`, + '["PL",null]', + { + headers, + }, + ); + executeCommonChecks(res); + sleep(3.9); + + res = http.post( + `${BASE_URL}/api/magento/setShippingAddressesOnCart`, + `[{"cart_id":"${vars.cart_id}","shipping_addresses":[{"address":{"firstname":"john","lastname":"LoadTest","street":["Load Test 11","11"],"city":"Load Test","region":"PL-10","country_code":"PL","postcode":"11-111","telephone":"123123123","save_in_address_book":false}}]}]`, + { + headers, + }, + ); + executeCommonChecks(res); + sleep(1.6); + + res = http.post( + `${BASE_URL}/api/magento/setShippingMethodsOnCart`, + `[{"cart_id":"${vars.cart_id}","shipping_methods":[{"carrier_code":"flatrate","method_code":"flatrate"}]},null]`, + { + headers, + }, + ); + executeCommonChecks(res); + sleep(1.3); + + res = http.post( + `${BASE_URL}/api/magento/cart`, + `["${vars.cart_id}"]`, + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/cart`, + `["${vars.cart_id}"]`, + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/countries`, + '[]', + { + headers, + }, + ); + executeCommonChecks(res); + sleep(1.7); + + res = http.post( + `${BASE_URL}/api/magento/country`, + '["PL",null]', + { + headers, + }, + ); + + res = http.post( + `${BASE_URL}/api/magento/country`, + '["PL",null]', + { + headers, + }, + ); + executeCommonChecks(res); + sleep(1.4); + + res = http.post( + `${BASE_URL}/api/magento/setBillingAddressOnCart`, + `[{"cart_id":"${vars.cart_id}","billing_address":{"address":{"firstname":"john","lastname":"LoadTest","street":["Load Test 11","11"],"city":"Load Test","region":"PL-10","country_code":"PL","postcode":"11-111","telephone":"123123123","save_in_address_book":false},"same_as_shipping":true}},null]`, + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/setShippingMethodsOnCart`, + `[{"cart_id":"${vars.cart_id}","shipping_methods":[{"carrier_code":"flatrate","method_code":"flatrate"}]},null]`, + { + headers, + }, + ); + executeCommonChecks(res); + sleep(0.5); + + res = http.post( + `${BASE_URL}/api/magento/cart`, + `["${vars.cart_id}"]`, + { + headers, + }, + ); + + res = http.post( + `${BASE_URL}/api/magento/cart`, + `["${vars.cart_id}"]`, + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/getAvailablePaymentMethods`, + `[{"cartId":"${vars.cart_id}"},null]`, + { + headers, + }, + ); + executeCommonChecks(res); + sleep(1.2); + + res = http.post( + `${BASE_URL}/api/magento/setPaymentMethodOnCart`, + `[{"cart_id":"${vars.cart_id}","payment_method":{"code":"checkmo"}},null]`, + { + headers, + }, + ); + executeCommonChecks(res); + sleep(1.5); + }); + + group('Place the order', () => { + res = http.post( + `${BASE_URL}/api/magento/placeOrder`, + `[{"cart_id":"${vars.cart_id}"},null]`, + { + headers, + }, + ); + executeCommonChecks(res); + }); + }); +} diff --git a/packages/load-tests/src/tests/magento/utils/customerData.js b/packages/load-tests/src/utils/customerData.js similarity index 100% rename from packages/load-tests/src/tests/magento/utils/customerData.js rename to packages/load-tests/src/utils/customerData.js From 7753d1531d6a27712932c142de0a4bb623e16ed4 Mon Sep 17 00:00:00 2001 From: Bartosz Herba Date: Thu, 28 Jul 2022 08:55:08 +0200 Subject: [PATCH 13/24] feat: add i18n base url configuration - set baseUrl to env variable --- packages/theme/nuxt.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/theme/nuxt.config.js b/packages/theme/nuxt.config.js index 2a305497f..9eb8a6f23 100755 --- a/packages/theme/nuxt.config.js +++ b/packages/theme/nuxt.config.js @@ -135,6 +135,7 @@ export default () => { ], i18n: { country: 'US', + baseUrl: process.env.VSF_STORE_URL, strategy: 'prefix', locales: [ { From 6b53b9de057899470756560a593c93a33e30cebb Mon Sep 17 00:00:00 2001 From: Bartosz Herba Date: Wed, 27 Jul 2022 08:31:17 +0200 Subject: [PATCH 14/24] test: add load test for a new user registration --- .../tests/scenarios/newUserRegistration.js | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 packages/load-tests/src/tests/scenarios/newUserRegistration.js diff --git a/packages/load-tests/src/tests/scenarios/newUserRegistration.js b/packages/load-tests/src/tests/scenarios/newUserRegistration.js new file mode 100644 index 000000000..d35bf8c53 --- /dev/null +++ b/packages/load-tests/src/tests/scenarios/newUserRegistration.js @@ -0,0 +1,95 @@ +// Creator: k6 Browser Recorder 0.6.2 +import { check, sleep, group } from 'k6'; +import http from 'k6/http'; +import { setDefaultCookies } from './utils/setDefaultCookies.js'; +import { customerData } from '../../utils/customerData.js'; + +export const options = { + vus: 200, + duration: '5m', + thresholds: { + http_req_failed: ['rate<0.01'], // http errors should be less than 1% + }, +}; + +const { BASE_URL } = __ENV; + +if (BASE_URL === undefined) { + throw new Error('BASE_URL is not set'); +} + +const headers = { + accept: 'application/json, text/plain, */*', + 'content-type': 'application/json', + 'upgrade-insecure-requests': '1', + 'sec-ch-ua': '".Not/A)Brand";v="99", "Google Chrome";v="103", "Chromium";v="103"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"macOS"', +}; + +const executeCommonChecks = (res) => { + check(res, { + 'is status 200': (r) => r.status === 200, + 'has no errors': (r) => !r.json().errors, + }); +}; + +export default function main() { + let res; + setDefaultCookies(BASE_URL); + + group( + `New user registration - page ${BASE_URL}`, + () => { + res = http.get( + `${BASE_URL}/default`, + { + headers, + }, + ); + sleep(0.6); + + group('Input user data', () => { + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + '[{"query":"\\n query storeConfig {\\n storeConfig {\\n store_code,\\n default_title,\\n store_name,\\n default_display_currency_code,\\n locale,\\n header_logo_src,\\n logo_width,\\n logo_height,\\n logo_alt\\n }\\n }\\n"}]', + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + '[{"query":"\\n query getStoresAndCurrencies {\\n availableStores {\\n store_code\\n }\\n currency {\\n available_currency_codes\\n }\\n }\\n"}]', + { + headers, + }, + ); + executeCommonChecks(res); + + res = http.post( + `${BASE_URL}/api/magento/customQuery`, + '[{"query":"\\n query getStoresAndCurrencies {\\n availableStores {\\n store_code\\n }\\n currency {\\n available_currency_codes\\n }\\n }\\n"}]', + { + headers, + }, + ); + executeCommonChecks(res); + sleep(8); + }); + + group('Save new user', () => { + res = http.post( + `${BASE_URL}/api/magento/createCustomer`, + `[{"email":"${customerData.getEmail()}","password":"Admin123!","recaptchaToken":"recaptcha_bypass_code","firstname":"load","lastname":"test","is_subscribed":false},{}]`, + { + headers, + }, + ); + }); + + executeCommonChecks(res); + }, + ); +} From 180fc7884b9c4754e8426502a6f7612febfe14a4 Mon Sep 17 00:00:00 2001 From: Bartosz Herba Date: Wed, 27 Jul 2022 07:47:26 +0200 Subject: [PATCH 15/24] fix: update installation guide - change npm -> yarn because npm does not work for our app --- docs/installation-setup/installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation-setup/installation.md b/docs/installation-setup/installation.md index 0cf98a437..ecd93b690 100644 --- a/docs/installation-setup/installation.md +++ b/docs/installation-setup/installation.md @@ -35,7 +35,7 @@ Go to the newly created directory and install the required dependencies: ```bash cd -npm install +yarn install ``` ### Step 2. Setup and configure Magento @@ -51,7 +51,7 @@ With the Magento instance setup and configured, you can connect your project to The project is now ready. You can start the application in development mode using the command below. You can read more about available commands and environments on the [Commands and deployment](https://nuxtjs.org/docs/2.x/get-started/commands/) page in Nuxt.js documentation. ```bash -npm run dev +yarn run dev ``` ## Recommended tools From 20962e76c09d6ad711007886126ddb54ef450dc9 Mon Sep 17 00:00:00 2001 From: Bartosz Herba Date: Tue, 26 Jul 2022 09:17:21 +0200 Subject: [PATCH 16/24] refactor: remove style duplication in my account route components --- .../AddressesDetails/AddressEdit.vue | 8 ++---- .../MyAccount/AddressesDetails/AddressNew.vue | 7 +---- .../AddressesDetails/AddressesDetails.vue | 7 +---- .../customer/pages/MyAccount/MyNewsletter.vue | 8 +----- .../customer/pages/MyAccount/MyReviews.vue | 27 ++----------------- .../customer/pages/MyAccount/MyWishlist.vue | 9 +------ .../pages/MyAccount/ResetPassword.vue | 4 +++ .../modules/customer/pages/styles/shared.scss | 26 ++++++++++++++++++ 8 files changed, 38 insertions(+), 58 deletions(-) create mode 100644 packages/theme/modules/customer/pages/styles/shared.scss diff --git a/packages/theme/modules/customer/pages/MyAccount/AddressesDetails/AddressEdit.vue b/packages/theme/modules/customer/pages/MyAccount/AddressesDetails/AddressEdit.vue index a7df3bbed..2a93eefc0 100644 --- a/packages/theme/modules/customer/pages/MyAccount/AddressesDetails/AddressEdit.vue +++ b/packages/theme/modules/customer/pages/MyAccount/AddressesDetails/AddressEdit.vue @@ -69,10 +69,6 @@ export default defineComponent({ diff --git a/packages/theme/modules/customer/pages/MyAccount/AddressesDetails/AddressNew.vue b/packages/theme/modules/customer/pages/MyAccount/AddressesDetails/AddressNew.vue index 44fc40e46..b7f0cd0b6 100644 --- a/packages/theme/modules/customer/pages/MyAccount/AddressesDetails/AddressNew.vue +++ b/packages/theme/modules/customer/pages/MyAccount/AddressesDetails/AddressNew.vue @@ -43,10 +43,5 @@ export default defineComponent({ diff --git a/packages/theme/modules/customer/pages/MyAccount/AddressesDetails/AddressesDetails.vue b/packages/theme/modules/customer/pages/MyAccount/AddressesDetails/AddressesDetails.vue index f5efb8ccc..f50f59019 100644 --- a/packages/theme/modules/customer/pages/MyAccount/AddressesDetails/AddressesDetails.vue +++ b/packages/theme/modules/customer/pages/MyAccount/AddressesDetails/AddressesDetails.vue @@ -179,12 +179,7 @@ export default defineComponent({