Commit 9f43f5c696e for woocommerce

commit 9f43f5c696e1e855a819e6d2f366c1aa8dab3747
Author: Ahmar Zaidi <71930390+AhmarZaidi@users.noreply.github.com>
Date:   Fri Sep 11 18:40:26 2026 +0530

    fix: "Create shipping" label button doesn't refresh page (#67895)

    * fix: Create shipping lable button doesn't refresh page

    * Add focused tests for success & failure cases and clearing the busy state

    * add: changelog entry

    * fix: address PR feedback

    * fix: failing tests

    ---------

    Co-authored-by: Sam Najian <dev@najian.info>
    Co-authored-by: Fernando Espinosa <Ferdev@users.noreply.github.com>

diff --git a/plugins/woocommerce/changelog/fix-40683 b/plugins/woocommerce/changelog/fix-40683
new file mode 100644
index 00000000000..1b1760c9a7e
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-40683
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix the Create shipping label button by showing a completion notice and a Reload page button after WooCommerce Shipping is installed and activated.
diff --git a/plugins/woocommerce/client/admin/client/wp-admin-scripts/print-shipping-label-banner/setup-notice/index.js b/plugins/woocommerce/client/admin/client/wp-admin-scripts/print-shipping-label-banner/setup-notice/index.js
index c9c8f819a55..b6212ddcbe5 100644
--- a/plugins/woocommerce/client/admin/client/wp-admin-scripts/print-shipping-label-banner/setup-notice/index.js
+++ b/plugins/woocommerce/client/admin/client/wp-admin-scripts/print-shipping-label-banner/setup-notice/index.js
@@ -2,7 +2,7 @@
  * External dependencies
  */
 import { __, sprintf } from '@wordpress/i18n';
