Commit 065e18d741 for woocommerce

commit 065e18d741861ac58a9bae6ed94966fe2a7a3397
Author: Karol Manijak <20098064+kmanijak@users.noreply.github.com>
Date:   Fri Jan 30 18:55:50 2026 +0100

    Products by Tag/Category: journey starts with taxonomy picker (#62993)

    * Foundation for product picker

    * Updated handpicked product picker

    * Move the collection specific controls to the top of filters

    * Add changelog

    * Update docs etc

    * Bring back the comment

    * Prefix editor side CSS classes with .wc-block-editor-

    * Rename product picker files to be less confusing

    * Make Done button always visible but only disabled when no products is chosen

    * Update selector in E2E tests

    * Rename files to follow kebab-case convention

    * Introduce taxonomy pickers

    * Introduce taxonomy picker

    * Introduce Product brand picker and use it in Product Collection

    * Add changelog

    * Make Brand picker more aliogned with Categories than Tags

    * Revert By Brands related code so it can be implemented in separate PR

    * Update changelog

    * Simplify picker state handling

    * Change the file name to fol,low kebab-case

    * Add by brand filter on the top of inspector controls

    * Get rid of addFilter injecting collection specific filter since we moved this to the top anyway

    * Refactor colelction specific controls

    * Minor refactor

    * Remove unused references

    * Improve the styles of search list controlk

    * Add E2E tests

    * Adopt E2E tests to real life

diff --git a/plugins/woocommerce/changelog/add-58403-with-taxonomies b/plugins/woocommerce/changelog/add-58403-with-taxonomies
new file mode 100644
index 0000000000..0325ac332f
--- /dev/null
+++ b/plugins/woocommerce/changelog/add-58403-with-taxonomies
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Products by Tag/Category: journey starts with terms picker
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/index.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/index.tsx
index 115bed117d..875af68892 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/index.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/index.tsx
@@ -22,6 +22,9 @@ import CollectionSelectionModal from './collection-selection-modal';
 import { useProductCollectionUIState } from '../utils';
 import SingleProductPicker from './single-product-picker';
 import MultiProductPicker from './multi-product-picker';
+import TaxonomyPicker, {
+	getTaxonomySlugForCollection,
+} from './taxonomy-picker';
 import { useTracksLocation } from '../tracks-utils';
 import { useRegisterEmailCollections } from '../hooks/use-register-email-collections';

@@ -35,22 +38,40 @@ const Edit = ( props: ProductCollectionEditComponentProps ) => {

 	const [ isSelectionModalOpen, setIsSelectionModalOpen ] = useState( false );

-	// Track if the hand-picked products picker is active.
+	// Track if the collection-specific picker is active (Hand-Picked or Taxonomy).
 	// This allows multi-select before clicking "Done".
-	const [ isHandPickedPickerActive, setIsHandPickedPickerActive ] =
-		useState( false );
+	// Only one picker can be active at a time since collections are mutually exclusive.
+	const [ isPickerActive, setIsPickerActive ] = useState( false );

 	const isHandPickedCollection =
 		attributes.collection === CoreCollectionNames.HAND_PICKED;
 	const hasHandPickedProducts =
 		( attributes.query?.woocommerceHandPickedProducts?.length ?? 0 ) > 0;

-	// Activate the picker when Hand-Picked collection is selected with no products
+	const isTaxonomyCollection =
+		attributes.collection === CoreCollectionNames.BY_CATEGORY ||
+		attributes.collection === CoreCollectionNames.BY_TAG;
+
+	const taxonomySlug = getTaxonomySlugForCollection( attributes.collection );
+	const hasSelectedTerms = taxonomySlug
+		? ( attributes.query?.taxQuery?.[ taxonomySlug ]?.length ?? 0 ) > 0
+		: false;
+
+	// Activate the picker when a collection needs initial selection
 	useEffect( () => {
 		if ( isHandPickedCollection && ! hasHandPickedProducts ) {
-			setIsHandPickedPickerActive( true );
+			setIsPickerActive( true );
+		} else if ( isTaxonomyCollection && ! hasSelectedTerms ) {
+			setIsPickerActive( true );
 		}
-	}, [ isHandPickedCollection, hasHandPickedProducts ] );
+	}, [
+		isHandPickedCollection,
+		hasHandPickedProducts,
+		isTaxonomyCollection,
+		hasSelectedTerms,
+	] );
+
+	const dismissPicker = () => setIsPickerActive( false );

 	const hasInnerBlocks = useSelect(
 		( select ) =>
@@ -85,18 +106,19 @@ const Edit = ( props: ProductCollectionEditComponentProps ) => {
 	};

 	const renderComponent = () => {
-		// Show the hand-picked products picker if it's active (local state).
+		// Show the collection-specific picker if it's active (local state).
 		// This allows multi-select before clicking "Done".
-		// The inspector controls (HandPickedProductsControlField) are inside
-		// ProductCollectionContent, so they're automatically hidden while
-		// the picker is shown.
-		if ( isHandPickedCollection && isHandPickedPickerActive ) {
-			return (
-				<MultiProductPicker
-					{ ...props }
-					onDone={ () => setIsHandPickedPickerActive( false ) }
-				/>
-			);
+		// The inspector controls are inside ProductCollectionContent,
+		// so they're automatically hidden while the picker is shown.
+		if ( isPickerActive ) {
+			if ( isHandPickedCollection ) {
+				return (
+					<MultiProductPicker { ...props } onDone={ dismissPicker } />
+				);
+			}
+			if ( isTaxonomyCollection ) {
+				return <TaxonomyPicker { ...props } onDone={ dismissPicker } />;
+			}
 		}

 		switch ( productCollectionUIStateInEditor ) {
@@ -125,11 +147,12 @@ const Edit = ( props: ProductCollectionEditComponentProps ) => {
 				// This case is hit when no products are selected
 				// and the picker was previously dismissed but products were removed
 				return (
-					<MultiProductPicker
-						{ ...props }
-						onDone={ () => setIsHandPickedPickerActive( false ) }
-					/>
+					<MultiProductPicker { ...props } onDone={ dismissPicker } />
 				);
+			case ProductCollectionUIStatesInEditor.TAXONOMY_PICKER:
+				// This case is hit when no taxonomy terms are selected
+				// and the picker was previously dismissed but terms were removed
+				return <TaxonomyPicker { ...props } onDone={ dismissPicker } />;
 			case ProductCollectionUIStatesInEditor.VALID:
 			case ProductCollectionUIStatesInEditor.VALID_WITH_PREVIEW:
 				return (
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/inspector-controls/index.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/inspector-controls/index.tsx
index 8b7f55829f..f2c8e8f4b3 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/inspector-controls/index.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/inspector-controls/index.tsx
@@ -4,8 +4,6 @@
 import { InspectorControls } from '@wordpress/block-editor';
 import { __ } from '@wordpress/i18n';
 import { useMemo } from '@wordpress/element';
-import { EditorBlock } from '@woocommerce/types';
-import { addFilter } from '@wordpress/hooks';
 import { useIsEmailEditor } from '@woocommerce/email-editor';
 import { recordEvent } from '@woocommerce/tracks';
 import { CesFeedbackButton } from '@woocommerce/editor-components/ces-feedback-button';
@@ -21,7 +19,6 @@ import {
 import metadata from '../../block.json';
 import { useTracksLocation } from '../../tracks-utils';
 import {
-	ProductCollectionEditComponentProps,
 	ProductCollectionContentProps,
 	CoreFilterNames,
 	FilterName,
@@ -150,6 +147,39 @@ const ProductCollectionInspectorControls = (
 		query,
 	};

+	/**
+	 * Renders the collection-specific control based on the collection type.
+	 * These controls are placed at the top for easy access when editing.
+	 */
+	const renderCollectionSpecificControl = () => {
+		switch ( collection ) {
+			case CoreCollectionNames.HAND_PICKED:
+				return (
+					<PanelBody>
+						<HandPickedProductsControlField
+							{ ...queryControlProps }
+						/>
+					</PanelBody>
+				);
+			case CoreCollectionNames.BY_CATEGORY:
+			case CoreCollectionNames.BY_TAG:
+			case CoreCollectionNames.BY_BRAND:
+				return (
+					<PanelBody>
+						<TaxonomyControls
+							{ ...queryControlProps }
+							collection={ collection }
+							renderMode="standalone"
+						/>
+					</PanelBody>
+				);
+			case CoreCollectionNames.RELATED:
+				return <RelatedByControl { ...queryControlProps } />;
+			default:
+				return null;
+		}
+	};
+
 	return (
 		<InspectorControls>
 			<LinkedProductControl
@@ -159,20 +189,7 @@ const ProductCollectionInspectorControls = (
 				location={ props.location }
 			/>

-			{
-				/**
-				 * "Hand-Picked" collection-specific control.
-				 * Placed at the top for easy access when editing product selection.
-				 * Only rendered when ProductCollectionContent is shown (not during picker).
-				 */
-				collection === CoreCollectionNames.HAND_PICKED && (
-					<PanelBody>
-						<HandPickedProductsControlField
-							{ ...queryControlProps }
-						/>
-					</PanelBody>
-				)
-			}
+			{ renderCollectionSpecificControl() }

 			<ToolsPanel
 				label={ __( 'Settings', 'woocommerce' ) }
@@ -273,83 +290,3 @@ const ProductCollectionInspectorControls = (
 };

 export default ProductCollectionInspectorControls;
-
-const isProductCollection = ( blockName: string ) =>
-	blockName === metadata.name;
-
-const CollectionSpecificControls = (
-	props: ProductCollectionEditComponentProps
-) => {
-	const { attributes, context } = props;
-	const { collection } = attributes;
-
-	const setQueryAttributeBind = useMemo(
-		() => setQueryAttribute.bind( null, props ),
-		[ props ]
-	);
-	const tracksLocation = useTracksLocation( context.templateSlug );
-	const trackInteraction = ( filter: FilterName ) => {
-		return recordEvent(
-			'blocks_product_collection_inspector_control_clicked',
-			{
-				collection,
-				location: tracksLocation,
-				filter,
-			}
-		);
-	};
-	const queryControlProps = {
-		setQueryAttribute: setQueryAttributeBind,
-		trackInteraction,
-		query: attributes.query,
-	};
-
-	const isByTaxonomy =
-		collection === CoreCollectionNames.BY_CATEGORY ||
-		collection === CoreCollectionNames.BY_TAG ||
-		collection === CoreCollectionNames.BY_BRAND;
-
-	return (
-		<InspectorControls>
-			{
-				/**
-				 * "Related Products" collection-specific controls.
-				 */
-				collection === CoreCollectionNames.RELATED && (
-					<RelatedByControl { ...queryControlProps } />
-				)
-			}
-			{
-				/**
-				 * "By Taxonomy" collection-specific controls.
-				 */
-				isByTaxonomy && (
-					<PanelBody>
-						<TaxonomyControls
-							{ ...queryControlProps }
-							collection={ collection }
-							renderMode="standalone"
-						/>
-					</PanelBody>
-				)
-			}
-		</InspectorControls>
-	);
-};
-
-const withCollectionSpecificControls =
-	< T extends EditorBlock< T > >( BlockEdit: ElementType ) =>
-	( props: ProductCollectionEditComponentProps ) => {
-		if ( ! isProductCollection( props.name ) ) {
-			return <BlockEdit { ...props } />;
-		}
-
-		return (
-			<>
-				<CollectionSpecificControls { ...props } />
-				<BlockEdit { ...props } />
-			</>
-		);
-	};
-
-addFilter( 'editor.BlockEdit', metadata.name, withCollectionSpecificControls );
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/taxonomy-picker.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/taxonomy-picker.tsx
new file mode 100644
index 0000000000..7694942df9
--- /dev/null
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/taxonomy-picker.tsx
@@ -0,0 +1,166 @@
+/**
+ * External dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { Icon, category, tag } from '@wordpress/icons';
+import { Placeholder, Button } from '@wordpress/components';
+import { useBlockProps } from '@wordpress/block-editor';
+import ProductTagControl from '@woocommerce/editor-components/product-tag-control';
+import ProductCategoryControl from '@woocommerce/editor-components/product-category-control';
+
+/**
+ * Internal dependencies
+ */
+import type { ProductCollectionEditComponentProps } from '../types';
+import { CoreCollectionNames } from '../types';
+import { getCollectionByName } from '../collections';
+import { setQueryAttribute } from '../utils';
+
+interface TaxonomyPickerProps extends ProductCollectionEditComponentProps {
+	onDone: () => void;
+}
+
+/**
+ * Get the taxonomy slug for a given collection.
+ */
+export const getTaxonomySlugForCollection = (
+	collection: string | undefined
+): string | null => {
+	switch ( collection ) {
+		case CoreCollectionNames.BY_CATEGORY:
+			return 'product_cat';
+		case CoreCollectionNames.BY_TAG:
+			return 'product_tag';
+		default:
+			return null;
+	}
+};
+
+/**
+ * Get the description text for a given collection.
+ */
+const getDescriptionForCollection = (
+	collection: string | undefined
+): string => {
+	switch ( collection ) {
+		case CoreCollectionNames.BY_CATEGORY:
+			return __(
+				'Display a grid of products from your selected categories.',
+				'woocommerce'
+			);
+		case CoreCollectionNames.BY_TAG:
+			return __(
+				'Display a grid of products from your selected tags.',
+				'woocommerce'
+			);
+		default:
+			return __(
+				'Select taxonomy terms to display products from.',
+				'woocommerce'
+			);
+	}
+};
+
+/**
+ * Get the icon for a given collection.
+ */
+const getIconForCollection = ( collection: string | undefined ) => {
+	switch ( collection ) {
+		case CoreCollectionNames.BY_CATEGORY:
+			return category;
+		case CoreCollectionNames.BY_TAG:
+			return tag;
+		default:
+			// For brands and others, use category icon as fallback
+			return category;
+	}
+};
+
+const TaxonomyPicker = ( props: TaxonomyPickerProps ) => {
+	const { attributes, onDone } = props;
+	const blockProps = useBlockProps();
+
+	const collectionData = getCollectionByName( attributes.collection );
+	const taxonomySlug = getTaxonomySlugForCollection( attributes.collection );
+
+	// Get selected term IDs for the relevant taxonomy
+	const selectedTermIds: number[] = taxonomySlug
+		? attributes.query?.taxQuery?.[ taxonomySlug ] || []
+		: [];
+
+	const hasSelectedTerms = selectedTermIds.length > 0;
+
+	if ( ! collectionData || ! taxonomySlug ) {
+		return null;
+	}
+
+	const handleTermChange = ( termIds: number[] ) => {
+		setQueryAttribute( props, {
+			taxQuery: {
+				...attributes.query?.taxQuery,
+				[ taxonomySlug ]: termIds,
+			},
+		} );
+	};
+
+	const renderTaxonomyControl = () => {
+		switch ( attributes.collection ) {
+			case CoreCollectionNames.BY_CATEGORY:
+				return (
+					<ProductCategoryControl
+						selected={ selectedTermIds }
+						onChange={ ( value = [] ) => {
+							const ids = value.map(
+								( { id }: { id: number } ) => id
+							);
+							handleTermChange( ids );
+						} }
+					/>
+				);
+			case CoreCollectionNames.BY_TAG:
+				return (
+					<ProductTagControl
+						selected={ selectedTermIds }
+						onChange={ ( value = [] ) => {
+							const ids = value.map(
+								( { id }: { id: number | string } ) =>
+									Number( id )
+							);
+							handleTermChange( ids );
+						} }
+					/>
+				);
+			default:
+				return null;
+		}
+	};
+
+	return (
+		<div { ...blockProps }>
+			<Placeholder
+				icon={
+					// @ts-expect-error Icon types are incomplete
+					<Icon
+						icon={ getIconForCollection( attributes.collection ) }
+						className="block-editor-block-icon"
+					/>
+				}
+				label={ collectionData.title }
+			>
+				{ getDescriptionForCollection( attributes.collection ) }
+				<div className="wc-block-editor-product-collection__taxonomy-picker-selection">
+					{ renderTaxonomyControl() }
+					<Button
+						variant="primary"
+						onClick={ onDone }
+						disabled={ ! hasSelectedTerms }
+					>
+						{ __( 'Done', 'woocommerce' ) }
+					</Button>
+				</div>
+			</Placeholder>
+		</div>
+	);
+};
+
+export default TaxonomyPicker;
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/types.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/types.ts
index e8fab2ec71..92a041f685 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/types.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/types.ts
@@ -13,6 +13,7 @@ export enum ProductCollectionUIStatesInEditor {
 	COLLECTION_PICKER = 'collection_chooser',
 	PRODUCT_REFERENCE_PICKER = 'product_context_picker',
 	HAND_PICKED_PRODUCTS_PICKER = 'hand_picked_products_picker',
+	TAXONOMY_PICKER = 'taxonomy_picker',
 	VALID_WITH_PREVIEW = 'uses_reference_preview_mode',
 	VALID = 'valid',
 	DELETED_PRODUCT_REFERENCE = 'deleted_product_reference',
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/utils.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/utils.tsx
index 6c270f3462..988b73f885 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/utils.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/utils.tsx
@@ -285,7 +285,30 @@ export const useProductCollectionUIState = ( {
 		}

 		/**
-		 * Case 4: Preview mode - based on `usesReference` value
+		 * Case 4: Taxonomy picker for BY_CATEGORY, BY_TAG collections
+		 * Show the picker when no taxonomy terms are selected.
+		 */
+		const isTaxonomyCollection =
+			attributes.collection === CoreCollectionNames.BY_CATEGORY ||
+			attributes.collection === CoreCollectionNames.BY_TAG;
+
+		if ( isCollectionSelected && isTaxonomyCollection ) {
+			const taxonomySlug =
+				attributes.collection === CoreCollectionNames.BY_CATEGORY
+					? 'product_cat'
+					: 'product_tag';
+
+			const selectedTermIds =
+				attributes.query?.taxQuery?.[ taxonomySlug ] || [];
+			const hasSelectedTerms = selectedTermIds.length > 0;
+
+			if ( ! hasSelectedTerms ) {
+				return ProductCollectionUIStatesInEditor.TAXONOMY_PICKER;
+			}
+		}
+
+		/**
+		 * Case 5: Preview mode - based on `usesReference` value
 		 */
 		if ( isInRequiredLocation ) {
 			/**
@@ -331,6 +354,7 @@ export const useProductCollectionUIState = ( {
 		hasInnerBlocks,
 		attributes.query?.productReference,
 		attributes.query?.woocommerceHandPickedProducts,
+		attributes.query?.taxQuery,
 	] );

 	return { productCollectionUIStateInEditor, isLoading: ! hasResolved };
diff --git a/plugins/woocommerce/client/blocks/assets/js/editor-components/search-list-control/style.scss b/plugins/woocommerce/client/blocks/assets/js/editor-components/search-list-control/style.scss
index b9476d2543..bf342caa1c 100644
--- a/plugins/woocommerce/client/blocks/assets/js/editor-components/search-list-control/style.scss
+++ b/plugins/woocommerce/client/blocks/assets/js/editor-components/search-list-control/style.scss
@@ -80,13 +80,12 @@
 	}

 	ul {
+		display: flex;
+		flex-wrap: wrap;
+		gap: $gap-smallest;
 		list-style: none;
 		margin: 0;
 		padding: 0;
-
-		li {
-			float: left;
-		}
 	}
 }

diff --git a/plugins/woocommerce/client/blocks/tests/e2e/tests/product-collection/collection-pickers.block_theme.spec.ts b/plugins/woocommerce/client/blocks/tests/e2e/tests/product-collection/collection-pickers.block_theme.spec.ts
new file mode 100644
index 0000000000..3a5ce43c5b
--- /dev/null
+++ b/plugins/woocommerce/client/blocks/tests/e2e/tests/product-collection/collection-pickers.block_theme.spec.ts
@@ -0,0 +1,281 @@
+/**
+ * External dependencies
+ */
+import { test as base, expect } from '@woocommerce/e2e-utils';
+
+/**
+ * Internal dependencies
+ */
+import ProductCollectionPage, {
+	Collections,
+	SELECTORS,
+} from './product-collection.page';
+
+const test = base.extend< { pageObject: ProductCollectionPage } >( {
+	pageObject: async ( { page, admin, editor }, use ) => {
+		const pageObject = new ProductCollectionPage( {
+			page,
+			admin,
+			editor,
+		} );
+		await use( pageObject );
+	},
+} );
+
+/**
+ * Taxonomy-based collections configuration for parameterized tests.
+ */
+const taxonomyCollections: {
+	slug: Collections;
+	name: string;
+	termName: string;
+	termLabel: string;
+	expectedProductCount: number;
+}[] = [
+	{
+		slug: 'productsByCategory',
+		name: 'Products by Category',
+		termName: 'categories',
+		termLabel: 'Accessories',
+		expectedProductCount: 5,
+	},
+	{
+		slug: 'productsByTag',
+		name: 'Products by Tag',
+		termName: 'tags',
+		termLabel: 'Recommended',
+		expectedProductCount: 2,
+	},
+];
+
+test.describe( 'Product Collection: Collection Pickers', () => {
+	test.describe( 'Hand-Picked Products', () => {
+		test( 'Can select multiple products and Done button becomes enabled', async ( {
+			pageObject,
+			admin,
+			editor,
+		} ) => {
+			await admin.createNewPost();
+			await pageObject.insertProductCollection();
+			await pageObject.chooseCollectionInPost( 'handPicked' );
+
+			const productPicker = editor.canvas.locator(
+				SELECTORS.productPicker
+			);
+			const doneButton = productPicker.locator(
+				SELECTORS.pickerDoneButton
+			);
+
+			// Initially disabled
+			await expect( doneButton ).toBeDisabled();
+
+			// Select first product
+			await productPicker.getByText( 'Album (woo-album)' ).click();
+
+			// Done button should now be enabled
+			await expect( doneButton ).toBeEnabled();
+
+			// Select second product
+			await productPicker.getByText( 'Beanie (woo-beanie)' ).click();
+
+			// Click Done
+			await doneButton.click();
+
+			// Picker should be hidden and products should be displayed
+			await expect( productPicker ).toBeHidden();
+			await pageObject.refreshLocators( 'editor' );
+			await expect( pageObject.products ).toHaveCount( 2 );
+		} );
+
+		test( 'Picker is not shown after save and refresh', async ( {
+			pageObject,
+			admin,
+			editor,
+			page,
+		} ) => {
+			await admin.createNewPost();
+			await pageObject.insertProductCollection();
+			await pageObject.chooseCollectionInPost( 'handPicked' );
+
+			// Select a product and click Done
+			const productPicker = editor.canvas.locator(
+				SELECTORS.productPicker
+			);
+			await productPicker.getByText( 'Album (woo-album)' ).click();
+			await productPicker.locator( SELECTORS.pickerDoneButton ).click();
+
+			// Save and refresh
+			await editor.saveDraft();
+			await page.reload();
+			await editor.canvas.locator( 'body' ).waitFor();
+
+			// Click on the block to select it
+			await editor.canvas
+				.locator( '[data-type="woocommerce/product-collection"]' )
+				.first()
+				.click();
+
+			// Picker should not be shown
+			const pickerAfterRefresh = editor.canvas.locator(
+				SELECTORS.productPicker
+			);
+			await expect( pickerAfterRefresh ).toBeHidden();
+
+			// Products should be visible
+			await pageObject.refreshLocators( 'editor' );
+			await expect( pageObject.products ).toHaveCount( 1 );
+		} );
+
+		test( 'Products are displayed on frontend', async ( {
+			pageObject,
+			admin,
+			editor,
+		} ) => {
+			await admin.createNewPost();
+			await pageObject.insertProductCollection();
+			await pageObject.chooseCollectionInPost( 'handPicked' );
+
+			// Select products and click Done
+			const productPicker = editor.canvas.locator(
+				SELECTORS.productPicker
+			);
+			await productPicker.getByText( 'Album (woo-album)' ).click();
+			await productPicker.getByText( 'Beanie (woo-beanie)' ).click();
+			await productPicker.locator( SELECTORS.pickerDoneButton ).click();
+
+			await pageObject.refreshLocators( 'editor' );
+			await pageObject.publishAndGoToFrontend();
+			await expect( pageObject.products ).toHaveCount( 2 );
+		} );
+	} );
+
+	for ( const collection of taxonomyCollections ) {
+		test.describe( `${ collection.name }`, () => {
+			test( `Can select ${ collection.termName } and Done button becomes enabled`, async ( {
+				pageObject,
+				admin,
+				editor,
+			} ) => {
+				await admin.createNewPost();
+				await pageObject.insertProductCollection();
+				await pageObject.chooseCollectionInPost( collection.slug );
+
+				const taxonomyPicker = editor.canvas.locator(
+					SELECTORS.taxonomyPicker
+				);
+				const doneButton = taxonomyPicker.locator(
+					SELECTORS.pickerDoneButton
+				);
+
+				// Initially disabled
+				await expect( doneButton ).toBeDisabled();
+
+				// Select a term
+				await taxonomyPicker
+					.getByRole( 'checkbox', { name: collection.termLabel } )
+					.click();
+
+				// Done button should now be enabled
+				await expect( doneButton ).toBeEnabled();
+
+				// Click Done
+				await doneButton.click();
+
+				// Picker should be hidden and products should be displayed
+				await expect( taxonomyPicker ).toBeHidden();
+				await pageObject.refreshLocators( 'editor' );
+				await expect( pageObject.products ).toHaveCount(
+					collection.expectedProductCount
+				);
+			} );
+
+			test( `Products from selected ${ collection.termName } are displayed on frontend`, async ( {
+				pageObject,
+				admin,
+				editor,
+			} ) => {
+				await admin.createNewPost();
+				await pageObject.insertProductCollection();
+				await pageObject.chooseCollectionInPost( collection.slug );
+
+				// Select term and click Done
+				const taxonomyPicker = editor.canvas.locator(
+					SELECTORS.taxonomyPicker
+				);
+				await taxonomyPicker
+					.getByRole( 'checkbox', { name: collection.termLabel } )
+					.click();
+				await taxonomyPicker
+					.locator( SELECTORS.pickerDoneButton )
+					.click();
+
+				await pageObject.refreshLocators( 'editor' );
+				await pageObject.publishAndGoToFrontend();
+				await expect( pageObject.products ).toHaveCount(
+					collection.expectedProductCount
+				);
+			} );
+		} );
+	}
+
+	test.describe( 'Collection switching', () => {
+		test( 'Switching from Hand-Picked to Products by Category shows taxonomy picker', async ( {
+			pageObject,
+			admin,
+			editor,
+		} ) => {
+			await admin.createNewPost();
+			await pageObject.insertProductCollection();
+			await pageObject.chooseCollectionInPost( 'handPicked' );
+
+			// Select a product and click Done
+			const productPicker = editor.canvas.locator(
+				SELECTORS.productPicker
+			);
+			await productPicker.getByText( 'Album (woo-album)' ).click();
+			await productPicker.locator( SELECTORS.pickerDoneButton ).click();
+
+			// Switch to Products by Category using toolbar
+			await pageObject.changeCollectionUsingToolbar(
+				'productsByCategory'
+			);
+
+			// Taxonomy picker should now be shown
+			const taxonomyPicker = editor.canvas.locator(
+				SELECTORS.taxonomyPicker
+			);
+			await expect( taxonomyPicker ).toBeVisible();
+		} );
+
+		test( 'Switching to a non-picker collection displays products immediately', async ( {
+			pageObject,
+			admin,
+			editor,
+		} ) => {
+			await admin.createNewPost();
+			await pageObject.insertProductCollection();
+			await pageObject.chooseCollectionInPost( 'handPicked' );
+
+			// Select a product and click Done
+			const productPicker = editor.canvas.locator(
+				SELECTORS.productPicker
+			);
+			await productPicker.getByText( 'Album (woo-album)' ).click();
+			await productPicker.locator( SELECTORS.pickerDoneButton ).click();
+
+			// Switch to Featured Products (no picker needed)
+			await pageObject.changeCollectionUsingToolbar( 'featured' );
+
+			// No picker should be shown
+			await expect( productPicker ).toBeHidden();
+			const taxonomyPicker = editor.canvas.locator(
+				SELECTORS.taxonomyPicker
+			);
+			await expect( taxonomyPicker ).toBeHidden();
+
+			// Products should be displayed
+			await pageObject.refreshLocators( 'editor' );
+			await expect( pageObject.products ).toHaveCount( 4 );
+		} );
+	} );
+} );
diff --git a/plugins/woocommerce/client/blocks/tests/e2e/tests/product-collection/product-collection.page.ts b/plugins/woocommerce/client/blocks/tests/e2e/tests/product-collection/product-collection.page.ts
index 1d16de918f..9698f8d52d 100644
--- a/plugins/woocommerce/client/blocks/tests/e2e/tests/product-collection/product-collection.page.ts
+++ b/plugins/woocommerce/client/blocks/tests/e2e/tests/product-collection/product-collection.page.ts
@@ -57,6 +57,9 @@ export const SELECTORS = {
 	collectionPlaceholder:
 		'[data-type="woocommerce/product-collection"] .components-placeholder',
 	productPicker: '.wc-block-editor-product-collection__product-picker',
+	taxonomyPicker:
+		'.wc-block-editor-product-collection__taxonomy-picker-selection',
+	pickerDoneButton: '.components-button.is-primary',
 	linkedProductControl: {
 		button: '.wc-block-product-collection-linked-product-control__button',
 		popoverContent:
@@ -71,6 +74,9 @@ export type Collections =
 	| 'onSale'
 	| 'featured'
 	| 'relatedProducts'
+	| 'handPicked'
+	| 'productsByCategory'
+	| 'productsByTag'
 	| 'productCatalog'
 	| 'myCustomCollection'
 	| 'myCustomCollectionWithPreview'
@@ -90,6 +96,9 @@ const collectionToButtonNameMap = {
 	onSale: 'On Sale Products',
 	featured: 'Featured Products',
 	relatedProducts: 'Related Products',
+	handPicked: 'Hand-Picked Products',
+	productsByCategory: 'Products by Category',
+	productsByTag: 'Products by Tag',
 	productCatalog: 'create your own',
 	myCustomCollection: 'My Custom Collection',
 	myCustomCollectionWithPreview: 'My Custom Collection with Preview',