Skip to content

[tests] Cut linked products E2E from 4 titles to 2, moving relation clearing to PHPUnit - #68648

Open
vladolaru wants to merge 2 commits into
trunkfrom
testops-288/linked-products
Open

[tests] Cut linked products E2E from 4 titles to 2, moving relation clearing to PHPUnit#68648
vladolaru wants to merge 2 commits into
trunkfrom
testops-288/linked-products

Conversation

@vladolaru

@vladolaru vladolaru commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Submission Review Guidelines:

Changes proposed in this Pull Request:

The linked products spec drove the classic editor four times: twice to attach an up-sell or a cross-sell and see it on the storefront, and twice to detach one and see it gone. Attaching is a browser journey worth keeping, because it runs through the select2 search control that only exists in the editor. Detaching is a persistence assertion wearing a browser costume. The two remove titles move down a layer.

Refs TESTOPS-288

Part of the #68046 split.

Browser titles go from 4 to 2. The new WC_Meta_Box_Product_Data_Linked_Products_Test adds 2 tests and 35 assertions.

Removed E2E test Existing coverage Knowingly dropped
remove up-sells WC_Meta_Box_Product_Data_Linked_Products_Test::test_save_clears_and_hides_linked_products The removal gesture itself. See below.
remove cross-sells same method Same.

add up-sells and add cross-sells are byte-identical to the diff base.

What the PHP test actually proves

It posts through WC_Meta_Box_Product_Data::save() with both linked-product fields omitted, then asserts two things the browser titles asserted:

  • the stored relations come back empty from a real data-store round trip (wc_get_product(), then get_upsell_ids( 'edit' ) and get_cross_sell_ids( 'edit' ));
  • woocommerce_upsell_display() stops emitting its section and no longer names the products. That is the function the single-product template hooks, so it is the same markup the browser looked at, rendered by the same code;
  • the cart stops offering the products as cross-sells, asserted on WC()->cart->get_cross_sells() rather than on rendered markup. That is a correction to an earlier version of this pull request, and the reason is worth reading.

Why the cross-sell assertions are not on markup

The first version of this file asserted the rendered cart cross-sell section, exactly as it asserts the up-sell one. That failed on CI on all four PHP/WP matrix jobs, including HPOS:off, with Failed asserting that '' contains "div class="cross-sells"".

The cause is not the fixtures and not this test. woocommerce_cross_sell_display() opens with if ( is_checkout() ) { return; }. is_checkout() is true whenever WOOCOMMERCE_CHECKOUT is defined, and tests/legacy/unit-tests/coupon/coupon.php defines it. A PHP define cannot be undone, and phpunit.xml sets defaultTestSuite="wc-phpunit-legacy,wc-phpunit-main", so the legacy suite always runs first and that constant is always set by the time these tests run. The renderer therefore emits nothing here, every time.

Two consequences, and the second is the worse one:

  • the positive test failed, which is how this was noticed;
  • the clearing test's matching assertion — that the section does not render — passed vacuously, and would have gone on passing if cross-sells had rendered perfectly.

It reproduces in two tests rather than the full suite: run the coupon test that defines the constant alongside either of these. Under --testsuite=wc-phpunit-main alone, which excludes the legacy suite, both pass — which is why it was not caught before the PR opened.

WC()->cart->get_cross_sells() is the contract WC_Meta_Box_Product_Data actually feeds, and it is assertable whatever the checkout state. Both tests now assert it: the exact IDs in submitted order, no up-sell IDs crossing over, and the cart product not cross-selling itself. It is stricter than the string matching it replaces, and the clearing test now has an assertion that can fail.

What is no longer proven anywhere

The removal gesture. The deleted titles backspaced a token out of the select2 control and relied on the editor posting a shorter array; the PHP test supplies $_POST itself. Nothing in either layer now proves that the control produces an empty upsell_ids or crosssell_ids field when you clear it.

The two retained titles drive that same control in the opposite direction, adding a relation and seeing it persist and render, so the control is not untested — but "adding works, therefore removing works" is an inference, not a proof, and this PR is the place to say so rather than leave it to be discovered later.

Two deliberate changes to the new PHPUnit file