-import { Icon, cautionFilled } from '@wordpress/icons';
+import { Icon, cautionFilled, info } from '@wordpress/icons';

 export const setupErrorTypes = {
 	DOWNLOAD: 'download',
@@ -20,7 +20,24 @@ const setupErrorDescriptions = {
 	[ setupErrorTypes.START ]: __( 'start', 'woocommerce' ),
 };

-export default function SetupNotice( { isSetupError, errorReason } ) {
+export default function SetupNotice( {
+	isSetupError,
+	errorReason,
+	infoMessage,
+} ) {
+	if ( infoMessage ) {
+		return (
+			<div className="wc-admin-shipping-banner-install-info">
+				<Icon icon={ info } className="info-icon" />
+				{ infoMessage }
+			</div>
+		);
+	}
+
+	if ( ! isSetupError ) {
+		return null;
+	}
+
 	const getErrorMessage = ( errorType ) => {
 		// Default to 'set up' description if the error type somehow doesn't exist.
 		const description =
@@ -38,10 +55,6 @@ export default function SetupNotice( { isSetupError, errorReason } ) {
 		);
 	};

-	if ( ! isSetupError ) {
-		return null;
-	}
-
 	return (
 		<div className="wc-admin-shipping-banner-install-error">
 			<Icon icon={ cautionFilled } className="warning-icon" />
diff --git a/plugins/woocommerce/client/admin/client/wp-admin-scripts/print-shipping-label-banner/shipping-banner/index.js b/plugins/woocommerce/client/admin/client/wp-admin-scripts/print-shipping-label-banner/shipping-banner/index.js
index c8cd1c6e6e9..e44573d23db 100644
--- a/plugins/woocommerce/client/admin/client/wp-admin-scripts/print-shipping-label-banner/shipping-banner/index.js
+++ b/plugins/woocommerce/client/admin/client/wp-admin-scripts/print-shipping-label-banner/shipping-banner/index.js
@@ -42,6 +42,8 @@ export class ShippingBanner extends Component {
 			wcsSetupError: false,
 			isShippingLabelButtonBusy: false,
 			isWcsModalOpen: false,
+			isPluginInstalledAndActivated: false,
+			infoMessage: null,
 		};
 	}

@@ -72,8 +74,24 @@ export class ShippingBanner extends Component {
 	};

 	createShippingLabelClicked = () => {
-		const { activePlugins } = this.props;
-		this.setState( { isShippingLabelButtonBusy: true } );
+		if ( this.state.isPluginInstalledAndActivated ) {
+			window.location.reload( true );
+			return;
+		}
+
+		const { activePlugins, isRequesting } = this.props;
+		if ( isRequesting ) {
+			return;
+		}
+
+		this.setState( {
+			isShippingLabelButtonBusy: true,
+			infoMessage: __(
+				'Installing and activating WooCommerce Shipping in the background…',
+				'woocommerce'
+			),
+			wcsSetupError: false,
+		} );
 		this.trackElementClicked( 'shipping_banner_create_label' );
 		if ( ! activePlugins.includes( wcShippingPluginSlug ) ) {
 			this.installAndActivatePlugins( wcShippingPluginSlug );
@@ -84,54 +102,62 @@ export class ShippingBanner extends Component {

 	async installAndActivatePlugins( pluginSlug ) {
 		// Avoid double activating.
-		const {
-			installPlugins,
-			activatePlugins,
-			isRequesting,
-			activePlugins,
-			isWcstCompatible,
-			isIncompatibleWCShippingInstalled,
-		} = this.props;
+		const { installPlugins, activatePlugins, isRequesting } = this.props;
 		if ( isRequesting ) {
+			this.setState( { isShippingLabelButtonBusy: false } );
 			return false;
 		}
-		const install = await installPlugins( [ pluginSlug ] );
-		if ( install.success !== true ) {
+
+		try {
+			const install = await installPlugins( [ pluginSlug ] );
+			if ( ! install || install.success !== true ) {
+				this.setState( {
+					setupErrorReason: setupErrorTypes.INSTALL,
+					wcsSetupError: true,
+					isShippingLabelButtonBusy: false,
+					infoMessage: null,
+				} );
+				return;
+			}
+		} catch ( error ) {
 			this.setState( {
 				setupErrorReason: setupErrorTypes.INSTALL,
 				wcsSetupError: true,
+				isShippingLabelButtonBusy: false,
+				infoMessage: null,
 			} );
 			return;
 		}

-		const activation = await activatePlugins( [ pluginSlug ] );
-		if ( activation.success !== true ) {
+		try {
+			const activation = await activatePlugins( [ pluginSlug ] );
+			if ( ! activation || activation.success !== true ) {
+				this.setState( {
+					setupErrorReason: setupErrorTypes.ACTIVATE,
+					wcsSetupError: true,
+					isShippingLabelButtonBusy: false,
+					infoMessage: null,
+				} );
+				return;
+			}
+		} catch ( error ) {
 			this.setState( {
 				setupErrorReason: setupErrorTypes.ACTIVATE,
 				wcsSetupError: true,
+				isShippingLabelButtonBusy: false,
+				infoMessage: null,
 			} );
 			return;
 		}

-		/**
-		 * If a incompatible version of the WooCommerce Shipping plugin is installed, the necessary endpoints
-		 * are not available, so we need to reload the page to ensure to make the plugin usable.
-		 */
-		if ( isIncompatibleWCShippingInstalled ) {
-			window.location.reload( true );
-			return;
-		}
-
-		if (
-			! activePlugins.includes( wcShippingPluginSlug ) &&
-			isWcstCompatible
-		) {
-			this.acceptTosAndGetWCSAssets();
-		} else {
-			this.setState( {
-				showShippingBanner: false,
-			} );
-		}
+		this.setState( {
+			isPluginInstalledAndActivated: true,
+			isShippingLabelButtonBusy: false,
+			infoMessage: __(
+				'WooCommerce Shipping is installed and activated. Please reload the page to get started.',
+				'woocommerce'
+			),
+		} );
 	}

 	woocommerceServiceLinkClicked = () => {
@@ -169,7 +195,12 @@ export class ShippingBanner extends Component {
 			.then( () => getWcsAssets() )
 			.then( ( wcsAssets ) => this.loadWcsAssets( wcsAssets ) )
 			.catch( () => {
-				this.setState( { wcsSetupError: true } );
+				this.setState( {
+					wcsSetupError: true,
+					wcsAssetsLoading: false,
+					isShippingLabelButtonBusy: false,
+					infoMessage: null,
+				} );
 			} );
 	};

@@ -255,7 +286,7 @@ export class ShippingBanner extends Component {
 			.querySelectorAll( 'link[href*="/woocommerce-services/"]' )
 			.forEach( ( node ) => node.remove?.() );

-		Promise.all( [
+		return Promise.all( [
 			new Promise( ( resolve, reject ) => {
 				const script = document.createElement( 'script' );
 				script.src = jsPath;
@@ -406,6 +437,11 @@ export class ShippingBanner extends Component {
 		}

 		const { actionButtonLabel, headline } = this.props;
+		const { isPluginInstalledAndActivated, infoMessage } = this.state;
+		const buttonLabel = isPluginInstalledAndActivated
+			? __( 'Reload page', 'woocommerce' )
+			: actionButtonLabel;
+
 		return (
 			<div>
 				<div className="wc-admin-shipping-banner-container">
@@ -416,41 +452,44 @@ export class ShippingBanner extends Component {
 					/>
 					<div className="wc-admin-shipping-banner-blob">
 						<h3>{ headline }</h3>
-						<p>
-							{ interpolateComponents( {
-								mixedString: sprintf(
-									// translators: %s is the action button label.
-									__(
-										'By clicking "%s", {{wcsLink}}WooCommerce Shipping{{/wcsLink}} will be installed and you agree to its {{tosLink}}Terms of Service{{/tosLink}}.',
-										'woocommerce'
-									),
-									actionButtonLabel
-								),
-								components: {
-									tosLink: (
-										<ExternalLink
-											href="https://wordpress.com/tos"
-											target="_blank"
-											type="external"
-										/>
-									),
-									wcsLink: (
-										<ExternalLink
-											href="https://woocommerce.com/products/shipping/?utm_medium=product"
-											target="_blank"
-											type="external"
-											onClick={
-												this
-													.woocommerceServiceLinkClicked
-											}
-										/>
+						{ ! isPluginInstalledAndActivated && (
+							<p>
+								{ interpolateComponents( {
+									mixedString: sprintf(
+										// translators: %s is the action button label.
+										__(
+											'By clicking "%s", {{wcsLink}}WooCommerce Shipping{{/wcsLink}} will be installed and you agree to its {{tosLink}}Terms of Service{{/tosLink}}.',
+											'woocommerce'
+										),
+										actionButtonLabel
 									),
-								},
-							} ) }
-						</p>
+									components: {
+										tosLink: (
+											<ExternalLink
+												href="https://wordpress.com/tos"
+												target="_blank"
+												type="external"
+											/>
+										),
+										wcsLink: (
+											<ExternalLink
+												href="https://woocommerce.com/products/shipping/?utm_medium=product"
+												target="_blank"
+												type="external"
+												onClick={
+													this
+														.woocommerceServiceLinkClicked
+												}
+											/>
+										),
+									},
+								} ) }
+							</p>
+						) }
 						<SetupNotice
 							isSetupError={ this.isSetupError() }
 							errorReason={ this.state.setupErrorReason }
+							infoMessage={ infoMessage }
 						/>
 					</div>
 					<Button
@@ -459,7 +498,7 @@ export class ShippingBanner extends Component {
 						isBusy={ isShippingLabelButtonBusy }
 						onClick={ this.createShippingLabelClicked }
 					>
-						{ actionButtonLabel }
+						{ buttonLabel }
 					</Button>

 					<button
diff --git a/plugins/woocommerce/client/admin/client/wp-admin-scripts/print-shipping-label-banner/shipping-banner/test/index.js b/plugins/woocommerce/client/admin/client/wp-admin-scripts/print-shipping-label-banner/shipping-banner/test/index.js
index 2fdac725720..5e9b2014d32 100644
--- a/plugins/woocommerce/client/admin/client/wp-admin-scripts/print-shipping-label-banner/shipping-banner/test/index.js
+++ b/plugins/woocommerce/client/admin/client/wp-admin-scripts/print-shipping-label-banner/shipping-banner/test/index.js
@@ -71,6 +71,9 @@ describe( 'Tracking clicks in shippingBanner', () => {

 	it( 'should record an event when user clicks "Create shipping label"', async () => {
 		const actionButtonLabel = 'Create shipping label';
+		const activatePluginsMock = jest
+			.fn()
+			.mockResolvedValue( { success: true } );

 		const { getByRole } = render(
 			<ShippingBanner
@@ -79,9 +82,7 @@ describe( 'Tracking clicks in shippingBanner', () => {
 				installPlugins={ jest
 					.fn()
 					.mockResolvedValue( { success: true } ) }
-				activatePlugins={ jest
-					.fn()
-					.mockResolvedValue( { success: true } ) }
+				activatePlugins={ activatePluginsMock }
 				isRequesting={ false }
 				itemsCount={ 1 }
 				orderId={ 1 }
@@ -99,9 +100,10 @@ describe( 'Tracking clicks in shippingBanner', () => {
 			)
 		);

-		// Wait for any async calls to complete so we don't get `Warning: An update to ShippingBanner inside a test was not wrapped in act(...).
 		await waitFor( () => {
-			expect( acceptWcsTos ).toHaveBeenCalled();
+			expect( activatePluginsMock ).toHaveBeenCalledWith( [
+				wcsPluginSlug,
+			] );
 		} );
 	} );

@@ -172,10 +174,12 @@ describe( 'Create shipping label button', () => {
 	delete window.location; // jsdom won't allow to rewrite window.location unless deleted first
 	window.location = {
 		href: 'http://wcship.test/wp-admin/post.php?post=1000&action=edit',
+		reload: jest.fn(),
 	};

 	beforeEach( () => {
 		acceptWcsTos.mockClear();
+		window.location.reload.mockClear();
 	} );

 	it( 'should install WooCommerce Shipping when button is clicked', async () => {
@@ -201,11 +205,6 @@ describe( 'Create shipping label button', () => {
 			} )
 		);

-		// Wait for any async calls to complete so we don't get `Warning: An update to ShippingBanner inside a test was not wrapped in act(...).
-		await waitFor( () => {
-			expect( acceptWcsTos ).toHaveBeenCalled();
-		} );
-
 		await waitFor( () =>
 			expect( installPlugins ).toHaveBeenCalledWith( [
 				'woocommerce-shipping',
@@ -213,9 +212,9 @@ describe( 'Create shipping label button', () => {
 		);
 	} );

-	it( 'should activate WooCommerce Shipping when installation finishes', async () => {
+	it( 'should show info notice and switch button to "Reload page" when installation and activation finishes', async () => {
 		const actionButtonLabel = 'Create shipping label';
-		const { getByRole } = render(
+		const { getByRole, getByText, queryByText } = render(
 			<ShippingBanner
 				isJetpackConnected={ true }
 				activatePlugins={ activatePlugins }
@@ -235,16 +234,31 @@ describe( 'Create shipping label button', () => {
 			} )
 		);

-		// Wait for any async calls to complete so we don't get `Warning: An update to ShippingBanner inside a test was not wrapped in act(...).
-		await waitFor( () => {
-			expect( acceptWcsTos ).toHaveBeenCalled();
-		} );
-
 		await waitFor( () =>
 			expect( activatePlugins ).toHaveBeenCalledWith( [
 				'woocommerce-shipping',
 			] )
 		);
+
+		await waitFor( () =>
+			expect(
+				getByText(
+					'WooCommerce Shipping is installed and activated. Please reload the page to get started.'
+				)
+			).toBeInTheDocument()
+		);
+
+		expect(
+			queryByText( /By clicking "Create shipping label"/i )
+		).not.toBeInTheDocument();
+
+		const reloadButton = getByRole( 'button', { name: 'Reload page' } );
+		expect( reloadButton ).toBeInTheDocument();
+		expect( reloadButton ).not.toHaveClass( 'is-busy' );
+
+		userEvent.click( reloadButton );
+
+		expect( window.location.reload ).toHaveBeenCalledWith( true );
 	} );

 	it( 'should perform a request to accept the TOS and get WCS assets to load', async () => {
@@ -256,7 +270,7 @@ describe( 'Create shipping label button', () => {
 			<ShippingBanner
 				isJetpackConnected={ true }
 				activatePlugins={ activatePlugins }
-				activePlugins={ [ wcstPluginSlug ] }
+				activePlugins={ [ wcsPluginSlug ] }
 				installPlugins={ installPlugins }
 				isRequesting={ false }
 				itemsCount={ 1 }
@@ -301,7 +315,7 @@ describe( 'Create shipping label button', () => {
 				<ShippingBanner
 					isJetpackConnected={ true }
 					activatePlugins={ activatePlugins }
-					activePlugins={ [ wcstPluginSlug, 'jetpack' ] }
+					activePlugins={ [ wcsPluginSlug, 'jetpack' ] }
 					installPlugins={ installPlugins }
 					isRequesting={ false }
 					itemsCount={ 1 }
@@ -362,9 +376,9 @@ describe( 'Create shipping label button', () => {
 				assets: {
 					// Easy to identify string in our hijacked setter function.
 					wcshipping_create_label_script:
-						'wcshipping_create_label_script',
+						'/wcshipping_create_label_script',
 					wcshipping_shipment_tracking_script:
-						'wcshipping_create_label_script',
+						'/wcshipping_shipment_tracking_script',
 					// Empty string to avoid creating a script tag we also have to hijack.
 					wcshipping_create_label_style: '',
 					wcshipping_shipment_tracking_style: '',
@@ -372,16 +386,17 @@ describe( 'Create shipping label button', () => {
 			} )
 		);

-		// Force the script tag to trigger its onload().
-		// Adapted from https://stackoverflow.com/a/49204336.
-		// const scriptSrcProperty = window.HTMLScriptElement.prototype.src;
+		const originalSrc = Object.getOwnPropertyDescriptor(
+			window.HTMLScriptElement.prototype,
+			'src'
+		);
+
 		Object.defineProperty( window.HTMLScriptElement.prototype, 'src', {
+			configurable: true,
 			set( src ) {
 				if (
-					[
-						'wcshipping_create_label_script',
-						'wcshipping_shipment_tracking_script',
-					].includes( src )
+					src.includes( 'wcshipping_create_label_script' ) ||
+					src.includes( 'wcshipping_shipment_tracking_script' )
 				) {
 					setTimeout( () => {
 						this.onload();
@@ -428,6 +443,16 @@ describe( 'Create shipping label button', () => {
 		} );

 		expect( openWcsModalSpy ).toHaveBeenCalledTimes( 1 );
+
+		if ( originalSrc ) {
+			Object.defineProperty(
+				window.HTMLScriptElement.prototype,
+				'src',
+				originalSrc
+			);
+		} else {
+			delete window.HTMLScriptElement.prototype.src;
+		}
 	} );
 } );

@@ -439,8 +464,10 @@ describe( 'In the process of installing, activating, loading assets for WooComme
 				isJetpackConnected={ true }
 				activatePlugins={ jest.fn() }
 				activePlugins={ [ 'jetpack' ] }
-				installPlugins={ jest.fn() }
-				isRequesting={ true }
+				installPlugins={ jest
+					.fn()
+					.mockImplementation( () => new Promise( () => {} ) ) }
+				isRequesting={ false }
 				itemsCount={ 1 }
 				orderId={ 1 }
 				isWcstCompatible={ true }
@@ -520,6 +547,9 @@ describe( 'Setup error message', () => {
 				)
 			).toBeInTheDocument()
 		);
+		expect(
+			getByRole( 'button', { name: actionButtonLabel } )
+		).not.toHaveClass( 'is-busy' );
 	} );

 	it( 'should show if there is activation error', async () => {
@@ -552,6 +582,255 @@ describe( 'Setup error message', () => {
 				)
 			).toBeInTheDocument()
 		);
+		expect(
+			getByRole( 'button', { name: actionButtonLabel } )
+		).not.toHaveClass( 'is-busy' );
+	} );
+
+	it( 'should clear busy state and show error if setup API call fails', async () => {
+		acceptWcsTos.mockRejectedValueOnce( new Error( 'API Error' ) );
+		const actionButtonLabel = 'Create shipping label';
+
+		const { getByRole, getByText } = render(
+			<ShippingBanner
+				isJetpackConnected={ true }
+				activatePlugins={ jest.fn().mockReturnValue( {
+					success: true,
+				} ) }
+				activePlugins={ [ wcsPluginSlug, 'jetpack' ] }
+				installPlugins={ jest.fn().mockReturnValue( {
+					success: true,
+				} ) }
+				itemsCount={ 1 }
+				isRequesting={ false }
+				orderId={ 1 }
+				isWcstCompatible={ true }
+				actionButtonLabel={ actionButtonLabel }
+			/>
+		);
+
+		userEvent.click( getByRole( 'button', { name: actionButtonLabel } ) );
+
+		await waitFor( () =>
+			expect(
+				getByText(
+					'Unable to set up the plugin. Refresh the page and try again.'
+				)
+			).toBeInTheDocument()
+		);
+		expect(
+			getByRole( 'button', { name: actionButtonLabel } )
+		).not.toHaveClass( 'is-busy' );
+	} );
+
+	it( 'should clear busy state and show error if installPlugins throws an error', async () => {
+		const actionButtonLabel = 'Create shipping label';
+
+		const { getByRole, getByText } = render(
+			<ShippingBanner
+				isJetpackConnected={ true }
+				activatePlugins={ jest.fn() }
+				activePlugins={ [ 'jetpack' ] }
+				installPlugins={ jest
+					.fn()
+					.mockRejectedValue( new Error( 'Network error' ) ) }
+				itemsCount={ 1 }
+				isRequesting={ false }
+				orderId={ 1 }
+				isWcstCompatible={ true }
+				actionButtonLabel={ actionButtonLabel }
+			/>
+		);
+
+		userEvent.click( getByRole( 'button', { name: actionButtonLabel } ) );
+
+		await waitFor( () =>
+			expect(
+				getByText(
+					'Unable to install the plugin. Refresh the page and try again.'
+				)
+			).toBeInTheDocument()
+		);
+		expect(
+			getByRole( 'button', { name: actionButtonLabel } )
+		).not.toHaveClass( 'is-busy' );
+	} );
+
+	it( 'should clear busy state and show error if activatePlugins throws an error', async () => {
+		const actionButtonLabel = 'Create shipping label';
+
+		const { getByRole, getByText } = render(
+			<ShippingBanner
+				isJetpackConnected={ true }
+				activatePlugins={ jest
+					.fn()
+					.mockRejectedValue( new Error( 'Activation failed' ) ) }
+				activePlugins={ [ 'jetpack' ] }
+				installPlugins={ jest.fn().mockReturnValue( {
+					success: true,
+				} ) }
+				itemsCount={ 1 }
+				isRequesting={ false }
+				orderId={ 1 }
+				isWcstCompatible={ true }
+				actionButtonLabel={ actionButtonLabel }
+			/>
+		);
+
+		userEvent.click( getByRole( 'button', { name: actionButtonLabel } ) );
+
+		await waitFor( () =>
+			expect(
+				getByText(
+					'Unable to activate the plugin. Refresh the page and try again.'
+				)
+			).toBeInTheDocument()
+		);
+		expect(
+			getByRole( 'button', { name: actionButtonLabel } )
+		).not.toHaveClass( 'is-busy' );
+	} );
+
+	it( 'should clear busy state and show error if installPlugins returns null or undefined', async () => {
+		const actionButtonLabel = 'Create shipping label';
+
+		const { getByRole, getByText } = render(
+			<ShippingBanner
+				isJetpackConnected={ true }
+				activatePlugins={ jest.fn() }
+				activePlugins={ [ 'jetpack' ] }
+				installPlugins={ jest.fn().mockResolvedValue( undefined ) }
+				itemsCount={ 1 }
+				isRequesting={ false }
+				orderId={ 1 }
+				isWcstCompatible={ true }
+				actionButtonLabel={ actionButtonLabel }
+			/>
+		);
+
+		userEvent.click( getByRole( 'button', { name: actionButtonLabel } ) );
+
+		await waitFor( () =>
+			expect(
+				getByText(
+					'Unable to install the plugin. Refresh the page and try again.'
+				)
+			).toBeInTheDocument()
+		);
+		expect(
+			getByRole( 'button', { name: actionButtonLabel } )
+		).not.toHaveClass( 'is-busy' );
+	} );
+
+	it( 'should clear busy state and show error if activatePlugins returns null or undefined', async () => {
+		const actionButtonLabel = 'Create shipping label';
+
+		const { getByRole, getByText } = render(
+			<ShippingBanner
+				isJetpackConnected={ true }
+				activatePlugins={ jest.fn().mockResolvedValue( undefined ) }
+				activePlugins={ [ 'jetpack' ] }
+				installPlugins={ jest.fn().mockReturnValue( {
+					success: true,
+				} ) }
+				itemsCount={ 1 }
+				isRequesting={ false }
+				orderId={ 1 }
+				isWcstCompatible={ true }
+				actionButtonLabel={ actionButtonLabel }
+			/>
+		);
+
+		userEvent.click( getByRole( 'button', { name: actionButtonLabel } ) );
+
+		await waitFor( () =>
+			expect(
+				getByText(
+					'Unable to activate the plugin. Refresh the page and try again.'
+				)
+			).toBeInTheDocument()
+		);
+		expect(
+			getByRole( 'button', { name: actionButtonLabel } )
+		).not.toHaveClass( 'is-busy' );
+	} );
+
+	it( 'should not set busy state when isRequesting is true', () => {
+		const actionButtonLabel = 'Create shipping label';
+		const installPluginsMock = jest.fn();
+
+		const { getByRole } = render(
+			<ShippingBanner
+				isJetpackConnected={ true }
+				activatePlugins={ jest.fn() }
+				activePlugins={ [] }
+				installPlugins={ installPluginsMock }
+				itemsCount={ 1 }
+				isRequesting={ true }
+				orderId={ 1 }
+				isWcstCompatible={ true }
+				actionButtonLabel={ actionButtonLabel }
+			/>
+		);
+
+		userEvent.click( getByRole( 'button', { name: actionButtonLabel } ) );
+
+		expect(
+			getByRole( 'button', { name: actionButtonLabel } )
+		).not.toHaveClass( 'is-busy' );
+		expect( installPluginsMock ).not.toHaveBeenCalled();
+	} );
+
+	it( 'should reset wcsAssetsLoading to false when asset loading fails so retry attempts to load assets again', async () => {
+		const actionButtonLabel = 'Create shipping label';
+		getWcsLabelPurchaseConfigs.mockResolvedValue( {} );
+		getWcsAssets.mockResolvedValue( {
+			assets: {
+				wcshipping_create_label_script: '/path/to/fail.js',
+				wcshipping_create_label_style: '',
+				wcshipping_shipment_tracking_script:
+					'/path/to/fail-tracking.js',
+				wcshipping_shipment_tracking_style: '',
+			},
+		} );
+
+		// Simulate asset loading error
+		const loadWcsAssetsSpy = jest
+			.spyOn( ShippingBanner.prototype, 'loadWcsAssets' )
+			.mockRejectedValueOnce( new Error( 'Failed to load script' ) );
+
+		const { getByRole, getByText } = render(
+			<Fragment>
+				<div id="woocommerce-order-data" />
+				<div id="woocommerce-order-actions" />
+				<ShippingBanner
+					isJetpackConnected={ true }
+					activatePlugins={ jest.fn() }
+					activePlugins={ [ wcsPluginSlug, 'jetpack' ] }
+					installPlugins={ jest.fn() }
+					itemsCount={ 1 }
+					isRequesting={ false }
+					orderId={ 1 }
+					isWcstCompatible={ true }
+					actionButtonLabel={ actionButtonLabel }
+				/>
+			</Fragment>
+		);
+
+		userEvent.click( getByRole( 'button', { name: actionButtonLabel } ) );
+
+		await waitFor( () =>
+			expect(
+				getByText(
+					'Unable to set up the plugin. Refresh the page and try again.'
+				)
+			).toBeInTheDocument()
+		);
+		expect(
+			getByRole( 'button', { name: actionButtonLabel } )
+		).not.toHaveClass( 'is-busy' );
+
+		loadWcsAssetsSpy.mockRestore();
 	} );
 } );

@@ -626,7 +905,7 @@ describe( 'If incompatible WCS&T is active', () => {
 	it( 'should install and activate but show an error notice when an incompatible version of WCS&T is installed', async () => {
 		const actionButtonLabel = 'Install WooCommerce Shipping';

-		const { getByRole, findByText } = render(
+		const { getByRole } = render(
 			<Fragment>
 				<div id="woocommerce-order-data" />
 				<div id="woocommerce-order-actions" />
@@ -658,22 +937,11 @@ describe( 'If incompatible WCS&T is active', () => {
 			expect( acceptWcsTos ).not.toHaveBeenCalled();
 		} );

-		const notice = await findByText( ( _, element ) => {
-			const hasText = ( node ) =>
-				node.textContent ===
-				'Please update the WooCommerce Shipping & Tax plugin to the latest version to ensure compatibility with WooCommerce Shipping.';
-			const nodeHasText = hasText( element );
-			const childrenDontHaveText = Array.from( element.children ).every(
-				( child ) => ! hasText( child )
-			);
-			return nodeHasText && childrenDontHaveText;
-		} );
-
-		await waitFor( () => expect( notice ).toBeInTheDocument() );
+		const reloadButton = await waitFor( () =>
+			getByRole( 'button', { name: 'Reload page' } )
+		);
+		userEvent.click( reloadButton );

-		// Assert that the "update" link is present
-		const updateLink = await findByText( /update/i );
-		expect( updateLink ).toBeInTheDocument();
-		expect( updateLink.tagName ).toBe( 'A' ); // Ensures it's a link
+		expect( window.location.reload ).toHaveBeenCalledWith( true );
 	} );
 } );
diff --git a/plugins/woocommerce/client/admin/client/wp-admin-scripts/print-shipping-label-banner/style.scss b/plugins/woocommerce/client/admin/client/wp-admin-scripts/print-shipping-label-banner/style.scss
index 60f359098c9..6a1fb34cab1 100644
--- a/plugins/woocommerce/client/admin/client/wp-admin-scripts/print-shipping-label-banner/style.scss
+++ b/plugins/woocommerce/client/admin/client/wp-admin-scripts/print-shipping-label-banner/style.scss
@@ -63,6 +63,17 @@
 			}
 		}
 	}
+	.wc-admin-shipping-banner-install-info {
+		color: #007cba;
+		> .info-icon {
+			margin-right: 3px;
+			vertical-align: middle;
+			margin-top: -2px;
+			path {
+				fill: #007cba;
+			}
+		}
+	}
 	.components-external-link__icon {
 		display: none;
 	}