Skip to content

Commit a6933f3

Browse files
Merge pull request #9946 from magento-gl/ACQE-functional-deployment-version11
Bengals Functional Mainline deployment
2 parents 52f4632 + 435085f commit a6933f3

File tree

8 files changed

+311
-0
lines changed

8 files changed

+311
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright 2025 Adobe
5+
* All Rights Reserved.
6+
*/
7+
-->
8+
9+
<actionGroups
10+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
11+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
12+
<actionGroup name="StorefrontVerifyErrorMessageDisplayedWhileAddingProductWithEmptyFileToCartActionGroup" extends="AddToCartFromStorefrontProductPageActionGroup">
13+
<arguments>
14+
<argument name="productName" type="string"/>
15+
</arguments>
16+
<remove keyForRemoval="waitForSuccessMessage"/>
17+
<remove keyForRemoval="seeAddToCartSuccessMessage"/>
18+
<waitForText selector="{{StorefrontProductInfoMainSection.customEmptyFileValidationMessage}}" userInput="{{EmptyFileValidationMessage.fileValidationMessage}}" stepKey="verifyEmptyFileValidationMessage"/>
19+
</actionGroup>
20+
</actionGroups>

app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionData.xml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,4 +450,21 @@
450450
<entity name="ProductRadioButtonType" type="product_option">
451451
<data key="type">Radio Buttons</data>
452452
</entity>
453+
<entity name="ProductOptionTextFile" type="product_option">
454+
<var key="product_sku" entityType="product" entityKey="sku" />
455+
<data key="title">OptionFile</data>
456+
<data key="type">file</data>
457+
<data key="is_require">true</data>
458+
<data key="sort_order">3</data>
459+
<data key="price">9.99</data>
460+
<data key="price_type">fixed</data>
461+
<data key="file_extension">txt</data>
462+
</entity>
463+
<entity name="EmptyFiles">
464+
<data key="file1">emptyTxtFile.txt</data>
465+
<data key="file2">emptyPngFile.png</data>
466+
</entity>
467+
<entity name="EmptyFileValidationMessage">
468+
<data key="fileValidationMessage">The file is empty. Select another file and try again.</data>
469+
</entity>
453470
</entities>

