Skip to content

Commit

Permalink
MC-30171: Add to Cart Form wrong Form Key in FPC
Browse files Browse the repository at this point in the history
  • Loading branch information
Viktor Kopin committed Nov 27, 2020
1 parent f55f411 commit 6f68dc5
Show file tree
Hide file tree
Showing 12 changed files with 287 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@
<element name="addToCartButtonTitleIsAdding" type="text" selector="//button/span[text()='Adding...']"/>
<element name="addToCartButtonTitleIsAdded" type="text" selector="//button/span[text()='Added']"/>
<element name="addToCartButtonTitleIsAddToCart" type="text" selector="//button/span[text()='Add to Cart']"/>
<element name="inputFormKey" type="text" selector="input[name='form_key']"/>
</section>
</sections>
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
<see userInput="Your coupon was successfully applied." stepKey="seeSuccessMessage"/>
<click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder1"/>
<waitForPageLoad stepKey="waitForError"/>
<see stepKey="seeShippingMethodError" userInput="The shipping method is missing. Select the shipping method and try again."/>
<seeElementInDOM selector="{{CheckoutHeaderSection.errorMessageContainsText('The shipping method is missing. Select the shipping method and try again.')}}" stepKey="seeShippingMethodError"/>
<amOnPage stepKey="navigateToShippingPage" url="{{CheckoutShippingPage.url}}"/>
<waitForPageLoad stepKey="waitForShippingPageLoad"/>
<click stepKey="chooseFlatRateShipping" selector="{{CheckoutShippingMethodsSection.shippingMethodRowByName('Flat Rate')}}"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?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="AssertStorefrontAddToCartFormKeyValueIsNotCachedActionGroup">
<annotations>
<description>Assert that product page add to cart form key is different from cached value.</description>
</annotations>
<arguments>
<argument name="cachedValue" type="string"/>
</arguments>

<grabValueFrom selector="{{StorefrontProductActionSection.inputFormKey}}" stepKey="grabUpdatedValue"/>
<assertRegExp stepKey="validateCachedFormKey">
<expectedResult type="string">/\w{16}/</expectedResult>
<actualResult type="string">{{cachedValue}}</actualResult>
</assertRegExp>
<assertRegExp stepKey="validateUpdatedFormKey">
<expectedResult type="string">/\w{16}/</expectedResult>
<actualResult type="variable">grabUpdatedValue</actualResult>
</assertRegExp>
<assertNotEquals stepKey="assertFormKeyUpdated">
<expectedResult type="string">{{cachedValue}}</expectedResult>
<actualResult type="variable">grabUpdatedValue</actualResult>
</assertNotEquals>
</actionGroup>
</actionGroups>
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?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="StorefrontCachedInputFormKeyValueUpdatedTest">
<annotations>
<features value="PageCache"/>
<stories value="FormKey"/>
<title value="Form Key value should be updated by js script"/>
<description value="Form Key value should be updated by js script"/>
<testCaseId value="MC-39300"/>
<useCaseId value="MC-30171"/>
<severity value="AVERAGE"/>
<group value="pageCache"/>
</annotations>
<before>
<!-- Create Data -->
<createData entity="SimpleProduct2" stepKey="createProduct"/>
<actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanCache">
<argument name="tags" value="full_page"/>
</actionGroup>
</before>
<after>
<!-- Delete data -->
<deleteData createDataKey="createProduct" stepKey="deleteProduct"/>
</after>
<actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage">
<argument name="productUrl" value="$createProduct.custom_attributes[url_key]$"/>
</actionGroup>
<grabValueFrom selector="{{StorefrontProductActionSection.inputFormKey}}" stepKey="grabCachedValue"/>
<resetCookie userInput="PHPSESSID" stepKey="resetSessionCookie"/>
<resetCookie userInput="form_key" stepKey="resetFormKeyCookie"/>
<actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="reopenProductPage">
<argument name="productUrl" value="$createProduct.custom_attributes[url_key]$"/>
</actionGroup>
<actionGroup ref="AssertStorefrontAddToCartFormKeyValueIsNotCachedActionGroup" stepKey="assertValueIsUpdatedByScript">
<argument name="cachedValue" value="{$grabCachedValue}"/>
</actionGroup>
</test>
</tests>
41 changes: 41 additions & 0 deletions app/code/Magento/PageCache/ViewModel/FormKeyProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\PageCache\ViewModel;

use Magento\Framework\View\Element\Block\ArgumentInterface;
use Magento\PageCache\Model\Config;