It is otherwise exactly the migration branch's version.

  1. Fixture names. Five carried the campaign's internal slice identifiers and are renamed to neutral values. Every posted value still matches the value asserted against it.
  2. The clearing test proved nothing about its own seed. It set both relations, saved, cleared them, and asserted they were empty. Against a seed that had silently stored nothing, every assertion would have passed just as happily. It now reloads after seeding and asserts both relations are non-empty and in the submitted order before the clearing save. That is the two extra assertions in the 35.

Scope of the browser-coverage claim

These two titles are the only E2E coverage of the classic linked-products controls. tests/e2e/tests/blocks/cart-block.shopper.block_theme.spec.ts separately covers the Cross-Sells block, which is a different surface and is untouched here.

Why this is only the linked-products half

This started as one batch covering product images and up-sells/cross-sells. The two shared no commit, helper or fixture, so the batch was split. The images half is a separate PR.

Screenshots or screen recordings:

Not applicable. Test-only change.

How to test the changes in this Pull Request:

  1. Check out this branch and run pnpm install --frozen-lockfile from the repository root.
  2. Start the PHPUnit environment: pnpm --filter=@woocommerce/plugin-woocommerce env:test.
  3. Run the new class with an anchored filter — plugins/woocommerce/tests/ also contains WC_Meta_Box_Product_Data_POS_Visibility_Test, and PHPUnit's --filter is an unanchored substring regex, so a loose filter runs more than it claims: pnpm --filter=@woocommerce/plugin-woocommerce test:php:env -- --filter '/^WC_Meta_Box_Product_Data_Linked_Products_Test::/'. Expect OK (2 tests, 28 assertions).
  4. Start the E2E environment: pnpm --filter=@woocommerce/plugin-woocommerce env:e2e.
  5. Run the spec with retries disabled: pnpm --filter=@woocommerce/plugin-woocommerce test:e2e:with-env default --project=core-parallel --retries=0 --workers=1 tests/e2e/tests/product/product-linked-products.spec.ts. Both retained titles should pass. The summary reads 4 passed and 1 skipped: the 4 are the 2 product titles plus the global authentication and site setup projects, and the skip is install wc, which skips itself when INSTALL_WC is unset.
  6. Confirm the PHP test detects a failure to clear — this is the behavior the two removed browser titles used to guard. In plugins/woocommerce/includes/admin/meta-boxes/class-wc-meta-box-product-data.php, change the 'cross_sell_ids' fallback from : array(), to : $product->get_cross_sell_ids(),. Re-run step 3 and expect test_save_clears_and_hides_linked_products to fail at An omitted cross-sell field should clear every stored cross-sell ID., expecting an empty array and receiving the previous IDs. Revert.
  7. Confirm the PHP test keeps up-sells and cross-sells distinct. In the same file, change the 'upsell_ids' source from $_POST['upsell_ids'] to $_POST['crosssell_ids']. Re-run step 3 and expect test_save_persists_and_renders_distinct_linked_products to fail at Upsell IDs should retain their exact submitted order.. Revert.
  8. Confirm the cart assertion is load-bearing, since it replaces the markup assertion this PR removed. In plugins/woocommerce/includes/class-wc-cart.php, in get_cross_sells(), change $cross_sells = array_merge( $values['data']->get_cross_sell_ids(), $cross_sells ); to $cross_sells = array_merge( array(), $cross_sells );. The stored relations stay correct and only what the cart offers is emptied. Re-run step 3 and expect test_save_persists_and_renders_distinct_linked_products to fail at The cart should offer exactly the saved cross-sells, in their submitted order.. Revert.
  9. Reproduce the CI failure this PR fixes, against the previous version of the test file. Check out 9c0f6dc04d, then run the target test together with the legacy coupon test that defines WOOCOMMERCE_CHECKOUT: pnpm --filter=@woocommerce/plugin-woocommerce test:php:env -- --filter '/(test_percent_discount_item_limit|test_save_persists_and_renders_distinct_linked_products)/'. Expect 2 tests, 1 failure, Failed asserting that '' contains "div class="cross-sells"". Run the same command on this branch and expect OK. Note there is no --testsuite flag: adding --testsuite=wc-phpunit-main excludes the legacy suite and both versions pass, which is the trap.
  10. Confirm the retained browser titles still detect a regression in the editor control. In the same file, change the 'upsell_ids' fallback from : array(), to : array( 0 ), and re-run step 5; add up-sells should fail when the storefront does not list the product it just attached. Revert.