app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,5 +117,6 @@
117117
<element name="productCalenderButton" type="button" selector="//*[@id='product-options-wrapper']/div/div/fieldset/div/button" />
118118
<element name="productCalenderGoToday" type="button" selector="//*[@id='ui-datepicker-div']/div[2]/button[1]" />
119119
<element name="customDateField" type="text" selector='//*[@class="product-custom-option datetime-picker input-text _has-datepicker"]' />
120+
<element name="customEmptyFileValidationMessage" type="text" selector="//div[@data-ui-id='message-error']/div"/>
120121
</section>
121122
</sections>
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright 2025 Adobe
5+
* All Rights Reserved.
6+
*/
7+
-->
8+
9+
<tests
10+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
11+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
12+
<test name="StoreFrontUseEmptyFileAsACustomOptionOfFileTypeTest">
13+
<annotations>
14+
<features value="Catalog"/>
15+
<stories value="Empty file as a custom option for the file type"/>
16+
<title value="Product custom options"/>
17+
<description value="Verify the validation of an empty file as a custom option for the file type."/>
18+
<severity value="MAJOR"/>
19+
<testCaseId value="AC-3848"/>
20+
<group value="catalog"/>
21+
</annotations>
22+
<before>
23+
<!-- Pre-condition 1:Create Simple Product -->
24+
<createData entity="defaultSimpleProduct" stepKey="initialSimpleProduct"/>
25+
</before>
26+
<after>
27+
<!-- Delete product, Logout from Admin -->
28+
<deleteData createDataKey="initialSimpleProduct" stepKey="deleteSimpleProduct"/>
29+
<actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/>
30+
</after>
31+
<!-- Login As Admin -->
32+
<actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/>
33+
<!-- Step1: Open a product page in Admin and Open Customizable options tab.-->
34+
<actionGroup ref="AdminProductPageOpenByIdActionGroup" stepKey="openProductForEdit">
35+
<argument name="productId" value="$initialSimpleProduct.id$"/>
36+
</actionGroup>
37+
<!-- Open custom option panel -->
38+
<click selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" stepKey="openCustomizableOptions"/>
39+
<waitForPageLoad stepKey="waitForCustomOptionsOpen"/>
40+
<!-- Step2: Add Custom file option -->
41+
<actionGroup ref="AddProductCustomOptionFileActionGroup" stepKey="addFileOption">
42+
<argument name="option" value="ProductOptionTextFile"/>
43+
</actionGroup>
44+
<actionGroup ref="SaveProductFormActionGroup" stepKey="saveProduct"/>
45+
<!-- Step3: Open the product on Storefront -->
46+
<actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage">
47+
<argument name="productUrl" value="$$initialSimpleProduct.custom_attributes[url_key]$$"/>
48+
</actionGroup>
49+
<!-- Step4: Upload an empty file -->
50+
<actionGroup ref="StorefrontAttachOptionFileActionGroup" stepKey="selectAndAttachFile">
51+
<argument name="file" value="EmptyFiles.file1" />
52+
</actionGroup>
53+
<!--Step4 Assertion: Verify An error should be displayed when adding to cart -->
54+
<actionGroup ref="StorefrontVerifyErrorMessageDisplayedWhileAddingProductWithEmptyFileToCartActionGroup" stepKey="addProductToCart">
55+
<argument name="productName" value="$initialSimpleProduct.name$"/>
56+
</actionGroup>
57+
<!-- Open product in admin and delete existing custom option-->
58+
<actionGroup ref="AdminProductPageOpenByIdActionGroup" stepKey="openProductEditPage">
59+
<argument name="productId" value="$initialSimpleProduct.id$"/>
60+
</actionGroup>
61+
<!-- Open custom option panel -->
62+
<click selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" stepKey="openCustomizableOption"/>
63+
<waitForPageLoad stepKey="waitForCustomOptionOpen"/>
64+
<actionGroup ref="AdminDeleteProductCustomOptionActionGroup" stepKey="deleteCustomOptionFile">
65+
<argument name="option" value="ProductOptionTextFile"/>
66+
</actionGroup>
67+
<!-- Step5: Add a new custom option of File type that accepts only images-->
68+
<actionGroup ref="AddProductCustomOptionFileActionGroup" stepKey="addFileOptions">
69+
<argument name="option" value="ProductOptionFile"/>
70+
</actionGroup>
71+
<actionGroup ref="SaveProductFormActionGroup" stepKey="saveTheProduct"/>
72+
<!-- Step6: Open Product page in storeFront -->
73+
<actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openTheProductPage">
74+
<argument name="productUrl" value="$$initialSimpleProduct.custom_attributes[url_key]$$"/>
75+
</actionGroup>
76+
<!--Step7: Upload an empty image -->
77+
<actionGroup ref="StorefrontAttachOptionFileActionGroup" stepKey="selectAndAttachTheFile">
78+
<argument name="file" value="EmptyFiles.file2" />
79+
</actionGroup>
80+
<!-- Step7 Assertion: Verify An error should be displayed when adding to cart -->
81+
<actionGroup ref="StorefrontVerifyErrorMessageDisplayedWhileAddingProductWithEmptyFileToCartActionGroup" stepKey="addTheProductToCart">
82+
<argument name="productName" value="$initialSimpleProduct.name$"/>
83+
</actionGroup>
84+
</test>
85+
</tests>

