diff --git a/js/src/hooks/useGoogleAdsAccount.js b/js/src/hooks/useGoogleAdsAccount.js
index 9760ce87c8..16db5002f1 100644
--- a/js/src/hooks/useGoogleAdsAccount.js
+++ b/js/src/hooks/useGoogleAdsAccount.js
@@ -9,6 +9,7 @@ import { useCallback } from '@wordpress/element';
*/
import { STORE_KEY } from '.~/data/constants';
import { useAppDispatch } from '.~/data';
+import { GOOGLE_ADS_ACCOUNT_STATUS } from '.~/constants';
import useGoogleAccount from './useGoogleAccount';
const googleAdsAccountSelector = 'getGoogleAdsAccount';
@@ -36,6 +37,15 @@ const useGoogleAdsAccount = () => {
googleAdsAccountSelector
);
+ // The "incomplete" status means there is a connected account but billing is not yet set
+ // so it's considered as `true`.
+ // The main reason for not using a naming like `isGoogleAdsConnected` here is to make
+ // a slight distinction from the "connected" status.
+ const hasGoogleAdsConnection = [
+ GOOGLE_ADS_ACCOUNT_STATUS.CONNECTED,
+ GOOGLE_ADS_ACCOUNT_STATUS.INCOMPLETE,
+ ].includes( acc?.status );
+
return {
googleAdsAccount: acc,
isResolving: isResolvingGoogleAdsAccount,
@@ -43,6 +53,7 @@ const useGoogleAdsAccount = () => {
hasFinishedResolution: selector.hasFinishedResolution(
googleAdsAccountSelector
),
+ hasGoogleAdsConnection,
};
},
[ google, isResolving, refetchGoogleAdsAccount ]
diff --git a/js/src/setup-mc/setup-stepper/setup-paid-ads/paid-ads-features-section.js b/js/src/setup-mc/setup-stepper/setup-paid-ads/paid-ads-features-section.js
index 14099026e6..fe3330df4c 100644
--- a/js/src/setup-mc/setup-stepper/setup-paid-ads/paid-ads-features-section.js
+++ b/js/src/setup-mc/setup-stepper/setup-paid-ads/paid-ads-features-section.js
@@ -15,7 +15,7 @@ import AppDocumentationLink from '.~/components/app-documentation-link';
import CampaignPreview from '.~/components/paid-ads/campaign-preview';
import './paid-ads-features-section.scss';
-function FeatureList() {
+function FeatureList( { hideBudgetContent } ) {
const featuresItems = [
{
Icon: GridiconCheckmark,
@@ -24,22 +24,27 @@ function FeatureList() {
'google-listings-and-ads'
),
},
- {
- Icon: GridiconCheckmark,
- content: __(
- 'Set a daily budget, and only pay when someone clicks.',
- 'google-listings-and-ads'
- ),
- },
- {
- Icon: GridiconGift,
- content: __(
- 'Claim $500 in ads credit when you spend your first $500 with Google Ads. Terms and conditions apply.',
- 'google-listings-and-ads'
- ),
- },
];
+ if ( ! hideBudgetContent ) {
+ featuresItems.push(
+ {
+ Icon: GridiconCheckmark,
+ content: __(
+ 'Set a daily budget, and only pay when someone clicks.',
+ 'google-listings-and-ads'
+ ),
+ },
+ {
+ Icon: GridiconGift,
+ content: __(
+ 'Claim $500 in ads credit when you spend your first $500 with Google Ads. Terms and conditions apply.',
+ 'google-listings-and-ads'
+ ),
+ }
+ );
+ }
+
return (
{ featuresItems.map( ( { Icon, content }, idx ) => (
@@ -61,11 +66,13 @@ function FeatureList() {
* for the next actions: skip or continue the paid ads setup.
*
* @param {Object} props React props.
+ * @param {boolean} props.hideBudgetContent Whether to hide the content about the ad budget.
* @param {boolean} props.hideFooterButtons Whether to hide the buttons at the card footer.
* @param {JSX.Element} props.skipButton Button to skip paid ads setup.
* @param {JSX.Element} props.continueButton Button to continue paid ads setup.
*/
export default function PaidAdsFeaturesSection( {
+ hideBudgetContent,
hideFooterButtons,
skipButton,
continueButton,
@@ -120,7 +127,9 @@ export default function PaidAdsFeaturesSection( {
'google-listings-and-ads'
) }
-
+
{ __(
'Source: Google Internal Data, July 2020',
diff --git a/js/src/setup-mc/setup-stepper/setup-paid-ads/paid-ads-setup-sections.js b/js/src/setup-mc/setup-stepper/setup-paid-ads/paid-ads-setup-sections.js
index f29fa2ff7a..86c3739bf7 100644
--- a/js/src/setup-mc/setup-stepper/setup-paid-ads/paid-ads-setup-sections.js
+++ b/js/src/setup-mc/setup-stepper/setup-paid-ads/paid-ads-setup-sections.js
@@ -11,16 +11,14 @@ import { Form } from '@woocommerce/components';
import useGoogleAdsAccount from '.~/hooks/useGoogleAdsAccount';
import useTargetAudienceFinalCountryCodes from '.~/hooks/useTargetAudienceFinalCountryCodes';
import useGoogleAdsAccountBillingStatus from '.~/hooks/useGoogleAdsAccountBillingStatus';
-import GoogleAdsAccountSection from './google-ads-account-section';
import AudienceSection from '.~/components/paid-ads/audience-section';
import BudgetSection from '.~/components/paid-ads/budget-section';
import BillingCard from '.~/components/paid-ads/billing-card';
+import SpinnerCard from '.~/components/spinner-card';
+import Section from '.~/wcdl/section';
import validateCampaign from '.~/components/paid-ads/validateCampaign';
import clientSession from './clientSession';
-import {
- GOOGLE_ADS_ACCOUNT_STATUS,
- GOOGLE_ADS_BILLING_STATUS,
-} from '.~/constants';
+import { GOOGLE_ADS_BILLING_STATUS } from '.~/constants';
/**
* @typedef { import(".~/data/actions").CountryCode } CountryCode
@@ -77,7 +75,7 @@ function resolveInitialPaidAds( paidAds, targetAudience ) {
* @param {(onStatesReceived: PaidAdsData)=>void} props.onStatesReceived Callback to receive the data for setting up paid ads when initial and also when the audience, budget, and billing are updated.
*/
export default function PaidAdsSetupSections( { onStatesReceived } ) {
- const { googleAdsAccount } = useGoogleAdsAccount();
+ const { hasGoogleAdsConnection } = useGoogleAdsAccount();
const { data: targetAudience } = useTargetAudienceFinalCountryCodes();
const { billingStatus } = useGoogleAdsAccountBillingStatus();
@@ -135,7 +133,11 @@ export default function PaidAdsSetupSections( { onStatesReceived } ) {
}, [ targetAudience ] );
if ( ! targetAudience || ! billingStatus ) {
- return ;
+ return (
+
+ );
}
const initialValues = {
@@ -153,16 +155,12 @@ export default function PaidAdsSetupSections( { onStatesReceived } ) {
>
{ ( formProps ) => {
const { countryCodes } = formProps.values;
- const disabledAudience = ! [
- GOOGLE_ADS_ACCOUNT_STATUS.CONNECTED,
- GOOGLE_ADS_ACCOUNT_STATUS.INCOMPLETE,
- ].includes( googleAdsAccount?.status );
+ const disabledAudience = ! hasGoogleAdsConnection;
const disabledBudget =
disabledAudience || countryCodes.length === 0;
return (
<>
-
clientSession.getShowPaidAdsSetup( false )
@@ -123,31 +126,32 @@ export default function SetupPaidAds() {
function createSkipButton( text ) {
const eventProps = {
opened_paid_ads_setup: 'no',
- google_ads_account_status: 'unknown',
+ google_ads_account_status: googleAdsAccount?.status,
billing_method_status: 'unknown',
campaign_form_validation: 'unknown',
};
if ( showPaidAdsSetup ) {
const selector = select( STORE_KEY );
- const account = selector.getGoogleAdsAccount();
const billing = selector.getGoogleAdsAccountBillingStatus();
merge( eventProps, {
opened_paid_ads_setup: 'yes',
- google_ads_account_status: account?.status,
billing_method_status: billing?.status,
campaign_form_validation: paidAds.isValid ? 'valid' : 'invalid',
} );
}
+ const disabledSkip =
+ completing === ACTION_COMPLETE || ! hasGoogleAdsConnection;
+
return (
+
{
test.describe( 'Create your paid campaign', () => {
test.beforeAll( async () => {
setupBudgetPage = new SetupBudgetPage( page );
- await setupBudgetPage.fulfillBudgetRecommendation( {
- currency: 'USD',
- recommendations: [
- {
- country: 'US',
- daily_budget_low: 5,
- daily_budget_high: 15,
- },
- ],
- } );
} );
test( 'Continue to create paid campaign', async () => {
@@ -360,7 +350,7 @@ test.describe( 'Set up Ads account', () => {
test( 'Budget Recommendation', async () => {
await expect(
- page.getByText( 'set a daily budget of 5 to 15 USD' )
+ page.getByText( 'set a daily budget of 15 USD' )
).toBeVisible();
} );
} );
diff --git a/tests/e2e/specs/setup-mc/step-4-complete-campaign.test.js b/tests/e2e/specs/setup-mc/step-4-complete-campaign.test.js
index c34ac1a1c4..4f9594f7f2 100644
--- a/tests/e2e/specs/setup-mc/step-4-complete-campaign.test.js
+++ b/tests/e2e/specs/setup-mc/step-4-complete-campaign.test.js
@@ -61,6 +61,12 @@ test.describe( 'Complete your campaign', () => {
// Mock Merchant Center as connected
completeCampaign.mockMCConnected(),
+ // Mock Google Ads as not yet connected.
+ setupAdsAccountPage.mockAdsAccountDisconnected(),
+
+ // Mock there is no existing Google Ads account.
+ setupAdsAccountPage.mockAdsAccountsResponse( [] ),
+
// Mock MC step as paid_ads
completeCampaign.mockMCSetup( 'incomplete', 'paid_ads' ),
@@ -79,6 +85,40 @@ test.describe( 'Complete your campaign', () => {
completeCampaign.fulfillSyncableProductsCountRequest( {
count: 1024,
} ),
+
+ // The following mocks are requests will happen after completing the onboarding
+ completeCampaign.mockSuccessfulSettingsSyncRequest(),
+
+ completeCampaign.fulfillProductStatisticsRequest( {
+ timestamp: 1695011644,
+ statistics: {
+ active: 0,
+ expiring: 0,
+ pending: 0,
+ disapproved: 0,
+ not_synced: 1137,
+ },
+ scheduled_sync: 1,
+ } ),
+
+ completeCampaign.fulfillAccountIssuesRequest( {
+ issues: [],
+ page: 1,
+ total: 0,
+ } ),
+
+ completeCampaign.fulfillProductIssuesRequest( {
+ issues: [],
+ page: 1,
+ total: 0,
+ } ),
+
+ completeCampaign.fulfillMCReview( {
+ cooldown: 0,
+ issues: [],
+ reviewEligibleRegions: [],
+ status: 'ONBOARDING',
+ } ),
] );
await completeCampaign.goto();
@@ -127,124 +167,63 @@ test.describe( 'Complete your campaign', () => {
} );
} );
- test.describe( 'Click "Skip this step for now"', () => {
- test.beforeAll( async () => {
- await Promise.all( [
- // Mock settings sync request
- completeCampaign.mockSuccessfulSettingsSyncRequest(),
-
- // Mock product statistics request
- completeCampaign.fulfillProductStatisticsRequest( {
- timestamp: 1695011644,
- statistics: {
- active: 0,
- expiring: 0,
- pending: 0,
- disapproved: 0,
- not_synced: 1137,
- },
- scheduled_sync: 1,
- } ),
- ] );
- await completeCampaign.clickSkipStepButton();
- } );
-
- test( 'should see the setup success modal', async () => {
- const setupSuccessModal = page
- .locator( '.components-modal__content' )
- .filter( {
- hasText:
- 'You’ve successfully set up Google Listings & Ads!',
- } );
- await expect( setupSuccessModal ).toBeVisible();
- } );
-
- test( 'should see the url contains product-feed', async () => {
- expect( page.url() ).toMatch( /path=%2Fgoogle%2Fproduct-feed/ );
- } );
- } );
-
- test.describe( 'Set up paid ads', () => {
- test.beforeAll( async () => {
- await Promise.all( [
- // Mock settings sync request
- completeCampaign.mockSuccessfulSettingsSyncRequest(),
-
- // Mock product statistics request
- completeCampaign.fulfillProductStatisticsRequest( {
- timestamp: 1695011644,
- statistics: {
- active: 0,
- expiring: 0,
- pending: 0,
- disapproved: 0,
- not_synced: 1137,
- },
- scheduled_sync: 1,
- } ),
- ] );
- await completeCampaign.goto();
- } );
-
- test( 'should see the "Create a paid ad campaign" button is enabled', async () => {
- const button = completeCampaign.getCreatePaidAdButton();
- await expect( button ).toBeVisible();
- await expect( button ).toBeEnabled();
- } );
-
- test.describe( 'Click "Create a paid ad campaign" button', () => {
- test.beforeAll( async () => {
- await completeCampaign.clickCreatePaidAdButton();
- await setupAdsAccountPage.mockAdsAccountsResponse( [] );
+ test.describe( 'Google Ads', () => {
+ test.describe( 'Google Ads section', () => {
+ test.beforeEach( async () => {
+ await completeCampaign.goto();
} );
- test( 'should see "Complete setup" button is disabled', async () => {
- const completeSetupButton =
- completeCampaign.getCompleteSetupButton();
- await expect( completeSetupButton ).toBeVisible();
- await expect( completeSetupButton ).toBeDisabled();
+ test( 'should see Google Ads section', async () => {
+ const section = completeCampaign.getAdsAccountSection();
+ await expect( section ).toBeVisible();
} );
- test( 'should see "Skip paid ads creation" button is enabled', async () => {
- const skipPaidAdsCreationButton =
- completeCampaign.getSkipPaidAdsCreationButton();
- await expect( skipPaidAdsCreationButton ).toBeVisible();
- await expect( skipPaidAdsCreationButton ).toBeEnabled();
- } );
-
- test( 'should see "Google Ads" section is enabled', async () => {
- const googleAdsSection =
- completeCampaign.getAdsAccountSection();
- await expect( googleAdsSection ).toBeVisible();
+ test( 'should only see non-budget related items and no footer buttons in the Features section when not yet connected', async () => {
+ const section = completeCampaign.getPaidAdsFeaturesSection();
+ const items = section.locator(
+ '.gla-paid-ads-features-section__feature-list > div'
+ );
+ await expect( items ).toHaveCount( 1 );
+ await expect( items ).toContainText( 'Promote your products' );
- // Cannot use toBeEnabled() because is not a native control element
- // such as