diff --git a/plugins/woocommerce/.wp-env.json b/plugins/woocommerce/.wp-env.json index a9b1dea4818..58ecc03c740 100644 --- a/plugins/woocommerce/.wp-env.json +++ b/plugins/woocommerce/.wp-env.json @@ -10,7 +10,8 @@ "ALTERNATE_WP_CRON": true }, "mappings": { - "wp-cli.yml": "./tests/wp-cli.yml" + "wp-cli.yml": "./tests/wp-cli.yml", + "wp-content/plugins/filter-setter.php": "./tests/e2e-pw/bin/filter-setter.php" }, "lifecycleScripts": { "afterStart": "./tests/e2e-pw/bin/test-env-setup.sh", diff --git a/plugins/woocommerce/changelog/fix-order-confirmation-access b/plugins/woocommerce/changelog/fix-order-confirmation-access new file mode 100644 index 00000000000..a1170f00ebe --- /dev/null +++ b/plugins/woocommerce/changelog/fix-order-confirmation-access @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Adds a grace period during which email verification will not be needed before the order confirmation (or payment) page is rendered. diff --git a/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-checkout.php b/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-checkout.php index 28e53766b98..96903d1f747 100644 --- a/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-checkout.php +++ b/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-checkout.php @@ -390,6 +390,31 @@ class WC_Shortcode_Checkout { return true; } + /** + * Controls the grace period within which we do not require any sort of email verification step before rendering + * the 'order received' or 'order pay' pages. + * + * To eliminate the grace period, set to zero (or to a negative value). Note that this filter is not invoked + * at all if email verification is deemed to be unnecessary (in other words, it cannot be used to force + * verification in *all* cases). + * + * @since 8.0.0 + * + * @param int $grace_period Time in seconds after an order is placed before email verification may be required. + * @param WC_Order $order The order for which this grace period is being assessed. + * @param string $context Indicates the context in which we might verify the email address. Typically 'order-pay' or 'order-received'. + */ + $verification_grace_period = (int) apply_filters( 'woocommerce_order_email_verification_grace_period', 10 * MINUTE_IN_SECONDS, $order, $context ); + $date_created = $order->get_date_created(); + + // We do not need to verify the email address if we are within the grace period immediately following order creation. + if ( + is_a( $date_created, WC_DateTime::class ) + && time() - $date_created->getTimestamp() <= $verification_grace_period + ) { + return false; + } + $session = wc()->session; $session_email = ''; diff --git a/plugins/woocommerce/tests/e2e-pw/bin/filter-setter.php b/plugins/woocommerce/tests/e2e-pw/bin/filter-setter.php new file mode 100644 index 00000000000..515cfb5b3f4 --- /dev/null +++ b/plugins/woocommerce/tests/e2e-pw/bin/filter-setter.php @@ -0,0 +1,87 @@ + $spec ) { + // A priority may be specified as part of the spec, else use the default priority (10). + $priority = isset( $spec['priority'] ) && is_int( $spec['priority'] ) + ? $spec['priority'] + : 10; + + // If the spec is not an array, then it is probably intended as the literal value. + if ( ! is_array( $spec ) ) { + $value = $spec; + } elseif ( isset( $spec['value'] ) ) { + $value = $spec['value']; + } + + // If we know the value, we can establish our filter callback. + if ( isset( $value ) ) { + $callback = function () use ( $value ) { + return $value; + }; + } + + // We also support specifying a callback function. + if ( is_array( $spec ) && isset( $spec['callback'] ) && is_string( $spec['callback'] ) ) { + $callback = $spec['callback']; + } + + // Ensure we have a callback, then setup the filter. + if ( isset( $callback ) ) { + add_filter( $hook, $callback, $priority ); + } +} + diff --git a/plugins/woocommerce/tests/e2e-pw/bin/test-env-setup.sh b/plugins/woocommerce/tests/e2e-pw/bin/test-env-setup.sh index 79a52b1d8ac..9fba2ee9cb2 100755 --- a/plugins/woocommerce/tests/e2e-pw/bin/test-env-setup.sh +++ b/plugins/woocommerce/tests/e2e-pw/bin/test-env-setup.sh @@ -10,6 +10,9 @@ wp-env run tests-cli wp theme activate twentynineteen echo -e 'Update URL structure \n' wp-env run tests-cli wp rewrite structure '/%postname%/' --hard +echo -e 'Activate Filter Setter utility plugin \n' +wp-env run tests-cli wp plugin activate filter-setter + echo -e 'Add Customer user \n' wp-env run tests-cli wp user create customer customer@woocommercecoree2etestsuite.com \ --user_pass=password \ diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout.spec.js index 0bb9759cb60..6aabb57cb29 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout.spec.js @@ -1,6 +1,7 @@ const { test, expect } = require( '@playwright/test' ); const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; const { admin, customer } = require( '../../test-data/data' ); +const { setFilterValue, clearFilters } = require( '../../utils/filters' ); const guestEmail = 'checkout-guest@example.com'; @@ -235,9 +236,9 @@ test.describe( 'Checkout page', () => { await page.locator( 'text=Place order' ).click(); - await expect( page.locator( 'h1.entry-title' ) ).toContainText( - 'Order received' - ); + await expect( + page.getByRole( 'heading', { name: 'Order received' } ) + ).toBeVisible(); // get order ID from the page const orderReceivedText = await page @@ -245,10 +246,20 @@ test.describe( 'Checkout page', () => { .textContent(); guestOrderId = await orderReceivedText.split( /(\s+)/ )[ 6 ].toString(); - // If we simulate a new browser context by dropping all cookies, and reload the page, the shopper should be - // prompted to complete an email validation step before they can proceed. + + // Let's simulate a new browser context (by dropping all cookies), and reload the page. This approximates a + // scenario where the server can no longer identify the shopper. However, so long as we are within the 10 minute + // grace period following initial order placement, the 'order received' page should still be rendered. await page.context().clearCookies(); await page.reload(); + await expect( + page.getByRole( 'heading', { name: 'Order received' } ) + ).toBeVisible(); + + // Let's simulate a scenario where the 10 minute grace period has expired. This time, we expect the shopper to + // be presented with a request to verify their email address. + await setFilterValue( page, 'woocommerce_order_email_verification_grace_period', 0 ); + await page.reload(); await expect( page.locator( 'form.woocommerce-verify-email p:nth-child(3)' ) ).toContainText( /verify the email address associated with the order/ ); @@ -267,9 +278,9 @@ test.describe( 'Checkout page', () => { // However if they supply the *correct* billing email address, they should see the order received page again. await page.fill( '#email', guestEmail ); await page.locator( 'form.woocommerce-verify-email button' ).click(); - await expect( page.locator( 'h1.entry-title' ) ).toContainText( - 'Order received' - ); + await expect( + page.getByRole( 'heading', { name: 'Order received' } ) + ).toBeVisible(); await page.goto( 'wp-login.php' ); await page.locator( 'input[name="log"]' ).fill( admin.username ); @@ -282,8 +293,8 @@ test.describe( 'Checkout page', () => { ); await expect( - page.locator( 'h2.woocommerce-order-data__heading' ) - ).toContainText( `Order #${ guestOrderId } details` ); + page.getByRole( 'heading', { name: `Order #${ guestOrderId } details` } ) + ).toBeVisible(); await expect( page.locator( '.wc-order-item-name' ) ).toContainText( simpleProductName ); @@ -296,6 +307,7 @@ test.describe( 'Checkout page', () => { await expect( page.locator( 'td.line_cost >> nth=0' ) ).toContainText( twoProductPrice ); + await clearFilters( page ); } ); test( 'allows existing customer to place order', async ( { page } ) => { diff --git a/plugins/woocommerce/tests/e2e-pw/utils/filters.js b/plugins/woocommerce/tests/e2e-pw/utils/filters.js new file mode 100644 index 00000000000..04c90a76b86 --- /dev/null +++ b/plugins/woocommerce/tests/e2e-pw/utils/filters.js @@ -0,0 +1,56 @@ +const defaultConfig = require( '../playwright.config' ); +const testURL = new URL( defaultConfig.use.baseURL ); + +/** + * Request that a WordPress filter be established for the specified hook and returning the specified value. + * + * 'Under the hood', this is done by communicating to a server-side plugin via cookies. Therefore, for the server-side + * code to observe the requested filter, you may need to `page.reload()` prior to writing assertions that rely on your + * filter. + * + * @param page + * @param hook + * @param value + * @param priority + */ +export async function setFilterValue( page, hook, value, priority = 10 ) { + const context = page.context(); + const existingCookies = await context.cookies(); + let filterSpecs = {}; + + for ( const cookie of existingCookies ) { + if ( cookie.name === 'e2e-filters' ) { + filterSpecs = JSON.parse( cookie.value ); + break; + } + } + + filterSpecs[hook] = { + value: value, + priority: priority + }; + + await context.addCookies( [ { + name: 'e2e-filters', + value: JSON.stringify( filterSpecs ), + path: '/', + domain: testURL.hostname + } ] ); +} + +/** + * Clears any server-side filters setup via setFilterValue(). + * + * As with its sister function, this mechanism relies on cookies and therefore a call to `page.reload()` may be required + * before performing further assertions. + * + * @param page + */ +export async function clearFilters( page ) { + await page.context().addCookies( [ { + name: 'e2e-filters', + value: '', + path: '/', + domain: testURL.hostname + } ] ); +} diff --git a/plugins/woocommerce/tests/e2e/docker/initialize.sh b/plugins/woocommerce/tests/e2e/docker/initialize.sh index f86c50f218e..a624331ffb3 100755 --- a/plugins/woocommerce/tests/e2e/docker/initialize.sh +++ b/plugins/woocommerce/tests/e2e/docker/initialize.sh @@ -24,5 +24,8 @@ wp plugin install https://github.com/woocommerce/woocommerce-reset/zipball/trunk # install the WP Mail Logging plugin to test emails wp plugin install wp-mail-logging --activate +# Activate our Filter Setter utility. +wp plugin activate filter-setter + # initialize pretty permalinks wp rewrite structure /%postname%/