Testing that has already taken place:

Everything below ran on this branch against a local WordPress, with retries disabled and one worker. Trunk was at adefb5f971.

  • Baseline first. Trunk's copies of this spec and its sibling ran green before the batch was extracted — 10 titles across both — so the environment was known good and any later failure is attributable to the change.
  • Extraction. The spec is byte-identical to the migration branch. The PHP test differs only by the two deliberate changes described above. Neither path has a trunk commit since the diff base, so nothing had to be merged.
  • Retained titles. 2 titles, both green at --retries=0 --workers=1.
  • Lower layer. WC_Meta_Box_Product_Data_Linked_Products_Test OK (2 tests, 28 assertions) under the anchored filter, and OK (3 tests, 29 assertions) when run together with the legacy coupon test that defines WOOCOMMERCE_CHECKOUT — the condition that used to fail. Before the fix that same pair of tests reported 1 failure. The assertion count fell from 35 because exact ID comparisons replaced several string matches, not because anything stopped being checked.
  • Mutation re-check. The mutation matrix groups both recorded mutations under one behavior family, so one kill would satisfy the campaign's rule. Both were run, because they prove different things: redirecting the up-sell field at the cross-sell POST key turns the distinctness test red at Upsell IDs should retain their exact submitted order., and making the cross-sell field preserve its previous value when omitted turns the clearing test red at An omitted cross-sell field should clear every stored cross-sell ID.. Each reverted cleanly and went green again. The second is the one that matters here: it is the direct evidence that the method absorbing the two removed titles really detects a failure to clear. Both were re-run with the legacy coupon test in the same process, so they are proven under the condition that broke this batch rather than only in a clean one.
  • A third mutation was added for the new cart assertion, because neither recorded mutation touches it. Replacing $values['data']->get_cross_sell_ids() with array() in WC_Cart::get_cross_sells() leaves the stored relations correct but empties what the cart offers; the positive test then fails at The cart should offer exactly the saved cross-sells, in their submitted order. and goes green on revert. That is what shows the replacement assertion carries weight rather than merely passing.
  • Lint. lint:changes:branch exits 0. ESLint reports 1 warning on this branch's copy of the spec and 1 on trunk's — no delta, no errors. oxlint attributes no new finding to the change.
  • PHPStan: not applicable, and checked rather than assumed. phpstan.neon scopes to woocommerce.php, src/ and includes/; this batch's only PHP file is a test.
  • Identifier sweep. No internal campaign identifiers remain in either path, verified with a detector proven against a file where such identifiers do exist.

Milestone

Note: Check the box above to have the milestone automatically assigned when merged.
Alternatively (e.g. for point releases), manually assign the appropriate milestone.

Changelog entry

  • Automatically create a changelog entry from the details below.
  • This Pull Request does not require a changelog entry. (Comment required below)
Changelog Entry Details

Significance

  • Patch
  • Minor
  • Major

Type

  • Fix - Fixes an existing bug
  • Add - Adds functionality
  • Update - Update existing functionality
  • Dev - Development related task
  • Tweak - A minor adjustment to the codebase
  • Performance - Address performance issues
  • Enhancement - Improvement to existing functionality

Message

Changelog Entry Comment

Comment

Created manually: plugins/woocommerce/changelog/testops-288-linked-products.

Use of AI Tools

