Skip to content

Commit 6f68dc5

Browse files
author
Viktor Kopin
committed
MC-30171: Add to Cart Form wrong Form Key in FPC
1 parent f55f411 commit 6f68dc5

File tree

12 files changed

+287
-12
lines changed

12 files changed

+287
-12
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@
1414
<element name="addToCartButtonTitleIsAdding" type="text" selector="//button/span[text()='Adding...']"/>
1515
<element name="addToCartButtonTitleIsAdded" type="text" selector="//button/span[text()='Added']"/>
1616
<element name="addToCartButtonTitleIsAddToCart" type="text" selector="//button/span[text()='Add to Cart']"/>
17+
<element name="inputFormKey" type="text" selector="input[name='form_key']"/>
1718
</section>
1819
</sections>

app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontFreeShippingRecalculationAfterCouponCodeAddedTest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
<see userInput="Your coupon was successfully applied." stepKey="seeSuccessMessage"/>
8989
<click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder1"/>
9090
<waitForPageLoad stepKey="waitForError"/>
91-
<see stepKey="seeShippingMethodError" userInput="The shipping method is missing. Select the shipping method and try again."/>
91+
<seeElementInDOM selector="{{CheckoutHeaderSection.errorMessageContainsText('The shipping method is missing. Select the shipping method and try again.')}}" stepKey="seeShippingMethodError"/>
9292
<amOnPage stepKey="navigateToShippingPage" url="{{CheckoutShippingPage.url}}"/>
9393
<waitForPageLoad stepKey="waitForShippingPageLoad"/>
9494
<click stepKey="chooseFlatRateShipping" selector="{{CheckoutShippingMethodsSection.shippingMethodRowByName('Flat Rate')}}"/>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
9+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
10+
<actionGroup name="AssertStorefrontAddToCartFormKeyValueIsNotCachedActionGroup">
11+
<annotations>
12+
<description>Assert that product page add to cart form key is different from cached value.</description>
13+
</annotations>
14+
<arguments>
15+
<argument name="cachedValue" type="string"/>
16+
</arguments>
17+
18+
<grabValueFrom selector="{{StorefrontProductActionSection.inputFormKey}}" stepKey="grabUpdatedValue"/>
19+
<assertRegExp stepKey="validateCachedFormKey">
20+
<expectedResult type="string">/\w{16}/</expectedResult>
21+
<actualResult type="string">{{cachedValue}}</actualResult>
22+
</assertRegExp>
23+
<assertRegExp stepKey="validateUpdatedFormKey">
24+
<expectedResult type="string">/\w{16}/</expectedResult>
25+
<actualResult type="variable">grabUpdatedValue</actualResult>
26+
</assertRegExp>
27+
<assertNotEquals stepKey="assertFormKeyUpdated">
28+
<expectedResult type="string">{{cachedValue}}</expectedResult>
29+
<actualResult type="variable">grabUpdatedValue</actualResult>
30+
</assertNotEquals>
31+
</actionGroup>
32+
</actionGroups>
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
9+
<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
11+
<test name="StorefrontCachedInputFormKeyValueUpdatedTest">
12+
<annotations>
13+
<features value="PageCache"/>
14+
<stories value="FormKey"/>
15+
<title value="Form Key value should be updated by js script"/>
16+
<description value="Form Key value should be updated by js script"/>
17+
<testCaseId value="MC-39300"/>
18+
<useCaseId value="MC-30171"/>
19+
<severity value="AVERAGE"/>
20+
<group value="pageCache"/>
21+
</annotations>
22+
<before>
23+
<!-- Create Data -->
24+
<createData entity="SimpleProduct2" stepKey="createProduct"/>
25+
<actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanCache">
26+
<argument name="tags" value="full_page"/>
27+
</actionGroup>
28+
</before>
29+
<after>
30+
<!-- Delete data -->
31+
<deleteData createDataKey="createProduct" stepKey="deleteProduct"/>
32+
</after>
33+
<actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage">
34+
<argument name="productUrl" value="$createProduct.custom_attributes[url_key]$"/>
35+
</actionGroup>
36+
<grabValueFrom selector="{{StorefrontProductActionSection.inputFormKey}}" stepKey="grabCachedValue"/>
37+
<resetCookie userInput="PHPSESSID" stepKey="resetSessionCookie"/>
38+
<resetCookie userInput="form_key" stepKey="resetFormKeyCookie"/>
39+
<actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="reopenProductPage">
40+
<argument name="productUrl" value="$createProduct.custom_attributes[url_key]$"/>
41+
</actionGroup>
42+
<actionGroup ref="AssertStorefrontAddToCartFormKeyValueIsNotCachedActionGroup" stepKey="assertValueIsUpdatedByScript">
43+
<argument name="cachedValue" value="{$grabCachedValue}"/>
44+
</actionGroup>
45+
</test>
46+
</tests>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\PageCache\ViewModel;
9+
10+
use Magento\Framework\View\Element\Block\ArgumentInterface;
11+
use Magento\PageCache\Model\Config;
12+
13+
/**
14+
* Adds script to update form key from cookie after script rendering
15+
*/
16+
class FormKeyProvider implements ArgumentInterface
17+
{
18+
/**
19+
* @var Config
20+
*/
21+
private $config;
22+
23+
/**
24+
* @param Config $config
25+
*/
26+
public function __construct(
27+
Config $config
28+
) {
29+
$this->config = $config;
30+
}
31+
32+
/**
33+
* Is full page cache enabled
34+
*
35+
* @return bool
36+
*/
37+
public function isFullPageCacheEnabled(): bool
38+
{
39+
return $this->config->isEnabled();
40+
}
41+
}

