Commit d330bb3bab for woocommerce

commit d330bb3bab3cb4943c42ab73143a6f189a3a0a3b
Author: Karol Manijak <20098064+kmanijak@users.noreply.github.com>
Date:   Mon Feb 2 11:30:48 2026 +0200

    Products by Brand: journey starts with brand picker (#63023)

    * Add API utilities and types for product brands

    Add getBrands() and getBrand() functions to fetch brand data from Store API.
    Add TypeScript types for brand response items and HOC injection.
    Add conversion function to transform brand API response to SearchListItem format.

    * Add withSearchedBrands HOC

    Create HOC for fetching and searching product brands, similar to withSearchedCategories.
    Provides brand list and loading state to wrapped components.

    * Add ProductBrandControl editor component

    Create hierarchical brand selector component similar to ProductCategoryControl.
    Uses SearchListControl with brand search and supports multi-select with operator toggle.

    * Integrate brand picker into Product Collection block

    Add BY_BRAND support to taxonomy picker with immediate picker on block insertion.
    Update UI state logic to show taxonomy picker for brand collections.
    Enable multi-select with Done button for brand selection.

    * Add E2E test support for Products by Brand collection

    Add productsByBrand to Collections type and collectionToButtonNameMap.

    * Add E2E tests for Products by Brand collection picker

    Add WooCommerce brand fixture with Hoodie, Beanie, and Album products.
    Add Products by Brand to parameterized taxonomy picker tests.

    * Refactor types

    * Add changelog

    * Minor improvmeents and guards

    * Simplify brands fetching mechanism

diff --git a/plugins/woocommerce/changelog/add-58403-brands b/plugins/woocommerce/changelog/add-58403-brands
new file mode 100644
index 0000000000..362f6501ee
--- /dev/null
+++ b/plugins/woocommerce/changelog/add-58403-brands
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Products by Brand: journey starts with choosing initial Brands
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 875af68892..02dc165ca3 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
@@ -50,7 +50,8 @@ const Edit = ( props: ProductCollectionEditComponentProps ) => {

 	const isTaxonomyCollection =
 		attributes.collection === CoreCollectionNames.BY_CATEGORY ||
-		attributes.collection === CoreCollectionNames.BY_TAG;
+		attributes.collection === CoreCollectionNames.BY_TAG ||
+		attributes.collection === CoreCollectionNames.BY_BRAND;

 	const taxonomySlug = getTaxonomySlugForCollection( attributes.collection );
 	const hasSelectedTerms = taxonomySlug
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
index 7694942df9..1a5ebd3e39 100644
--- 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
@@ -2,11 +2,12 @@
  * External dependencies
  */
 import { __ } from '@wordpress/i18n';
-import { Icon, category, tag } from '@wordpress/icons';
+import { Icon, category, tag, store } 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';
+import ProductBrandControl from '@woocommerce/editor-components/product-brand-control';

 /**
  * Internal dependencies
@@ -31,6 +32,8 @@ export const getTaxonomySlugForCollection = (
 			return 'product_cat';
 		case CoreCollectionNames.BY_TAG:
 			return 'product_tag';
+		case CoreCollectionNames.BY_BRAND:
+			return 'product_brand';
 		default:
 			return null;
 	}
@@ -53,6 +56,11 @@ const getDescriptionForCollection = (
 				'Display a grid of products from your selected tags.',
 				'woocommerce'
 			);
+		case CoreCollectionNames.BY_BRAND:
+			return __(
+				'Display a grid of products from your selected brands.',
+				'woocommerce'
+			);
 		default:
 			return __(
 				'Select taxonomy terms to display products from.',
@@ -70,8 +78,9 @@ const getIconForCollection = ( collection: string | undefined ) => {
 			return category;
 		case CoreCollectionNames.BY_TAG:
 			return tag;
+		case CoreCollectionNames.BY_BRAND:
+			return store;
 		default:
-			// For brands and others, use category icon as fallback
 			return category;
 	}
 };
@@ -130,6 +139,18 @@ const TaxonomyPicker = ( props: TaxonomyPickerProps ) => {
 						} }
 					/>
 				);
+			case CoreCollectionNames.BY_BRAND:
+				return (
+					<ProductBrandControl
+						selected={ selectedTermIds }
+						onChange={ ( value = [] ) => {
+							const ids = value.map(
+								( { id }: { id: number } ) => id
+							);
+							handleTermChange( ids );
+						} }
+					/>
+				);
 			default:
 				return null;
 		}
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 988b73f885..460b92331d 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,18 +285,29 @@ export const useProductCollectionUIState = ( {
 		}

 		/**
-		 * Case 4: Taxonomy picker for BY_CATEGORY, BY_TAG collections
+		 * Case 4: Taxonomy picker for BY_CATEGORY, BY_TAG, BY_BRAND collections
 		 * Show the picker when no taxonomy terms are selected.
 		 */
 		const isTaxonomyCollection =
 			attributes.collection === CoreCollectionNames.BY_CATEGORY ||
-			attributes.collection === CoreCollectionNames.BY_TAG;
+			attributes.collection === CoreCollectionNames.BY_TAG ||
+			attributes.collection === CoreCollectionNames.BY_BRAND;

 		if ( isCollectionSelected && isTaxonomyCollection ) {
-			const taxonomySlug =
-				attributes.collection === CoreCollectionNames.BY_CATEGORY
-					? 'product_cat'
-					: 'product_tag';
+			let taxonomySlug: string;
+			switch ( attributes.collection ) {
+				case CoreCollectionNames.BY_CATEGORY:
+					taxonomySlug = 'product_cat';
+					break;
+				case CoreCollectionNames.BY_TAG:
+					taxonomySlug = 'product_tag';
+					break;
+				case CoreCollectionNames.BY_BRAND:
+					taxonomySlug = 'product_brand';
+					break;
+				default:
+					taxonomySlug = '';
+			}

 			const selectedTermIds =
 				attributes.query?.taxQuery?.[ taxonomySlug ] || [];
diff --git a/plugins/woocommerce/client/blocks/assets/js/editor-components/product-brand-control/index.tsx b/plugins/woocommerce/client/blocks/assets/js/editor-components/product-brand-control/index.tsx
new file mode 100644
index 0000000000..219b082052
--- /dev/null
+++ b/plugins/woocommerce/client/blocks/assets/js/editor-components/product-brand-control/index.tsx
@@ -0,0 +1,218 @@
+/**
+ * External dependencies
+ */
+import { __, _n, sprintf } from '@wordpress/i18n';
+import {
+	SearchListControl,
+	SearchListItem,
+} from '@woocommerce/editor-components/search-list-control';
+import { SelectControl } from '@wordpress/components';
+import { withSearchedBrands } from '@woocommerce/block-hocs';
+import ErrorMessage from '@woocommerce/editor-components/error-placeholder/error-message';
+import clsx from 'clsx';
+import type { RenderItemArgs } from '@woocommerce/editor-components/search-list-control/types';
+import type {
+	ProductBrandResponseItem,
+	WithInjectedSearchedBrands,
+} from '@woocommerce/types';
+import { convertProductBrandResponseItemToSearchItem } from '@woocommerce/utils';
+
+/**
+ * Internal dependencies
+ */
+import './style.scss';
+import type { SearchListItem as SearchListItemProps } from '../search-list-control/types';
+
+interface ProductBrandControlProps {
+	/**
+	 * Callback to update the selected product brands.
+	 */
+	onChange: ( selected: SearchListItemProps[] ) => void;
+	/**
+	 * Whether or not the search control should be displayed in a compact way, so it occupies less space.
+	 */
+	isCompact?: boolean;
+	/**
+	 * Allow only a single selection. Defaults to false.
+	 */
+	isSingle?: boolean;
+	/**
+	 * Callback to update the brand operator. If not passed in, setting is not used.
+	 */
+	onOperatorChange?: ( operator: string ) => void;
+	/**
+	 * Setting for whether products should match all or any selected brands.
+	 */
+	operator?: 'all' | 'any';
+	/**
+	 * Whether or not to display the number of reviews for a brand in the list.
+	 */
+	showReviewCount?: boolean;
+}
+
+const ProductBrandControl = ( {
+	brands = [],
+	error = null,
+	isLoading = false,
+	onChange,
+	onOperatorChange,
+	operator = 'any',
+	selected = [],
+	isCompact = false,
+	isSingle = false,
+	showReviewCount,
+}: ProductBrandControlProps & WithInjectedSearchedBrands ) => {
+	const renderItem = ( args: RenderItemArgs< ProductBrandResponseItem > ) => {
+		const { item, search, depth = 0 } = args;
+
+		const accessibleName = ! item.breadcrumbs.length
+			? item.name
+			: `${ item.breadcrumbs.join( ', ' ) }, ${ item.name }`;
+
+		const listItemAriaLabel = showReviewCount
+			? sprintf(
+					/* translators: %1$s is the item name, %2$d is the count of reviews for the item. */
+					_n(
+						'%1$s, has %2$d review',
+						'%1$s, has %2$d reviews',
+						item.details?.review_count || 0,
+						'woocommerce'
+					),
+					accessibleName,
+					item.details?.review_count || 0
+			  )
+			: sprintf(
+					/* translators: %1$s is the item name, %2$d is the count of products for the item. */
+					_n(
+						'%1$s, has %2$d product',
+						'%1$s, has %2$d products',
+						item.details?.count || 0,
+						'woocommerce'
+					),
+					accessibleName,
+					item.details?.count || 0
+			  );
+
+		const listItemCountLabel = showReviewCount
+			? sprintf(
+					/* translators: %d is the count of reviews. */
+					_n(
+						'%d review',
+						'%d reviews',
+						item.details?.review_count || 0,
+						'woocommerce'
+					),
+					item.details?.review_count || 0
+			  )
+			: sprintf(
+					/* translators: %d is the count of products. */
+					_n(
+						'%d product',
+						'%d products',
+						item.details?.count || 0,
+						'woocommerce'
+					),
+					item.details?.count || 0
+			  );
+
+		return (
+			<SearchListItem
+				className={ clsx(
+					'woocommerce-product-brands__item',
+					'has-count',
+					{
+						'is-searching': search.length > 0,
+						'is-skip-level': depth === 0 && item.parent !== 0,
+					}
+				) }
+				{ ...args }
+				countLabel={ listItemCountLabel }
+				aria-label={ listItemAriaLabel }
+			/>
+		);
+	};
+
+	const messages = {
+		clear: __( 'Clear all product brands', 'woocommerce' ),
+		list: __( 'Product Brands', 'woocommerce' ),
+		noItems: __(
+			"Your store doesn't have any product brands.",
+			'woocommerce'
+		),
+		search: __( 'Search for product brands', 'woocommerce' ),
+		selected: ( n: number ) =>
+			sprintf(
+				/* translators: %d is the count of selected brands. */
+				_n(
+					'%d brand selected',
+					'%d brands selected',
+					n,
+					'woocommerce'
+				),
+				n
+			),
+		updated: __( 'Brand search results updated.', 'woocommerce' ),
+	};
+
+	if ( error ) {
+		return <ErrorMessage error={ error } />;
+	}
+
+	const currentList = brands.map(
+		convertProductBrandResponseItemToSearchItem
+	);
+
+	return (
+		<>
+			<SearchListControl
+				className="woocommerce-product-brands"
+				list={ currentList }
+				isLoading={ isLoading }
+				selected={ currentList.filter( ( { id } ) =>
+					selected.includes( Number( id ) )
+				) }
+				onChange={ onChange }
+				renderItem={ renderItem }
+				messages={ messages }
+				isCompact={ isCompact }
+				isHierarchical
+				isSingle={ isSingle }
+			/>
+			{ !! onOperatorChange && (
+				<div hidden={ selected.length < 2 }>
+					<SelectControl
+						className="woocommerce-product-brands__operator"
+						label={ __(
+							'Display products matching',
+							'woocommerce'
+						) }
+						help={ __(
+							'Pick at least two brands to use this setting.',
+							'woocommerce'
+						) }
+						value={ operator }
+						onChange={ onOperatorChange }
+						options={ [
+							{
+								label: __(
+									'Any selected brands',
+									'woocommerce'
+								),
+								value: 'any',
+							},
+							{
+								label: __(
+									'All selected brands',
+									'woocommerce'
+								),
+								value: 'all',
+							},
+						] }
+					/>
+				</div>
+			) }
+		</>
+	);
+};
+
+export default withSearchedBrands( ProductBrandControl );
diff --git a/plugins/woocommerce/client/blocks/assets/js/editor-components/product-brand-control/style.scss b/plugins/woocommerce/client/blocks/assets/js/editor-components/product-brand-control/style.scss
new file mode 100644
index 0000000000..5800d2762c
--- /dev/null
+++ b/plugins/woocommerce/client/blocks/assets/js/editor-components/product-brand-control/style.scss
@@ -0,0 +1,10 @@
+.woocommerce-product-brands__operator {
+	.components-base-control__help {
+		@include visually-hidden;
+	}
+
+	.components-base-control__label {
+		margin-bottom: 0;
+		margin-right: 0.5em;
+	}
+}
diff --git a/plugins/woocommerce/client/blocks/assets/js/editor-components/utils/index.js b/plugins/woocommerce/client/blocks/assets/js/editor-components/utils/index.js
index ba1a4e8470..4de4d3d6e3 100644
--- a/plugins/woocommerce/client/blocks/assets/js/editor-components/utils/index.js
+++ b/plugins/woocommerce/client/blocks/assets/js/editor-components/utils/index.js
@@ -191,6 +191,31 @@ export const getCategory = ( categoryId ) => {
 	} );
 };

+/**
+ * Get a promise that resolves to a list of brand objects from the Store API.
+ *
+ * @param {Object} queryArgs Query args to pass in.
+ */
+export const getBrands = ( queryArgs ) => {
+	return apiFetch( {
+		path: addQueryArgs( `wc/store/v1/products/brands`, {
+			per_page: 0,
+			...queryArgs,
+		} ),
+	} );
+};
+
+/**
+ * Get a promise that resolves to a brand object from the API.
+ *
+ * @param {number} brandId Id of the brand to retrieve.
+ */
+export const getBrand = ( brandId ) => {
+	return apiFetch( {
+		path: `wc/store/v1/products/brands/${ brandId }`,
+	} );
+};
+
 /**
  * Get a promise that resolves to a list of variation objects from the Store API
  * and the total number of variations.
diff --git a/plugins/woocommerce/client/blocks/assets/js/hocs/index.js b/plugins/woocommerce/client/blocks/assets/js/hocs/index.js
index 94822b62bc..24fa072f0d 100644
--- a/plugins/woocommerce/client/blocks/assets/js/hocs/index.js
+++ b/plugins/woocommerce/client/blocks/assets/js/hocs/index.js
@@ -1,5 +1,6 @@
 export { default as withAttributes } from './with-attributes';
 export { default as withSearchedCategories } from './with-searched-categories';
+export { default as withSearchedBrands } from './with-searched-brands';
 export { default as withCategory } from './with-category';
 export { default as withProduct } from './with-product';
 export { default as withProductVariations } from './with-product-variations';
diff --git a/plugins/woocommerce/client/blocks/assets/js/hocs/with-searched-brands.tsx b/plugins/woocommerce/client/blocks/assets/js/hocs/with-searched-brands.tsx
new file mode 100644
index 0000000000..1ccd0071d7
--- /dev/null
+++ b/plugins/woocommerce/client/blocks/assets/js/hocs/with-searched-brands.tsx
@@ -0,0 +1,71 @@
+/**
+ * External dependencies
+ */
+import { useEffect, useState } from '@wordpress/element';
+import { getBrands } from '@woocommerce/editor-components/utils';
+import type {
+	ProductBrandResponseItem,
+	WithInjectedSearchedBrands,
+} from '@woocommerce/types';
+
+/**
+ * Internal dependencies
+ */
+import { formatError } from '../base/utils/errors';
+
+export interface WithSearchedBrandsProps {
+	selected: number[];
+}
+
+/**
+ * A higher order component that enhances the provided component with brands from a search query.
+ */
+const withSearchedBrands = <
+	T extends Record< string, unknown > & WithSearchedBrandsProps
+>(
+	OriginalComponent: React.ComponentType< T & WithInjectedSearchedBrands >
+) => {
+	return ( { selected, ...props }: T ): JSX.Element => {
+		const [ isLoading, setIsLoading ] = useState( true );
+		const [ error, setError ] = useState< {
+			message: string;
+			type: string;
+		} | null >( null );
+		const [ brandsList, setBrandsList ] = useState<
+			ProductBrandResponseItem[]
+		>( [] );
+
+		const setErrorState = async ( e: {
+			message: string;
+			type: string;
+		} ) => {
+			const formattedError = ( await formatError( e ) ) as {
+				message: string;
+				type: string;
+			};
+			setError( formattedError );
+			setIsLoading( false );
+		};
+
+		useEffect( () => {
+			getBrands( {} )
+				.then( ( results ) => {
+					setBrandsList( results as ProductBrandResponseItem[] );
+					setIsLoading( false );
+				} )
+				.catch( setErrorState );
+		}, [] );
+
+		return (
+			<OriginalComponent
+				{ ...( props as T ) }
+				selected={ selected }
+				error={ error }
+				brands={ brandsList }
+				isLoading={ isLoading }
+			/>
+		);
+	};
+};
+
+export default withSearchedBrands;
diff --git a/plugins/woocommerce/client/blocks/assets/js/types/type-defs/hocs.ts b/plugins/woocommerce/client/blocks/assets/js/types/type-defs/hocs.ts
index 56fe7d5065..859fea477e 100644
--- a/plugins/woocommerce/client/blocks/assets/js/types/type-defs/hocs.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/types/type-defs/hocs.ts
@@ -5,6 +5,7 @@ import { ErrorObject } from '@woocommerce/editor-components/error-placeholder';
 import type {
 	ProductResponseItem,
 	ProductCategoryResponseItem,
+	ProductBrandResponseItem,
 } from '@woocommerce/types';

 export interface WithInjectedProductVariations {
@@ -34,6 +35,13 @@ export interface WithInjectedSearchedCategories {
 	selected: number[];
 }

+export interface WithInjectedSearchedBrands {
+	error: ErrorObject | null;
+	isLoading: boolean;
+	brands: ProductBrandResponseItem[];
+	selected: number[];
+}
+
 export interface WithInjectedInstanceId {
 	instanceId: string | number;
 }
diff --git a/plugins/woocommerce/client/blocks/assets/js/types/type-defs/product-category-response.ts b/plugins/woocommerce/client/blocks/assets/js/types/type-defs/product-category-response.ts
index ac00ace516..eac6c996ed 100644
--- a/plugins/woocommerce/client/blocks/assets/js/types/type-defs/product-category-response.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/types/type-defs/product-category-response.ts
@@ -1,4 +1,8 @@
-export interface ProductCategoryResponseImageItem {
+/**
+ * Generic taxonomy term image response from the Store API.
+ * Used for categories, brands, and other hierarchical taxonomies.
+ */
+export interface TaxonomyResponseImageItem {
 	id: number;
 	src: string;
 	thumbnail: string;
@@ -8,14 +12,24 @@ export interface ProductCategoryResponseImageItem {
 	alt: string;
 }

-export interface ProductCategoryResponseItem {
+/**
+ * Generic taxonomy term response from the Store API.
+ * Used for categories, brands, and other hierarchical taxonomies.
+ */
+export interface TaxonomyResponseItem {
 	id: number;
 	name: string;
 	slug: string;
 	description: string;
 	parent: number;
 	count: number;
-	image: ProductCategoryResponseImageItem | null;
+	image: TaxonomyResponseImageItem | null;
 	review_count: number;
 	permalink: string;
 }
+
+// Aliases for backward compatibility and semantic clarity
+export type ProductCategoryResponseImageItem = TaxonomyResponseImageItem;
+export type ProductCategoryResponseItem = TaxonomyResponseItem;
+export type ProductBrandResponseImageItem = TaxonomyResponseImageItem;
+export type ProductBrandResponseItem = TaxonomyResponseItem;
diff --git a/plugins/woocommerce/client/blocks/assets/js/utils/products.ts b/plugins/woocommerce/client/blocks/assets/js/utils/products.ts
index 3d2ed1d053..98defde38a 100644
--- a/plugins/woocommerce/client/blocks/assets/js/utils/products.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/utils/products.ts
@@ -5,6 +5,7 @@ import type { SearchListItem } from '@woocommerce/editor-components/search-list-
 import type {
 	ProductResponseItem,
 	ProductCategoryResponseItem,
+	ProductBrandResponseItem,
 } from '@woocommerce/types';

 /**
@@ -46,6 +47,26 @@ export const convertProductCategoryResponseItemToSearchItem = (
 	};
 };

+/**
+ * Converts a Product Brand object into a shape compatible with the `SearchListControl`
+ */
+export const convertProductBrandResponseItemToSearchItem = (
+	brand: ProductBrandResponseItem
+): SearchListItem< ProductBrandResponseItem > => {
+	const { id, name, parent, count } = brand;
+
+	return {
+		id,
+		name,
+		parent,
+		count,
+		breadcrumbs: [],
+		children: [],
+		details: brand,
+		value: brand.slug,
+	};
+};
+
 /**
  * Get the src of the first image attached to a product (the featured image).
  */
diff --git a/plugins/woocommerce/client/blocks/tests/e2e/bin/scripts/products.sh b/plugins/woocommerce/client/blocks/tests/e2e/bin/scripts/products.sh
index 0e10af7b8e..16c2ea038b 100644
--- a/plugins/woocommerce/client/blocks/tests/e2e/bin/scripts/products.sh
+++ b/plugins/woocommerce/client/blocks/tests/e2e/bin/scripts/products.sh
@@ -27,6 +27,13 @@ tag_id=$(wp wc product_tag create --name="Recommended" --slug="recommended" --de
 wp wc product update $hoodie_product_id --tags="[ { \"id\": $tag_id } ]" --user=1
 wp wc product update $beanie_product_id --tags="[ { \"id\": $tag_id } ]" --user=1

+# Create a brand, so we can add tests for brand-related blocks and templates.
+album_product_id=$(wp post list --post_type=product --field=ID --name="Album" --format=ids)
+brand_id=$(wp term create product_brand "WooCommerce" --slug="woocommerce" --description="Official WooCommerce products" --porcelain)
+wp post term set $hoodie_product_id product_brand $brand_id --by=id
+wp post term set $beanie_product_id product_brand $brand_id --by=id
+wp post term set $album_product_id product_brand $brand_id --by=id
+
 # This is a non-hacky work around to set up the cross sells product.
 cap_product_id=$(wp post list --post_type=product --field=ID --name="Cap" --format=ids)
 wp post meta update $beanie_product_id _crosssell_ids "$cap_product_id"
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
index 3a5ce43c5b..fa59e4966b 100644
--- 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
@@ -46,6 +46,13 @@ const taxonomyCollections: {
 		termLabel: 'Recommended',
 		expectedProductCount: 2,
 	},
+	{
+		slug: 'productsByBrand',
+		name: 'Products by Brand',
+		termName: 'brands',
+		termLabel: 'WooCommerce',
+		expectedProductCount: 3,
+	},
 ];

 test.describe( 'Product Collection: Collection Pickers', () => {
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 9698f8d52d..390a3108e9 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
@@ -77,6 +77,7 @@ export type Collections =
 	| 'handPicked'
 	| 'productsByCategory'
 	| 'productsByTag'
+	| 'productsByBrand'
 	| 'productCatalog'
 	| 'myCustomCollection'
 	| 'myCustomCollectionWithPreview'
@@ -99,6 +100,7 @@ const collectionToButtonNameMap = {
 	handPicked: 'Hand-Picked Products',
 	productsByCategory: 'Products by Category',
 	productsByTag: 'Products by Tag',
+	productsByBrand: 'Products by Brand',
 	productCatalog: 'create your own',
 	myCustomCollection: 'My Custom Collection',
 	myCustomCollectionWithPreview: 'My Custom Collection with Preview',