Skip to content

Commit 1083afc

Browse files
committed
add pay-order-page tests
1 parent 544f709 commit 1083afc

File tree

5 files changed

+407
-55
lines changed

5 files changed

+407
-55
lines changed

playwright.config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ export default defineConfig({
4242
testMatch: '**/monei-settings-api.spec.ts',
4343
workers: 1
4444
},
45+
{
46+
name: 'pay-order-tests',
47+
dependencies: ['setup'],
48+
use: { ...devices['Desktop Chrome'] },
49+
testMatch: '**/pay-order-gateway-tests.spec.ts',
50+
},
4551
],
4652

4753
/* Run your local dev server before starting the tests */
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import { WordPressApiClient } from '../setup/wordpress-api-client';
2+
import { ProductType } from '../fixtures/product-types';
3+
import { UserType } from '../fixtures/user-types';
4+
5+
export interface OrderData {
6+
id: number;
7+
order_key: string;
8+
total: string;
9+
status: string;
10+
currency: string;
11+
date_created: string;
12+
}
13+
14+
export class OrderCreationHelper {
15+
private apiClient: WordPressApiClient;
16+
private createdOrderIds: number[] = [];
17+
18+
constructor(apiClient?: WordPressApiClient) {
19+
this.apiClient = apiClient || new WordPressApiClient();
20+
}
21+
22+
/**
23+
* Create a WooCommerce order programmatically via REST API
24+
*/
25+
async createOrder(options: {
26+
productType?: ProductType;
27+
userType?: UserType;
28+
paymentMethod?: string;
29+
status?: string;
30+
lineItems?: Array<{ sku: string; quantity: number }>;
31+
shippingMethod?: string;
32+
couponCodes?: string[];
33+
}): Promise<OrderData> {
34+
const {
35+
productType,
36+
userType,
37+
paymentMethod = 'monei',
38+
status = 'pending',
39+
lineItems,
40+
shippingMethod = 'flat_rate',
41+
couponCodes = []
42+
} = options;
43+
44+
// Build line items
45+
const items = lineItems || (productType ? [{
46+
sku: productType.sku,
47+
quantity: 1
48+
}] : []);
49+
50+
// Build order data
51+
const orderData = {
52+
payment_method: paymentMethod,
53+
payment_method_title: this.getPaymentMethodTitle(paymentMethod),
54+
set_paid: false,
55+
status: status,
56+
billing: userType ? {
57+
first_name: userType.firstName,
58+
last_name: userType.lastName,
59+
address_1: userType.address,
60+
address_2: '',
61+
city: userType.city,
62+
state: userType.state || '',
63+
postcode: userType.postcode,
64+
country: userType.country,
65+
email: userType.email,
66+
phone: userType.phone
67+
} : undefined,
68+
shipping: userType ? {
69+
first_name: userType.firstName,
70+
last_name: userType.lastName,
71+
address_1: userType.address,
72+
address_2: '',
73+
city: userType.city,
74+
state: userType.state || '',
75+
postcode: userType.postcode,
76+
country: userType.country
77+
} : undefined,
78+
line_items: items,
79+
shipping_lines: [
80+
{
81+
method_id: shippingMethod,
82+
method_title: this.getShippingMethodTitle(shippingMethod),
83+
total: '10.00'
84+
}
85+
],
86+
coupon_lines: couponCodes.map(code => ({ code }))
87+
};
88+
89+
try {
90+
const response = await this.apiClient.wooCommerce.post('orders', orderData);
91+
const order = response.data;
92+
93+
this.createdOrderIds.push(order.id);
94+
95+
console.log(`📦 Created order #${order.id}`);
96+
console.log(` Status: ${order.status}`);
97+
console.log(` Total: ${order.currency_symbol}${order.total}`);
98+
console.log(` Key: ${order.order_key}`);
99+
100+
return {
101+
id: order.id,
102+
order_key: order.order_key,
103+
total: order.total,
104+
status: order.status,
105+
currency: order.currency,
106+
date_created: order.date_created
107+
};
108+
} catch (error) {
109+
console.error('Failed to create order:', error);
110+
throw error;
111+
}
112+
}
113+
114+
/**
115+
* Create multiple orders for batch testing
116+
*/
117+
async createBatchOrders(count: number, options: Parameters<typeof this.createOrder>[0]): Promise<OrderData[]> {
118+
const orders: OrderData[] = [];
119+
120+
for (let i = 0; i < count; i++) {
121+
const order = await this.createOrder(options);
122+
orders.push(order);
123+
}
124+
125+
return orders;
126+
}
127+
128+
/**
129+
* Update order status
130+
*/
131+
async updateOrderStatus(orderId: number, status: string): Promise<void> {
132+
await this.apiClient.wooCommerce.put(`orders/${orderId}`, { status });
133+
console.log(`📝 Updated order #${orderId} status to: ${status}`);
134+
}
135+
136+
/**
137+
* Add a note to an order
138+
*/
139+
async addOrderNote(orderId: number, note: string, customerNote: boolean = false): Promise<void> {
140+
await this.apiClient.wooCommerce.post(`orders/${orderId}/notes`, {
141+
note: note,
142+
customer_note: customerNote
143+
});
144+
}
145+
146+
/**
147+
* Clean up all created orders
148+
*/
149+
async cleanup(): Promise<void> {
150+
for (const orderId of this.createdOrderIds) {
151+
try {
152+
await this.apiClient.wooCommerce.delete(`orders/${orderId}`, { force: true });
153+
console.log(`✅ Cleaned up order #${orderId}`);
154+
} catch (error) {
155+
console.error(`Failed to delete order ${orderId}:`, error.message);
156+
}
157+
}
158+
this.createdOrderIds = [];
159+
}
160+
161+
/**
162+
* Get the list of created order IDs (for manual cleanup if needed)
163+
*/
164+
getCreatedOrderIds(): number[] {
165+
return [...this.createdOrderIds];
166+
}
167+
168+
/**
169+
* Build the pay-order URL for a given order
170+
*/
171+
static buildPayOrderUrl(orderId: number, orderKey: string): string {
172+
return `/checkout/order-pay/${orderId}/?pay_for_order=true&key=${orderKey}`;
173+
}
174+
175+
private getPaymentMethodTitle(method: string): string {
176+
const titles: Record<string, string> = {
177+
'monei': 'MONEI',
178+
'monei-hosted': 'MONEI Hosted',
179+
'monei_paypal': 'PayPal via MONEI',
180+
'monei_bizum': 'Bizum via MONEI',
181+
'monei_multibanco': 'Multibanco via MONEI',
182+
'monei_mbway': 'MBWay via MONEI'
183+
};
184+
return titles[method] || method;
185+
}
186+
187+
private getShippingMethodTitle(method: string): string {
188+
const titles: Record<string, string> = {
189+
'flat_rate': 'Flat Rate',
190+
'free_shipping': 'Free Shipping',
191+
'local_pickup': 'Local Pickup'
192+
};
193+
return titles[method] || method;
194+
}
195+
}

