diff --git a/tests/e2e/global-setup.js b/tests/e2e/global-setup.js index cef4fa7c63..aa755c9f5c 100644 --- a/tests/e2e/global-setup.js +++ b/tests/e2e/global-setup.js @@ -1,5 +1,13 @@ -const { chromium, expect } = require( '@playwright/test' ); +/** + * Internal dependencies + */ const { admin } = require( './config/default.json' ).users; +const { LOAD_STATE } = require( './utils/constants' ); + +/** + * External dependencies + */ +const { chromium, expect } = require( '@playwright/test' ); const fs = require( 'fs' ); /* eslint-disable no-console */ @@ -48,9 +56,9 @@ module.exports = async ( config ) => { .locator( 'input[name="pwd"]' ) .fill( admin.password ); await adminPage.locator( 'text=Log In' ).click(); - await adminPage.waitForLoadState( 'networkidle' ); + await adminPage.waitForLoadState( LOAD_STATE.NETWORK_IDLE ); await adminPage.goto( `/wp-admin` ); - await adminPage.waitForLoadState( 'domcontentloaded' ); + await adminPage.waitForLoadState( LOAD_STATE.DOM_CONTENT_LOADED ); await expect( adminPage.locator( 'div.wrap > h1' ) ).toHaveText( 'Dashboard' diff --git a/tests/e2e/specs/dashboard/edit-free-listings.test.js b/tests/e2e/specs/dashboard/edit-free-listings.test.js index 87a8e60858..0def13c2e9 100644 --- a/tests/e2e/specs/dashboard/edit-free-listings.test.js +++ b/tests/e2e/specs/dashboard/edit-free-listings.test.js @@ -2,10 +2,12 @@ * External dependencies */ import { expect, test } from '@playwright/test'; + /** * Internal dependencies */ import { clearOnboardedMerchant, setOnboardedMerchant } from '../../utils/api'; +import { LOAD_STATE } from '../../utils/constants'; import { checkSnackBarMessage } from '../../utils/page'; import DashboardPage from '../../utils/pages/dashboard.js'; import EditFreeListingsPage from '../../utils/pages/edit-free-listings.js'; @@ -55,7 +57,7 @@ test.describe( 'Edit Free Listings', () => { test( 'Edit Free Listings should show modal', async () => { await dashboardPage.clickEditFreeListings(); - await page.waitForLoadState( 'domcontentloaded' ); + await page.waitForLoadState( LOAD_STATE.DOM_CONTENT_LOADED ); const continueToEditButton = await dashboardPage.getContinueToEditButton(); @@ -66,7 +68,7 @@ test.describe( 'Edit Free Listings', () => { test( 'Continue to edit Free listings', async () => { await dashboardPage.clickContinueToEditButton(); - await page.waitForLoadState( 'domcontentloaded' ); + await page.waitForLoadState( LOAD_STATE.DOM_CONTENT_LOADED ); } ); test( 'Check recommended shipping settings', async () => { diff --git a/tests/e2e/specs/get-started.test.js b/tests/e2e/specs/get-started.test.js index f58f5c427b..bf2ba8f231 100644 --- a/tests/e2e/specs/get-started.test.js +++ b/tests/e2e/specs/get-started.test.js @@ -1,3 +1,8 @@ +/** + * Internal dependencies + */ +import { LOAD_STATE } from '../utils/constants'; + /** * External dependencies */ @@ -22,13 +27,13 @@ test( 'Merchant who is getting started clicks on the Marketing > GLA link, click // the submenu is now opened, the GLA sub menu item is now visible to the user, // we can call `click` now. await page.getByRole( 'link', { name: 'Google Listings & Ads' } ).click(); - await page.waitForLoadState( 'networkidle' ); + await page.waitForLoadState( LOAD_STATE.NETWORK_IDLE ); await expect( page ).toHaveTitle( /Google Listings & Ads/ ); // click on the call-to-action button. await page.getByText( 'Start listing products →' ).first().click(); - await page.waitForLoadState( 'networkidle' ); + await page.waitForLoadState( LOAD_STATE.NETWORK_IDLE ); // Check we are in the Setup MC page. await expect( diff --git a/tests/e2e/specs/setup-mc/step-1-accounts.test.js b/tests/e2e/specs/setup-mc/step-1-accounts.test.js index 4d39804779..8b36519628 100644 --- a/tests/e2e/specs/setup-mc/step-1-accounts.test.js +++ b/tests/e2e/specs/setup-mc/step-1-accounts.test.js @@ -1,3 +1,9 @@ +/** + * Internal dependencies + */ +import SetUpAccountsPage from '../../utils/pages/setup-mc/step-1-set-up-accounts'; +import { LOAD_STATE } from '../../utils/constants'; + /** * External dependencies */ @@ -5,19 +11,31 @@ const { test, expect } = require( '@playwright/test' ); test.use( { storageState: process.env.ADMINSTATE } ); -test.describe( 'Merchant who is getting started', () => { - test.beforeEach( async ( { page } ) => { - // Go to the setup page short way - directly via URL. - await page.goto( - '/wp-admin/admin.php?page=wc-admin&path=%2Fgoogle%2Fsetup-mc' - ); - await page.waitForLoadState( 'networkidle' ); +test.describe.configure( { mode: 'serial' } ); + +/** + * @type {import('../../utils/pages/setup-mc/step-1-set-up-accounts.js').default} setUpAccountsPage + */ +let setUpAccountsPage = null; + +/** + * @type {import('@playwright/test').Page} page + */ +let page = null; + +test.describe( 'Set up accounts', () => { + test.beforeAll( async ( { browser } ) => { + page = await browser.newPage(); + setUpAccountsPage = new SetUpAccountsPage( page ); } ); - test( 'should see accounts step header, "Connect your WordPress.com account" & connect button', async ( { - page, - } ) => { - // Wait for API calls and the page to render. + test.afterAll( async () => { + await setUpAccountsPage.closePage(); + } ); + + test( 'should see accounts step header, "Connect your WordPress.com account" & connect button', async () => { + await setUpAccountsPage.goto(); + await expect( page.getByRole( 'heading', { name: 'Set up your accounts' } ) ).toBeVisible(); @@ -28,110 +46,539 @@ test.describe( 'Merchant who is getting started', () => { ) ).toBeVisible(); - expect( + await expect( page.getByRole( 'button', { name: 'Connect' } ).first() ).toBeEnabled(); + + const wpAccountCard = setUpAccountsPage.getWPAccountCard(); + await expect( wpAccountCard ).toBeEnabled(); + await expect( wpAccountCard ).toContainText( 'WordPress.com' ); + + const googleAccountCard = setUpAccountsPage.getGoogleAccountCard(); + await expect( googleAccountCard.getByRole( 'button' ) ).toBeDisabled(); + + const mcAccountCard = setUpAccountsPage.getMCAccountCard(); + await expect( mcAccountCard.getByRole( 'button' ) ).toBeDisabled(); + + const continueButton = setUpAccountsPage.getContinueButton(); + await expect( continueButton ).toBeDisabled(); } ); - test( 'after clicking the "Connect your WordPress.com account" button, should send an API request to connect Jetpack, and redirect to the returned URL', async ( { - page, - baseURL, - } ) => { - // Mock Jetpack connect - await page.route( /\/wc\/gla\/jetpack\/connect\b/, ( route ) => - route.fulfill( { - content: 'application/json', - headers: { 'Access-Control-Allow-Origin': '*' }, - body: JSON.stringify( { - url: baseURL + 'auth_url', - } ), - } ) - ); - - // Click the enabled connect button. - page.locator( "//button[text()='Connect'][not(@disabled)]" ).click(); - await page.waitForLoadState( 'networkidle' ); - - // Expect the user to be redirected - await page.waitForURL( baseURL + 'auth_url' ); + test.describe( 'FAQ panels', () => { + test( 'should see two questions in FAQ', async () => { + const faqTitles = setUpAccountsPage.getFAQPanelTitle(); + await expect( faqTitles ).toHaveCount( 2 ); + } ); + + test( 'should not see FAQ rows when FAQ titles are not clicked', async () => { + const faqRows = setUpAccountsPage.getFAQPanelRow(); + await expect( faqRows ).toHaveCount( 0 ); + } ); + + test( 'should see one FAQ rows when the first FAQ title is clicked', async () => { + const faqTitle = setUpAccountsPage.getFAQPanelTitle().first(); + await faqTitle.click(); + const faqRow = setUpAccountsPage.getFAQPanelRow(); + await expect( faqRow ).toBeVisible(); + } ); + + test( 'should see two FAQ rows when two FAQ titles are clicked', async () => { + const faqTitle2 = setUpAccountsPage.getFAQPanelTitle().nth( 1 ); + await faqTitle2.click(); + const faqRows = setUpAccountsPage.getFAQPanelRow(); + await expect( faqRows ).toHaveCount( 2 ); + for ( const faqRow of await faqRows.all() ) { + await expect( faqRow ).toBeVisible(); + } + } ); } ); -} ); -test.describe( 'Merchant with Jetpack connected & Google not connected', () => { - test.beforeEach( async ( { page } ) => { - // Mock Jetpack as connected - await page.route( /\/wc\/gla\/jetpack\/connected\b/, ( route ) => - route.fulfill( { - content: 'application/json', - headers: { 'Access-Control-Allow-Origin': '*' }, - body: JSON.stringify( { - active: 'yes', - owner: 'yes', - displayName: 'testUser', - email: 'mail@example.com', - } ), - } ) - ); - - // Mock google as not connected. - // When pending even WPORG will not render yet. - // If not mocked will fail and render nothing, - // as Jetpack is mocked only on the client-side. - await page.route( /\/wc\/gla\/google\/connected\b/, ( route ) => - route.fulfill( { - content: 'application/json', - headers: { 'Access-Control-Allow-Origin': '*' }, - body: JSON.stringify( { - active: 'no', - email: '', - } ), - } ) - ); - - // Go to the setup page short way - directly via URL. - await page.goto( - '/wp-admin/admin.php?page=wc-admin&path=%2Fgoogle%2Fsetup-mc' - ); - - // Wait for API calls and the page to render. - await page.waitForLoadState( 'networkidle' ); - page.getByRole( 'heading', { name: 'Set up your accounts' } ); + test.describe( 'Connect WordPress.com account', () => { + test( 'should send an API request to connect Jetpack, and redirect to the returned URL', async ( { + baseURL, + } ) => { + // Mock Jetpack connect + await setUpAccountsPage.mockJetpackConnect( baseURL + 'auth_url' ); + + // Click the enabled connect button. + page.locator( + "//button[text()='Connect'][not(@disabled)]" + ).click(); + await page.waitForLoadState( LOAD_STATE.NETWORK_IDLE ); + + // Expect the user to be redirected + await page.waitForURL( baseURL + 'auth_url' ); + + expect( page.url() ).toMatch( baseURL + 'auth_url' ); + } ); } ); - test( 'should see their WPORG email, "Google" title & connect button', async ( { - page, - } ) => { - await expect( page.getByText( 'mail@example.com' ) ).toBeVisible(); + test.describe( 'Connect Google account', () => { + test.beforeAll( async () => { + // Mock Jetpack as connected + await setUpAccountsPage.mockJetpackConnected( + 'Test user', + 'jetpack@example.com' + ); - await expect( - page.getByText( 'Google', { exact: true } ) - ).toBeVisible(); + // Mock google as not connected. + // When pending even WPORG will not render yet. + // If not mocked will fail and render nothing, + // as Jetpack is mocked only on the client-side. + await setUpAccountsPage.mockGoogleNotConnected(); - expect( - page.getByRole( 'button', { name: 'Connect' } ).first() - ).toBeEnabled(); + await setUpAccountsPage.goto(); + } ); + + test( 'should see their WPORG email, "Google" title & connect button', async () => { + const jetpackDescriptionRow = + setUpAccountsPage.getJetpackDescriptionRow(); + + await expect( jetpackDescriptionRow ).toContainText( + 'jetpack@example.com' + ); + + const googleAccountCard = setUpAccountsPage.getGoogleAccountCard(); + + await expect( + googleAccountCard.getByText( 'Google', { exact: true } ) + ).toBeVisible(); + + await expect( + googleAccountCard + .getByRole( 'button', { name: 'Connect' } ) + .first() + ).toBeEnabled(); + + const mcAccountCard = setUpAccountsPage.getMCAccountCard(); + await expect( mcAccountCard.getByRole( 'button' ) ).toBeDisabled(); + + const continueButton = setUpAccountsPage.getContinueButton(); + await expect( continueButton ).toBeDisabled(); + } ); + + test( 'after clicking the "Connect your Google account" button should send an API request to connect Google account, and redirect to the returned URL', async ( { + baseURL, + } ) => { + // Mock google connect. + await setUpAccountsPage.mockGoogleConnect( + baseURL + 'google_auth' + ); + + // Click the enabled connect button + page.locator( + "//button[text()='Connect'][not(@disabled)]" + ).click(); + await page.waitForLoadState( LOAD_STATE.NETWORK_IDLE ); + + // Expect the user to be redirected + await page.waitForURL( baseURL + 'google_auth' ); + + expect( page.url() ).toMatch( baseURL + 'google_auth' ); + } ); + } ); + + test.describe( 'Connect Merchant Center account', () => { + test.beforeAll( async () => { + await Promise.all( [ + // Mock Jetpack as connected. + setUpAccountsPage.mockJetpackConnected( + 'Test user', + 'jetpack@example.com' + ), + + // Mock google as connected. + setUpAccountsPage.mockGoogleConnected( 'google@example.com' ), + + // Mock merchant center as not connected. + setUpAccountsPage.mockMCNotConnected(), + ] ); + } ); + + test.describe( 'Merchant Center has no existing accounts', () => { + test.beforeAll( async () => { + // Mock merchant center has no accounts + await setUpAccountsPage.mockMCHasNoAccounts(); + await setUpAccountsPage.goto(); + } ); + + test( 'should see their WPORG email, Google email, "Google Merchant Center" title & "Create account" button', async () => { + const jetpackDescriptionRow = + setUpAccountsPage.getJetpackDescriptionRow(); + await expect( jetpackDescriptionRow ).toContainText( + 'jetpack@example.com' + ); + + const googleDescriptionRow = + setUpAccountsPage.getGoogleDescriptionRow(); + await expect( googleDescriptionRow ).toContainText( + 'google@example.com' + ); + + const mcTitleRow = setUpAccountsPage.getMCTitleRow(); + await expect( mcTitleRow ).toContainText( + 'Google Merchant Center' + ); + + const createAccountButton = + setUpAccountsPage.getMCCreateAccountButtonFromPage(); + await expect( createAccountButton ).toBeEnabled(); + + const continueButton = setUpAccountsPage.getContinueButton(); + await expect( continueButton ).toBeDisabled(); + } ); + + test( 'click "Create account" button should see the modal of confirmation of creating account', async () => { + // Click the create account button + const createAccountButton = + setUpAccountsPage.getMCCreateAccountButtonFromPage(); + await createAccountButton.click(); + await page.waitForLoadState( LOAD_STATE.DOM_CONTENT_LOADED ); + + const modalHeader = setUpAccountsPage.getModalHeader(); + await expect( modalHeader ).toContainText( + 'Create Google Merchant Center Account' + ); + + const modalCheckbox = setUpAccountsPage.getModalCheckbox(); + await expect( modalCheckbox ).toBeEnabled(); + + const createAccountButtonFromModal = + setUpAccountsPage.getMCCreateAccountButtonFromModal(); + await expect( createAccountButtonFromModal ).toBeDisabled(); + + // Click the checkbox of accepting ToS, the create account button will be enabled. + await modalCheckbox.click(); + await expect( createAccountButtonFromModal ).toBeEnabled(); + } ); + + test.describe( + 'click "Create account" button from the modal', + () => { + test( 'should see Merchant Center "Connected" when the website is not claimed', async ( { + baseURL, + } ) => { + await Promise.all( [ + // Mock Merchant Center create accounts + setUpAccountsPage.mockMCCreateAccountWebsiteNotClaimed(), + + // Mock Merchant Center as connected with ID 12345 + setUpAccountsPage.mockMCConnected( 12345 ), + ] ); + + const createAccountButtonFromModal = + setUpAccountsPage.getMCCreateAccountButtonFromModal(); + await createAccountButtonFromModal.click(); + await page.waitForLoadState( LOAD_STATE.NETWORK_IDLE ); + const mcConnectedLabel = + setUpAccountsPage.getMCConnectedLabel(); + await expect( mcConnectedLabel ).toContainText( + 'Connected' + ); + + const host = new URL( baseURL ).host; + const mcDescriptionRow = + setUpAccountsPage.getMCDescriptionRow(); + await expect( mcDescriptionRow ).toContainText( + `${ host } (12345)` + ); + + const continueButton = + setUpAccountsPage.getContinueButton(); + await expect( continueButton ).toBeEnabled(); + } ); + + test.describe( + 'when the website is already claimed', + () => { + test( 'should see "Reclaim my URL" button, "Switch account" button, and site URL input', async ( { + baseURL, + } ) => { + const host = new URL( baseURL ).host; + + await Promise.all( [ + // Mock merchant center has no accounts + setUpAccountsPage.mockMCHasNoAccounts(), + + // Mock Merchant Center as not connected + setUpAccountsPage.mockMCNotConnected(), + ] ); + + await setUpAccountsPage.goto(); + + // Mock Merchant Center create accounts + await setUpAccountsPage.mockMCCreateAccountWebsiteClaimed( + 12345, + host + ); + + // Click "Create account" button from the page. + const createAccountButton = + setUpAccountsPage.getMCCreateAccountButtonFromPage(); + await createAccountButton.click(); + await page.waitForLoadState( + LOAD_STATE.DOM_CONTENT_LOADED + ); + + // Check the checkbox to accept ToS. + const modalCheckbox = + setUpAccountsPage.getModalCheckbox(); + await modalCheckbox.click(); + + // Click "Create account" button from the modal. + const createAccountButtonFromModal = + setUpAccountsPage.getMCCreateAccountButtonFromModal(); + await createAccountButtonFromModal.click(); + await page.waitForLoadState( + LOAD_STATE.NETWORK_IDLE + ); + + const reclaimButton = + setUpAccountsPage.getReclaimMyURLButton(); + await expect( reclaimButton ).toBeVisible(); + + const switchAccountButton = + setUpAccountsPage.getSwitchAccountButton(); + await expect( + switchAccountButton + ).toBeVisible(); + + const reclaimingURLInput = + setUpAccountsPage.getReclaimingURLInput(); + await expect( reclaimingURLInput ).toHaveValue( + baseURL + ); + + const continueButton = + setUpAccountsPage.getContinueButton(); + await expect( continueButton ).toBeDisabled(); + } ); + + test( 'click "Reclaim my URL" should send a claim overwrite request and see Merchant Center "Connected"', async ( { + baseURL, + } ) => { + await Promise.all( [ + // Mock Merchant Center accounts claim overwrite + setUpAccountsPage.mockMCAccountsClaimOverwrite( + 12345 + ), + + // Mock Merchant Center as connected with ID 12345 + setUpAccountsPage.mockMCConnected( 12345 ), + ] ); + + const reclaimButton = + setUpAccountsPage.getReclaimMyURLButton(); + await reclaimButton.click(); + await page.waitForLoadState( + LOAD_STATE.NETWORK_IDLE + ); + + const mcConnectedLabel = + setUpAccountsPage.getMCConnectedLabel(); + await expect( mcConnectedLabel ).toContainText( + 'Connected' + ); + + const host = new URL( baseURL ).host; + const mcDescriptionRow = + setUpAccountsPage.getMCDescriptionRow(); + await expect( mcDescriptionRow ).toContainText( + `${ host } (12345)` + ); + + const continueButton = + setUpAccountsPage.getContinueButton(); + await expect( continueButton ).toBeEnabled(); + } ); + } + ); + } + ); + } ); + + test.describe( 'Merchant Center has existing accounts', () => { + test.beforeAll( async () => { + await Promise.all( [ + // Mock merchant center as not connected. + setUpAccountsPage.mockMCNotConnected(), + + // Mock merchant center has accounts + setUpAccountsPage.fulfillMCAccounts( [ + { + id: 12345, + subaccount: true, + name: 'MC Account 1', + domain: 'https://example.com', + }, + { + id: 23456, + subaccount: true, + name: 'MC Account 2', + domain: 'https://example.com', + }, + ] ), + ] ); + + await setUpAccountsPage.goto(); + } ); + + test.describe( 'connect to an existing account', () => { + test( 'should see "Select an existing account" title', async () => { + const selectAccountTitle = + setUpAccountsPage.getSelectExistingMCAccountTitle(); + await expect( selectAccountTitle ).toContainText( + 'Select an existing account' + ); + } ); + + test( 'should see "Or, create a new Merchant Center account" text', async () => { + const mcFooter = setUpAccountsPage.getMCCardFooter(); + await expect( mcFooter ).toContainText( + 'Or, create a new Merchant Center account' + ); + } ); + + test( 'should see "Connect" button', async () => { + const connectButton = setUpAccountsPage.getConnectButton(); + await expect( connectButton ).toBeEnabled(); + } ); + + test( 'should see "Continue" button is disabled', async () => { + const continueButton = + setUpAccountsPage.getContinueButton(); + await expect( continueButton ).toBeDisabled(); + } ); + + test( 'select MC Account 2 and click "Connect" button should see Merchant Center "Connected"', async ( { + baseURL, + } ) => { + await Promise.all( [ + // Mock Merchant Center create accounts + setUpAccountsPage.mockMCCreateAccountWebsiteNotClaimed(), + + // Mock Merchant Center as connected with ID 12345 + setUpAccountsPage.mockMCConnected( 23456 ), + ] ); + + // Select MC Account 2 from the options + const mcAccountsSelect = + setUpAccountsPage.getMCAccountsSelect(); + await mcAccountsSelect.selectOption( { + label: 'MC Account 2 ・ https://example.com (23456)', + } ); + + // Click connect button + const connectButton = setUpAccountsPage.getConnectButton(); + await connectButton.click(); + await page.waitForLoadState( LOAD_STATE.NETWORK_IDLE ); + + const mcConnectedLabel = + setUpAccountsPage.getMCConnectedLabel(); + await expect( mcConnectedLabel ).toContainText( + 'Connected' + ); + + const host = new URL( baseURL ).host; + const mcDescriptionRow = + setUpAccountsPage.getMCDescriptionRow(); + await expect( mcDescriptionRow ).toContainText( + `${ host } (23456)` + ); + + const continueButton = + setUpAccountsPage.getContinueButton(); + await expect( continueButton ).toBeEnabled(); + } ); + } ); + + test.describe( + 'click "Or, create a new Merchant Center account"', + () => { + test.beforeAll( async () => { + await Promise.all( [ + // Mock merchant center as not connected. + setUpAccountsPage.mockMCNotConnected(), + + // Mock merchant center has accounts + setUpAccountsPage.fulfillMCAccounts( [ + { + id: 12345, + subaccount: true, + name: 'MC Account 1', + domain: 'https://example.com', + }, + { + id: 23456, + subaccount: true, + name: 'MC Account 2', + domain: 'https://example.com', + }, + ] ), + ] ); + + await setUpAccountsPage.goto(); + } ); + + test( 'should see see a modal to ensure the intention of creating a new account', async () => { + // Click 'Or, create a new Merchant Center account' + const mcFooterButton = + setUpAccountsPage.getMCCardFooterButton(); + await mcFooterButton.click(); + await page.waitForLoadState( + LOAD_STATE.DOM_CONTENT_LOADED + ); + + const modalHeader = setUpAccountsPage.getModalHeader(); + await expect( modalHeader ).toContainText( + 'Create Google Merchant Center Account' + ); + + const modalCheckbox = + setUpAccountsPage.getModalCheckbox(); + await expect( modalCheckbox ).toBeEnabled(); + + const modalPrimaryButton = + setUpAccountsPage.getModalPrimaryButton(); + await expect( modalPrimaryButton ).toContainText( + 'Create account' + ); + await expect( modalPrimaryButton ).toBeDisabled(); + + // Select the checkbox, the button should be enabled. + await modalCheckbox.click(); + await expect( modalPrimaryButton ).toBeEnabled(); + } ); + } + ); + } ); } ); - test( 'after clicking the "Connect your Google account" button should send an API request to connect Google account, and redirect to the returned URL', async ( { - page, - baseURL, - } ) => { - // Mock google connect. - await page.route( /\/wc\/gla\/google\/connect\b/, ( route ) => - route.fulfill( { - content: 'application/json', - headers: { 'Access-Control-Allow-Origin': '*' }, - body: JSON.stringify( { - url: baseURL + 'google_auth', - } ), - } ) - ); - - // Click the enabled connect button - page.locator( "//button[text()='Connect'][not(@disabled)]" ).click(); - await page.waitForLoadState( 'networkidle' ); - - // Expect the user to be redirected - await page.waitForURL( baseURL + 'google_auth' ); + test.describe( 'Links', () => { + test( 'should contain the correct URL for "Google Merchant Center Help" link', async () => { + await setUpAccountsPage.goto(); + const link = setUpAccountsPage.getMCHelpLink(); + await expect( link ).toBeVisible(); + await expect( link ).toHaveAttribute( + 'href', + 'https://support.google.com/merchants/topic/9080307' + ); + } ); + + test( 'should contain the correct URL for CSS Partners link', async () => { + const cssPartersLink = + 'https://comparisonshoppingpartners.withgoogle.com/find_a_partner/'; + const link = setUpAccountsPage.getCSSPartnersLink(); + await expect( link ).toBeVisible(); + await expect( link ).toHaveAttribute( 'href', cssPartersLink ); + + const faqTitle2 = setUpAccountsPage.getFAQPanelTitle().nth( 1 ); + await faqTitle2.click(); + const linkInFAQ = setUpAccountsPage.getCSSPartnersLink( + 'Please find more information here' + ); + await expect( linkInFAQ ).toBeVisible(); + await expect( linkInFAQ ).toHaveAttribute( 'href', cssPartersLink ); + } ); } ); } ); diff --git a/tests/e2e/utils/constants.js b/tests/e2e/utils/constants.js new file mode 100644 index 0000000000..3460f444b2 --- /dev/null +++ b/tests/e2e/utils/constants.js @@ -0,0 +1,4 @@ +export const LOAD_STATE = { + NETWORK_IDLE: 'networkidle', + DOM_CONTENT_LOADED: 'domcontentloaded', +}; diff --git a/tests/e2e/utils/customer.js b/tests/e2e/utils/customer.js index 6418f14e93..4e1bff04ca 100644 --- a/tests/e2e/utils/customer.js +++ b/tests/e2e/utils/customer.js @@ -12,6 +12,7 @@ const { expect } = require( '@playwright/test' ); /** * Internal dependencies */ +import { LOAD_STATE } from './constants'; const config = require( '../config/default.json' ); /** @@ -30,7 +31,7 @@ export async function singleProductAddToCart( page, productID ) { ).toBeVisible(); // Wait till all tracking event request have been sent after page reloaded. - await page.waitForLoadState( 'networkidle' ); + await page.waitForLoadState( LOAD_STATE.NETWORK_IDLE ); } /** diff --git a/tests/e2e/utils/mock-requests.js b/tests/e2e/utils/mock-requests.js index 406ccf1caa..3fbcf36dbb 100644 --- a/tests/e2e/utils/mock-requests.js +++ b/tests/e2e/utils/mock-requests.js @@ -14,13 +14,15 @@ export default class MockRequests { /** * Fulfill a request with a payload. * - * @param {RegExp|string} url The url to fulfill. + * @param {RegExp|string} url The url to fulfill. * @param {Object} payload The payload to send. + * @param {number} status The HTTP status in the response. * @return {Promise} */ - async fulfillRequest( url, payload ) { + async fulfillRequest( url, payload, status = 200 ) { await this.page.route( url, ( route ) => route.fulfill( { + status, content: 'application/json', headers: { 'Access-Control-Allow-Origin': '*' }, body: JSON.stringify( payload ), @@ -54,6 +56,46 @@ export default class MockRequests { ); } + /** + * Fulfill the MC accounts request. + * + * @param {Object} payload + * @param {number} status + * @return {Promise} + */ + async fulfillMCAccounts( payload, status = 200 ) { + await this.fulfillRequest( + /\/wc\/gla\/mc\/accounts\b/, + payload, + status + ); + } + + /** + * Fulfill the MC accounts claim-overwrite request. + * + * @param {Object} payload + * @param {number} status + * @return {Promise} + */ + async fulfillMCAccountsClaimOverwrite( payload, status = 200 ) { + await this.fulfillRequest( + /\/wc\/gla\/mc\/accounts\/claim-overwrite\b/, + payload, + status + ); + } + + /** + * Fulfill the MC connection request. + * + * @param {Object} payload + * @return {Promise} + */ + async fulfillMCConnection( payload ) { + await this.fulfillRequest( /\/wc\/gla\/mc\/connection\b/, payload ); + } + /** * Fulfill the JetPack Connection request. * @@ -64,6 +106,16 @@ export default class MockRequests { await this.fulfillRequest( /\/wc\/gla\/jetpack\/connected\b/, payload ); } + /** + * Fulfill the request to connect Jetpack. + * + * @param {Object} payload + * @return {Promise} + */ + async fulfillConnectJetPack( payload ) { + await this.fulfillRequest( /\/wc\/gla\/jetpack\/connect\b/, payload ); + } + /** * Fulfill the Google Connection request. * @@ -74,6 +126,16 @@ export default class MockRequests { await this.fulfillRequest( /\/wc\/gla\/google\/connected\b/, payload ); } + /** + * Fulfill the request to connect Google. + * + * @param {Object} payload + * @return {Promise} + */ + async fulfillConnectGoogle( payload ) { + await this.fulfillRequest( /\/wc\/gla\/google\/connect\b/, payload ); + } + /** * Fulfill the Ads Connection request. * @@ -93,4 +155,168 @@ export default class MockRequests { async fulfillSettingsSync( payload ) { await this.fulfillRequest( /\/wc\/gla\/mc\/settings\/sync\b/, payload ); } + + /** + * Mock the request to connect Jetpack + * + * @param {string} url + */ + async mockJetpackConnect( url ) { + await this.fulfillConnectJetPack( { url } ); + } + + /** + * Mock Jetpack as connected. + * + * @param {string} displayName + * @param {string} email + */ + async mockJetpackConnected( + displayName = 'John', + email = 'mail@example.com' + ) { + await this.fulfillJetPackConnection( { + active: 'yes', + owner: 'yes', + displayName, + email, + } ); + } + + /** + * Mock the request to connect Google. + * + * @param {string} url + */ + async mockGoogleConnect( url ) { + await this.fulfillConnectGoogle( { url } ); + } + + /** + * Mock Google as connected. + * + * @param {string} email + */ + async mockGoogleConnected( email = 'mail@example.com' ) { + await this.fulfillGoogleConnection( { + active: 'yes', + email, + scope: [ + 'https://www.googleapis.com/auth/content', + 'https://www.googleapis.com/auth/adwords', + 'https://www.googleapis.com/auth/userinfo.email', + 'https://www.googleapis.com/auth/siteverification.verify_only', + 'openid', + ], + } ); + } + + /** + * Mock Google as not connected. + */ + async mockGoogleNotConnected() { + await this.fulfillGoogleConnection( { + active: 'no', + email: '', + scope: [], + } ); + } + + /** + * Mock MC as connected. + * + * @param {number} id + */ + async mockMCConnected( id = 1234 ) { + await this.fulfillMCConnection( { + id, + status: 'connected', + } ); + } + + /** + * Mock MC as not connected. + */ + async mockMCNotConnected() { + await this.fulfillMCConnection( { + id: 0, + status: 'disconnected', + } ); + } + + /** + * Mock MC has accounts. + */ + async mockMCHasAccounts() { + await this.fulfillMCAccounts( [ + { + id: 12345, + subaccount: true, + name: 'MC Account 1', + domain: 'https://example.com', + }, + { + id: 23456, + subaccount: true, + name: 'MC Account 2', + domain: 'https://example.com', + }, + ] ); + } + + /** + * Mock MC has no accounts. + */ + async mockMCHasNoAccounts() { + await this.fulfillMCAccounts( [] ); + } + + /** + * Mock MC create account where the website is not claimed. + * + * @param {number} id + */ + async mockMCCreateAccountWebsiteNotClaimed( id = 12345 ) { + await this.fulfillMCAccounts( { + id, + subaccount: null, + name: null, + domain: null, + } ); + } + + /** + * Mock MC create account where the website is claimed. + * + * @param {number} id + * @param {string} websiteUrl + */ + async mockMCCreateAccountWebsiteClaimed( + id = 12345, + websiteUrl = 'example.com' + ) { + await this.fulfillMCAccounts( + { + message: + 'Website already claimed, use overwrite to complete the process.', + id, + website_url: websiteUrl, + }, + 403 + ); + } + + /** + * Mock MC accounts claim overwrite. + * + * @param {number} id + */ + async mockMCAccountsClaimOverwrite( id = 12345 ) { + await this.fulfillMCAccountsClaimOverwrite( { + id, + subaccount: null, + name: null, + domain: null, + } ); + } } diff --git a/tests/e2e/utils/pages/dashboard.js b/tests/e2e/utils/pages/dashboard.js index 925426330a..5f2d5d44af 100644 --- a/tests/e2e/utils/pages/dashboard.js +++ b/tests/e2e/utils/pages/dashboard.js @@ -1,6 +1,7 @@ /** * Internal dependencies */ +import { LOAD_STATE } from '../constants'; import MockRequests from '../mock-requests'; /** @@ -84,7 +85,7 @@ export default class DashboardPage extends MockRequests { async goto() { await this.page.goto( '/wp-admin/admin.php?page=wc-admin&path=%2Fgoogle%2Fdashboard', - { waitUntil: 'domcontentloaded' } + { waitUntil: LOAD_STATE.DOM_CONTENT_LOADED } ); } diff --git a/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js b/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js new file mode 100644 index 0000000000..3f87e9be52 --- /dev/null +++ b/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js @@ -0,0 +1,406 @@ +/** + * Internal dependencies + */ +import { LOAD_STATE } from '../../constants'; +import MockRequests from '../../mock-requests'; + +/** + * Set up accounts page object class. + */ +export default class SetUpAccountsPage extends MockRequests { + /** + * @param {import('@playwright/test').Page} page + */ + constructor( page ) { + super( page ); + this.page = page; + } + + /** + * Close the current page. + * + * @return {Promise} + */ + async closePage() { + await this.page.close(); + } + + /** + * Go to the set up mc page. + * + * @return {Promise} + */ + async goto() { + await this.page.goto( + '/wp-admin/admin.php?page=wc-admin&path=%2Fgoogle%2Fsetup-mc', + { waitUntil: LOAD_STATE.NETWORK_IDLE } + ); + } + + /** + * Get "Create account" button. + * + * @return {import('@playwright/test').Locator} Get "Create account" button. + */ + getCreateAccountButton() { + return this.page.getByRole( 'button', { + name: 'Create account', + exact: true, + } ); + } + + /** + * Get MC "Create account" button from the page. + * + * @return {import('@playwright/test').Locator} Get MC "Create account" button from the page. + */ + getMCCreateAccountButtonFromPage() { + const button = this.getCreateAccountButton(); + return button.locator( ':scope.is-secondary' ); + } + + /** + * Get MC "Create account" button from the modal. + * + * @return {import('@playwright/test').Locator} Get MC "Create account" button from the modal. + */ + getMCCreateAccountButtonFromModal() { + const button = this.getCreateAccountButton(); + return button.locator( ':scope.is-primary' ); + } + + /** + * Get .gla-account-card__title class. + * + * @return {import('@playwright/test').Locator} Get .gla-account-card__title class. + */ + getCardTitleClass() { + return this.page.locator( '.gla-account-card__title' ); + } + + /** + * Get .gla-account-card__description class. + * + * @return {import('@playwright/test').Locator} Get .gla-account-card__description class. + */ + getCardDescriptionClass() { + return this.page.locator( '.gla-account-card__description' ); + } + + /** + * Get Jetpack description row. + * + * @return {import('@playwright/test').Locator} Get Jetpack description row. + */ + getJetpackDescriptionRow() { + return this.getCardDescriptionClass().first(); + } + + /** + * Get Google description row. + * + * @return {import('@playwright/test').Locator} Get Google description row. + */ + getGoogleDescriptionRow() { + return this.getCardDescriptionClass().nth( 1 ); + } + + /** + * Get Merchant Center description row. + * + * @return {import('@playwright/test').Locator} Get Merchant Center description row. + */ + getMCDescriptionRow() { + return this.getCardDescriptionClass().nth( 2 ); + } + + /** + * Get Google Merchant Center title. + * + * @return {import('@playwright/test').Locator} Get Google Merchant Center title. + */ + getMCTitleRow() { + return this.getCardTitleClass().nth( 2 ); + } + + /** + * Get modal. + * + * @return {import('@playwright/test').Locator} Get modal. + */ + getModal() { + return this.page.locator( '.components-modal__content' ); + } + + /** + * Get modal header. + * + * @return {import('@playwright/test').Locator} Get modal header. + */ + getModalHeader() { + return this.page.locator( '.components-modal__header' ); + } + + /** + * Get modal checkbox. + * + * @return {import('@playwright/test').Locator} Get modal checkbox. + */ + getModalCheckbox() { + return this.page.getByRole( 'checkbox' ); + } + + /** + * Get modal primary button. + * + * @return {import('@playwright/test').Locator} Get modal primary button. + */ + getModalPrimaryButton() { + return this.getModal().locator( 'button.is-primary' ); + } + + /** + * Get modal secondary button. + * + * @return {import('@playwright/test').Locator} Get modal secondary button. + */ + getModalSecondaryButton() { + return this.getModal().locator( 'button.is-secondary' ); + } + + /** + * Get .gla-connected-icon-label class. + * + * @return {import('@playwright/test').Locator} Get .gla-connected-icon-label class. + */ + getConnectedLabelClass() { + return this.page.locator( '.gla-connected-icon-label' ); + } + + /** + * Get Jetpack connected label. + * + * @return {import('@playwright/test').Locator} Get Jetpack connected label. + */ + getJetpackConnectedLabel() { + return this.getConnectedLabelClass().first(); + } + + /** + * Get Google connected label. + * + * @return {import('@playwright/test').Locator} Get Google connected label. + */ + getGoogleConnectedLabel() { + return this.getConnectedLabelClass().nth( 1 ); + } + + /** + * Get Merchant Center connected label. + * + * @return {import('@playwright/test').Locator} Get Merchant Center connected label. + */ + getMCConnectedLabel() { + return this.getConnectedLabelClass().nth( 2 ); + } + + /** + * Get "Reclaim my URL" button. + * + * @return {import('@playwright/test').Locator} Get "Reclaim my URL" button. + */ + getReclaimMyURLButton() { + return this.page.getByRole( 'button', { + name: 'Reclaim my URL', + exact: true, + } ); + } + + /** + * Get "Switch account" button. + * + * @return {import('@playwright/test').Locator} Get "Switch account" button. + */ + getSwitchAccountButton() { + return this.page.getByRole( 'button', { + name: 'Switch account', + exact: true, + } ); + } + + /** + * Get reclaiming URL input. + * + * @return {import('@playwright/test').Locator} Get reclaiming URL input. + */ + getReclaimingURLInput() { + return this.page.locator( 'input#inspector-input-control-0' ); + } + + /** + * Get sub section title row. + * + * @return {import('@playwright/test').Locator} Get sub section title row. + */ + getSubSectionTitleRow() { + return this.page.locator( '.wcdl-subsection-title' ); + } + + /** + * Get section footer row. + * + * @return {import('@playwright/test').Locator} Get section footer row. + */ + getSectionFooterRow() { + return this.page.locator( '.wcdl-section-card-footer' ); + } + + /** + * Get select existing Merchant Center account title. + * + * @return {import('@playwright/test').Locator} Get select existing Merchant Center account title. + */ + getSelectExistingMCAccountTitle() { + return this.getSubSectionTitleRow().nth( 3 ); + } + + /** + * Get MC accounts select element. + * + * @return {import('@playwright/test').Locator} Get select MC accounts select element. + */ + getMCAccountsSelect() { + return this.page.locator( 'select[id*="inspector-select-control"]' ); + } + + /** + * Get "Connect" button. + * + * @return {import('@playwright/test').Locator} Get "Connect" button. + */ + getConnectButton() { + return this.page.getByRole( 'button', { + name: 'Connect', + exact: true, + } ); + } + + /** + * Get account cards. + * + * @return {import('@playwright/test').Locator} Get account cards. + */ + getAccountCards() { + return this.page.locator( '.gla-account-card' ); + } + + /** + * Get WordPress account card. + * + * @return {import('@playwright/test').Locator} Get WordPress account card. + */ + getWPAccountCard() { + return this.getAccountCards().first(); + } + + /** + * Get Google account card. + * + * @return {import('@playwright/test').Locator} Get Google account card. + */ + getGoogleAccountCard() { + return this.getAccountCards().nth( 1 ); + } + + /** + * Get Merchant Center account card. + * + * @return {import('@playwright/test').Locator} Get Merchant Center account card. + */ + getMCAccountCard() { + return this.getAccountCards().nth( 2 ); + } + + /** + * Get Merchant Center card footer. + * + * @return {import('@playwright/test').Locator} Get Merchant Center card footer. + */ + getMCCardFooter() { + return this.getMCAccountCard().locator( '.wcdl-section-card-footer' ); + } + + /** + * Get Merchant Center card footer button. + * + * @return {import('@playwright/test').Locator} Get Merchant Center card footer button. + */ + getMCCardFooterButton() { + return this.getMCCardFooter().getByRole( 'button' ); + } + + /** + * Get "Continue" button. + * + * @return {import('@playwright/test').Locator} Get "Continue" button. + */ + getContinueButton() { + return this.page.getByRole( 'button', { + name: 'Continue', + exact: true, + } ); + } + + /** + * Get FAQ panel. + * + * @return {import('@playwright/test').Locator} Get FAQ panel. + */ + getFAQPanel() { + return this.page.locator( '.gla-faqs-panel' ); + } + + /** + * Get FAQ panel title. + * + * @return {import('@playwright/test').Locator} Get FAQ panel title. + */ + getFAQPanelTitle() { + return this.getFAQPanel().locator( '.components-panel__body-title' ); + } + + /** + * Get FAQ panel row. + * + * @return {import('@playwright/test').Locator} Get FAQ panel row. + */ + getFAQPanelRow() { + return this.getFAQPanel().locator( '.components-panel__row' ); + } + + /** + * Get link of Google Merchant Center Help. + * + * @return {import('@playwright/test').Locator} Get link of Google Merchant Center Help. + */ + getMCHelpLink() { + return this.page.getByRole( 'link', { + name: 'Google Merchant Center Help', + exact: true, + } ); + } + + /** + * Get link of CSS partners. + * + * @param {string} name + * + * @return {import('@playwright/test').Locator} Get link of CSS partners. + */ + getCSSPartnersLink( name = 'here' ) { + return this.page.getByRole( 'link', { + name, + exact: true, + } ); + } +}