Files
proffesor-for-testing__agen…/tests/e2e/pages/CheckoutPage.ts
Profa 2253d05618 feat: flatten project structure — archive v2, promote v3 to root
Remove the nested v3/ subdirectory by moving all active source code,
tests, configs, and assets directly to the project root. Archive v2/
(dead code, 847MB) to v2-archive.tar.gz.

Key changes:
- Move v3/src/ → src/, v3/tests/ → tests/, v3/packages/ → packages/
- Move v3/tsconfig.json, v3/vitest.config.ts to root
- Merge v3/package.json into root package.json (ESM, scripts, deps, exports)
- Merge v3/scripts/ into scripts/, v3/implementation/ into docs/implementation/
- Update all CI workflows to remove "cd v3 &&" and v3/ path prefixes
- Fix all init installer path resolution (3-level-up → 2-level-up)
- Fix hooks to use "node ./dist/cli/bundle.js" instead of bare "aqe"
- Update settings.json ADR/DDD directories to docs/implementation/
- Fix helper scripts (statusline, adr-compliance, ddd-tracker, guidance-hooks)
- Update 58 agent definitions: aqe/v3/ → aqe/ memory namespaces
- Update 15 guidance shards: v3/src/ → src/ path references
- Fix sync interfaces: remove v3/ and v2/ path references
- Fix test path calculations (process.cwd() + '..' no longer needed)
- Fix flaky perf test threshold (500ms → 1000ms for CI environments)
- Fix complexity-analyzer test to match ADR-051 keyword-based eligibility

Build passes, 17,901 tests pass, CLI and MCP bundles verified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 11:35:41 +00:00

237 lines
7.5 KiB
TypeScript