/**
* Adds script to update form key from cookie after script rendering
*/
class FormKeyProvider implements ArgumentInterface
{
/**
* @var Config
*/
private $config;

/**
* @param Config $config
*/
public function __construct(
Config $config
) {
$this->config = $config;
}

/**
* Is full page cache enabled
*
* @return bool
*/
public function isFullPageCacheEnabled(): bool
{
return $this->config->isEnabled();
}
}
7 changes: 7 additions & 0 deletions app/code/Magento/PageCache/view/frontend/layout/default.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@
<referenceBlock name="head.components">
<block class="Magento\Framework\View\Element\Js\Components" name="pagecache_page_head_components" template="Magento_PageCache::js/components.phtml"/>
</referenceBlock>
<referenceBlock name="head.additional">
<block name="form_key_provider" template="Magento_PageCache::form_key_provider.phtml">
<arguments>
<argument name="form_key_provider" xsi:type="object">Magento\PageCache\ViewModel\FormKeyProvider</argument>
</arguments>
</block>
</referenceBlock>
<referenceContainer name="content">
<block class="Magento\PageCache\Block\Javascript" template="Magento_PageCache::javascript.phtml" name="pageCache" as="pageCache"/>
</referenceContainer>
Expand Down
3 changes: 2 additions & 1 deletion app/code/Magento/PageCache/view/frontend/requirejs-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ var config = {
'*': {
pageCache: 'Magento_PageCache/js/page-cache'
}
}
},
deps: ['Magento_PageCache/js/form-key-provider']
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
if ($block->getFormKeyProvider()->isFullPageCacheEnabled()): ?>
<script type="text/x-magento-init">
{
"*": {
"Magento_PageCache/js/form-key-provider": {}
}
}
</script>
<?php endif; ?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
define(function () {
'use strict';

return function () {
var formKey,
inputElements,
inputSelector = 'input[name="form_key"]';

/**
* Set form_key cookie
* @private
*/
function setFormKeyCookie(value) {
var expires,
secure,
date = new Date(),
isSecure = !!window.cookiesConfig && window.cookiesConfig.secure;

date.setTime(date.getTime() + 86400000);
expires = '; expires=' + date.toUTCString();
secure = isSecure ? '; secure' : '';

document.cookie = 'form_key=' + (value || '') + expires + secure + '; path=/';
}

/**
* Retrieves form key from cookie
* @private
*/
function getFormKeyCookie() {
var cookie,
i,
nameEQ = 'form_key=',
cookieArr = document.cookie.split(';');

for (i = 0; i < cookieArr.length; i++) {
cookie = cookieArr[i];

while (cookie.charAt(0) === ' ') {
cookie = cookie.substring(1, cookie.length);
}

if (cookie.indexOf(nameEQ) === 0) {
return cookie.substring(nameEQ.length, cookie.length);
}
}

return null;
}

/**
* Generate form key string
* @private
*/
function generateFormKeyString() {
var result = '',
length = 16,
chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';

while (length--) {
result += chars[Math.round(Math.random() * (chars.length - 1))];
}

return result;
}

/**
* Init form_key inputs with value
* @private
*/
function initFormKey() {
formKey = getFormKeyCookie();

if (!formKey) {
formKey = generateFormKeyString();
setFormKeyCookie(formKey);
}
inputElements = document.querySelectorAll(inputSelector);

if (inputElements.length) {
Array.prototype.forEach.call(inputElements, function (element) {
element.setAttribute('value', formKey);
});
}
}

initFormKey();
};
});
7 changes: 4 additions & 3 deletions app/code/Magento/PageCache/view/frontend/web/js/page-cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ define([
'jquery',
'domReady',
'consoleLogger',
'Magento_PageCache/js/form-key-provider',
'jquery-ui-modules/widget',
'mage/cookies'
], function ($, domReady, consoleLogger) {
], function ($, domReady, consoleLogger, formKeyInit) {
'use strict';

/**
Expand Down Expand Up @@ -99,6 +100,7 @@ define([

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

domReady(function () {
$('body')
.formKey();
formKeyInit();
});

return {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

/* eslint-disable max-nested-callbacks */
define([
'jquery',
'Magento_PageCache/js/form-key-provider'
], function ($, formKeyInit) {
'use strict';

describe('Testing FormKey Provider', function () {
var inputContainer;

beforeEach(function () {
inputContainer = document.createElement('input');
inputContainer.setAttribute('value', '');
inputContainer.setAttribute('name', 'form_key');
document.querySelector('body').appendChild(inputContainer);
});

afterEach(function () {
$(inputContainer).remove();
document.cookie = 'form_key= ; expires = Thu, 01 Jan 1970 00:00:00 GMT';
});

it('sets value of input[form_key]', function () {
var expires,
date = new Date();

date.setTime(date.getTime() + 86400000);
expires = '; expires=' + date.toUTCString();
document.cookie = 'form_key=FAKE_COOKIE' + expires + '; path=/';
formKeyInit();
expect($(inputContainer).val()).toEqual('FAKE_COOKIE');
});

it('widget sets value to input[form_key] in case it empty', function () {
document.cookie = 'form_key= ; expires = Thu, 01 Jan 1970 00:00:00 GMT';
formKeyInit();
expect($(inputContainer).val()).toEqual(jasmine.any(String));
expect($(inputContainer).val().length).toEqual(16);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,6 @@ define([
expect($.mage.cookies.set).toHaveBeenCalled();
expect(inputContainer.val()).toEqual(jasmine.any(String));
});

it('widget exists on load on body', function (done) {
$(function () {
expect($('body').data('mageFormKey')).toBeDefined();
done();
});
});
});

describe('Testing PageCache Widget', function () {
Expand Down

0 comments on commit 6f68dc5

Please sign in to comment.