diff --git a/changelog/dev-10084-shopper-myaccount-renew-subscription-e2e-test b/changelog/dev-10084-shopper-myaccount-renew-subscription-e2e-test new file mode 100644 index 00000000000..0bf90c28107 --- /dev/null +++ b/changelog/dev-10084-shopper-myaccount-renew-subscription-e2e-test @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Migrate the Shopper Renew Subscription spec to Playwright and remove the corresponding Puppeteer test. diff --git a/tests/e2e-pw/config/default.ts b/tests/e2e-pw/config/default.ts index a3742804047..d370c84d25f 100644 --- a/tests/e2e-pw/config/default.ts +++ b/tests/e2e-pw/config/default.ts @@ -76,12 +76,12 @@ export const config = { }, 'subscriptions-customer': { billing: { - first_name: 'I am', - last_name: 'Subscriptions Customer', + firstname: 'I am', + lastname: 'Subscriptions Customer', company: 'Automattic', country: 'United States (US)', - address_1: '60 29th Street #343', - address_2: 'billing', + addressfirstline: '60 29th Street #343', + addresssecondline: 'billing', city: 'San Francisco', state: 'CA', postcode: '94110', @@ -89,12 +89,12 @@ export const config = { email: 'e2e-wcpay-subscriptions-customer@woo.com', }, shipping: { - first_name: 'I am', - last_name: 'Subscriptions Recipient', + firstname: 'I am', + lastname: 'Subscriptions Recipient', company: 'Automattic', country: 'United States (US)', - address_1: '60 29th Street #343', - address_2: 'shipping', + addressfirstline: '60 29th Street #343', + addresssecondline: 'shipping', city: 'San Francisco', state: 'CA', postcode: '94110', diff --git a/tests/e2e-pw/specs/shopper/shopper-myaccount-renew-subscription.spec.ts b/tests/e2e-pw/specs/shopper/shopper-myaccount-renew-subscription.spec.ts new file mode 100644 index 00000000000..1388b66d305 --- /dev/null +++ b/tests/e2e-pw/specs/shopper/shopper-myaccount-renew-subscription.spec.ts @@ -0,0 +1,78 @@ +/** + * External dependencies + */ +import { test, expect, Page } from '@playwright/test'; + +/** + * Internal dependencies + */ +import { config } from '../../config/default'; +import { describeif, getAnonymousShopper } from '../../utils/helpers'; +import * as shopper from '../../utils/shopper'; +import * as navigation from '../../utils/shopper-navigation'; +import { products, shouldRunSubscriptionsTests } from '../../utils/constants'; +import RestAPI from '../../utils/rest-api'; + +describeif( shouldRunSubscriptionsTests )( + 'Subscriptions > Renew a subscription in my account', + () => { + const customerBillingConfig = + config.addresses[ 'subscriptions-customer' ].billing; + + let subscriptionId: string; + let page: Page; + + test.beforeAll( async ( { browser }, { project } ) => { + const restApi = new RestAPI( project.use.baseURL ); + await restApi.deleteCustomerByEmailAddress( + customerBillingConfig.email + ); + + const { shopperPage } = await getAnonymousShopper( browser ); + page = shopperPage; + } ); + + test( 'should be able to purchase a subscription', async () => { + await shopper.addCartProduct( + page, + products.SUBSCRIPTION_SIGNUP_FEE + ); + await shopper.setupCheckout( page, customerBillingConfig ); + await shopper.fillCardDetails( page, config.cards.basic ); + await shopper.placeOrder( page ); + await expect( + page.getByRole( 'heading', { name: 'Order received' } ) + ).toBeVisible(); + + subscriptionId = await page + .getByLabel( 'View subscription number' ) + .innerText(); + } ); + + test( 'should be able to renew a subscription in my account', async () => { + await navigation.goToSubscriptions( page ); + + if ( ! subscriptionId ) { + throw new Error( 'Subscription ID is not set' ); + } + + const numericSubscriptionId = subscriptionId.substring( 1 ); + + await page + .getByLabel( + `View subscription number ${ numericSubscriptionId }` + ) + .click(); + + await page.getByText( 'Renew now' ).click(); + await page + .getByText( 'Complete checkout to renew now.' ) + .isVisible(); + await shopper.focusPlaceOrderButton( page ); + await shopper.placeOrder( page ); + await expect( + page.getByRole( 'heading', { name: 'Order received' } ) + ).toBeVisible(); + } ); + } +); diff --git a/tests/e2e-pw/utils/constants.ts b/tests/e2e-pw/utils/constants.ts new file mode 100644 index 00000000000..94f4a02d53d --- /dev/null +++ b/tests/e2e-pw/utils/constants.ts @@ -0,0 +1,6 @@ +export const shouldRunSubscriptionsTests = + process.env.SKIP_WC_SUBSCRIPTIONS_TESTS !== '1'; + +export const products = { + SUBSCRIPTION_SIGNUP_FEE: 70, +}; diff --git a/tests/e2e-pw/utils/helpers.ts b/tests/e2e-pw/utils/helpers.ts index 8e0c6881300..6fb1b9d779a 100644 --- a/tests/e2e-pw/utils/helpers.ts +++ b/tests/e2e-pw/utils/helpers.ts @@ -93,3 +93,9 @@ export const getAnonymousShopper = async ( const shopperPage = await shopperContext.newPage(); return { shopperPage, shopperContext }; }; + +/** + * Conditionally determine whether or not to skip a test suite. + */ +export const describeif = ( condition: boolean ) => + condition ? test.describe : test.describe.skip; diff --git a/tests/e2e-pw/utils/rest-api.ts b/tests/e2e-pw/utils/rest-api.ts new file mode 100644 index 00000000000..400262422d2 --- /dev/null +++ b/tests/e2e-pw/utils/rest-api.ts @@ -0,0 +1,68 @@ +/** + * External dependencies + */ +import { HTTPClientFactory } from '@woocommerce/api'; + +/** + * Internal dependencies + */ +import { config } from '../config/default'; + +const userEndpoint = '/wp/v2/users'; + +class RestAPI { + private baseUrl: string; + + constructor( baseUrl: string ) { + if ( ! baseUrl ) { + throw new Error( 'Base URL is required.' ); + } + this.baseUrl = baseUrl; + } + + private getAdminClient() { + return HTTPClientFactory.build( this.baseUrl ) + .withBasicAuth( + config.users.admin.username, + config.users.admin.password + ) + .create(); + } + + /** + * Deletes a customer account by their email address if the user exists. + * + * Copied from https://github.com/woocommerce/woocommerce/blob/trunk/packages/js/e2e-utils/src/flows/with-rest-api.js#L374 + * + * @param {string} emailAddress Customer user account email address. + * @return {Promise} + */ + async deleteCustomerByEmailAddress( + emailAddress: string + ): Promise< void > { + const client = this.getAdminClient(); + + const query = { + search: emailAddress, + context: 'edit', + }; + const customers = await client.get( userEndpoint, query ); + + if ( customers.data && customers.data.length ) { + for ( let c = 0; c < customers.data.length; c++ ) { + const deleteUser = { + id: customers.data[ c ].id, + force: true, + reassign: 1, + }; + + await client.delete( + `${ userEndpoint }/${ deleteUser.id }`, + deleteUser + ); + } + } + } +} + +export default RestAPI; diff --git a/tests/e2e-pw/utils/shopper-navigation.ts b/tests/e2e-pw/utils/shopper-navigation.ts index 7f96a1e9055..8343bcc5cee 100644 --- a/tests/e2e-pw/utils/shopper-navigation.ts +++ b/tests/e2e-pw/utils/shopper-navigation.ts @@ -43,3 +43,8 @@ export const goToOrder = async ( page: Page, orderId: string ) => { waitUntil: 'load', } ); }; + +export const goToSubscriptions = ( page: Page ) => + page.goto( '/my-account/subscriptions/', { + waitUntil: 'load', + } ); diff --git a/tests/e2e-pw/utils/shopper.ts b/tests/e2e-pw/utils/shopper.ts index b39869b8426..8f520ea38fe 100644 --- a/tests/e2e-pw/utils/shopper.ts +++ b/tests/e2e-pw/utils/shopper.ts @@ -12,6 +12,24 @@ export const isUIUnblocked = async ( page: Page ) => { await expect( page.locator( '.blockUI' ) ).toHaveCount( 0 ); }; +/** + * Waits for the UI to refresh after a user interaction. + * + * Woo core blocks and refreshes the UI after 1s after each key press + * in a text field or immediately after a select field changes. + * We need to wait to make sure that all key presses were processed by that mechanism. + */ +export const waitForUiRefresh = ( page: Page ) => page.waitForTimeout( 1000 ); + +/** + * Takes off the focus out of the Stripe elements to let Stripe logic + * wrap up and make sure the Place Order button is clickable. + */ +export const focusPlaceOrderButton = async ( page: Page ) => { + await page.locator( '#place_order' ).focus(); + await waitForUiRefresh( page ); +}; + export const fillBillingAddress = async ( page: Page, billingAddress: CustomerAddress @@ -188,10 +206,7 @@ export const setupCheckout = async ( ) => { await navigation.goToCheckout( page ); await fillBillingAddress( page, billingAddress ); - // Woo core blocks and refreshes the UI after 1s after each key press - // in a text field or immediately after a select field changes. - // We need to wait to make sure that all key presses were processed by that mechanism. - await page.waitForTimeout( 1000 ); + await waitForUiRefresh( page ); await isUIUnblocked( page ); await page .locator( '.wc_payment_method.payment_method_woocommerce_payments' ) @@ -252,10 +267,7 @@ export const placeOrderWithCurrency = async ( await navigation.goToShopWithCurrency( page, currency ); await setupProductCheckout( page, [ [ config.products.simple.name, 1 ] ] ); await fillCardDetails( page, config.cards.basic ); - // Takes off the focus out of the Stripe elements to let Stripe logic - // wrap up and make sure the Place Order button is clickable. - await page.locator( '#place_order' ).focus(); - await page.waitForTimeout( 1000 ); + await focusPlaceOrderButton( page ); await placeOrder( page ); await page.waitForURL( /\/order-received\//, { waitUntil: 'load' } ); await expect( diff --git a/tests/e2e/specs/subscriptions/shopper/shopper-myaccount-renew-subscription.spec.js b/tests/e2e/specs/subscriptions/shopper/shopper-myaccount-renew-subscription.spec.js deleted file mode 100644 index e44f026cc42..00000000000 --- a/tests/e2e/specs/subscriptions/shopper/shopper-myaccount-renew-subscription.spec.js +++ /dev/null @@ -1,81 +0,0 @@ -/** - * External dependencies - */ -import config from 'config'; -const { shopper, withRestApi } = require( '@woocommerce/e2e-utils' ); -import { - RUN_SUBSCRIPTIONS_TESTS, - describeif, - shopperWCP, -} from '../../../utils'; -import { fillCardDetails, setupCheckout } from '../../../utils/payments'; - -const productSlug = 'subscription-signup-fee-product'; -const customerBilling = config.get( - 'addresses.subscriptions-customer.billing' -); -let subscriptionId; - -const testSelectors = { - subscriptionIdField: '.woocommerce-orders-table__cell-subscription-id > a', - subscriptionRenewButton: 'a.button.subscription_renewal_early', - wcNotice: 'div.wc-block-components-notice-banner', - wcOldNotice: - '.woocommerce .woocommerce-notices-wrapper .woocommerce-message', -}; - -describeif( RUN_SUBSCRIPTIONS_TESTS )( - 'Subscriptions > Renew a subscription in my account', - () => { - beforeAll( async () => { - // Delete the user, if present - await withRestApi.deleteCustomerByEmail( customerBilling.email ); - } ); - afterAll( async () => { - await shopper.logout(); - } ); - - it( 'should be able to purchase a subscription', async () => { - // Open the subscription product & add to cart - await shopperWCP.addToCartBySlug( productSlug ); - - // Checkout - await setupCheckout( customerBilling ); - const card = config.get( 'cards.basic' ); - await fillCardDetails( page, card ); - await shopper.placeOrder(); - await expect( page ).toMatchTextContent( 'Order received' ); - - // Get the subscription ID - const subscriptionIdField = await page.$( - testSelectors.subscriptionIdField - ); - subscriptionId = await subscriptionIdField.evaluate( - ( el ) => el.innerText - ); - } ); - - it( 'should be able to renew a subscription in my account', async () => { - // Go to my account and click to renew a subscription - await shopperWCP.goToSubscriptions(); - await expect( page ).toClick( testSelectors.subscriptionIdField, { - text: subscriptionId, - } ); - await page.waitForNavigation( { waitUntil: 'networkidle0' } ); - await expect( page ).toClick( - testSelectors.subscriptionRenewButton - ); - - // Place an order to renew a subscription - await shopperWCP.waitForSubscriptionsErrorBanner( - 'Complete checkout to renew now.', - testSelectors.wcNotice, - testSelectors.wcOldNotice - ); - await page.waitForNavigation( { waitUntil: 'networkidle0' } ); - - await shopper.placeOrder(); - await expect( page ).toMatchTextContent( 'Order received' ); - } ); - } -);