tests/pages/pay-for-order.ts

Lines changed: 81 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export class PayForOrderPage {
1010
orderTotalSelector: string;
1111
paymentMethodsSelector: string;
1212
placeOrderButtonSelector: string;
13+
savedPaymentMethodsSelector: string;
14+
errorMessageSelector: string;
1315

1416
// Credit Card specific locators
1517
cardholderNameInputSelector: string;
@@ -27,90 +29,115 @@ export class PayForOrderPage {
2729
this.orderTotalSelector = '.wc-block-components-totals-footer-item .wc-block-components-totals-item__value';
2830
this.paymentMethodsSelector = '.wc-block-components-radio-control__input';
2931
this.placeOrderButtonSelector = '.wc-block-components-checkout-place-order-button';
32+
this.savedPaymentMethodsSelector = '.wc-block-saved-payment-method-options';
33+
this.errorMessageSelector = '.wc-block-components-notice-banner--error';
3034
} else {
3135
this.orderTotalSelector = '.order-total .amount';
3236
this.paymentMethodsSelector = '.wc_payment_method input[name="payment_method"]';
3337
this.placeOrderButtonSelector = '#place_order';
38+
this.savedPaymentMethodsSelector = '.wc-saved-payment-methods';
39+
this.errorMessageSelector = '.woocommerce-error';
3440
}
3541

3642
this.cardholderNameInputSelector = '#monei-card-holder-name';
3743
this.cardInputContainerSelector = '#monei-card-element';
3844
this.cardErrorContainerSelector = '#monei-card-errors';
3945
}
4046

41-
async navigateToPayForOrderPage(orderId: string) {
42-
await this.page.goto(`/checkout/order-pay/${orderId}/?pay_for_order=true&key=wc_order_XXXXXXXXXXXXX`);
43-
await this.waitForPageLoad();
44-
}
45-
46-
async waitForPageLoad() {
47-
if (this.checkoutType.isBlockCheckout) {
48-
await this.page.waitForSelector('.wc-block-checkout__main');
49-
} else {
50-
await this.page.waitForSelector('#order_review');
51-
}
47+
async navigateToPayForOrderPage(orderId: string, orderKey: string) {
48+
const url = `/checkout/order-pay/${orderId}/?pay_for_order=true&key=${orderKey}`;
49+
await this.page.goto(url);
50+
await this.page.waitForLoadState('networkidle');
5251
}
5352

5453
async getOrderTotal(): Promise<string> {
55-
const totalElement = await this.page.locator(this.orderTotalSelector);
56-
return totalElement.innerText();
54+
await this.page.waitForSelector(this.orderTotalSelector);
55+
const totalElement = await this.page.locator(this.orderTotalSelector).first();
56+
return await totalElement.innerText();
5757
}
5858

5959
async selectPaymentMethod(paymentMethod: PaymentMethod) {
60-
await this.page.click(`${this.paymentMethodsSelector}[value="${paymentMethod.id}"]`);
61-
await this.page.waitForSelector(`#payment_method_${paymentMethod.id}`);
60+
const selector = paymentMethod.selector.classic;
61+
62+
await this.page.waitForSelector(selector);
63+
await this.page.click(selector);
64+
65+
// Wait for payment method UI to update
66+
await this.page.waitForTimeout(500);
67+
}
68+
69+
async getAvailablePaymentMethods(): Promise<string[]> {
70+
await this.page.waitForSelector(this.paymentMethodsSelector);
71+
72+
const methods: string[] = [];
73+
const inputs = await this.page.$$(this.paymentMethodsSelector);
74+
75+
for (const input of inputs) {
76+
const value = await input.getAttribute('value');
77+
if (value) {
78+
methods.push(value);
79+
}
80+
}
81+
82+
return methods;
6283
}
6384

64-
async fillCreditCardDetails(cardDetails: {
65-
cardNumber: string;
66-
expiryDate: string;
67-
cvc: string;
68-
cardholderName: string;
69-
}) {
70-
await this.page.fill(this.cardholderNameInputSelector, cardDetails.cardholderName);
71-
72-
// Assuming the card details are entered into an iframe
73-
const frameHandle = await this.page.waitForSelector(`${this.cardInputContainerSelector} iframe`);
74-
const frame = await frameHandle.contentFrame();
75-
76-
await frame?.fill('[name="cardnumber"]', cardDetails.cardNumber);
77-
await frame?.fill('[name="exp-date"]', cardDetails.expiryDate);
78-
await frame?.fill('[name="cvc"]', cardDetails.cvc);
85+
async fillCardholderName(name: string) {
86+
await this.page.waitForSelector(this.cardholderNameInputSelector);
87+
await this.page.fill(this.cardholderNameInputSelector, name);
7988
}
8089

81-
async placeOrder() {
90+
async clickPlaceOrder() {
91+
await this.page.waitForSelector(this.placeOrderButtonSelector);
92+
93+
// Ensure button is enabled
94+
await this.page.waitForFunction(
95+
selector => {
96+
const button = document.querySelector(selector);
97+
return button && !(button as HTMLButtonElement).disabled;
98+
},
99+
this.placeOrderButtonSelector,
100+
{ timeout: 10000 }
101+
);
102+
82103
await this.page.click(this.placeOrderButtonSelector);
83-
await this.page.waitForNavigation({ waitUntil: 'networkidle' });
84104
}
85105

86-
async verifyOrderDetails(expectedTotal: string) {
87-
const actualTotal = await this.getOrderTotal();
88-
expect(actualTotal).toBe(expectedTotal);
106+
async hasSavedPaymentMethods(): Promise<boolean> {
107+
try {
108+
await this.page.waitForSelector(this.savedPaymentMethodsSelector, { timeout: 5000 });
109+
return true;
110+
} catch {
111+
return false;
112+
}
89113
}
90114

91-
async verifyPaymentMethodVisibility(paymentMethod: PaymentMethod, shouldBeVisible: boolean) {
92-
const selector = this.checkoutType.isBlockCheckout
93-
? `.wc-block-components-radio-control__input[value="${paymentMethod.id}"]`
94-
: `input[name="payment_method"][value="${paymentMethod.id}"]`;
115+
async selectSavedPaymentMethod(index: number = 0) {
116+
const savedMethodSelector = this.checkoutType.isBlockCheckout ?
117+
`.wc-block-saved-payment-method-options__option:nth-child(${index + 1}) input` :
118+
`.wc-saved-payment-methods input[type="radio"]:nth-child(${index + 1})`;
95119

96-
if (shouldBeVisible) {
97-
await expect(this.page.locator(selector)).toBeVisible();
98-
} else {
99-
await expect(this.page.locator(selector)).toBeHidden();
100-
}
120+
await this.page.click(savedMethodSelector);
101121
}
102122

103-
async getErrorMessage(): Promise<string | null> {
104-
const errorSelector = this.checkoutType.isBlockCheckout
105-
? '.wc-block-components-notice-banner__content'
106-
: '.woocommerce-error';
123+
async waitForPaymentProcessing() {
124+
// Wait for either success redirect or error message
125+
await Promise.race([
126+
this.page.waitForSelector('.woocommerce-order-received', { timeout: 30000 }),
127+
this.page.waitForSelector(this.errorMessageSelector, { timeout: 30000 })
128+
]);
129+
}
107130

108-
const errorElement = await this.page.locator(errorSelector);
109-
return errorElement.isVisible() ? errorElement.innerText() : null;
131+
async getErrorMessage(): Promise<string | null> {
132+
const errorElement = await this.page.locator(this.errorMessageSelector);
133+
if (await errorElement.isVisible()) {
134+
return await errorElement.innerText();
135+
}
136+
return null;
110137
}
111138

112-
async waitForSuccessfulPayment() {
113-
// Wait for the success message or redirection to the order received page
114-
await this.page.waitForSelector('.woocommerce-order-received', { timeout: 30000 });
139+
async isOrderDetailsVisible(): Promise<boolean> {
140+
return await this.page.isVisible('.woocommerce-order-pay') ||
141+
await this.page.isVisible('.wc-block-checkout__order-pay');
115142
}
116-
}
143+
}

tests/setup/wordpress-api-client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ interface WordPressError {
1818
export class WordPressApiClient {
1919
private baseUrl: string;
2020
private wpAuth: string;
21-
private wooCommerce: any;
21+
wooCommerce: any;
2222

2323
constructor() {
2424
this.baseUrl = process.env.TESTSITE_URL || 'http://localhost:8080';

0 commit comments

Comments
 (0)