import { Page, Locator, FrameLocator, expect } from '@playwright/test';
import { BasePage } from './BasePage';
/**
* Checkout Page Object Model
* Handles interactions with Shopify checkout
*
* Note: Shopify checkout is a hosted solution with standardized elements
*
* Generated by: qe-queen-coordinator
*/
export class CheckoutPage extends BasePage {
// Contact information
readonly emailInput: Locator;
readonly phoneInput: Locator;
// Shipping address
readonly firstNameInput: Locator;
readonly lastNameInput: Locator;
readonly addressInput: Locator;
readonly apartmentInput: Locator;
readonly cityInput: Locator;
readonly countrySelector: Locator;
readonly stateSelector: Locator;
readonly zipInput: Locator;
// Shipping method
readonly shippingMethods: Locator;
readonly shippingMethodRadios: Locator;
// Payment
readonly paymentSection: Locator;
readonly cardNumberIframe: FrameLocator;
readonly expiryIframe: FrameLocator;
readonly cvvIframe: FrameLocator;
readonly cardNameInput: Locator;
// Order summary
readonly orderSummary: Locator;
readonly subtotalAmount: Locator;
readonly shippingAmount: Locator;
readonly taxAmount: Locator;
readonly totalAmount: Locator;
// Actions
readonly continueButton: Locator;
readonly payNowButton: Locator;
readonly returnToCartLink: Locator;
// Error messages
readonly errorMessages: Locator;
readonly fieldErrors: Locator;
constructor(page: Page) {
super(page);
// Contact information - Shopify checkout selectors
this.emailInput = page.locator('#email, [name="email"], [data-email]');
this.phoneInput = page.locator('#phone, [name="phone"], [data-phone]');
// Shipping address
this.firstNameInput = page.locator('#first_name, [name="first_name"], [autocomplete="given-name"]');
this.lastNameInput = page.locator('#last_name, [name="last_name"], [autocomplete="family-name"]');
this.addressInput = page.locator('#address1, [name="address1"], [autocomplete="address-line1"]');
this.apartmentInput = page.locator('#address2, [name="address2"], [autocomplete="address-line2"]');
this.cityInput = page.locator('#city, [name="city"], [autocomplete="address-level2"]');
this.countrySelector = page.locator('#country, [name="country"], [autocomplete="country"]');
this.stateSelector = page.locator('#province, [name="province"], [autocomplete="address-level1"]');
this.zipInput = page.locator('#zip, [name="zip"], [autocomplete="postal-code"]');
// Shipping method
this.shippingMethods = page.locator('.shipping-method, [data-shipping-method]');
this.shippingMethodRadios = page.locator('[name="shipping_method"]');
// Payment - typically in iframes
this.paymentSection = page.locator('.payment-section, [data-payment]');
this.cardNumberIframe = page.frameLocator('[name*="card-number"], [data-card-number]');
this.expiryIframe = page.frameLocator('[name*="expiry"], [data-expiry]');
this.cvvIframe = page.frameLocator('[name*="cvv"], [data-cvv]');
this.cardNameInput = page.locator('#name, [name="name"], [autocomplete="cc-name"]');
// Order summary
this.orderSummary = page.locator('.order-summary, [data-order-summary]');
this.subtotalAmount = page.locator('[data-subtotal], .subtotal-amount');
this.shippingAmount = page.locator('[data-shipping], .shipping-amount');
this.taxAmount = page.locator('[data-tax], .tax-amount');
this.totalAmount = page.locator('[data-total], .total-amount, .payment-due');
// Actions
this.continueButton = page.locator('button:has-text("Continue"), [data-continue], #continue_button');
this.payNowButton = page.locator('button:has-text("Pay"), [data-pay], #checkout-pay-button');
this.returnToCartLink = page.locator('a:has-text("Return"), [data-return-to-cart]');
// Errors
this.errorMessages = page.locator('.error-message, [data-error], .notice--error');
this.fieldErrors = page.locator('.field-error, [data-field-error]');
}
/**
* Verify checkout page loaded
*/
async verifyCheckoutLoaded() {
await expect(this.page).toHaveURL(/.*checkout.*/);
// Wait for email input to be visible (first step)
await expect(this.emailInput).toBeVisible({ timeout: 15000 });
}
/**
* Fill contact information
*/
async fillContactInfo(email: string, phone?: string) {
await this.emailInput.fill(email);
if (phone && await this.phoneInput.isVisible()) {
await this.phoneInput.fill(phone);
}
}
/**
* Fill shipping address
*/
async fillShippingAddress(address: {
firstName: string;
lastName: string;
address1: string;
address2?: string;
city: string;
country: string;
state?: string;
zip: string;
}) {
await this.firstNameInput.fill(address.firstName);
await this.lastNameInput.fill(address.lastName);
await this.addressInput.fill(address.address1);
if (address.address2) {
await this.apartmentInput.fill(address.address2);
}
await this.cityInput.fill(address.city);
// Country selector
if (await this.countrySelector.isVisible()) {
await this.countrySelector.selectOption({ label: address.country });
}
// State/Province selector
if (address.state && await this.stateSelector.isVisible()) {
await this.stateSelector.selectOption({ label: address.state });
}
await this.zipInput.fill(address.zip);
}
/**
* Select shipping method by index
*/
async selectShippingMethod(index: number = 0) {
const methods = this.shippingMethodRadios;
if (await methods.count() > 0) {
await methods.nth(index).check();
}
}
/**
* Continue to next step
*/
async continueToNextStep() {
await this.continueButton.click();
await this.page.waitForLoadState('networkidle');
}
/**
* Get total amount
*/
async getTotalAmount(): Promise<string> {
const total = await this.totalAmount.textContent();
return total?.trim() || '';
}
/**
* Check for validation errors
*/
async hasValidationErrors(): Promise<boolean> {
return await this.errorMessages.isVisible().catch(() => false) ||
await this.fieldErrors.isVisible().catch(() => false);
}
/**
* Get error messages
*/
async getErrorMessages(): Promise<string[]> {
const errors = await this.errorMessages.allTextContents();
return errors.map(e => e.trim());
}
/**
* Return to cart
*/
async returnToCart() {
await this.returnToCartLink.click();
await this.page.waitForURL('**/cart');
}
/**
* Complete checkout (test mode only)
* Note: This requires test payment credentials
*/
async completeCheckout(testCard: {
number: string;
expiry: string;
cvv: string;
name: string;
}) {
// Payment details are in iframes - handle carefully
// This is a simplified version; actual implementation depends on Shopify's payment UI
// Fill card name if visible
if (await this.cardNameInput.isVisible()) {
await this.cardNameInput.fill(testCard.name);
}
// Click pay button
await this.payNowButton.click();
// Wait for order confirmation
await this.page.waitForURL('**/thank_you**', { timeout: 30000 });
}
/**
* Fill express checkout email
*/
async fillExpressCheckoutEmail(email: string) {
await this.emailInput.fill(email);
await this.page.keyboard.press('Tab');
}
}