Commit b908c0a7a31 for woocommerce
commit b908c0a7a317ccede0bf607f8409252a779ff00d
Author: Vlad Olaru <vlad.olaru@automattic.com>
Date: Thu Sep 17 21:35:46 2026 +0300
[tests] Cut product search E2E from 3 titles to 2, moving the search query to PHPUnit (#68650)
* test(products): Cut product search E2E from 3 titles to 1
Three browser titles searched the admin product list: one for a
partial match, one that opened the matched product, and one that
searched for nonsense and checked the empty state. The first two were
the same journey split in half, and both were really asking whether
the search query finds the right products.
They become one journey, `can find and open a product from a partial
search`, which does the search and opens the result in a single pass.
The query itself moves to PHPUnit:
WC_Admin_List_Table_Products_Test gains
test_product_search_returns_partial_title_match and
test_product_search_returns_no_results_for_missing_term, which drive
the list table's own request query down to
WC_Product_Data_Store_CPT::search_products().
Note what the PHP tests do and do not assert. They assert the search
results, as IDs, not the rendered list. That is the right layer for
"does this term match this product", but it means the empty state's
rendered `No products found` text is no longer covered by them. The
next commit adds a browser title for it, because nothing else in the
suite asserts that markup.
class-wc-admin-list-table-products-test.php had moved on trunk since
this work branched: #68094 added two stock-status join tests. Rather
than take the frozen state blind and drop them, trunk's copy was kept
and only this branch's two methods re-applied on top. The file's diff
against trunk is purely additive, the class goes from 24 methods to
26, and PHPUnit from 42 tests to 44.
Carries the mega-branch commit:
- 12619d3af2 test(products): Move search results below E2E
Refs TESTOPS-288
Refs #68046
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(e2e): Add a product search empty-state canary
The previous commit moved the "no results" search behavior to
PHPUnit, where it is asserted as an empty array of product IDs. That
is the right layer for the query, but it leaves the rendered empty
state uncovered: after that commit nothing in the suite asserts the
admin product list's `No products found` markup, and nothing did
outside this spec beforehand either.
Add one title that searches for a term no product can match and
asserts the `.no-items` text. It reuses the spec's existing fixtures
and adds no new ones.
Refs TESTOPS-288
Refs #68046
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore(changelog): Correct the product search E2E title count
The entry said the spec was cut from 3 titles to 1, but it keeps 2:
the merged search journey and the empty-state canary, as the PR title
and description say. CodeRabbit raised this in its review of this PR.
Refs TESTOPS-288
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(products): Rename the product search fixture prefix
The fixture product name began with "Slice target", which leaked the
migration campaign's internal work-item naming into a test fixture.
The searched substring is substr( $target_token, 4, 8 ) taken from a
random wp_generate_password() token, not from the prefix, so the rename
changes nothing the test proves. The class passes, 44 tests.
Refs TESTOPS-288
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
diff --git a/plugins/woocommerce/changelog/testops-288-products-admin-search b/plugins/woocommerce/changelog/testops-288-products-admin-search
new file mode 100644
index 00000000000..9d61eff6482
--- /dev/null
+++ b/plugins/woocommerce/changelog/testops-288-products-admin-search
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+Comment: Cut the product search E2E spec from 3 titles to 2; partial-match and no-result search queries now covered by WC_Admin_List_Table_Products_Test.
+
diff --git a/plugins/woocommerce/tests/e2e/tests/product/product-search.spec.ts b/plugins/woocommerce/tests/e2e/tests/product/product-search.spec.ts
index 822f0397eb5..871aa9d7c39 100644
--- a/plugins/woocommerce/tests/e2e/tests/product/product-search.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/product/product-search.spec.ts
@@ -30,40 +30,62 @@ test.describe( 'Products > Search and View a product', () => {
} );
} );
- test( 'can do a partial search for a product', async ( { page } ) => {
- // create a partial search string
- const searchString = testProduct.name.substring(
- 0,
- testProduct.name.length / 2
- );
-
+ // Canary for the admin product list's empty state. The PHPUnit coverage this
+ // batch adds asserts the search *results*, as IDs, so nothing below the browser
+ // asserts the `No products found` markup, and nothing else in the suite does
+ // either.
+ test( 'shows the empty state when a search matches no products', async ( {
+ page,
+ } ) => {
await page.goto( 'wp-admin/edit.php?post_type=product' );
await expect( page.locator( '#post-search-input' ) ).toBeVisible();
- await page.locator( '#post-search-input' ).fill( searchString );
+ await page
+ .locator( '#post-search-input' )
+ .fill( `no-such-product-${ Date.now() }` );
await page.locator( '#search-submit' ).click();
- // A partial search can match products that parallel workers create from
- // this same spec, so scope the assertion to this test's product instead
- // of asserting on every `.row-title` match.
- await expect(
- page.locator( '.row-title', { hasText: testProduct.name } )
- ).toBeVisible();
+ await expect( page.locator( '.no-items' ) ).toContainText(
+ 'No products found'
+ );
} );
- test( "can view a product's details after search", async ( { page } ) => {
- const productIdInURL = new RegExp( `post=${ productId }` );
+ test( 'can find and open a product from a partial search', async ( {
+ page,
+ } ) => {
+ const searchString = testProduct.name.substring(
+ 0,
+ testProduct.name.length / 2
+ );
await page.goto( 'wp-admin/edit.php?post_type=product' );
- await page.locator( '#post-search-input' ).fill( testProduct.name );
+ await expect( page.locator( '#post-search-input' ) ).toBeVisible();
+ await page.locator( '#post-search-input' ).fill( searchString );
await page.locator( '#search-submit' ).click();
- await page
- .locator( '.row-title', { hasText: testProduct.name } )
+ const productLink = page.getByRole( 'link', {
+ name: testProduct.name,
+ exact: true,
+ } );
+ const productRow = page.locator( '#the-list tr' ).filter( {
+ has: productLink,
+ } );
+ await expect( productRow ).toHaveCount( 1 );
+ await expect(
+ productRow.getByRole( 'link', {
+ name: testProduct.name,
+ exact: true,
+ } )
+ ).toHaveCount( 1 );
+ await productRow
+ .getByRole( 'link', { name: testProduct.name, exact: true } )
.click();
- await expect( page ).toHaveURL( productIdInURL );
+ await expect( page ).toHaveURL( /wp-admin\/post\.php/ );
+ expect( new URL( page.url() ).searchParams.get( 'post' ) ).toBe(
+ String( productId )
+ );
await expect( page.locator( '#title' ) ).toHaveValue(
testProduct.name
);
@@ -71,17 +93,4 @@ test.describe( 'Products > Search and View a product', () => {
testProduct.regular_price
);
} );
-
- test( 'returns no results for non-existent product search', async ( {
- page,
- } ) => {
- await page.goto( 'wp-admin/edit.php?post_type=product' );
-
- await page.locator( '#post-search-input' ).fill( 'abcd1234' );
- await page.locator( '#search-submit' ).click();
-
- await expect( page.locator( '.no-items' ) ).toContainText(
- 'No products found'
- );
- } );
} );
diff --git a/plugins/woocommerce/tests/php/includes/admin/list-tables/class-wc-admin-list-table-products-test.php b/plugins/woocommerce/tests/php/includes/admin/list-tables/class-wc-admin-list-table-products-test.php
index 045cd1e7f90..ca367273f25 100644
--- a/plugins/woocommerce/tests/php/includes/admin/list-tables/class-wc-admin-list-table-products-test.php
+++ b/plugins/woocommerce/tests/php/includes/admin/list-tables/class-wc-admin-list-table-products-test.php
@@ -86,6 +86,76 @@ class WC_Admin_List_Table_Products_Test extends WC_Unit_Test_Case {
);
}
+ /**
+ * @testdox Product searches return a partial title match.
+ */
+ public function test_product_search_returns_partial_title_match(): void {
+ $target_token = wp_generate_password( 16, false );
+ $search_term = substr( $target_token, 4, 8 );
+ $target_name = 'Search target ' . $target_token;
+ $control_name = 'Unrelated fixture';
+ $target = null;
+ $control = null;
+
+ $this->assertFalse(
+ false !== stripos( $control_name, $search_term ),
+ 'The control title must not contain the target search substring.'
+ );
+
+ try {
+ $target = WC_Helper_Product::create_simple_product();
+ $target->set_name( $target_name );
+ $target->set_status( 'publish' );
+ $target->set_date_created( '2024-01-02 00:00:00' );
+ $target->save();
+
+ $control = WC_Helper_Product::create_simple_product();
+ $control->set_name( $control_name );
+ $control->set_status( 'publish' );
+ $control->set_date_created( '2024-01-01 00:00:00' );
+ $control->save();
+
+ $this->assertSame(
+ array( $target->get_id() ),
+ $this->get_search_results( $search_term ),
+ 'A strict interior title substring should return only its matching product.'
+ );
+ } finally {
+ if ( $control instanceof WC_Product ) {
+ $control->delete( true );
+ }
+
+ if ( $target instanceof WC_Product ) {
+ $target->delete( true );
+ }
+ }
+ }
+
+ /**
+ * @testdox Product searches return no results for a missing term.
+ */
+ public function test_product_search_returns_no_results_for_missing_term(): void {
+ $control = null;
+
+ try {
+ $control = WC_Helper_Product::create_simple_product();
+ $control->set_name( 'Product Search Control ' . wp_generate_password( 8, false ) );
+ $control->set_status( 'publish' );
+ $control->set_date_created( '2024-01-01 00:00:00' );
+ $control->save();
+
+ $this->assertSame(
+ array(),
+ $this->get_search_results( 'Missing Product Search ' . wp_generate_password( 8, false ) ),
+ 'A missing product search term should not admit unrelated published products.'
+ );
+ } finally {
+ if ( $control instanceof WC_Product ) {
+ $control->delete( true );
+ }
+ }
+ }
+
/**
* @testdox Product searches prioritize titles that match parsed search terms.
* @dataProvider parsed_search_term_provider