app/code/Magento/PageCache/view/frontend/layout/default.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@
1010
<referenceBlock name="head.components">
1111
<block class="Magento\Framework\View\Element\Js\Components" name="pagecache_page_head_components" template="Magento_PageCache::js/components.phtml"/>
1212
</referenceBlock>
13+
<referenceBlock name="head.additional">
14+
<block name="form_key_provider" template="Magento_PageCache::form_key_provider.phtml">
15+
<arguments>
16+
<argument name="form_key_provider" xsi:type="object">Magento\PageCache\ViewModel\FormKeyProvider</argument>
17+
</arguments>
18+
</block>
19+
</referenceBlock>
1320
<referenceContainer name="content">
1421
<block class="Magento\PageCache\Block\Javascript" template="Magento_PageCache::javascript.phtml" name="pageCache" as="pageCache"/>
1522
</referenceContainer>

app/code/Magento/PageCache/view/frontend/requirejs-config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ var config = {
88
'*': {
99
pageCache: 'Magento_PageCache/js/page-cache'
1010
}
11-
}
11+
},
12+
deps: ['Magento_PageCache/js/form-key-provider']
1213
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
if ($block->getFormKeyProvider()->isFullPageCacheEnabled()): ?>
7+
<script type="text/x-magento-init">
8+
{
9+
"*": {
10+
"Magento_PageCache/js/form-key-provider": {}
11+
}
12+
}
13+
</script>
14+
<?php endif; ?>
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* Copyright © Magento, Inc. All rights reserved.
3+
* See COPYING.txt for license details.
4+
*/
5+
define(function () {
6+
'use strict';
7+
8+
return function () {
9+
var formKey,
10+
inputElements,
11+
inputSelector = 'input[name="form_key"]';
12+
13+
/**
14+
* Set form_key cookie
15+
* @private
16+
*/
17+
function setFormKeyCookie(value) {
18+
var expires,
19+
secure,
20+
date = new Date(),
21+
isSecure = !!window.cookiesConfig && window.cookiesConfig.secure;
22+
23+
date.setTime(date.getTime() + 86400000);
24+
expires = '; expires=' + date.toUTCString();
25+
secure = isSecure ? '; secure' : '';
26+
27+
document.cookie = 'form_key=' + (value || '') + expires + secure + '; path=/';
28+
}
29+
30+
/**
31+
* Retrieves form key from cookie
32+
* @private
33+
*/
34+
function getFormKeyCookie() {
35+
var cookie,
36+
i,
37+
nameEQ = 'form_key=',
38+
cookieArr = document.cookie.split(';');
39+
40+
for (i = 0; i < cookieArr.length; i++) {
41+
cookie = cookieArr[i];
42+
43+
while (cookie.charAt(0) === ' ') {
44+
cookie = cookie.substring(1, cookie.length);
45+
}
46+
47+
if (cookie.indexOf(nameEQ) === 0) {
48+
return cookie.substring(nameEQ.length, cookie.length);
49+
}
50+
}
51+
52+
return null;
53+
}
54+
55+
/**
56+
* Generate form key string
57+
* @private
58+
*/
59+
function generateFormKeyString() {
60+
var result = '',
61+
length = 16,
62+
chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
63+
64+
while (length--) {
65+
result += chars[Math.round(Math.random() * (chars.length - 1))];
66+
}
67+
68+
return result;
69+
}
70+
71+
/**
72+
* Init form_key inputs with value
73+
* @private
74+
*/
75+
function initFormKey() {
76+
formKey = getFormKeyCookie();
77+
78+
if (!formKey) {
79+
formKey = generateFormKeyString();
80+
setFormKeyCookie(formKey);
81+
}
82+
inputElements = document.querySelectorAll(inputSelector);
83+
84+
if (inputElements.length) {
85+
Array.prototype.forEach.call(inputElements, function (element) {
86+
element.setAttribute('value', formKey);
87+
});
88+
}
89+
}
90+
91+
initFormKey();
92+
};
93+
});

app/code/Magento/PageCache/view/frontend/web/js/page-cache.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ define([
77
'jquery',
88
'domReady',
99
'consoleLogger',
10+
'Magento_PageCache/js/form-key-provider',
1011
'jquery-ui-modules/widget',
1112
'mage/cookies'
12-
], function ($, domReady, consoleLogger) {
13+
], function ($, domReady, consoleLogger, formKeyInit) {
1314
'use strict';
1415

1516
/**
@@ -99,6 +100,7 @@ define([
99100

100101
/**
101102
* FormKey Widget - this widget is generating from key, saves it to cookie and
103+
* @deprecated see Magento/PageCache/view/frontend/web/js/form-key-provider.js
102104
*/
103105
$.widget('mage.formKey', {
104106
options: {
@@ -298,8 +300,7 @@ define([
298300
});
299301

300302
domReady(function () {
301-
$('body')
302-
.formKey();
303+
formKeyInit();
303304
});
304305

305306
return {

0 commit comments

Comments
 (0)