[Experimental] Add E2E tests for Additional Checkout Fields (#43836)
* Add additional-checkout-fields-test-helper wp-env plugin * Ensure Checkout page waits for email to be visible before filling * Handle additional fields in checkout page utils * Add test for filling additional checkout fields * Add additional check for gov-id validation * Close context used for plugin-checking * Update types for additional fields to be key/value objects * Check billing gov-id is different to shipping one * Move additional fields plugin to inline with checkout tests * Use additional fields plugin from local dir * Await email input to check Checkout block is loaded * Add test to verify error message shows when leaving a field blank * Update comment * Add test for checking/unchecking checkboxes * Update check in additional field test plugin * Change fields multiple times in one test * Add server-side validation tests * Get exact matches for checkout form fields * Remove unnecessarily translated strings * update hook in test plugin to compare to confirmation gov id * Fill in gov ID confirmation field * Register a field of each type in each location * Don't validate field unless both are filled * Fill additional fields and check their values * Fill additional fields * Check select values in order confirmation * Change the values of all field types multiple times * Make checkout wait until not calculating before submitting * Update tests to use all additional field types * Blur after editing select box * Add customer area checks * Check shipping isn't calculating before submitting * Add merchant-side tests for additional checkout fields * Ensure customer data is done updating before submitting * use waitForFunction to check for updating shipping * Add changelog * Ensure fields are blurred before pressing button * Update validation error check * Add test for logged out shopper * make specific function to wait for checkout to be finished updating * Add test to ensure fields are saved across orders * Add sanitize and validate callbacks to gov id fields * Make purchase type select field required * Add sanitization test * Use experimental function in test plugin * Add standalone sanitization filter test * Update testing plugin to use new and renamed filters * Fix typo * Move empty value test to logged out shopper block and check empty val * Update tests to include tests for new validation/sanitization filters * Add verifyAdditionalFieldsDetails function to checkout page * Use new verifyAdditionalFieldsDetails function to improve readability * Update second test to use new function to improve readability * Update third test to improve readability * Update fourth function to improve readability * Update fourth test to improve readability * Update guest shopper test to be more readable * Update helper function to only return value not assert * Remove check causing test to fail in CI * Make check for guest shopper same as logged in * Move guest shopper tests to their own file * Ensure unchecking checkbox works OK
This commit is contained in:
parent
a7383579be
commit
5cbb0ad5bf
|
@ -0,0 +1,243 @@
|
|||
<?php
|
||||
/**
|
||||
* Plugin Name: Additional checkout fields plugin
|
||||
* Description: Add additional checkout fields
|
||||
* @package WordPress
|
||||
*/
|
||||
|
||||
class Additional_Checkout_Fields_Test_Helper {
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'plugins_loaded', array( $this, 'enable_custom_checkout_fields' ) );
|
||||
add_action( 'plugins_loaded', array( $this, 'disable_custom_checkout_fields' ) );
|
||||
add_action( 'woocommerce_loaded', array( $this, 'register_custom_checkout_fields' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @var string Define option name to decide if additional fields should be turned on.
|
||||
*/
|
||||
private $additional_checkout_fields_option_name = 'woocommerce_additional_checkout_fields';
|
||||
|
||||
/**
|
||||
* Define URL endpoint for enabling additional checkout fields.
|
||||
*/
|
||||
public function enable_custom_checkout_fields() {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
if ( isset( $_GET['enable_custom_checkout_fields'] ) ) {
|
||||
update_option( $this->additional_checkout_fields_option_name, 'yes' );
|
||||
echo 'Enabled custom checkout fields';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Define URL endpoint for disabling additional checkout fields.
|
||||
*/
|
||||
public function disable_custom_checkout_fields() {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
if ( isset( $_GET['disable_custom_checkout_fields'] ) ) {
|
||||
update_option( $this->additional_checkout_fields_option_name, 'no' );
|
||||
echo 'Disabled custom checkout fields';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers custom checkout fields for the WooCommerce checkout form.
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception If there is an error during the registration of the checkout fields.
|
||||
*/
|
||||
public function register_custom_checkout_fields() {
|
||||
// Address fields, checkbox, textbox, select
|
||||
__experimental_woocommerce_blocks_register_checkout_field(
|
||||
array(
|
||||
'id' => 'first-plugin-namespace/government-ID',
|
||||
'label' => 'Government ID',
|
||||
'location' => 'address',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'sanitize_callback' => function( $field_value ) {
|
||||
return str_replace( ' ', '', $field_value );
|
||||
},
|
||||
'validate_callback' => function( $field_value ) {
|
||||
$match = preg_match( '/^[0-9]{5}$/', $field_value );
|
||||
if ( 0 === $match || false === $match ) {
|
||||
return new \WP_Error( 'invalid_government_id', 'Invalid government ID.' );
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
__experimental_woocommerce_blocks_register_checkout_field(
|
||||
array(
|
||||
'id' => 'first-plugin-namespace/confirm-government-ID',
|
||||
'label' => 'Confirm government ID',
|
||||
'location' => 'address',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'sanitize_callback' => function( $field_value ) {
|
||||
return str_replace( ' ', '', $field_value );
|
||||
},
|
||||
'validate_callback' => function( $field_value ) {
|
||||
$match = preg_match( '/^[0-9]{5}$/', $field_value );
|
||||
if ( 0 === $match || false === $match ) {
|
||||
return new \WP_Error( 'invalid_government_id', 'Invalid government ID.' );
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
__experimental_woocommerce_blocks_register_checkout_field(
|
||||
array(
|
||||
'id' => 'first-plugin-namespace/truck-size-ok',
|
||||
'label' => 'Can a truck fit down your road?',
|
||||
'location' => 'address',
|
||||
'type' => 'checkbox',
|
||||
)
|
||||
);
|
||||
__experimental_woocommerce_blocks_register_checkout_field(
|
||||
array(
|
||||
'id' => 'first-plugin-namespace/road-size',
|
||||
'label' => 'How wide is your road?',
|
||||
'location' => 'address',
|
||||
'type' => 'select',
|
||||
'options' => array(
|
||||
array(
|
||||
'label' => 'Wide',
|
||||
'value' => 'wide',
|
||||
),
|
||||
array(
|
||||
'label' => 'Super wide',
|
||||
'value' => 'super-wide',
|
||||
),
|
||||
array(
|
||||
'label' => 'Narrow',
|
||||
'value' => 'narrow',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Fake sanitization function that removes full stops from the Government ID string.
|
||||
add_filter(
|
||||
'__experimental_woocommerce_blocks_sanitize_additional_field',
|
||||
function ( $field_value, $field_key ) {
|
||||
if ( 'first-plugin-namespace/government-ID' === $field_key ) {
|
||||
$field_value = str_replace( '.', '', $field_value );
|
||||
}
|
||||
return $field_value;
|
||||
},
|
||||
10,
|
||||
2
|
||||
);
|
||||
|
||||
add_action(
|
||||
'__experimental_woocommerce_blocks_validate_additional_field',
|
||||
function ( WP_Error $errors, $field_key, $field_value ) {
|
||||
if ( 'first-plugin-namespace/government-ID' === $field_key || 'first-plugin-namespace/confirm-government-ID' === $field_key ) {
|
||||
$match = preg_match( '/[A-Z0-9]{5}/', $field_value );
|
||||
if ( 0 === $match || false === $match ) {
|
||||
$errors->add( 'invalid_gov_id', 'Please ensure your government ID matches the correct format.' );
|
||||
}
|
||||
}
|
||||
},
|
||||
10,
|
||||
4
|
||||
);
|
||||
|
||||
add_action(
|
||||
'__experimental_woocommerce_blocks_validate_location_address_fields',
|
||||
function ( \WP_Error $errors, $fields, $group ) {
|
||||
if ( $fields['first-plugin-namespace/government-ID'] !== $fields['first-plugin-namespace/confirm-government-ID'] ) {
|
||||
$errors->add( 'gov_id_mismatch', 'Please ensure your government ID matches the confirmation.' );
|
||||
}
|
||||
},
|
||||
10,
|
||||
3
|
||||
);
|
||||
|
||||
// Contact fields, one checkbox, select, and text input.
|
||||
__experimental_woocommerce_blocks_register_checkout_field(
|
||||
array(
|
||||
'id' => 'second-plugin-namespace/marketing-opt-in',
|
||||
'label' => 'Do you want to subscribe to our newsletter?',
|
||||
'location' => 'contact',
|
||||
'type' => 'checkbox',
|
||||
)
|
||||
);
|
||||
__experimental_woocommerce_blocks_register_checkout_field(
|
||||
array(
|
||||
'id' => 'second-plugin-namespace/gift-message-in-package',
|
||||
'label' => 'Enter a gift message to include in the package',
|
||||
'location' => 'contact',
|
||||
'type' => 'text',
|
||||
)
|
||||
);
|
||||
__experimental_woocommerce_blocks_register_checkout_field(
|
||||
array(
|
||||
'id' => 'second-plugin-namespace/type-of-purchase',
|
||||
'label' => 'Is this a personal purchase or a business purchase?',
|
||||
'location' => 'contact',
|
||||
'required' => true,
|
||||
'type' => 'select',
|
||||
'options' => [
|
||||
[
|
||||
'label' => 'Personal',
|
||||
'value' => 'personal',
|
||||
],
|
||||
[
|
||||
'label' => 'Business',
|
||||
'value' => 'business',
|
||||
],
|
||||
],
|
||||
)
|
||||
);
|
||||
|
||||
// A field of each type in additional information section.
|
||||
|
||||
__experimental_woocommerce_blocks_register_checkout_field(
|
||||
array(
|
||||
'id' => 'third-plugin-namespace/please-send-me-a-free-gift',
|
||||
'label' => 'Would you like a free gift with your order?',
|
||||
'location' => 'additional',
|
||||
'type' => 'checkbox',
|
||||
)
|
||||
);
|
||||
|
||||
__experimental_woocommerce_blocks_register_checkout_field(
|
||||
array(
|
||||
'id' => 'third-plugin-namespace/what-is-your-favourite-colour',
|
||||
'label' => 'What is your favourite colour?',
|
||||
'location' => 'additional',
|
||||
'type' => 'text',
|
||||
)
|
||||
);
|
||||
|
||||
__experimental_woocommerce_blocks_register_checkout_field(
|
||||
array(
|
||||
'id' => 'third-plugin-namespace/how-did-you-hear-about-us',
|
||||
'label' => 'How did you hear about us?',
|
||||
'location' => 'additional',
|
||||
'type' => 'select',
|
||||
'options' => array(
|
||||
array(
|
||||
'value' => 'google',
|
||||
'label' => 'Google',
|
||||
),
|
||||
array(
|
||||
'value' => 'facebook',
|
||||
'label' => 'Facebook',
|
||||
),
|
||||
array(
|
||||
'value' => 'friend',
|
||||
'label' => 'From a friend',
|
||||
),
|
||||
array(
|
||||
'value' => 'other',
|
||||
'label' => 'Other',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
new Additional_Checkout_Fields_Test_Helper();
|
|
@ -0,0 +1,302 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { expect, test as base } from '@woocommerce/e2e-playwright-utils';
|
||||
import { guestFile } from '@woocommerce/e2e-utils';
|
||||
import {
|
||||
installPluginFromPHPFile,
|
||||
uninstallPluginFromPHPFile,
|
||||
} from '@woocommerce/e2e-mocks/custom-plugins';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { REGULAR_PRICED_PRODUCT_NAME } from './constants';
|
||||
import { CheckoutPage } from './checkout.page';
|
||||
|
||||
const test = base.extend< { checkoutPageObject: CheckoutPage } >( {
|
||||
checkoutPageObject: async ( { page }, use ) => {
|
||||
const pageObject = new CheckoutPage( {
|
||||
page,
|
||||
} );
|
||||
await use( pageObject );
|
||||
},
|
||||
} );
|
||||
|
||||
test.describe( 'Shopper → Additional Checkout Fields', () => {
|
||||
test.describe( 'Guest shopper', () => {
|
||||
test.use( { storageState: guestFile } );
|
||||
test.beforeAll( async () => {
|
||||
await installPluginFromPHPFile(
|
||||
`${ __dirname }/additional-checkout-fields-plugin.php`
|
||||
);
|
||||
} );
|
||||
test.afterAll( async () => {
|
||||
await uninstallPluginFromPHPFile(
|
||||
`${ __dirname }/additional-checkout-fields-plugin.php`
|
||||
);
|
||||
} );
|
||||
|
||||
test.beforeEach( async ( { frontendUtils } ) => {
|
||||
await frontendUtils.emptyCart();
|
||||
await frontendUtils.goToShop();
|
||||
await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
|
||||
await frontendUtils.goToCheckout();
|
||||
} );
|
||||
|
||||
test( 'Shopper can see an error message when a required field is not filled in the checkout form', async ( {
|
||||
checkoutPageObject,
|
||||
} ) => {
|
||||
await checkoutPageObject.editShippingDetails();
|
||||
await checkoutPageObject.unsyncBillingWithShipping();
|
||||
await checkoutPageObject.editBillingDetails();
|
||||
await checkoutPageObject.fillInCheckoutWithTestData(
|
||||
{},
|
||||
{
|
||||
contact: {
|
||||
'Enter a gift message to include in the package':
|
||||
'This is for you!',
|
||||
},
|
||||
address: {
|
||||
shipping: {
|
||||
'Government ID': '',
|
||||
'Confirm government ID': '',
|
||||
},
|
||||
billing: {
|
||||
'Government ID': '54321',
|
||||
'Confirm government ID': '54321',
|
||||
},
|
||||
},
|
||||
additional: {
|
||||
'How did you hear about us?': 'Other',
|
||||
'What is your favourite colour?': 'Blue',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Use the data store to specifically unset the field value - this is because it might be saved in the user-state.
|
||||
await checkoutPageObject.page.evaluate( () => {
|
||||
window.wp.data.dispatch( 'wc/store/cart' ).setShippingAddress( {
|
||||
'first-plugin-namespace/road-size': '',
|
||||
} );
|
||||
} );
|
||||
|
||||
await checkoutPageObject.placeOrder( false );
|
||||
|
||||
await expect(
|
||||
checkoutPageObject.page.getByText(
|
||||
'Please enter a valid government id'
|
||||
)
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
checkoutPageObject.page.getByText(
|
||||
'Please select a valid option'
|
||||
)
|
||||
).toBeVisible();
|
||||
} );
|
||||
|
||||
test( 'Shopper can fill in the checkout form with additional fields and can have different value for same field in shipping and billing address', async ( {
|
||||
checkoutPageObject,
|
||||
frontendUtils,
|
||||
} ) => {
|
||||
await checkoutPageObject.unsyncBillingWithShipping();
|
||||
await checkoutPageObject.fillInCheckoutWithTestData(
|
||||
{},
|
||||
{
|
||||
contact: {
|
||||
'Enter a gift message to include in the package':
|
||||
'This is for you!',
|
||||
'Is this a personal purchase or a business purchase?':
|
||||
'business',
|
||||
},
|
||||
address: {
|
||||
shipping: {
|
||||
'Government ID': '12345',
|
||||
'Confirm government ID': '12345',
|
||||
},
|
||||
billing: {
|
||||
'Government ID': '54321',
|
||||
'Confirm government ID': '54321',
|
||||
},
|
||||
},
|
||||
additional: {
|
||||
'How did you hear about us?': 'Other',
|
||||
'What is your favourite colour?': 'Blue',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Fill select fields "manually" (Not part of "fillInCheckoutWithTestData"). This is a workaround for select
|
||||
// fields until we recreate th Combobox component. This is because the aria-label includes the value so getting
|
||||
// by label alone is not reliable unless we know the value.
|
||||
await checkoutPageObject.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Shipping address',
|
||||
} )
|
||||
.getByLabel( 'How wide is your road?' )
|
||||
.fill( 'wide' );
|
||||
await checkoutPageObject.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Billing address',
|
||||
} )
|
||||
.getByLabel( 'How wide is your road?' )
|
||||
.fill( 'narrow' );
|
||||
|
||||
await checkoutPageObject.page.evaluate(
|
||||
'document.activeElement.blur()'
|
||||
);
|
||||
|
||||
await checkoutPageObject.page
|
||||
.getByLabel( 'Would you like a free gift with your order?' )
|
||||
.check();
|
||||
await checkoutPageObject.page
|
||||
.getByLabel( 'Do you want to subscribe to our newsletter?' )
|
||||
.check();
|
||||
await checkoutPageObject.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Shipping address',
|
||||
} )
|
||||
.getByLabel( 'Can a truck fit down your road?' )
|
||||
.check();
|
||||
|
||||
await checkoutPageObject.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Billing address',
|
||||
} )
|
||||
.getByLabel( 'Can a truck fit down your road?' )
|
||||
.check();
|
||||
|
||||
await checkoutPageObject.page.waitForRequest( ( req ) => {
|
||||
return req.url().includes( 'batch' );
|
||||
} );
|
||||
|
||||
await checkoutPageObject.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Billing address',
|
||||
} )
|
||||
.getByLabel( 'Can a truck fit down your road?' )
|
||||
.uncheck();
|
||||
|
||||
await checkoutPageObject.page.waitForRequest( ( req ) => {
|
||||
return req.url().includes( 'batch' );
|
||||
} );
|
||||
|
||||
await checkoutPageObject.waitForCheckoutToFinishUpdating();
|
||||
|
||||
await checkoutPageObject.placeOrder();
|
||||
|
||||
expect(
|
||||
await checkoutPageObject.verifyAdditionalFieldsDetails( [
|
||||
[ 'Government ID', '12345' ],
|
||||
[ 'Government ID', '54321' ],
|
||||
[ 'What is your favourite colour?', 'Blue' ],
|
||||
[
|
||||
'Enter a gift message to include in the package',
|
||||
'This is for you!',
|
||||
],
|
||||
[ 'Do you want to subscribe to our newsletter?', 'Yes' ],
|
||||
[ 'Would you like a free gift with your order?', 'Yes' ],
|
||||
[ 'Can a truck fit down your road?', 'Yes' ],
|
||||
[ 'Can a truck fit down your road?', 'No' ],
|
||||
[ 'How wide is your road?', 'Wide' ],
|
||||
[ 'How wide is your road?', 'Narrow' ],
|
||||
[
|
||||
'Is this a personal purchase or a business purchase?',
|
||||
'business',
|
||||
],
|
||||
] )
|
||||
).toBe( true );
|
||||
|
||||
await frontendUtils.emptyCart();
|
||||
await frontendUtils.goToShop();
|
||||
await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
|
||||
await frontendUtils.goToCheckout();
|
||||
|
||||
await checkoutPageObject.editShippingDetails();
|
||||
await checkoutPageObject.editBillingDetails();
|
||||
|
||||
// Now check all the fields previously filled are still filled on a fresh checkout.
|
||||
await expect(
|
||||
checkoutPageObject.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Contact information',
|
||||
} )
|
||||
.getByLabel(
|
||||
'Enter a gift message to include in the package'
|
||||
)
|
||||
).toHaveValue( 'This is for you!' );
|
||||
await expect(
|
||||
checkoutPageObject.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Contact information',
|
||||
} )
|
||||
.getByLabel(
|
||||
'Is this a personal purchase or a business purchase?'
|
||||
)
|
||||
).toHaveValue( 'Business' );
|
||||
await expect(
|
||||
checkoutPageObject.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Contact information',
|
||||
} )
|
||||
.getByLabel( 'Do you want to subscribe to our newsletter?' )
|
||||
).toBeChecked();
|
||||
await expect(
|
||||
checkoutPageObject.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Shipping address',
|
||||
} )
|
||||
.getByLabel( 'Government ID', { exact: true } )
|
||||
).toHaveValue( '12345' );
|
||||
await expect(
|
||||
checkoutPageObject.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Shipping address',
|
||||
} )
|
||||
.getByLabel( 'Confirm Government ID' )
|
||||
).toHaveValue( '12345' );
|
||||
await expect(
|
||||
checkoutPageObject.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Shipping address',
|
||||
} )
|
||||
.getByLabel( 'Can a truck fit down your road?' )
|
||||
).toBeChecked();
|
||||
await expect(
|
||||
checkoutPageObject.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Shipping address',
|
||||
} )
|
||||
.getByLabel( 'How wide is your road?' )
|
||||
).toHaveValue( 'Wide' );
|
||||
await expect(
|
||||
checkoutPageObject.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Billing address',
|
||||
} )
|
||||
.getByLabel( 'Government ID', { exact: true } )
|
||||
).toHaveValue( '54321' );
|
||||
await expect(
|
||||
checkoutPageObject.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Billing address',
|
||||
} )
|
||||
.getByLabel( 'Confirm Government ID' )
|
||||
).toHaveValue( '54321' );
|
||||
await expect(
|
||||
checkoutPageObject.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Billing address',
|
||||
} )
|
||||
.getByLabel( 'Can a truck fit down your road?' )
|
||||
).not.toBeChecked();
|
||||
await expect(
|
||||
checkoutPageObject.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Billing address',
|
||||
} )
|
||||
.getByLabel( 'How wide is your road?' )
|
||||
).toHaveValue( 'Narrow' );
|
||||
} );
|
||||
} );
|
||||
} );
|
|
@ -0,0 +1,411 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { expect, test as base } from '@woocommerce/e2e-playwright-utils';
|
||||
import { adminFile } from '@woocommerce/e2e-utils';
|
||||
import {
|
||||
installPluginFromPHPFile,
|
||||
uninstallPluginFromPHPFile,
|
||||
} from '@woocommerce/e2e-mocks/custom-plugins';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { REGULAR_PRICED_PRODUCT_NAME } from './constants';
|
||||
import { CheckoutPage } from './checkout.page';
|
||||
|
||||
const test = base.extend< { checkoutPageObject: CheckoutPage } >( {
|
||||
checkoutPageObject: async ( { page }, use ) => {
|
||||
const pageObject = new CheckoutPage( {
|
||||
page,
|
||||
} );
|
||||
await use( pageObject );
|
||||
},
|
||||
} );
|
||||
|
||||
test.describe( 'Merchant → Additional Checkout Fields', () => {
|
||||
test.use( { storageState: adminFile } );
|
||||
test.beforeAll( async () => {
|
||||
await installPluginFromPHPFile(
|
||||
`${ __dirname }/additional-checkout-fields-plugin.php`
|
||||
);
|
||||
} );
|
||||
test.afterAll( async () => {
|
||||
await uninstallPluginFromPHPFile(
|
||||
`${ __dirname }/additional-checkout-fields-plugin.php`
|
||||
);
|
||||
} );
|
||||
|
||||
test.beforeEach( async ( { frontendUtils } ) => {
|
||||
await frontendUtils.emptyCart();
|
||||
await frontendUtils.goToShop();
|
||||
await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
|
||||
await frontendUtils.goToCheckout();
|
||||
} );
|
||||
|
||||
test( 'Merchant can see additional fields in the order admin page', async ( {
|
||||
checkoutPageObject,
|
||||
admin,
|
||||
} ) => {
|
||||
await checkoutPageObject.editShippingDetails();
|
||||
await checkoutPageObject.unsyncBillingWithShipping();
|
||||
await checkoutPageObject.editBillingDetails();
|
||||
await checkoutPageObject.fillInCheckoutWithTestData(
|
||||
{},
|
||||
{
|
||||
contact: {
|
||||
'Enter a gift message to include in the package':
|
||||
'This is for you!',
|
||||
'Is this a personal purchase or a business purchase?':
|
||||
'business',
|
||||
},
|
||||
address: {
|
||||
shipping: {
|
||||
'Government ID': '12345',
|
||||
'Confirm government ID': '12345',
|
||||
},
|
||||
billing: {
|
||||
'Government ID': '54321',
|
||||
'Confirm government ID': '54321',
|
||||
},
|
||||
},
|
||||
additional: {
|
||||
'How did you hear about us?': 'Other',
|
||||
'What is your favourite colour?': 'Blue',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Fill select fields "manually" (Not part of "fillInCheckoutWithTestData"). This is a workaround for select
|
||||
// fields until we recreate th Combobox component. This is because the aria-label includes the value so getting
|
||||
// by label alone is not reliable unless we know the value.
|
||||
await checkoutPageObject.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Shipping address',
|
||||
} )
|
||||
.getByLabel( 'How wide is your road?' )
|
||||
.fill( 'wide' );
|
||||
await checkoutPageObject.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Billing address',
|
||||
} )
|
||||
.getByLabel( 'How wide is your road?' )
|
||||
.fill( 'narrow' );
|
||||
|
||||
await checkoutPageObject.page.evaluate(
|
||||
'document.activeElement.blur()'
|
||||
);
|
||||
|
||||
await checkoutPageObject.page
|
||||
.getByLabel( 'Would you like a free gift with your order?' )
|
||||
.check();
|
||||
await checkoutPageObject.page
|
||||
.getByLabel( 'Do you want to subscribe to our newsletter?' )
|
||||
.check();
|
||||
await checkoutPageObject.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Shipping address',
|
||||
} )
|
||||
.getByLabel( 'Can a truck fit down your road?' )
|
||||
.check();
|
||||
|
||||
await checkoutPageObject.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Billing address',
|
||||
} )
|
||||
.getByLabel( 'Can a truck fit down your road?' )
|
||||
.uncheck();
|
||||
|
||||
await checkoutPageObject.placeOrder();
|
||||
|
||||
const orderId = checkoutPageObject.getOrderId();
|
||||
await admin.page.goto(
|
||||
`wp-admin/post.php?post=${ orderId }&action=edit`
|
||||
);
|
||||
|
||||
await expect(
|
||||
admin.page.getByText( 'Government ID: 12345', { exact: true } )
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText( 'Confirm government ID: 12345', {
|
||||
exact: true,
|
||||
} )
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText( 'Government ID: 54321', { exact: true } )
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText( 'Confirm government ID: 54321', {
|
||||
exact: true,
|
||||
} )
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText( 'What is your favourite colour?: Blue' )
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText(
|
||||
'Enter a gift message to include in the package: This is for you!'
|
||||
)
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText(
|
||||
'Do you want to subscribe to our newsletter?: Yes'
|
||||
)
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText(
|
||||
'Would you like a free gift with your order?: Yes'
|
||||
)
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText( 'Can a truck fit down your road?: Yes' )
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText( 'Can a truck fit down your road?: No' )
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText( 'How wide is your road?: Wide' )
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText( 'How wide is your road?: Narrow' )
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText(
|
||||
'Is this a personal purchase or a business purchase?: Business'
|
||||
)
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText( 'How did you hear about us?: Other' )
|
||||
).toBeVisible();
|
||||
} );
|
||||
|
||||
test( 'Merchant can edit custom fields from the order admin page', async ( {
|
||||
checkoutPageObject,
|
||||
admin,
|
||||
} ) => {
|
||||
await checkoutPageObject.editShippingDetails();
|
||||
await checkoutPageObject.unsyncBillingWithShipping();
|
||||
await checkoutPageObject.editBillingDetails();
|
||||
await checkoutPageObject.fillInCheckoutWithTestData(
|
||||
{},
|
||||
{
|
||||
contact: {
|
||||
'Enter a gift message to include in the package':
|
||||
'This is for you!',
|
||||
'Is this a personal purchase or a business purchase?':
|
||||
'business',
|
||||
},
|
||||
address: {
|
||||
shipping: {
|
||||
'Government ID': '12345',
|
||||
'Confirm government ID': '12345',
|
||||
},
|
||||
billing: {
|
||||
'Government ID': '54321',
|
||||
'Confirm government ID': '54321',
|
||||
},
|
||||
},
|
||||
additional: {
|
||||
'How did you hear about us?': 'Other',
|
||||
'What is your favourite colour?': 'Blue',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Fill select fields "manually" (Not part of "fillInCheckoutWithTestData"). This is a workaround for select
|
||||
// fields until we recreate th Combobox component. This is because the aria-label includes the value so getting
|
||||
// by label alone is not reliable unless we know the value.
|
||||
await checkoutPageObject.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Shipping address',
|
||||
} )
|
||||
.getByLabel( 'How wide is your road?' )
|
||||
.fill( 'wide' );
|
||||
await checkoutPageObject.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Billing address',
|
||||
} )
|
||||
.getByLabel( 'How wide is your road?' )
|
||||
.fill( 'narrow' );
|
||||
|
||||
await checkoutPageObject.page.evaluate(
|
||||
'document.activeElement.blur()'
|
||||
);
|
||||
|
||||
await checkoutPageObject.page
|
||||
.getByLabel( 'Would you like a free gift with your order?' )
|
||||
.check();
|
||||
await checkoutPageObject.page
|
||||
.getByLabel( 'Do you want to subscribe to our newsletter?' )
|
||||
.check();
|
||||
await checkoutPageObject.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Shipping address',
|
||||
} )
|
||||
.getByLabel( 'Can a truck fit down your road?' )
|
||||
.check();
|
||||
|
||||
await checkoutPageObject.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Billing address',
|
||||
} )
|
||||
.getByLabel( 'Can a truck fit down your road?' )
|
||||
.uncheck();
|
||||
|
||||
await checkoutPageObject.placeOrder();
|
||||
|
||||
const orderId = checkoutPageObject.getOrderId();
|
||||
await admin.page.goto(
|
||||
`wp-admin/post.php?post=${ orderId }&action=edit`
|
||||
);
|
||||
|
||||
await admin.page
|
||||
.getByRole( 'heading', { name: 'Billing Edit' } )
|
||||
.getByRole( 'link' )
|
||||
.click();
|
||||
|
||||
// Change all the billing details
|
||||
await admin.page
|
||||
.getByRole( 'textbox', {
|
||||
name: 'Government ID',
|
||||
exact: true,
|
||||
} )
|
||||
.fill( '99999' );
|
||||
await admin.page
|
||||
.getByRole( 'textbox', {
|
||||
name: 'Confirm government ID',
|
||||
exact: true,
|
||||
} )
|
||||
.fill( '99999' );
|
||||
await admin.page
|
||||
.getByRole( 'checkbox', {
|
||||
name: 'Can a truck fit down your road?',
|
||||
} )
|
||||
.check();
|
||||
|
||||
// Use Locator here because the select2 box is duplicated in shipping.
|
||||
await admin.page
|
||||
.locator( '[id="\\/billing\\/first-plugin-namespace\\/road-size"]' )
|
||||
.selectOption( 'wide' );
|
||||
|
||||
// Handle changing the contact fields.
|
||||
await admin.page
|
||||
.getByLabel( 'Do you want to subscribe to our newsletter?' )
|
||||
.uncheck();
|
||||
await admin.page
|
||||
.getByLabel( 'Enter a gift message to include in the package' )
|
||||
.fill( 'Some other message' );
|
||||
await admin.page
|
||||
.getByLabel( 'Is this a personal purchase or a business purchase?' )
|
||||
.selectOption( 'personal' );
|
||||
|
||||
await admin.page
|
||||
.getByRole( 'button', { name: 'Update' } )
|
||||
.first()
|
||||
.click();
|
||||
|
||||
await admin.page
|
||||
.getByRole( 'heading', { name: 'Shipping Edit' } )
|
||||
.getByRole( 'link' )
|
||||
.click();
|
||||
|
||||
// Change all the shipping details
|
||||
await admin.page
|
||||
.getByRole( 'textbox', {
|
||||
name: 'Government ID',
|
||||
exact: true,
|
||||
} )
|
||||
.fill( '88888' );
|
||||
await admin.page
|
||||
.getByRole( 'textbox', {
|
||||
name: 'Confirm government ID',
|
||||
exact: true,
|
||||
} )
|
||||
.fill( '88888' );
|
||||
await admin.page
|
||||
.getByRole( 'checkbox', {
|
||||
name: 'Can a truck fit down your road?',
|
||||
} )
|
||||
.uncheck();
|
||||
|
||||
// Use Locator here because the select2 box is duplicated in billing.
|
||||
await admin.page
|
||||
.locator(
|
||||
'[id="\\/shipping\\/first-plugin-namespace\\/road-size"]'
|
||||
)
|
||||
.selectOption( 'super-wide' );
|
||||
|
||||
// Handle changing the additional information fields.
|
||||
await admin.page
|
||||
.getByLabel( 'Would you like a free gift with your order?' )
|
||||
.uncheck();
|
||||
await admin.page
|
||||
.getByLabel( 'What is your favourite colour?' )
|
||||
.fill( 'Green' );
|
||||
await admin.page
|
||||
.getByLabel( 'How did you hear about us?' )
|
||||
.selectOption( 'google' );
|
||||
|
||||
await admin.page
|
||||
.getByRole( 'button', { name: 'Update' } )
|
||||
.first()
|
||||
.click();
|
||||
|
||||
await admin.page.waitForLoadState( 'domcontentloaded' );
|
||||
|
||||
await expect(
|
||||
admin.page.getByText( 'Government ID: 88888', { exact: true } )
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText( 'Confirm government ID: 88888', {
|
||||
exact: true,
|
||||
} )
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText( 'Government ID: 99999', { exact: true } )
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText( 'Confirm government ID: 99999', {
|
||||
exact: true,
|
||||
} )
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText( 'What is your favourite colour?: Green' )
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText(
|
||||
'Enter a gift message to include in the package: Some other message'
|
||||
)
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText(
|
||||
'Do you want to subscribe to our newsletter?: No'
|
||||
)
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText(
|
||||
'Would you like a free gift with your order?: No'
|
||||
)
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText( 'Can a truck fit down your road?: Yes' )
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText( 'Can a truck fit down your road?: No' )
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText( 'How wide is your road?: Super wide' )
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText( 'How wide is your road?: Wide' )
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText(
|
||||
'Is this a personal purchase or a business purchase?: Personal'
|
||||
)
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
admin.page.getByText( 'How did you hear about us?: Google' )
|
||||
).toBeVisible();
|
||||
} );
|
||||
} );
|
File diff suppressed because it is too large
Load Diff
|
@ -52,7 +52,21 @@ export class CheckoutPage {
|
|||
return nameIsVisible && priceIsVisible;
|
||||
}
|
||||
|
||||
async fillInCheckoutWithTestData( overrideData = {} ) {
|
||||
async fillInCheckoutWithTestData(
|
||||
overrideData = {},
|
||||
additionalFields: {
|
||||
address?: {
|
||||
shipping?: Record< string, string >;
|
||||
billing?: Record< string, string >;
|
||||
};
|
||||
contact?: Record< string, string >;
|
||||
additional?: Record< string, string >;
|
||||
} = {
|
||||
address: { shipping: {}, billing: {} },
|
||||
additional: {},
|
||||
contact: {},
|
||||
}
|
||||
) {
|
||||
const isShippingOpen = await this.page
|
||||
.getByRole( 'group', {
|
||||
name: 'Shipping address',
|
||||
|
@ -67,17 +81,100 @@ export class CheckoutPage {
|
|||
|
||||
const testData = { ...this.testData, ...overrideData };
|
||||
|
||||
await this.page.getByLabel( 'Email address' ).fill( testData.email );
|
||||
await this.fillContactInformation(
|
||||
testData.email,
|
||||
additionalFields.contact || {}
|
||||
);
|
||||
if ( isShippingOpen ) {
|
||||
await this.fillShippingDetails( testData );
|
||||
await this.fillShippingDetails(
|
||||
testData,
|
||||
additionalFields.address?.shipping || {}
|
||||
);
|
||||
}
|
||||
if ( isBillingOpen ) {
|
||||
await this.fillBillingDetails( testData );
|
||||
// Additional billing details
|
||||
await this.fillBillingDetails( testData, {
|
||||
...( additionalFields.address?.shipping || {} ),
|
||||
...( additionalFields.address?.billing || {} ),
|
||||
} );
|
||||
}
|
||||
|
||||
if (
|
||||
typeof additionalFields.additional !== 'undefined' &&
|
||||
Object.keys( additionalFields.additional ).length > 0
|
||||
) {
|
||||
await this.fillAdditionalInformationSection(
|
||||
additionalFields.additional
|
||||
);
|
||||
}
|
||||
|
||||
// Blur active field to trigger shipping rates update, then wait for requests to finish.
|
||||
await this.page.evaluate( 'document.activeElement.blur()' );
|
||||
}
|
||||
|
||||
async fillContactInformation(
|
||||
email: string,
|
||||
additionalFields: Record< string, string >
|
||||
) {
|
||||
const contactSection = this.page.getByRole( 'group', {
|
||||
name: 'Contact information',
|
||||
} );
|
||||
await contactSection.getByLabel( 'Email address' ).fill( email );
|
||||
|
||||
// Rest of additional data passed in from the overrideData object.
|
||||
for ( const [ label, value ] of Object.entries( additionalFields ) ) {
|
||||
const field = contactSection.getByLabel( label );
|
||||
await field.fill( value );
|
||||
}
|
||||
}
|
||||
|
||||
async fillAdditionalInformationSection(
|
||||
additionalFields: Record< string, string >
|
||||
) {
|
||||
const contactSection = this.page.getByRole( 'group', {
|
||||
name: 'Additional order information',
|
||||
} );
|
||||
|
||||
// Rest of additional data passed in from the overrideData object.
|
||||
for ( const [ label, value ] of Object.entries( additionalFields ) ) {
|
||||
const field = contactSection.getByLabel( label );
|
||||
await field.fill( value );
|
||||
}
|
||||
}
|
||||
|
||||
async fillAdditionalInformation(
|
||||
email: string,
|
||||
additionalFields: { label: string; value: string }[]
|
||||
) {
|
||||
await this.page.getByLabel( 'Email address' ).fill( email );
|
||||
|
||||
// Rest of additional data passed in from the overrideData object.
|
||||
for ( const { label, value } of additionalFields ) {
|
||||
const field = this.page.getByLabel( label );
|
||||
await field.fill( value );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Blurs the current input and waits for the checkout to finish any loading or calculating.
|
||||
*/
|
||||
async waitForCheckoutToFinishUpdating() {
|
||||
await this.page.evaluate( 'document.activeElement.blur()' );
|
||||
await this.page.waitForFunction( () => {
|
||||
return (
|
||||
! window.wp.data
|
||||
.select( 'wc/store/checkout' )
|
||||
.isCalculating() &&
|
||||
! window.wp.data
|
||||
.select( 'wc/store/cart' )
|
||||
.isShippingRateBeingSelected() &&
|
||||
! window.wp.data
|
||||
.select( 'wc/store/cart' )
|
||||
.isCustomerDataUpdating()
|
||||
);
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Place order and wait for redirect to order received page.
|
||||
*
|
||||
|
@ -85,12 +182,32 @@ export class CheckoutPage {
|
|||
* when testing for errors on the checkout page.
|
||||
*/
|
||||
async placeOrder( waitForRedirect = true ) {
|
||||
await this.waitForCheckoutToFinishUpdating();
|
||||
await this.page.getByText( 'Place Order', { exact: true } ).click();
|
||||
if ( waitForRedirect ) {
|
||||
await this.page.waitForURL( /order-received/ );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the additional field values are visible on the confirmation page.
|
||||
*/
|
||||
async verifyAdditionalFieldsDetails( values: [ string, string ][] ) {
|
||||
for ( const [ label, value ] of values ) {
|
||||
const visible = await this.page
|
||||
.getByText(
|
||||
`${ label }${ value }` // No space between these due to the way the markup is output on the confirmation page.
|
||||
)
|
||||
.isVisible();
|
||||
|
||||
if ( ! visible ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// If one of the fields above is false the function would have returned early.
|
||||
return true;
|
||||
}
|
||||
|
||||
async verifyAddressDetails(
|
||||
shippingOrBilling: 'shipping' | 'billing',
|
||||
overrideAddressDetails = {}
|
||||
|
@ -161,7 +278,10 @@ export class CheckoutPage {
|
|||
}
|
||||
}
|
||||
|
||||
async fillBillingDetails( customerBillingDetails ) {
|
||||
async fillBillingDetails(
|
||||
customerBillingDetails: Record< string, string >,
|
||||
additionalFields: Record< string, string > = {}
|
||||
) {
|
||||
await this.editBillingDetails();
|
||||
const billingForm = this.page.getByRole( 'group', {
|
||||
name: 'Billing address',
|
||||
|
@ -210,11 +330,21 @@ export class CheckoutPage {
|
|||
if ( await postcode.isVisible() ) {
|
||||
await postcode.fill( customerBillingDetails.postcode );
|
||||
}
|
||||
|
||||
// Rest of additional data passed in from the overrideData object.
|
||||
for ( const [ label, value ] of Object.entries( additionalFields ) ) {
|
||||
const field = billingForm.getByLabel( label, { exact: true } );
|
||||
await field.fill( value );
|
||||
}
|
||||
|
||||
// Blur active field to trigger customer address update.
|
||||
await this.page.evaluate( 'document.activeElement.blur()' );
|
||||
}
|
||||
|
||||
async fillShippingDetails( customerShippingDetails ) {
|
||||
async fillShippingDetails(
|
||||
customerShippingDetails: Record< string, string >,
|
||||
additionalFields: Record< string, string > = {}
|
||||
) {
|
||||
await this.editShippingDetails();
|
||||
const shippingForm = this.page.getByRole( 'group', {
|
||||
name: 'Shipping address',
|
||||
|
@ -262,6 +392,12 @@ export class CheckoutPage {
|
|||
await postcode.fill( customerShippingDetails.postcode );
|
||||
}
|
||||
|
||||
// Rest of additional data passed in from the overrideData object.
|
||||
for ( const [ label, value ] of Object.entries( additionalFields ) ) {
|
||||
const field = shippingForm.getByLabel( label, { exact: true } );
|
||||
await field.fill( value );
|
||||
}
|
||||
|
||||
// Blur active field to trigger customer address update.
|
||||
await this.page.evaluate( 'document.activeElement.blur()' );
|
||||
}
|
||||
|
|
|
@ -48,6 +48,8 @@ export class FrontendUtils {
|
|||
await this.page.goto( '/checkout', {
|
||||
waitUntil: 'domcontentloaded',
|
||||
} );
|
||||
|
||||
await this.page.waitForSelector( '#email' );
|
||||
}
|
||||
|
||||
async goToCart() {
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
Significance: patch
|
||||
Type: update
|
||||
Comment: This only adds E2E tests and the feature is behind a feature flag too
|
||||
|
||||
|
Loading…
Reference in New Issue