Commit ae3a68a552b for woocommerce

commit ae3a68a552b47f13adf8193f8b270ec8e64a9b8c
Author: Vlad Olaru <vlad.olaru@automattic.com>
Date:   Mon Sep 21 11:23:45 2026 +0300

    [e2e] Stop the WooCommerce.com connect E2E test at WooCommerce's own OAuth redirect (#68900)

    * test(e2e): Stop the WooCommerce.com connect test at Woo's own redirect

    The test clicked Connect and then asserted on WordPress.com's login
    page: that page.url() contained wordpress.com/log-in, and that a "Log
    in to Woo with WordPress.com" heading was visible. Both sit two hops
    past anything WooCommerce controls. WooCommerce redirects to
    WooCommerce.com's oauth/authorize, that bounces to WordPress.com, and
    WordPress.com renders whatever it renders. CI runners now get a
    Cloudflare "Confirm you are human" challenge there instead of the login
    form, so the test fails on trunk and on every branch.

    Intercept the wc-helper-connect request instead. The real request still
    runs, nonce check and server-side oauth/request_token exchange with
    WooCommerce.com included, but its redirect is read rather than
    followed, and the browser gets a 204 so it stays on the extensions
    page. The assertions then cover the authorize URL WooCommerce itself
    built: a non-empty secret, which is proof the token exchange actually
    happened, plus home_url, redirect_uri carrying wc-helper-return and a
    nonce, redirect_admin_url, and wum-installed.

    This widens coverage rather than narrowing it. Mutating seven values in
    _helper_auth_connect, the old assertions caught three, and only
    indirectly, because WooCommerce.com refused the bad request so the
    browser never reached WordPress.com. The new assertions catch all
    seven directly. Runtime drops from roughly 36s to roughly 3s, since
    the browser no longer leaves the store.

    Tag the test @services, as it still needs WooCommerce.com reachable.

    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

    * test(e2e): Strengthen WooCommerce.com callback assertions

    The OAuth hand-off test accepted callback URLs on another host and
    admin page values that merely started with wc-admin. Those gaps could
    hide a broken return path after connecting a store.

    Check both callback origins against the configured store origin and
    compare the parsed admin page parameter exactly.

    * test(e2e): Assert the OAuth hand-off goes to WooCommerce.com

    The connect test checked the authorize URL's path and every store-side
    callback parameter, but never the host the store hands off to. A
    redirect built against a different origin would still carry
    `/oauth/authorize`, so the test would pass.

    Pin the authorize origin to https://woocommerce.com. That is the
    default `woocommerce_helper_api_base`, which stores can filter but the
    E2E environment leaves alone.

    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/fix-e2e-wccom-connect-third-party-dependency b/plugins/woocommerce/changelog/fix-e2e-wccom-connect-third-party-dependency