app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/StorefrontProcessCheckoutToPaymentActionGroup.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<fillField selector="{{CheckoutShippingSection.postcode}}" userInput="{{CustomerAddressSimple.postcode}}" stepKey="enterPostcode"/>
2727
<fillField selector="{{CheckoutShippingSection.telephone}}" userInput="{{CustomerAddressSimple.telephone}}" stepKey="enterTelephone"/>
2828
<waitForLoadingMaskToDisappear stepKey="waitForShippingMethods"/>
29+
<waitForElementVisible selector="{{CheckoutShippingMethodsSection.checkShippingMethodByName('')}}" stepKey="waitForSelectShippingMethod"/>
2930
<click selector="{{CheckoutShippingMethodsSection.checkShippingMethodByName('')}}" stepKey="selectShippingMethod"/>
3031
<waitForElement selector="{{CheckoutShippingSection.next}}" time="30" stepKey="waitForTheNextButton"/>
3132
<waitForElementNotVisible selector=".loading-mask" time="300" stepKey="waitForProcessShippingMethod"/>

dev/tests/acceptance/tests/_data/emptyPngFile.png

Loading

dev/tests/acceptance/tests/_data/emptyTxtFile.txt

Whitespace-only changes.
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Checkout\Helper;
10+
11+
use Magento\Checkout\Helper\Data;
12+
use Magento\Framework\ObjectManagerInterface;
13+
use Magento\Quote\Model\Quote;
14+
use Magento\Quote\Model\QuoteManagement;
15+
use Magento\Sales\Api\OrderRepositoryInterface;
16+
use Magento\Sales\Model\Order;
17+
use Magento\TestFramework\Helper\Bootstrap;
18+
use Magento\TestFramework\Mail\Template\TransportBuilderMock;
19+
use PHPUnit\Framework\TestCase;
20+
21+
/**
22+
* Integration test for sending the "payment failed" email on virtual product order failure.
23+
*
24+
* Ensures that when a virtual product order fails during payment, the appropriate failure email is
25+
* sent and does not include any shipping address or method information.
26+
*
27+
* @magentoDbIsolation enabled
28+
* @magentoAppIsolation enabled
29+
* @magentoDataFixture Magento/Checkout/_files/quote_with_virtual_product_and_address.php
30+
*
31+
* @AllureSuite("Checkout")
32+
* @AllureFeature("Payment Failed Email")
33+
*/
34+
class DataTest extends TestCase
35+
{
36+
/**
37+
* @var ObjectManagerInterface
38+
*/
39+
private ObjectManagerInterface $objectManager;
40+
41+
/**
42+
* @var QuoteManagement
43+
*/
44+
private QuoteManagement $quoteManagement;
45+
46+
/**
47+
* @var Data
48+
*/
49+
private Data $checkoutHelper;
50+
51+
/**
52+
* @var OrderRepositoryInterface
53+
*/
54+
private OrderRepositoryInterface $orderRepository;
55+
56+
/**
57+
* @var TransportBuilderMock
58+
*/
59+
private TransportBuilderMock $transportBuilder;
60+
61+
/**
62+
* Reserved order ID used in fixture.
63+
*/
64+
private const FIXTURE_RESERVED_ORDER_ID = 'test_order_with_virtual_product';
65+
66+
/**
67+
* Payment method code to use in test.
68+
*/
69+
private const PAYMENT_METHOD = 'checkmo';
70+
71+
/**
72+
* Set up required Magento services for the test.
73+
*
74+
* @return void
75+
*/
76+
protected function setUp(): void
77+
{
78+
parent::setUp();
79+
80+
$this->objectManager = Bootstrap::getObjectManager();
81+
$this->quoteManagement = $this->objectManager->get(QuoteManagement::class);
82+
$this->checkoutHelper = $this->objectManager->get(Data::class);
83+
$this->orderRepository = $this->objectManager->get(OrderRepositoryInterface::class);
84+
$this->transportBuilder = $this->objectManager->get(TransportBuilderMock::class);
85+
}
86+
87+
/**
88+
* Test sending the "payment failed" email for an order with a virtual product.
89+
*
90+
* This test verifies that:
91+
* - The payment failure email is sent successfully.
92+
* - The email content does not include shipping address or shipping method
93+
* since the product is virtual.
94+
*
95+
* @return void
96+
*/
97+
public function testSendPaymentFailedEmail(): void
98+
{
99+
[$order, $quote] = $this->createOrderFromFixture();
100+
$this->simulatePaymentFailure($order);
101+
102+
$this->checkoutHelper->sendPaymentFailedEmail(
103+
$quote,
104+
'Simulated payment failure',
105+
'onepage'
106+
);
107+
108+
$message = $this->transportBuilder->getSentMessage();
109+
$this->assertNotNull($message, 'Expected a payment failed email to be sent.');
110+
111+
$emailBody = $message->getBody();
112+
if (method_exists($emailBody, 'bodyToString')) {
113+
$emailContent = quoted_printable_decode($emailBody->bodyToString());
114+
} elseif (method_exists($emailBody, 'getParts') && isset($emailBody->getParts()[0])) {
115+
$emailContent = $emailBody->getParts()[0]->getRawContent();
116+
} else {
117+
$this->fail('Unable to extract email content for assertion.');
118+
}
119+
120+
$this->assertStringNotContainsString(
121+
'Shipping Address',
122+
$emailContent,
123+
'Shipping address should not appear in the payment failed email for virtual product.'
124+
);
125+
$this->assertStringNotContainsString(
126+
'Shipping Method',
127+
$emailContent,
128+
'Shipping method should not appear in the payment failed email for virtual product.'
129+
);
130+
$this->assertStringContainsString(
131+
'Simulated payment failure',
132+
$emailContent,
133+
'Expected payment failure message to be present in the email.'
134+
);
135+
}
136+
137+
/**
138+
* Prepare an order from a fixture quote containing a virtual product.
139+
*
140+
* Loads the quote with reserved_order_id from fixture,
141+
* sets payment method, submits the quote to create the order.
142+
*
143+
* @return array{0: Order, 1: Quote} Returns the created order and the original quote.
144+
*/
145+
private function createOrderFromFixture(): array
146+
{
147+
/** @var Quote $quote */
148+
$quote = $this->objectManager->create(Quote::class)
149+
->load(self::FIXTURE_RESERVED_ORDER_ID, 'reserved_order_id');
150+
151+
$this->assertNotNull($quote->getId(), 'Failed to load quote from fixture.');
152+
$this->assertNotEmpty($quote->getAllItems(), 'Quote from fixture is empty.');
153+
154+
$quote->getPayment()->setMethod(self::PAYMENT_METHOD);
155+
156+
$order = $this->quoteManagement->submit($quote);
157+
158+
$this->assertNotNull($order->getId(), 'Order was not created from quote.');
159+
$this->assertNotEmpty($order->getIncrementId(), 'Order increment ID is missing.');
160+
161+
return [$order, $quote];
162+
}
163+
164+
/**
165+
* Simulate a payment failure by cancelling the order and adding a history comment.
166+
*
167+
* This method updates the order state and status to 'canceled',
168+
* adds a comment explaining the payment failure, and saves the order.
169+
*
170+
* @param Order $order
171+
* @return void
172+
*/
173+
private function simulatePaymentFailure(Order $order): void
174+
{
175+
$order->setState(Order::STATE_CANCELED)
176+
->setStatus(Order::STATE_CANCELED)
177+
->addCommentToStatusHistory((string)__('Simulated: Payment failure due to gateway timeout.'));
178+
179+
$this->orderRepository->save($order);
180+
181+
$this->assertSame(
182+
Order::STATE_CANCELED,
183+
$order->getState(),
184+
'Order state should be canceled after simulating payment failure.'
185+
);
186+
}
187+
}

0 commit comments

Comments
 (0)