mirror of
https://github.com/proffesor-for-testing/agentic-qe.git
synced 2026-09-19 08:45:47 +08:00
2253d05618
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>
237 lines
7.5 KiB
TypeScript
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');
|
|
}
|
|
}
|