new file mode 100644
index 00000000000..44cecdaa472
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-e2e-wccom-connect-third-party-dependency
@@ -0,0 +1,3 @@
+Significance: patch
+Type: dev
+Comment: Assert the WooCommerce.com connect E2E test against WooCommerce's own OAuth redirect instead of following it out to WordPress.com.
diff --git a/plugins/woocommerce/tests/e2e/tests/onboarding/setup-checklist.spec.ts b/plugins/woocommerce/tests/e2e/tests/onboarding/setup-checklist.spec.ts
index 18288040691..ede9a8d1d1c 100644
--- a/plugins/woocommerce/tests/e2e/tests/onboarding/setup-checklist.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/onboarding/setup-checklist.spec.ts
@@ -12,6 +12,10 @@ import {
 import { expect, tags, test as baseTest } from '../../fixtures/fixtures';
 import { ADMIN_STATE_PATH } from '../../playwright.config';

+// The wp-admin request that starts the WooCommerce.com OAuth hand-off.
+const isConnectRequest = ( url: URL ) =>
+	url.searchParams.get( 'wc-helper-connect' ) === '1';
+
 const test = baseTest.extend( {
 	storageState: ADMIN_STATE_PATH,

@@ -115,62 +119,116 @@ test(
 	}
 );

-test( 'Can connect to WooCommerce.com', async ( { page } ) => {
-	await page.goto( 'wp-admin/admin.php?page=wc-admin' );
-	await test.step( 'Go to WC Home and make sure the total sales is visible', async () => {
-		await page
-			.getByRole( 'menuitem', { name: 'Total sales' } )
-			.waitFor( { state: 'visible', timeout: 30000 } );
-	} );
-
-	await test.step( 'Go to the extensions tab and connect store', async () => {
-		const connectButton = page.getByRole( 'link', {
-			name: 'Connect',
-			exact: true,
+test(
+	'Can connect to WooCommerce.com',
+	{ tag: [ tags.SERVICES ] },
+	async ( { page, baseURL } ) => {
+		// Clicking Connect asks WooCommerce to start the OAuth hand-off: it checks a nonce, trades a
+		// token with WooCommerce.com server-side, then redirects the browser on to WooCommerce.com,
+		// which bounces it to WordPress.com to log in. Building that redirect is WooCommerce's job;
+		// what WordPress.com renders is not. Run the real request, but stop at the redirect, so the
+		// test doesn't depend on a third-party page that bot-challenges CI runners.
+		let authorizeUrl: string | undefined;
+		await page.route( isConnectRequest, async ( route ) => {
+			// Let the real request run — nonce check, server-side token exchange with
+			// WooCommerce.com and all — but read the redirect instead of following it.
+			const response = await route.fetch( { maxRedirects: 0 } );
+			authorizeUrl = response.headers().location;
+			// 204 leaves the browser where it is rather than sending it onward.
+			await route.fulfill( { status: 204, body: '' } );
 		} );

-		// Set up response waiter BEFORE navigation to avoid race condition
-		const waitForSubscriptionsResponse = page.waitForResponse(
-			( response ) =>
-				response
-					.url()
-					.includes( '/wp-json/wc/v3/marketplace/subscriptions' ) &&
-				response.status() === 200
-		);
+		await page.goto( 'wp-admin/admin.php?page=wc-admin' );
+		await test.step( 'Go to WC Home and make sure the total sales is visible', async () => {
+			await page
+				.getByRole( 'menuitem', { name: 'Total sales' } )
+				.waitFor( { state: 'visible', timeout: 30000 } );
+		} );

-		await page.goto(
-			'wp-admin/admin.php?page=wc-admin&tab=my-subscriptions&path=%2Fextensions'
-		);
+		await test.step( 'Go to the extensions tab and connect store', async () => {
+			const connectButton = page.getByRole( 'link', {
+				name: 'Connect',
+				exact: true,
+			} );
+
+			// Set up response waiter BEFORE navigation to avoid race condition
+			const waitForSubscriptionsResponse = page.waitForResponse(
+				( response ) =>
+					response
+						.url()
+						.includes(
+							'/wp-json/wc/v3/marketplace/subscriptions'
+						) && response.status() === 200
+			);

-		await expect(
-			page.getByText(
-				'Hundreds of vetted products and services. Unlimited potential.'
-			)
-		).toBeVisible( { timeout: 30000 } );
-		await expect(
-			page.getByRole( 'button', { name: 'My Subscriptions' } )
-		).toBeVisible();
-		await expect( connectButton ).toBeVisible();
+			await page.goto(
+				'wp-admin/admin.php?page=wc-admin&tab=my-subscriptions&path=%2Fextensions'
+			);

-		// Wait for the API response before checking button attributes
-		await waitForSubscriptionsResponse;
+			await expect(
+				page.getByText(
+					'Hundreds of vetted products and services. Unlimited potential.'
+				)
+			).toBeVisible( { timeout: 30000 } );
+			await expect(
+				page.getByRole( 'button', { name: 'My Subscriptions' } )
+			).toBeVisible();
+			await expect( connectButton ).toBeVisible();

-		await expect( connectButton ).toHaveAttribute(
-			'href',
-			/my-subscriptions/
-		);
-		await connectButton.click();
-	} );
-
-	await test.step( 'Check that we are sent to wp.com', async () => {
-		// Use polling assertion for URL check since page.url() is not auto-retrying
-		await expect
-			.poll( () => page.url(), { timeout: 30000 } )
-			.toContain( 'wordpress.com/log-in' );
-		await expect(
-			page.getByRole( 'heading', {
-				name: 'Log in to Woo with WordPress.com',
-			} )
-		).toBeVisible( { timeout: 30000 } );
-	} );
-} );
+			// Wait for the API response before checking button attributes
+			await waitForSubscriptionsResponse;
+
+			await expect( connectButton ).toHaveAttribute(
+				'href',
+				/my-subscriptions/
+			);
+			await connectButton.click();
+		} );
+
+		await test.step( 'Check we hand off to WooCommerce.com with a valid OAuth request', async () => {
+			await expect
+				.poll( () => authorizeUrl ?? '', { timeout: 30000 } )
+				.toContain( '/oauth/authorize' );
+
+			const authorize = new URL( authorizeUrl ?? '' );
+			const params = authorize.searchParams;
+			const storeOrigin = new URL( baseURL ?? '' ).origin;
+
+			// The hand-off goes to WooCommerce.com itself. A store can repoint the Helper API
+			// with the `woocommerce_helper_api_base` filter; the E2E environment leaves it at
+			// the default.
+			expect( authorize.origin ).toBe( 'https://woocommerce.com' );
+
+			// `secret` is minted by WooCommerce.com in reply to the server-side
+			// `oauth/request_token` call, so a non-empty value is proof that the
+			// handshake actually ran.
+			expect( params.get( 'secret' ) ).toBeTruthy();
+
+			// The store identifies itself, and says where to come back to afterwards.
+			expect( new URL( params.get( 'home_url' ) ?? '' ).origin ).toBe(
+				storeOrigin
+			);
+
+			const redirectUri = new URL( params.get( 'redirect_uri' ) ?? '' );
+			expect( redirectUri.origin ).toBe( storeOrigin );
+			expect( redirectUri.pathname ).toContain( 'admin.php' );
+			expect( redirectUri.searchParams.get( 'wc-helper-return' ) ).toBe(
+				'1'
+			);
+			expect(
+				redirectUri.searchParams.get( 'wc-helper-nonce' )
+			).toBeTruthy();
+
+			// Where the merchant lands in wp-admin once the connection completes.
+			const redirectAdminUrl = new URL(
+				params.get( 'redirect_admin_url' ) ?? ''
+			);
+			expect( redirectAdminUrl.origin ).toBe( storeOrigin );
+			expect( redirectAdminUrl.searchParams.get( 'page' ) ).toBe(
+				'wc-admin'
+			);
+
+			expect( [ '0', '1' ] ).toContain( params.get( 'wum-installed' ) );
+		} );
+	}
+);