diff --git a/CHANGELOG.md b/CHANGELOG.md index 7237ebfacf5e1..e17c4e14ffc7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -785,7 +785,7 @@ Tests: * Refactored controller actions in the Product area * Moved commands cache.php, indexer.php, log.php, test.php, compiler.php, singletenant\_compiler.php, generator.php, pack.php, deploy.php and file\_assembler.php to the new bin/magento CLI framework * Data Migration Tool - * The Data Migraiton Tool is published in the separate [repository](https://github.com/magento/data-migration-tool-ce "Data Migration Tool repository") + * The Data Migration Tool is published in the separate [repository](https://github.com/magento/data-migration-tool-ce "Data Migration Tool repository") * Fixed bugs * Fixed an issue where error appeared during placing order with virtual product * Fixed an issue where billing and shipping sections didn't contain address information on order print diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingButtonTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingButtonTest.xml index 505e178f49f43..e660a2eb8d428 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingButtonTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingButtonTest.xml @@ -13,6 +13,9 @@ <description value="Test log in to AdvancedReporting and tests AdvancedReportingButtonTest"/> <testCaseId value="MC-14800"/> + <skip> + <issueId value="MC-14800" /> + </skip> <severity value="CRITICAL"/> <group value="analytics"/> <group value="mtf_migrated"/> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminSuccessLoginActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminSuccessLoginActionGroup.xml new file mode 100644 index 0000000000000..844f58c789a15 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminSuccessLoginActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminSuccessLoginActionGroup"> + <waitForElementVisible selector="{{AdminHeaderSection.adminUserAccountText}}" stepKey="waitForAdminAccountTextVisible" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertMessageOnAdminLoginActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertMessageOnAdminLoginActionGroup.xml new file mode 100644 index 0000000000000..ccc7cd24350c5 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertMessageOnAdminLoginActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertMessageOnAdminLoginActionGroup"> + <arguments> + <argument name="message" type="string" defaultValue="The account sign-in was incorrect or your account is disabled temporarily. Please wait and try again later." /> + <argument name="messageType" type="string" defaultValue="error" /> + </arguments> + <see userInput="{{message}}" selector="{{AdminLoginMessagesSection.messageByType(messageType)}}" stepKey="verifyMessage" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAdminWithCredentialsActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAdminWithCredentialsActionGroup.xml new file mode 100644 index 0000000000000..6aaa612b249b6 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAdminWithCredentialsActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="LoginAdminWithCredentialsActionGroup"> + <arguments> + <argument name="adminUser" type="string" /> + <argument name="adminPassword" type="string" /> + </arguments> + <amOnPage url="{{_ENV.MAGENTO_BACKEND_NAME}}" stepKey="navigateToAdmin"/> + <fillField selector="{{AdminLoginFormSection.username}}" userInput="{{adminUser}}" stepKey="fillUsername"/> + <fillField selector="{{AdminLoginFormSection.password}}" userInput="{{adminPassword}}" stepKey="fillPassword"/> + <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> + <closeAdminNotification stepKey="closeAdminNotification"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml index 1070bc409962a..b2fbadcbe38e2 100644 --- a/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml @@ -10,11 +10,11 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="LoginAsAdmin"> <arguments> - <argument name="adminUser" defaultValue="_ENV"/> + <argument name="adminUser" type="entity" defaultValue="DefaultAdminUser"/> </arguments> <amOnPage url="{{AdminLoginPage.url}}" stepKey="navigateToAdmin"/> - <fillField selector="{{AdminLoginFormSection.username}}" userInput="{{adminUser.MAGENTO_ADMIN_USERNAME}}" stepKey="fillUsername"/> - <fillField selector="{{AdminLoginFormSection.password}}" userInput="{{adminUser.MAGENTO_ADMIN_PASSWORD}}" stepKey="fillPassword"/> + <fillField selector="{{AdminLoginFormSection.username}}" userInput="{{adminUser.username}}" stepKey="fillUsername"/> + <fillField selector="{{AdminLoginFormSection.password}}" userInput="{{adminUser.password}}" stepKey="fillPassword"/> <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> <closeAdminNotification stepKey="closeAdminNotification"/> </actionGroup> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminHeaderSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminHeaderSection.xml index 441ce886f117b..5b517c7be8a79 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminHeaderSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminHeaderSection.xml @@ -10,5 +10,6 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminHeaderSection"> <element name="pageTitle" type="text" selector=".page-header h1.page-title"/> + <element name="adminUserAccountText" type="text" selector=".page-header .admin-user-account-text" /> </section> </sections> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminLoginMessagesSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminLoginMessagesSection.xml new file mode 100644 index 0000000000000..f6ada50ada357 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminLoginMessagesSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminLoginMessagesSection"> + <element name="messageByType" type="block" selector=".login-content .messages .message-{{messageType}}" parameterized="true" /> + </section> +</sections> diff --git a/app/code/Magento/Braintree/Model/Multishipping/PlaceOrder.php b/app/code/Magento/Braintree/Model/Multishipping/PlaceOrder.php new file mode 100644 index 0000000000000..a6c1b088400a7 --- /dev/null +++ b/app/code/Magento/Braintree/Model/Multishipping/PlaceOrder.php @@ -0,0 +1,177 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Braintree\Model\Multishipping; + +use Magento\Braintree\Gateway\Command\GetPaymentNonceCommand; +use Magento\Braintree\Model\Ui\ConfigProvider; +use Magento\Braintree\Observer\DataAssignObserver; +use Magento\Braintree\Model\Ui\PayPal\ConfigProvider as PaypalConfigProvider; +use Magento\Multishipping\Model\Checkout\Type\Multishipping\PlaceOrderInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\Data\OrderPaymentExtensionInterface; +use Magento\Sales\Api\Data\OrderPaymentExtensionInterfaceFactory; +use Magento\Sales\Api\Data\OrderPaymentInterface; +use Magento\Sales\Api\OrderManagementInterface; +use Magento\Vault\Api\Data\PaymentTokenInterface; + +/** + * Order payments processing for multishipping checkout flow. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class PlaceOrder implements PlaceOrderInterface +{ + /** + * @var OrderManagementInterface + */ + private $orderManagement; + + /** + * @var OrderPaymentExtensionInterfaceFactory + */ + private $paymentExtensionFactory; + + /** + * @var GetPaymentNonceCommand + */ + private $getPaymentNonceCommand; + + /** + * @param OrderManagementInterface $orderManagement + * @param OrderPaymentExtensionInterfaceFactory $paymentExtensionFactory + * @param GetPaymentNonceCommand $getPaymentNonceCommand + */ + public function __construct( + OrderManagementInterface $orderManagement, + OrderPaymentExtensionInterfaceFactory $paymentExtensionFactory, + GetPaymentNonceCommand $getPaymentNonceCommand + ) { + $this->orderManagement = $orderManagement; + $this->paymentExtensionFactory = $paymentExtensionFactory; + $this->getPaymentNonceCommand = $getPaymentNonceCommand; + } + + /** + * @inheritdoc + */ + public function place(array $orderList): array + { + if (empty($orderList)) { + return []; + } + + $errorList = []; + $firstOrder = $this->orderManagement->place(array_shift($orderList)); + // get payment token from first placed order + $paymentToken = $this->getPaymentToken($firstOrder); + + foreach ($orderList as $order) { + try { + /** @var OrderInterface $order */ + $orderPayment = $order->getPayment(); + $this->setVaultPayment($orderPayment, $paymentToken); + $this->orderManagement->place($order); + } catch (\Exception $e) { + $incrementId = $order->getIncrementId(); + $errorList[$incrementId] = $e; + } + } + + return $errorList; + } + + /** + * Sets vault payment method. + * + * @param OrderPaymentInterface $orderPayment + * @param PaymentTokenInterface $paymentToken + * @return void + */ + private function setVaultPayment(OrderPaymentInterface $orderPayment, PaymentTokenInterface $paymentToken): void + { + $vaultMethod = $this->getVaultPaymentMethod( + $orderPayment->getMethod() + ); + $orderPayment->setMethod($vaultMethod); + + $publicHash = $paymentToken->getPublicHash(); + $customerId = $paymentToken->getCustomerId(); + $result = $this->getPaymentNonceCommand->execute( + ['public_hash' => $publicHash, 'customer_id' => $customerId] + ) + ->get(); + + $orderPayment->setAdditionalInformation( + DataAssignObserver::PAYMENT_METHOD_NONCE, + $result['paymentMethodNonce'] + ); + $orderPayment->setAdditionalInformation( + PaymentTokenInterface::PUBLIC_HASH, + $publicHash + ); + $orderPayment->setAdditionalInformation( + PaymentTokenInterface::CUSTOMER_ID, + $customerId + ); + } + + /** + * Returns vault payment method. + * + * For placing sequence of orders, we need to replace the original method on the vault method. + * + * @param string $method + * @return string + */ + private function getVaultPaymentMethod(string $method): string + { + $vaultPaymentMap = [ + ConfigProvider::CODE => ConfigProvider::CC_VAULT_CODE, + PaypalConfigProvider::PAYPAL_CODE => PaypalConfigProvider::PAYPAL_VAULT_CODE + ]; + + return $vaultPaymentMap[$method] ?? $method; + } + + /** + * Returns payment token. + * + * @param OrderInterface $order + * @return PaymentTokenInterface + * @throws \BadMethodCallException + */ + private function getPaymentToken(OrderInterface $order): PaymentTokenInterface + { + $orderPayment = $order->getPayment(); + $extensionAttributes = $this->getExtensionAttributes($orderPayment); + $paymentToken = $extensionAttributes->getVaultPaymentToken(); + + if ($paymentToken === null) { + throw new \BadMethodCallException('Vault Payment Token should be defined for placed order payment.'); + } + + return $paymentToken; + } + + /** + * Gets payment extension attributes. + * + * @param OrderPaymentInterface $payment + * @return OrderPaymentExtensionInterface + */ + private function getExtensionAttributes(OrderPaymentInterface $payment): OrderPaymentExtensionInterface + { + $extensionAttributes = $payment->getExtensionAttributes(); + if (null === $extensionAttributes) { + $extensionAttributes = $this->paymentExtensionFactory->create(); + $payment->setExtensionAttributes($extensionAttributes); + } + + return $extensionAttributes; + } +} diff --git a/app/code/Magento/Braintree/composer.json b/app/code/Magento/Braintree/composer.json index 5af56a2afd3fe..2f956076f3846 100644 --- a/app/code/Magento/Braintree/composer.json +++ b/app/code/Magento/Braintree/composer.json @@ -21,7 +21,8 @@ "magento/module-quote": "*", "magento/module-sales": "*", "magento/module-ui": "*", - "magento/module-vault": "*" + "magento/module-vault": "*", + "magento/module-multishipping": "*" }, "suggest": { "magento/module-checkout-agreements": "*", diff --git a/app/code/Magento/Braintree/etc/config.xml b/app/code/Magento/Braintree/etc/config.xml index 9de4773af023a..fe4cfab9c0e30 100644 --- a/app/code/Magento/Braintree/etc/config.xml +++ b/app/code/Magento/Braintree/etc/config.xml @@ -42,7 +42,7 @@ <paymentInfoKeys>cc_type,cc_number,avsPostalCodeResponseCode,avsStreetAddressResponseCode,cvvResponseCode,processorAuthorizationCode,processorResponseCode,processorResponseText,liabilityShifted,liabilityShiftPossible,riskDataId,riskDataDecision</paymentInfoKeys> <avs_ems_adapter>Magento\Braintree\Model\AvsEmsCodeMapper</avs_ems_adapter> <cvv_ems_adapter>Magento\Braintree\Model\CvvEmsCodeMapper</cvv_ems_adapter> - <group>braintree</group> + <group>braintree_group</group> </braintree> <braintree_paypal> <model>BraintreePayPalFacade</model> @@ -68,7 +68,7 @@ <privateInfoKeys>processorResponseCode,processorResponseText,paymentId</privateInfoKeys> <paymentInfoKeys>processorResponseCode,processorResponseText,paymentId,payerEmail</paymentInfoKeys> <supported_locales>en_US,en_GB,en_AU,da_DK,fr_FR,fr_CA,de_DE,zh_HK,it_IT,nl_NL,no_NO,pl_PL,es_ES,sv_SE,tr_TR,pt_BR,ja_JP,id_ID,ko_KR,pt_PT,ru_RU,th_TH,zh_CN,zh_TW</supported_locales> - <group>braintree</group> + <group>braintree_group</group> </braintree_paypal> <braintree_cc_vault> <model>BraintreeCreditCardVaultFacade</model> @@ -78,7 +78,7 @@ <tokenFormat>Magento\Braintree\Model\InstantPurchase\CreditCard\TokenFormatter</tokenFormat> <additionalInformation>Magento\Braintree\Model\InstantPurchase\PaymentAdditionalInformationProvider</additionalInformation> </instant_purchase> - <group>braintree</group> + <group>braintree_group</group> </braintree_cc_vault> <braintree_paypal_vault> <model>BraintreePayPalVaultFacade</model> @@ -88,7 +88,7 @@ <tokenFormat>Magento\Braintree\Model\InstantPurchase\PayPal\TokenFormatter</tokenFormat> <additionalInformation>Magento\Braintree\Model\InstantPurchase\PaymentAdditionalInformationProvider</additionalInformation> </instant_purchase> - <group>braintree</group> + <group>braintree_group</group> </braintree_paypal_vault> </payment> </default> diff --git a/app/code/Magento/Braintree/etc/frontend/di.xml b/app/code/Magento/Braintree/etc/frontend/di.xml index ea417c407dffd..d8d3a93b71dc3 100644 --- a/app/code/Magento/Braintree/etc/frontend/di.xml +++ b/app/code/Magento/Braintree/etc/frontend/di.xml @@ -61,4 +61,12 @@ <argument name="resolver" xsi:type="object">Magento\Braintree\Model\LocaleResolver</argument> </arguments> </type> + <type name="Magento\Multishipping\Model\Checkout\Type\Multishipping\PlaceOrderPool"> + <arguments> + <argument name="services" xsi:type="array"> + <item name="braintree" xsi:type="string">Magento\Braintree\Model\Multishipping\PlaceOrder</item> + <item name="braintree_paypal" xsi:type="string">Magento\Braintree\Model\Multishipping\PlaceOrder</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Braintree/etc/payment.xml b/app/code/Magento/Braintree/etc/payment.xml index dbabd91151022..4cae049aaf5a9 100644 --- a/app/code/Magento/Braintree/etc/payment.xml +++ b/app/code/Magento/Braintree/etc/payment.xml @@ -8,8 +8,16 @@ <payment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Payment:etc/payment.xsd"> <groups> - <group id="braintree"> + <group id="braintree_group"> <label>Braintree</label> </group> </groups> + <methods> + <method name="braintree"> + <allow_multiple_address>1</allow_multiple_address> + </method> + <method name="braintree_paypal"> + <allow_multiple_address>1</allow_multiple_address> + </method> + </methods> </payment> diff --git a/app/code/Magento/Braintree/view/frontend/layout/multishipping_checkout_billing.xml b/app/code/Magento/Braintree/view/frontend/layout/multishipping_checkout_billing.xml new file mode 100644 index 0000000000000..06390d403e63d --- /dev/null +++ b/app/code/Magento/Braintree/view/frontend/layout/multishipping_checkout_billing.xml @@ -0,0 +1,20 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <body> + <referenceBlock name="checkout_billing"> + <arguments> + <argument name="form_templates" xsi:type="array"> + <item name="braintree" xsi:type="string">Magento_Braintree::multishipping/form.phtml</item> + <item name="braintree_paypal" xsi:type="string">Magento_Braintree::multishipping/form_paypal.phtml</item> + </argument> + </arguments> + </referenceBlock> + </body> +</page> diff --git a/app/code/Magento/Braintree/view/frontend/templates/multishipping/form.phtml b/app/code/Magento/Braintree/view/frontend/templates/multishipping/form.phtml new file mode 100644 index 0000000000000..bf8aa8dd09c2c --- /dev/null +++ b/app/code/Magento/Braintree/view/frontend/templates/multishipping/form.phtml @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +?> +<script> + require([ + 'uiLayout', + 'jquery' + ], function (layout, $) { + $(function () { + var paymentMethodData = { + method: 'braintree' + }; + layout([ + { + component: 'Magento_Braintree/js/view/payment/method-renderer/multishipping/hosted-fields', + name: 'payment_method_braintree', + method: paymentMethodData.method, + item: paymentMethodData + } + ]); + + $('body').trigger('contentUpdated'); + }) + }) +</script> +<!-- ko template: getTemplate() --><!-- /ko --> diff --git a/app/code/Magento/Braintree/view/frontend/templates/multishipping/form_paypal.phtml b/app/code/Magento/Braintree/view/frontend/templates/multishipping/form_paypal.phtml new file mode 100644 index 0000000000000..ea3eb2214c2d8 --- /dev/null +++ b/app/code/Magento/Braintree/view/frontend/templates/multishipping/form_paypal.phtml @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +?> +<script> + require([ + 'uiLayout', + 'jquery' + ], function (layout, $) { + $(function () { + var paymentMethodData = { + method: 'braintree_paypal' + }; + layout([ + { + component: 'Magento_Braintree/js/view/payment/method-renderer/multishipping/paypal', + name: 'payment_method_braintree_paypal', + method: paymentMethodData.method, + item: paymentMethodData + } + ]); + + $('body').trigger('contentUpdated'); + }) + }) +</script> +<!-- ko template: getTemplate() --><!-- /ko --> diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/multishipping/hosted-fields.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/multishipping/hosted-fields.js new file mode 100644 index 0000000000000..1ceebc8e66282 --- /dev/null +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/multishipping/hosted-fields.js @@ -0,0 +1,102 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +/*browser:true*/ +/*global define*/ + +define([ + 'jquery', + 'Magento_Braintree/js/view/payment/method-renderer/hosted-fields', + 'Magento_Braintree/js/validator', + 'Magento_Ui/js/model/messageList', + 'mage/translate', + 'Magento_Checkout/js/model/full-screen-loader', + 'Magento_Checkout/js/action/set-payment-information', + 'Magento_Checkout/js/model/payment/additional-validators' +], function ( + $, + Component, + validator, + messageList, + $t, + fullScreenLoader, + setPaymentInformationAction, + additionalValidators +) { + 'use strict'; + + return Component.extend({ + defaults: { + template: 'Magento_Braintree/payment/multishipping/form' + }, + + /** + * Get list of available CC types + * + * @returns {Object} + */ + getCcAvailableTypes: function () { + var availableTypes = validator.getAvailableCardTypes(), + billingCountryId; + + billingCountryId = $('#multishipping_billing_country_id').val(); + + if (billingCountryId && validator.getCountrySpecificCardTypes(billingCountryId)) { + return validator.collectTypes( + availableTypes, validator.getCountrySpecificCardTypes(billingCountryId) + ); + } + + return availableTypes; + }, + + /** + * @override + */ + placeOrder: function () { + var self = this; + + this.validatorManager.validate(self, function () { + return self.setPaymentInformation(); + }); + }, + + /** + * @override + */ + setPaymentInformation: function () { + if (additionalValidators.validate()) { + + fullScreenLoader.startLoader(); + + $.when( + setPaymentInformationAction( + this.messageContainer, + this.getData() + ) + ).done(this.done.bind(this)) + .fail(this.fail.bind(this)); + } + }, + + /** + * {Function} + */ + fail: function () { + fullScreenLoader.stopLoader(); + + return this; + }, + + /** + * {Function} + */ + done: function () { + fullScreenLoader.stopLoader(); + $('#multishipping-billing-form').submit(); + + return this; + } + }); +}); diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/multishipping/paypal.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/multishipping/paypal.js new file mode 100644 index 0000000000000..6702e58d1214b --- /dev/null +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/multishipping/paypal.js @@ -0,0 +1,143 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +/*browser:true*/ +/*global define*/ +define([ + 'jquery', + 'underscore', + 'Magento_Braintree/js/view/payment/method-renderer/paypal', + 'Magento_Checkout/js/action/set-payment-information', + 'Magento_Checkout/js/model/payment/additional-validators', + 'Magento_Checkout/js/model/full-screen-loader', + 'mage/translate' +], function ( + $, + _, + Component, + setPaymentInformationAction, + additionalValidators, + fullScreenLoader, + $t +) { + 'use strict'; + + return Component.extend({ + defaults: { + template: 'Magento_Braintree/payment/multishipping/paypal', + submitButtonSelector: '#payment-continue span' + }, + + /** + * @override + */ + onActiveChange: function (isActive) { + this.updateSubmitButtonTitle(isActive); + + this._super(isActive); + }, + + /** + * @override + */ + beforePlaceOrder: function (data) { + this._super(data); + + this.updateSubmitButtonTitle(true); + }, + + /** + * @override + */ + getShippingAddress: function () { + return {}; + }, + + /** + * @override + */ + getData: function () { + var data = this._super(); + + data['additional_data']['is_active_payment_token_enabler'] = true; + + return data; + }, + + /** + * @override + */ + isActiveVault: function () { + return true; + }, + + /** + * Skipping order review step on checkout with multiple addresses is not allowed. + * + * @returns {Boolean} + */ + isSkipOrderReview: function () { + return false; + }, + + /** + * Checks if payment method nonce is already received. + * + * @returns {Boolean} + */ + isPaymentMethodNonceReceived: function () { + return this.paymentMethodNonce !== null; + }, + + /** + * Updates submit button title on multi-addresses checkout billing form. + * + * @param {Boolean} isActive + */ + updateSubmitButtonTitle: function (isActive) { + var title = this.isPaymentMethodNonceReceived() || !isActive ? + $t('Go to Review Your Order') : $t('Continue to PayPal'); + + $(this.submitButtonSelector).html(title); + }, + + /** + * @override + */ + placeOrder: function () { + if (!this.isPaymentMethodNonceReceived()) { + this.payWithPayPal(); + } else { + fullScreenLoader.startLoader(); + + $.when( + setPaymentInformationAction( + this.messageContainer, + this.getData() + ) + ).done(this.done.bind(this)) + .fail(this.fail.bind(this)); + } + }, + + /** + * {Function} + */ + fail: function () { + fullScreenLoader.stopLoader(); + + return this; + }, + + /** + * {Function} + */ + done: function () { + fullScreenLoader.stopLoader(); + $('#multishipping-billing-form').submit(); + + return this; + } + }); +}); diff --git a/app/code/Magento/Braintree/view/frontend/web/template/payment/multishipping/form.html b/app/code/Magento/Braintree/view/frontend/web/template/payment/multishipping/form.html new file mode 100644 index 0000000000000..964e15df166d3 --- /dev/null +++ b/app/code/Magento/Braintree/view/frontend/web/template/payment/multishipping/form.html @@ -0,0 +1,106 @@ +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<div data-bind="attr: {class: 'payment-method payment-method-' + getCode()}, css: {'_active': isActive()}"> + <div> + <form id="co-transparent-form-braintree" class="form" data-bind="" method="post" action="#" novalidate="novalidate"> + <fieldset data-bind="attr: {class: 'fieldset payment items ccard ' + getCode(), id: 'payment_form_' + getCode()}"> + <legend class="legend"> + <span><!-- ko i18n: 'Credit Card Information'--><!-- /ko --></span> + </legend> + <br> + <div class="field type"> + <div class="control"> + <ul class="credit-card-types"> + <!-- ko foreach: {data: getCcAvailableTypes(), as: 'item'} --> + <li class="item" data-bind="css: { + _active: $parent.selectedCardType() == item, + _inactive: $parent.selectedCardType() != null && $parent.selectedCardType() != item + } "> + <!--ko if: $parent.getIcons(item) --> + <img data-bind="attr: { + 'src': $parent.getIcons(item).url, + 'width': $parent.getIcons(item).width, + 'height': $parent.getIcons(item).height + }"> + <!--/ko--> + </li> + <!--/ko--> + </ul> + <input type="hidden" + name="payment[cc_type]" + class="input-text" + value="" + data-bind="attr: {id: getCode() + '_cc_type', 'data-container': getCode() + '-cc-type'}, + value: creditCardType + "> + </div> + </div> + <div class="field number required"> + <label data-bind="attr: {for: getCode() + '_cc_number'}" class="label"> + <span><!-- ko i18n: 'Credit Card Number'--><!-- /ko --></span> + </label> + <div class="control"> + <div data-bind="attr: {id: getCode() + '_cc_number'}" class="hosted-control"></div> + <div class="hosted-error"><!-- ko i18n: 'Please, enter valid Credit Card Number'--><!-- /ko --></div> + </div> + </div> + <div class="field number required"> + <label data-bind="attr: {for: getCode() + '_expiration'}" class="label"> + <span><!-- ko i18n: 'Expiration Date'--><!-- /ko --></span> + </label> + <div class="control"> + <div class="hosted-date-wrap"> + <div data-bind="attr: {id: getCode() + '_expirationMonth'}" + class="hosted-control hosted-date"></div> + + <div data-bind="attr: {id: getCode() + '_expirationYear'}" + class="hosted-control hosted-date"></div> + + <div class="hosted-error"><!-- ko i18n: 'Please, enter valid Expiration Date'--><!-- /ko --></div> + </div> + </div> + </div> + <!-- ko if: (hasVerification())--> + <div class="field cvv required" data-bind="attr: {id: getCode() + '_cc_type_cvv_div'}"> + <label data-bind="attr: {for: getCode() + '_cc_cid'}" class="label"> + <span><!-- ko i18n: 'Card Verification Number'--><!-- /ko --></span> + </label> + <div class="control _with-tooltip"> + <div data-bind="attr: {id: getCode() + '_cc_cid'}" class="hosted-control hosted-cid"></div> + <div class="hosted-error"><!-- ko i18n: 'Please, enter valid Card Verification Number'--><!-- /ko --></div> + + <div class="field-tooltip toggle"> + <span class="field-tooltip-action action-cvv" + tabindex="0" + data-toggle="dropdown" + data-bind="attr: {title: $t('What is this?')}, mageInit: {'dropdown':{'activeClass': '_active'}}"> + <span><!-- ko i18n: 'What is this?'--><!-- /ko --></span> + </span> + <div class="field-tooltip-content" + data-target="dropdown" + data-bind="html: getCvvImageHtml()"></div> + </div> + </div> + </div> + <!-- /ko --> + </fieldset> + <input type="submit" id="braintree_submit" style="display:none" /> + </form> + + <div class="actions-toolbar no-display"> + <div class="primary"> + <button data-role="review-save" + type="submit" + data-bind="{click: placeOrderClick}" + class="action primary checkout"> + <span data-bind="i18n: 'Place Order'"></span> + </button> + </div> + </div> + </div> +</div> \ No newline at end of file diff --git a/app/code/Magento/Braintree/view/frontend/web/template/payment/multishipping/paypal.html b/app/code/Magento/Braintree/view/frontend/web/template/payment/multishipping/paypal.html new file mode 100644 index 0000000000000..722989e41f98f --- /dev/null +++ b/app/code/Magento/Braintree/view/frontend/web/template/payment/multishipping/paypal.html @@ -0,0 +1,40 @@ +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<div class="payment-method" data-bind="css: {'_active': isActive()}"> + <div class="payment-method-title field choice"> + <label class="label" data-bind="attr: {'for': getCode()}"> + <!-- PayPal Logo --> + <img data-bind="attr: {src: getPaymentAcceptanceMarkSrc(), alt: $t('Acceptance Mark'), title: $t('Acceptance Mark')}" + class="payment-icon"/> + <!-- PayPal Logo --> + <span text="getTitle()"></span> + </label> + </div> + + <div class="payment-method-content"> + <each args="getRegion('messages')" render=""></each> + <fieldset class="braintree-paypal-fieldset" data-bind='attr: {id: "payment_form_" + getCode()}'> + <div id="paypal-container"></div> + </fieldset> + <div class="actions-toolbar braintree-paypal-actions" data-bind="visible: isReviewRequired()"> + <div class="payment-method-item braintree-paypal-account"> + <span class="payment-method-type">PayPal</span> + <span class="payment-method-description" text="customerEmail()"></span> + </div> + <div class="actions-toolbar no-display"> + <div class="primary"> + <button data-button="paypal-place" data-role="review-save" + type="submit" + data-bind="{click: placeOrder}" + class="action primary checkout"> + <span data-bind="i18n: 'Place Order'"></span> + </button> + </div> + </div> + </div> + </div> +</div> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AdminLoginWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AdminLoginWithCaptchaActionGroup.xml new file mode 100644 index 0000000000000..07329e2659876 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AdminLoginWithCaptchaActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminLoginWithCaptchaActionGroup" extends="LoginAsAdmin"> + <arguments> + <argument name="captcha" type="string" /> + </arguments> + <fillField stepKey="fillCaptchaField" after="fillPassword" userInput="{{captcha}}" selector="{{AdminLoginFormSection.captchaField}}" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup.xml new file mode 100644 index 0000000000000..a371f177e3552 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup"> + <waitForPageLoad stepKey="waitForPageLoaded" /> + <dontSee selector="{{StorefrontCustomerSignInFormSection.captchaField}}" stepKey="dontSeeCaptchaField"/> + <dontSee selector="{{StorefrontCustomerSignInFormSection.captchaImg}}" stepKey="dontSeeCaptchaImage"/> + <dontSee selector="{{StorefrontCustomerSignInFormSection.captchaReload}}" stepKey="dontSeeCaptchaReloadButton"/> + <reloadPage stepKey="refreshPage"/> + <waitForPageLoad stepKey="waitForPageReloaded" /> + <dontSee selector="{{StorefrontCustomerSignInFormSection.captchaField}}" stepKey="dontSeeCaptchaFieldAfterPageReload"/> + <dontSee selector="{{StorefrontCustomerSignInFormSection.captchaImg}}" stepKey="dontSeeCaptchaImageAfterPageReload"/> + <dontSee selector="{{StorefrontCustomerSignInFormSection.captchaReload}}" stepKey="dontSeeCaptchaReloadButtonAfterPageReload"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnAdminLoginFormActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnAdminLoginFormActionGroup.xml new file mode 100644 index 0000000000000..aa02588000d2b --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnAdminLoginFormActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCaptchaVisibleOnAdminLoginFormActionGroup"> + <waitForElementVisible selector="{{AdminLoginFormSection.captchaField}}" stepKey="seeCaptchaField"/> + <waitForElementVisible selector="{{AdminLoginFormSection.captchaImg}}" stepKey="seeCaptchaImage"/> + <waitForElementVisible selector="{{AdminLoginFormSection.captchaReload}}" stepKey="seeCaptchaReloadButton"/> + <reloadPage stepKey="refreshPage"/> + <waitForPageLoad stepKey="waitForPageReloaded" /> + <waitForElementVisible selector="{{AdminLoginFormSection.captchaField}}" stepKey="seeCaptchaFieldAfterPageReload"/> + <waitForElementVisible selector="{{AdminLoginFormSection.captchaImg}}" stepKey="seeCaptchaImageAfterPageReload"/> + <waitForElementVisible selector="{{AdminLoginFormSection.captchaReload}}" stepKey="seeCaptchaReloadButtonAfterPageReload"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnContactUsFormActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnContactUsFormActionGroup.xml new file mode 100644 index 0000000000000..d800c65cabb60 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnContactUsFormActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCaptchaVisibleOnContactUsFormActionGroup"> + <waitForElementVisible selector="{{StorefrontContactUsFormSection.captchaField}}" stepKey="waitToSeeCaptchaField"/> + <waitForElementVisible selector="{{StorefrontContactUsFormSection.captchaImg}}" stepKey="waitToSeeCaptchaImage"/> + <waitForElementVisible selector="{{StorefrontContactUsFormSection.captchaReload}}" stepKey="waitToSeeCaptchaReloadButton"/> + <reloadPage stepKey="refreshPage"/> + <waitForPageLoad stepKey="waitForPageReloaded" /> + <waitForElementVisible selector="{{StorefrontContactUsFormSection.captchaField}}" stepKey="waitToSeeCaptchaFieldAfterPageReload"/> + <waitForElementVisible selector="{{StorefrontContactUsFormSection.captchaImg}}" stepKey="waitToSeeCaptchaImageAfterPageReload"/> + <waitForElementVisible selector="{{StorefrontContactUsFormSection.captchaReload}}" stepKey="waitToSeeCaptchaReloadButtonAfterPageReload"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup.xml new file mode 100644 index 0000000000000..6c09d1d49381f --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup"> + <waitForElementVisible selector="{{StorefrontCustomerCreateFormSection.captchaField}}" stepKey="waitForCaptchaField"/> + <waitForElementVisible selector="{{StorefrontCustomerCreateFormSection.captchaImg}}" stepKey="waitForCaptchaImage"/> + <waitForElementVisible selector="{{StorefrontCustomerCreateFormSection.captchaReload}}" stepKey="waitForCaptchaReloadButton"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountInfoActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountInfoActionGroup.xml new file mode 100644 index 0000000000000..c68ffbfb5be4b --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountInfoActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCaptchaVisibleOnCustomerAccountInfoActionGroup"> + <checkOption selector="{{StorefrontCustomerAccountInformationSection.changeEmail}}" stepKey="clickChangeEmailCheckbox" /> + <waitForElementVisible selector="{{StorefrontCustomerAccountInformationSection.captchaField}}" stepKey="seeCaptchaField"/> + <waitForElementVisible selector="{{StorefrontCustomerAccountInformationSection.captchaImg}}" stepKey="seeCaptchaImage"/> + <waitForElementVisible selector="{{StorefrontCustomerAccountInformationSection.captchaReload}}" stepKey="seeCaptchaReloadButton"/> + <reloadPage stepKey="refreshPage"/> + <waitForPageLoad stepKey="waitForPageReloaded" /> + <checkOption selector="{{StorefrontCustomerAccountInformationSection.changeEmail}}" stepKey="clickChangeEmailCheckboxAfterPageReload" /> + <waitForElementVisible selector="{{StorefrontCustomerAccountInformationSection.captchaField}}" stepKey="seeCaptchaFieldAfterPageReload"/> + <waitForElementVisible selector="{{StorefrontCustomerAccountInformationSection.captchaImg}}" stepKey="seeCaptchaImageAfterPageReload"/> + <waitForElementVisible selector="{{StorefrontCustomerAccountInformationSection.captchaReload}}" stepKey="seeCaptchaReloadButtonAfterPageReload"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerLoginFormActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerLoginFormActionGroup.xml new file mode 100644 index 0000000000000..5616b099c026d --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerLoginFormActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCaptchaVisibleOnCustomerLoginFormActionGroup"> + <waitForElementVisible selector="{{StorefrontCustomerSignInFormSection.captchaField}}" stepKey="waitToSeeCaptchaField"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInFormSection.captchaImg}}" stepKey="waitToSeeCaptchaImage"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInFormSection.captchaReload}}" stepKey="waitToSeeCaptchaReloadButton"/> + <reloadPage stepKey="refreshPage"/> + <waitForPageLoad stepKey="waitForPageReloaded" /> + <waitForElementVisible selector="{{StorefrontCustomerSignInFormSection.captchaField}}" stepKey="waitToSeeCaptchaFieldAfterPageReload"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInFormSection.captchaImg}}" stepKey="waitToSeeCaptchaImageAfterPageReload"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInFormSection.captchaReload}}" stepKey="waitToSeeCaptchaReloadButtonAfterPageReload"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml index beb2c2bffa135..f3b6eb1d9af84 100644 --- a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="CaptchaFormsDisplayingActionGroup"> <click selector="{{CaptchaFormsDisplayingSection.store}}" stepKey="ClickToGoStores"/> <waitForPageLoad stepKey="waitForStoresLoaded"/> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailWithCaptchaActionGroup.xml new file mode 100644 index 0000000000000..8aff3d5482f2c --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailWithCaptchaActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCustomerChangeEmailWithCaptchaActionGroup" extends="StorefrontCustomerChangeEmailActionGroup"> + <arguments> + <argument name="captcha" type="string" /> + </arguments> + + <fillField selector="{{StorefrontCustomerAccountInformationSection.captchaField}}" userInput="{{captcha}}" stepKey="fillCaptchaField" after="fillCurrentPassword" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillContactUsFormWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillContactUsFormWithCaptchaActionGroup.xml new file mode 100644 index 0000000000000..3546fa2e57a33 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillContactUsFormWithCaptchaActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontFillContactUsFormWithCaptchaActionGroup" extends="StorefrontFillContactUsFormActionGroup"> + <arguments> + <argument name="captcha" type="string" /> + </arguments> + <fillField stepKey="fillCaptchaField" after="fillComment" userInput="{{captcha}}" selector="{{StorefrontContactUsFormSection.captchaField}}" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup.xml new file mode 100644 index 0000000000000..d67ebc1a00768 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup" extends="StorefrontFillCustomerAccountCreationFormActionGroup"> + <arguments> + <argument name="captcha" type="string" /> + </arguments> + <fillField stepKey="fillCaptchaField" after="fillConfirmPassword" userInput="{{captcha}}" selector="{{StorefrontCustomerCreateFormSection.captchaField}}" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithCaptchaActionGroup.xml new file mode 100644 index 0000000000000..5ad727a8fe99d --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithCaptchaActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontFillCustomerLoginFormWithCaptchaActionGroup" extends="StorefrontFillCustomerLoginFormActionGroup"> + <arguments> + <argument name="captcha" type="string" /> + </arguments> + <fillField stepKey="fillCaptchaField" after="fillPassword" userInput="{{captcha}}" selector="{{StorefrontCustomerSignInFormSection.captchaField}}" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaConfigData.xml b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaConfigData.xml new file mode 100644 index 0000000000000..90f48c320f2ac --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaConfigData.xml @@ -0,0 +1,142 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="StorefrontCustomerCaptchaEnableConfigData"> + <!-- Magento default value --> + <data key="path">customer/captcha/enable</data> + <data key="scope_id">0</data> + <data key="label">Yes</data> + <data key="value">1</data> + </entity> + <entity name="StorefrontCustomerCaptchaDisableConfigData"> + <data key="path">customer/captcha/enable</data> + <data key="scope_id">0</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> + <entity name="StorefrontCaptchaOnCustomerCreateFormConfigData"> + <data key="path">customer/captcha/forms</data> + <data key="scope_id">0</data> + <data key="label">Create user</data> + <data key="value">user_create</data> + </entity> + <entity name="StorefrontCaptchaOnContactUsFormConfigData"> + <data key="path">customer/captcha/forms</data> + <data key="scope_id">0</data> + <data key="label">Contact Us</data> + <data key="value">contact_us</data> + </entity> + <entity name="StorefrontCaptchaOnCustomerLoginConfigData"> + <!-- Magento default value --> + <data key="path">customer/captcha/forms</data> + <data key="scope_id">0</data> + <data key="label">Login</data> + <data key="value">user_login</data> + </entity> + <entity name="StorefrontCaptchaOnCustomerChangePasswordConfigData"> + <data key="path">customer/captcha/forms</data> + <data key="scope_id">0</data> + <data key="label">Change password</data> + <data key="value">user_edit</data> + </entity> + <entity name="StorefrontCaptchaOnCustomerForgotPasswordConfigData"> + <!-- Magento default value --> + <data key="path">customer/captcha/forms</data> + <data key="scope_id">0</data> + <data key="label">Forgot password</data> + <data key="value">user_forgotpassword</data> + </entity> + <entity name="StorefrontCustomerCaptchaModeAlwaysConfigData"> + <data key="path">customer/captcha/mode</data> + <data key="scope_id">0</data> + <data key="label">Always</data> + <data key="value">always</data> + </entity> + <entity name="StorefrontCustomerCaptchaModeAfterFailConfigData"> + <!-- Magento default value --> + <data key="path">customer/captcha/mode</data> + <data key="scope_id">0</data> + <data key="label">After number of attempts to login</data> + <data key="value">after_fail</data> + </entity> + <entity name="StorefrontCustomerCaptchaLength3ConfigData"> + <data key="path">customer/captcha/length</data> + <data key="scope">admin</data> + <data key="scope_id">1</data> + <data key="label">3</data> + <data key="value">3</data> + </entity> + <entity name="StorefrontCustomerCaptchaSymbols1ConfigData"> + <data key="path">customer/captcha/symbols</data> + <data key="scope">admin</data> + <data key="scope_id">1</data> + <data key="label">1</data> + <data key="value">1</data> + </entity> + <entity name="StorefrontCustomerCaptchaDefaultLengthConfigData"> + <!-- Magento default value --> + <data key="path">customer/captcha/length</data> + <data key="scope">admin</data> + <data key="scope_id">1</data> + <data key="label">4-5</data> + <data key="value">4-5</data> + </entity> + <entity name="StorefrontCustomerCaptchaDefaultSymbolsConfigData"> + <!-- Magento default value --> + <data key="path">customer/captcha/symbols</data> + <data key="scope">admin</data> + <data key="scope_id">1</data> + <data key="label">ABCDEFGHJKMnpqrstuvwxyz23456789</data> + <data key="value">ABCDEFGHJKMnpqrstuvwxyz23456789</data> + </entity> + <entity name="AdminCaptchaEnableConfigData"> + <!-- Magento default value --> + <data key="path">admin/captcha/enable</data> + <data key="scope_id">0</data> + <data key="label">Yes</data> + <data key="value">1</data> + </entity> + <entity name="AdminCaptchaDisableConfigData"> + <data key="path">admin/captcha/enable</data> + <data key="scope_id">0</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> + <entity name="AdminCaptchaLength3ConfigData"> + <data key="path">admin/captcha/length</data> + <data key="scope">admin</data> + <data key="scope_id">1</data> + <data key="label">3</data> + <data key="value">3</data> + </entity> + <entity name="AdminCaptchaSymbols1ConfigData"> + <data key="path">admin/captcha/symbols</data> + <data key="scope">admin</data> + <data key="scope_id">1</data> + <data key="label">1</data> + <data key="value">1</data> + </entity> + <entity name="AdminCaptchaDefaultLengthConfigData"> + <!-- Magento default value --> + <data key="path">admin/captcha/length</data> + <data key="scope">admin</data> + <data key="scope_id">1</data> + <data key="label">4-5</data> + <data key="value">4-5</data> + </entity> + <entity name="AdminCaptchaDefaultSymbolsConfigData"> + <!-- Magento default value --> + <data key="path">admin/captcha/symbols</data> + <data key="scope">admin</data> + <data key="scope_id">1</data> + <data key="label">ABCDEFGHJKMnpqrstuvwxyz23456789</data> + <data key="value">ABCDEFGHJKMnpqrstuvwxyz23456789</data> + </entity> +</entities> diff --git a/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaData.xml b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaData.xml new file mode 100644 index 0000000000000..d8fb206b8111c --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaData.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="WrongCaptcha"> + <data key="value" unique="suffix">WrongCAPTCHA</data> + </entity> + + <!-- This CAPTCHA will only work if "StorefrontCustomerCaptchaLength3ConfigData" and "StorefrontCustomerCaptchaSymbols1ConfigData" config is set. --> + <entity name="PreconfiguredCaptcha"> + <data key="value">111</data> + </entity> +</entities> diff --git a/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaFormsDisplayingData.xml b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaFormsDisplayingData.xml index 9db8110c0f64b..57a09219fe4db 100644 --- a/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaFormsDisplayingData.xml +++ b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaFormsDisplayingData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="CaptchaData"> <data key="createUser">Create user</data> <data key="login">Login</data> diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/AdminLoginFormSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/AdminLoginFormSection.xml new file mode 100644 index 0000000000000..2bcc6fc542d82 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Section/AdminLoginFormSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminLoginFormSection"> + <element name="captchaField" type="input" selector="#login-form input[name='captcha[backend_login]']" /> + <element name="captchaImg" type="block" selector="#login-form img#backend_login"/> + <element name="captchaReload" type="block" selector="#login-form img#captcha-reload"/> + </section> +</sections> diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontContactUsCaptchaSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontContactUsCaptchaSection.xml new file mode 100644 index 0000000000000..f587812576ff1 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontContactUsCaptchaSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontContactUsCaptchaSection"> + <element name="captchaField" type="input" selector="#captcha_contact_us"/> + <element name="captchaImg" type="block" selector=".captcha-img"/> + <element name="captchaReload" type="block" selector=".captcha-reload"/> + </section> +</sections> diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontContactUsFormSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontContactUsFormSection.xml new file mode 100644 index 0000000000000..60cf961ba7e8c --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontContactUsFormSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontContactUsFormSection"> + <element name="captchaField" type="input" selector="#contact-form input[name='captcha[contact_us]']" /> + <element name="captchaImg" type="block" selector="#contact-form img.captcha-img"/> + <element name="captchaReload" type="block" selector="#contact-form button.captcha-reload"/> + </section> +</sections> diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml new file mode 100644 index 0000000000000..a273c8d4abd28 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCustomerAccountInformationSection"> + <element name="captchaField" type="input" selector="#captcha_user_edit"/> + <element name="captchaImg" type="block" selector=".captcha-img"/> + <element name="captchaReload" type="block" selector=".captcha-reload"/> + </section> +</sections> diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml new file mode 100644 index 0000000000000..f48e6124cb214 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCustomerCreateFormSection"> + <element name="captchaField" type="input" selector="#captcha_user_create"/> + <element name="captchaImg" type="block" selector=".captcha-img"/> + <element name="captchaReload" type="block" selector=".captcha-reload"/> + </section> +</sections> diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml index 7a0557c4a2744..54aa36d1ca267 100644 --- a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml +++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml @@ -8,7 +8,7 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> - <section name="StorefrontCustomerSignInPopupFormSection"> + <section name="StorefrontCustomerSignInFormSection"> <element name="captchaField" type="input" selector="#captcha_user_login"/> <element name="captchaImg" type="block" selector=".captcha-img"/> <element name="captchaReload" type="block" selector=".captcha-reload"/> diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInPopupFormSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInPopupFormSection.xml new file mode 100644 index 0000000000000..7a0557c4a2744 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInPopupFormSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCustomerSignInPopupFormSection"> + <element name="captchaField" type="input" selector="#captcha_user_login"/> + <element name="captchaImg" type="block" selector=".captcha-img"/> + <element name="captchaReload" type="block" selector=".captcha-reload"/> + </section> +</sections> diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/AdminLoginWithCaptchaTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/AdminLoginWithCaptchaTest.xml new file mode 100644 index 0000000000000..e5ee55910df65 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Test/AdminLoginWithCaptchaTest.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminLoginWithCaptchaTest"> + <annotations> + <features value="Captcha"/> + <stories value="Admin login + Captcha"/> + <title value="Captcha on Admin login form"/> + <description value="Test creation for admin login with captcha."/> + <testCaseId value="MC-14012" /> + <severity value="MAJOR"/> + <group value="captcha"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <magentoCLI command="config:set {{AdminCaptchaLength3ConfigData.path}} {{AdminCaptchaLength3ConfigData.value}}" stepKey="setCaptchaLength" /> + <magentoCLI command="config:set {{AdminCaptchaSymbols1ConfigData.path}} {{AdminCaptchaSymbols1ConfigData.value}}" stepKey="setCaptchaSymbols" /> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/> + </before> + <after> + <magentoCLI command="config:set {{AdminCaptchaDefaultLengthConfigData.path}} {{AdminCaptchaDefaultLengthConfigData.value}}" stepKey="setDefaultCaptchaLength" /> + <magentoCLI command="config:set {{AdminCaptchaDefaultSymbolsConfigData.path}} {{AdminCaptchaDefaultSymbolsConfigData.value}}" stepKey="setDefaultCaptchaSymbols" /> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/> + </after> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdminWithWrongCredentialsFirstAttempt"> + <argument name="adminUser" value="AdminUserWrongCredentials" /> + </actionGroup> + <actionGroup ref="AssertMessageOnAdminLoginActionGroup" stepKey="seeFirstLoginErrorMessage" /> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdminWithWrongCredentialsSecondAttempt"> + <argument name="adminUser" value="AdminUserWrongCredentials" /> + </actionGroup> + <actionGroup ref="AssertMessageOnAdminLoginActionGroup" stepKey="seeSecondLoginErrorMessage" /> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdminWithWrongCredentialsThirdAttempt"> + <argument name="adminUser" value="AdminUserWrongCredentials" /> + </actionGroup> + <actionGroup ref="AssertMessageOnAdminLoginActionGroup" stepKey="seeThirdLoginErrorMessage" /> + + <!-- Check captcha visibility on admin login page --> + <actionGroup ref="AssertCaptchaVisibleOnAdminLoginFormActionGroup" stepKey="assertCaptchaVisible" /> + + <!-- Submit form with incorrect captcha --> + <actionGroup ref="AdminLoginWithCaptchaActionGroup" stepKey="loginAsAdminWithIncorrectCaptcha"> + <argument name="adminUser" value="DefaultAdminUser" /> + <argument name="captcha" value="{{WrongCaptcha.value}}" /> + </actionGroup> + <actionGroup ref="AssertMessageOnAdminLoginActionGroup" stepKey="seeIncorrectCaptchaErrorMessage"> + <argument name="message" value="Incorrect CAPTCHA." /> + </actionGroup> + <actionGroup ref="AssertCaptchaVisibleOnAdminLoginFormActionGroup" stepKey="assertCaptchaVisibleAfterIncorrectCaptcha" /> + + <actionGroup ref="AdminLoginWithCaptchaActionGroup" stepKey="loginAsAdminWithCorrectCaptcha"> + <argument name="adminUser" value="DefaultAdminUser" /> + <argument name="captcha" value="{{PreconfiguredCaptcha.value}}" /> + </actionGroup> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="verifyAdminLoggedIn" /> + </test> +</tests> diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaEditCustomerEmailTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaEditCustomerEmailTest.xml new file mode 100644 index 0000000000000..54237087227d8 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaEditCustomerEmailTest.xml @@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCaptchaEditCustomerEmailTest"> + <annotations> + <features value="Captcha"/> + <stories value="Customer Account Info Edit + Captcha"/> + <title value="Test for checking captcha on the customer account edit page."/> + <description value="Test for checking captcha on the customer account edit page and customer is locked."/> + <testCaseId value="MC-14013" /> + <severity value="MAJOR"/> + <group value="captcha"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Setup CAPTCHA for testing --> + <magentoCLI command="config:set {{StorefrontCaptchaOnCustomerChangePasswordConfigData.path}} {{StorefrontCaptchaOnCustomerChangePasswordConfigData.value}}" stepKey="enableUserEditCaptcha"/> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaLength3ConfigData.path}} {{StorefrontCustomerCaptchaLength3ConfigData.value}}" stepKey="setCaptchaLength" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaSymbols1ConfigData.path}} {{StorefrontCustomerCaptchaSymbols1ConfigData.value}}" stepKey="setCaptchaSymbols" /> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/> + + <createData entity="Simple_US_Customer" stepKey="customer"/> + <!-- Sign in as customer --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginToStorefrontAccount"> + <argument name="Customer" value="$$customer$$"/> + </actionGroup> + </before> + <after> + <!-- Revert Captcha forms configurations --> + <magentoCLI command="config:set {{StorefrontCaptchaOnCustomerLoginConfigData.path}} {{StorefrontCaptchaOnCustomerLoginConfigData.value}},{{StorefrontCaptchaOnCustomerForgotPasswordConfigData.value}}" stepKey="enableCaptchaOnDefaultForms" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultLengthConfigData.path}} {{StorefrontCustomerCaptchaDefaultLengthConfigData.value}}" stepKey="setDefaultCaptchaLength" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.path}} {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.value}}" stepKey="setDefaultCaptchaSymbols" /> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/> + + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> + </after> + + <!-- Open Customer edit page --> + <actionGroup ref="StorefrontOpenCustomerAccountInfoEditPageActionGroup" stepKey="goToCustomerEditPage" /> + + <!-- Update email with incorrect password 3 times. --> + <actionGroup ref="StorefrontCustomerChangeEmailActionGroup" stepKey="changeEmailFirstAttempt"> + <argument name="email" value="$$customer.email$$" /> + <argument name="password" value="{{Colorado_US_Customer.password}}" /> + </actionGroup> + + <actionGroup ref="AssertMessageCustomerChangeAccountInfoActionGroup" stepKey="assertAccountMessageFirstAttempt"> + <argument name="message" value="The password doesn't match this account. Verify the password and try again." /> + <argument name="messageType" value="error" /> + </actionGroup> + + <actionGroup ref="StorefrontCustomerChangeEmailActionGroup" stepKey="changeEmailSecondAttempt"> + <argument name="email" value="$$customer.email$$" /> + <argument name="password" value="{{Colorado_US_Customer.password}}" /> + </actionGroup> + + <actionGroup ref="AssertMessageCustomerChangeAccountInfoActionGroup" stepKey="assertAccountMessageSecondAttempt"> + <argument name="message" value="The password doesn't match this account. Verify the password and try again." /> + <argument name="messageType" value="error" /> + </actionGroup> + + <actionGroup ref="StorefrontCustomerChangeEmailActionGroup" stepKey="changeEmailThirdAttempt"> + <argument name="email" value="$$customer.email$$" /> + <argument name="password" value="{{Colorado_US_Customer.password}}" /> + </actionGroup> + + <actionGroup ref="AssertMessageCustomerChangeAccountInfoActionGroup" stepKey="assertAccountMessageThirdAttempt"> + <argument name="message" value="The password doesn't match this account. Verify the password and try again." /> + <argument name="messageType" value="error" /> + </actionGroup> + + <!-- Check captcha visibility after incorrect password submit form --> + <actionGroup ref="AssertCaptchaVisibleOnCustomerAccountInfoActionGroup" stepKey="assertCaptchaVisible" /> + + <!-- Try to submit form with incorrect captcha --> + <actionGroup ref="StorefrontCustomerChangeEmailWithCaptchaActionGroup" stepKey="changeEmailWithIncorrectCaptcha"> + <argument name="email" value="$$customer.email$$" /> + <argument name="password" value="{{Colorado_US_Customer.password}}" /> + <argument name="captcha" value="{{WrongCaptcha.value}}" /> + </actionGroup> + + <actionGroup ref="AssertMessageCustomerChangeAccountInfoActionGroup" stepKey="assertAccountMessageAfterIncorrectCaptcha"> + <argument name="message" value="Incorrect CAPTCHA" /> + <argument name="messageType" value="error" /> + </actionGroup> + + <!-- Update customer email correct password and CAPTCHA --> + <actionGroup ref="StorefrontCustomerChangeEmailWithCaptchaActionGroup" stepKey="changeEmailCorrectAttempt"> + <argument name="email" value="$$customer.email$$" /> + <argument name="password" value="$$customer.password$$" /> + <argument name="captcha" value="{{PreconfiguredCaptcha.value}}" /> + </actionGroup> + <actionGroup ref="AssertMessageCustomerChangeAccountInfoActionGroup" stepKey="assertAccountMessageCorrectAttempt" /> + </test> +</tests> diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnContactUsTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnContactUsTest.xml new file mode 100644 index 0000000000000..0c6a3f31c1df2 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnContactUsTest.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCaptchaOnContactUsTest"> + <annotations> + <features value="Captcha"/> + <stories value="Submit Contact us form + Captcha"/> + <title value="Captcha on contact us form test"/> + <description value="Test creation for send comment using the contact us form with captcha."/> + <testCaseId value="MC-14103" /> + <severity value="MAJOR"/> + <group value="captcha"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaLength3ConfigData.path}} {{StorefrontCustomerCaptchaLength3ConfigData.value}}" stepKey="setCaptchaLength" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaSymbols1ConfigData.path}} {{StorefrontCustomerCaptchaSymbols1ConfigData.value}}" stepKey="setCaptchaSymbols" /> + <magentoCLI command="config:set {{StorefrontCaptchaOnContactUsFormConfigData.path}} {{StorefrontCaptchaOnContactUsFormConfigData.value}}" stepKey="enableUserEditCaptcha"/> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/> + </before> + <after> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultLengthConfigData.path}} {{StorefrontCustomerCaptchaDefaultLengthConfigData.value}}" stepKey="setDefaultCaptchaLength" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.path}} {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.value}}" stepKey="setDefaultCaptchaSymbols" /> + <magentoCLI command="config:set {{StorefrontCaptchaOnCustomerLoginConfigData.path}} {{StorefrontCaptchaOnCustomerLoginConfigData.value}},{{StorefrontCaptchaOnCustomerForgotPasswordConfigData.value}}" stepKey="enableCaptchaOnDefaultForms" /> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/> + </after> + + <!-- Open storefront contact us form --> + <actionGroup ref="StorefrontOpenContactUsPageActionGroup" stepKey="goToContactUsPage" /> + + <!-- Check Captcha items --> + <actionGroup ref="AssertCaptchaVisibleOnContactUsFormActionGroup" stepKey="seeCaptchaOnContactUsForm" /> + + <!-- Submit Contact Us form --> + <actionGroup ref="StorefrontFillContactUsFormWithCaptchaActionGroup" stepKey="fillContactUsFormWithWrongCaptcha"> + <argument name="customer" value="Simple_US_Customer" /> + <argument name="contactUsData" value="DefaultContactUsData" /> + <argument name="captcha" value="{{WrongCaptcha.value}}" /> + </actionGroup> + <actionGroup ref="StorefrontSubmitContactUsFormActionGroup" stepKey="submitContactUsFormWithWrongCaptcha" /> + + <!-- Check Captcha items after form reload --> + <actionGroup ref="AssertMessageContactUsFormActionGroup" stepKey="verifyErrorMessage"> + <argument name="message" value="Incorrect CAPTCHA" /> + <argument name="messageType" value="error" /> + </actionGroup> + <actionGroup ref="AssertCaptchaVisibleOnContactUsFormActionGroup" stepKey="seeCaptchaOnContactUsFormAfterWrongCaptcha" /> + + <actionGroup ref="StorefrontFillContactUsFormWithCaptchaActionGroup" stepKey="fillContactUsFormWithCorrectCaptcha"> + <argument name="customer" value="Simple_US_Customer" /> + <argument name="contactUsData" value="DefaultContactUsData" /> + <argument name="captcha" value="{{PreconfiguredCaptcha.value}}" /> + </actionGroup> + <actionGroup ref="StorefrontSubmitContactUsFormActionGroup" stepKey="submitContactUsFormWithCorrectCaptcha" /> + <actionGroup ref="AssertMessageContactUsFormActionGroup" stepKey="verifySuccessMessage" /> + </test> +</tests> diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnCustomerLoginTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnCustomerLoginTest.xml new file mode 100644 index 0000000000000..5a1be68d3f251 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnCustomerLoginTest.xml @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCaptchaOnCustomerLoginTest"> + <annotations> + <features value="Captcha"/> + <stories value="Login with Customer Account + Captcha"/> + <title value="Captcha customer login page test"/> + <description value="Check CAPTCHA on Storefront Login Page."/> + <severity value="MAJOR"/> + <testCaseId value="MC-14010" /> + <group value="captcha"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaLength3ConfigData.path}} {{StorefrontCustomerCaptchaLength3ConfigData.value}}" stepKey="setCaptchaLength" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaSymbols1ConfigData.path}} {{StorefrontCustomerCaptchaSymbols1ConfigData.value}}" stepKey="setCaptchaSymbols" /> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/> + <createData entity="Simple_US_Customer" stepKey="customer"/> + </before> + <after> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultLengthConfigData.path}} {{StorefrontCustomerCaptchaDefaultLengthConfigData.value}}" stepKey="setDefaultCaptchaLength" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.path}} {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.value}}" stepKey="setDefaultCaptchaSymbols" /> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> + </after> + + <!-- Open storefront login form --> + <actionGroup ref="StorefrontOpenCustomerLoginPageActionGroup" stepKey="goToSignInPage" /> + + <!-- Login with wrong credentials 3 times --> + <actionGroup ref="StorefrontFillCustomerLoginFormActionGroup" stepKey="fillLoginFormFirstAttempt"> + <argument name="customer" value="Colorado_US_Customer" /> + </actionGroup> + <actionGroup ref="StorefrontClickSignOnCustomerLoginFormActionGroup" stepKey="clickSignInAccountButtonFirstAttempt" /> + <actionGroup ref="AssertMessageCustomerLoginActionGroup" stepKey="seeErrorMessageAfterFirstAttempt" /> + <actionGroup ref="AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup" stepKey="dontSeeCaptchaAfterFirstAttempt" /> + + <actionGroup ref="StorefrontFillCustomerLoginFormActionGroup" stepKey="fillLoginFormSecondAttempt"> + <argument name="customer" value="Colorado_US_Customer" /> + </actionGroup> + <actionGroup ref="StorefrontClickSignOnCustomerLoginFormActionGroup" stepKey="clickSignInAccountButtonSecondAttempt" /> + <actionGroup ref="AssertMessageCustomerLoginActionGroup" stepKey="seeErrorMessageAfterSecondAttempt" /> + <actionGroup ref="AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup" stepKey="dontSeeCaptchaAfterSecondAttempt" /> + + <actionGroup ref="StorefrontFillCustomerLoginFormActionGroup" stepKey="fillLoginFormThirdAttempt"> + <argument name="customer" value="Colorado_US_Customer" /> + </actionGroup> + <actionGroup ref="StorefrontClickSignOnCustomerLoginFormActionGroup" stepKey="clickSignInAccountButtonThirdAttempt" /> + <actionGroup ref="AssertMessageCustomerLoginActionGroup" stepKey="seeErrorMessageAfterThirdAttempt" /> + <actionGroup ref="AssertCaptchaVisibleOnCustomerLoginFormActionGroup" stepKey="seeCaptchaAfterThirdAttempt" /> + + <!-- Submit form with incorrect captcha --> + <actionGroup ref="StorefrontFillCustomerLoginFormWithCaptchaActionGroup" stepKey="fillLoginFormCorrectAccountIncorrectCaptcha"> + <argument name="customer" value="$$customer$$" /> + <argument name="captcha" value="{{WrongCaptcha.value}}" /> + </actionGroup> + <actionGroup ref="StorefrontClickSignOnCustomerLoginFormActionGroup" stepKey="clickSignInAccountButtonCorrectAccountIncorrectCaptcha" /> + <actionGroup ref="AssertMessageCustomerLoginActionGroup" stepKey="seeErrorMessageAfterIncorrectCaptcha"> + <argument name="message" value="Incorrect CAPTCHA" /> + </actionGroup> + + <actionGroup ref="StorefrontFillCustomerLoginFormWithCaptchaActionGroup" stepKey="fillLoginFormCorrectAccountCorrectCaptcha"> + <argument name="customer" value="$$customer$$" /> + <argument name="captcha" value="{{PreconfiguredCaptcha.value}}" /> + </actionGroup> + <actionGroup ref="StorefrontClickSignOnCustomerLoginFormActionGroup" stepKey="clickSignInAccountButtonCorrectAccountCorrectCaptcha" /> + <actionGroup ref="AssertCustomerWelcomeMessageActionGroup" stepKey="assertCustomerLoggedIn"> + <argument name="customerFullName" value="$$customer.firstname$$ $$customer.lastname$$" /> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaRegisterNewCustomerTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaRegisterNewCustomerTest.xml new file mode 100644 index 0000000000000..2c331f958e467 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaRegisterNewCustomerTest.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCaptchaRegisterNewCustomerTest"> + <annotations> + <features value="Captcha"/> + <stories value="Create New Customer Account + Captcha"/> + <title value="Test creation for customer register with captcha on storefront."/> + <description value="Test creation for customer register with captcha on storefront."/> + <severity value="MAJOR"/> + <testCaseId value="MC-14805" /> + <group value="captcha"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Enable captcha for customer. --> + <magentoCLI command="config:set {{StorefrontCaptchaOnCustomerCreateFormConfigData.path}} {{StorefrontCaptchaOnCustomerCreateFormConfigData.value}}" stepKey="enableUserRegistrationCaptcha" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaModeAlwaysConfigData.path}} {{StorefrontCustomerCaptchaModeAlwaysConfigData.value}}" stepKey="alwaysEnableCaptcha" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaLength3ConfigData.path}} {{StorefrontCustomerCaptchaLength3ConfigData.value}}" stepKey="setCaptchaLength" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaSymbols1ConfigData.path}} {{StorefrontCustomerCaptchaSymbols1ConfigData.value}}" stepKey="setCaptchaSymbols" /> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/> + </before> + <after> + <!-- Set default configuration. --> + <magentoCLI command="config:set {{StorefrontCaptchaOnCustomerLoginConfigData.path}} {{StorefrontCaptchaOnCustomerLoginConfigData.value}},{{StorefrontCaptchaOnCustomerForgotPasswordConfigData.value}}" stepKey="enableCaptchaOnDefaultForms" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaModeAfterFailConfigData.path}} {{StorefrontCustomerCaptchaModeAfterFailConfigData.value}}" stepKey="defaultCaptchaMode" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultLengthConfigData.path}} {{StorefrontCustomerCaptchaDefaultLengthConfigData.value}}" stepKey="setDefaultCaptchaLength" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.path}} {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.value}}" stepKey="setDefaultCaptchaSymbols" /> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/> + </after> + + <!-- Open Customer registration page --> + <actionGroup ref="StorefrontOpenCustomerAccountCreatePageActionGroup" stepKey="goToCustomerAccountCreatePage" /> + + <!-- Check captcha visibility registration page load --> + <actionGroup ref="AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup" stepKey="verifyCaptchaVisible" /> + + <!-- Submit form with incorrect captcha --> + <actionGroup ref="StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup" stepKey="fillNewCustomerAccountFormWithIncorrectCaptcha"> + <argument name="customer" value="Simple_US_Customer" /> + <argument name="captcha" value="{{WrongCaptcha.value}}" /> + </actionGroup> + + <actionGroup ref="StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup" stepKey="clickCreateAnAccountButton" /> + + <actionGroup ref="AssertMessageCustomerCreateAccountActionGroup" stepKey="assertMessage"> + <argument name="message" value="Incorrect CAPTCHA" /> + <argument name="messageType" value="error" /> + </actionGroup> + + <actionGroup ref="AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup" stepKey="verifyCaptchaVisibleAfterFail" /> + + <!-- Submit form with correct captcha --> + <actionGroup ref="StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup" stepKey="fillNewCustomerAccountFormWithCorrectCaptcha"> + <argument name="customer" value="Simple_US_Customer" /> + <argument name="captcha" value="{{PreconfiguredCaptcha.value}}" /> + </actionGroup> + <actionGroup ref="StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup" stepKey="clickCreateAnAccountButtonAfterCorrectCaptcha" /> + <actionGroup ref="AssertMessageCustomerCreateAccountActionGroup" stepKey="assertSuccessMessage" /> + </test> +</tests> diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml new file mode 100644 index 0000000000000..36d7989b9acc1 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontResetCustomerPasswordFailedTest"> + <before> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDisableConfigData.path}} {{StorefrontCustomerCaptchaDisableConfigData.value}}" stepKey="disableCaptcha"/> + </before> + <after> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaEnableConfigData.path}} {{StorefrontCustomerCaptchaEnableConfigData.value}}" stepKey="enableCaptcha"/> + </after> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Category/Tree.php b/app/code/Magento/Catalog/Block/Adminhtml/Category/Tree.php index a67f55235b6df..83ec501592489 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Category/Tree.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Category/Tree.php @@ -71,7 +71,7 @@ public function __construct( } /** - * @return void + * @inheritdoc */ protected function _construct() { @@ -80,7 +80,7 @@ protected function _construct() } /** - * @return $this + * @inheritdoc */ protected function _prepareLayout() { @@ -182,6 +182,8 @@ public function getSuggestedCategoriesJson($namePart) } /** + * Get add root button html + * * @return string */ public function getAddRootButtonHtml() @@ -190,6 +192,8 @@ public function getAddRootButtonHtml() } /** + * Get add sub button html + * * @return string */ public function getAddSubButtonHtml() @@ -198,6 +202,8 @@ public function getAddSubButtonHtml() } /** + * Get expand button html + * * @return string */ public function getExpandButtonHtml() @@ -206,6 +212,8 @@ public function getExpandButtonHtml() } /** + * Get collapse button html + * * @return string */ public function getCollapseButtonHtml() @@ -214,6 +222,8 @@ public function getCollapseButtonHtml() } /** + * Get store switcher + * * @return string */ public function getStoreSwitcherHtml() @@ -222,6 +232,8 @@ public function getStoreSwitcherHtml() } /** + * Get loader tree url + * * @param bool|null $expanded * @return string */ @@ -235,6 +247,8 @@ public function getLoadTreeUrl($expanded = null) } /** + * Get nodes url + * * @return string */ public function getNodesUrl() @@ -243,6 +257,8 @@ public function getNodesUrl() } /** + * Get switcher tree url + * * @return string */ public function getSwitchTreeUrl() @@ -254,6 +270,8 @@ public function getSwitchTreeUrl() } /** + * Get is was expanded + * * @return bool * @SuppressWarnings(PHPMD.BooleanGetMethodName) */ @@ -263,6 +281,8 @@ public function getIsWasExpanded() } /** + * Get move url + * * @return string */ public function getMoveUrl() @@ -271,6 +291,8 @@ public function getMoveUrl() } /** + * Get tree + * * @param mixed|null $parenNodeCategory * @return array */ @@ -282,6 +304,8 @@ public function getTree($parenNodeCategory = null) } /** + * Get tree json + * * @param mixed|null $parenNodeCategory * @return string */ @@ -367,7 +391,7 @@ protected function _getNodeJson($node, $level = 0) } } - if ($isParent || $node->getLevel() < 2) { + if ($isParent || $node->getLevel() < 1) { $item['expanded'] = true; } @@ -390,6 +414,8 @@ public function buildNodeName($node) } /** + * Is category movable + * * @param Node|array $node * @return bool */ @@ -403,6 +429,8 @@ protected function _isCategoryMoveable($node) } /** + * Is parent selected category + * * @param Node|array $node * @return bool */ diff --git a/app/code/Magento/Catalog/Block/Product/View/GalleryOptions.php b/app/code/Magento/Catalog/Block/Product/View/GalleryOptions.php new file mode 100644 index 0000000000000..0384c9cd9acce --- /dev/null +++ b/app/code/Magento/Catalog/Block/Product/View/GalleryOptions.php @@ -0,0 +1,156 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Block\Product\View; + +use Magento\Framework\View\Element\Block\ArgumentInterface; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Catalog\Block\Product\Context; +use Magento\Framework\Stdlib\ArrayUtils; + +/** + * Gallery options block. + */ +class GalleryOptions extends AbstractView implements ArgumentInterface +{ + /** + * @var Json + */ + private $jsonSerializer; + + /** + * @var Gallery + */ + private $gallery; + + /** + * @param Context $context + * @param ArrayUtils $arrayUtils + * @param Json $jsonSerializer + * @param Gallery $gallery + * @param array $data + */ + public function __construct( + Context $context, + ArrayUtils $arrayUtils, + Json $jsonSerializer, + Gallery $gallery, + array $data = [] + ) { + $this->gallery = $gallery; + $this->jsonSerializer = $jsonSerializer; + parent::__construct($context, $arrayUtils, $data); + } + + /** + * Retrieve gallery options in JSON format + * + * @return string + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.ElseExpression) + */ + public function getOptionsJson() + { + $optionItems = null; + + //Special case for gallery/nav which can be the string "thumbs/false/dots" + if (is_bool($this->getVar("gallery/nav"))) { + $optionItems['nav'] = $this->getVar("gallery/nav") ? 'true' : 'false'; + } else { + $optionItems['nav'] = $this->escapeHtml($this->getVar("gallery/nav")); + } + + $optionItems['loop'] = $this->getVar("gallery/loop"); + $optionItems['keyboard'] = $this->getVar("gallery/keyboard"); + $optionItems['arrows'] = $this->getVar("gallery/arrows"); + $optionItems['allowfullscreen'] = $this->getVar("gallery/allowfullscreen"); + $optionItems['showCaption'] = $this->getVar("gallery/caption"); + $optionItems['width'] = (int)$this->escapeHtml( + $this->gallery->getImageAttribute('product_page_image_medium', 'width') + ); + $optionItems['thumbwidth'] = (int)$this->escapeHtml( + $this->gallery->getImageAttribute('product_page_image_small', 'width') + ); + + if ($this->gallery->getImageAttribute('product_page_image_small', 'height') || + $this->gallery->getImageAttribute('product_page_image_small', 'width')) { + $optionItems['thumbheight'] = (int)$this->escapeHtml( + $this->gallery->getImageAttribute('product_page_image_small', 'height') ?: + $this->gallery->getImageAttribute('product_page_image_small', 'width') + ); + } + + if ($this->gallery->getImageAttribute('product_page_image_medium', 'height') || + $this->gallery->getImageAttribute('product_page_image_medium', 'width')) { + $optionItems['height'] = (int)$this->escapeHtml( + $this->gallery->getImageAttribute('product_page_image_medium', 'height') ?: + $this->gallery->getImageAttribute('product_page_image_medium', 'width') + ); + } + + if ($this->getVar("gallery/transition/duration")) { + $optionItems['transitionduration'] = + (int)$this->escapeHtml($this->getVar("gallery/transition/duration")); + } + + $optionItems['transition'] = $this->escapeHtml($this->getVar("gallery/transition/effect")); + $optionItems['navarrows'] = $this->getVar("gallery/navarrows"); + $optionItems['navtype'] = $this->escapeHtml($this->getVar("gallery/navtype")); + $optionItems['navdir'] = $this->escapeHtml($this->getVar("gallery/navdir")); + + if ($this->getVar("gallery/thumbmargin")) { + $optionItems['thumbmargin'] = (int)$this->escapeHtml($this->getVar("gallery/thumbmargin")); + } + + return $this->jsonSerializer->serialize($optionItems); + } + + /** + * Retrieve gallery fullscreen options in JSON format + * + * @return string + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.ElseExpression) + */ + public function getFSOptionsJson() + { + $fsOptionItems = null; + + //Special case for gallery/nav which can be the string "thumbs/false/dots" + if (is_bool($this->getVar("gallery/fullscreen/nav"))) { + $fsOptionItems['nav'] = $this->getVar("gallery/fullscreen/nav") ? 'true' : 'false'; + } else { + $fsOptionItems['nav'] = $this->escapeHtml($this->getVar("gallery/fullscreen/nav")); + } + + $fsOptionItems['loop'] = $this->getVar("gallery/fullscreen/loop"); + $fsOptionItems['navdir'] = $this->escapeHtml($this->getVar("gallery/fullscreen/navdir")); + $fsOptionItems['navarrows'] = $this->getVar("gallery/fullscreen/navarrows"); + $fsOptionItems['navtype'] = $this->escapeHtml($this->getVar("gallery/fullscreen/navtype")); + $fsOptionItems['arrows'] = $this->getVar("gallery/fullscreen/arrows"); + $fsOptionItems['showCaption'] = $this->getVar("gallery/fullscreen/caption"); + + if ($this->getVar("gallery/fullscreen/transition/duration")) { + $fsOptionItems['transitionduration'] = (int)$this->escapeHtml( + $this->getVar("gallery/fullscreen/transition/duration") + ); + } + + $fsOptionItems['transition'] = $this->escapeHtml($this->getVar("gallery/fullscreen/transition/effect")); + + if ($this->getVar("gallery/fullscreen/keyboard")) { + $fsOptionItems['keyboard'] = $this->getVar("gallery/fullscreen/keyboard"); + } + + if ($this->getVar("gallery/fullscreen/thumbmargin")) { + $fsOptionItems['thumbmargin'] = + (int)$this->escapeHtml($this->getVar("gallery/fullscreen/thumbmargin")); + } + + return $this->jsonSerializer->serialize($fsOptionItems); + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php b/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php index 0941aa2478935..9cb6cda4d0a09 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php +++ b/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Model\Product\Option; use Magento\Catalog\Api\ProductCustomOptionRepositoryInterface as OptionRepository; @@ -58,11 +60,26 @@ public function execute($entity, $arguments = []) } } if ($options) { - foreach ($options as $option) { - $this->optionRepository->save($option); - } + $this->processOptionsSaving($options, (bool)$entity->dataHasChangedFor('sku'), (string)$entity->getSku()); } return $entity; } + + /** + * Save custom options + * + * @param array $options + * @param bool $hasChangedSku + * @param string $newSku + */ + private function processOptionsSaving(array $options, bool $hasChangedSku, string $newSku) + { + foreach ($options as $option) { + if ($hasChangedSku && $option->hasData('product_sku')) { + $option->setProductSku($newSku); + } + $this->optionRepository->save($option); + } + } } diff --git a/app/code/Magento/Catalog/Observer/SetSpecialPriceStartDate.php b/app/code/Magento/Catalog/Observer/SetSpecialPriceStartDate.php index a597b8fddda9f..ed9f89efc6891 100644 --- a/app/code/Magento/Catalog/Observer/SetSpecialPriceStartDate.php +++ b/app/code/Magento/Catalog/Observer/SetSpecialPriceStartDate.php @@ -3,9 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Observer; use Magento\Framework\Event\ObserverInterface; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; /** * Set value for Special Price start date @@ -13,21 +16,20 @@ class SetSpecialPriceStartDate implements ObserverInterface { /** - * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface + * @var TimezoneInterface */ private $localeDate; /** - * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate - * @codeCoverageIgnore + * @param TimezoneInterface $localeDate */ - public function __construct(\Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate) + public function __construct(TimezoneInterface $localeDate) { $this->localeDate = $localeDate; } /** - * Set the current date to Special Price From attribute if it empty + * Set the current date to Special Price From attribute if it's empty. * * @param \Magento\Framework\Event\Observer $observer * @return $this @@ -36,8 +38,8 @@ public function execute(\Magento\Framework\Event\Observer $observer) { /** @var $product \Magento\Catalog\Model\Product */ $product = $observer->getEvent()->getProduct(); - if ($product->getSpecialPrice() && !$product->getSpecialFromDate()) { - $product->setData('special_from_date', $this->localeDate->date()); + if ($product->getSpecialPrice() && ! $product->getSpecialFromDate()) { + $product->setData('special_from_date', $this->localeDate->date()->setTime(0, 0)); } return $this; diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml index 2d966dde64c4a..b914d5e20712d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml @@ -52,4 +52,51 @@ <selectOption selector="{{AdminProductCustomizableOptionsSection.optionPriceType('0')}}" userInput="{{option.price_type}}" stepKey="selectPriceType"/> <fillField selector="{{AdminProductCustomizableOptionsSection.optionFileExtensions('0')}}" userInput="{{option.file_extension}}" stepKey="fillCompatibleExtensions"/> </actionGroup> + <actionGroup name="AddProductCustomOptionField"> + <arguments> + <argument name="option" defaultValue="ProductOptionField"/> + <argiment name="optionIndex" type="string"/> + </arguments> + <conditionalClick selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" dependentSelector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" visible="false" stepKey="openCustomOptionSection"/> + <click selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" stepKey="clickAddOption"/> + <waitForElementVisible selector="{{AdminProductCustomizableOptionsSection.lastOptionTitle}}" stepKey="waitForOption"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.lastOptionTitle}}" userInput="{{option.title}}" stepKey="fillTitle"/> + <click selector="{{AdminProductCustomizableOptionsSection.lastOptionTypeParent}}" stepKey="openTypeSelect"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionType('Field')}}" stepKey="selectTypeFile"/> + <waitForElementVisible selector="{{AdminProductCustomizableOptionsSection.optionPrice(optionIndex)}}" stepKey="waitForElements"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionPrice(optionIndex)}}" userInput="{{option.price}}" stepKey="fillPrice"/> + <selectOption selector="{{AdminProductCustomizableOptionsSection.optionPriceType(optionIndex)}}" userInput="{{option.price_type}}" stepKey="selectPriceType"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionSku(optionIndex)}}" userInput="{{option.title}}" stepKey="fillSku"/> + </actionGroup> + <actionGroup name="importProductCustomizableOptions"> + <arguments> + <argument name="productName" type="string"/> + </arguments> + <click selector="{{AdminProductCustomizableOptionsSection.importOptions}}" stepKey="clickImportOptions"/> + <waitForElementVisible selector="{{AdminProductImportOptionsSection.selectProductTitle}}" stepKey="waitForTitleVisible"/> + <conditionalClick selector="{{AdminProductImportOptionsSection.resetFiltersButton}}" dependentSelector="{{AdminProductImportOptionsSection.resetFiltersButton}}" visible="true" stepKey="clickResetFilters"/> + <click selector="{{AdminProductImportOptionsSection.filterButton}}" stepKey="clickFilterButton"/> + <waitForElementVisible selector="{{AdminProductImportOptionsSection.nameField}}" stepKey="waitForNameField"/> + <fillField selector="{{AdminProductImportOptionsSection.nameField}}" userInput="{{productName}}" stepKey="fillProductName"/> + <click selector="{{AdminProductImportOptionsSection.applyFiltersButton}}" stepKey="clickApplyFilters"/> + <checkOption selector="{{AdminProductImportOptionsSection.firstRowItemCheckbox}}" stepKey="checkProductCheckbox"/> + <click selector="{{AdminProductImportOptionsSection.importButton}}" stepKey="clickImport"/> + </actionGroup> + <actionGroup name="resetImportOptionFilter"> + <conditionalClick selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" dependentSelector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" visible="false" stepKey="openCustomOptionSection"/> + <click selector="{{AdminProductCustomizableOptionsSection.importOptions}}" stepKey="clickImportOptions"/> + <click selector="{{AdminProductImportOptionsSection.resetFiltersButton}}" stepKey="clickResetFilterButton"/> + </actionGroup> + <actionGroup name="checkCustomizableOptionImport"> + <arguments> + <argument name="option" defaultValue="ProductOptionField"/> + <argiment name="optionIndex" type="string"/> + </arguments> + <grabValueFrom selector="{{AdminProductCustomizableOptionsSection.optionTitleInput(optionIndex)}}" stepKey="grabOptionTitle"/> + <grabValueFrom selector="{{AdminProductCustomizableOptionsSection.optionPrice(optionIndex)}}" stepKey="grabOptionPrice"/> + <grabValueFrom selector="{{AdminProductCustomizableOptionsSection.optionSku(optionIndex)}}" stepKey="grabOptionSku"/> + <assertEquals expected="{{option.title}}" expectedType="string" actual="$grabOptionTitle" stepKey="assertOptionTitle"/> + <assertEquals expected="{{option.price}}" expectedType="string" actual="$grabOptionPrice" stepKey="assertOptionPrice"/> + <assertEquals expected="{{option.title}}" expectedType="string" actual="$grabOptionSku" stepKey="assertOptionSku"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ConstData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ConstData.xml index 8a26b6babdbbc..d09880f14afbf 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ConstData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ConstData.xml @@ -13,4 +13,8 @@ <data key="one">1</data> <data key="two">2</data> </entity> + <entity name="prodNameWithSpecChars"> + <data key="trademark">"Pursuit Lumaflex™ Tone Band"</data> + <data key="skumark">"x™"</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml index 383797933074e..ba4a623e35def 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml @@ -35,6 +35,10 @@ <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> </entity> + <entity name="ApiSimpleProductWithSpecCharInName" type="product" extends="ApiSimpleProduct"> + <data key="name">Pursuit Lumaflex&trade; Tone Band</data> + <data key="sku" unique="suffix">x&trade;</data> + </entity> <entity name="ApiSimpleProductWithCustomPrice" type="product" extends="ApiSimpleProduct"> <data key="price">100</data> </entity> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml index 14e714cb2b6b7..fba28b3feaff1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml @@ -16,5 +16,6 @@ <element name="categoryInTreeUnderRoot" type="text" selector="//li/ul/li[@class='x-tree-node']/div/a/span[contains(text(), '{{name}}')]" parameterized="true"/> <element name="lastCreatedCategory" type="block" selector=".x-tree-root-ct li li:last-child" /> <element name="treeContainer" type="block" selector=".tree-holder" /> + <element name="expandRootCategory" type="text" selector="img.x-tree-elbow-end-plus"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml index fc78c25ec49fa..352d219351fb8 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml @@ -45,5 +45,15 @@ <element name="optionPriceType" type="select" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr//*[@name='product[options][{{var}}][price_type]']" parameterized="true"/> <element name="optionSku" type="input" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr//*[@name='product[options][{{index}}][sku]']" parameterized="true"/> <element name="optionFileExtensions" type="input" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr//*[@name='product[options][{{index}}][file_extension]']" parameterized="true"/> + <element name="importOptions" type="button" selector="//button[@data-index='button_import']" timeout="30"/> </section> -</sections> \ No newline at end of file + <section name="AdminProductImportOptionsSection"> + <element name="selectProductTitle" type="text" selector="//h1[contains(text(), 'Select Product')]" timeout="30"/> + <element name="filterButton" type="button" selector="//button[@data-action='grid-filter-expand']" timeout="30"/> + <element name="nameField" type="input" selector="//input[@name='name']" timeout="30"/> + <element name="applyFiltersButton" type="button" selector="//button[@data-action='grid-filter-apply']" timeout="30"/> + <element name="resetFiltersButton" type="button" selector="//button[@data-action='grid-filter-reset']" timeout="30"/> + <element name="firstRowItemCheckbox" type="input" selector="//input[@data-action='select-row']" timeout="30"/> + <element name="importButton" type="button" selector="//button[contains(@class, 'action-primary')]/span[contains(text(), 'Import')]" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml index 02bdbac313076..07dd26381fe08 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml @@ -17,6 +17,7 @@ <element name="productGridElement2" type="text" selector="#addselector" /> <element name="productGridRows" type="text" selector="table.data-grid tr.data-row"/> <element name="firstProductRow" type="text" selector="table.data-grid tr.data-row:first-of-type"/> + <element name="firstProductRowName" type="text" selector="table.data-grid tr.data-row:first-of-type > td:nth-of-type(4)"/> <element name="firstProductRowEditButton" type="button" selector="table.data-grid tr.data-row td .action-menu-item:first-of-type"/> <element name="productThumbnail" type="text" selector="table.data-grid tr:nth-child({{row}}) td.data-grid-thumbnail-cell > img" parameterized="true"/> <element name="productThumbnailBySrc" type="text" selector="img.admin__control-thumbnail[src*='{{pattern}}']" parameterized="true"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml index c6bad0efb3ca7..292b2d7008bc1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml @@ -12,5 +12,6 @@ <element name="subCategory" type="button" selector="//ul[contains(@class,'submenu')]//span[contains(text(),'{{var1}}')]" parameterized="true"/> <element name="breadcrumbs" type="textarea" selector=".items"/> <element name="categoryBreadcrumbs" type="textarea" selector=".breadcrumbs li"/> + <element name="categoryBreadcrumbsByNumber" type="textarea" selector=".breadcrumbs li:nth-of-type({{number}})" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml index 3027416ee520b..bcfab6ccfdf1f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml @@ -34,7 +34,7 @@ <createData entity="CatalogAttributeSet" stepKey="createAttributeSet"/> <actionGroup ref="LoginAsAdmin" stepKey="login"/> - <amOnPage url="{{AdminProductAttributeSetEditPage.url}}/$$createAttributeSet.attribute_set_id$$}}/" stepKey="onAttributeSetEdit"/> + <amOnPage url="{{AdminProductAttributeSetEditPage.url}}/$$createAttributeSet.attribute_set_id$$/" stepKey="onAttributeSetEdit"/> <actionGroup ref="AssignAttributeToGroup" stepKey="assignAttributeToGroup"> <argument name="group" value="Product Details"/> <argument name="attribute" value="$$createProductAttribute.attribute_code$$"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilterByNameByStoreViewOnProductGridTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilterByNameByStoreViewOnProductGridTest.xml new file mode 100644 index 0000000000000..f3ec225540c75 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilterByNameByStoreViewOnProductGridTest.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminFilterByNameByStoreViewOnProductGridTest"> + <annotations> + <features value="Catalog"/> + <stories value="Filter products"/> + <title value="Product grid filtering by store view level attribute"/> + <description value="Verify that products grid can be filtered on all store view level by attribute"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-98755"/> + <useCaseId value="MAGETWO-98335"/> + <group value="catalog"/> + </annotations> + <before> + <createData entity="SimpleProduct2" stepKey="createSimpleProduct"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <actionGroup ref="ClearProductsFilterActionGroup" stepKey="clearProductsFilter"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <amOnPage url="{{AdminProductEditPage.url($$createSimpleProduct.id$$)}}" stepKey="goToEditPage"/> + <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="switchToDefaultStoreView"> + <argument name="storeView" value="_defaultStore.name"/> + </actionGroup> + <scrollToTopOfPage stepKey="scrollToTopOfAdminProductFormSection"/> + <click selector="{{AdminProductFormSection.productNameUseDefault}}" stepKey="uncheckUseDefault"/> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{SimpleProduct.name}}" stepKey="fillNewName"/> + <actionGroup ref="saveProductForm" stepKey="saveSimpleProduct"/> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> + <actionGroup ref="filterProductGridByName" stepKey="filterGridByName"> + <argument name="product" value="SimpleProduct"/> + </actionGroup> + <see selector="{{AdminProductGridSection.firstProductRow}}" userInput="{{SimpleProduct2.name}}" stepKey="seeProductNameInGrid"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml new file mode 100644 index 0000000000000..79ff7bcade77b --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminImportCustomizableOptionToProductWithSKUTest"> + <annotations> + <features value="Catalog"/> + <title value="Import customizable options to a product with existing SKU"/> + <description value="Import customizable options to a product with existing SKU"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-98211"/> + <useCaseId value="MAGETWO-70232"/> + <group value="catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create category--> + <comment userInput="Create category" stepKey="commentCreateCategory"/> + <createData entity="ApiCategory" stepKey="createCategory"/> + <!-- Create two product --> + <comment userInput="Create two product" stepKey="commentCreateTwoProduct"/> + <createData entity="SimpleProduct2" stepKey="createFirstProduct"/> + <createData entity="ApiSimpleProduct" stepKey="createSecondProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <!--Delete second product with changed sku--> + <comment userInput="Delete second product with changed sku" stepKey="commentDeleteProduct"/> + <actionGroup ref="deleteProductBySku" stepKey="deleteSecondProduct"> + <argument name="sku" value="$$createFirstProduct.sku$$-1"/> + </actionGroup> + <!--Delete created data--> + <comment userInput="Delete created data" stepKey="commentDeleteCreatedData"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createFirstProduct" stepKey="deleteFirstProduct"/> + <actionGroup ref="logout" stepKey="logoutOfAdmin"/> + </after> + <!--Go to product page --> + <comment userInput="Go to product page" stepKey="commentGoToProductPage"/> + <amOnPage url="{{AdminProductEditPage.url($$createFirstProduct.id$$)}}" stepKey="goToProductEditPage"/> + <waitForPageLoad stepKey="waitForProductEditPageLoad"/> + <actionGroup ref="AddProductCustomOptionField" stepKey="addCutomOption1"> + <argument name="option" value="ProductOptionField"/> + <argument name="optionIndex" value="0"/> + </actionGroup> + <actionGroup ref="AddProductCustomOptionField" stepKey="addCutomOption2"> + <argument name="option" value="ProductOptionField2"/> + <argument name="optionIndex" value="1"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + <!--Change second product sku to first product sku--> + <comment userInput="Change second product sku to first product sku" stepKey="commentChangeSecondProduct"/> + <amOnPage url="{{AdminProductEditPage.url($$createSecondProduct.id$$)}}" stepKey="goToProductEditPage1"/> + <waitForPageLoad stepKey="waitForProductEditPageLoad1"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="$$createFirstProduct.sku$$" stepKey="fillProductSku1"/> + <!--Import customizable options and check--> + <comment userInput="Import customizable options and check" stepKey="commentImportOptions"/> + <conditionalClick selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" dependentSelector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" visible="false" stepKey="openCustomOptionSection"/> + <actionGroup ref="importProductCustomizableOptions" stepKey="importOptions"> + <argument name="productName" value="$$createFirstProduct.name$$"/> + </actionGroup> + <actionGroup ref="checkCustomizableOptionImport" stepKey="checkFirstOptionImport"> + <argument name="option" value="ProductOptionField"/> + <argument name="optionIndex" value="0"/> + </actionGroup> + <actionGroup ref="checkCustomizableOptionImport" stepKey="checkSecondOptionImport"> + <argument name="option" value="ProductOptionField2"/> + <argument name="optionIndex" value="1"/> + </actionGroup> + <!--Save product and check sku changed message--> + <comment userInput="Save product and check sku changed message" stepKey="commentSAveProductAndCheck"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct1"/> + <see userInput="SKU for product $$createSecondProduct.name$$ has been changed to $$createFirstProduct.sku$$-1." stepKey="seeSkuChangedMessage"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml index 060720ab007eb..8316f54c15a52 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml @@ -65,6 +65,8 @@ </actionGroup> <deleteData createDataKey="category" stepKey="deletePreReqCategory"/> <deleteData createDataKey="product" stepKey="deleteFirstProduct"/> + <magentoCLI stepKey="reindex" command="indexer:reindex"/> + <magentoCLI stepKey="flushCache" command="cache:flush"/> <actionGroup ref="logout" stepKey="logout"/> </after> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml index 3086f4398e08d..51af9d78dfd46 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml @@ -11,8 +11,8 @@ <test name="AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest"> <annotations> <features value="Catalog"/> - <title value="Check that 'trie price' block not available for simple product from options without 'trie price'"/> - <description value="Check that 'trie price' block not available for simple product from options without 'trie price'"/> + <title value="Check that 'tier price' block not available for simple product from options without 'tier price'"/> + <description value="Check that 'tier price' block not available for simple product from options without 'tier price'"/> <severity value="MAJOR"/> <testCaseId value="MAGETWO-97050"/> <useCaseId value="MAGETWO-96842"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest.xml index 52022f32fd8ec..d895993217e32 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest.xml @@ -262,10 +262,10 @@ </actionGroup> <click stepKey="openAttributes" selector="{{AdminProductAttributesSection.sectionHeader}}"/> <waitForElementVisible selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttribute.attribute_code)}}" stepKey="waitforLabel"/> - <seeOptionIsSelected selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttribute.attribute_code)}}" userInput="{{dropdownProductAttribute.option3_frontend}}" stepKey="seeDefaultIsCorrect"/> - <see stepKey="seeOption1Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttribute.attribute_code)}}" userInput="{{dropdownProductAttribute.option1_frontend}}"/> - <see stepKey="seeOption2Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttribute.attribute_code)}}" userInput="{{dropdownProductAttribute.option2_frontend}}"/> - <see stepKey="seeOption3Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttribute.attribute_code)}}" userInput="{{dropdownProductAttribute.option3_frontend}}"/> + <seeOptionIsSelected selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttribute.attribute_code)}}" userInput="{{dropdownProductAttribute.option3_admin}}" stepKey="seeDefaultIsCorrect"/> + <see stepKey="seeOption1Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttribute.attribute_code)}}" userInput="{{dropdownProductAttribute.option1_admin}}"/> + <see stepKey="seeOption2Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttribute.attribute_code)}}" userInput="{{dropdownProductAttribute.option2_admin}}"/> + <see stepKey="seeOption3Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttribute.attribute_code)}}" userInput="{{dropdownProductAttribute.option3_admin}}"/> </test> <test name="CreateProductAttributeEntityDropdownWithSingleQuoteTest"> @@ -333,8 +333,8 @@ </actionGroup> <click stepKey="openAttributes" selector="{{AdminProductAttributesSection.sectionHeader}}"/> <waitForElementVisible selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttributeWithQuote.attribute_code)}}" stepKey="waitforLabel"/> - <seeOptionIsSelected selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttributeWithQuote.attribute_code)}}" userInput="{{dropdownProductAttributeWithQuote.option1_frontend}}" stepKey="seeDefaultIsCorrect"/> - <see stepKey="seeOption1Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttributeWithQuote.attribute_code)}}" userInput="{{dropdownProductAttributeWithQuote.option1_frontend}}"/> + <seeOptionIsSelected selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttributeWithQuote.attribute_code)}}" userInput="{{dropdownProductAttributeWithQuote.option1_admin}}" stepKey="seeDefaultIsCorrect"/> + <see stepKey="seeOption1Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttributeWithQuote.attribute_code)}}" userInput="{{dropdownProductAttributeWithQuote.option1_admin}}"/> </test> <test name="CreateProductAttributeEntityMultiSelectTest"> @@ -417,9 +417,9 @@ </actionGroup> <click stepKey="openAttributes" selector="{{AdminProductAttributesSection.sectionHeader}}"/> <waitForElementVisible selector="{{AdminProductAttributesSection.attributeDropdownByCode(multiselectProductAttribute.attribute_code)}}" stepKey="waitforLabel"/> - <seeOptionIsSelected selector="{{AdminProductAttributesSection.attributeDropdownByCode(multiselectProductAttribute.attribute_code)}}" userInput="{{multiselectProductAttribute.option3_frontend}}" stepKey="seeDefaultIsCorrect"/> - <see stepKey="seeOption1Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(multiselectProductAttribute.attribute_code)}}" userInput="{{multiselectProductAttribute.option1_frontend}}"/> - <see stepKey="seeOption2Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(multiselectProductAttribute.attribute_code)}}" userInput="{{multiselectProductAttribute.option2_frontend}}"/> - <see stepKey="seeOption3Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(multiselectProductAttribute.attribute_code)}}" userInput="{{multiselectProductAttribute.option3_frontend}}"/> + <seeOptionIsSelected selector="{{AdminProductAttributesSection.attributeDropdownByCode(multiselectProductAttribute.attribute_code)}}" userInput="{{multiselectProductAttribute.option3_admin}}" stepKey="seeDefaultIsCorrect"/> + <see stepKey="seeOption1Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(multiselectProductAttribute.attribute_code)}}" userInput="{{multiselectProductAttribute.option1_admin}}"/> + <see stepKey="seeOption2Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(multiselectProductAttribute.attribute_code)}}" userInput="{{multiselectProductAttribute.option2_admin}}"/> + <see stepKey="seeOption3Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(multiselectProductAttribute.attribute_code)}}" userInput="{{multiselectProductAttribute.option3_admin}}"/> </test> </tests> diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Product/View/GalleryOptionsTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/View/GalleryOptionsTest.php new file mode 100644 index 0000000000000..7ed8b13fce750 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/View/GalleryOptionsTest.php @@ -0,0 +1,223 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Test\Unit\Block\Product\View; + +use Magento\Catalog\Block\Product\Context; +use Magento\Catalog\Block\Product\View\Gallery; +use Magento\Catalog\Block\Product\View\GalleryOptions; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\Escaper; +use Magento\Framework\View\Config; +use Magento\Framework\Config\View; +use Magento\Framework\Serialize\Serializer\Json; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class GalleryOptionsTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var GalleryOptions + */ + private $model; + + /** + * @var Gallery|\PHPUnit_Framework_MockObject_MockObject + */ + private $gallery; + + /** + * @var Context|\PHPUnit_Framework_MockObject_MockObject + */ + private $context; + + /** + * @var Json + */ + private $jsonSerializer; + + /** + * @var View|\PHPUnit_Framework_MockObject_MockObject + */ + private $configView; + + /** + * @var Config|\PHPUnit_Framework_MockObject_MockObject + */ + private $viewConfig; + + /** + * @var Escaper + */ + private $escaper; + + protected function setUp() + { + $objectManager = new ObjectManager($this); + + $this->escaper = $objectManager->getObject(Escaper::class); + $this->configView = $this->createMock(View::class); + + $this->viewConfig = $this->createConfiguredMock( + Config::class, + [ + 'getViewConfig' => $this->configView + ] + ); + + $this->context = $this->createConfiguredMock( + Context::class, + [ + 'getEscaper' => $this->escaper, + 'getViewConfig' => $this->viewConfig + ] + ); + + $this->gallery = $this->createMock(Gallery::class); + + $this->jsonSerializer = $objectManager->getObject( + Json::class + ); + + $this->model = $objectManager->getObject(GalleryOptions::class, [ + 'context' => $this->context, + 'jsonSerializer' => $this->jsonSerializer, + 'gallery' => $this->gallery + ]); + } + + public function testGetOptionsJson() + { + $configMap = [ + ['Magento_Catalog', 'gallery/nav', 'thumbs'], + ['Magento_Catalog', 'gallery/loop', false], + ['Magento_Catalog', 'gallery/keyboard', true], + ['Magento_Catalog', 'gallery/arrows', true], + ['Magento_Catalog', 'gallery/caption', false], + ['Magento_Catalog', 'gallery/allowfullscreen', true], + ['Magento_Catalog', 'gallery/navdir', 'horizontal'], + ['Magento_Catalog', 'gallery/navarrows', true], + ['Magento_Catalog', 'gallery/navtype', 'slides'], + ['Magento_Catalog', 'gallery/thumbmargin', '5'], + ['Magento_Catalog', 'gallery/transition/effect', 'slide'], + ['Magento_Catalog', 'gallery/transition/duration', '500'], + ]; + + $imageAttributesMap = [ + ['product_page_image_medium','height',null, 100], + ['product_page_image_medium','width',null, 200], + ['product_page_image_small','height',null, 300], + ['product_page_image_small','width',null, 400] + ]; + + $this->configView->expects($this->any()) + ->method('getVarValue') + ->will($this->returnValueMap($configMap)); + $this->gallery->expects($this->any()) + ->method('getImageAttribute') + ->will($this->returnValueMap($imageAttributesMap)); + + $json = $this->model->getOptionsJson(); + + $decodedJson = $this->jsonSerializer->unserialize($json); + + $this->assertSame('thumbs', $decodedJson['nav']); + $this->assertSame(false, $decodedJson['loop']); + $this->assertSame(true, $decodedJson['keyboard']); + $this->assertSame(true, $decodedJson['arrows']); + $this->assertSame(false, $decodedJson['showCaption']); + $this->assertSame(true, $decodedJson['allowfullscreen']); + $this->assertSame('horizontal', $decodedJson['navdir']); + $this->assertSame(true, $decodedJson['navarrows']); + $this->assertSame('slides', $decodedJson['navtype']); + $this->assertSame(5, $decodedJson['thumbmargin']); + $this->assertSame('slide', $decodedJson['transition']); + $this->assertSame(500, $decodedJson['transitionduration']); + $this->assertSame(100, $decodedJson['height']); + $this->assertSame(200, $decodedJson['width']); + $this->assertSame(300, $decodedJson['thumbheight']); + $this->assertSame(400, $decodedJson['thumbwidth']); + } + + public function testGetFSOptionsJson() + { + $configMap = [ + ['Magento_Catalog', 'gallery/fullscreen/nav', false], + ['Magento_Catalog', 'gallery/fullscreen/loop', true], + ['Magento_Catalog', 'gallery/fullscreen/keyboard', true], + ['Magento_Catalog', 'gallery/fullscreen/arrows', false], + ['Magento_Catalog', 'gallery/fullscreen/caption', true], + ['Magento_Catalog', 'gallery/fullscreen/navdir', 'vertical'], + ['Magento_Catalog', 'gallery/fullscreen/navarrows', false], + ['Magento_Catalog', 'gallery/fullscreen/navtype', 'thumbs'], + ['Magento_Catalog', 'gallery/fullscreen/thumbmargin', '10'], + ['Magento_Catalog', 'gallery/fullscreen/transition/effect', 'dissolve'], + ['Magento_Catalog', 'gallery/fullscreen/transition/duration', '300'] + ]; + + $this->configView->expects($this->any()) + ->method('getVarValue') + ->will($this->returnValueMap($configMap)); + + $json = $this->model->getFSOptionsJson(); + + $decodedJson = $this->jsonSerializer->unserialize($json); + + //Note, this tests the special case for nav variable set to false. It + //Should not be converted to boolean. + $this->assertSame('false', $decodedJson['nav']); + $this->assertSame(true, $decodedJson['loop']); + $this->assertSame(false, $decodedJson['arrows']); + $this->assertSame(true, $decodedJson['keyboard']); + $this->assertSame(true, $decodedJson['showCaption']); + $this->assertSame('vertical', $decodedJson['navdir']); + $this->assertSame(false, $decodedJson['navarrows']); + $this->assertSame(10, $decodedJson['thumbmargin']); + $this->assertSame('thumbs', $decodedJson['navtype']); + $this->assertSame('dissolve', $decodedJson['transition']); + $this->assertSame(300, $decodedJson['transitionduration']); + } + + public function testGetOptionsJsonOptionals() + { + $configMap = [ + ['Magento_Catalog', 'gallery/fullscreen/thumbmargin', false], + ['Magento_Catalog', 'gallery/fullscreen/transition/duration', false] + ]; + + $this->configView->expects($this->any()) + ->method('getVarValue') + ->will($this->returnValueMap($configMap)); + + $json = $this->model->getOptionsJson(); + + $decodedJson = $this->jsonSerializer->unserialize($json); + + $this->assertArrayNotHasKey('thumbmargin', $decodedJson); + $this->assertArrayNotHasKey('transitionduration', $decodedJson); + } + + public function testGetFSOptionsJsonOptionals() + { + $configMap = [ + ['Magento_Catalog', 'gallery/fullscreen/keyboard', false], + ['Magento_Catalog', 'gallery/fullscreen/thumbmargin', false], + ['Magento_Catalog', 'gallery/fullscreen/transition/duration', false] + ]; + + $this->configView->expects($this->any()) + ->method('getVarValue') + ->will($this->returnValueMap($configMap)); + + $json = $this->model->getFSOptionsJson(); + + $decodedJson = $this->jsonSerializer->unserialize($json); + + $this->assertArrayNotHasKey('thumbmargin', $decodedJson); + $this->assertArrayNotHasKey('keyboard', $decodedJson); + $this->assertArrayNotHasKey('transitionduration', $decodedJson); + } +} diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php old mode 100755 new mode 100644 index 99f7122efa0a8..8326c3b531892 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php @@ -676,7 +676,7 @@ public function setupAttributeMeta(ProductAttributeInterface $attribute, $groupC // TODO: Refactor to $attribute->getOptions() when MAGETWO-48289 is done $attributeModel = $this->getAttributeModel($attribute); if ($attributeModel->usesSource()) { - $options = $attributeModel->getSource()->getAllOptions(); + $options = $attributeModel->getSource()->getAllOptions(true, true); $meta = $this->arrayManager->merge($configPath, $meta, [ 'options' => $this->convertOptionsValueToString($options), ]); diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php index f4334bc25efd8..e5451c8e49847 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php @@ -5,10 +5,16 @@ */ namespace Magento\Catalog\Ui\DataProvider\Product; +use Magento\Catalog\Model\ResourceModel\Eav\Attribute; +use Magento\Framework\Exception\LocalizedException; +use Magento\Eav\Model\Entity\Attribute\AttributeInterface; + /** * Collection which is used for rendering product list in the backend. * * Used for product grid and customizes behavior of the default Product collection for grid needs. + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class ProductCollection extends \Magento\Catalog\Model\ResourceModel\Product\Collection { @@ -25,4 +31,63 @@ protected function _productLimitationJoinPrice() $this->_productLimitationFilters->setUsePriceIndex(false); return $this->_productLimitationPrice(true); } + + /** + * Add attribute filter to collection + * + * @param AttributeInterface|integer|string|array $attribute + * @param null|string|array $condition + * @param string $joinType + * @return $this + * @throws LocalizedException + */ + public function addAttributeToFilter($attribute, $condition = null, $joinType = 'inner') + { + $storeId = (int)$this->getStoreId(); + if ($attribute === 'is_saleable' + || is_array($attribute) + || $storeId !== $this->getDefaultStoreId() + ) { + return parent::addAttributeToFilter($attribute, $condition, $joinType); + } + + if ($attribute instanceof AttributeInterface) { + $attributeModel = $attribute; + } else { + $attributeModel = $this->getEntity()->getAttribute($attribute); + if ($attributeModel === false) { + throw new LocalizedException( + __('Invalid attribute identifier for filter (%1)', get_class($attribute)) + ); + } + } + + if ($attributeModel->isScopeGlobal() || $attributeModel->getBackend()->isStatic()) { + return parent::addAttributeToFilter($attribute, $condition, $joinType); + } + + $this->addAttributeToFilterAllStores($attributeModel, $condition); + + return $this; + } + + /** + * Add attribute to filter by all stores + * + * @param Attribute $attributeModel + * @param array $condition + * @return void + */ + private function addAttributeToFilterAllStores(Attribute $attributeModel, array $condition): void + { + $tableName = $this->getTable($attributeModel->getBackendTable()); + $entity = $this->getEntity(); + $fKey = 'e.' . $this->getEntityPkName($entity); + $pKey = $tableName . '.' . $this->getEntityPkName($entity); + $condition = "({$pKey} = {$fKey}) AND (" + . $this->_getConditionSql("{$tableName}.value", $condition) + . ')'; + $selectExistsInAllStores = $this->getConnection()->select()->from($tableName); + $this->getSelect()->exists($selectExistsInAllStores, $condition); + } } diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml index 578281f44c4cf..d689daef4bcab 100644 --- a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml +++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml @@ -132,6 +132,7 @@ <settings> <addField>true</addField> <filter>text</filter> + <bodyTmpl>ui/grid/cells/html</bodyTmpl> <label translate="true">Name</label> </settings> </column> @@ -154,6 +155,7 @@ <column name="sku" sortOrder="60"> <settings> <filter>text</filter> + <bodyTmpl>ui/grid/cells/html</bodyTmpl> <label translate="true">SKU</label> </settings> </column> diff --git a/app/code/Magento/Catalog/view/frontend/layout/catalog_product_view.xml b/app/code/Magento/Catalog/view/frontend/layout/catalog_product_view.xml index 8d3248896b434..4283e3be5a9bf 100644 --- a/app/code/Magento/Catalog/view/frontend/layout/catalog_product_view.xml +++ b/app/code/Magento/Catalog/view/frontend/layout/catalog_product_view.xml @@ -121,7 +121,11 @@ </arguments> </block> </container> - <block class="Magento\Catalog\Block\Product\View\Gallery" name="product.info.media.image" template="Magento_Catalog::product/view/gallery.phtml"/> + <block class="Magento\Catalog\Block\Product\View\Gallery" name="product.info.media.image" template="Magento_Catalog::product/view/gallery.phtml"> + <arguments> + <argument name="gallery_options" xsi:type="object">Magento\Catalog\Block\Product\View\GalleryOptions</argument> + </arguments> + </block> <container name="skip_gallery_after.wrapper" htmlTag="div" htmlClass="action-skip-wrapper"> <block class="Magento\Framework\View\Element\Template" after="product.info.media.image" name="skip_gallery_after" template="Magento_Theme::html/skip.phtml"> <arguments> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/gallery.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/gallery.phtml index 1bfa30478df8a..a810dc2916152 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/gallery.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/gallery.phtml @@ -45,44 +45,8 @@ "mixins":["magnifier/magnify"], "magnifierOpts": <?= /* @escapeNotVerified */ $block->getMagnifier() ?>, "data": <?= /* @escapeNotVerified */ $block->getGalleryImagesJson() ?>, - "options": { - "nav": "<?= /* @escapeNotVerified */ $block->getVar("gallery/nav") ?>", - "loop": <?= /* @escapeNotVerified */ $block->getVar("gallery/loop") ? 'true' : 'false' ?>, - "keyboard": <?= /* @escapeNotVerified */ $block->getVar("gallery/keyboard") ? 'true' : 'false' ?>, - "arrows": <?= /* @escapeNotVerified */ $block->getVar("gallery/arrows") ? 'true' : 'false' ?>, - "allowfullscreen": <?= /* @escapeNotVerified */ $block->getVar("gallery/allowfullscreen") ? 'true' : 'false' ?>, - "showCaption": <?= /* @escapeNotVerified */ $block->getVar("gallery/caption") ? 'true' : 'false' ?>, - "width": "<?= /* @escapeNotVerified */ $block->getImageAttribute('product_page_image_medium', 'width') ?>", - "thumbwidth": "<?= /* @escapeNotVerified */ $block->getImageAttribute('product_page_image_small', 'width') ?>", - <?php if ($block->getImageAttribute('product_page_image_small', 'height') || $block->getImageAttribute('product_page_image_small', 'width')): ?> - "thumbheight": <?php /* @escapeNotVerified */ echo $block->getImageAttribute('product_page_image_small', 'height') - ?: $block->getImageAttribute('product_page_image_small', 'width'); ?>, - <?php endif; ?> - <?php if ($block->getImageAttribute('product_page_image_medium', 'height') || $block->getImageAttribute('product_page_image_medium', 'width')): ?> - "height": <?php /* @escapeNotVerified */ echo $block->getImageAttribute('product_page_image_medium', 'height') - ?: $block->getImageAttribute('product_page_image_medium', 'width'); ?>, - <?php endif; ?> - <?php if ($block->getVar("gallery/transition/duration")): ?> - "transitionduration": <?= /* @escapeNotVerified */ $block->getVar("gallery/transition/duration") ?>, - <?php endif; ?> - "transition": "<?= /* @escapeNotVerified */ $block->getVar("gallery/transition/effect") ?>", - "navarrows": <?= /* @escapeNotVerified */ $block->getVar("gallery/navarrows") ? 'true' : 'false' ?>, - "navtype": "<?= /* @escapeNotVerified */ $block->getVar("gallery/navtype") ?>", - "navdir": "<?= /* @escapeNotVerified */ $block->getVar("gallery/navdir") ?>" - }, - "fullscreen": { - "nav": "<?= /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/nav") ?>", - "loop": <?= /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/loop") ? 'true' : 'false' ?>, - "navdir": "<?= /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/navdir") ?>", - "navarrows": <?= /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/navarrows") ? 'true' : 'false' ?>, - "navtype": "<?= /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/navtype") ?>", - "arrows": <?= /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/arrows") ? 'true' : 'false' ?>, - "showCaption": <?= /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/caption") ? 'true' : 'false' ?>, - <?php if ($block->getVar("gallery/fullscreen/transition/duration")): ?> - "transitionduration": <?= /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/transition/duration") ?>, - <?php endif; ?> - "transition": "<?= /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/transition/effect") ?>" - }, + "options": <?= /* @noEscape */ $block->getGalleryOptions()->getOptionsJson() ?>, + "fullscreen": <?= /* @noEscape */ $block->getGalleryOptions()->getFSOptionsJson() ?>, "breakpoints": <?= /* @escapeNotVerified */ $block->getBreakpoints() ?> } } diff --git a/app/code/Magento/CatalogInventory/Model/StockStateProvider.php b/app/code/Magento/CatalogInventory/Model/StockStateProvider.php index 31fd5606a9849..6851b05aa56a6 100644 --- a/app/code/Magento/CatalogInventory/Model/StockStateProvider.php +++ b/app/code/Magento/CatalogInventory/Model/StockStateProvider.php @@ -119,14 +119,12 @@ public function checkQuoteItemQty(StockItemInterface $stockItem, $qty, $summaryQ $result->setItemIsQtyDecimal($stockItem->getIsQtyDecimal()); if (!$stockItem->getIsQtyDecimal()) { $result->setHasQtyOptionUpdate(true); - $qty = (int) $qty; + $qty = (int) $qty ?: 1; /** * Adding stock data to quote item */ $result->setItemQty($qty); - $qty = $this->getNumber($qty); - $origQty = (int) $origQty; - $result->setOrigQty($origQty); + $result->setOrigQty((int)$this->getNumber($origQty) ?: 1); } if ($stockItem->getMinSaleQty() && $qty < $stockItem->getMinSaleQty()) { diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml index 5b3679bed77e0..393e25e474f12 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml @@ -46,7 +46,9 @@ <selectOption selector="{{WidgetSection.WidgetTemplate}}" userInput="Category Link Block Template" stepKey="selectTemplate" /> <click selector="{{WidgetSection.BtnChooser}}" stepKey="clickSelectCategoryBtn" /> <waitForLoadingMaskToDisappear stepKey="wait3"/> - <click userInput="$$createPreReqCategory.name$$" stepKey="selectPreCreateCategory" /> + <click selector="{{AdminCategorySidebarTreeSection.expandRootCategory}}" stepKey="expandRootCategory" /> + <waitForElementVisible selector="{{WidgetSection.PreCreateCategory('$$createPreReqCategory.name$$')}}" stepKey="expandWait" /> + <click selector="{{WidgetSection.PreCreateCategory('$$createPreReqCategory.name$$')}}" stepKey="selectPreCreateCategory" /> <waitForElementNotVisible selector="{{WidgetSection.SelectCategoryTitle}}" stepKey="waitForSlideoutCloses1" /> <click selector="{{WidgetSection.InsertWidget}}" stepKey="clickInsertWidget" /> <waitForElementNotVisible selector="{{WidgetSection.InsertWidgetTitle}}" stepKey="waitForSlideOutCloses2" /> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml index 123d25f92b6b7..9ee9d27de477a 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml @@ -50,6 +50,8 @@ <selectOption selector="{{WidgetSection.WidgetTemplate}}" userInput="Product Link Block Template" stepKey="selectTemplate" /> <click selector="{{WidgetSection.BtnChooser}}" stepKey="clickSelectPageBtn" /> <waitForLoadingMaskToDisappear stepKey="wait4"/> + <click selector="{{AdminCategorySidebarTreeSection.expandRootCategory}}" stepKey="expandRootCategory" /> + <waitForElementVisible selector="{{WidgetSection.PreCreateCategory('$$createPreReqCategory.name$$')}}" stepKey="expandWait" /> <click selector="{{WidgetSection.PreCreateCategory('$$createPreReqCategory.name$$')}}" stepKey="selectPreCategory" /> <waitForLoadingMaskToDisappear stepKey="waitLoadingMask" /> <click selector="{{WidgetSection.PreCreateProduct('$$createPreReqProduct.name$$')}}" stepKey="selectPreProduct" /> diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php index 46f10608bc95e..a849d964eaed5 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php @@ -1232,6 +1232,8 @@ public function isPossibleBuyFromList($product) /** * Returns array of sub-products for specified configurable product * + * $requiredAttributeIds - one dimensional array, if provided + * * Result array contains all children for specified configurable product * * @param \Magento\Catalog\Model\Product $product @@ -1245,9 +1247,12 @@ public function getUsedProducts($product, $requiredAttributeIds = null) __METHOD__, $product->getData($metadata->getLinkField()), $product->getStoreId(), - $this->getCustomerSession()->getCustomerGroupId(), - $requiredAttributeIds + $this->getCustomerSession()->getCustomerGroupId() ]; + if ($requiredAttributeIds !== null) { + sort($requiredAttributeIds); + $keyParts[] = implode('', $requiredAttributeIds); + } $cacheKey = $this->getUsedProductsCacheKey($keyParts); return $this->loadUsedProducts($product, $cacheKey); } diff --git a/app/code/Magento/Contact/Test/Mftf/ActionGroup/AssertMessageContactUsFormActionGroup.xml b/app/code/Magento/Contact/Test/Mftf/ActionGroup/AssertMessageContactUsFormActionGroup.xml new file mode 100644 index 0000000000000..eec2194825166 --- /dev/null +++ b/app/code/Magento/Contact/Test/Mftf/ActionGroup/AssertMessageContactUsFormActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertMessageContactUsFormActionGroup"> + <arguments> + <argument name="message" type="string" defaultValue="Thanks for contacting us with your comments and questions. We'll respond to you very soon." /> + <argument name="messageType" type="string" defaultValue="success" /> + </arguments> + <see userInput="{{message}}" selector="{{StorefrontContactUsMessagesSection.messageByType(messageType)}}" stepKey="verifyMessage" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontFillContactUsFormActionGroup.xml b/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontFillContactUsFormActionGroup.xml new file mode 100644 index 0000000000000..df4964ea0423d --- /dev/null +++ b/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontFillContactUsFormActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontFillContactUsFormActionGroup"> + <arguments> + <argument name="customer" type="entity" /> + <argument name="contactUsData" type="entity" /> + </arguments> + <fillField selector="{{StorefrontContactUsFormSection.nameField}}" userInput="{{customer.firstname}}" stepKey="fillName"/> + <fillField selector="{{StorefrontContactUsFormSection.emailField}}" userInput="{{customer.email}}" stepKey="fillEmail"/> + <fillField selector="{{StorefrontContactUsFormSection.commentField}}" userInput="{{contactUsData.comment}}" stepKey="fillComment"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontOpenContactUsPageActionGroup.xml b/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontOpenContactUsPageActionGroup.xml new file mode 100644 index 0000000000000..d333d5d998960 --- /dev/null +++ b/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontOpenContactUsPageActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontOpenContactUsPageActionGroup"> + <amOnPage url="{{StorefrontContactUsPage.url}}" stepKey="amOnContactUpPage"/> + <waitForPageLoad stepKey="waitForContactUpPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontSubmitContactUsFormActionGroup.xml b/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontSubmitContactUsFormActionGroup.xml new file mode 100644 index 0000000000000..f3fe34f20c319 --- /dev/null +++ b/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontSubmitContactUsFormActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontSubmitContactUsFormActionGroup"> + <click selector="{{StorefrontContactUsFormSection.submitFormButton}}" stepKey="clickSubmitFormButton"/> + <waitForPageLoad stepKey="waitForCommentSubmitted" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Contact/Test/Mftf/Data/ContactUsData.xml b/app/code/Magento/Contact/Test/Mftf/Data/ContactUsData.xml new file mode 100644 index 0000000000000..eadf760776c58 --- /dev/null +++ b/app/code/Magento/Contact/Test/Mftf/Data/ContactUsData.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="DefaultContactUsData"> + <data key="comment" unique="suffix">Lorem ipsum dolor sit amet, ne enim aliquando eam, oblique deserunt no usu. Unique: </data> + </entity> +</entities> diff --git a/app/code/Magento/Contact/Test/Mftf/Page/StorefrontContactUsPage.xml b/app/code/Magento/Contact/Test/Mftf/Page/StorefrontContactUsPage.xml new file mode 100644 index 0000000000000..5e793b2338507 --- /dev/null +++ b/app/code/Magento/Contact/Test/Mftf/Page/StorefrontContactUsPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="StorefrontContactUsPage" url="/contact/" area="storefront" module="Magento_Contact"> + <section name="StorefrontContactUsFormSection"/> + </page> +</pages> diff --git a/app/code/Magento/Contact/Test/Mftf/Section/StorefrontContactUsFormSection.xml b/app/code/Magento/Contact/Test/Mftf/Section/StorefrontContactUsFormSection.xml new file mode 100644 index 0000000000000..fdaddf33f5170 --- /dev/null +++ b/app/code/Magento/Contact/Test/Mftf/Section/StorefrontContactUsFormSection.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontContactUsFormSection"> + <element name="nameField" type="input" selector="#contact-form input[name='name']" /> + <element name="emailField" type="input" selector="#contact-form input[name='email']" /> + <element name="phoneField" type="input" selector="#contact-form input[name='telephone']" /> + <element name="commentField" type="textarea" selector="#contact-form textarea[name='comment']" /> + <element name="submitFormButton" type="button" selector="#contact-form button[type='submit']" timeout="30" /> + </section> +</sections> diff --git a/app/code/Magento/Contact/Test/Mftf/Section/StorefrontContactUsMessagesSection.xml b/app/code/Magento/Contact/Test/Mftf/Section/StorefrontContactUsMessagesSection.xml new file mode 100644 index 0000000000000..0970f1f8f6b20 --- /dev/null +++ b/app/code/Magento/Contact/Test/Mftf/Section/StorefrontContactUsMessagesSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontContactUsMessagesSection"> + <element name="messageByType" type="block" selector="#maincontent .message-{{messageType}}" parameterized="true" /> + </section> +</sections> diff --git a/app/code/Magento/Contact/view/frontend/web/css/source/_module.less b/app/code/Magento/Contact/view/frontend/web/css/source/_module.less index 0aaec05aa2afe..d79806eecbe9b 100644 --- a/app/code/Magento/Contact/view/frontend/web/css/source/_module.less +++ b/app/code/Magento/Contact/view/frontend/web/css/source/_module.less @@ -21,6 +21,16 @@ } } +// +// Desktop +// _____________________________________________ + +.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) { + .contact-index-index .column:not(.sidebar-additional) .form.contact { + min-width: 600px; + } +} + // Mobile .media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) { .contact-index-index { diff --git a/app/code/Magento/Customer/Controller/Address/File/Upload.php b/app/code/Magento/Customer/Controller/Address/File/Upload.php new file mode 100644 index 0000000000000..adb4c7abd1729 --- /dev/null +++ b/app/code/Magento/Customer/Controller/Address/File/Upload.php @@ -0,0 +1,146 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Controller\Address\File; + +use Magento\Framework\App\Action\Action; +use Magento\Framework\App\Action\Context; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\Api\CustomAttributesDataInterface; +use Magento\Customer\Api\AddressMetadataInterface; +use Magento\Customer\Model\FileUploader; +use Magento\Customer\Model\FileUploaderFactory; +use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\Exception\LocalizedException; +use Psr\Log\LoggerInterface; +use Magento\Customer\Model\FileProcessorFactory; + +/** + * Class for upload files for customer custom address attributes + */ +class Upload extends Action implements HttpPostActionInterface +{ + /** + * @var FileUploaderFactory + */ + private $fileUploaderFactory; + + /** + * @var AddressMetadataInterface + */ + private $addressMetadataService; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var FileProcessorFactory + */ + private $fileProcessorFactory; + + /** + * @param Context $context + * @param FileUploaderFactory $fileUploaderFactory + * @param AddressMetadataInterface $addressMetadataService + * @param LoggerInterface $logger + * @param FileProcessorFactory $fileProcessorFactory + */ + public function __construct( + Context $context, + FileUploaderFactory $fileUploaderFactory, + AddressMetadataInterface $addressMetadataService, + LoggerInterface $logger, + FileProcessorFactory $fileProcessorFactory + ) { + $this->fileUploaderFactory = $fileUploaderFactory; + $this->addressMetadataService = $addressMetadataService; + $this->logger = $logger; + $this->fileProcessorFactory = $fileProcessorFactory; + parent::__construct($context); + } + + /** + * @inheritDoc + */ + public function execute() + { + try { + $requestedFiles = $this->getRequest()->getFiles('custom_attributes'); + if (empty($requestedFiles)) { + $result = $this->processError(__('No files for upload.')); + } else { + $attributeCode = key($requestedFiles); + $attributeMetadata = $this->addressMetadataService->getAttributeMetadata($attributeCode); + + /** @var FileUploader $fileUploader */ + $fileUploader = $this->fileUploaderFactory->create([ + 'attributeMetadata' => $attributeMetadata, + 'entityTypeCode' => AddressMetadataInterface::ENTITY_TYPE_ADDRESS, + 'scope' => CustomAttributesDataInterface::CUSTOM_ATTRIBUTES, + ]); + + $errors = $fileUploader->validate(); + if (true !== $errors) { + $errorMessage = implode('</br>', $errors); + $result = $this->processError(($errorMessage)); + } else { + $result = $fileUploader->upload(); + $this->moveTmpFileToSuitableFolder($result); + } + } + } catch (LocalizedException $e) { + $result = $this->processError($e->getMessage(), $e->getCode()); + } catch (\Exception $e) { + $this->logger->critical($e); + $result = $this->processError($e->getMessage(), $e->getCode()); + } + + /** @var \Magento\Framework\Controller\Result\Json $resultJson */ + $resultJson = $this->resultFactory->create(ResultFactory::TYPE_JSON); + $resultJson->setData($result); + return $resultJson; + } + + /** + * Move file from temporary folder to the 'customer_address' media folder + * + * @param array $fileInfo + * @throws LocalizedException + */ + private function moveTmpFileToSuitableFolder(&$fileInfo) + { + $fileName = $fileInfo['file']; + $fileProcessor = $this->fileProcessorFactory + ->create(['entityTypeCode' => AddressMetadataInterface::ENTITY_TYPE_ADDRESS]); + + $newFilePath = $fileProcessor->moveTemporaryFile($fileName); + $fileInfo['file'] = $newFilePath; + $fileInfo['url'] = $fileProcessor->getViewUrl( + $newFilePath, + 'file' + ); + } + + /** + * Prepare result array for errors + * + * @param string $message + * @param int $code + * @return array + */ + private function processError($message, $code = 0) + { + $result = [ + 'error' => $message, + 'errorcode' => $code, + ]; + + return $result; + } +} diff --git a/app/code/Magento/Customer/Model/Address.php b/app/code/Magento/Customer/Model/Address.php index e9aa2839095d5..ea9b103f42273 100644 --- a/app/code/Magento/Customer/Model/Address.php +++ b/app/code/Magento/Customer/Model/Address.php @@ -122,7 +122,7 @@ public function __construct( } /** - * Init model + * Initialize address model * * @return void */ @@ -167,7 +167,14 @@ public function updateData(AddressInterface $address) } /** - * @inheritdoc + * Create address data object based on current address model. + * + * @param int|null $defaultBillingAddressId + * @param int|null $defaultShippingAddressId + * @return AddressInterface + * Use Api/Data/AddressInterface as a result of service operations. Don't rely on the model to provide + * the instance of Api/Data/AddressInterface + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function getDataModel($defaultBillingAddressId = null, $defaultShippingAddressId = null) { @@ -257,7 +264,7 @@ public function getDefaultAttributeCodes() } /** - * Clone object handler + * Clone address * * @return void */ @@ -356,7 +363,11 @@ public function reindex() } /** - * @inheritdoc + * Get a list of custom attribute codes. + * + * By default, entity can be extended only using extension attributes functionality. + * + * @return string[] * @since 100.0.6 */ protected function getCustomAttributesCodes() diff --git a/app/code/Magento/Customer/Model/Customer.php b/app/code/Magento/Customer/Model/Customer.php index b00f393f53734..1287dbe5df708 100644 --- a/app/code/Magento/Customer/Model/Customer.php +++ b/app/code/Magento/Customer/Model/Customer.php @@ -1054,17 +1054,6 @@ public function resetErrors() return $this; } - /** - * Prepare customer for delete - * - * @return $this - */ - public function beforeDelete() - { - //TODO : Revisit and figure handling permissions in MAGETWO-11084 Implementation: Service Context Provider - return parent::beforeDelete(); - } - /** * Processing object after save data * @@ -1305,7 +1294,7 @@ public function getResetPasswordLinkExpirationPeriod() } /** - * Create address instance + * Create Address from Factory * * @return Address */ @@ -1315,7 +1304,7 @@ protected function _createAddressInstance() } /** - * Create address collection instance + * Create Address Collection from Factory * * @return \Magento\Customer\Model\ResourceModel\Address\Collection */ @@ -1325,7 +1314,7 @@ protected function _createAddressCollection() } /** - * Returns templates types + * Get Template Types * * @return array */ diff --git a/app/code/Magento/Customer/Model/FileProcessor.php b/app/code/Magento/Customer/Model/FileProcessor.php index 6a8472758c169..c16faea284296 100644 --- a/app/code/Magento/Customer/Model/FileProcessor.php +++ b/app/code/Magento/Customer/Model/FileProcessor.php @@ -3,8 +3,13 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Customer\Model; +/** + * Processor class for work with uploaded files + */ class FileProcessor { /** @@ -232,7 +237,7 @@ public function moveTemporaryFile($fileName) ); } - $fileName = $dispersionPath . '/' . $fileName; + $fileName = $dispersionPath . '/' . $destinationFileName; return $fileName; } diff --git a/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php b/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php index b25838e245488..ddc0576cb0dae 100644 --- a/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php +++ b/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php @@ -171,7 +171,14 @@ public function __construct( } /** - * @inheritdoc + * Create or update a customer. + * + * @param \Magento\Customer\Api\Data\CustomerInterface $customer + * @param string $passwordHash + * @return \Magento\Customer\Api\Data\CustomerInterface + * @throws \Magento\Framework\Exception\InputException If bad input is provided + * @throws \Magento\Framework\Exception\State\InputMismatchException If the provided email is already used + * @throws \Magento\Framework\Exception\LocalizedException * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ @@ -304,7 +311,13 @@ private function populateCustomerWithSecureData($customerModel, $passwordHash = } /** - * @inheritdoc + * Retrieve customer. + * + * @param string $email + * @param int|null $websiteId + * @return \Magento\Customer\Api\Data\CustomerInterface + * @throws \Magento\Framework\Exception\NoSuchEntityException If customer with the specified email does not exist. + * @throws \Magento\Framework\Exception\LocalizedException */ public function get($email, $websiteId = null) { @@ -313,7 +326,12 @@ public function get($email, $websiteId = null) } /** - * @inheritdoc + * Get customer by Customer ID. + * + * @param int $customerId + * @return \Magento\Customer\Api\Data\CustomerInterface + * @throws \Magento\Framework\Exception\NoSuchEntityException If customer with the specified ID does not exist. + * @throws \Magento\Framework\Exception\LocalizedException */ public function getById($customerId) { @@ -322,7 +340,15 @@ public function getById($customerId) } /** - * @inheritdoc + * Retrieve customers which match a specified criteria. + * + * This call returns an array of objects, but detailed information about each object’s attributes might not be + * included. See http://devdocs.magento.com/codelinks/attributes.html#CustomerRepositoryInterface to determine + * which call to use to get detailed information about all attributes for an object. + * + * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria + * @return \Magento\Customer\Api\Data\CustomerSearchResultsInterface + * @throws \Magento\Framework\Exception\LocalizedException */ public function getList(SearchCriteriaInterface $searchCriteria) { @@ -362,7 +388,11 @@ public function getList(SearchCriteriaInterface $searchCriteria) } /** - * @inheritdoc + * Delete customer. + * + * @param \Magento\Customer\Api\Data\CustomerInterface $customer + * @return bool true on success + * @throws \Magento\Framework\Exception\LocalizedException */ public function delete(CustomerInterface $customer) { @@ -370,7 +400,12 @@ public function delete(CustomerInterface $customer) } /** - * @inheritdoc + * Delete customer by Customer ID. + * + * @param int $customerId + * @return bool true on success + * @throws \Magento\Framework\Exception\NoSuchEntityException + * @throws \Magento\Framework\Exception\LocalizedException */ public function deleteById($customerId) { diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerLoggedInActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerLoggedInActionGroup.xml new file mode 100644 index 0000000000000..d9da950fe7115 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerLoggedInActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCustomerWelcomeMessageActionGroup"> + <arguments> + <argument name="customerFullName" type="string" /> + </arguments> + <see userInput="Welcome, {{customerFullName}}!" selector="{{StorefrontPanelHeaderSection.welcomeMessage}}" stepKey="verifyMessage" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerResetPasswordActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerResetPasswordActionGroup.xml new file mode 100644 index 0000000000000..644254443d129 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerResetPasswordActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCustomerResetPasswordActionGroup"> + <arguments> + <argument name="url" type="string"/> + <argument name="message" type="string" defaultValue="" /> + <argument name="messageType" type="string" defaultValue="success" /> + </arguments> + + <waitForElementVisible selector="{{StorefrontCustomerLoginMessagesSection.messageByType(messageType)}}" stepKey="waitForMessage" /> + <see stepKey="seeMessage" userInput="{{message}}" selector="{{StorefrontCustomerLoginMessagesSection.messageByType(messageType)}}"/> + <seeInCurrentUrl stepKey="seeCorrectCurrentUrl" url="{{url}}"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerChangeAccountInfoActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerChangeAccountInfoActionGroup.xml new file mode 100644 index 0000000000000..ab48184ed98a8 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerChangeAccountInfoActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertMessageCustomerChangeAccountInfoActionGroup"> + <arguments> + <argument name="message" type="string" defaultValue="You saved the account information." /> + <argument name="messageType" type="string" defaultValue="success" /> + </arguments> + <see userInput="{{message}}" selector="{{StorefrontCustomerAccountMainSection.messageByType(messageType)}}" stepKey="verifyMessage" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerCreateAccountActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerCreateAccountActionGroup.xml new file mode 100644 index 0000000000000..65c9b025a9c2d --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerCreateAccountActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertMessageCustomerCreateAccountActionGroup"> + <arguments> + <argument name="message" type="string" defaultValue="Thank you for registering with Main Website Store." /> + <argument name="messageType" type="string" defaultValue="success" /> + </arguments> + <see userInput="{{message}}" selector="{{StorefrontCustomerAccountMainSection.messageByType(messageType)}}" stepKey="verifyMessage" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerLoginActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerLoginActionGroup.xml new file mode 100644 index 0000000000000..6b88661985873 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerLoginActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertMessageCustomerLoginActionGroup"> + <arguments> + <argument name="message" type="string" defaultValue="The account sign-in was incorrect or your account is disabled temporarily. Please wait and try again later." /> + <argument name="messageType" type="string" defaultValue="error" /> + </arguments> + <see userInput="{{message}}" selector="{{StorefrontCustomerLoginMessagesSection.messageByType(messageType)}}" stepKey="verifyMessage" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StoreFrontClickSignInButtonActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StoreFrontClickSignInButtonActionGroup.xml deleted file mode 100644 index b12858fc1037e..0000000000000 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StoreFrontClickSignInButtonActionGroup.xml +++ /dev/null @@ -1,15 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="StorefrontClickSignInButtonActionGroup"> - <click stepKey="signIn" selector="{{StorefrontPanelHeaderSection.customerLoginLink}}" /> - <waitForPageLoad stepKey="waitForStorefrontSignInPageLoad"/> - </actionGroup> -</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup.xml new file mode 100644 index 0000000000000..dfb9d1b2c259a --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup"> + <click stepKey="clickCreateAccountButton" selector="{{StorefrontCustomerCreateFormSection.createAccountButton}}" /> + <waitForPageLoad stepKey="waitForCustomerSaved" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickSignOnCustomerLoginFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickSignOnCustomerLoginFormActionGroup.xml new file mode 100644 index 0000000000000..9cd52b841fca4 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickSignOnCustomerLoginFormActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontClickSignOnCustomerLoginFormActionGroup"> + <click stepKey="clickSignInButton" selector="{{StorefrontCustomerSignInFormSection.signInAccountButton}}" /> + <waitForPageLoad stepKey="waitForCustomerSignIn" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailActionGroup.xml new file mode 100644 index 0000000000000..844d13aa1fe43 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCustomerChangeEmailActionGroup"> + <arguments> + <argument name="email" type="string" /> + <argument name="password" type="string" /> + </arguments> + <conditionalClick selector="{{StorefrontCustomerSidebarSection.sidebarTab('Account Information')}}" dependentSelector="{{StorefrontCustomerSidebarSection.sidebarTab('Account Information')}}" visible="true" stepKey="openAccountInfoTab" /> + <waitForPageLoad stepKey="waitForAccountInfoTabOpened" /> + <checkOption selector="{{StorefrontCustomerAccountInformationSection.changeEmail}}" stepKey="clickChangeEmailCheckbox" /> + <fillField selector="{{StorefrontCustomerAccountInformationSection.email}}" userInput="{{email}}" stepKey="fillEmail" /> + <fillField selector="{{StorefrontCustomerAccountInformationSection.currentPassword}}" userInput="{{password}}" stepKey="fillCurrentPassword" /> + <click selector="{{StorefrontCustomerAccountInformationSection.saveButton}}" stepKey="saveChange" /> + <waitForPageLoad stepKey="waitForPageLoaded" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerResetPasswordActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerResetPasswordActionGroup.xml new file mode 100644 index 0000000000000..a28593f1b77b7 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerResetPasswordActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCustomerResetPasswordActionGroup"> + <arguments> + <argument name="email" type="string" /> + </arguments> + + <amOnPage stepKey="amOnSignInPage" url="{{StorefrontCustomerSignInPage.url}}"/> + <click stepKey="clickForgotPasswordLink" selector="{{StorefrontCustomerSignInFormSection.forgotPasswordLink}}"/> + <see stepKey="seePageTitle" userInput="Forgot Your Password" selector="{{StorefrontForgotPasswordSection.pageTitle}}"/> + <!-- Enter email and submit the forgot password form --> + <fillField stepKey="fillEmailField" userInput="{{email}}" selector="{{StorefrontForgotPasswordSection.email}}"/> + <click stepKey="clickResetPassword" selector="{{StorefrontForgotPasswordSection.resetMyPasswordButton}}"/> + <waitForPageLoad stepKey="waitForPageLoaded" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormActionGroup.xml new file mode 100644 index 0000000000000..62d77d4548cce --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontFillCustomerAccountCreationFormActionGroup"> + <arguments> + <argument name="customer" type="entity" /> + </arguments> + + <fillField stepKey="fillFirstName" userInput="{{customer.firstname}}" selector="{{StorefrontCustomerCreateFormSection.firstnameField}}" /> + <fillField stepKey="fillLastName" userInput="{{customer.lastname}}" selector="{{StorefrontCustomerCreateFormSection.lastnameField}}" /> + <fillField stepKey="fillEmail" userInput="{{customer.email}}" selector="{{StorefrontCustomerCreateFormSection.emailField}}"/> + <fillField stepKey="fillPassword" userInput="{{customer.password}}" selector="{{StorefrontCustomerCreateFormSection.passwordField}}"/> + <fillField stepKey="fillConfirmPassword" userInput="{{customer.password}}" selector="{{StorefrontCustomerCreateFormSection.confirmPasswordField}}"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormActionGroup.xml new file mode 100644 index 0000000000000..22883ada7c2b1 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontFillCustomerLoginFormActionGroup"> + <arguments> + <argument name="customer" type="entity" /> + </arguments> + + <fillField userInput="{{customer.email}}" selector="{{StorefrontCustomerSignInFormSection.emailField}}" stepKey="fillEmail"/> + <fillField userInput="{{customer.password}}" selector="{{StorefrontCustomerSignInFormSection.passwordField}}" stepKey="fillPassword"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStoreFrontPasswordAutocompleteOffActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountCreatePageActionGroup.xml similarity index 50% rename from app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStoreFrontPasswordAutocompleteOffActionGroup.xml rename to app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountCreatePageActionGroup.xml index 23a067cd94eea..0ae470d0497ab 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStoreFrontPasswordAutocompleteOffActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountCreatePageActionGroup.xml @@ -5,10 +5,11 @@ * See COPYING.txt for license details. */ --> + <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="AssertStorefrontPasswordAutoCompleteOffActionGroup"> - <amOnPage stepKey="amOnSignInPage" url="{{StorefrontCustomerSignInPage.url}}"/> - <assertElementContainsAttribute selector="{{StorefrontCustomerSignInFormSection.passwordField}}" attribute="autocomplete" expectedValue="off" stepKey="assertSignInPasswordAutocompleteOff"/> + <actionGroup name="StorefrontOpenCustomerAccountCreatePageActionGroup"> + <amOnPage url="{{StorefrontCustomerCreatePage.url}}" stepKey="goToCustomerAccountCreatePage"/> + <waitForPageLoad stepKey="waitForPageLoaded"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountInfoEditPageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountInfoEditPageActionGroup.xml new file mode 100644 index 0000000000000..c1ea2da8a9519 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountInfoEditPageActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontOpenCustomerAccountInfoEditPageActionGroup"> + <amOnPage url="{{StorefrontCustomerEditPage.url}}" stepKey="goToCustomerEditPage"/> + <waitForPageLoad stepKey="waitForEditPage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerLoginPageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerLoginPageActionGroup.xml new file mode 100644 index 0000000000000..0a5c72265528a --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerLoginPageActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontOpenCustomerLoginPageActionGroup"> + <amOnPage url="{{StorefrontCustomerSignInPage.url}}" stepKey="amOnSignInPage"/> + <waitForPageLoad stepKey="waitForPageLoaded"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerEditPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerEditPage.xml new file mode 100644 index 0000000000000..d4cf90dde08ff --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerEditPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="StorefrontCustomerEditPage" url="/customer/account/edit/" area="storefront" module="Magento_Customer"> + <section name="StorefrontCustomerAccountInformationSection"/> + </page> +</pages> \ No newline at end of file diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml index b819a78002c62..8a633ec5bc271 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml @@ -11,13 +11,14 @@ <section name="StorefrontCustomerAccountInformationSection"> <element name="firstName" type="input" selector="#firstname"/> <element name="lastName" type="input" selector="#lastname"/> - <element name="changeEmail" type="checkbox" selector="#change_email"/> - <element name="changePassword" type="checkbox" selector="#change_password"/> + <element name="changeEmail" type="checkbox" selector=".form-edit-account input[name='change_email']"/> + <element name="changePassword" type="checkbox" selector=".form-edit-account input[name='change_password']"/> <element name="testAddedAttributeFiled" type="input" selector="//input[contains(@id,'{{var}}')]" parameterized="true"/> <element name="saveButton" type="button" selector="#form-validate .action.save.primary" timeout="30"/> <element name="currentPassword" type="input" selector="#current-password"/> <element name="newPassword" type="input" selector="#password"/> <element name="confirmNewPassword" type="input" selector="#password-confirmation"/> <element name="confirmNewPasswordError" type="text" selector="#password-confirmation-error"/> + <element name="email" type="input" selector=".form-edit-account input[name='email']" /> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountMainSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountMainSection.xml index 3a4329969ae2b..c00b54b3964da 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountMainSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountMainSection.xml @@ -10,5 +10,6 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCustomerAccountMainSection"> <element name="pageTitle" type="text" selector="#maincontent .column.main [data-ui-id='page-title-wrapper']" /> + <element name="messageByType" type="block" selector="#maincontent .message-{{messageType}}" parameterized="true" /> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerLoginMessagesSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerLoginMessagesSection.xml index 078021db062cc..a9859cf58751b 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerLoginMessagesSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerLoginMessagesSection.xml @@ -11,5 +11,6 @@ <section name="StorefrontCustomerLoginMessagesSection"> <element name="successMessage" type="text" selector=".message-success"/> <element name="errorMessage" type="text" selector=".message-error"/> + <element name="messageByType" type="block" selector="#maincontent .message-{{messageType}}" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml index f52b379379ad1..7bc057b8be7b7 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml @@ -12,7 +12,7 @@ <element name="emailField" type="input" selector="#email"/> <element name="passwordField" type="input" selector="#pass"/> <element name="signInAccountButton" type="button" selector="#send2" timeout="30"/> - <element name="forgotPasswordLink" type="link" selector=".action.remind" timeout="10"/> + <element name="forgotPasswordLink" type="button" selector=".action.remind" timeout="10"/> </section> <section name="StorefrontCustomerSignInPopupFormSection"> <element name="errorMessage" type="input" selector="[data-ui-id='checkout-cart-validationmessages-message-error']"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml index 4d7572aedc59b..dab298f6b416b 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml @@ -9,7 +9,10 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontPanelHeaderSection"> + <!-- Element name="WelcomeMessage" Deprecated due to incorrect naming convention please use name="welcomeMessage" --> <element name="WelcomeMessage" type="text" selector=".greet.welcome span"/> + + <element name="welcomeMessage" type="text" selector="header>.panel .greet.welcome" /> <element name="createAnAccountLink" type="select" selector="//div[@class='panel wrapper']//li/a[contains(.,'Create an Account')]" timeout="30"/> <element name="notYouLink" type="button" selector=".greet.welcome span a"/> <element name="customerWelcome" type="text" selector=".panel.header .customer-welcome"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontForgotPasswordTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontForgotPasswordTest.xml index 12e603bd3748c..8d4be5fda3c79 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontForgotPasswordTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontForgotPasswordTest.xml @@ -20,10 +20,11 @@ <group value="mtf_migrated"/> </annotations> <before> - <magentoCLI command="config:set customer/captcha/enable 0" stepKey="disableCaptcha"/> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDisableConfigData.path}} {{StorefrontCustomerCaptchaDisableConfigData.value}}" stepKey="disableCaptcha"/> <createData stepKey="customer" entity="Simple_US_Customer"/> </before> <after> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaEnableConfigData.path}} {{StorefrontCustomerCaptchaEnableConfigData.value}}" stepKey="enableCaptcha"/> <deleteData stepKey="deleteCustomer" createDataKey="customer" /> </after> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml new file mode 100644 index 0000000000000..3121bd0da9d2d --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontResetCustomerPasswordFailedTest"> + <annotations> + <features value="Customer"/> + <title value="Customer tries to reset password several times"/> + <description value="Customer tries to reset password several times"/> + <severity value="CRITICAL" /> + <testCaseId value="MC-14374" /> + <group value="Customer"/> + <group value="security"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <createData stepKey="customer" entity="Simple_US_Customer"/> + </before> + <after> + <deleteData stepKey="deleteCustomer" createDataKey="customer" /> + </after> + + <actionGroup ref="StorefrontCustomerResetPasswordActionGroup" stepKey="resetPasswordFirstAttempt"> + <argument name="email" value="$$customer.email$$" /> + </actionGroup> + <actionGroup ref="AssertCustomerResetPasswordActionGroup" stepKey="seePageWithSuccessMessage"> + <argument name="url" value="{{StorefrontCustomerSignInPage.url}}"/> + <argument name="message" value="If there is an account associated with $$customer.email$$ you will receive an email with a link to reset your password."/> + </actionGroup> + <actionGroup ref="StorefrontCustomerResetPasswordActionGroup" stepKey="resetPasswordSecondAttempt"> + <argument name="email" value="$$customer.email$$" /> + </actionGroup> + <actionGroup ref="AssertCustomerResetPasswordActionGroup" stepKey="seePageWithErrorMessage"> + <argument name="url" value="{{StorefrontForgotPasswordPage.url}}"/> + <argument name="message" value="We received too many requests for password resets. Please wait and try again later or contact hello@example.com."/> + <argument name="messageType" value="error" /> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Developer/i18n/de_DE.csv b/app/code/Magento/Developer/i18n/de_DE.csv deleted file mode 100644 index e9c858101b71c..0000000000000 --- a/app/code/Magento/Developer/i18n/de_DE.csv +++ /dev/null @@ -1,5 +0,0 @@ -"Front-end development workflow","Front-end development workflow" -"Workflow type","Workflow type" -"Server side less compilation","Server side less compilation" -"Client side less compilation","Client side less compilation" -"Not available in production mode","Not available in production mode" diff --git a/app/code/Magento/Developer/i18n/es_ES.csv b/app/code/Magento/Developer/i18n/es_ES.csv deleted file mode 100644 index e9c858101b71c..0000000000000 --- a/app/code/Magento/Developer/i18n/es_ES.csv +++ /dev/null @@ -1,5 +0,0 @@ -"Front-end development workflow","Front-end development workflow" -"Workflow type","Workflow type" -"Server side less compilation","Server side less compilation" -"Client side less compilation","Client side less compilation" -"Not available in production mode","Not available in production mode" diff --git a/app/code/Magento/Developer/i18n/fr_FR.csv b/app/code/Magento/Developer/i18n/fr_FR.csv deleted file mode 100644 index e9c858101b71c..0000000000000 --- a/app/code/Magento/Developer/i18n/fr_FR.csv +++ /dev/null @@ -1,5 +0,0 @@ -"Front-end development workflow","Front-end development workflow" -"Workflow type","Workflow type" -"Server side less compilation","Server side less compilation" -"Client side less compilation","Client side less compilation" -"Not available in production mode","Not available in production mode" diff --git a/app/code/Magento/Developer/i18n/nl_NL.csv b/app/code/Magento/Developer/i18n/nl_NL.csv deleted file mode 100644 index e9c858101b71c..0000000000000 --- a/app/code/Magento/Developer/i18n/nl_NL.csv +++ /dev/null @@ -1,5 +0,0 @@ -"Front-end development workflow","Front-end development workflow" -"Workflow type","Workflow type" -"Server side less compilation","Server side less compilation" -"Client side less compilation","Client side less compilation" -"Not available in production mode","Not available in production mode" diff --git a/app/code/Magento/Developer/i18n/pt_BR.csv b/app/code/Magento/Developer/i18n/pt_BR.csv deleted file mode 100644 index e9c858101b71c..0000000000000 --- a/app/code/Magento/Developer/i18n/pt_BR.csv +++ /dev/null @@ -1,5 +0,0 @@ -"Front-end development workflow","Front-end development workflow" -"Workflow type","Workflow type" -"Server side less compilation","Server side less compilation" -"Client side less compilation","Client side less compilation" -"Not available in production mode","Not available in production mode" diff --git a/app/code/Magento/Developer/i18n/zh_Hans_CN.csv b/app/code/Magento/Developer/i18n/zh_Hans_CN.csv deleted file mode 100644 index e9c858101b71c..0000000000000 --- a/app/code/Magento/Developer/i18n/zh_Hans_CN.csv +++ /dev/null @@ -1,5 +0,0 @@ -"Front-end development workflow","Front-end development workflow" -"Workflow type","Workflow type" -"Server side less compilation","Server side less compilation" -"Client side less compilation","Client side less compilation" -"Not available in production mode","Not available in production mode" diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/PriceFieldsProvider.php b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/PriceFieldsProvider.php index 875d384a20596..56c84593256be 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/PriceFieldsProvider.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/PriceFieldsProvider.php @@ -72,13 +72,15 @@ public function __construct( */ public function getFields(array $productIds, $storeId) { + $websiteId = $this->storeManager->getStore($storeId)->getWebsiteId(); + $priceData = $this->dataProvider->getSearchableAttribute('price') ? $this->resourceIndex->getPriceIndexData($productIds, $storeId) : []; $fields = []; foreach ($productIds as $productId) { - $fields[$productId] = $this->getProductPriceData($productId, $storeId, $priceData); + $fields[$productId] = $this->getProductPriceData($productId, $websiteId, $priceData); } return $fields; diff --git a/app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php b/app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php index 1fc3257ff2c1e..10b29a50a4064 100644 --- a/app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php +++ b/app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php @@ -408,7 +408,9 @@ protected function _saveValidatedBunches() if ($source->valid()) { try { $rowData = $source->current(); - $skuSet[$rowData['sku']] = true; + if (array_key_exists('sku', $rowData)) { + $skuSet[$rowData['sku']] = true; + } } catch (\InvalidArgumentException $e) { $this->addRowError($e->getMessage(), $this->_processedRowsCount); $this->_processedRowsCount++; @@ -436,7 +438,7 @@ protected function _saveValidatedBunches() $source->next(); } } - $this->_processedEntitiesCount = count($skuSet); + $this->_processedEntitiesCount = (count($skuSet)) ? : $this->_processedRowsCount; return $this; } diff --git a/app/code/Magento/Msrp/i18n/de_DE.csv b/app/code/Magento/Msrp/i18n/de_DE.csv deleted file mode 100644 index be8e733a4bfd1..0000000000000 --- a/app/code/Magento/Msrp/i18n/de_DE.csv +++ /dev/null @@ -1,2 +0,0 @@ -"Enabling MAP by default will hide all product prices on Storefront.","Enabling MAP by default will hide all product prices on Storefront." -"Warning!","Warning!" diff --git a/app/code/Magento/Msrp/i18n/es_ES.csv b/app/code/Magento/Msrp/i18n/es_ES.csv deleted file mode 100644 index be8e733a4bfd1..0000000000000 --- a/app/code/Magento/Msrp/i18n/es_ES.csv +++ /dev/null @@ -1,2 +0,0 @@ -"Enabling MAP by default will hide all product prices on Storefront.","Enabling MAP by default will hide all product prices on Storefront." -"Warning!","Warning!" diff --git a/app/code/Magento/Msrp/i18n/fr_FR.csv b/app/code/Magento/Msrp/i18n/fr_FR.csv deleted file mode 100644 index be8e733a4bfd1..0000000000000 --- a/app/code/Magento/Msrp/i18n/fr_FR.csv +++ /dev/null @@ -1,2 +0,0 @@ -"Enabling MAP by default will hide all product prices on Storefront.","Enabling MAP by default will hide all product prices on Storefront." -"Warning!","Warning!" diff --git a/app/code/Magento/Msrp/i18n/nl_NL.csv b/app/code/Magento/Msrp/i18n/nl_NL.csv deleted file mode 100644 index be8e733a4bfd1..0000000000000 --- a/app/code/Magento/Msrp/i18n/nl_NL.csv +++ /dev/null @@ -1,2 +0,0 @@ -"Enabling MAP by default will hide all product prices on Storefront.","Enabling MAP by default will hide all product prices on Storefront." -"Warning!","Warning!" diff --git a/app/code/Magento/Msrp/i18n/pt_BR.csv b/app/code/Magento/Msrp/i18n/pt_BR.csv deleted file mode 100644 index be8e733a4bfd1..0000000000000 --- a/app/code/Magento/Msrp/i18n/pt_BR.csv +++ /dev/null @@ -1,2 +0,0 @@ -"Enabling MAP by default will hide all product prices on Storefront.","Enabling MAP by default will hide all product prices on Storefront." -"Warning!","Warning!" diff --git a/app/code/Magento/Msrp/i18n/zh_Hans_CN.csv b/app/code/Magento/Msrp/i18n/zh_Hans_CN.csv deleted file mode 100644 index be8e733a4bfd1..0000000000000 --- a/app/code/Magento/Msrp/i18n/zh_Hans_CN.csv +++ /dev/null @@ -1,2 +0,0 @@ -"Enabling MAP by default will hide all product prices on Storefront.","Enabling MAP by default will hide all product prices on Storefront." -"Warning!","Warning!" diff --git a/app/code/Magento/Multishipping/view/frontend/templates/checkout/billing.phtml b/app/code/Magento/Multishipping/view/frontend/templates/checkout/billing.phtml index da553b7823da9..4354cfb7c1c3e 100644 --- a/app/code/Magento/Multishipping/view/frontend/templates/checkout/billing.phtml +++ b/app/code/Magento/Multishipping/view/frontend/templates/checkout/billing.phtml @@ -55,6 +55,10 @@ <div class="box-content"> <address> <?= /* @noEscape */ $block->getCheckoutData()->getAddressHtml($block->getAddress()); ?> + <input type="hidden" + id="multishipping_billing_country_id" + value="<?= /* @noEscape */ $block->getAddress()->getCountryId(); ?>" + name="multishipping_billing_country_id"/> </address> </div> </div> @@ -80,35 +84,44 @@ $block->setMethodFormTemplate($code, $methodsForms[$code]); } ?> - <dt class="item-title"> - <?php if ($methodsCount > 1) : ?> - <input type="radio" - id="p_method_<?= $block->escapeHtml($code); ?>" - value="<?= $block->escapeHtml($code); ?>" - name="payment[method]" - title="<?= $block->escapeHtml($_method->getTitle()) ?>" - <?php if ($checked) : ?> - checked="checked" + <div data-bind="scope: 'payment_method_<?= $block->escapeHtml($code);?>'"> + <dt class="item-title"> + <?php if ($methodsCount > 1) : ?> + <input type="radio" + id="p_method_<?= $block->escapeHtml($code); ?>" + value="<?= $block->escapeHtml($code); ?>" + name="payment[method]" + title="<?= $block->escapeHtml($_method->getTitle()) ?>" + data-bind=" + value: getCode(), + checked: isChecked, + click: selectPaymentMethod, + visible: isRadioButtonVisible()" + <?php if ($checked) : ?> + checked="checked" + <?php endif; ?> + class="radio"/> + <?php else : ?> + <input type="radio" + id="p_method_<?= $block->escapeHtml($code); ?>" + value="<?= $block->escapeHtml($code); ?>" + name="payment[method]" + data-bind=" + value: getCode(), + afterRender: selectPaymentMethod" + checked="checked" + class="radio solo method" /> <?php endif; ?> - class="radio"/> - <?php else : ?> - <input type="radio" - id="p_method_<?= $block->escapeHtml($code); ?>" - value="<?= $block->escapeHtml($code); ?>" - name="payment[method]" - checked="checked" - class="radio solo method" /> - <?php endif; ?> - <label for="p_method_<?= $block->escapeHtml($code); ?>"> - <?= $block->escapeHtml($_method->getTitle()) ?> - </label> - </dt> - <?php if ($html = $block->getChildHtml('payment.method.' . $code)) : ?> - <dd class="item-content <?= $checked ? '' : 'no-display'; ?>" - data-bind="scope: 'payment_method_<?= $block->escapeHtml($code);?>'"> - <?= /* @noEscape */ $html; ?> - </dd> - <?php endif; ?> + <label for="p_method_<?= $block->escapeHtml($code); ?>"> + <?= $block->escapeHtml($_method->getTitle()) ?> + </label> + </dt> + <?php if ($html = $block->getChildHtml('payment.method.' . $code)) : ?> + <dd class="item-content <?= $checked ? '' : 'no-display'; ?>"> + <?= /* @noEscape */ $html; ?> + </dd> + <?php endif; ?> + </div> <?php endforeach; ?> </dl> <?= $block->getChildHtml('payment_methods_after') ?> diff --git a/app/code/Magento/OfflinePayments/view/frontend/layout/multishipping_checkout_billing.xml b/app/code/Magento/OfflinePayments/view/frontend/layout/multishipping_checkout_billing.xml new file mode 100644 index 0000000000000..32810ecef20da --- /dev/null +++ b/app/code/Magento/OfflinePayments/view/frontend/layout/multishipping_checkout_billing.xml @@ -0,0 +1,19 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <body> + <referenceBlock name="checkout_billing"> + <arguments> + <argument name="form_templates" xsi:type="array"> + <item name="checkmo" xsi:type="string">Magento_OfflinePayments::multishipping/checkmo_form.phtml</item> + </argument> + </arguments> + </referenceBlock> + </body> +</page> diff --git a/app/code/Magento/OfflinePayments/view/frontend/templates/multishipping/checkmo_form.phtml b/app/code/Magento/OfflinePayments/view/frontend/templates/multishipping/checkmo_form.phtml new file mode 100644 index 0000000000000..b96918243a7a7 --- /dev/null +++ b/app/code/Magento/OfflinePayments/view/frontend/templates/multishipping/checkmo_form.phtml @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +?> +<script> + require([ + 'uiLayout', + 'jquery' + ], function (layout, $) { + $(function () { + var paymentMethodData = { + method: 'checkmo' + }; + layout([ + { + component: 'Magento_Checkout/js/view/payment/default', + name: 'payment_method_checkmo', + method: paymentMethodData.method, + item: paymentMethodData + } + ]); + + $('body').trigger('contentUpdated'); + }) + }) +</script> diff --git a/app/code/Magento/PageCache/etc/varnish4.vcl b/app/code/Magento/PageCache/etc/varnish4.vcl index 8068447e5ca99..c73c4e39170e6 100644 --- a/app/code/Magento/PageCache/etc/varnish4.vcl +++ b/app/code/Magento/PageCache/etc/varnish4.vcl @@ -91,10 +91,11 @@ sub vcl_recv { } } - # Remove Google gclid parameters to minimize the cache objects - set req.url = regsuball(req.url,"\?gclid=[^&]+$",""); # strips when QS = "?gclid=AAA" - set req.url = regsuball(req.url,"\?gclid=[^&]+&","?"); # strips when QS = "?gclid=AAA&foo=bar" - set req.url = regsuball(req.url,"&gclid=[^&]+",""); # strips when QS = "?foo=bar&gclid=AAA" or QS = "?foo=bar&gclid=AAA&bar=baz" + # Remove all marketing get parameters to minimize the cache objects + if (req.url ~ "(\?|&)(gclid|cx|ie|cof|siteurl|zanpid|origin|mc_[a-z]+|utm_[a-z]+)=") { + set req.url = regsuball(req.url, "(gclid|cx|ie|cof|siteurl|zanpid|origin|mc_[a-z]+|utm_[a-z]+)=[-_A-z0-9+()%.]+&?", ""); + set req.url = regsub(req.url, "[?|&]+$", ""); + } # Static files caching if (req.url ~ "^/(pub/)?(media|static)/") { diff --git a/app/code/Magento/PageCache/etc/varnish5.vcl b/app/code/Magento/PageCache/etc/varnish5.vcl index 6c8414a5cb641..ea586858eff75 100644 --- a/app/code/Magento/PageCache/etc/varnish5.vcl +++ b/app/code/Magento/PageCache/etc/varnish5.vcl @@ -92,10 +92,11 @@ sub vcl_recv { } } - # Remove Google gclid parameters to minimize the cache objects - set req.url = regsuball(req.url,"\?gclid=[^&]+$",""); # strips when QS = "?gclid=AAA" - set req.url = regsuball(req.url,"\?gclid=[^&]+&","?"); # strips when QS = "?gclid=AAA&foo=bar" - set req.url = regsuball(req.url,"&gclid=[^&]+",""); # strips when QS = "?foo=bar&gclid=AAA" or QS = "?foo=bar&gclid=AAA&bar=baz" + # Remove all marketing get parameters to minimize the cache objects + if (req.url ~ "(\?|&)(gclid|cx|ie|cof|siteurl|zanpid|origin|mc_[a-z]+|utm_[a-z]+)=") { + set req.url = regsuball(req.url, "(gclid|cx|ie|cof|siteurl|zanpid|origin|mc_[a-z]+|utm_[a-z]+)=[-_A-z0-9+()%.]+&?", ""); + set req.url = regsub(req.url, "[?|&]+$", ""); + } # Static files caching if (req.url ~ "^/(pub/)?(media|static)/") { diff --git a/app/code/Magento/Paypal/README.md b/app/code/Magento/Paypal/README.md index 8f4453ae0a058..0ed4f2e90291b 100644 --- a/app/code/Magento/Paypal/README.md +++ b/app/code/Magento/Paypal/README.md @@ -1,6 +1,6 @@ Module Magento\PayPal implements integration with the PayPal payment system. Namely, it enables the following payment methods: -*PayPal Express Checkout -*PayPal Payments Standard -*PayPal Payments Pro -*PayPal Credit -*PayFlow Payment Gateway +* PayPal Express Checkout +* PayPal Payments Standard +* PayPal Payments Pro +* PayPal Credit +* PayFlow Payment Gateway diff --git a/app/code/Magento/ProductVideo/i18n/de_DE.csv b/app/code/Magento/ProductVideo/i18n/de_DE.csv deleted file mode 100644 index ca24668bb8d16..0000000000000 --- a/app/code/Magento/ProductVideo/i18n/de_DE.csv +++ /dev/null @@ -1,10 +0,0 @@ -"Add video","Add video" -"New Video","New Video" -"Product Video","Product Video" -"YouTube API key","YouTube API key" -"You have not entered youtube API key. No information about youtube video will be retrieved.","You have not entered youtube API key. No information about youtube video will be retrieved." -"Url","Url" -"Preview Image","Preview Image" -"Get Video Information","Get Video Information" -"Youtube or Vimeo supported","Youtube or Vimeo supported" -"Delete image in all store views","Delete image in all store views" diff --git a/app/code/Magento/ProductVideo/i18n/es_ES.csv b/app/code/Magento/ProductVideo/i18n/es_ES.csv deleted file mode 100644 index ca24668bb8d16..0000000000000 --- a/app/code/Magento/ProductVideo/i18n/es_ES.csv +++ /dev/null @@ -1,10 +0,0 @@ -"Add video","Add video" -"New Video","New Video" -"Product Video","Product Video" -"YouTube API key","YouTube API key" -"You have not entered youtube API key. No information about youtube video will be retrieved.","You have not entered youtube API key. No information about youtube video will be retrieved." -"Url","Url" -"Preview Image","Preview Image" -"Get Video Information","Get Video Information" -"Youtube or Vimeo supported","Youtube or Vimeo supported" -"Delete image in all store views","Delete image in all store views" diff --git a/app/code/Magento/ProductVideo/i18n/fr_FR.csv b/app/code/Magento/ProductVideo/i18n/fr_FR.csv deleted file mode 100644 index ca24668bb8d16..0000000000000 --- a/app/code/Magento/ProductVideo/i18n/fr_FR.csv +++ /dev/null @@ -1,10 +0,0 @@ -"Add video","Add video" -"New Video","New Video" -"Product Video","Product Video" -"YouTube API key","YouTube API key" -"You have not entered youtube API key. No information about youtube video will be retrieved.","You have not entered youtube API key. No information about youtube video will be retrieved." -"Url","Url" -"Preview Image","Preview Image" -"Get Video Information","Get Video Information" -"Youtube or Vimeo supported","Youtube or Vimeo supported" -"Delete image in all store views","Delete image in all store views" diff --git a/app/code/Magento/ProductVideo/i18n/nl_NL.csv b/app/code/Magento/ProductVideo/i18n/nl_NL.csv deleted file mode 100644 index 5ad8386573040..0000000000000 --- a/app/code/Magento/ProductVideo/i18n/nl_NL.csv +++ /dev/null @@ -1,10 +0,0 @@ -"Add video","Add video" -"New Video","New Video" -"Product Video","Product Video" -"YouTube API key","YouTube API key" -"You have not entered youtube API key. No information about youtube video will be retrieved.","You have not entered youtube API key. No information about youtube video will be retrieved." -"Url","Url" -"Preview Image","Preview Image" -"Get Video Information","Get Video Information" -"Youtube or Vimeo supported","Youtube or Vimeo supported" -"Delete image in all store views","Delete image in all store views" \ No newline at end of file diff --git a/app/code/Magento/ProductVideo/i18n/pt_BR.csv b/app/code/Magento/ProductVideo/i18n/pt_BR.csv deleted file mode 100644 index 5ad8386573040..0000000000000 --- a/app/code/Magento/ProductVideo/i18n/pt_BR.csv +++ /dev/null @@ -1,10 +0,0 @@ -"Add video","Add video" -"New Video","New Video" -"Product Video","Product Video" -"YouTube API key","YouTube API key" -"You have not entered youtube API key. No information about youtube video will be retrieved.","You have not entered youtube API key. No information about youtube video will be retrieved." -"Url","Url" -"Preview Image","Preview Image" -"Get Video Information","Get Video Information" -"Youtube or Vimeo supported","Youtube or Vimeo supported" -"Delete image in all store views","Delete image in all store views" \ No newline at end of file diff --git a/app/code/Magento/ProductVideo/i18n/zh_Hans_CN.csv b/app/code/Magento/ProductVideo/i18n/zh_Hans_CN.csv deleted file mode 100644 index 5ad8386573040..0000000000000 --- a/app/code/Magento/ProductVideo/i18n/zh_Hans_CN.csv +++ /dev/null @@ -1,10 +0,0 @@ -"Add video","Add video" -"New Video","New Video" -"Product Video","Product Video" -"YouTube API key","YouTube API key" -"You have not entered youtube API key. No information about youtube video will be retrieved.","You have not entered youtube API key. No information about youtube video will be retrieved." -"Url","Url" -"Preview Image","Preview Image" -"Get Video Information","Get Video Information" -"Youtube or Vimeo supported","Youtube or Vimeo supported" -"Delete image in all store views","Delete image in all store views" \ No newline at end of file diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/QuoteAddressFactory.php b/app/code/Magento/QuoteGraphQl/Model/Cart/QuoteAddressFactory.php index 734843e1be09b..582055bc6e13d 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/QuoteAddressFactory.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/QuoteAddressFactory.php @@ -7,12 +7,13 @@ namespace Magento\QuoteGraphQl\Model\Cart; -use Magento\Customer\Api\Data\AddressInterface as CustomerAddress; +use Magento\CustomerGraphQl\Model\Customer\Address\GetCustomerAddress; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Quote\Model\Quote\Address as QuoteAddress; use Magento\Quote\Model\Quote\AddressFactory as BaseQuoteAddressFactory; -use Magento\QuoteGraphQl\Model\Cart\QuoteAddress\Validator; /** * Create QuoteAddress @@ -23,21 +24,22 @@ class QuoteAddressFactory * @var BaseQuoteAddressFactory */ private $quoteAddressFactory; + /** - * @var Validator + * @var GetCustomerAddress */ - private $quoteAddressValidator; + private $getCustomerAddress; /** * @param BaseQuoteAddressFactory $quoteAddressFactory - * @param Validator $quoteAddressValidator + * @param GetCustomerAddress $getCustomerAddress */ public function __construct( BaseQuoteAddressFactory $quoteAddressFactory, - Validator $quoteAddressValidator + GetCustomerAddress $getCustomerAddress ) { $this->quoteAddressFactory = $quoteAddressFactory; - $this->quoteAddressValidator = $quoteAddressValidator; + $this->getCustomerAddress = $getCustomerAddress; } /** @@ -52,28 +54,29 @@ public function createBasedOnInputData(array $addressInput): QuoteAddress $quoteAddress = $this->quoteAddressFactory->create(); $quoteAddress->addData($addressInput); - $this->quoteAddressValidator->validate($quoteAddress); - return $quoteAddress; } /** - * Create QuoteAddress based on CustomerAddress + * Create Quote Address based on Customer Address * - * @param CustomerAddress $customerAddress + * @param int $customerAddressId + * @param int $customerId * @return QuoteAddress * @throws GraphQlInputException + * @throws GraphQlAuthorizationException + * @throws GraphQlNoSuchEntityException */ - public function createBasedOnCustomerAddress(CustomerAddress $customerAddress): QuoteAddress + public function createBasedOnCustomerAddress(int $customerAddressId, int $customerId): QuoteAddress { + $customerAddress = $this->getCustomerAddress->execute((int)$customerAddressId, $customerId); + $quoteAddress = $this->quoteAddressFactory->create(); try { $quoteAddress->importCustomerAddressData($customerAddress); } catch (LocalizedException $e) { throw new GraphQlInputException(__($e->getMessage()), $e); } - $this->quoteAddressValidator->validate($quoteAddress); - return $quoteAddress; } } diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php index 31524ea023222..c2bac13c07067 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php @@ -7,8 +7,9 @@ namespace Magento\QuoteGraphQl\Model\Cart; -use Magento\CustomerGraphQl\Model\Customer\Address\GetCustomerAddress; use Magento\CustomerGraphQl\Model\Customer\GetCustomer; +use Magento\Framework\GraphQl\Exception\GraphQlAuthenticationException; +use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Framework\GraphQl\Query\Resolver\ContextInterface; @@ -29,11 +30,6 @@ class SetBillingAddressOnCart */ private $getCustomer; - /** - * @var GetCustomerAddress - */ - private $getCustomerAddress; - /** * @var AssignBillingAddressToCart */ @@ -42,18 +38,15 @@ class SetBillingAddressOnCart /** * @param QuoteAddressFactory $quoteAddressFactory * @param GetCustomer $getCustomer - * @param GetCustomerAddress $getCustomerAddress * @param AssignBillingAddressToCart $assignBillingAddressToCart */ public function __construct( QuoteAddressFactory $quoteAddressFactory, GetCustomer $getCustomer, - GetCustomerAddress $getCustomerAddress, AssignBillingAddressToCart $assignBillingAddressToCart ) { $this->quoteAddressFactory = $quoteAddressFactory; $this->getCustomer = $getCustomer; - $this->getCustomerAddress = $getCustomerAddress; $this->assignBillingAddressToCart = $assignBillingAddressToCart; } @@ -65,6 +58,8 @@ public function __construct( * @param array $billingAddressInput * @return void * @throws GraphQlInputException + * @throws GraphQlAuthenticationException + * @throws GraphQlAuthorizationException * @throws GraphQlNoSuchEntityException */ public function execute(ContextInterface $context, CartInterface $cart, array $billingAddressInput): void @@ -97,8 +92,10 @@ public function execute(ContextInterface $context, CartInterface $cart, array $b $billingAddress = $this->quoteAddressFactory->createBasedOnInputData($addressInput); } else { $customer = $this->getCustomer->execute($context); - $customerAddress = $this->getCustomerAddress->execute((int)$customerAddressId, (int)$customer->getId()); - $billingAddress = $this->quoteAddressFactory->createBasedOnCustomerAddress($customerAddress); + $billingAddress = $this->quoteAddressFactory->createBasedOnCustomerAddress( + (int)$customerAddressId, + (int)$customer->getId() + ); } $this->assignBillingAddressToCart->execute($cart, $billingAddress, $useForShipping); diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCart.php index bbca7721754cb..6b0e2a311bf44 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCart.php @@ -7,7 +7,6 @@ namespace Magento\QuoteGraphQl\Model\Cart; -use Magento\CustomerGraphQl\Model\Customer\Address\GetCustomerAddress; use Magento\CustomerGraphQl\Model\Customer\GetCustomer; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Query\Resolver\ContextInterface; @@ -28,11 +27,6 @@ class SetShippingAddressesOnCart implements SetShippingAddressesOnCartInterface */ private $getCustomer; - /** - * @var GetCustomerAddress - */ - private $getCustomerAddress; - /** * @var AssignShippingAddressToCart */ @@ -41,18 +35,15 @@ class SetShippingAddressesOnCart implements SetShippingAddressesOnCartInterface /** * @param QuoteAddressFactory $quoteAddressFactory * @param GetCustomer $getCustomer - * @param GetCustomerAddress $getCustomerAddress * @param AssignShippingAddressToCart $assignShippingAddressToCart */ public function __construct( QuoteAddressFactory $quoteAddressFactory, GetCustomer $getCustomer, - GetCustomerAddress $getCustomerAddress, AssignShippingAddressToCart $assignShippingAddressToCart ) { $this->quoteAddressFactory = $quoteAddressFactory; $this->getCustomer = $getCustomer; - $this->getCustomerAddress = $getCustomerAddress; $this->assignShippingAddressToCart = $assignShippingAddressToCart; } @@ -86,8 +77,10 @@ public function execute(ContextInterface $context, CartInterface $cart, array $s $shippingAddress = $this->quoteAddressFactory->createBasedOnInputData($addressInput); } else { $customer = $this->getCustomer->execute($context); - $customerAddress = $this->getCustomerAddress->execute((int)$customerAddressId, (int)$customer->getId()); - $shippingAddress = $this->quoteAddressFactory->createBasedOnCustomerAddress($customerAddress); + $shippingAddress = $this->quoteAddressFactory->createBasedOnCustomerAddress( + (int)$customerAddressId, + (int)$customer->getId() + ); } $this->assignShippingAddressToCart->execute($cart, $shippingAddress); diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/AppliedCoupon.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/AppliedCoupon.php index ca69b763929be..8251089abcd60 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/AppliedCoupon.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/AppliedCoupon.php @@ -11,12 +11,27 @@ use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Quote\Api\CouponManagementInterface; /** * @inheritdoc */ class AppliedCoupon implements ResolverInterface { + /** + * @var CouponManagementInterface + */ + private $couponManagement; + + /** + * @param CouponManagementInterface $couponManagement + */ + public function __construct( + CouponManagementInterface $couponManagement + ) { + $this->couponManagement = $couponManagement; + } + /** * @inheritdoc */ @@ -26,9 +41,9 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value throw new LocalizedException(__('"model" value should be specified')); } $cart = $value['model']; + $cartId = $cart->getId(); - $appliedCoupon = $cart->getCouponCode(); - + $appliedCoupon = $this->couponManagement->get($cartId); return $appliedCoupon ? ['code' => $appliedCoupon] : null; } } diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/PlaceOrder.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/PlaceOrder.php new file mode 100644 index 0000000000000..3bd46a664f2ab --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/PlaceOrder.php @@ -0,0 +1,83 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\QuoteGraphQl\Model\Resolver; + +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Quote\Api\CartManagementInterface; +use Magento\QuoteGraphQl\Model\Cart\GetCartForUser; +use Magento\Sales\Api\OrderRepositoryInterface; + +/** + * @inheritdoc + */ +class PlaceOrder implements ResolverInterface +{ + /** + * @var CartManagementInterface + */ + private $cartManagement; + + /** + * @var GetCartForUser + */ + private $getCartForUser; + + /** + * @var OrderRepositoryInterface + */ + private $orderRepository; + + /** + * @param GetCartForUser $getCartForUser + * @param CartManagementInterface $cartManagement + * @param OrderRepositoryInterface $orderRepository + */ + public function __construct( + GetCartForUser $getCartForUser, + CartManagementInterface $cartManagement, + OrderRepositoryInterface $orderRepository + ) { + $this->getCartForUser = $getCartForUser; + $this->cartManagement = $cartManagement; + $this->orderRepository = $orderRepository; + } + + /** + * @inheritdoc + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) + { + if (!isset($args['input']['cart_id']) || empty($args['input']['cart_id'])) { + throw new GraphQlInputException(__('Required parameter "cart_id" is missing')); + } + $maskedCartId = $args['input']['cart_id']; + + $cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId()); + + try { + $orderId = $this->cartManagement->placeOrder($cart->getId()); + $order = $this->orderRepository->get($orderId); + + return [ + 'order' => [ + 'order_id' => $order->getIncrementId(), + ], + ]; + } catch (NoSuchEntityException $e) { + throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e); + } catch (LocalizedException $e) { + throw new GraphQlInputException(__('Unable to place order: %message', ['message' => $e->getMessage()]), $e); + } + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php index 5a708ceaedc28..f81ea3020d3d0 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php @@ -62,7 +62,11 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value try { $this->couponManagement->remove($cartId); } catch (NoSuchEntityException $e) { - throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e); + $message = $e->getMessage(); + if (preg_match('/The "\d+" Cart doesn\'t contain products/', $message)) { + $message = 'Cart does not contain products'; + } + throw new GraphQlNoSuchEntityException(__($message), $e); } catch (CouldNotDeleteException $e) { throw new LocalizedException(__($e->getMessage()), $e); } diff --git a/app/code/Magento/QuoteGraphQl/composer.json b/app/code/Magento/QuoteGraphQl/composer.json index 1bf4d581a5fe3..22ca9cfdfae9a 100644 --- a/app/code/Magento/QuoteGraphQl/composer.json +++ b/app/code/Magento/QuoteGraphQl/composer.json @@ -10,7 +10,8 @@ "magento/module-catalog": "*", "magento/module-store": "*", "magento/module-customer": "*", - "magento/module-customer-graph-ql": "*" + "magento/module-customer-graph-ql": "*", + "magento/module-sales": "*" }, "suggest": { "magento/module-graph-ql": "*" diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index 79cea3855f6f3..9ec3492f64531 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -17,6 +17,7 @@ type Mutation { setBillingAddressOnCart(input: SetBillingAddressOnCartInput): SetBillingAddressOnCartOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\SetBillingAddressOnCart") setShippingMethodsOnCart(input: SetShippingMethodsOnCartInput): SetShippingMethodsOnCartOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\SetShippingMethodsOnCart") setPaymentMethodOnCart(input: SetPaymentMethodOnCartInput): SetPaymentMethodOnCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\SetPaymentMethodOnCart") + placeOrder(input: PlaceOrderInput): PlaceOrderOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\PlaceOrder") } input AddSimpleProductsToCartInput { @@ -114,6 +115,10 @@ input ShippingMethodInput { method_code: String! } +input PlaceOrderInput { + cart_id: String! +} + input SetPaymentMethodOnCartInput { cart_id: String! payment_method: PaymentMethodInput! @@ -144,6 +149,10 @@ type ApplyCouponToCartOutput { cart: Cart! } +type PlaceOrderOutput { + order: Order! +} + type Cart { items: [CartItemInterface] @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartItems") applied_coupon: AppliedCoupon @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\AppliedCoupon") @@ -285,3 +294,7 @@ type CartItemSelectedOptionValuePrice { units: String! type: PriceTypeEnum! } + +type Order { + order_id: String +} diff --git a/app/code/Magento/Sales/Block/Adminhtml/Totals.php b/app/code/Magento/Sales/Block/Adminhtml/Totals.php index 83b155293c2b9..8172a3c0db4ad 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Totals.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Totals.php @@ -5,6 +5,11 @@ */ namespace Magento\Sales\Block\Adminhtml; +use Magento\Sales\Model\Order; + +/** + * Adminhtml sales totals block + */ class Totals extends \Magento\Sales\Block\Order\Totals { /** @@ -67,12 +72,16 @@ protected function _initTotals() if (!$this->getSource()->getIsVirtual() && ((double)$this->getSource()->getShippingAmount() || $this->getSource()->getShippingDescription()) ) { + $shippingLabel = __('Shipping & Handling'); + if ($this->isFreeShipping($this->getOrder()) && $this->getSource()->getDiscountDescription()) { + $shippingLabel .= sprintf(' (%s)', $this->getSource()->getDiscountDescription()); + } $this->_totals['shipping'] = new \Magento\Framework\DataObject( [ 'code' => 'shipping', 'value' => $this->getSource()->getShippingAmount(), 'base_value' => $this->getSource()->getBaseShippingAmount(), - 'label' => __('Shipping & Handling'), + 'label' => $shippingLabel, ] ); } @@ -109,4 +118,23 @@ protected function _initTotals() return $this; } + + /** + * Availability of free shipping in at least one order item + * + * @param Order $order + * @return bool + */ + private function isFreeShipping(Order $order): bool + { + $isFreeShipping = false; + foreach ($order->getItems() as $orderItem) { + if ($orderItem->getFreeShipping() == '1') { + $isFreeShipping = true; + break; + } + } + + return $isFreeShipping; + } } diff --git a/app/code/Magento/Sales/Model/Order/Pdf/Items/Creditmemo/DefaultCreditmemo.php b/app/code/Magento/Sales/Model/Order/Pdf/Items/Creditmemo/DefaultCreditmemo.php index 11908864236f6..48934e24a3795 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/Items/Creditmemo/DefaultCreditmemo.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/Items/Creditmemo/DefaultCreditmemo.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Model\Order\Pdf\Items\Creditmemo; /** @@ -66,11 +68,18 @@ public function draw() $lines = []; // draw Product name - $lines[0] = [['text' => $this->string->split($item->getName(), 35, true, true), 'feed' => 35]]; + $lines[0] = [ + [ + // phpcs:ignore Magento2.Functions.DiscouragedFunction + 'text' => $this->string->split(html_entity_decode($item->getName()), 35, true, true), + 'feed' => 35 + ] + ]; // draw SKU $lines[0][] = [ - 'text' => $this->string->split($this->getSku($item), 17), + // phpcs:ignore Magento2.Functions.DiscouragedFunction + 'text' => $this->string->split(html_entity_decode($this->getSku($item)), 17), 'feed' => 255, 'align' => 'right', ]; diff --git a/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php b/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php index 8562328025540..23c2c00daadc3 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Model\Order\Pdf\Items\Invoice; /** @@ -66,11 +68,18 @@ public function draw() $lines = []; // draw Product name - $lines[0] = [['text' => $this->string->split($item->getName(), 35, true, true), 'feed' => 35]]; + $lines[0] = [ + [ + // phpcs:ignore Magento2.Functions.DiscouragedFunction + 'text' => $this->string->split(html_entity_decode($item->getName()), 35, true, true), + 'feed' => 35 + ] + ]; // draw SKU $lines[0][] = [ - 'text' => $this->string->split($this->getSku($item), 17), + // phpcs:ignore Magento2.Functions.DiscouragedFunction + 'text' => $this->string->split(html_entity_decode($this->getSku($item)), 17), 'feed' => 290, 'align' => 'right', ]; diff --git a/app/code/Magento/Sales/Model/Order/Pdf/Items/Shipment/DefaultShipment.php b/app/code/Magento/Sales/Model/Order/Pdf/Items/Shipment/DefaultShipment.php index 6007e1dcf2b47..a88b508ba0789 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/Items/Shipment/DefaultShipment.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/Items/Shipment/DefaultShipment.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Model\Order\Pdf\Items\Shipment; /** @@ -65,14 +67,21 @@ public function draw() $lines = []; // draw Product name - $lines[0] = [['text' => $this->string->split($item->getName(), 60, true, true), 'feed' => 100]]; + $lines[0] = [ + [ + // phpcs:ignore Magento2.Functions.DiscouragedFunction + 'text' => $this->string->split(html_entity_decode($item->getName()), 60, true, true), + 'feed' => 100 + ] + ]; // draw QTY $lines[0][] = ['text' => $item->getQty() * 1, 'feed' => 35]; // draw SKU $lines[0][] = [ - 'text' => $this->string->split($this->getSku($item), 25), + // phpcs:ignore Magento2.Functions.DiscouragedFunction + 'text' => $this->string->split(html_entity_decode($this->getSku($item)), 25), 'feed' => 565, 'align' => 'right', ]; diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminInvoiceActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminInvoiceActionGroup.xml index 0061411d576e2..b90bac7e0881b 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminInvoiceActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminInvoiceActionGroup.xml @@ -19,18 +19,15 @@ <see selector="{{AdminInvoiceOrderInformationSection.customerName}}" userInput="{{customer.firstname}}" stepKey="seeCustomerName"/> <see selector="{{AdminInvoiceOrderInformationSection.customerEmail}}" userInput="{{customer.email}}" stepKey="seeCustomerEmail"/> <see selector="{{AdminInvoiceOrderInformationSection.customerGroup}}" userInput="{{customerGroup.code}}" stepKey="seeCustomerGroup"/> - <see selector="{{AdminInvoiceAddressInformationSection.billingAddress}}" userInput="{{billingAddress.street[0]}}" stepKey="seeBillingAddressStreet"/> <see selector="{{AdminInvoiceAddressInformationSection.billingAddress}}" userInput="{{billingAddress.city}}" stepKey="seeBillingAddressCity"/> <see selector="{{AdminInvoiceAddressInformationSection.billingAddress}}" userInput="{{billingAddress.country_id}}" stepKey="seeBillingAddressCountry"/> <see selector="{{AdminInvoiceAddressInformationSection.billingAddress}}" userInput="{{billingAddress.postcode}}" stepKey="seeBillingAddressPostcode"/> - <see selector="{{AdminInvoiceAddressInformationSection.shippingAddress}}" userInput="{{shippingAddress.street[0]}}" stepKey="seeShippingAddressStreet"/> <see selector="{{AdminInvoiceAddressInformationSection.shippingAddress}}" userInput="{{shippingAddress.city}}" stepKey="seeShippingAddressCity"/> <see selector="{{AdminInvoiceAddressInformationSection.shippingAddress}}" userInput="{{shippingAddress.country_id}}" stepKey="seeShippingAddressCountry"/> <see selector="{{AdminInvoiceAddressInformationSection.shippingAddress}}" userInput="{{shippingAddress.postcode}}" stepKey="seeShippingAddressPostcode"/> </actionGroup> - <!--Check that product is in invoice items--> <actionGroup name="seeProductInInvoiceItems"> <arguments> @@ -38,7 +35,6 @@ </arguments> <see selector="{{AdminInvoiceItemsSection.skuColumn}}" userInput="{{product.sku}}" stepKey="seeProductSkuInGrid"/> </actionGroup> - <!--Admin Fast Create Invoice--> <actionGroup name="adminFastCreateInvoice"> <click selector="{{AdminOrderDetailsMainActionsSection.invoice}}" stepKey="clickInvoiceButton"/> @@ -50,19 +46,22 @@ <click selector="{{AdminOrderInvoicesTabSection.viewInvoice}}" stepKey="openInvoicePage"/> <waitForPageLoad stepKey="waitForInvoicePageLoad"/> </actionGroup> - + <actionGroup name="clearInvoicesGridFilters"> + <amOnPage url="{{AdminInvoicesPage.url}}" stepKey="goToInvoices"/> + <waitForPageLoad stepKey="waitInvoicesGridToLoad"/> + <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clearFilters" /> + <waitForPageLoad stepKey="waitInvoicesGrid"/> + </actionGroup> <actionGroup name="goToInvoiceIntoOrder"> <click selector="{{AdminOrderDetailsMainActionsSection.invoice}}" stepKey="clickInvoiceAction"/> <seeInCurrentUrl url="{{AdminInvoiceNewPage.url}}" stepKey="seeOrderInvoiceUrl"/> <see selector="{{AdminHeaderSection.pageTitle}}" userInput="New Invoice" stepKey="seePageNameNewInvoicePage"/> </actionGroup> - <actionGroup name="StartCreateInvoiceFromOrderPage"> <click selector="{{AdminOrderDetailsMainActionsSection.invoice}}" stepKey="clickInvoiceAction"/> <seeInCurrentUrl url="{{AdminInvoiceNewPage.url}}" stepKey="seeNewInvoiceUrl"/> <see selector="{{AdminHeaderSection.pageTitle}}" userInput="New Invoice" stepKey="seeNewInvoicePageTitle"/> </actionGroup> - <actionGroup name="SubmitInvoice"> <click selector="{{AdminInvoiceMainActionsSection.submitInvoice}}" stepKey="clickSubmitInvoice"/> <waitForElementVisible selector="{{AdminMessagesSection.successMessage}}" stepKey="waitForMessageAppears"/> @@ -70,4 +69,15 @@ <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="grabOrderId"/> <seeInCurrentUrl url="{{AdminOrderDetailsPage.url('$grabOrderId')}}" stepKey="seeViewOrderPageInvoice"/> </actionGroup> + <!--Filter invoices by order id --> + <actionGroup name="filterInvoiceGridByOrderId"> + <arguments> + <argument name="orderId" type="string"/> + </arguments> + <amOnPage url="{{AdminInvoicesPage.url}}" stepKey="goToInvoices"/> + <click selector="{{AdminInvoicesGridSection.filter}}" stepKey="clickFilter"/> + <fillField selector="{{AdminInvoicesFiltersSection.orderNum}}" userInput="{{orderId}}" stepKey="fillOrderIdForFilter"/> + <click selector="{{AdminInvoicesFiltersSection.applyFilters}}" stepKey="clickApplyFilters"/> + <waitForPageLoad stepKey="waitForFiltersApply"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Search/i18n/de_DE.csv b/app/code/Magento/Search/i18n/de_DE.csv deleted file mode 100644 index 8b4b04aa3b9ec..0000000000000 --- a/app/code/Magento/Search/i18n/de_DE.csv +++ /dev/null @@ -1 +0,0 @@ -"Search Engine","Search Engine" diff --git a/app/code/Magento/Search/i18n/es_ES.csv b/app/code/Magento/Search/i18n/es_ES.csv deleted file mode 100644 index 8b4b04aa3b9ec..0000000000000 --- a/app/code/Magento/Search/i18n/es_ES.csv +++ /dev/null @@ -1 +0,0 @@ -"Search Engine","Search Engine" diff --git a/app/code/Magento/Search/i18n/fr_FR.csv b/app/code/Magento/Search/i18n/fr_FR.csv deleted file mode 100644 index 8b4b04aa3b9ec..0000000000000 --- a/app/code/Magento/Search/i18n/fr_FR.csv +++ /dev/null @@ -1 +0,0 @@ -"Search Engine","Search Engine" diff --git a/app/code/Magento/Search/i18n/nl_NL.csv b/app/code/Magento/Search/i18n/nl_NL.csv deleted file mode 100644 index 8b4b04aa3b9ec..0000000000000 --- a/app/code/Magento/Search/i18n/nl_NL.csv +++ /dev/null @@ -1 +0,0 @@ -"Search Engine","Search Engine" diff --git a/app/code/Magento/Search/i18n/pt_BR.csv b/app/code/Magento/Search/i18n/pt_BR.csv deleted file mode 100644 index c10566a7c9800..0000000000000 --- a/app/code/Magento/Search/i18n/pt_BR.csv +++ /dev/null @@ -1 +0,0 @@ -"Search Engine","Mecanismo de Busca" diff --git a/app/code/Magento/Search/i18n/zh_Hans_CN.csv b/app/code/Magento/Search/i18n/zh_Hans_CN.csv deleted file mode 100644 index 8b4b04aa3b9ec..0000000000000 --- a/app/code/Magento/Search/i18n/zh_Hans_CN.csv +++ /dev/null @@ -1 +0,0 @@ -"Search Engine","Search Engine" diff --git a/app/code/Magento/Swatches/i18n/de_DE.csv b/app/code/Magento/Swatches/i18n/de_DE.csv deleted file mode 100644 index f0aa34bbef26e..0000000000000 --- a/app/code/Magento/Swatches/i18n/de_DE.csv +++ /dev/null @@ -1,2 +0,0 @@ -"Swatch","Swatch" -"Swatches per Product","Swatches per Product" diff --git a/app/code/Magento/Swatches/i18n/es_ES.csv b/app/code/Magento/Swatches/i18n/es_ES.csv deleted file mode 100644 index f0aa34bbef26e..0000000000000 --- a/app/code/Magento/Swatches/i18n/es_ES.csv +++ /dev/null @@ -1,2 +0,0 @@ -"Swatch","Swatch" -"Swatches per Product","Swatches per Product" diff --git a/app/code/Magento/Swatches/i18n/fr_FR.csv b/app/code/Magento/Swatches/i18n/fr_FR.csv deleted file mode 100644 index f0aa34bbef26e..0000000000000 --- a/app/code/Magento/Swatches/i18n/fr_FR.csv +++ /dev/null @@ -1,2 +0,0 @@ -"Swatch","Swatch" -"Swatches per Product","Swatches per Product" diff --git a/app/code/Magento/Swatches/i18n/nl_NL.csv b/app/code/Magento/Swatches/i18n/nl_NL.csv deleted file mode 100644 index f0aa34bbef26e..0000000000000 --- a/app/code/Magento/Swatches/i18n/nl_NL.csv +++ /dev/null @@ -1,2 +0,0 @@ -"Swatch","Swatch" -"Swatches per Product","Swatches per Product" diff --git a/app/code/Magento/Swatches/i18n/pt_BR.csv b/app/code/Magento/Swatches/i18n/pt_BR.csv deleted file mode 100644 index f0aa34bbef26e..0000000000000 --- a/app/code/Magento/Swatches/i18n/pt_BR.csv +++ /dev/null @@ -1,2 +0,0 @@ -"Swatch","Swatch" -"Swatches per Product","Swatches per Product" diff --git a/app/code/Magento/Swatches/i18n/zh_Hans_CN.csv b/app/code/Magento/Swatches/i18n/zh_Hans_CN.csv deleted file mode 100644 index f0aa34bbef26e..0000000000000 --- a/app/code/Magento/Swatches/i18n/zh_Hans_CN.csv +++ /dev/null @@ -1,2 +0,0 @@ -"Swatch","Swatch" -"Swatches per Product","Swatches per Product" diff --git a/app/code/Magento/Theme/Controller/Result/JsFooterPlugin.php b/app/code/Magento/Theme/Controller/Result/JsFooterPlugin.php new file mode 100644 index 0000000000000..a81f29280af96 --- /dev/null +++ b/app/code/Magento/Theme/Controller/Result/JsFooterPlugin.php @@ -0,0 +1,38 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Theme\Controller\Result; + +/** + * Plugin for putting messages to cookies + */ +class JsFooterPlugin +{ + /** + * Put all javascript to footer before sending the response + * + * @param \Magento\Framework\App\Response\Http $subject + * @return void + */ + public function beforeSendResponse(\Magento\Framework\App\Response\Http $subject) + { + $content = $subject->getContent(); + $script = []; + if (strpos($content, '</body') !== false) { + $pattern = '#<script[^>]*+(?<!text/x-magento-template.)>.*?</script>#is'; + $content = preg_replace_callback( + $pattern, + function ($matchPart) use (&$script) { + $script[] = $matchPart[0]; + return ''; + }, + $content + ); + $subject->setContent( + str_replace('</body', implode("\n", $script) . "\n</body", $content) + ); + } + } +} diff --git a/app/code/Magento/Theme/etc/frontend/di.xml b/app/code/Magento/Theme/etc/frontend/di.xml index 7db2783cd8dfa..3837c6f717b54 100644 --- a/app/code/Magento/Theme/etc/frontend/di.xml +++ b/app/code/Magento/Theme/etc/frontend/di.xml @@ -26,4 +26,7 @@ <type name="Magento\Framework\Controller\ResultInterface"> <plugin name="result-messages" type="Magento\Theme\Controller\Result\MessagePlugin"/> </type> + <type name="Magento\Framework\App\Response\Http"> + <plugin name="result-js-footer" type="Magento\Theme\Controller\Result\JsFooterPlugin"/> + </type> </config> diff --git a/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php b/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php index 9304d25cc8a98..d16b1eaad7f37 100644 --- a/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php +++ b/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php @@ -115,7 +115,7 @@ public function __toString() $this->compiler->compile($templateRootElement, $this->component, $this->component); $this->appendLayoutConfiguration(); $result = $this->compiler->postprocessing($this->template->__toString()); - } catch (\Exception $e) { + } catch (\Throwable $e) { $this->logger->critical($e->getMessage()); $result = $e->getMessage(); } diff --git a/app/code/Magento/Ui/i18n/de_DE.csv b/app/code/Magento/Ui/i18n/de_DE.csv deleted file mode 100644 index 2efac126b857c..0000000000000 --- a/app/code/Magento/Ui/i18n/de_DE.csv +++ /dev/null @@ -1,4 +0,0 @@ -"Log JS Errors to Session Storage","Log JS Errors to Session Storage" -"If enabled, can be used by functional tests for extended reporting","If enabled, can be used by functional tests for extended reporting" -"Log JS Errors to Session Storage Key","Log JS Errors to Session Storage Key" -"Use this key to retrieve collected js errors","Use this key to retrieve collected js errors" diff --git a/app/code/Magento/Ui/i18n/es_ES.csv b/app/code/Magento/Ui/i18n/es_ES.csv deleted file mode 100644 index 2efac126b857c..0000000000000 --- a/app/code/Magento/Ui/i18n/es_ES.csv +++ /dev/null @@ -1,4 +0,0 @@ -"Log JS Errors to Session Storage","Log JS Errors to Session Storage" -"If enabled, can be used by functional tests for extended reporting","If enabled, can be used by functional tests for extended reporting" -"Log JS Errors to Session Storage Key","Log JS Errors to Session Storage Key" -"Use this key to retrieve collected js errors","Use this key to retrieve collected js errors" diff --git a/app/code/Magento/Ui/i18n/fr_FR.csv b/app/code/Magento/Ui/i18n/fr_FR.csv deleted file mode 100644 index 2efac126b857c..0000000000000 --- a/app/code/Magento/Ui/i18n/fr_FR.csv +++ /dev/null @@ -1,4 +0,0 @@ -"Log JS Errors to Session Storage","Log JS Errors to Session Storage" -"If enabled, can be used by functional tests for extended reporting","If enabled, can be used by functional tests for extended reporting" -"Log JS Errors to Session Storage Key","Log JS Errors to Session Storage Key" -"Use this key to retrieve collected js errors","Use this key to retrieve collected js errors" diff --git a/app/code/Magento/Ui/i18n/nl_NL.csv b/app/code/Magento/Ui/i18n/nl_NL.csv deleted file mode 100644 index 2efac126b857c..0000000000000 --- a/app/code/Magento/Ui/i18n/nl_NL.csv +++ /dev/null @@ -1,4 +0,0 @@ -"Log JS Errors to Session Storage","Log JS Errors to Session Storage" -"If enabled, can be used by functional tests for extended reporting","If enabled, can be used by functional tests for extended reporting" -"Log JS Errors to Session Storage Key","Log JS Errors to Session Storage Key" -"Use this key to retrieve collected js errors","Use this key to retrieve collected js errors" diff --git a/app/code/Magento/Ui/i18n/pt_BR.csv b/app/code/Magento/Ui/i18n/pt_BR.csv deleted file mode 100644 index 2efac126b857c..0000000000000 --- a/app/code/Magento/Ui/i18n/pt_BR.csv +++ /dev/null @@ -1,4 +0,0 @@ -"Log JS Errors to Session Storage","Log JS Errors to Session Storage" -"If enabled, can be used by functional tests for extended reporting","If enabled, can be used by functional tests for extended reporting" -"Log JS Errors to Session Storage Key","Log JS Errors to Session Storage Key" -"Use this key to retrieve collected js errors","Use this key to retrieve collected js errors" diff --git a/app/code/Magento/Ui/i18n/zh_Hans_CN.csv b/app/code/Magento/Ui/i18n/zh_Hans_CN.csv deleted file mode 100644 index 2efac126b857c..0000000000000 --- a/app/code/Magento/Ui/i18n/zh_Hans_CN.csv +++ /dev/null @@ -1,4 +0,0 @@ -"Log JS Errors to Session Storage","Log JS Errors to Session Storage" -"If enabled, can be used by functional tests for extended reporting","If enabled, can be used by functional tests for extended reporting" -"Log JS Errors to Session Storage Key","Log JS Errors to Session Storage Key" -"Use this key to retrieve collected js errors","Use this key to retrieve collected js errors" diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/image-uploader.js b/app/code/Magento/Ui/view/base/web/js/form/element/image-uploader.js index 0ae09f14fa946..b490ac557e71b 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/image-uploader.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/image-uploader.js @@ -11,8 +11,7 @@ define([ 'Magento_Ui/js/modal/alert', 'Magento_Ui/js/lib/validation/validator', 'Magento_Ui/js/form/element/file-uploader', - 'mage/adminhtml/browser', - 'mage/adminhtml/tools' + 'mage/adminhtml/browser' ], function ($, _, utils, uiAlert, validator, Element, browser) { 'use strict'; diff --git a/app/code/Magento/Ui/view/base/web/js/lib/core/storage/local.js b/app/code/Magento/Ui/view/base/web/js/lib/core/storage/local.js index 038907c21224d..adeb510ab3e40 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/core/storage/local.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/core/storage/local.js @@ -114,8 +114,8 @@ define([ * * @param {String} path - Path to the property. * - * @example Retrieveing data. - * localStoarge => + * @example Retrieving data. + * localStorage => * 'appData' => ' * "one": {"two": "three"} * ' @@ -139,7 +139,7 @@ define([ * * @example Setting data. * storage.set('one.two', 'four'); - * => localStoarge => + * => localStorage => * 'appData' => ' * "one": {"two": "four"} * ' @@ -159,7 +159,7 @@ define([ * * @example Removing data. * storage.remove('one.two', 'four'); - * => localStoarge => + * => localStorage => * 'appData' => ' * "one": {} * ' diff --git a/app/code/Magento/Ui/view/base/web/js/lib/knockout/template/renderer.js b/app/code/Magento/Ui/view/base/web/js/lib/knockout/template/renderer.js index 2e0c53373f807..2cfd961619249 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/knockout/template/renderer.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/knockout/template/renderer.js @@ -519,7 +519,7 @@ define([ }, /** - * Custom 'render' attrobute handler function. Wraps child elements + * Custom 'render' attribute handler function. Wraps child elements * of a node with knockout's 'ko template:' comment tag. * * @param {HTMLElement} node - Element to be processed. diff --git a/app/code/Magento/Ui/view/base/web/js/lib/logger/logger-utils.js b/app/code/Magento/Ui/view/base/web/js/lib/logger/logger-utils.js index fe83f600132ed..bf7ae0cdc3e98 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/logger/logger-utils.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/logger/logger-utils.js @@ -42,14 +42,14 @@ define([], function () { * Method that creates object of messages * @param {String} requested - log message that showing that request for class is started * @param {String} loaded - log message that show when requested class is loaded - * @param {String} failded - log message that show when requested class is failed + * @param {String} failed - log message that show when requested class is failed * @returns {Object} */ - LogUtils.prototype.createMessages = function (requested, loaded, failded) { + LogUtils.prototype.createMessages = function (requested, loaded, failed) { return { requested: requested || '', loaded: loaded || '', - failed: failded || '' + failed: failed || '' }; }; @@ -57,14 +57,14 @@ define([], function () { * Method that creates object of log levels * @param {String} requested - log message that showing that request for class is started * @param {String} loaded - log message that show when requested class is loaded - * @param {String} failded - log message that show when requested class is failed + * @param {String} failed - log message that show when requested class is failed * @returns {Object} */ - LogUtils.prototype.createLevels = function (requested, loaded, failded) { + LogUtils.prototype.createLevels = function (requested, loaded, failed) { return { requested: requested || 'info', loaded: loaded || 'info', - failed: failded || 'warn' + failed: failed || 'warn' }; }; diff --git a/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js b/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js index 8ebbf88775b86..407984c7881a2 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js @@ -64,7 +64,7 @@ define([ } /** - * Validates provied value by a specified set of rules. + * Validates provided value by a specified set of rules. * * @param {(String|Object)} rules - One or many validation rules. * @param {*} value - Value to be checked. diff --git a/app/code/Magento/Ui/view/base/web/js/lib/view/utils/dom-observer.js b/app/code/Magento/Ui/view/base/web/js/lib/view/utils/dom-observer.js index 03012918f4a0d..f8e752fb77af2 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/view/utils/dom-observer.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/view/utils/dom-observer.js @@ -141,7 +141,7 @@ define([ } /** - * Calls handlers assocoiated with an added node. + * Calls handlers associated with an added node. * Adds listeners for the node removal. * * @param {HTMLElement} node - Added node. @@ -163,7 +163,7 @@ define([ } /** - * Calls handlers assocoiated with a removed node. + * Calls handlers associated with a removed node. * * @param {HTMLElement} node - Removed node. */ diff --git a/app/code/Magento/Ui/view/frontend/web/templates/form/element/uploader/uploader.html b/app/code/Magento/Ui/view/frontend/web/templates/form/element/uploader/uploader.html new file mode 100644 index 0000000000000..226ad2915bb61 --- /dev/null +++ b/app/code/Magento/Ui/view/frontend/web/templates/form/element/uploader/uploader.html @@ -0,0 +1,37 @@ +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<div class="field-control" css="'_with-tooltip': $data.tooltip"> + <div class="file-uploader" data-role="drop-zone" css="_loading: isLoading"> + <div class="file-uploader-area"> + <input type="file" afterRender="onElementRender" attr="id: uid, name: inputName, multiple: isMultipleFiles" + disable="disabled"/> + <label class="file-uploader-button action-default" attr="for: uid" translate="'Upload'"/> + + <span class="file-uploader-spinner"/> + <render args="fallbackResetTpl" if="$data.showFallbackReset && $data.isDifferedFromDefault"/> + </div> + + <render args="tooltipTpl" if="$data.tooltip"/> + + <div class="field-note" if="$data.notice" attr="id: noticeId"> + <span html="notice"/> + </div> + + <each args="data: value, as: '$file'" render="$parent.getPreviewTmpl($file)"/> + + <div if="isMultipleFiles" class="file-uploader-summary"> + <label attr="for: uid" + class="file-uploader-placeholder" + css="'placeholder-' + placeholderType"> + <span class="file-uploader-placeholder-text" + translate="'Click here or drag and drop to add files.'"/> + </label> + </div> + </div> + <render args="$data.service.template" if="$data.hasService()"/> +</div> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/MetaData/url_rewrite-meta.xml b/app/code/Magento/UrlRewrite/Test/Mftf/MetaData/url_rewrite-meta.xml deleted file mode 100644 index 0738b17d6e0f0..0000000000000 --- a/app/code/Magento/UrlRewrite/Test/Mftf/MetaData/url_rewrite-meta.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> - <operation name="CreateUrlRewrite" dataType="urlRewrite" type="create" auth="adminFormKey" url="/admin/url_rewrite/save/" method="POST" successRegex="/messages-message-success/"> - <field key="store_id">integer</field> - <field key="redirect_type">integer</field> - <field key="request_path">string</field> - <field key="target_path">string</field> - <field key="description">string</field> - </operation> -</operations> diff --git a/app/code/Magento/User/Test/Mftf/Data/UserData.xml b/app/code/Magento/User/Test/Mftf/Data/UserData.xml index 80c1cc3022964..d602f094ce4e5 100644 --- a/app/code/Magento/User/Test/Mftf/Data/UserData.xml +++ b/app/code/Magento/User/Test/Mftf/Data/UserData.xml @@ -8,6 +8,14 @@ <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="DefaultAdminUser" type="user"> + <data key="username">{{_ENV.MAGENTO_ADMIN_USERNAME}}</data> + <data key="password">{{_ENV.MAGENTO_ADMIN_PASSWORD}}</data> + </entity> + <entity name="AdminUserWrongCredentials"> + <data key="username" unique="suffix">username_</data> + <data key="password" unique="suffix">password_</data> + </entity> <entity name="admin" type="user"> <data key="email">admin@magento.com</data> <data key="password">admin123</data> diff --git a/app/code/Magento/Widget/Block/Adminhtml/Widget/Options.php b/app/code/Magento/Widget/Block/Adminhtml/Widget/Options.php index cc2af4996d562..32bae10c801c8 100644 --- a/app/code/Magento/Widget/Block/Adminhtml/Widget/Options.php +++ b/app/code/Magento/Widget/Block/Adminhtml/Widget/Options.php @@ -91,7 +91,7 @@ public function getMainFieldset() if ($this->_getData('main_fieldset') instanceof \Magento\Framework\Data\Form\Element\Fieldset) { return $this->_getData('main_fieldset'); } - $mainFieldsetHtmlId = 'options_fieldset' . md5($this->getWidgetType()); + $mainFieldsetHtmlId = 'options_fieldset' . hash('sha256', $this->getWidgetType()); $this->setMainFieldsetHtmlId($mainFieldsetHtmlId); $fieldset = $this->getForm()->addFieldset( $mainFieldsetHtmlId, @@ -141,7 +141,6 @@ protected function _addField($parameter) { $form = $this->getForm(); $fieldset = $this->getMainFieldset(); - //$form->getElement('options_fieldset'); // prepare element data with values (either from request of from default values) $fieldName = $parameter->getKey(); @@ -166,9 +165,13 @@ protected function _addField($parameter) if (is_array($data['value'])) { foreach ($data['value'] as &$value) { - $value = html_entity_decode($value); + if (is_string($value)) { + // phpcs:ignore Magento2.Functions.DiscouragedFunction + $value = html_entity_decode($value); + } } } else { + // phpcs:ignore Magento2.Functions.DiscouragedFunction $data['value'] = html_entity_decode($data['value']); } diff --git a/app/code/Magento/Widget/Model/Widget.php b/app/code/Magento/Widget/Model/Widget.php index 52dc8e7837a3c..d07e84186b2c9 100644 --- a/app/code/Magento/Widget/Model/Widget.php +++ b/app/code/Magento/Widget/Model/Widget.php @@ -248,17 +248,13 @@ public function getWidgets($filters = []) $result = $widgets; // filter widgets by params - if (is_array($filters) && count($filters) > 0) { + if (is_array($filters) && !empty($filters)) { foreach ($widgets as $code => $widget) { - try { - foreach ($filters as $field => $value) { - if (!isset($widget[$field]) || (string)$widget[$field] != $value) { - throw new \Exception(); - } + foreach ($filters as $field => $value) { + if (!isset($widget[$field]) || (string)$widget[$field] != $value) { + unset($result[$code]); + break; } - } catch (\Exception $e) { - unset($result[$code]); - continue; } } } @@ -323,8 +319,6 @@ public function getWidgetDeclaration($type, $params = [], $asIs = true) $directive .= $this->getWidgetPageVarName($params); - $directive .= sprintf(' type_name="%s"', $widget['name']); - $directive .= '}}'; if ($asIs) { diff --git a/app/code/Magento/Widget/Model/Widget/Config.php b/app/code/Magento/Widget/Model/Widget/Config.php index 4f81ef33f47f7..00b055b35a69d 100644 --- a/app/code/Magento/Widget/Model/Widget/Config.php +++ b/app/code/Magento/Widget/Model/Widget/Config.php @@ -120,6 +120,7 @@ public function getWidgetPlaceholderImageUrls() /** * Return url to error image + * * @return string */ public function getErrorImageUrl() @@ -129,6 +130,7 @@ public function getErrorImageUrl() /** * Return url to wysiwyg plugin + * * @return string */ public function getWysiwygJsPluginSrc() @@ -157,7 +159,7 @@ public function getWidgetWindowUrl($config) } } - if (count($skipped) > 0) { + if (!empty($skipped)) { $params['skip_widgets'] = $this->encodeWidgetsToQuery($skipped); } return $this->_backendUrl->getUrl('adminhtml/widget/index', $params); @@ -189,6 +191,8 @@ public function decodeWidgetsFromQuery($queryParam) } /** + * Get available widgets. + * * @param \Magento\Framework\DataObject $config Editor element config * @return array */ @@ -202,7 +206,7 @@ public function getAvailableWidgets($config) if (is_array($skipped) && in_array($widget['type'], $skipped)) { continue; } - $result[] = $widget['name']->getText(); + $result[$widget['type']] = $widget['name']->getText(); } } diff --git a/app/code/Magento/Widget/Test/Unit/Model/WidgetTest.php b/app/code/Magento/Widget/Test/Unit/Model/WidgetTest.php index 5c546d7e2435c..850a3fbe83211 100644 --- a/app/code/Magento/Widget/Test/Unit/Model/WidgetTest.php +++ b/app/code/Magento/Widget/Test/Unit/Model/WidgetTest.php @@ -224,7 +224,6 @@ public function testGetWidgetDeclaration() $this->assertContains('title="my "widget""', $result); $this->assertContains('conditions_encoded="encoded-conditions-string"', $result); $this->assertContains('page_var_name="pasdf"', $result); - $this->assertContains('type_name=""}}', $result); } /** @@ -275,7 +274,6 @@ public function testGetWidgetDeclarationWithZeroValueParam() ); $this->assertContains('{{widget type="Magento\CatalogWidget\Block\Product\ProductsList"', $result); $this->assertContains('page_var_name="pasdf"', $result); - $this->assertContains('type_name=""}}', $result); $this->assertContains('products_count=""', $result); } } diff --git a/app/design/frontend/Magento/blank/web/css/source/_forms.less b/app/design/frontend/Magento/blank/web/css/source/_forms.less index c9f3c3d72ef4c..26f5ff89e99e3 100644 --- a/app/design/frontend/Magento/blank/web/css/source/_forms.less +++ b/app/design/frontend/Magento/blank/web/css/source/_forms.less @@ -101,6 +101,18 @@ .lib-form-validation-note(); } + .product-options-wrapper { + .date { + &.required { + div[for*='options'] { + &.mage-error { + display: none !important; + } + } + } + } + } + .field .tooltip { .lib-tooltip(right); .tooltip-content { diff --git a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less index e5915969c91b9..77fb53a2ab02a 100644 --- a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less +++ b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less @@ -34,12 +34,12 @@ .product { &-items { - font-size: 0; + .lib-inline-block-space-container(); &:extend(.abs-reset-list all); } &-item { - font-size: 1.4rem; + .lib-inline-block-space-item(); vertical-align: top; .products-grid & { diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_checkout.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_checkout.less index 3ea1f5b7f6842..ac5ab0d87bf62 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_checkout.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_checkout.less @@ -7,6 +7,8 @@ // Variables // _____________________________________________ +@import 'fields/_file-uploader.less'; + @checkout-wrapper__margin: @indent__base; @checkout-wrapper__columns: 16; diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/fields/_file-uploader.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/fields/_file-uploader.less new file mode 100644 index 0000000000000..7b06186ef9ad3 --- /dev/null +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/fields/_file-uploader.less @@ -0,0 +1,450 @@ +// /** +// * Copyright © Magento, Inc. All rights reserved. +// * See COPYING.txt for license details. +// */ + +// +// Components -> Single File Uploader +// _____________________________________________ + +// +// Variables +// --------------------------------------------- + +@icon-delete__content: '\e604'; +@icon-file__content: '\e626'; + + +@file-uploader-preview__border-color: @color-lighter-grayish; +@file-uploader-preview__background-color: @color-white; +@file-uploader-preview-focus__color: @color-blue2; + +@file-uploader-document-icon__color: @color-gray80; +@file-uploader-document-icon__size: 7rem; +@file-uploader-document-icon__z-index: @data-grid-file-uploader-image__z-index + 1; + +@file-uploader-video-icon__color: @color-gray80; +@file-uploader-video-icon__size: 4rem; +@file-uploader-video-icon__z-index: @data-grid-file-uploader-image__z-index + 1; + +@file-uploader-placeholder-icon__color: @color-gray80; +@file-uploader-placeholder-icon__z-index: @data-grid-file-uploader-image__z-index + 1; + +@file-uploader-delete-icon__color: @color-brownie; +@file-uploader-delete-icon__hover__color: @color-brownie-vanilla; +@file-uploader-delete-icon__font-size: 1.6rem; + +@file-uploader-muted-text__color: @color-gray62; + +@file-uploader-preview__width: 150px; +@file-uploader-preview__height: @file-uploader-preview__width; +@file-uploader-preview__opacity: .7; + +@file-uploader-spinner-dimensions: 15px; + +@file-uploader-dragover__background: @color-gray83; +@file-uploader-dragover-focus__color: @color-blue2; + +// Grid uploader + +@data-grid-file-uploader-image__size: 5rem; +@data-grid-file-uploader-image__z-index: 1; + +@data-grid-file-uploader-menu-button__width: 2rem; + +@data-grid-file-uploader-upload-icon__color: @color-darkie-gray; +@data-grid-file-uploader-upload-icon__hover__color: @color-very-dark-gray; +@data-grid-file-uploader-upload-icon__line-height: 48px; + +@data-grid-file-uploader-wrapper__size: @data-grid-file-uploader-image__size + 2rem; + +// +// Single file uploader +// --------------------------------------------- + +.file-uploader-area { + position: relative; + + input[type='file'] { + cursor: pointer; + opacity: 0; + overflow: hidden; + position: absolute; + visibility: hidden; + width: 0; + + &:focus { + + .file-uploader-button { + box-shadow: 0 0 0 1px @file-uploader-preview-focus__color; + } + } + + &:disabled { + + .file-uploader-button { + cursor: default; + opacity: .5; + pointer-events: none; + } + } + } +} + +.file-uploader-summary { + display: inline-block; + vertical-align: top; +} + +.file-uploader-button { + background: @color-gray-darken0; + border: 1px solid @color-gray_light; + box-sizing: border-box; + color: @color-black_dark; + cursor: pointer; + display: inline-block; + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 1.4rem; + font-weight: 600; + line-height: 1.6rem; + margin: 0; + padding: 7px 15px; + vertical-align: middle; + + &._is-dragover { + background: @file-uploader-dragover__background; + border: 1px solid @file-uploader-preview-focus__color; + } +} + +.file-uploader-spinner { + background-image: url('@{baseDir}images/loader-1.gif'); + background-position: 50%; + background-repeat: no-repeat; + background-size: @file-uploader-spinner-dimensions; + display: none; + height: 30px; + margin-left: @indent__s; + vertical-align: top; + width: @file-uploader-spinner-dimensions; +} + +.file-uploader-preview { + .action-remove { + &:extend(.abs-action-reset all); + .lib-icon-font ( + @icon-delete__content, + @_icon-font: @icons__font-name, + @_icon-font-size: @file-uploader-delete-icon__font-size, + @_icon-font-color: @file-uploader-delete-icon__color, + @_icon-font-color-hover: @file-uploader-delete-icon__hover__color, + @_icon-font-text-hide: true, + @_icon-font-display: block + ); + bottom: 4px; + cursor: pointer; + display: block; + height: 27px; + left: 6px; + padding: 2px; + position: absolute; + text-decoration: none; + width: 25px; + z-index: 2; + } + + &:hover { + .preview-image img, + .preview-link:before { + opacity: @file-uploader-preview__opacity; + } + } + + .preview-link { + display: block; + height: 100%; + } + + .preview-image img { + bottom: 0; + left: 0; + margin: auto; + max-height: 100%; + max-width: 100%; + position: absolute; + right: 0; + top: 0; + z-index: 1; + } + + .preview-video { + .lib-icon-font( + @icon-file__content, + @_icon-font: @icons__font-name, + @_icon-font-size: @file-uploader-video-icon__size, + @_icon-font-color: @file-uploader-video-icon__color, + @_icon-font-color-hover: @file-uploader-video-icon__color + ); + + &:before { + left: 0; + margin-top: -@file-uploader-video-icon__size / 2; + position: absolute; + right: 0; + top: 50%; + z-index: @file-uploader-video-icon__z-index; + } + } + + .preview-document { + .lib-icon-font( + @icon-file__content, + @_icon-font: @icons__font-name, + @_icon-font-size: @file-uploader-document-icon__size, + @_icon-font-color: @file-uploader-document-icon__color, + @_icon-font-color-hover: @file-uploader-document-icon__color + ); + + &:before { + left: 0; + margin-top: -@file-uploader-document-icon__size / 2; + position: absolute; + right: 0; + top: 50%; + z-index: @file-uploader-document-icon__z-index; + } + } +} + +.file-uploader-preview, +.file-uploader-placeholder { + background: @file-uploader-preview__background-color; + border: 1px solid @file-uploader-preview__border-color; + box-sizing: border-box; + cursor: pointer; + display: block; + height: @file-uploader-preview__height; + line-height: 1; + margin: @indent__s @indent__m @indent__s 0; + overflow: hidden; + position: relative; + width: @file-uploader-preview__width; +} + +.file-uploader { + &._loading { + .file-uploader-spinner { + display: inline-block; + } + } + + .admin__field-note, + .admin__field-error { + margin-bottom: @indent__s; + } + + .file-uploader-filename { + .lib-text-overflow(); + max-width: @file-uploader-preview__width; + word-break: break-all; + + &:first-child { + margin-bottom: @indent__s; + } + } + + .file-uploader-meta { + color: @file-uploader-muted-text__color; + } + + .admin__field-fallback-reset { + margin-left: @indent__s; + } + + ._keyfocus & .action-remove { + &:focus { + box-shadow: 0 0 0 1px @file-uploader-preview-focus__color; + } + } +} + +// Placeholder for multiple uploader +.file-uploader-placeholder { + &.placeholder-document { + .lib-icon-font( + @icon-file__content, + @_icon-font: @icons__font-name, + @_icon-font-size: 5rem, + @_icon-font-color: @file-uploader-placeholder-icon__color, + @_icon-font-color-hover: @file-uploader-placeholder-icon__color + ); + + &:before { + left: 0; + position: absolute; + right: 0; + top: 20px; + z-index: @file-uploader-placeholder-icon__z-index; + } + } + + &.placeholder-image { + .lib-icon-font( + @icon-file__content, + @_icon-font: @icons__font-name, + @_icon-font-size: 5rem, + @_icon-font-color: @file-uploader-placeholder-icon__color, + @_icon-font-color-hover: @file-uploader-placeholder-icon__color + ); + + &:before { + left: 0; + position: absolute; + right: 0; + top: 20px; + z-index: @file-uploader-placeholder-icon__z-index; + } + } + + &.placeholder-video { + .lib-icon-font( + @icon-file__content, + @_icon-font: @icons__font-name, + @_icon-font-size: 3rem, + @_icon-font-color: @file-uploader-placeholder-icon__color, + @_icon-font-color-hover: @file-uploader-placeholder-icon__color + ); + + &:before { + left: 0; + position: absolute; + right: 0; + top: 30px; + z-index: @file-uploader-placeholder-icon__z-index; + } + } +} + +.file-uploader-placeholder-text { + bottom: 0; + color: @color-blue-dodger; + font-size: 1.1rem; + left: 0; + line-height: @line-height__base; + margin-bottom: 15%; + padding: 0 @indent__base; + position: absolute; + right: 0; + text-align: center; +} + +// +// Grid image uploader +// --------------------------------------------- + +.data-grid-file-uploader { + min-width: @data-grid-file-uploader-wrapper__size; + + &._loading { + .file-uploader-spinner { + display: block; + } + + .file-uploader-button { + &:before { + display: none; + } + } + } + + .file-uploader-image { + background: transparent; + bottom: 0; + left: 0; + margin: auto; + max-height: 100%; + max-width: 100%; + position: absolute; + right: 0; + top: 0; + z-index: @data-grid-file-uploader-image__z-index; + + + .file-uploader-area { + .file-uploader-button { + &:before { + display: none; + } + } + } + } + + .file-uploader-area { + z-index: @data-grid-file-uploader-image__z-index + 1; + } + + .file-uploader-spinner { + height: 100%; + margin: 0; + position: absolute; + top: 0; + width: 100%; + } + + .file-uploader-button { + display: block; + height: @data-grid-file-uploader-upload-icon__line-height; + text-align: center; + + .lib-icon-font ( + @icon-file__content, + @_icon-font: @icons__font-name, + @_icon-font-size: 1.3rem, + @_icon-font-line-height: @data-grid-file-uploader-upload-icon__line-height, + @_icon-font-color: @data-grid-file-uploader-upload-icon__color, + @_icon-font-color-hover: @data-grid-file-uploader-upload-icon__hover__color, + @_icon-font-text-hide: true, + @_icon-font-display: block + ); + } + + .action-select-wrap { + float: left; + + .action-select { + border: 1px solid @file-uploader-preview__border-color; + display: block; + height: @data-grid-file-uploader-image__size; + margin-left: -1px; + padding: 0; + width: @data-grid-file-uploader-menu-button__width; + + &:after { + border-color: @data-grid-file-uploader-upload-icon__color transparent transparent transparent; + left: 50%; + margin: 0 0 0 -5px; + } + + &:hover { + &:after { + border-color: @data-grid-file-uploader-upload-icon__hover__color transparent transparent transparent; + } + } + + > span { + display: none; + } + } + + .action-menu { + left: 4rem; + right: auto; + z-index: @data-grid-file-uploader-image__z-index + 1; + } + } +} + +.data-grid-file-uploader-inner { + border: 1px solid @file-uploader-preview__border-color; + float: left; + height: @data-grid-file-uploader-image__size; + position: relative; + width: @data-grid-file-uploader-image__size; +} diff --git a/app/design/frontend/Magento/luma/web/css/source/_forms.less b/app/design/frontend/Magento/luma/web/css/source/_forms.less index 98dd57dead74c..8533318a12d1b 100644 --- a/app/design/frontend/Magento/luma/web/css/source/_forms.less +++ b/app/design/frontend/Magento/luma/web/css/source/_forms.less @@ -104,6 +104,10 @@ .select-styling(); } + select.admin__control-multiselect { + height: auto; + } + .field-error, div.mage-error[generated] { margin-top: 7px; @@ -113,6 +117,18 @@ .lib-form-validation-note(); } + .product-options-wrapper { + .date { + &.required { + div[for*='options'] { + &.mage-error { + display: none !important; + } + } + } + } + } + // TEMP .field .tooltip { diff --git a/dev/tests/api-functional/testsuite/Magento/Analytics/Api/LinkProviderTest.php b/dev/tests/api-functional/testsuite/Magento/Analytics/Api/LinkProviderTest.php index 6fd7551676660..4bf1335f20667 100644 --- a/dev/tests/api-functional/testsuite/Magento/Analytics/Api/LinkProviderTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Analytics/Api/LinkProviderTest.php @@ -95,6 +95,6 @@ public function testGetAll() */ private function isTestBaseUrlSecure() { - return strpos('https://', TESTS_BASE_URL) !== false; + return strpos(TESTS_BASE_URL, 'https://') !== false; } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/CouponTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/CouponTest.php index 828784ca27885..316fe157993b9 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/CouponTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/CouponTest.php @@ -126,63 +126,6 @@ public function testGuestCustomerAttemptToChangeCustomerCart() $this->graphQlQuery($query); } - /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php - * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php - */ - public function testRemoveCoupon() - { - $couponCode = '2?ds5!2d'; - - /* Apply coupon to the quote */ - $this->quoteResource->load( - $this->quote, - 'test_order_with_simple_product_without_address', - 'reserved_order_id' - ); - $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); - $this->quoteResource->load( - $this->quote, - 'test_order_with_simple_product_without_address', - 'reserved_order_id' - ); - $query = $this->prepareAddCouponRequestQuery($maskedQuoteId, $couponCode); - $this->graphQlQuery($query); - - /* Remove coupon from quote */ - $query = $this->prepareRemoveCouponRequestQuery($maskedQuoteId); - $response = $this->graphQlQuery($query); - - self::assertArrayHasKey('removeCouponFromCart', $response); - self::assertNull($response['removeCouponFromCart']['cart']['applied_coupon']['code']); - } - - /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php - * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php - * @magentoApiDataFixture Magento/Customer/_files/customer.php - */ - public function testRemoveCouponFromCustomerCartByGuest() - { - $this->quoteResource->load( - $this->quote, - 'test_order_with_simple_product_without_address', - 'reserved_order_id' - ); - $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); - $this->quoteResource->load( - $this->quote, - 'test_order_with_simple_product_without_address', - 'reserved_order_id' - ); - $this->quote->setCustomerId(1); - $this->quoteResource->save($this->quote); - $query = $this->prepareRemoveCouponRequestQuery($maskedQuoteId); - - self::expectExceptionMessage('The current user cannot perform operations on cart "' . $maskedQuoteId . '"'); - $this->graphQlQuery($query); - } - /** * @param string $maskedQuoteId * @param string $couponCode @@ -200,26 +143,6 @@ private function prepareAddCouponRequestQuery(string $maskedQuoteId, string $cou } } } -QUERY; - } - - /** - * @param string $maskedQuoteId - * @return string - */ - private function prepareRemoveCouponRequestQuery(string $maskedQuoteId): string - { - return <<<QUERY -mutation { - removeCouponFromCart(input: {cart_id: "$maskedQuoteId"}) { - cart { - applied_coupon { - code - } - } - } -} - QUERY; } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailablePaymentMethodsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailablePaymentMethodsTest.php index ba640bc3402ba..673d496302662 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailablePaymentMethodsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailablePaymentMethodsTest.php @@ -39,7 +39,7 @@ protected function setUp() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -60,7 +60,7 @@ public function testGetAvailablePaymentMethods() /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -79,7 +79,7 @@ public function testGetAvailablePaymentMethodsFromGuestCart() /** * _security * @magentoApiDataFixture Magento/Customer/_files/three_customers.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -97,7 +97,7 @@ public function testGetAvailablePaymentMethodsFromAnotherCustomerCart() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailableShippingMethodsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailableShippingMethodsTest.php index 71cf0201951a3..2b647f61c4c63 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailableShippingMethodsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailableShippingMethodsTest.php @@ -41,7 +41,7 @@ protected function setUp() * Test case: get available shipping methods from current customer quote * * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -77,7 +77,7 @@ public function testGetAvailableShippingMethods() /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -98,7 +98,7 @@ public function testGetAvailableShippingMethodsFromGuestCart() * * _security * @magentoApiDataFixture Magento/Customer/_files/three_customers.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -118,7 +118,7 @@ public function testGetAvailableShippingMethodsFromAnotherCustomerCart() * Test case: get available shipping methods when all shipping methods are disabled * * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php index eb62b8c92f310..2cac1f8c4fbf0 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php @@ -36,7 +36,7 @@ protected function setUp() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php @@ -55,7 +55,7 @@ public function testGetCart() self::assertNotEmpty($response['cart']['items'][0]['id']); self::assertEquals(2, $response['cart']['items'][0]['qty']); - self::assertEquals('simple', $response['cart']['items'][0]['product']['sku']); + self::assertEquals('simple_product', $response['cart']['items'][0]['product']['sku']); self::assertNotEmpty($response['cart']['items'][1]['id']); self::assertEquals(2, $response['cart']['items'][1]['qty']); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedShippingMethodTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedShippingMethodTest.php index 16d085c1d09be..ba169d7a5bbc9 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedShippingMethodTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedShippingMethodTest.php @@ -39,7 +39,7 @@ protected function setUp() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -69,7 +69,7 @@ public function testGetSelectedShippingMethod() /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -89,7 +89,7 @@ public function testGetSelectedShippingMethodFromGuestCart() /** * _security * @magentoApiDataFixture Magento/Customer/_files/three_customers.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -108,7 +108,7 @@ public function testGetSelectedShippingMethodFromAnotherCustomerCart() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedBillingAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedBillingAddressTest.php index 4396f5fbac189..1ba94346073db 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedBillingAddressTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedBillingAddressTest.php @@ -39,7 +39,7 @@ protected function setUp() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php @@ -79,7 +79,7 @@ public function testGeSpecifiedBillingAddress() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -132,7 +132,7 @@ public function testGeSpecifiedBillingAddressOfNonExistentCart() /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php @@ -150,7 +150,7 @@ public function testGeSpecifiedBillingAddressFromAnotherGuestCart() /** * _security * @magentoApiDataFixture Magento/Customer/_files/three_customers.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/PlaceOrderTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/PlaceOrderTest.php new file mode 100644 index 0000000000000..4220f8932caa1 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/PlaceOrderTest.php @@ -0,0 +1,296 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Customer; + +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\ResourceModel\Order\CollectionFactory; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for placing an order for customer + */ +class PlaceOrderTest extends GraphQlAbstract +{ + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @var CollectionFactory + */ + private $orderCollectionFactory; + + /** + * @var OrderRepositoryInterface + */ + private $orderRepository; + + /** + * @var Registry + */ + private $registry; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + $this->orderCollectionFactory = $objectManager->get(CollectionFactory::class); + $this->orderRepository = $objectManager->get(OrderRepositoryInterface::class); + $this->registry = Bootstrap::getObjectManager()->get(Registry::class); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_checkmo_payment_method.php + */ + public function testPlaceOrder() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + + self::assertArrayHasKey('placeOrder', $response); + self::assertArrayHasKey('order_id', $response['placeOrder']['order']); + self::assertEquals($reservedOrderId, $response['placeOrder']['order']['order_id']); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + */ + public function testPlaceOrderWithNoItemsInCart() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessage( + 'Unable to place order: A server error stopped your order from being placed. ' . + 'Please try to place your order again' + ); + $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testPlaceOrderWithNoShippingAddress() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessage( + 'Unable to place order: Some addresses can\'t be used due to the configurations for specific countries' + ); + $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + */ + public function testPlaceOrderWithNoShippingMethod() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessage( + 'Unable to place order: The shipping method is missing. Select the shipping method and try again' + ); + $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + */ + public function testPlaceOrderWithNoBillingAddress() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessageRegExp( + '/Unable to place order: Please check the billing address information*/' + ); + $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + */ + public function testPlaceOrderWithNoPaymentMethod() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessage('Unable to place order: Enter a valid payment method and try again'); + $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/set_simple_product_out_of_stock.php + */ + public function testPlaceOrderWithOutOfStockProduct() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessage('Unable to place order: Some of the products are out of stock'); + $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_checkmo_payment_method.php + */ + public function testPlaceOrderOfGuestCart() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessageRegExp('/The current user cannot perform operations on cart*/'); + $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/three_customers.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_checkmo_payment_method.php + */ + public function testPlaceOrderOfAnotherCustomerCart() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessageRegExp('/The current user cannot perform operations on cart*/'); + $this->graphQlQuery($query, [], '', $this->getHeaderMap('customer3@search.example.com')); + } + + /** + * @param string $maskedQuoteId + * @return string + */ + private function getQuery(string $maskedQuoteId): string + { + return <<<QUERY +mutation { + placeOrder(input: {cart_id: "{$maskedQuoteId}"}) { + order { + order_id + } + } +} +QUERY; + } + + /** + * @param string $username + * @param string $password + * @return array + * @throws \Magento\Framework\Exception\AuthenticationException + */ + private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + return $headerMap; + } + + /** + * @inheritdoc + */ + public function tearDown() + { + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', true); + + $orderCollection = $this->orderCollectionFactory->create(); + foreach ($orderCollection as $order) { + $this->orderRepository->delete($order); + } + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', false); + + parent::tearDown(); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveCouponFromCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveCouponFromCartTest.php new file mode 100644 index 0000000000000..feba8c5c64259 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveCouponFromCartTest.php @@ -0,0 +1,169 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Customer; + +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Check removing of the coupon from customer cart + */ +class RemoveCouponFromCartTest extends GraphQlAbstract +{ + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/Checkout/_files/discount_10percent_generalusers.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/apply_coupon.php + */ + public function testRemoveCouponFromCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + + self::assertArrayHasKey('removeCouponFromCart', $response); + self::assertNull($response['removeCouponFromCart']['cart']['applied_coupon']['code']); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @expectedException \Exception + * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id" + */ + public function testRemoveCouponFromNonExistentCart() + { + $maskedQuoteId = 'non_existent_masked_id'; + $query = $this->getQuery($maskedQuoteId); + + $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @expectedException \Exception + * @expectedExceptionMessage Cart does not contain products + */ + public function testRemoveCouponFromEmptyCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testRemoveCouponFromCartIfCouponWasNotSet() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + + self::assertArrayHasKey('removeCouponFromCart', $response); + self::assertNull($response['removeCouponFromCart']['cart']['applied_coupon']['code']); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/apply_coupon.php + */ + public function testRemoveCouponFromGuestCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessage('The current user cannot perform operations on cart "' . $maskedQuoteId . '"'); + $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/three_customers.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/Checkout/_files/discount_10percent_generalusers.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/apply_coupon.php + */ + public function testRemoveCouponFromAnotherCustomerCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessage('The current user cannot perform operations on cart "' . $maskedQuoteId . '"'); + $this->graphQlQuery($query, [], '', $this->getHeaderMap('customer3@search.example.com')); + } + + /** + * @param string $maskedQuoteId + * @return string + */ + private function getQuery(string $maskedQuoteId): string + { + return <<<QUERY +mutation { + removeCouponFromCart(input: {cart_id: "$maskedQuoteId"}) { + cart { + applied_coupon { + code + } + } + } +} + +QUERY; + } + + /** + * @param string $username + * @param string $password + * @return array + */ + private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + return $headerMap; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php index 2ee5bb6708f4c..f5bc6276b10e8 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php @@ -57,7 +57,7 @@ protected function setUp() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -116,7 +116,7 @@ public function testSetNewBillingAddress() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -194,7 +194,7 @@ public function testSetNewBillingAddressWithUseForShippingParameter() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -241,7 +241,7 @@ public function testSetBillingAddressFromAddressBook() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -276,7 +276,7 @@ public function testSetNotExistedBillingAddressFromAddressBook() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -325,7 +325,7 @@ public function testSetNewBillingAddressAndFromAddressBookAtSameTime() * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Customer/_files/customer_address.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -459,7 +459,7 @@ public function testSetBillingAddressOnNonExistentCart() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -611,16 +611,16 @@ private function getHeaderMap(string $username = 'customer@example.com', string } /** - * @param string $reversedOrderId + * @param string $reservedOrderId * @param int $customerId * @return string */ private function assignQuoteToCustomer( - string $reversedOrderId = 'test_order_with_simple_product_without_address', + string $reservedOrderId = 'test_order_with_simple_product_without_address', int $customerId = 1 ): string { $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedOrderId, 'reserved_order_id'); + $this->quoteResource->load($quote, $reservedOrderId, 'reserved_order_id'); $quote->setCustomerId($customerId); $this->quoteResource->save($quote); return $this->quoteIdToMaskedId->execute((int)$quote->getId()); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetOfflineShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetOfflineShippingMethodsOnCartTest.php index bd147cc4a197e..b7b7823263106 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetOfflineShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetOfflineShippingMethodsOnCartTest.php @@ -48,7 +48,7 @@ protected function setUp() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodOnCartTest.php index 98eded8300665..73feefe2b094b 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodOnCartTest.php @@ -41,7 +41,7 @@ protected function setUp() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -62,7 +62,7 @@ public function testSetPaymentOnCartWithSimpleProduct() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -100,7 +100,7 @@ public function testSetPaymentOnCartWithVirtualProduct() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -135,7 +135,7 @@ public function testSetPaymentOnNonExistentCart() /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -155,7 +155,7 @@ public function testSetPaymentMethodToGuestCart() /** * _security * @magentoApiDataFixture Magento/Customer/_files/three_customers.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -174,7 +174,7 @@ public function testSetPaymentMethodToAnotherCustomerCart() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -227,7 +227,7 @@ public function dataProviderSetPaymentMethodWithoutRequiredParameters(): array /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php index f765851f2372e..f731ff5b89db2 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php @@ -57,7 +57,7 @@ protected function setUp() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -166,7 +166,7 @@ public function testSetNewShippingAddressOnCartWithVirtualProduct() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -211,7 +211,7 @@ public function testSetShippingAddressFromAddressBook() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -248,7 +248,7 @@ public function testSetNonExistentShippingAddressFromAddressBook() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -371,7 +371,7 @@ public function testSetShippingAddressToAnotherCustomerCart() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -427,7 +427,7 @@ public function dataProviderUpdateWithMissedRequiredParameters(): array /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -587,16 +587,16 @@ private function getHeaderMap(string $username = 'customer@example.com', string } /** - * @param string $reversedOrderId + * @param string $reservedOrderId * @param int $customerId * @return string */ private function assignQuoteToCustomer( - string $reversedOrderId = 'test_order_with_simple_product_without_address', + string $reservedOrderId = 'test_order_with_simple_product_without_address', int $customerId = 1 ): string { $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedOrderId, 'reserved_order_id'); + $this->quoteResource->load($quote, $reservedOrderId, 'reserved_order_id'); $quote->setCustomerId($customerId); $this->quoteResource->save($quote); return $this->quoteIdToMaskedId->execute((int)$quote->getId()); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php index 29ddbefd8e405..9219b5a67022c 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php @@ -48,7 +48,7 @@ protected function setUp() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -86,7 +86,7 @@ public function testSetShippingMethodOnCartWithSimpleProduct() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -124,7 +124,7 @@ public function testReSetShippingMethod() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -257,7 +257,7 @@ public function dataProviderSetShippingMethodWithWrongParameters(): array /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -302,7 +302,7 @@ public function testSetMultipleShippingMethods() /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -331,7 +331,7 @@ public function testSetShippingMethodToGuestCart() /** * _security * @magentoApiDataFixture Magento/Customer/_files/three_customers.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -360,7 +360,7 @@ public function testSetShippingMethodToAnotherCustomerCart() /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetMaskedQuoteIdByReservedOrderId.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetMaskedQuoteIdByReservedOrderId.php index eab362c3a0a6f..9bb9bef9bdb09 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetMaskedQuoteIdByReservedOrderId.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetMaskedQuoteIdByReservedOrderId.php @@ -50,14 +50,14 @@ public function __construct( /** * Get masked quote id by reserved order id * - * @param string $reversedOrderId + * @param string $reservedOrderId * @return string * @throws NoSuchEntityException */ - public function execute(string $reversedOrderId): string + public function execute(string $reservedOrderId): string { $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedOrderId, 'reserved_order_id'); + $this->quoteResource->load($quote, $reservedOrderId, 'reserved_order_id'); return $this->quoteIdToMaskedId->execute((int)$quote->getId()); } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetQuoteShippingAddressIdByReservedQuoteId.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetQuoteShippingAddressIdByReservedQuoteId.php index fa42ad4d71fb2..a56949b6f563a 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetQuoteShippingAddressIdByReservedQuoteId.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetQuoteShippingAddressIdByReservedQuoteId.php @@ -40,13 +40,13 @@ public function __construct( /** * Get quote shipping address id by reserved order id * - * @param string $reversedOrderId + * @param string $reservedOrderId * @return int */ - public function execute(string $reversedOrderId): int + public function execute(string $reservedOrderId): int { $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedOrderId, 'reserved_order_id'); + $this->quoteResource->load($quote, $reservedOrderId, 'reserved_order_id'); return (int)$quote->getShippingAddress()->getId(); } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailablePaymentMethodsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailablePaymentMethodsTest.php index 8271a76d88f12..af1f72fe71620 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailablePaymentMethodsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailablePaymentMethodsTest.php @@ -31,7 +31,7 @@ protected function setUp() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -52,7 +52,7 @@ public function testGetAvailablePaymentMethods() /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -69,7 +69,7 @@ public function testGetAvailablePaymentMethodsFromCustomerCart() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailableShippingMethodsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailableShippingMethodsTest.php index 38fda50ba5836..a8113657eff6e 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailableShippingMethodsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailableShippingMethodsTest.php @@ -33,7 +33,7 @@ protected function setUp() /** * Test case: get available shipping methods from current customer quote * - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -69,7 +69,7 @@ public function testGetAvailableShippingMethods() /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -87,7 +87,7 @@ public function testGetAvailableShippingMethodsFromCustomerCart() /** * Test case: get available shipping methods when all shipping methods are disabled * - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php index 6b1f540e4d47a..654b0b963af15 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php @@ -28,7 +28,7 @@ protected function setUp() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php @@ -47,7 +47,7 @@ public function testGetCart() self::assertNotEmpty($response['cart']['items'][0]['id']); self::assertEquals(2, $response['cart']['items'][0]['qty']); - self::assertEquals('simple', $response['cart']['items'][0]['product']['sku']); + self::assertEquals('simple_product', $response['cart']['items'][0]['product']['sku']); self::assertNotEmpty($response['cart']['items'][1]['id']); self::assertEquals(2, $response['cart']['items'][1]['qty']); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSelectedShippingMethodTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSelectedShippingMethodTest.php index c31b48ccc1087..bfdecca782319 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSelectedShippingMethodTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSelectedShippingMethodTest.php @@ -31,7 +31,7 @@ protected function setUp() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -61,7 +61,7 @@ public function testGetSelectedShippingMethod() /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -79,7 +79,7 @@ public function testGetSelectedShippingMethodFromCustomerCart() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSpecifiedBillingAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSpecifiedBillingAddressTest.php index 0110384b8f605..d592443aed499 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSpecifiedBillingAddressTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSpecifiedBillingAddressTest.php @@ -31,7 +31,7 @@ protected function setUp() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php @@ -69,7 +69,7 @@ public function testGeSpecifiedBillingAddress() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -118,7 +118,7 @@ public function testGetBillingAddressOfNonExistentCart() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveCouponFromCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveCouponFromCartTest.php new file mode 100644 index 0000000000000..edb5f9cbf267c --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveCouponFromCartTest.php @@ -0,0 +1,128 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Guest; + +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Check removing of the coupon from guest cart + */ +class RemoveCouponFromCartTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/apply_coupon.php + */ + public function testRemoveCouponFromCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlQuery($query); + + self::assertArrayHasKey('removeCouponFromCart', $response); + self::assertNull($response['removeCouponFromCart']['cart']['applied_coupon']['code']); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id" + */ + public function testRemoveCouponFromNonExistentCart() + { + $maskedQuoteId = 'non_existent_masked_id'; + $query = $this->getQuery($maskedQuoteId); + + $this->graphQlQuery($query); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @expectedException \Exception + * @expectedExceptionMessage Cart does not contain products + */ + public function testRemoveCouponFromEmptyCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + $this->graphQlQuery($query); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testRemoveCouponFromCartIfCouponWasNotSet() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlQuery($query); + + self::assertArrayHasKey('removeCouponFromCart', $response); + self::assertNull($response['removeCouponFromCart']['cart']['applied_coupon']['code']); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/Checkout/_files/discount_10percent_generalusers.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/apply_coupon.php + */ + public function testRemoveCouponFromCustomerCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessage('The current user cannot perform operations on cart "' . $maskedQuoteId . '"'); + $this->graphQlQuery($query); + } + + /** + * @param string $maskedQuoteId + * @return string + */ + private function getQuery(string $maskedQuoteId): string + { + return <<<QUERY +mutation { + removeCouponFromCart(input: {cart_id: "{$maskedQuoteId}"}) { + cart { + applied_coupon { + code + } + } + } +} +QUERY; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php index 5fe0ae05903c5..421dbeb813a4c 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php @@ -28,7 +28,7 @@ protected function setUp() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -86,7 +86,7 @@ public function testSetNewBillingAddress() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -208,7 +208,7 @@ public function testSetBillingAddressToCustomerCart() /** * _security - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -280,7 +280,7 @@ public function testSetBillingAddressOnNonExistentCart() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetOfflineShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetOfflineShippingMethodsOnCartTest.php index 8ed9ef84d6fbd..477c93efd31d0 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetOfflineShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetOfflineShippingMethodsOnCartTest.php @@ -40,7 +40,7 @@ protected function setUp() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetPaymentMethodOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetPaymentMethodOnCartTest.php index c9078fd84f6bc..879d0fd917291 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetPaymentMethodOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetPaymentMethodOnCartTest.php @@ -33,7 +33,7 @@ protected function setUp() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -53,7 +53,7 @@ public function testSetPaymentOnCartWithSimpleProduct() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -89,7 +89,7 @@ public function testSetPaymentOnCartWithVirtualProduct() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -122,7 +122,7 @@ public function testSetPaymentOnNonExistentCart() /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -141,7 +141,7 @@ public function testSetPaymentMethodToCustomerCart() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -194,7 +194,7 @@ public function dataProviderSetPaymentMethodWithoutRequiredParameters(): array } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php index f86b621497200..38a835ba89232 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php @@ -28,7 +28,7 @@ protected function setUp() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -135,7 +135,7 @@ public function testSetNewShippingAddressOnCartWithVirtualProduct() /** * _security - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -172,7 +172,7 @@ public function testSetShippingAddressFromAddressBook() /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -209,7 +209,7 @@ public function testSetShippingAddressToCustomerCart() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -308,7 +308,7 @@ public function dataProviderUpdateWithMissedRequiredParameters(): array } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php index ca26f8fe5aaf0..2eac002253ff0 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php @@ -40,7 +40,7 @@ protected function setUp() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -79,7 +79,7 @@ public function testSetShippingMethodOnCartWithSimpleProduct() * Shipping address for quote will be created automatically BUT with NULL values (considered that address * is not set) * - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php @@ -106,7 +106,7 @@ public function testSetShippingMethodOnCartWithSimpleProductAndWithoutAddress() /** * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -143,7 +143,7 @@ public function testReSetShippingMethod() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -275,7 +275,7 @@ public function dataProviderSetShippingMethodWithWrongParameters(): array /** * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -320,7 +320,7 @@ public function testSetMultipleShippingMethods() /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -348,7 +348,7 @@ public function testSetShippingMethodToCustomerCart() /** * _security - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/CouponManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/CouponManagementTest.php index 1aee493d8e0cb..1fb8fc43b0db6 100644 --- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/CouponManagementTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/CouponManagementTest.php @@ -9,6 +9,9 @@ use Magento\TestFramework\TestCase\WebapiAbstract; +/** + * Coupon management service tests + */ class CouponManagementTest extends WebapiAbstract { const SERVICE_VERSION = 'V1'; @@ -93,7 +96,7 @@ public function testSetCouponThrowsExceptionIfCouponDoesNotExist() $serviceInfo = [ 'rest' => [ - 'resourcePath' => self::RESOURCE_PATH . $cartId . '/coupons/' . $couponCode, + 'resourcePath' => self::RESOURCE_PATH . $cartId . '/coupons/' . urlencode($couponCode), 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT, ], 'soap' => [ @@ -129,7 +132,7 @@ public function testSetCouponSuccess() $couponCode = $salesRule->getPrimaryCoupon()->getCode(); $serviceInfo = [ 'rest' => [ - 'resourcePath' => self::RESOURCE_PATH . $cartId . '/coupons/' . $couponCode, + 'resourcePath' => self::RESOURCE_PATH . $cartId . '/coupons/' . urlencode($couponCode), 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT, ], 'soap' => [ @@ -232,7 +235,7 @@ public function testSetMyCouponThrowsExceptionIfCouponDoesNotExist() $serviceInfo = [ 'rest' => [ - 'resourcePath' => self::RESOURCE_PATH . 'mine/coupons/' . $couponCode, + 'resourcePath' => self::RESOURCE_PATH . 'mine/coupons/' . urlencode($couponCode), 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT, 'token' => $token, ], @@ -280,7 +283,7 @@ public function testSetMyCouponSuccess() $serviceInfo = [ 'rest' => [ - 'resourcePath' => self::RESOURCE_PATH . 'mine/coupons/' . $couponCode, + 'resourcePath' => self::RESOURCE_PATH . 'mine/coupons/' . urlencode($couponCode), 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT, 'token' => $token, ], diff --git a/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaEditCustomerTest.xml b/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaEditCustomerTest.xml index 12b9808adb9e0..0c0e06d63b6c9 100644 --- a/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaEditCustomerTest.xml +++ b/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaEditCustomerTest.xml @@ -14,6 +14,7 @@ <data name="attempts" xsi:type="number">3</data> <data name="captcha" xsi:type="string">111</data> <data name="configData" xsi:type="string">captcha_storefront_user_edit_failures_number, customer_max_login_failures_number</data> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <constraint name="Magento\Security\Test\Constraint\AssertCustomerIsLocked" /> <constraint name="Magento\Customer\Test\Constraint\AssertCustomerIsLockedOnBackend" /> </variation> diff --git a/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnAdminLoginTest.xml b/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnAdminLoginTest.xml index 186439bb9f157..9242bfbef2374 100644 --- a/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnAdminLoginTest.xml +++ b/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnAdminLoginTest.xml @@ -12,6 +12,7 @@ <data name="customAdmin/data/captcha" xsi:type="string">111</data> <data name="pageTitle" xsi:type="string">Dashboard</data> <data name="configData" xsi:type="string">captcha_backend_login</data> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable" /> </variation> </testCase> diff --git a/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnContactUsTest.xml b/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnContactUsTest.xml index a88cd98e3c31b..1a25afeabc5de 100644 --- a/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnContactUsTest.xml +++ b/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnContactUsTest.xml @@ -12,6 +12,7 @@ <data name="comment/data/captcha" xsi:type="string">111</data> <data name="comment/data/customer/dataset" xsi:type="string">default</data> <data name="configData" xsi:type="string">captcha_storefront_contact_us</data> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <constraint name="Magento\Contact\Test\Constraint\AssertContactUsSuccessMessage" /> </variation> </testCase> diff --git a/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnStoreFrontLoginTest.xml b/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnStoreFrontLoginTest.xml index 8e4327db5eddc..8068b2cbc050e 100644 --- a/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnStoreFrontLoginTest.xml +++ b/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnStoreFrontLoginTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Captcha\Test\TestCase\CaptchaOnStoreFrontLoginTest" summary="Check CAPTCHA on StoreFront Login Page" ticketId="MAGETWO-43639"> <variation name="CaptchaOnStoreFrontLoginTestVariation1"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="customer/dataset" xsi:type="string">default</data> <data name="captcha" xsi:type="string">111</data> <data name="configData" xsi:type="string">captcha_storefront_login</data> diff --git a/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnStoreFrontRegisterTest.xml b/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnStoreFrontRegisterTest.xml index 8e83c189efc2f..b0ce6dfa561ae 100644 --- a/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnStoreFrontRegisterTest.xml +++ b/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnStoreFrontRegisterTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Captcha\Test\TestCase\CaptchaOnStoreFrontRegisterTest" summary="Check CAPTCHA on StoreFront Register Page" ticketId="MAGETWO-43602"> <variation name="CaptchaOnStoreFrontRegisterTestVariation1"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="customer/dataset" xsi:type="string">register_customer</data> <data name="customer/data/captcha" xsi:type="string">111</data> <data name="configData" xsi:type="string">captcha_storefront_register</data> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View/CustomOptions.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View/CustomOptions.php index dcc8cce970098..4e8e0f97d70d5 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View/CustomOptions.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View/CustomOptions.php @@ -128,7 +128,8 @@ class CustomOptions extends Form * * @var string */ - private $validationErrorMessage = '//div[@class="mage-error"][contains(text(), "required field")]'; + private $validationErrorMessage = '//div[@class="mage-error"][contains(text(), "required field")' . + 'and not(contains(@style,\'display\'))]'; /** * Get product options @@ -148,6 +149,7 @@ public function getOptions(FixtureInterface $product) foreach ($dataOptions as $option) { $title = $option['title']; if (!isset($listCustomOptions[$title])) { + // phpcs:ignore Magento2.Exceptions.DirectThrow throw new \Exception("Can't find option: \"{$title}\""); } diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/ResetCustomerPasswordFailedTest.xml b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/ResetCustomerPasswordFailedTest.xml index 0b5b8a059cbbd..b4fbe7bb92929 100644 --- a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/ResetCustomerPasswordFailedTest.xml +++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/ResetCustomerPasswordFailedTest.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Security\Test\TestCase\ResetCustomerPasswordFailedTest" summary="Reset customer password."> <variation name="ResetPasswordTestVariation"> - <data name="tag" xsi:type="string">severity:S1</data> + <data name="tag" xsi:type="string">severity:S1,mftf_migrated:yes</data> <data name="customer/dataset" xsi:type="string">customer_US</data> <data name="attempts" xsi:type="string">2</data> <data name="configData" xsi:type="string">captcha_storefront_disable</data> diff --git a/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/assign_items_per_address.php b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/assign_items_per_address.php new file mode 100644 index 0000000000000..91cea7dc96602 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/assign_items_per_address.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Quote\Api\CartRepositoryInterface; + +$store = $storeManager->getStore(); +$quote->setReservedOrderId('multishipping_quote_id_braintree') + ->setStoreId($store->getId()) + ->setCustomerEmail('customer001@test.com'); + +/** @var CartRepositoryInterface $quoteRepository */ +$quoteRepository = $objectManager->get(CartRepositoryInterface::class); +$quote->collectTotals(); +$quoteRepository->save($quote); + +$items = $quote->getAllItems(); +$addressList = $quote->getAllShippingAddresses(); + +foreach ($addressList as $key => $address) { + $item = $items[$key]; + // set correct quantity per shipping address + $item->setQty(1); + $address->setTotalQty(1); + $address->addItem($item); +} + +// assign virtual product to the billing address +$billingAddress = $quote->getBillingAddress(); +$virtualItem = $items[sizeof($items) - 1]; +$billingAddress->setTotalQty(1); +$billingAddress->addItem($virtualItem); + +// need to recollect totals +$quote->setTotalsCollectedFlag(false); +$quote->collectTotals(); +$quoteRepository->save($quote); diff --git a/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/payment_braintree.php b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/payment_braintree.php new file mode 100644 index 0000000000000..3e1db90f1f2c8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/payment_braintree.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Quote\Model\Quote\Payment; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; +use Magento\Quote\Api\Data\PaymentInterface; +use Magento\Braintree\Model\Ui\ConfigProvider; + +/** + * @var Magento\Quote\Model\Quote $quote + */ + +if (empty($quote)) { + throw new \Exception('$quote should be defined in the parent fixture'); +} + +/** @var ObjectManager $objectManager */ +$objectManager = Bootstrap::getObjectManager(); + +/** @var PaymentInterface $payment */ +$payment = $objectManager->create(Payment::class); +$payment->setMethod(ConfigProvider::CODE); +$quote->setPayment($payment); diff --git a/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/payment_braintree_paypal.php b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/payment_braintree_paypal.php new file mode 100644 index 0000000000000..e4bba222078b0 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/payment_braintree_paypal.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Quote\Model\Quote\Payment; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; +use Magento\Quote\Api\Data\PaymentInterface; +use Magento\Braintree\Model\Ui\PayPal\ConfigProvider; + +/** + * @var Magento\Quote\Model\Quote $quote + */ + +if (empty($quote)) { + throw new \Exception('$quote should be defined in the parent fixture'); +} + +/** @var ObjectManager $objectManager */ +$objectManager = Bootstrap::getObjectManager(); + +/** @var PaymentInterface $payment */ +$payment = $objectManager->create(Payment::class); +$payment->setMethod(ConfigProvider::PAYPAL_CODE); +$quote->setPayment($payment); diff --git a/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/quote_with_split_items_braintree.php b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/quote_with_split_items_braintree.php new file mode 100644 index 0000000000000..1c56e611dd6db --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/quote_with_split_items_braintree.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Quote\Model\Quote; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; + +/** @var ObjectManager $objectManager */ +$objectManager = Bootstrap::getObjectManager(); + +/** @var StoreManagerInterface $storeManager */ +$storeManager = $objectManager->get(StoreManagerInterface::class); + +/** @var Quote $quote */ +$quote = $objectManager->create(Quote::class); + +require __DIR__ . '/../../../Magento/Multishipping/Fixtures/shipping_address_list.php'; +require __DIR__ . '/../../../Magento/Multishipping/Fixtures/billing_address.php'; +require __DIR__ . '/payment_braintree.php'; +require __DIR__ . '/../../../Magento/Multishipping/Fixtures/items.php'; +require __DIR__ . '/assign_items_per_address.php'; diff --git a/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/quote_with_split_items_braintree_paypal.php b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/quote_with_split_items_braintree_paypal.php new file mode 100644 index 0000000000000..4bd8e926abb76 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/quote_with_split_items_braintree_paypal.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Quote\Model\Quote; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; + +/** @var ObjectManager $objectManager */ +$objectManager = Bootstrap::getObjectManager(); + +/** @var StoreManagerInterface $storeManager */ +$storeManager = $objectManager->get(StoreManagerInterface::class); + +/** @var Quote $quote */ +$quote = $objectManager->create(Quote::class); + +require __DIR__ . '/../../../Magento/Multishipping/Fixtures/shipping_address_list.php'; +require __DIR__ . '/../../../Magento/Multishipping/Fixtures/billing_address.php'; +require __DIR__ . '/payment_braintree_paypal.php'; +require __DIR__ . '/../../../Magento/Multishipping/Fixtures/items.php'; +require __DIR__ . '/assign_items_per_address.php'; diff --git a/dev/tests/integration/testsuite/Magento/Braintree/Model/MultishippingTest.php b/dev/tests/integration/testsuite/Magento/Braintree/Model/MultishippingTest.php new file mode 100644 index 0000000000000..91bc0388d8551 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Braintree/Model/MultishippingTest.php @@ -0,0 +1,254 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Braintree\Model; + +use Braintree\Result\Successful; +use Braintree\Transaction; +use Magento\Braintree\Gateway\Command\GetPaymentNonceCommand; +use Magento\Braintree\Model\Adapter\BraintreeAdapter; +use Magento\Braintree\Model\Adapter\BraintreeAdapterFactory; +use Magento\Checkout\Model\Session as CheckoutSession; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Multishipping\Model\Checkout\Type\Multishipping; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Model\Quote; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order\Email\Sender\OrderSender; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; +use \PHPUnit_Framework_MockObject_MockObject as MockObject; +use Magento\Payment\Gateway\Command\ResultInterface as CommandResultInterface; + +/** + * Tests Magento\Multishipping\Model\Checkout\Type\Multishipping with Braintree and BraintreePayPal payments. + * + * @magentoAppArea frontend + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class MultishippingTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var BraintreeAdapter|MockObject + */ + private $adapter; + + /** + * @var Multishipping + */ + private $model; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + + $orderSender = $this->getMockBuilder(OrderSender::class) + ->disableOriginalConstructor() + ->getMock(); + + $adapterFactory = $this->getMockBuilder(BraintreeAdapterFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->adapter = $this->getMockBuilder(BraintreeAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + $adapterFactory->method('create') + ->willReturn($this->adapter); + + $this->objectManager->addSharedInstance($adapterFactory, BraintreeAdapterFactory::class); + $this->objectManager->addSharedInstance($this->getPaymentNonceMock(), GetPaymentNonceCommand::class); + + $this->model = $this->objectManager->create( + Multishipping::class, + ['orderSender' => $orderSender] + ); + } + + /** + * Checks a case when multiple orders are created successfully using Braintree payment method. + * + * @magentoAppIsolation enabled + * @magentoDataFixture Magento/Braintree/Fixtures/quote_with_split_items_braintree.php + * @magentoConfigFixture current_store payment/braintree/active 1 + * @return void + */ + public function testCreateOrdersWithBraintree() + { + $this->adapter->method('sale') + ->willReturn( + $this->getTransactionStub() + ); + $this->createOrders(); + } + + /** + * Checks a case when multiple orders are created successfully using Braintree PayPal payment method. + * + * @magentoAppIsolation enabled + * @magentoDataFixture Magento/Braintree/Fixtures/quote_with_split_items_braintree_paypal.php + * @magentoConfigFixture current_store payment/braintree_paypal/active 1 + * @return void + */ + public function testCreateOrdersWithBraintreePaypal() + { + $this->adapter->method('sale') + ->willReturn( + $this->getTransactionPaypalStub() + ); + $this->createOrders(); + } + + /** + * Creates orders for multishipping checkout flow. + * + * @return void + */ + private function createOrders() + { + $expectedPlacedOrdersNumber = 3; + $quote = $this->getQuote('multishipping_quote_id_braintree'); + + /** @var CheckoutSession $session */ + $session = $this->objectManager->get(CheckoutSession::class); + $session->replaceQuote($quote); + + $this->model->createOrders(); + + $orderList = $this->getOrderList((int)$quote->getId()); + self::assertCount( + $expectedPlacedOrdersNumber, + $orderList, + 'Total successfully placed orders number mismatch' + ); + } + + /** + * Creates stub for Braintree capture Transaction. + * + * @return Successful + */ + private function getTransactionStub(): Successful + { + $transaction = $this->getMockBuilder(Transaction::class) + ->disableOriginalConstructor() + ->getMock(); + $transaction->status = 'submitted_for_settlement'; + $transaction->creditCard = [ + 'last4' => '1111', + 'cardType' => 'Visa', + 'expirationMonth' => '12', + 'expirationYear' => '2021' + ]; + + $creditCardDetails = new \stdClass(); + $creditCardDetails->token = '4fdg'; + $creditCardDetails->expirationMonth = '12'; + $creditCardDetails->expirationYear = '2021'; + $creditCardDetails->cardType = 'Visa'; + $creditCardDetails->last4 = '1111'; + $creditCardDetails->expirationDate = '12/2021'; + $transaction->creditCardDetails = $creditCardDetails; + + $response = new Successful(); + $response->success = true; + $response->transaction = $transaction; + + return $response; + } + + /** + * Creates stub for BraintreePaypal capture Transaction. + * + * @return Successful + */ + private function getTransactionPaypalStub(): Successful + { + $transaction = $this->getMockBuilder(Transaction::class) + ->disableOriginalConstructor() + ->getMock(); + $transaction->status = 'submitted_for_settlement'; + $transaction->paypal = [ + 'token' => 'fchxqx', + 'payerEmail' => 'payer@example.com', + 'paymentId' => 'PAY-33ac47a28e7f54791f6cda45', + ]; + $paypalDetails = new \stdClass(); + $paypalDetails->token = 'fchxqx'; + $paypalDetails->payerEmail = 'payer@example.com'; + $paypalDetails->paymentId = '33ac47a28e7f54791f6cda45'; + $transaction->paypalDetails = $paypalDetails; + + $response = new Successful(); + $response->success = true; + $response->transaction = $transaction; + + return $response; + } + + /** + * Retrieves quote by reserved order id. + * + * @param string $reservedOrderId + * @return Quote + */ + private function getQuote(string $reservedOrderId): Quote + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter('reserved_order_id', $reservedOrderId) + ->create(); + + /** @var CartRepositoryInterface $quoteRepository */ + $quoteRepository = $this->objectManager->get(CartRepositoryInterface::class); + $items = $quoteRepository->getList($searchCriteria)->getItems(); + + return array_pop($items); + } + + /** + * Get list of orders by quote id. + * + * @param int $quoteId + * @return array + */ + private function getOrderList(int $quoteId): array + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter('quote_id', $quoteId) + ->create(); + + /** @var OrderRepositoryInterface $orderRepository */ + $orderRepository = $this->objectManager->get(OrderRepositoryInterface::class); + return $orderRepository->getList($searchCriteria)->getItems(); + } + + /** + * Returns GetPaymentNonceCommand command mock. + * + * @return MockObject + */ + private function getPaymentNonceMock(): MockObject + { + $commandResult = $this->createMock(CommandResultInterface::class); + $commandResult->method('get') + ->willReturn(['paymentMethodNonce' => 'testNonce']); + $paymentNonce = $this->createMock(GetPaymentNonceCommand::class); + $paymentNonce->method('execute') + ->willReturn($commandResult); + + return $paymentNonce; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Checkout/_files/discount_10percent_generalusers.php b/dev/tests/integration/testsuite/Magento/Checkout/_files/discount_10percent_generalusers.php index 507f6b755bcda..e66227a60e8f0 100644 --- a/dev/tests/integration/testsuite/Magento/Checkout/_files/discount_10percent_generalusers.php +++ b/dev/tests/integration/testsuite/Magento/Checkout/_files/discount_10percent_generalusers.php @@ -21,7 +21,7 @@ ], 'customer_group_ids' => [1], 'coupon_type' => \Magento\SalesRule\Model\Rule::COUPON_TYPE_SPECIFIC, - 'coupon_code' => uniqid(), + 'coupon_code' => '2?ds5!2d', 'simple_action' => \Magento\SalesRule\Model\Rule::BY_PERCENT_ACTION, 'discount_amount' => 10, 'discount_step' => 1 diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/set_simple_product_out_of_stock.php b/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/set_simple_product_out_of_stock.php new file mode 100644 index 0000000000000..f465f482275c1 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/set_simple_product_out_of_stock.php @@ -0,0 +1,19 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); + +$product = $productRepository->get('simple_product'); +$extensionAttributes = $product->getExtensionAttributes(); +$stockItem = $extensionAttributes->getStockItem(); +$stockItem->setIsInStock(false); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/simple_product.php b/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/simple_product.php new file mode 100644 index 0000000000000..732c18d4d7340 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/simple_product.php @@ -0,0 +1,45 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\Data\ProductInterfaceFactory; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Framework\Api\DataObjectHelper; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductInterfaceFactory $productFactory */ +$productFactory = $objectManager->get(ProductInterfaceFactory::class); +/** @var DataObjectHelper $dataObjectHelper */ +$dataObjectHelper = Bootstrap::getObjectManager()->get(DataObjectHelper::class); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); + +$product = $productFactory->create(); +$productData = [ + ProductInterface::TYPE_ID => Type::TYPE_SIMPLE, + ProductInterface::ATTRIBUTE_SET_ID => 4, + ProductInterface::SKU => 'simple_product', + ProductInterface::NAME => 'Simple Product', + ProductInterface::PRICE => 10, + ProductInterface::VISIBILITY => Visibility::VISIBILITY_BOTH, + ProductInterface::STATUS => Status::STATUS_ENABLED, +]; +$dataObjectHelper->populateWithArray($product, $productData, ProductInterface::class); +/** Out of interface */ +$product + ->setWebsiteIds([1]) + ->setStockData([ + 'qty' => 85.5, + 'is_in_stock' => true, + 'manage_stock' => true, + 'is_qty_decimal' => true + ]); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/simple_product_rollback.php b/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/simple_product_rollback.php new file mode 100644 index 0000000000000..9a54f663c9c13 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/simple_product_rollback.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); + +$currentArea = $registry->registry('isSecureArea'); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +try { + $productRepository->deleteById('simple_product'); +} catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + /** + * Tests which are wrapped with MySQL transaction clear all data by transaction rollback. + */ +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', $currentArea); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_simple_product.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_simple_product.php index d23381e33d436..f62b463d94003 100644 --- a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_simple_product.php +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_simple_product.php @@ -20,7 +20,7 @@ /** @var CartRepositoryInterface $cartRepository */ $cartRepository = Bootstrap::getObjectManager()->get(CartRepositoryInterface::class); -$product = $productRepository->get('simple'); +$product = $productRepository->get('simple_product'); $quote = $quoteFactory->create(); $quoteResource->load($quote, 'test_quote', 'reserved_order_id'); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/apply_coupon.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/apply_coupon.php new file mode 100644 index 0000000000000..c70efa9a12a5d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/apply_coupon.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Quote\Api\CouponManagementInterface; +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var CouponManagementInterface $couponManagement */ +$couponManagement = Bootstrap::getObjectManager()->get(CouponManagementInterface::class); +/** @var QuoteFactory $quoteFactory */ +$quoteFactory = Bootstrap::getObjectManager()->get(QuoteFactory::class); +/** @var QuoteResource $quoteResource */ +$quoteResource = Bootstrap::getObjectManager()->get(QuoteResource::class); + +$quote = $quoteFactory->create(); +$quoteResource->load($quote, 'test_quote', 'reserved_order_id'); +$couponManagement->set($quote->getId(), '2?ds5!2d'); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/apply_coupon_rollback.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/apply_coupon_rollback.php new file mode 100644 index 0000000000000..5431c25b7df53 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/apply_coupon_rollback.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Quote\Api\CouponManagementInterface; +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var CouponManagementInterface $couponManagement */ +$couponManagement = Bootstrap::getObjectManager()->get(CouponManagementInterface::class); +/** @var QuoteFactory $quoteFactory */ +$quoteFactory = Bootstrap::getObjectManager()->get(QuoteFactory::class); +/** @var QuoteResource $quoteResource */ +$quoteResource = Bootstrap::getObjectManager()->get(QuoteResource::class); + +$quote = $quoteFactory->create(); +$quoteResource->load($quote, 'test_quote', 'reserved_order_id'); +$couponManagement->remove($quote->getId()); diff --git a/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/TotalsTest.php b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/TotalsTest.php new file mode 100644 index 0000000000000..1125fc1730718 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/TotalsTest.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Block\Adminhtml; + +use Magento\Framework\View\LayoutInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\OrderFactory; + +/** + * Test class for \Magento\Sales\Block\Adminhtml\Totals + */ +class TotalsTest extends \Magento\TestFramework\TestCase\AbstractBackendController +{ + /** @var LayoutInterface */ + private $layout; + + /** @var Totals */ + private $block; + + /** @var OrderFactory */ + private $orderFactory; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->layout = $this->_objectManager->get(LayoutInterface::class); + $this->block = $this->layout->createBlock(Totals::class, 'totals_block'); + $this->orderFactory = $this->_objectManager->get(OrderFactory::class); + } + + /** + * @magentoDataFixture Magento/Sales/_files/order_with_free_shipping_by_coupon.php + */ + public function testShowShippingCoupon() + { + /** @var Order $order */ + $order = $this->orderFactory->create(); + $order->loadByIncrementId('100000001'); + + $this->block->setOrder($order); + $this->block->toHtml(); + + $shippingTotal = $this->block->getTotal('shipping'); + $this->assertNotFalse($shippingTotal, 'Shipping method is absent on the total\'s block.'); + $this->assertContains( + '1234567890', + $shippingTotal->getLabel(), + 'Coupon code is absent in the shipping method label name.' + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_free_shipping_by_coupon.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_free_shipping_by_coupon.php new file mode 100644 index 0000000000000..57ccffadaa4d0 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_free_shipping_by_coupon.php @@ -0,0 +1,35 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Item as OrderItem; + +require __DIR__ . '/../../../Magento/Sales/_files/order.php'; +/** @var \Magento\Catalog\Model\Product $product */ + +/** @var OrderItem $orderItem */ +$orderItem = $objectManager->create(OrderItem::class); +$orderItem->setProductId($product->getId()) + ->setQtyOrdered(2) + ->setBasePrice($product->getPrice()) + ->setPrice($product->getPrice()) + ->setRowTotal($product->getPrice()) + ->setProductType('simple') + ->setName($product->getName()) + ->setFreeShipping('1'); + +/** @var Order $order */ +$order->setShippingDescription('Flat Rate - Fixed') + ->setShippingAmount(0) + ->setCouponCode('1234567890') + ->setDiscountDescription('1234567890') + ->addItem($orderItem); + +/** @var OrderRepositoryInterface $orderRepository */ +$orderRepository = $objectManager->create(OrderRepositoryInterface::class); +$orderRepository->save($order); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_free_shipping_by_coupon_rollback.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_free_shipping_by_coupon_rollback.php new file mode 100644 index 0000000000000..1fb4b4636ab29 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_free_shipping_by_coupon_rollback.php @@ -0,0 +1,8 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require 'default_rollback.php'; diff --git a/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php b/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php index 92f0302d93baf..6bdb74ef7b89a 100644 --- a/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php +++ b/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php @@ -47,7 +47,7 @@ class ConfigOptionsListConstants const CONFIG_PATH_SCD_ON_DEMAND_IN_PRODUCTION = 'static_content_on_demand_in_production'; /** - * Paramater for forcing HTML minification even if file is already minified. + * Parameter for forcing HTML minification even if file is already minified. */ const CONFIG_PATH_FORCE_HTML_MINIFICATION = 'force_html_minification'; diff --git a/lib/internal/Magento/Framework/Data/Form/Element/Label.php b/lib/internal/Magento/Framework/Data/Form/Element/Label.php index 901dcb5289e8d..70b7885e7a0d0 100644 --- a/lib/internal/Magento/Framework/Data/Form/Element/Label.php +++ b/lib/internal/Magento/Framework/Data/Form/Element/Label.php @@ -4,13 +4,13 @@ * See COPYING.txt for license details. */ -/** - * Data form abstract class - * - * @author Magento Core Team <core@magentocommerce.com> - */ namespace Magento\Framework\Data\Form\Element; +use Magento\Framework\Phrase; + +/** + * Label form element. + */ class Label extends \Magento\Framework\Data\Form\Element\AbstractElement { /** @@ -37,8 +37,13 @@ public function __construct( public function getElementHtml() { $html = $this->getBold() ? '<div class="control-value special">' : '<div class="control-value">'; - $html .= $this->getEscapedValue() . '</div>'; + if (is_string($this->getValue()) || $this->getValue() instanceof Phrase) { + $html .= $this->getEscapedValue(); + } + + $html .= '</div>'; $html .= $this->getAfterElementHtml(); + return $html; } } diff --git a/lib/internal/Magento/Framework/Event/Invoker/InvokerDefault.php b/lib/internal/Magento/Framework/Event/Invoker/InvokerDefault.php index a7a387b5def81..acd0a61633557 100644 --- a/lib/internal/Magento/Framework/Event/Invoker/InvokerDefault.php +++ b/lib/internal/Magento/Framework/Event/Invoker/InvokerDefault.php @@ -9,7 +9,12 @@ namespace Magento\Framework\Event\Invoker; use Magento\Framework\Event\Observer; +use Psr\Log\LoggerInterface; +use Magento\Framework\App\State; +/** + * Default Invoker. + */ class InvokerDefault implements \Magento\Framework\Event\InvokerInterface { /** @@ -22,20 +27,29 @@ class InvokerDefault implements \Magento\Framework\Event\InvokerInterface /** * Application state * - * @var \Magento\Framework\App\State + * @var State */ protected $_appState; + /** + * @var LoggerInterface + */ + private $logger; + /** * @param \Magento\Framework\Event\ObserverFactory $observerFactory - * @param \Magento\Framework\App\State $appState + * @param State $appState + * @param LoggerInterface $logger */ public function __construct( \Magento\Framework\Event\ObserverFactory $observerFactory, - \Magento\Framework\App\State $appState + State $appState, + LoggerInterface $logger = null ) { $this->_observerFactory = $observerFactory; $this->_appState = $appState; + $this->logger = $logger ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(LoggerInterface::class); } /** @@ -61,6 +75,8 @@ public function dispatch(array $configuration, Observer $observer) } /** + * Execute Observer. + * * @param \Magento\Framework\Event\ObserverInterface $object * @param Observer $observer * @return $this @@ -70,7 +86,7 @@ protected function _callObserverMethod($object, $observer) { if ($object instanceof \Magento\Framework\Event\ObserverInterface) { $object->execute($observer); - } elseif ($this->_appState->getMode() == \Magento\Framework\App\State::MODE_DEVELOPER) { + } elseif ($this->_appState->getMode() == State::MODE_DEVELOPER) { throw new \LogicException( sprintf( 'Observer "%s" must implement interface "%s"', @@ -78,6 +94,12 @@ protected function _callObserverMethod($object, $observer) \Magento\Framework\Event\ObserverInterface::class ) ); + } else { + $this->logger->warning(sprintf( + 'Observer "%s" must implement interface "%s"', + get_class($object), + \Magento\Framework\Event\ObserverInterface::class + )); } return $this; } diff --git a/lib/internal/Magento/Framework/Event/Test/Unit/Invoker/InvokerDefaultTest.php b/lib/internal/Magento/Framework/Event/Test/Unit/Invoker/InvokerDefaultTest.php index 37f650dbef6a0..e6ec123823854 100644 --- a/lib/internal/Magento/Framework/Event/Test/Unit/Invoker/InvokerDefaultTest.php +++ b/lib/internal/Magento/Framework/Event/Test/Unit/Invoker/InvokerDefaultTest.php @@ -5,6 +5,9 @@ */ namespace Magento\Framework\Event\Test\Unit\Invoker; +/** + * Test for Magento\Framework\Event\Invoker\InvokerDefault. + */ class InvokerDefaultTest extends \PHPUnit\Framework\TestCase { /** @@ -32,6 +35,11 @@ class InvokerDefaultTest extends \PHPUnit\Framework\TestCase */ protected $_invokerDefault; + /** + * @var |Psr\Log|LoggerInterface + */ + private $loggerMock; + protected function setUp() { $this->_observerFactoryMock = $this->createMock(\Magento\Framework\Event\ObserverFactory::class); @@ -41,10 +49,12 @@ protected function setUp() ['execute'] ); $this->_appStateMock = $this->createMock(\Magento\Framework\App\State::class); + $this->loggerMock = $this->createMock(\Psr\Log\LoggerInterface::class); $this->_invokerDefault = new \Magento\Framework\Event\Invoker\InvokerDefault( $this->_observerFactoryMock, - $this->_appStateMock + $this->_appStateMock, + $this->loggerMock ); } @@ -166,13 +176,15 @@ public function testWrongInterfaceCallWithDisabledDeveloperMode($shared) $this->returnValue($notObserver) ); $this->_appStateMock->expects( - $this->once() + $this->exactly(1) )->method( 'getMode' )->will( $this->returnValue(\Magento\Framework\App\State::MODE_PRODUCTION) ); + $this->loggerMock->expects($this->once())->method('warning'); + $this->_invokerDefault->dispatch( [ 'shared' => $shared, diff --git a/lib/internal/Magento/Framework/View/Layout/etc/elements.xsd b/lib/internal/Magento/Framework/View/Layout/etc/elements.xsd index 39cdec05a65ea..6486b39070788 100755 --- a/lib/internal/Magento/Framework/View/Layout/etc/elements.xsd +++ b/lib/internal/Magento/Framework/View/Layout/etc/elements.xsd @@ -313,6 +313,7 @@ </xs:choice> <xs:attribute type="elementNameType" name="name" use="required"/> <xs:attribute type="xs:string" name="template" use="optional"/> + <xs:attribute type="xs:string" name="class" use="optional"/> <xs:attribute type="xs:boolean" name="display" default="true" use="optional"/> <xs:attribute type="xs:boolean" name="remove" use="optional"/> </xs:complexType> diff --git a/lib/internal/Magento/Framework/Webapi/CustomAttribute/PreprocessorInterface.php b/lib/internal/Magento/Framework/Webapi/CustomAttribute/PreprocessorInterface.php new file mode 100644 index 0000000000000..8de30e92218c0 --- /dev/null +++ b/lib/internal/Magento/Framework/Webapi/CustomAttribute/PreprocessorInterface.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Webapi\CustomAttribute; + +/** + * Interface for attribute preprocessor + */ +interface PreprocessorInterface +{ + /** + * Check if this attribute data should be processed + * + * @param string $key + * @param mixed $attribute + * @return bool + */ + public function shouldBeProcessed(string $key, $attribute): bool; + + /** + * Process attribute object according to type rules + * + * @param string $key + * @param mixed $attribute + */ + public function process(string $key, &$attribute); + + /** + * Get list of affected attributes for the current preprocessor + * + * @return array + */ + public function getAffectedAttributes(): array; +} diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index a9b553f6dd6f9..c253a400bed93 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -20,6 +20,7 @@ use Magento\Framework\Reflection\MethodsMap; use Magento\Framework\Reflection\TypeProcessor; use Magento\Framework\Webapi\Exception as WebapiException; +use Magento\Framework\Webapi\CustomAttribute\PreprocessorInterface; use Zend\Code\Reflection\ClassReflection; /** @@ -72,6 +73,16 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface */ private $config; + /** + * @var PreprocessorInterface[] + */ + private $customAttributePreprocessors; + + /** + * @var array + */ + private $attributesPreprocessorsMap = []; + /** * Initialize dependencies. * @@ -82,6 +93,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface * @param MethodsMap $methodsMap * @param ServiceTypeToEntityTypeMap $serviceTypeToEntityTypeMap * @param ConfigInterface $config + * @param array $customAttributePreprocessors */ public function __construct( TypeProcessor $typeProcessor, @@ -90,7 +102,8 @@ public function __construct( CustomAttributeTypeLocatorInterface $customAttributeTypeLocator, MethodsMap $methodsMap, ServiceTypeToEntityTypeMap $serviceTypeToEntityTypeMap = null, - ConfigInterface $config = null + ConfigInterface $config = null, + array $customAttributePreprocessors = [] ) { $this->typeProcessor = $typeProcessor; $this->objectManager = $objectManager; @@ -101,6 +114,7 @@ public function __construct( ?: \Magento\Framework\App\ObjectManager::getInstance()->get(ServiceTypeToEntityTypeMap::class); $this->config = $config ?: \Magento\Framework\App\ObjectManager::getInstance()->get(ConfigInterface::class); + $this->customAttributePreprocessors = $customAttributePreprocessors; } /** @@ -132,7 +146,6 @@ private function getNameFinder() * @param string $serviceMethodName name of the method that we are trying to call * @param array $inputArray data to send to method in key-value format * @return array list of parameters that can be used to call the service method - * @throws InputException if no value is provided for required parameters * @throws WebapiException */ public function process($serviceClassName, $serviceMethodName, array $inputArray) @@ -216,6 +229,7 @@ private function getConstructorData(string $className, array $data): array * @param array $data * @return object the newly created and populated object * @throws \Exception + * @throws SerializationException * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function _createFromArray($className, $data) @@ -261,6 +275,7 @@ protected function _createFromArray($className, $data) } else { $setterValue = $this->convertValue($value, $returnType); } + // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (SerializationException $e) { throw new SerializationException( new Phrase( @@ -289,12 +304,11 @@ protected function convertCustomAttributeValue($customAttributesValueArray, $dat $dataObjectClassName = ltrim($dataObjectClassName, '\\'); foreach ($customAttributesValueArray as $key => $customAttribute) { + $this->runCustomAttributePreprocessors($key, $customAttribute); if (!is_array($customAttribute)) { $customAttribute = [AttributeValue::ATTRIBUTE_CODE => $key, AttributeValue::VALUE => $customAttribute]; } - list($customAttributeCode, $customAttributeValue) = $this->processCustomAttribute($customAttribute); - $entityType = $this->serviceTypeToEntityTypeMap->getEntityType($dataObjectClassName); if ($entityType) { $type = $this->customAttributeTypeLocator->getType( @@ -310,6 +324,7 @@ protected function convertCustomAttributeValue($customAttributesValueArray, $dat ) { try { $attributeValue = $this->convertValue($customAttributeValue, $type); + // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (SerializationException $e) { throw new SerializationException( new Phrase( @@ -331,11 +346,49 @@ protected function convertCustomAttributeValue($customAttributesValueArray, $dat return $result; } + /** + * Get map of preprocessors related to the custom attributes + * + * @return array + */ + private function getAttributesPreprocessorsMap(): array + { + if (!$this->attributesPreprocessorsMap) { + foreach ($this->customAttributePreprocessors as $attributePreprocessor) { + foreach ($attributePreprocessor->getAffectedAttributes() as $attributeKey) { + $this->attributesPreprocessorsMap[$attributeKey][] = $attributePreprocessor; + } + } + } + + return $this->attributesPreprocessorsMap; + } + + /** + * Prepare attribute value by loaded attribute preprocessors + * + * @param mixed $key + * @param mixed $customAttribute + */ + private function runCustomAttributePreprocessors($key, &$customAttribute) + { + $preprocessorsMap = $this->getAttributesPreprocessorsMap(); + if ($key && is_array($customAttribute) && array_key_exists($key, $preprocessorsMap)) { + $preprocessorsList = $preprocessorsMap[$key]; + foreach ($preprocessorsList as $attributePreprocessor) { + if ($attributePreprocessor->shouldBeProcessed($key, $customAttribute)) { + $attributePreprocessor->process($key, $customAttribute); + } + } + } + } + /** * Derive the custom attribute code and value. * * @param string[] $customAttribute * @return string[] + * @throws SerializationException */ private function processCustomAttribute($customAttribute) { diff --git a/lib/web/css/source/lib/variables/_colors.less b/lib/web/css/source/lib/variables/_colors.less index 9c694468e9f62..ffb0e8e797d81 100644 --- a/lib/web/css/source/lib/variables/_colors.less +++ b/lib/web/css/source/lib/variables/_colors.less @@ -7,9 +7,13 @@ // Color variables // _____________________________________________ +@color-blue-dodger: #008bdb; +@color-black_dark: #333333; + @color-white: #fff; @color-black: #000; +@color-darkie-gray: #8a837f; @color-gray19: #303030; @color-gray20: #333; @color-gray34: #575757; @@ -29,12 +33,16 @@ @color-gray79: #c9c9c9; @color-gray80: #ccc; @color-gray82: #d1d1d1; +@color-gray83: #d4d4d4; @color-gray89: #e3e3e3; @color-gray90: #e5e5e5; @color-gray91: #e8e8e8; @color-gray92: #ebebeb; @color-gray94: #f0f0f0; @color-gray95: #f2f2f2; +@color-gray_light: #cccccc; +@color-lighter-grayish: #cacaca; +@color-very-dark-gray: #666; @color-white-smoke: #f5f5f5; @color-white-dark-smoke: #efefef; @color-white-fog: #f8f8f8; @@ -80,6 +88,8 @@ @color-pink1: #fae5e5; @color-dark-pink1: #800080; // Legacy pink +@color-brownie: #514943; +@color-brownie-vanilla: #736963; @color-brownie1: #6f4400; @color-brownie-light1: #c07600; diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js index 18d71aad2071a..cfcdef0b701c9 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js @@ -132,7 +132,7 @@ define([ attributes.type = attributes.type.replace(/\\\\/g, '\\'); imageSrc = config.placeholders[attributes.type]; - if (config.types.indexOf(attributes['type_name']) > -1) { + if (imageSrc) { imageHtml += '<span class="magento-placeholder magento-widget mceNonEditable" ' + 'contenteditable="false">'; } else { @@ -147,8 +147,8 @@ define([ imageHtml += ' src="' + imageSrc + '"'; imageHtml += ' />'; - if (attributes['type_name']) { - imageHtml += attributes['type_name']; + if (config.types[attributes.type]) { + imageHtml += config.types[attributes.type]; } imageHtml += '</span>'; diff --git a/pub/.htaccess b/pub/.htaccess index 85a204c85e8a8..6a97a6d14dc00 100644 --- a/pub/.htaccess +++ b/pub/.htaccess @@ -47,11 +47,6 @@ php_flag session.auto_start off ############################################ -## Enable resulting html compression - - #php_flag zlib.output_compression on - -########################################### # Disable user agent verification to not break multiple image upload php_flag suhosin.session.cryptua off diff --git a/setup/performance-toolkit/benchmark.jmx b/setup/performance-toolkit/benchmark.jmx index c0d57ba0e2cef..2035da9d58265 100644 --- a/setup/performance-toolkit/benchmark.jmx +++ b/setup/performance-toolkit/benchmark.jmx @@ -364,6 +364,16 @@ <stringProp name="Argument.value">${__P(graphQLPoolUsers,1)}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> + <elementProp name="graphqlAddConfigurableProductToCartPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlAddConfigurableProductToCartPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlAddConfigurableProductToCartPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphqlAddSimpleProductToCartPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlAddSimpleProductToCartPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlAddSimpleProductToCartPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> <elementProp name="graphqlGetCategoryListByCategoryIdPercentage" elementType="Argument"> <stringProp name="Argument.name">graphqlGetCategoryListByCategoryIdPercentage</stringProp> <stringProp name="Argument.value">${__P(graphqlGetCategoryListByCategoryIdPercentage,0)}</stringProp> @@ -40572,6 +40582,534 @@ vars.putObject("category", categories[number]); </hashTree> </hashTree> + + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Add Simple Product To Cart" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlAddSimpleProductToCartPercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "Add Simple Product To Cart"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Simple Product Data" enabled="true"> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = vars.getObject("randomIntGenerator"); +number = random.nextInt(props.get("simple_products_list").size()); +product = props.get("simple_products_list").get(number); + +vars.put("product_url_key", product.get("url_key")); +vars.put("product_id", product.get("id")); +vars.put("product_name", product.get("title")); +vars.put("product_uenc", product.get("uenc")); +vars.put("product_sku", product.get("sku")); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/product_browsing_and_adding_items_to_the_cart/simple_products_setup.jmx</stringProp></BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create Empty Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n createEmptyCart\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/create_empty_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract cart id" enabled="true"> + <stringProp name="VAR">quote_id</stringProp> + <stringProp name="JSONPATH">$.data.createEmptyCart</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Add Simple Product To Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation { \n addSimpleProductsToCart(\n input: {\n cart_id: \"${quote_id}\"\n cartItems: [\n {\n data: {\n qty: 2\n sku: \"${product_sku}\"\n }\n }\n ]\n }\n ) {\n cart {\n items {\n qty\n product {\n sku\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/add_simple_product_to_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1421843282">addSimpleProductsToCart</stringProp> + <stringProp name="-1173443935">"sku":"${product_sku}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"{\n cart(cart_id: \"${quote_id}\") {\n items {\n id\n qty\n product {\n sku\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract item id" enabled="true"> + <stringProp name="VAR">item_id</stringProp> + <stringProp name="JSONPATH">$.data.cart.items[0].id</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Remove Simple Product From Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n removeItemFromCart(\n input: {\n cart_id: \"${quote_id}\"\n cart_item_id: ${item_id}\n }\n ) {\n cart {\n items {\n qty\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/remove_simple_product_from_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1452665323">{"data":{"removeItemFromCart":{"cart":{"items":[]}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + </hashTree> + + + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Add Configurable Product To Cart" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlAddConfigurableProductToCartPercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "Add Configurable Product To Cart"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Configurable Product Data" enabled="true"> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = vars.getObject("randomIntGenerator"); +number = random.nextInt(props.get("configurable_products_list").size()); +product = props.get("configurable_products_list").get(number); + +vars.put("product_url_key", product.get("url_key")); +vars.put("product_id", product.get("id")); +vars.put("product_name", product.get("title")); +vars.put("product_uenc", product.get("uenc")); +vars.put("product_sku", product.get("sku")); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/product_browsing_and_adding_items_to_the_cart/configurable_products_setup.jmx</stringProp></BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Configurable Product Details by name" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"query productDetailByName($name: String, $onServer: Boolean!) {\n products(filter: { name: { eq: $name } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"name":"${product_name}","onServer":false},"operationName":"productDetailByName"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_configurable_product_details_by_name.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1201352014">"sku":"${product_sku}","name":"${product_name}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract Configurable Product option" enabled="true"> + <stringProp name="VAR">product_option</stringProp> + <stringProp name="JSONPATH">$.data.products.items[0].variants[0].product.sku</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/extract_configurable_product_option.jmx</stringProp></com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create Empty Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n createEmptyCart\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/create_empty_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract cart id" enabled="true"> + <stringProp name="VAR">quote_id</stringProp> + <stringProp name="JSONPATH">$.data.createEmptyCart</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Add Configurable Product To Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n addConfigurableProductsToCart(\n input: {\n cart_id: \"${quote_id}\"\n cartItems: [\n {\n variant_sku: \"${product_option}\"\n data: {\n qty: 2\n sku: \"${product_option}\"\n }\n }\n ]\n }\n ) {\n cart {\n items {\n id\n qty\n product {\n name\n sku\n }\n ... on ConfigurableCartItem {\n configurable_options {\n option_label\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/add_configurable_product_to_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1421843282">addConfigurableProductsToCart</stringProp> + <stringProp name="675049292">"sku":"${product_option}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"{\n cart(cart_id: \"${quote_id}\") {\n items {\n id\n qty\n product {\n sku\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract item id" enabled="true"> + <stringProp name="VAR">item_id</stringProp> + <stringProp name="JSONPATH">$.data.cart.items[0].id</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Remove Configurable Product From Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n removeItemFromCart(\n input: {\n cart_id: \"${quote_id}\"\n cart_item_id: ${item_id}\n }\n ) {\n cart {\n items {\n qty\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/remove_configurable_product_from_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1452665323">{"data":{"removeItemFromCart":{"cart":{"items":[]}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + </hashTree> + </hashTree>