diff --git a/js/src/components/free-ad-credit/index.scss b/js/src/components/free-ad-credit/index.scss index f36176c07d..9732c19755 100644 --- a/js/src/components/free-ad-credit/index.scss +++ b/js/src/components/free-ad-credit/index.scss @@ -8,7 +8,7 @@ svg { flex: 0 0 auto; - fill: #007017; + fill: $gla-color-green; } &__title { diff --git a/js/src/components/paid-ads/ads-campaign/paid-ads-features-section.js b/js/src/components/paid-ads/ads-campaign/paid-ads-features-section.js index 8abdde4e33..94da88b4da 100644 --- a/js/src/components/paid-ads/ads-campaign/paid-ads-features-section.js +++ b/js/src/components/paid-ads/ads-campaign/paid-ads-features-section.js @@ -14,7 +14,6 @@ import AppDocumentationLink from '.~/components/app-documentation-link'; import CampaignPreview from '.~/components/paid-ads/campaign-preview'; import FreeAdCredit from '.~/components/free-ad-credit'; import VerticalGapLayout from '.~/components/vertical-gap-layout'; -import useFreeAdCredit from '.~/hooks/useFreeAdCredit'; import './paid-ads-features-section.scss'; function FeatureList() { @@ -49,8 +48,6 @@ function FeatureList() { * for the next actions: skip or continue the paid ads setup. */ export default function PaidAdsFeaturesSection() { - const hasFreeAdCredit = useFreeAdCredit(); - return (
- - { hasFreeAdCredit && } + diff --git a/js/src/dashboard/index.scss b/js/src/dashboard/index.scss index c1dfa3b54a..417e5efb82 100644 --- a/js/src/dashboard/index.scss +++ b/js/src/dashboard/index.scss @@ -26,6 +26,7 @@ margin-bottom: var(--main-gap); @include break-medium { + align-items: flex-start; flex-direction: row; } diff --git a/js/src/dashboard/summary-section/index.js b/js/src/dashboard/summary-section/index.js index f5c2c71bed..a4ffc23da8 100644 --- a/js/src/dashboard/summary-section/index.js +++ b/js/src/dashboard/summary-section/index.js @@ -7,7 +7,8 @@ import { SummaryNumber } from '@woocommerce/components'; /** * Internal dependencies */ -import { glaData, REPORT_SOURCE_PAID, REPORT_SOURCE_FREE } from '.~/constants'; +import { REPORT_SOURCE_PAID, REPORT_SOURCE_FREE } from '.~/constants'; +import useAdsCampaigns from '.~/hooks/useAdsCampaigns'; import useAdsCurrency from '.~/hooks/useAdsCurrency'; import useCurrencyFormat from '.~/hooks/useCurrencyFormat'; import usePerformance from './usePerformance'; @@ -106,17 +107,21 @@ const PaidPerformanceCard = () => { }; export default function SummarySection() { - const { adsSetupComplete } = glaData; + const { loaded, data: adsCampaignsData } = useAdsCampaigns(); + if ( ! loaded ) { + return null; + } + const showCampaignPromotionCard = ! adsCampaignsData?.length; return ( <> - { adsSetupComplete ? ( - - ) : ( + { showCampaignPromotionCard ? ( + ) : ( + ) } { - const showFreeCredit = - adsAccount.sub_account || - adsAccount.status === GOOGLE_ADS_ACCOUNT_STATUS.DISCONNECTED; - - return ( - <> -

- { showFreeCredit - ? __( - 'Create your first campaign and get $500 in ad credit*', - 'google-listings-and-ads' - ) - : __( - 'Create your first campaign', - 'google-listings-and-ads' - ) } -

- - - ); -}; +import PaidFeatures from './paid-features'; function PaidCampaignPromotionCard() { const { googleAdsAccount } = useGoogleAdsAccount(); return (
- { googleAdsAccount ? ( - - ) : ( - - ) } + { googleAdsAccount ? : }
); } diff --git a/js/src/dashboard/summary-section/paid-features/free-ad-credit.js b/js/src/dashboard/summary-section/paid-features/free-ad-credit.js new file mode 100644 index 0000000000..2c4360d6ef --- /dev/null +++ b/js/src/dashboard/summary-section/paid-features/free-ad-credit.js @@ -0,0 +1,44 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import GridiconGift from 'gridicons/dist/gift'; +import { createInterpolateElement } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import AppDocumentationLink from '.~/components/app-documentation-link'; + +/** + * Render the Free Ads Credit inside the Paid Features component. + * + * @fires gla_documentation_link_click with `{ context: 'dashboard', link_id: 'free-ad-credit-terms', href: 'https://www.google.com/ads/coupons/terms/' }` + * @return {JSX.Element} Free Ads Credit component. + */ +const FreeAdCredit = () => { + return ( +
+ +
+ { createInterpolateElement( + __( + 'Claim $500 in ads credit when you spend your first $500 with Google Ads. Terms and conditions apply.', + 'google-listings-and-ads' + ), + { + termLink: ( + + ), + } + ) } +
+
+ ); +}; + +export default FreeAdCredit; diff --git a/js/src/dashboard/summary-section/paid-features/index.js b/js/src/dashboard/summary-section/paid-features/index.js new file mode 100644 index 0000000000..9694c7edb5 --- /dev/null +++ b/js/src/dashboard/summary-section/paid-features/index.js @@ -0,0 +1,101 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { createInterpolateElement } from '@wordpress/element'; +import { Flex, FlexBlock, FlexItem } from '@wordpress/components'; +import GridiconCheckmark from 'gridicons/dist/checkmark'; + +/** + * Internal dependencies + */ +import { ContentLink } from '.~/components/guide-page-content'; +import CampaignPreview from '.~/components/paid-ads/campaign-preview'; +import AddPaidCampaignButton from '.~/components/paid-ads/add-paid-campaign-button'; +import FreeAdCredit from './free-ad-credit'; +import VerticalGapLayout from '.~/components/vertical-gap-layout'; +import './index.scss'; + +function FeatureList() { + const featuresItems = [ + { + Icon: GridiconCheckmark, + content: __( + 'Reach more customer by advertising your products across Google Ads channels like Search, YouTube and Discover.', + 'google-listings-and-ads' + ), + }, + { + Icon: GridiconCheckmark, + content: __( + 'Set a daily budget and only pay when people click on your ads.', + 'google-listings-and-ads' + ), + }, + { + Icon: GridiconCheckmark, + content: createInterpolateElement( + __( + "Performance Max uses the best of Google's AI to show the most impactful ads for your products at the right time and place. Learn more about Performance Max technology.", + 'google-listings-and-ads' + ), + { + link: ( + + ), + } + ), + }, + ]; + + return ( +
+ { featuresItems.map( ( { Icon, content }, idx ) => ( + + + { content } + + ) ) } +
+ ); +} + +/** + * Returns a component with paid features content. + * + * @return {JSX.Element} Paid Features component. + */ +const PaidFeatures = () => { + return ( + + + + + + + + + + + + { __( 'Create Campaign', 'google-listings-and-ads' ) } + + + ); +}; + +export default PaidFeatures; diff --git a/js/src/dashboard/summary-section/paid-features/index.scss b/js/src/dashboard/summary-section/paid-features/index.scss new file mode 100644 index 0000000000..1902c36b30 --- /dev/null +++ b/js/src/dashboard/summary-section/paid-features/index.scss @@ -0,0 +1,44 @@ +.gla-paid-features { + + &__content { + flex-direction: column; + + @media (min-width: $break-small) { + align-items: flex-start; + flex-direction: row; + gap: $grid-unit-20; + } + } + + &__feature-list { + display: flex; + flex-direction: column; + gap: $grid-unit-20; + line-height: $gla-line-height-smaller; + color: $gray-800; + text-align: left; + + .gridicon { + fill: $alert-green; + } + } + + .gla-free-ad-credit-claim { + text-align: left; + background-color: $white; + display: flex; + align-items: flex-start; + gap: $grid-unit; + padding: $grid-unit-20 $grid-unit-15; + color: $gray-900; + + .gridicon { + fill: $gla-color-green; + flex: 0 0 auto; + } + } + + .components-button { + align-self: flex-end; + } +} diff --git a/js/src/dashboard/summary-section/summary-section.test.js b/js/src/dashboard/summary-section/summary-section.test.js index 3b43dbfcdd..9a2a3ba607 100644 --- a/js/src/dashboard/summary-section/summary-section.test.js +++ b/js/src/dashboard/summary-section/summary-section.test.js @@ -7,6 +7,7 @@ import { render } from '@testing-library/react'; * Internal dependencies */ import SummarySection from '.~/dashboard/summary-section'; +import useAdsCampaigns from '.~/hooks/useAdsCampaigns'; // Mimic no data loaded. jest.mock( './usePerformance', () => @@ -22,34 +23,56 @@ jest.mock( '.~/hooks/useAdsCurrency', () => } ) ); jest.mock( '.~/hooks/useCurrencyFormat', () => jest.fn() ); +jest.mock( '.~/hooks/useAdsCampaigns', () => + jest.fn().mockName( 'useAdsCampaigns' ) +); describe( 'SummarySection when no data is loaded', () => { + beforeAll( () => { + useAdsCampaigns.mockImplementation( () => { + return { + loading: false, + loaded: true, + data: [ + { + id: 10, + name: 'PMax Campaign', + status: 'enabled', + type: 'performance_max', + amount: 20, + displayCountries: [ 'US' ], + }, + ], + }; + } ); + } ); + it( 'Shows no data message for Free Campaigns', async () => { - const { queryByText } = render( ); + const { findByText } = render( ); expect( - queryByText( + await findByText( "We're having trouble loading this data. Try again later, or track your performance in Google Merchant Center." ) ).toBeTruthy(); - const link = queryByText( 'Open Google Merchant Center' ); + const link = await findByText( 'Open Google Merchant Center' ); expect( link ).toBeTruthy(); expect( link.href ).toBe( 'https://merchants.google.com/mc/reporting/dashboard' ); } ); - it( 'Shows no data message for Paid Campaigns', () => { - const { queryByText } = render( ); + it( 'Shows no data message for Paid Campaigns', async () => { + const { findByText } = render( ); expect( - queryByText( + await findByText( "We're having trouble loading this data. Try again later, or track your performance in Google Ads." ) ).toBeTruthy(); - const link = queryByText( 'Open Google Ads' ); + const link = await findByText( 'Open Google Ads' ); expect( link ).toBeTruthy(); expect( link.href ).toBe( 'https://ads.google.com/' ); diff --git a/js/src/hooks/useFreeAdCredit.js b/js/src/hooks/useFreeAdCredit.js deleted file mode 100644 index 556f7fc919..0000000000 --- a/js/src/hooks/useFreeAdCredit.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * External dependencies - */ -import { getDateDifferenceInDays } from '@woocommerce/date'; - -/** - * Internal dependencies - */ -import useGoogleAdsAccount from '.~/hooks/useGoogleAdsAccount'; - -/** - * Returns a Boolean value indicating whether the user is eligible for free ad credit. - * - * This will retrieve the user's Google Ads account. - * - * To be eligible for the free ad credit, the account must be a sub-account - * that is newly created within the last 60 days. - * If users disconnected the account and reconnect again, - * it will not be seen as a newly created sub-account - * and hence won't be eligible for the free ad credit. - * - * @return {boolean} Value indicating whether the user is eligible for free ad credit. - */ -const useFreeAdCredit = () => { - const { googleAdsAccount } = useGoogleAdsAccount(); - - return ( - googleAdsAccount && - googleAdsAccount.sub_account && - getDateDifferenceInDays( - new Date(), - new Date( googleAdsAccount.created_timestamp * 1000 ) - ) < 60 - ); -}; - -export default useFreeAdCredit; diff --git a/js/src/setup-ads/ads-stepper/setup-accounts/index.js b/js/src/setup-ads/ads-stepper/setup-accounts/index.js index 17ee5995b6..021303dc92 100644 --- a/js/src/setup-ads/ads-stepper/setup-accounts/index.js +++ b/js/src/setup-ads/ads-stepper/setup-accounts/index.js @@ -18,7 +18,6 @@ import FreeAdCredit from '.~/components/free-ad-credit'; import useGoogleAdsAccount from '.~/hooks/useGoogleAdsAccount'; import useGoogleAdsAccountStatus from '.~/hooks/useGoogleAdsAccountStatus'; import useGoogleAccount from '.~/hooks/useGoogleAccount'; -import useFreeAdCredit from '.~/hooks/useFreeAdCredit'; import AppSpinner from '.~/components/app-spinner'; import Section from '.~/wcdl/section'; @@ -27,7 +26,6 @@ const SetupAccounts = ( props ) => { const { google } = useGoogleAccount(); const { googleAdsAccount, hasGoogleAdsConnection } = useGoogleAdsAccount(); const { hasAccess, step } = useGoogleAdsAccountStatus(); - const hasFreeAdCredit = useFreeAdCredit(); if ( ! google || ( google.active === 'yes' && ! googleAdsAccount ) ) { return ; @@ -72,7 +70,7 @@ const SetupAccounts = ( props ) => { ) } /> - { hasFreeAdCredit && } +
diff --git a/tests/e2e/specs/add-paid-campaigns/add-paid-campaigns.test.js b/tests/e2e/specs/add-paid-campaigns/add-paid-campaigns.test.js index f9803e0538..c2600613ac 100644 --- a/tests/e2e/specs/add-paid-campaigns/add-paid-campaigns.test.js +++ b/tests/e2e/specs/add-paid-campaigns/add-paid-campaigns.test.js @@ -42,11 +42,6 @@ let dashboardPage = null; */ let setupAdsAccounts = null; -/** - * @type {import('@playwright/test').Locator} adsConnectionButton - */ -let adsConnectionButton = null; - /** * @type {import('../../utils/pages/setup-ads/setup-budget.js').default} setupBudgetPage */ @@ -90,20 +85,14 @@ test.describe( 'Set up Ads account', () => { } ); test( 'Dashboard page contains Add Paid campaign buttons', async () => { - //Add page campaign in the Performance (Paid Campaigns) section - await expect( - dashboardPage.getAdsConnectionAllProgramsButton( 'summary-card' ) - ).toBeEnabled(); - //Add page campaign in the programs section. - adsConnectionButton = dashboardPage.getAdsConnectionAllProgramsButton(); - await expect( adsConnectionButton ).toBeEnabled(); + await expect( dashboardPage.addPaidCampaignButton ).toBeEnabled(); } ); test.describe( 'Set up your accounts page', async () => { test.beforeAll( async () => { await setupAdsAccounts.mockAdsAccountsResponse( [] ); - await adsConnectionButton.click(); + await dashboardPage.addPaidCampaignButton.click(); await page.waitForLoadState( LOAD_STATE.DOM_CONTENT_LOADED ); } ); diff --git a/tests/e2e/specs/dashboard/paid-features.test.js b/tests/e2e/specs/dashboard/paid-features.test.js new file mode 100644 index 0000000000..9eace25aca --- /dev/null +++ b/tests/e2e/specs/dashboard/paid-features.test.js @@ -0,0 +1,108 @@ +/** + * External dependencies + */ +import { expect, test } from '@playwright/test'; + +/** + * Internal dependencies + */ +import { + clearOnboardedMerchant, + setOnboardedMerchant, + setCompletedAdsSetup, + clearCompletedAdsSetup, +} from '../../utils/api'; +import DashboardPage from '../../utils/pages/dashboard.js'; + +test.use( { storageState: process.env.ADMINSTATE } ); + +test.describe.configure( { mode: 'serial' } ); + +/** + * @type {import('../../utils/pages/dashboard.js').default} dashboardPage + */ +let dashboardPage = null; + +/** + * @type {import('@playwright/test').Page} page + */ +let page = null; + +test.describe( 'Paid Feature Listing', () => { + test.beforeAll( async ( { browser } ) => { + page = await browser.newPage(); + dashboardPage = new DashboardPage( page ); + await setOnboardedMerchant(); + await dashboardPage.mockRequests(); + await dashboardPage.goto(); + } ); + + test.afterAll( async () => { + await clearOnboardedMerchant(); + await page.close(); + } ); + + test( 'Paid Features Listing is visible if ads campaign setup is not complete', async () => { + await expect( dashboardPage.googleAdsSummaryCard ).toContainText( + 'Google Ads' + ); + + await expect( dashboardPage.paidFeatures ).toContainText( + 'Reach more customer by advertising your products across Google Ads channels like Search, YouTube and Discover.' + ); + + // FreeAdCredit component content visible. + await expect( dashboardPage.paidFeatures ).toContainText( + 'Claim $500 in ads credit when you spend your first $500 with Google Ads.' + ); + + await expect( dashboardPage.createCampaignButton ).toBeEnabled(); + await dashboardPage.mockAdsAccountsResponse( [] ); + await dashboardPage.createCampaignButton.click(); + await expect( + page.getByRole( 'heading', { + level: 1, + name: 'Set up your accounts', + } ) + ).toBeVisible(); + } ); + + test.describe( 'When ads campaign setup is complete', async () => { + test.beforeAll( async () => { + await setCompletedAdsSetup(); + } ); + + test.afterAll( async () => { + await clearCompletedAdsSetup(); + await page.close(); + } ); + test( 'When no campaign present', async () => { + await dashboardPage.fulfillAdsCampaignsRequest( [] ); + await dashboardPage.goto(); + await expect( dashboardPage.googleAdsSummaryCard ).toContainText( + 'Google Ads' + ); + + await expect( dashboardPage.paidFeatures ).toBeVisible(); + } ); + test( 'When at least one campaign present', async () => { + await dashboardPage.fulfillAdsCampaignsRequest( [ + { + id: 111111111, + name: 'Test Campaign', + status: 'enabled', + type: 'performance_max', + amount: 1, + country: 'US', + targeted_locations: [ 'US' ], + }, + ] ); + await dashboardPage.goto(); + await expect( dashboardPage.googleAdsSummaryCard ).toContainText( + /Google Ads.*Total Sales.*Total Spend/ + ); + + await expect( dashboardPage.paidFeatures ).not.toBeVisible(); + } ); + } ); +} ); diff --git a/tests/e2e/specs/product-feed/product-feed-campaign-notice.test.js b/tests/e2e/specs/product-feed/product-feed-campaign-notice.test.js index 4b57cbd87e..24250d9179 100644 --- a/tests/e2e/specs/product-feed/product-feed-campaign-notice.test.js +++ b/tests/e2e/specs/product-feed/product-feed-campaign-notice.test.js @@ -119,6 +119,7 @@ test.describe( 'Product Feed Page', () => { await expect( createCampaignButton ).toBeVisible(); await createCampaignButton.click(); await page.waitForLoadState( LOAD_STATE.DOM_CONTENT_LOADED ); + await productFeedPage.mockAdsAccountsResponse( [] ); await expect( page.getByRole( 'heading', { level: 1, 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 e85e6fd755..4b381325d3 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 @@ -9,6 +9,7 @@ const { test, expect } = require( '@playwright/test' ); import SetupBudgetPage from '../../utils/pages/setup-ads/setup-budget'; import CompleteCampaign from '../../utils/pages/setup-mc/step-4-complete-campaign'; import SetupAdsAccountPage from '../../utils/pages/setup-ads/setup-ads-accounts'; +import DashboardPage from '../../utils/pages/dashboard'; import { checkFAQExpandable, getFAQPanelTitle, @@ -35,6 +36,11 @@ let completeCampaign = null; */ let setupAdsAccountPage = null; +/** + * @type {import('../../utils/pages/dashboard.js').default} dashboardPage + */ +let dashboardPage = null; + /** * @type {import('@playwright/test').Page} page */ @@ -44,6 +50,7 @@ test.describe( 'Complete your campaign', () => { test.beforeAll( async ( { browser } ) => { page = await browser.newPage(); setupBudgetPage = new SetupBudgetPage( page ); + dashboardPage = new DashboardPage( page ); completeCampaign = new CompleteCampaign( page ); setupAdsAccountPage = new SetupAdsAccountPage( page ); await Promise.all( [ @@ -417,16 +424,14 @@ test.describe( 'Complete your campaign', () => { test( 'should see buttons on Dashboard for Google Ads onboarding', async () => { await page.keyboard.press( 'Escape' ); await page.getByRole( 'tab', { name: 'Dashboard' } ).click(); + const { addPaidCampaignButton, createCampaignButton } = + dashboardPage; - const buttons = page.getByRole( 'button', { - name: 'Add paid campaign', - } ); + await expect( addPaidCampaignButton ).toBeVisible(); + await expect( addPaidCampaignButton ).toBeEnabled(); - await expect( buttons ).toHaveCount( 2 ); - for ( const button of await buttons.all() ) { - await expect( button ).toBeVisible(); - await expect( button ).toBeEnabled(); - } + await expect( createCampaignButton ).toBeVisible(); + await expect( createCampaignButton ).toBeEnabled(); } ); } ); @@ -456,7 +461,7 @@ test.describe( 'Complete your campaign', () => { } ); test.describe( 'Free Ad Credit', () => { - test( 'should not see the Free Ad Credit section if the account is not eligible', async () => { + test( 'should see the Free Ad Credit section always', async () => { await setupAdsAccountPage.mockAdsAccountConnected(); await completeCampaign.goto(); await setupAdsAccountPage.awaitAdsConnectionResponse(); @@ -466,21 +471,6 @@ test.describe( 'Complete your campaign', () => { page.getByText( 'Create a campaign to advertise your products' ) ).toBeVisible(); - await expect( - page.getByText( - 'Spend $500 to get $500 in Google Ads credits!' - ) - ).not.toBeVisible(); - } ); - - test( 'should see the Free Ad Credit section if the account is eligible', async () => { - await setupAdsAccountPage.mockAdsAccountConnected( 12345, { - sub_account: true, - created_timestamp: Math.floor( Date.now() / 1000 ), - } ); - await completeCampaign.goto(); - await setupAdsAccountPage.awaitAdsConnectionResponse(); - await expect( page.getByText( 'Spend $500 to get $500 in Google Ads credits!' diff --git a/tests/e2e/utils/mock-requests.js b/tests/e2e/utils/mock-requests.js index 4230c8c50d..3ebbb81817 100644 --- a/tests/e2e/utils/mock-requests.js +++ b/tests/e2e/utils/mock-requests.js @@ -64,6 +64,19 @@ export default class MockRequests { ); } + /** + * Fulfill the Ads Report Program request. + * + * @param {Object} payload + * @return {Promise} + */ + async fulfillAdsReportProgram( payload ) { + await this.fulfillRequest( + /\/wc\/gla\/ads\/reports\/programs\b/, + payload + ); + } + /** * Fulfill the Target Audience request. * diff --git a/tests/e2e/utils/pages/dashboard.js b/tests/e2e/utils/pages/dashboard.js index 57aba3ed61..b53bd867cc 100644 --- a/tests/e2e/utils/pages/dashboard.js +++ b/tests/e2e/utils/pages/dashboard.js @@ -20,6 +20,20 @@ export default class DashboardPage extends MockRequests { this.editFreeListingButton = this.freeListingRow.getByRole( 'button', { name: 'Edit', } ); + this.googleAdsSummaryCard = this.page.locator( + '.gla-dashboard__performance .gla-summary-card:nth-child(1)' + ); + this.paidFeatures = + this.googleAdsSummaryCard.locator( '.gla-paid-features' ); + this.createCampaignButton = this.paidFeatures.locator( 'button', { + hasText: 'Create Campaign', + } ); + this.addPaidCampaignButton = this.page.locator( + '.gla-all-programs-table-card button', + { + hasText: 'Add paid campaign', + } + ); } /** @@ -49,6 +63,20 @@ export default class DashboardPage extends MockRequests { next_page: null, } ); + await this.fulfillAdsReportProgram( { + products: null, + campaigns: null, + intervals: null, + totals: { + sales: 0, + conversions: 0, + spend: 0, + clicks: 0, + impressions: 0, + }, + next_page: null, + } ); + await this.fulfillTargetAudience( { location: 'selected', countries: [ 'US' ], @@ -127,23 +155,4 @@ export default class DashboardPage extends MockRequests { const continueToEditButton = await this.getContinueToEditButton(); await continueToEditButton.click(); } - - /** - * Get the Ads connection button. - * - * @param {('programs-table'|'summary-card')} [type] The type of button to get. Either 'programs-table' or 'summary-card'. - * @return {import('@playwright/test').Locator} Get the Ads connection button. - */ - getAdsConnectionAllProgramsButton( type = 'programs-table' ) { - return this.page.locator( - `${ - type === 'programs-table' - ? '.gla-all-programs-table-card button' - : '.gla-summary-card' - }`, - { - hasText: 'Add paid campaign', - } - ); - } }