The migration was produced by an agent-run campaign with per-test mutation verification (see #68046). This PR was assembled, verified in isolation, and reviewed by an agent; the author reviewed the diff and takes responsibility for it.


🤖 Generated with Claude Code

@github-actions github-actions Bot added plugin: woocommerce Issues related to the WooCommerce Core plugin. focus: e2e tests Issues related to e2e tests labels Sep 12, 2026
The linked products spec drove the classic editor four times: twice to
attach an up-sell or a cross-sell and see it on the storefront, and
twice to detach one and see it gone. Attaching is a browser journey
worth keeping, because it runs through the select2 search control that
only exists in the editor. Detaching is a persistence assertion
wearing a browser costume: it posts an empty field and checks the
front end stops rendering the relation.

The two `remove` titles go. WC_Meta_Box_Product_Data_Linked_Products_Test
takes that behavior through WC_Meta_Box_Product_Data::save(), asserting
both that the cleared relation is persisted as empty and that
woocommerce_upsell_display() and woocommerce_cross_sell_display() stop
rendering their sections. Those are the functions the single-product
and cart templates hook, so it is the same markup the browser titles
looked at.

What does not survive is the removal gesture itself. Nothing now
drives the select2 control's backspace and checks that it posts a
shorter array. The two retained titles exercise that same control in
the opposite direction, which is good evidence but is not proof.

`add up-sells` and `add cross-sells` stay byte-identical to the diff
base.

Three deliberate changes to the new PHPUnit file before landing:

- Five fixture names carried the campaign's internal slice
  identifiers; they are renamed to neutral values.
- The clearing test seeded the relations and then asserted they were
  empty, without ever checking that the seed had persisted. It would
  have passed just as happily against a seed that stored nothing. It
  now proves its own precondition first.
- The cross-sell assertions moved off the rendered markup and onto the
  cart's own cross-sell list. The reason is below, because it is not a
  preference.

The cross-sell markup could not be asserted here, and the first
version of this file was wrong to try.

woocommerce_cross_sell_display() begins `if ( is_checkout() ) return;`.
is_checkout() is true whenever WOOCOMMERCE_CHECKOUT is defined, and
tests/legacy/unit-tests/coupon/coupon.php defines it -- a plain PHP
define, which cannot be undone for the rest of the process. phpunit.xml
sets defaultTestSuite="wc-phpunit-legacy,wc-phpunit-main", so the legacy
suite always runs first and that constant is always set by the time
these tests run.

So on CI the renderer emitted nothing here, every time. The positive
test failed asserting that '' contains div class="cross-sells", on all
four PHP/WP matrix jobs including HPOS:off. Worse, the clearing test's
matching assertion -- that the section does *not* render -- passed
vacuously for the same reason, and would have kept passing if
cross-sells had rendered perfectly.

Reproducing it needs two tests rather than the full suite: run the
coupon test that defines the constant together with either of these and
the failure appears. Under --testsuite=wc-phpunit-main alone, which
excludes the legacy suite, both tests pass, which is why this was not
caught locally before.

WC()->cart->get_cross_sells() is the contract WC_Meta_Box_Product_Data
actually feeds, and it is assertable regardless of checkout state. Both
tests now assert it: exact IDs in submitted order, no upsell IDs
crossing over, and the cart product not cross-selling itself. That is
stronger than the string matching it replaces, and the clearing test
now has an assertion that can fail. The up-sell markup assertions are
untouched: woocommerce_upsell_display() has no such guard.

This is the linked products half of a batch that also covered product
images. The two shared no commit, helper or fixture, so they were
split; the images half is a separate PR.

Carries the mega-branch commit:
- ff6181c test(e2e): Reduce linked products browser coverage

Refs TESTOPS-288
Refs #68046

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vladolaru
vladolaru force-pushed the testops-288/linked-products branch from 9c0f6dc to 558c3d9 Compare September 12, 2026 23:58
@vladolaru
vladolaru marked this pull request as ready for review September 13, 2026 14:06
@coderabbitai

coderabbitai Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 59 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Advanced

Run ID: fe126fb8-3268-47a7-b528-5bc3d511355e

📥 Commits

Reviewing files that changed from the base of the PR and between 3eeb64f and 775e8e0.

📒 Files selected for processing (3)
  • plugins/woocommerce/changelog/testops-288-linked-products
  • plugins/woocommerce/tests/e2e/tests/product/product-linked-products.spec.ts
  • plugins/woocommerce/tests/php/includes/admin/meta-boxes/class-wc-meta-box-product-data-linked-products-test.php

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

focus: e2e tests Issues related to e2e tests plugin: woocommerce Issues related to the WooCommerce Core plugin.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant