Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate the Shopper Renew Subscription E2E test to Playwright #10144

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: dev

Migrate the Shopper Renew Subscription spec to Playwright and remove the corresponding Puppeteer test.
16 changes: 8 additions & 8 deletions tests/e2e-pw/config/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,25 +76,25 @@ 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',
phone: '123456789',
email: '[email protected]',
},
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',
Expand Down
Original file line number Diff line number Diff line change
@@ -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 );
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're using project.use.baseURL to get the same base URL Playwright is using while running the tests.

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' );
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this throw to provide a better error in case the previous test fails. We can remove it if not desired.

}

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();
} );
}
);
6 changes: 6 additions & 0 deletions tests/e2e-pw/utils/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const shouldRunSubscriptionsTests =
process.env.SKIP_WC_SUBSCRIPTIONS_TESTS !== '1';

export const products = {
Copy link
Contributor Author

@eduardoumpierre eduardoumpierre Jan 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure about the names of those constants. I wanted to use SHOULD_RUN_SUBSCRIPTIONS_TESTS and PRODUCTS, but since ESLint flagged it, I opted to keep both in lower camel case for now.

SUBSCRIPTION_SIGNUP_FEE: 70,
};
6 changes: 6 additions & 0 deletions tests/e2e-pw/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
68 changes: 68 additions & 0 deletions tests/e2e-pw/utils/rest-api.ts
Original file line number Diff line number Diff line change
@@ -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<void>}
*/
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;
5 changes: 5 additions & 0 deletions tests/e2e-pw/utils/shopper-navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
} );
28 changes: 20 additions & 8 deletions tests/e2e-pw/utils/shopper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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' )
Expand Down Expand Up @@ -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(
Expand Down

This file was deleted.

Loading