Commit e557338793d for woocommerce
commit e557338793d40decd00ee9b58760a30ff4ca18c4
Author: Vlad Olaru <vlad.olaru@automattic.com>
Date: Thu Sep 24 18:00:59 2026 +0300
Revert "[tests] Demote onboarding profiler and add-product coverage below E2E" (#69053)
revert: Revert "[tests] Demote onboarding profiler and add-product coverage below E2E" (#68634)
This reverts commit db0994e7f1b031fe4a63f19cd2d52d2f8efaa769.
Rubik asked for the E2E migration's test changes in its areas to be
reverted until the team can review them. #68634 is one of the 11 PRs on
the revert list Rubik agreed on 2026-09-24.
The revert brings back the browser tests the PR removed or cut down and
removes the lower-layer tests it added in their place. Only test code
and changelog entries change; nothing ships.
Refs TESTOPS-288
Refs #68634
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
diff --git a/plugins/woocommerce/changelog/testops-288-onboarding b/plugins/woocommerce/changelog/testops-288-onboarding
deleted file mode 100644
index 52980851752..00000000000
--- a/plugins/woocommerce/changelog/testops-288-onboarding
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: dev
-Comment: Move onboarding profiler and add-product coverage below E2E; seven browser titles become three.
-
diff --git a/plugins/woocommerce/client/admin/client/core-profiler/pages/Plugins/test/Plugins.test.tsx b/plugins/woocommerce/client/admin/client/core-profiler/pages/Plugins/test/Plugins.test.tsx
index 0d23265d043..f4f9f41da21 100644
--- a/plugins/woocommerce/client/admin/client/core-profiler/pages/Plugins/test/Plugins.test.tsx
+++ b/plugins/woocommerce/client/admin/client/core-profiler/pages/Plugins/test/Plugins.test.tsx
@@ -1,8 +1,7 @@
/**
* External dependencies
*/
-import { render, screen, within } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
+import { render, screen, fireEvent } from '@testing-library/react';
import { Extension } from '@woocommerce/data';
/**
@@ -10,63 +9,53 @@ import { Extension } from '@woocommerce/data';
*/
import { computePluginsSelection, joinWithAnd, Plugins } from '../Plugins';
-const getPluginCheckbox = ( name: string ) => {
- const card = screen
- .getByRole( 'heading', { level: 3, name } )
- .closest( '.woocommerce-profiler-plugins-plugin-card' );
-
- expect( card ).not.toBeNull();
-
- return within( card! ).getByRole( 'checkbox' );
-};
-
describe( 'Plugins Component', () => {
const mockSendEvent = jest.fn();
const mockContext = {
pluginsAvailable: [
{
- slug: 'woocommerce-payments',
- name: 'WooPayments',
- label: 'WooPayments',
+ slug: 'plugin1',
+ name: 'Plugin 1',
+ label: 'Plugin 1',
is_activated: false,
description: '',
- key: 'woocommerce-payments',
+ key: 'plugin1',
image_url: '',
manage_url: '',
is_built_by_wc: false,
is_visible: true,
},
{
- slug: 'google-listings-and-ads',
- name: 'Google for WooCommerce',
- label: 'Google for WooCommerce',
- is_activated: false,
+ slug: 'plugin2',
+ name: 'Plugin 2',
+ label: 'Plugin 2',
+ is_activated: true,
description: '',
- key: 'google-listings-and-ads',
+ key: 'plugin2',
image_url: '',
manage_url: '',
is_built_by_wc: false,
is_visible: true,
},
{
- slug: 'jetpack',
- name: 'Jetpack',
- label: 'Jetpack',
- is_activated: true,
+ slug: 'plugin3',
+ name: 'Plugin 3',
+ label: 'Plugin 3',
+ is_activated: false,
description: '',
- key: 'jetpack',
+ key: 'plugin3',
image_url: '',
manage_url: '',
is_built_by_wc: false,
is_visible: true,
},
{
- slug: 'mailpoet',
- name: 'MailPoet',
- label: 'MailPoet',
+ slug: 'plugin4',
+ name: 'Plugin 4',
+ label: 'Plugin 4',
is_activated: false,
description: '',
- key: 'mailpoet:alt',
+ key: 'plugin4',
image_url: '',
manage_url: '',
is_built_by_wc: false,
@@ -77,9 +66,6 @@ describe( 'Plugins Component', () => {
pluginsInstallationErrors: [],
};
const navigationProgress = 80;
- beforeEach( () => {
- mockSendEvent.mockClear();
- } );
it( 'renders correctly', () => {
render(
@@ -94,17 +80,13 @@ describe( 'Plugins Component', () => {
/No commitment required – you can remove them at any time/
)
).toBeInTheDocument();
- expect(
- screen.getByRole( 'heading', { level: 3, name: 'WooPayments' } )
- ).toBeInTheDocument();
- expect(
- screen.getByText( 'Google for WooCommerce' )
- ).toBeInTheDocument();
- expect( screen.getByText( 'Jetpack' ) ).toBeInTheDocument();
- expect( screen.getByText( 'MailPoet' ) ).toBeInTheDocument();
+ expect( screen.getByText( 'Plugin 1' ) ).toBeInTheDocument();
+ expect( screen.getByText( 'Plugin 2' ) ).toBeInTheDocument();
+ expect( screen.getByText( 'Plugin 3' ) ).toBeInTheDocument();
+ expect( screen.getByText( 'Plugin 4' ) ).toBeInTheDocument();
} );
- it( 'selects each default inactive recommendation', () => {
+ it( 'handles plugin selection', () => {
render(
<Plugins
context={ mockContext }
@@ -112,52 +94,83 @@ describe( 'Plugins Component', () => {
navigationProgress={ navigationProgress }
/>
);
+ const checkboxLabel = screen.getByText( 'Plugin 1' );
+ fireEvent.click( checkboxLabel ); // because the checkbox is enabled by default, let's uncheck it
+ fireEvent.click( checkboxLabel ); // then check it
+ const checkboxLabel3 = screen.getByText( 'Plugin 3' );
+ fireEvent.click( checkboxLabel3 );
+ const checkboxLabel4 = screen.getByText( 'Plugin 4' ); // attempt to uncheck 4, but it shouldn't do anything since it is already unchecked
+ fireEvent.click( checkboxLabel4 );
+ const installButton = screen.getByText( 'Continue' );
+ fireEvent.click( installButton );
- expect( getPluginCheckbox( 'WooPayments' ) ).toBeChecked();
- expect( getPluginCheckbox( 'Google for WooCommerce' ) ).toBeChecked();
- expect( getPluginCheckbox( 'MailPoet' ) ).toBeChecked();
- expect(
- screen
- .getByText( 'Jetpack' )
- .closest( '.woocommerce-profiler-plugins-plugin-card' )
- ).toHaveClass( 'is-installed' );
+ expect( mockSendEvent ).toHaveBeenCalledWith( {
+ type: 'PLUGINS_INSTALLATION_REQUESTED',
+ payload: {
+ pluginsSelected: [ 'plugin1' ],
+ pluginsShown: [ 'plugin1', 'plugin2', 'plugin3', 'plugin4' ],
+ pluginsUnselected: [ 'plugin3', 'plugin4' ],
+ },
+ } );
} );
- it( 'completes without selecting when every plugin is installed', async () => {
+ it( 'handles case where all plugins are already installed', () => {
render(
<Plugins
context={ {
...mockContext,
pluginsAvailable: mockContext.pluginsAvailable.map(
- ( plugin ) => ( { ...plugin, is_activated: true } )
+ ( plugin ) => ( {
+ ...plugin,
+ is_activated: true,
+ } )
),
} }
sendEvent={ mockSendEvent }
navigationProgress={ navigationProgress }
/>
);
-
+ const plugin1Card = screen
+ .getByText( 'Plugin 1' )
+ .closest( '.woocommerce-profiler-plugins-plugin-card' );
+ expect( plugin1Card ).toHaveClass( 'is-installed' );
+ expect( plugin1Card ).toHaveTextContent( 'Installed' );
expect(
screen
- .getByRole( 'heading', { level: 3, name: 'WooPayments' } )
+ .getByText( 'Plugin 2' )
.closest( '.woocommerce-profiler-plugins-plugin-card' )
).toHaveTextContent( 'Installed' );
-
- await userEvent.click( screen.getByText( 'Continue' ) );
-
+ const continueButton = screen.getByText( 'Continue' );
+ fireEvent.click( continueButton );
expect( mockSendEvent ).toHaveBeenCalledWith( {
type: 'PLUGINS_PAGE_COMPLETED_WITHOUT_SELECTING_PLUGINS',
} );
} );
- it( 'retries the previous selection after an installation error', async () => {
+ it( 'initialises with all plugins selected when there were no errors previously', () => {
+ render(
+ <Plugins
+ context={ mockContext }
+ sendEvent={ mockSendEvent }
+ navigationProgress={ navigationProgress }
+ />
+ );
+ const checkboxLabels = screen.getAllByRole( 'checkbox' );
+ expect( checkboxLabels ).toHaveLength( 3 );
+ checkboxLabels.forEach( ( checkbox ) => {
+ expect( checkbox ).toBeChecked();
+ } );
+ } );
+
+ it( 'initialises with the previous selection correctly when there were errors previously', () => {
render(
<Plugins
context={ {
...mockContext,
+ pluginsAvailable: [ ...mockContext.pluginsAvailable ],
pluginsInstallationErrors: [
{
- plugin: 'woocommerce-payments',
+ plugin: 'plugin4',
error: 'Installation failed',
errorDetails: {
data: {
@@ -169,72 +182,36 @@ describe( 'Plugins Component', () => {
},
},
],
- pluginsSelected: [ 'woocommerce-payments', 'mailpoet:alt' ],
+ pluginsSelected: [ 'plugin4' ],
} }
sendEvent={ mockSendEvent }
navigationProgress={ navigationProgress }
/>
);
-
expect(
screen.getByText(
/Oops! We encountered a problem while installing/
)
).toBeInTheDocument();
- expect( getPluginCheckbox( 'WooPayments' ) ).toBeChecked();
- expect(
- getPluginCheckbox( 'Google for WooCommerce' )
- ).not.toBeChecked();
- expect( getPluginCheckbox( 'MailPoet' ) ).toBeChecked();
-
- await userEvent.click( screen.getByText( 'Please try again' ) );
-
- expect( mockSendEvent ).toHaveBeenCalledWith( {
- type: 'PLUGINS_INSTALLATION_REQUESTED',
- payload: {
- pluginsShown: [
- 'woocommerce-payments',
- 'google-listings-and-ads',
- 'jetpack',
- 'mailpoet',
- ],
- pluginsSelected: [ 'woocommerce-payments', 'mailpoet' ],
- pluginsUnselected: [ 'google-listings-and-ads' ],
- },
- } );
- } );
-
- it( 'submits normalized shown, selected, and unselected plugin keys', async () => {
- render(
- <Plugins
- context={ mockContext }
- sendEvent={ mockSendEvent }
- navigationProgress={ navigationProgress }
- />
- );
-
- await userEvent.click( getPluginCheckbox( 'MailPoet' ) );
- await userEvent.click( screen.getByText( 'Continue' ) );
-
- expect( mockSendEvent ).toHaveBeenCalledWith( {
- type: 'PLUGINS_INSTALLATION_REQUESTED',
- payload: {
- pluginsShown: [
- 'woocommerce-payments',
- 'google-listings-and-ads',
- 'jetpack',
- 'mailpoet',
- ],
- pluginsSelected: [
- 'woocommerce-payments',
- 'google-listings-and-ads',
- ],
- pluginsUnselected: [ 'mailpoet' ],
- },
- } );
+ const checkbox1 = screen
+ .getByText( 'Plugin 1' )
+ .closest( '.woocommerce-profiler-plugins-plugin-card' )
+ ?.querySelector( 'input[type="checkbox"]' );
+ expect( checkbox1 ).not.toBeChecked();
+ const checkbox3 = screen
+ .getByText( 'Plugin 3' )
+ .closest( '.woocommerce-profiler-plugins-plugin-card' )
+ ?.querySelector( 'input[type="checkbox"]' );
+ expect( checkbox3 ).not.toBeChecked();
+ const checkbox4 = screen
+ // use role because error message also contains the plugin name
+ .getByRole( 'heading', { level: 3, name: 'Plugin 4' } )
+ .closest( '.woocommerce-profiler-plugins-plugin-card' )
+ ?.querySelector( 'input[type="checkbox"]' );
+ expect( checkbox4 ).toBeChecked();
} );
- it( 'handles skip action', async () => {
+ it( 'handles skip action', () => {
render(
<Plugins
context={ mockContext }
@@ -243,7 +220,7 @@ describe( 'Plugins Component', () => {
/>
);
const skipButton = screen.getByText( 'Skip this step' );
- await userEvent.click( skipButton );
+ fireEvent.click( skipButton );
expect( mockSendEvent ).toHaveBeenCalledWith( {
type: 'PLUGINS_PAGE_SKIPPED',
} );
diff --git a/plugins/woocommerce/tests/e2e/fixtures/site.setup.ts b/plugins/woocommerce/tests/e2e/fixtures/site.setup.ts
index 595eb096dd8..9aef9137c0b 100644
--- a/plugins/woocommerce/tests/e2e/fixtures/site.setup.ts
+++ b/plugins/woocommerce/tests/e2e/fixtures/site.setup.ts
@@ -87,21 +87,6 @@ setup( 'setup site', async ( { baseURL, restApi } ) => {
await skipOnboardingWizard();
} );
- await setup.step( 'dismiss email improvements prompts', async () => {
- await setOption(
- request,
- baseURL,
- 'woocommerce_admin_dismissed_email_improvements_modal',
- 'yes'
- );
- await setOption(
- request,
- baseURL,
- 'woocommerce_admin_dismissed_try_email_improvements_modal',
- 'yes'
- );
- } );
-
await setup.step( 'determine if multisite', async () => {
const response = await restApi.get( `${ WC_API_PATH }/system_status` );
const { environment } = response.data;
diff --git a/plugins/woocommerce/tests/e2e/tests/onboarding/add-product-task.spec.ts b/plugins/woocommerce/tests/e2e/tests/onboarding/add-product-task.spec.ts
index d3a5b8398a3..5bf49547d42 100644
--- a/plugins/woocommerce/tests/e2e/tests/onboarding/add-product-task.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/onboarding/add-product-task.spec.ts
@@ -41,9 +41,6 @@ const show_task_list = async ( restApi: any, task_list_name: string ) => {
test.describe( 'Add Product Task', () => {
test.use( { storageState: ADMIN_STATE_PATH } );
- // Ids this spec creates, so teardown can delete its own products and nobody else's.
- const createdProductIds: number[] = [];
-
test.beforeAll( async ( { restApi } ) => {
const productIds = [];
@@ -83,72 +80,19 @@ test.describe( 'Add Product Task', () => {
} );
} );
- test.afterEach( async ( { restApi } ) => {
- // The test leaves a product behind if it fails before its own delete, and leaves the
- // setup task list hidden if it fails between hiding and showing it. Both are torn down
- // here rather than in the body so that a failure reports the assertion that failed
- // instead of a cleanup error raised on the way out.
- //
- // Only this spec's own products are deleted. The site is shared with the rest of the
- // run, so sweeping every id the products endpoint returns would take other specs'
- // fixtures with it.
- // Copy rather than drain: an id stays tracked until the batch confirms it deleted, so a
- // later afterEach retries whatever this one could not remove.
- const idsToDelete = [ ...createdProductIds ];
- // Every teardown call runs before anything is asserted: a failed delete must not stop
- // the task list from being unhidden, or the rest of this serial project inherits it.
- const deleteResponse = idsToDelete.length
- ? await restApi.post( `${ WC_API_PATH }/products/batch`, {
- delete: idsToDelete,
- } )
- : null;
- const taskListShown = await show_task_list( restApi, 'setup' );
-
- // On a passing run the test deletes its own product, so there is nothing left here
- // and no response to check. Asserting 200 unconditionally would pass whether a
- // delete happened or not.
- if ( deleteResponse !== null ) {
- expect( deleteResponse.status ).toBe( 200 );
- // The batch endpoint reports per-item failures in the body and still
- // returns 200, so the status on its own says nothing about whether the
- // products actually went.
- // An extension filter can reshape these results, so an entry that is missing,
- // errored or carries no id counts as not deleted rather than throwing here.
- const batchDeleteResults = deleteResponse.data?.delete;
- const deleted: Array< { id?: number; error?: unknown } | null > =
- Array.isArray( batchDeleteResults ) ? batchDeleteResults : [];
- const confirmedIds = new Set(
- deleted
- .filter( ( item ) => item && ! item.error )
- .map( ( item ) => item?.id )
- );
- const unconfirmedIds = idsToDelete.filter(
- ( id ) => ! confirmedIds.has( id )
- );
- createdProductIds.splice(
- 0,
- createdProductIds.length,
- ...unconfirmedIds
- );
- expect( deleted ).toHaveLength( idsToDelete.length );
- expect( unconfirmedIds ).toEqual( [] );
- }
- expect( taskListShown ).toBe( true );
- } );
-
test.afterAll( async ( { restApi } ) => {
await restApi.post( `${ WC_ADMIN_API_PATH }/onboarding/profile`, {
skipped: false,
} );
} );
- test( 'Products page redirects to add product task when no products exist', async ( {
+ test( 'Add product task displays options for different product types', async ( {
page,
- restApi,
} ) => {
- const productName = `Add product task product ${ Date.now() }`;
-
+ // Navigate to the task list
await page.goto( 'wp-admin/admin.php?page=wc-admin&task=products' );
+
+ // Verify product type options are displayed
await expect(
page.getByRole( 'menuitem', { name: 'Physical product' } )
).toBeVisible();
@@ -165,7 +109,23 @@ test.describe( 'Add Product Task', () => {
/wp-has-current-submenu/
);
+ await page
+ .getByRole( 'menuitem', { name: 'Physical product' } )
+ .click();
+ await expect(
+ page.locator(
+ '#menu-posts-product .wp-submenu li.current > a[href="post-new.php?post_type=product"]'
+ )
+ ).toBeVisible();
+ } );
+
+ test( 'Products page redirects to add product task when no products exist', async ( {
+ page,
+ } ) => {
+ // Navigate to All Products page
await page.goto( 'wp-admin/edit.php?post_type=product' );
+
+ // Verify redirect to add product task
await expect( page ).toHaveURL(
/.+path=%2Fadd-product.+task=products/
);
@@ -192,11 +152,57 @@ test.describe( 'Add Product Task', () => {
await expect(
page.locator( '#toplevel_page_woocommerce' )
).toHaveClass( /wp-has-current-submenu/ );
+ } );
+
+ test( 'Products page shows products table when products exist', async ( {
+ page,
+ restApi,
+ } ) => {
+ // Create a test product
+ await restApi.post( `${ WC_API_PATH }/products`, {
+ name: 'Test Product',
+ type: 'simple',
+ regular_price: '10.00',
+ } );
+
+ // Navigate to All Products page
+ await page.goto( 'wp-admin/edit.php?post_type=product' );
+
+ // Verify products table is visible
+ await expect( page.locator( '.wp-list-table' ) ).toBeVisible();
+ await expect(
+ page.getByRole( 'columnheader', { name: 'Name' } )
+ ).toHaveCount( 2 );
+ await expect(
+ page.getByRole( 'columnheader', { name: 'SKU' } )
+ ).toHaveCount( 2 );
+ await expect(
+ page.getByRole( 'columnheader', { name: 'Price' } )
+ ).toHaveCount( 2 );
+ await expect(
+ page.locator( '.wp-list-table > tbody > tr' )
+ ).toHaveCount( 1 );
+
+ // Clean up - delete test product
+ const products = await restApi.get( `${ WC_API_PATH }/products` );
+ for ( const product of products.data ) {
+ await restApi.delete( `${ WC_API_PATH }/products/${ product.id }`, {
+ force: true,
+ } );
+ }
+ } );
+ test( 'Products page redirects to add product task when no products exist and task list is hidden', async ( {
+ page,
+ restApi,
+ } ) => {
+ // Hide the task list
expect( await hide_task_list( restApi, 'setup' ) ).toBe( true );
+ // Navigate to All Products page
await page.goto( 'wp-admin/edit.php?post_type=product' );
+ // Verify redirect to add product task
await expect( page ).toHaveURL(
/.+path=%2Fadd-product.+task=products/
);
@@ -210,51 +216,7 @@ test.describe( 'Add Product Task', () => {
page.getByRole( 'menuitem', { name: 'Grouped product' } )
).toBeVisible();
+ // Reset task list to visible
expect( await show_task_list( restApi, 'setup' ) ).toBe( true );
-
- const productResponse = await restApi.post(
- `${ WC_API_PATH }/products`,
- {
- name: productName,
- type: 'simple',
- regular_price: '10.00',
- }
- );
- const createdProductId = productResponse.data.id;
- createdProductIds.push( createdProductId );
- expect( productResponse.status ).toBe( 201 );
- expect(
- Number.isSafeInteger( createdProductId ) && createdProductId > 0
- ).toBe( true );
-
- await page.goto( 'wp-admin/edit.php?post_type=product' );
-
- await expect( page.locator( '.wp-list-table' ) ).toBeVisible();
- await expect(
- page.getByRole( 'link', { name: productName, exact: true } )
- ).toBeVisible();
-
- const deleteResponse = await restApi.delete(
- `${ WC_API_PATH }/products/${ createdProductId }`,
- { force: true }
- );
- expect( deleteResponse.status ).toBe( 200 );
- // Stop tracking only once the delete is confirmed. Splicing first means a
- // failed delete leaves a published product that the afterEach sweep no
- // longer knows about -- in a spec whose whole premise is an empty catalog.
- createdProductIds.splice(
- createdProductIds.indexOf( createdProductId ),
- 1
- );
-
- await page.goto( 'wp-admin/admin.php?page=wc-admin&task=products' );
- await page
- .getByRole( 'menuitem', { name: 'Physical product' } )
- .click();
- await expect(
- page.locator(
- '#menu-posts-product .wp-submenu li.current > a[href="post-new.php?post_type=product"]'
- )
- ).toBeVisible();
} );
} );
diff --git a/plugins/woocommerce/tests/e2e/tests/onboarding/onboarding-wizard.spec.ts b/plugins/woocommerce/tests/e2e/tests/onboarding/onboarding-wizard.spec.ts
index 35bb6552996..4dbb6b59e94 100644
--- a/plugins/woocommerce/tests/e2e/tests/onboarding/onboarding-wizard.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/onboarding/onboarding-wizard.spec.ts
@@ -2,7 +2,7 @@
* External dependencies
*/
import { request } from '@playwright/test';
-import { WC_ADMIN_API_PATH } from '@woocommerce/e2e-utils-playwright';
+import type { Page } from '@playwright/test';
/**
* Internal dependencies
@@ -12,6 +12,12 @@ import { setOption } from '../../utils/options';
import { setComingSoon } from '../../utils/coming-soon';
import { ADMIN_STATE_PATH } from '../../playwright.config';
+const getPluginLocator = ( page: Page, slug: string ) => {
+ return page.locator(
+ `.woocommerce-profiler-plugins-plugin-card[data-slug="${ slug }"]`
+ );
+};
+
test.use( { storageState: ADMIN_STATE_PATH } );
test.afterAll( async ( { baseURL } ) => {
@@ -22,9 +28,7 @@ test.describe(
'Store owner can complete the core profiler',
{ tag: tags.SKIP_ON_EXTERNAL_ENV },
() => {
- // Completing the profiler saves the store location, and the location list leaves out
- // the selected option. Reset it before every test so each one can pick the same location.
- test.beforeEach( async ( { baseURL } ) => {
+ test.beforeAll( async ( { baseURL } ) => {
try {
await setOption(
request,
@@ -32,12 +36,6 @@ test.describe(
'woocommerce_remote_variant_assignment',
'60'
);
- await setOption(
- request,
- baseURL,
- 'woocommerce_default_country',
- 'US:CA'
- );
} catch ( error ) {
console.log( error );
}
@@ -122,6 +120,12 @@ test.describe(
name: 'Get a boost with our free features',
} )
).toBeVisible();
+ // check that WooPayments is displayed because Australia is a supported country
+ await expect(
+ page.getByRole( 'heading', {
+ name: 'Get paid with WooPayments',
+ } )
+ ).toBeVisible();
// skip this step so that no extensions are installed
await page
.getByRole( 'button', { name: 'Skip this step' } )
@@ -143,8 +147,7 @@ test.describe(
// dashboard shown
await expect(
page.getByRole( 'heading', {
- name: 'Home',
- exact: true,
+ name: 'Welcome to WooCommerce Core E2E Test Suite',
} )
).toBeVisible();
@@ -199,70 +202,18 @@ test.describe(
} );
} );
- test( 'Can complete the core profiler with an extension selected', async ( {
+ test( 'Can complete the core profiler installing default extensions', async ( {
page,
- restApi,
} ) => {
test.skip(
!! process.env.IS_MULTISITE,
'Test not working on a multisite setup, see https://github.com/woocommerce/woocommerce/issues/55066'
);
-
- // Installing from WordPress.org would make this title depend on an outside
- // service and leave real plugins on the shared site, so the two requests the
- // profiler sends for the selection are answered here with the REST
- // controller's success shape. What stays under test is the browser half:
- // the selection reaches those requests, and a successful install finishes
- // the profiler on the home screen.
- const slug = 'google-listings-and-ads';
- const pluginRequests: Array< { path: string; body: unknown } > = [];
- await page.route(
- ( url ) =>
- /\/wc-admin\/plugins\/(install|activate)(?:[?&]|$)/.test(
- decodeURIComponent( url.toString() )
- ),
- async ( route ) => {
- const path = /\/wc-admin\/plugins\/activate(?:[?&]|$)/.test(
- decodeURIComponent( route.request().url() )
- )
- ? 'activate'
- : 'install';
- pluginRequests.push( {
- path,
- body: route.request().postDataJSON(),
- } );
- const errors = { errors: {}, error_data: {} };
- await route.fulfill( {
- json:
- path === 'install'
- ? {
- data: {
- installed: [ slug ],
- results: { [ slug ]: true },
- install_time: { [ slug ]: 1 },
- plugin_details: {},
- },
- errors,
- success: true,
- }
- : {
- data: {
- activated: [ slug ],
- active: [ slug ],
- plugin_details: {},
- },
- errors,
- success: true,
- },
- } );
- }
- );
-
await page.goto(
'wp-admin/admin.php?page=wc-admin&path=%2Fsetup-wizard'
);
- await test.step( 'Walk the profiler to the extensions step', async () => {
+ await test.step( 'Intro page and opt in to data sharing', async () => {
await expect(
page.getByRole( 'heading', { name: 'Welcome to Woo!' } )
).toBeVisible();
@@ -274,7 +225,9 @@ test.describe(
await page
.getByRole( 'button', { name: 'Set up my store' } )
.click();
+ } );
+ await test.step( 'User profile information', async () => {
await expect(
page.getByRole( 'heading', {
name: 'Which one of these best describes you?',
@@ -282,32 +235,42 @@ test.describe(
).toBeVisible();
await page
.getByRole( 'radio' )
- .filter( { hasText: 'just starting my business' } )
+ .filter( { hasText: 'already selling' } )
+ .click();
+ await page.getByLabel( 'Select an option' ).click();
+ await page
+ .getByRole( 'option' )
+ .filter( { hasText: 'selling offline' } )
.click();
await page.getByRole( 'button', { name: 'Continue' } ).click();
+ } );
+ await test.step( 'Business Information', async () => {
await expect(
page.getByRole( 'heading', {
name: 'Tell us a bit about your store',
} )
).toBeVisible();
+ await expect(
+ page.getByPlaceholder( 'Ex. My awesome store' )
+ ).toHaveValue( 'WooCommerce Core E2E Test Suite' );
await page
.locator(
'form.woocommerce-profiler-business-information-form > div > div > div > div > input'
)
.first()
.click();
+ // select food and drink
await page
- .getByRole( 'option', { name: 'Clothing and accessories' } )
+ .getByRole( 'option', { name: 'Food and drink' } )
.click();
- // The location field is required.
+ // select a WooPayments incompatible location
await page.getByRole( 'combobox' ).last().click();
- await page.getByRole( 'combobox' ).last().fill( 'Australia' );
+ await page.getByRole( 'combobox' ).last().fill( 'Afghanistan' );
await page
- .getByRole( 'option', {
- name: 'Australia — Northern Territory',
- } )
+ .getByRole( 'option', { name: 'Afghanistan' } )
.click();
+
await page
.getByPlaceholder( 'wordpress@example.com' )
.fill( 'merchant@example.com' );
@@ -315,57 +278,208 @@ test.describe(
await page.getByRole( 'button', { name: 'Continue' } ).click();
} );
- await test.step( 'Select one extension and continue', async () => {
+ await test.step( 'Extensions -- install some suggested extensions', async () => {
await expect(
page.getByRole( 'heading', {
name: 'Get a boost with our free features',
} )
).toBeVisible();
- // Every recommendation that is not active starts selected. Clear them all,
- // then pick one that needs no Jetpack connection, so the profiler ends on
- // the home screen rather than the Jetpack authorization page.
- const cards = page.locator(
- '.woocommerce-profiler-plugins-plugin-card'
- );
- await expect( cards.first() ).toBeVisible();
- for ( const checkbox of await cards
- .getByRole( 'checkbox' )
- .all() ) {
- await checkbox.uncheck();
- }
+ // check that WooPayments is not displayed because Afghanistan is not a supported country
await expect(
- cards.locator( 'input[type="checkbox"]:checked' )
- ).toHaveCount( 0 );
- await cards
- .and( page.locator( `[data-slug="${ slug }"]` ) )
+ page.getByRole( 'heading', {
+ name: 'Get paid with WooPayments',
+ } )
+ ).not.toBeAttached();
+
+ // select and install the rest of the extensions
+ try {
+ await page
+ .getByText(
+ 'Boost content creation with Jetpack AI AssistantSave time on content creation'
+ )
+ .getByRole( 'checkbox' )
+ .uncheck( { timeout: 2000 } );
+ } catch ( e ) {
+ console.log(
+ 'Checkbox not present for Jetpack AI Assistant'
+ );
+ }
+ try {
+ await getPluginLocator( page, 'pinterest-for-woocommerce' )
+ .getByRole( 'checkbox' )
+ .check( { timeout: 2000 } );
+ } catch ( e ) {
+ console.log( 'Checkbox not present for Pinterest' );
+ }
+ try {
+ await getPluginLocator( page, 'mailchimp-for-woocommerce' )
+ .getByRole( 'checkbox' )
+ .uncheck( { timeout: 2000 } );
+ } catch ( e ) {
+ console.log( 'Checkbox not present for MailChimp' );
+ }
+ await getPluginLocator( page, 'google-listings-and-ads' )
.getByRole( 'checkbox' )
- .check();
+ .check( { timeout: 2000 } );
await page.getByRole( 'button', { name: 'Continue' } ).click();
} );
- await test.step( 'Confirm the selection was installed and the profiler finished', async () => {
+ await test.step( 'Confirm that core profiler was completed and a couple of default extensions installed', async () => {
+ page.on( 'dialog', ( dialog ) => dialog.accept() );
+ // intermediate page shown
+ // the next two are soft assertions because depending on the extensions selected, they may or may not appear
+ // and we want the test to complete in order for cleanup to happen
+ await expect
+ .soft(
+ page
+ .getByRole( 'heading' )
+ .filter( { hasText: 'get your features ready' } )
+ )
+ .toBeVisible( { timeout: 30000 } );
+ await expect
+ .soft(
+ page
+ .getByRole( 'heading' )
+ .filter( { hasText: 'Extending your store' } )
+ )
+ .toBeVisible( { timeout: 30000 } );
+ // dashboard shown
+ await expect(
+ page.getByRole( 'heading', {
+ name: 'Welcome to WooCommerce Core E2E Test Suite',
+ } )
+ ).toBeVisible( { timeout: 30000 } );
+ // go to the plugins page to make sure that extensions were installed
+ await page.goto( 'wp-admin/plugins.php?plugin_status=active' );
+ await expect(
+ page.getByRole( 'heading', {
+ name: 'Plugins',
+ exact: true,
+ } )
+ ).toBeVisible();
+ // confirm that the optional plugins are present
+ try {
+ await expect(
+ page.locator(
+ `[data-slug="pinterest-for-woocommerce"]`
+ )
+ ).toBeVisible();
+ } catch {
+ console.log(
+ `Pinterest is not found or not visible on the page`
+ );
+ }
+
+ try {
+ await expect(
+ page.locator( `[data-slug="google-listings-and-ads"]` )
+ ).toBeVisible();
+ } catch {
+ console.log(
+ `Google for WooCommerce is not found or not visible on the page`
+ );
+ }
+
+ try {
+ await expect(
+ page.locator(
+ `[data-slug="mailchimp-for-woocommerce"]`
+ )
+ ).toBeHidden();
+ } catch {
+ console.log( `MailChimp is found on the page` );
+ }
+
+ try {
+ await expect(
+ page.locator( `[data-slug="jetpack"]` )
+ ).toBeHidden();
+ } catch {
+ console.log( `Jetpack is found on the page` );
+ }
+ } );
+
+ await test.step( 'Confirm that information from core profiler saved', async () => {
+ await page.goto( 'wp-admin/admin.php?page=wc-settings' );
await expect(
- page.getByRole( 'heading', { name: 'Home', exact: true } )
+ page.getByRole( 'textbox', { name: 'Afghanistan' } )
).toBeVisible();
+ await expect(
+ page.getByRole( 'textbox', { name: 'Afghan afghani (؋)' } )
+ ).toBeVisible();
+ await expect(
+ page.getByRole( 'textbox', { name: 'Left with space' } )
+ ).toBeVisible();
+ await expect(
+ page.getByLabel( 'Thousand separator', { exact: true } )
+ ).toHaveValue( '.' );
+ await expect(
+ page.getByLabel( 'Decimal separator', { exact: true } )
+ ).toHaveValue( ',' );
+ await expect(
+ page.getByLabel( 'Number of decimals' )
+ ).toHaveValue( '0' );
+ } );
- expect( pluginRequests ).toEqual( [
- {
- path: 'install',
- body: {
- plugins: slug,
- async: false,
- source: 'core-profiler',
- },
- },
- { path: 'activate', body: { plugins: slug } },
- ] );
-
- // The profiler records what the install reported, which only happens on
- // the success path: a failed install returns to the extensions step.
- const { data: profile } = await restApi.get(
- `${ WC_ADMIN_API_PATH }/onboarding/profile`
- );
- expect( profile.business_extensions ).toEqual( [ slug ] );
+ await test.step( 'Clean up installed extensions', async () => {
+ const deactivateAndDeletePlugin = async ( slug: string ) => {
+ await page.goto( 'wp-admin/plugins.php' );
+ const pluginRow = page.locator(
+ `tr[data-slug="${ slug }"]`
+ );
+
+ // Skip if plugin is not present
+ if ( ! ( await pluginRow.isVisible() ) ) {
+ return;
+ }
+
+ // Deactivate if active
+ const deactivateLink = pluginRow.getByRole( 'link', {
+ name: 'Deactivate',
+ exact: true,
+ } );
+ if ( await deactivateLink.isVisible() ) {
+ await deactivateLink.click();
+ await expect(
+ page.getByText( 'Plugin deactivated.' )
+ ).toBeVisible();
+ }
+
+ // Delete plugin
+ const deleteLink = pluginRow.getByRole( 'link', {
+ name: 'Delete',
+ exact: true,
+ } );
+ if ( await deleteLink.isVisible() ) {
+ try {
+ await deleteLink.click();
+ await expect(
+ page.getByText( 'was successfully deleted.' )
+ ).toBeVisible( { timeout: 5000 } );
+ } catch ( e ) {
+ await page
+ .getByText( 'Yes, delete these files and data' )
+ .click();
+ await page
+ .getByText(
+ 'The selected plugin has been deleted.'
+ )
+ .waitFor();
+ }
+ }
+ };
+
+ await deactivateAndDeletePlugin( 'google-listings-and-ads' );
+ await deactivateAndDeletePlugin( 'pinterest-for-woocommerce' );
+ } );
+
+ await test.step( 'Confirm that the store is in coming soon mode after completing the core profiler', async () => {
+ await page.goto( 'wp-admin/admin.php?page=wc-admin' );
+ await expect(
+ page
+ .getByRole( 'menuitem' )
+ .filter( { hasText: 'coming soon' } )
+ ).toBeVisible();
} );
} );
}
@@ -408,8 +522,7 @@ test.describe(
await expect(
page.getByRole( 'heading', {
- name: 'Home',
- exact: true,
+ name: 'Welcome to WooCommerce Core E2E Test Suite',
} )
).toBeVisible();
diff --git a/plugins/woocommerce/tests/php/src/Admin/Features/OnboardingTasks/Tasks/ProductsTest.php b/plugins/woocommerce/tests/php/src/Admin/Features/OnboardingTasks/Tasks/ProductsTest.php
deleted file mode 100644
index 72ab4f1176c..00000000000
--- a/plugins/woocommerce/tests/php/src/Admin/Features/OnboardingTasks/Tasks/ProductsTest.php
+++ /dev/null
@@ -1,188 +0,0 @@
-<?php
-
-declare( strict_types = 1 );
-
-namespace Automattic\WooCommerce\Tests\Admin\Features\OnboardingTasks\Tasks;
-
-use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
-use Automattic\WooCommerce\Admin\Features\OnboardingTasks\TaskList;
-use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\Products;
-use WC_Product_Simple;
-use WC_Unit_Test_Case;
-
-/**
- * Tests for the Products onboarding task.
- */
-class ProductsTest extends WC_Unit_Test_Case {
-
- /**
- * The System Under Test.
- *
- * @var Products
- */
- private $sut;
-
- /**
- * The parent task list used by the Products task.
- *
- * @var TaskList
- */
- private $task_list;
-
- /**
- * The redirect interceptor registered for the test.
- *
- * @var callable
- */
- private $redirect_interceptor;
-
- /**
- * The location of an intercepted redirect.
- *
- * @var string|null
- */
- private $redirect_location;
-
- /**
- * The status of an intercepted redirect.
- *
- * @var int|null
- */
- private $redirect_status;
-
- /**
- * Set up test fixtures.
- */
- public function setUp(): void {
- parent::setUp();
-
- delete_transient( Products::HAS_PRODUCT_TRANSIENT );
- delete_option( Task::COMPLETED_OPTION );
- update_option( TaskList::HIDDEN_OPTION, array( 'setup' ) );
-
- $this->redirect_location = null;
- $this->redirect_status = null;
- $this->redirect_interceptor = function ( $location, $status ) {
- $this->redirect_location = (string) $location;
- $this->redirect_status = (int) $status;
-
- throw new \RuntimeException( 'Redirect intercepted.' );
- };
- add_filter( 'wp_redirect', $this->redirect_interceptor, 10, 2 );
-
- $this->task_list = new TaskList(
- array(
- 'id' => 'setup',
- 'hidden_id' => 'setup',
- )
- );
- $this->sut = new Products( $this->task_list );
- }
-
- /**
- * @testdox Redirects an empty product list through the registered current_screen hook.
- */
- public function test_redirects_empty_product_list_through_registered_current_screen_hook(): void {
- $this->trigger_current_screen( 'edit-product' );
-
- $this->assertSame(
- admin_url( 'admin.php?page=wc-admin&path=/add-product&task=products' ),
- $this->redirect_location,
- 'An empty product list should redirect to the Products task.'
- );
- $this->assertSame( 302, $this->redirect_status, 'The Products task redirect should use the default 302 status.' );
- }
-
- /**
- * @testdox Does not redirect non-product screens.
- *
- * @dataProvider non_product_screen_provider
- *
- * @param string $screen_id Screen ID to trigger.
- */
- public function test_does_not_redirect_non_product_screens( string $screen_id ): void {
- $this->trigger_current_screen( $screen_id );
-
- $this->assertNull( $this->redirect_location, "The {$screen_id} screen should not redirect." );
- $this->assertNull( $this->redirect_status, "The {$screen_id} screen should not set a redirect status." );
- }
-
- /**
- * Provides named non-product screens.
- *
- * @return array<string, array{screen_id: string}>
- */
- public function non_product_screen_provider(): array {
- return array(
- 'dashboard' => array( 'screen_id' => 'dashboard' ),
- 'non-product posts' => array( 'screen_id' => 'edit-post' ),
- );
- }
-
- /**
- * @testdox Does not redirect the product list when a published product exists.
- */
- public function test_does_not_redirect_product_list_when_published_product_exists(): void {
- $this->create_product( 'publish' );
-
- $this->trigger_current_screen( 'edit-product' );
-
- $this->assertNull( $this->redirect_location, 'A published product should keep the product list accessible.' );
- $this->assertNull( $this->redirect_status, 'A published product should not produce a redirect status.' );
- }
-
- /**
- * @testdox Redirects the product list when only an auto-draft product exists.
- */
- public function test_redirects_product_list_when_only_auto_draft_product_exists(): void {
- $this->create_product( 'auto-draft' );
-
- $this->trigger_current_screen( 'edit-product' );
-
- $this->assertSame(
- admin_url( 'admin.php?page=wc-admin&path=/add-product&task=products' ),
- $this->redirect_location,
- 'An auto-draft alone should not keep the product list accessible.'
- );
- $this->assertSame( 302, $this->redirect_status, 'The auto-draft redirect should use the default 302 status.' );
- }
-
- /**
- * @testdox Is always accessible when its parent task list is hidden.
- */
- public function test_is_always_accessible_when_parent_task_list_is_hidden(): void {
- $this->assertTrue( $this->task_list->is_hidden(), 'The Products task parent list should use the persisted hidden-list state.' );
- $this->assertTrue( $this->sut->is_always_accessible(), 'The Products task should remain accessible when its parent list is hidden.' );
- }
-
- /**
- * Creates a real simple product with the requested status.
- *
- * @param string $status Product status.
- * @return int Product ID.
- */
- private function create_product( string $status ): int {
- $product = new WC_Product_Simple();
- $product->set_name( "Products task {$status} fixture" );
- $product->set_status( $status );
- $product_id = $product->save();
- $this->assertGreaterThan( 0, $product_id, 'The product fixture should persist before the task reads it.' );
-
- return $product_id;
- }
-
- /**
- * Triggers the registered current_screen callbacks for a WordPress screen.
- *
- * @param string $screen_id Screen ID to trigger.
- */
- private function trigger_current_screen( string $screen_id ): void {
- try {
- set_current_screen( $screen_id );
- } catch ( \RuntimeException $exception ) {
- if ( 'Redirect intercepted.' !== $exception->getMessage() ) {
- throw $exception;
- }
- }
- }
-}
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/RemoteFreeExtensions/DefaultFreeExtensionsTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/RemoteFreeExtensions/DefaultFreeExtensionsTest.php
index dd20fbb08ca..bc42af76103 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Admin/RemoteFreeExtensions/DefaultFreeExtensionsTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/RemoteFreeExtensions/DefaultFreeExtensionsTest.php
@@ -161,53 +161,6 @@ class DefaultFreeExtensionsTest extends WC_Unit_Test_Case {
);
}
- /**
- * @testdox Core profiler WooPayments visibility should follow the store country.
- * @dataProvider core_profiler_woocommerce_payments_visibility_provider
- *
- * @param string $country Store country and optional state.
- * @param bool $should_include Whether WooPayments should be recommended.
- */
- public function test_core_profiler_woocommerce_payments_visibility_by_country( string $country, bool $should_include ): void {
- update_option( 'woocommerce_default_country', $country );
- update_option( 'woocommerce_store_address', '1 Test Street' );
- update_option( 'woocommerce_remote_variant_assignment', 60 );
- update_option( 'active_plugins', array() );
- update_option( 'woocommerce_onboarding_profile', array() );
-
- $results = EvaluateExtension::evaluate_bundles(
- DefaultFreeExtensions::get_all(),
- array( 'obw/core-profiler' )
- );
-
- $this->assertSame( array(), $results['errors'], 'The real core profiler bundle should evaluate without errors.' );
- $this->assertCount( 1, $results['bundles'], 'Only the core profiler bundle should be evaluated.' );
- $plugin_slugs = array_map(
- static function ( $plugin ) {
- return $plugin->key;
- },
- $results['bundles'][0]['plugins']
- );
-
- if ( $should_include ) {
- $this->assertContains( 'woocommerce-payments', $plugin_slugs );
- } else {
- $this->assertNotContains( 'woocommerce-payments', $plugin_slugs );
- }
- }
-
- /**
- * Store countries for core profiler WooPayments visibility.
- *
- * @return array<string, array{string, bool}>
- */
- public function core_profiler_woocommerce_payments_visibility_provider(): array {
- return array(
- 'AU:NT' => array( 'AU:NT', true ),
- 'AF' => array( 'AF', false ),
- );
- }
-
/**
* Evaluates bundles passed as argument and extracts keys of recommended plugins.
*