|
| 1 | +<?xml version="1.0" encoding="UTF-8"?> |
| 2 | +<!-- |
| 3 | +/** |
| 4 | + * Copyright 2026 Adobe |
| 5 | + * All Rights Reserved. |
| 6 | + */ |
| 7 | +--> |
| 8 | +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| 9 | + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> |
| 10 | + |
| 11 | + <!-- |
| 12 | + * Regression guard for AC-16636: sri.js race condition. |
| 13 | + * |
| 14 | + * sri.js registers a require.config({ onNodeCreated }) callback that stamps |
| 15 | + * integrity attributes onto every script tag RequireJS injects. It must be |
| 16 | + * evaluated by the browser BEFORE requirejs-config.js hands RequireJS its |
| 17 | + * module map, otherwise modules begin loading before the handler is wired up |
| 18 | + * and are injected without integrity attributes. |
| 19 | + * |
| 20 | + * This test: |
| 21 | + * 1. Deploys static content in production mode with JS minification enabled. |
| 22 | + * 2. Places a guest order to exercise the full checkout JS stack. |
| 23 | + * 3. Verifies on the order success page that sri.js appears before |
| 24 | + * requirejs-config.js in the DOM. |
| 25 | + * 4. Verifies that all versioned static JS files end in .min.js and carry |
| 26 | + * a valid sha256 integrity attribute. |
| 27 | + --> |
| 28 | + <test name="SriJsOrderingTest"> |
| 29 | + <annotations> |
| 30 | + <features value="Checkout"/> |
| 31 | + <stories value="Subresource Integrity"/> |
| 32 | + <title value="Verify sri.js is positioned before requirejs-config.js and all minified scripts have SRI on checkout success page"/> |
| 33 | + <description value="AC-16636 regression guard: sri.js must appear before requirejs-config.js in the DOM so that the onNodeCreated SRI handler is registered before RequireJS starts loading modules. Verified on the order success page after a full guest checkout."/> |
| 34 | + <severity value="CRITICAL"/> |
| 35 | + <testCaseId value="AC-16636"/> |
| 36 | + <group value="csp"/> |
| 37 | + <group value="csp_sri"/> |
| 38 | + <group value="checkout"/> |
| 39 | + </annotations> |
| 40 | + |
| 41 | + <before> |
| 42 | + <!-- Clear any stale maintenance flag left by a previous crashed test run --> |
| 43 | + <magentoCLI command="maintenance:disable" stepKey="disableStaleMaintenanceMode"/> |
| 44 | + <createData entity="SimpleProduct2" stepKey="createProduct"/> |
| 45 | + <magentoCLI command="config:set dev/js/minify_files 1" stepKey="enableMinification"/> |
| 46 | + <magentoCLI command="deploy:mode:set production" stepKey="setProductionMode"/> |
| 47 | + <magentoCLI command="setup:static-content:deploy en_US -s quick -f" stepKey="deployStaticContent" timeout="600"/> |
| 48 | + <magentoCLI command="maintenance:disable" stepKey="disableMaintenanceMode"/> |
| 49 | + <magentoCLI command="cache:flush" stepKey="flushCache"/> |
| 50 | + </before> |
| 51 | + |
| 52 | + <after> |
| 53 | + <magentoCLI command="maintenance:disable" stepKey="disableMaintenanceAfter"/> |
| 54 | + <magentoCLI command="config:set dev/js/minify_files 0" stepKey="disableMinification"/> |
| 55 | + <magentoCLI command="deploy:mode:set developer" stepKey="restoreDeveloperMode"/> |
| 56 | + <magentoCLI command="cache:flush" stepKey="flushCacheAfter"/> |
| 57 | + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> |
| 58 | + </after> |
| 59 | + |
| 60 | + <!-- Add product to cart and navigate to checkout --> |
| 61 | + <amOnPage url="{{StorefrontProductPage.url($$createProduct.custom_attributes[url_key]$$)}}" stepKey="goToProductPage"/> |
| 62 | + <waitForPageLoad stepKey="waitForProductPage"/> |
| 63 | + <waitForElementClickable selector="{{StorefrontProductActionSection.addToCart}}" stepKey="waitForAddToCartButton"/> |
| 64 | + <actionGroup ref="AddToCartFromStorefrontProductPageActionGroup" stepKey="addToCart"> |
| 65 | + <argument name="productName" value="$$createProduct.name$$"/> |
| 66 | + </actionGroup> |
| 67 | + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckout"/> |
| 68 | + <waitForPageLoad stepKey="waitForCheckoutLoad"/> |
| 69 | + <waitForElementVisible selector="{{CheckoutShippingSection.shippingTab}}" stepKey="waitForShippingTab"/> |
| 70 | + |
| 71 | + <!-- |
| 72 | + * Run SRI assertions here — on the checkout shipping page — where RequireJS |
| 73 | + * has fully bootstrapped and dynamically loaded all its modules. |
| 74 | + * This is the page most likely to expose the race condition. |
| 75 | + --> |
| 76 | + |
| 77 | + <!-- |
| 78 | + * Assert 1: sri.js appears before requirejs-config.js in the DOM. |
| 79 | + * This is the direct regression guard for the race condition fix in |
| 80 | + * Magento\RequireJs\Block\Html\Head\Config::_prepareLayout(). |
| 81 | + --> |
| 82 | + <executeJS |
| 83 | + function=" |
| 84 | + var scripts = Array.from(document.querySelectorAll('script[src]')); |
| 85 | + var sriIdx = scripts.findIndex(function(s) { return s.src.indexOf('Magento_Csp/js/sri') !== -1; }); |
| 86 | + var configIdx = scripts.findIndex(function(s) { return s.src.indexOf('requirejs-config') !== -1; }); |
| 87 | + return sriIdx !== -1 && configIdx !== -1 && sriIdx < configIdx; |
| 88 | + " |
| 89 | + stepKey="checkSriJsBeforeRequireJsConfig"/> |
| 90 | + <assertTrue stepKey="assertSriJsBeforeRequireJsConfig" |
| 91 | + message="sri.js must appear before requirejs-config.js in the page head so that the onNodeCreated handler is registered before RequireJS starts loading modules"> |
| 92 | + <actualResult type="variable">checkSriJsBeforeRequireJsConfig</actualResult> |
| 93 | + </assertTrue> |
| 94 | + |
| 95 | + <!-- |
| 96 | + * Assert 2: window.sriHashes inline script appears before requirejs-config.js in the DOM. |
| 97 | + * window.sriHashes must be defined before requirejs-config.js executes so that |
| 98 | + * sri.js onNodeCreated callbacks can read it on the first require() call. |
| 99 | + * On a cached load requirejs-config.js executes instantly — if window.sriHashes |
| 100 | + * is defined later in the page the integrity attributes are never applied. |
| 101 | + --> |
| 102 | + <executeJS |
| 103 | + function=" |
| 104 | + var scripts = Array.from(document.scripts); |
| 105 | + var sriHashesIdx = scripts.findIndex(function(s) { return s.textContent.indexOf('window.sriHashes') !== -1; }); |
| 106 | + var configIdx = scripts.findIndex(function(s) { var src = s.getAttribute('src') || ''; return src.indexOf('requirejs-config') !== -1; }); |
| 107 | + return sriHashesIdx !== -1 && configIdx !== -1 && sriHashesIdx < configIdx; |
| 108 | + " |
| 109 | + stepKey="checkSriHashesBeforeRequireJsConfig"/> |
| 110 | + <assertTrue stepKey="assertSriHashesBeforeRequireJsConfig" |
| 111 | + message="window.sriHashes inline script must appear before requirejs-config.js so it is defined before RequireJS fires onNodeCreated"> |
| 112 | + <actualResult type="variable">checkSriHashesBeforeRequireJsConfig</actualResult> |
| 113 | + </assertTrue> |
| 114 | + |
| 115 | + <!-- |
| 116 | + * Assert 3: window.sriHashes is populated. |
| 117 | + * A count of zero means SRI is effectively disabled regardless of script ordering. |
| 118 | + --> |
| 119 | + <executeJS function="return window.sriHashes ? Object.keys(window.sriHashes).length : 0;" stepKey="getSriHashCount"/> |
| 120 | + <assertGreaterThan stepKey="assertSriHashesNotEmpty" |
| 121 | + message="window.sriHashes must be populated after static deploy"> |
| 122 | + <actualResult type="variable">getSriHashCount</actualResult> |
| 123 | + <expectedResult type="int">0</expectedResult> |
| 124 | + </assertGreaterThan> |
| 125 | + |
| 126 | + <!-- |
| 127 | + * Assert 4: All versioned static scripts are minified (.min.js) and have an integrity attribute. |
| 128 | + * Minification is enabled in before, so every deployed JS file must end in .min.js. |
| 129 | + * Returns the count of scripts that are either non-minified or missing integrity. |
| 130 | + --> |
| 131 | + <executeJS |
| 132 | + function=" |
| 133 | + var staticUrlPattern = /\/static\/version\d+\//; |
| 134 | + var violations = 0; |
| 135 | + document.querySelectorAll('script[src]').forEach(function(script) { |
| 136 | + var src = script.getAttribute('src'); |
| 137 | + if (!src || !staticUrlPattern.test(src)) { return; } |
| 138 | + if (src.indexOf('.min.js') === -1) { violations++; } |
| 139 | + if (!script.getAttribute('integrity')) { violations++; } |
| 140 | + }); |
| 141 | + return violations; |
| 142 | + " |
| 143 | + stepKey="getMinificationAndSriViolations"/> |
| 144 | + <assertEquals stepKey="assertAllScriptsMinifiedWithSri" |
| 145 | + message="All versioned static JS files must end in .min.js and have a sha256 integrity attribute"> |
| 146 | + <actualResult type="variable">getMinificationAndSriViolations</actualResult> |
| 147 | + <expectedResult type="int">0</expectedResult> |
| 148 | + </assertEquals> |
| 149 | + |
| 150 | + <!-- Complete the guest order to confirm checkout remains functional after the fix --> |
| 151 | + <actionGroup ref="GuestCheckoutFillingShippingSectionActionGroup" stepKey="fillShipping"/> |
| 152 | + <waitForPageLoad stepKey="waitForPaymentPage"/> |
| 153 | + <waitForElementVisible selector="{{CheckoutPaymentSection.placeOrder}}" time="30" stepKey="waitForPlaceOrder"/> |
| 154 | + <waitForElementClickable selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="waitForPlaceOrderClickable"/> |
| 155 | + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> |
| 156 | + <waitForPageLoad stepKey="waitForSuccessPage"/> |
| 157 | + <waitForElementVisible selector="{{CheckoutSuccessMainSection.success}}" stepKey="waitForSuccessMessage"/> |
| 158 | + |
| 159 | + </test> |
| 160 | + |
| 161 | +</tests> |
0 commit comments