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>
233 lines
6.8 KiB
TypeScript
233 lines
6.8 KiB
TypeScript
import { Page, Locator, expect } from '@playwright/test';
|
|
import { BasePage } from './BasePage';
|
|
|
|
/**
|
|
* Cart Page Object Model
|
|
* Handles interactions with the shopping cart
|
|
*
|
|
* Generated by: qe-queen-coordinator
|
|
*/
|
|
export class CartPage extends BasePage {
|
|
// Cart state
|
|
readonly emptyCartMessage: Locator;
|
|
readonly cartTable: Locator;
|
|
readonly cartItems: Locator;
|
|
|
|
// Cart item details
|
|
readonly itemTitles: Locator;
|
|
readonly itemPrices: Locator;
|
|
readonly itemQuantities: Locator;
|
|
readonly itemTotals: Locator;
|
|
readonly removeButtons: Locator;
|
|
|
|
// Cart totals
|
|
readonly subtotal: Locator;
|
|
readonly discountRow: Locator;
|
|
readonly shippingRow: Locator;
|
|
readonly total: Locator;
|
|
|
|
// Actions
|
|
readonly updateCartButton: Locator;
|
|
readonly checkoutButton: Locator;
|
|
readonly continueShoppingLink: Locator;
|
|
|
|
// Promo code
|
|
readonly promoCodeInput: Locator;
|
|
readonly applyPromoButton: Locator;
|
|
readonly promoError: Locator;
|
|
readonly promoSuccess: Locator;
|
|
|
|
// Notes
|
|
readonly cartNotes: Locator;
|
|
|
|
constructor(page: Page) {
|
|
super(page);
|
|
|
|
// Cart state selectors
|
|
this.emptyCartMessage = page.locator('.cart-empty, [data-cart-empty], :text("cart is empty")');
|
|
this.cartTable = page.locator('.cart-table, table, [data-cart-items]');
|
|
this.cartItems = page.locator('.cart-item, tr[data-line-item], .line-item');
|
|
|
|
// Item details
|
|
this.itemTitles = page.locator('.cart-item__title, .line-item__title, [data-item-title]');
|
|
this.itemPrices = page.locator('.cart-item__price, .line-item__price, [data-item-price]');
|
|
this.itemQuantities = page.locator('.cart-item__quantity input, [name="updates[]"], .quantity-input');
|
|
this.itemTotals = page.locator('.cart-item__total, .line-item__total');
|
|
this.removeButtons = page.locator('.cart-item__remove, [data-remove], a:has-text("Remove")');
|
|
|
|
// Cart totals
|
|
this.subtotal = page.locator('.cart-subtotal, [data-subtotal], :text("Subtotal")');
|
|
this.discountRow = page.locator('.cart-discount, [data-discount]');
|
|
this.shippingRow = page.locator('.cart-shipping, [data-shipping]');
|
|
this.total = page.locator('.cart-total, [data-total]');
|
|
|
|
// Actions
|
|
this.updateCartButton = page.locator('[name="update"], button:has-text("Update"), .update-cart');
|
|
this.checkoutButton = page.locator('[name="checkout"], button:has-text("Check"), .checkout-button, a[href*="checkout"]');
|
|
this.continueShoppingLink = page.locator('a:has-text("Continue"), [data-continue-shopping]');
|
|
|
|
// Promo code
|
|
this.promoCodeInput = page.locator('[name="discount"], #discount-code, .promo-input');
|
|
this.applyPromoButton = page.locator('button:has-text("Apply"), .apply-discount');
|
|
this.promoError = page.locator('.discount-error, [data-discount-error]');
|
|
this.promoSuccess = page.locator('.discount-success, [data-discount-success]');
|
|
|
|
// Cart notes
|
|
this.cartNotes = page.locator('[name="note"], .cart-notes, textarea');
|
|
}
|
|
|
|
/**
|
|
* Navigate to cart page
|
|
*/
|
|
async navigate() {
|
|
await this.goto('/cart');
|
|
await this.waitForPageLoad();
|
|
}
|
|
|
|
/**
|
|
* Check if cart is empty
|
|
*/
|
|
async isCartEmpty(): Promise<boolean> {
|
|
return await this.emptyCartMessage.isVisible().catch(() => false);
|
|
}
|
|
|
|
/**
|
|
* Get number of items in cart
|
|
*/
|
|
async getItemCount(): Promise<number> {
|
|
if (await this.isCartEmpty()) return 0;
|
|
return await this.cartItems.count();
|
|
}
|
|
|
|
/**
|
|
* Get all item names in cart
|
|
*/
|
|
async getItemNames(): Promise<string[]> {
|
|
if (await this.isCartEmpty()) return [];
|
|
const names = await this.itemTitles.allTextContents();
|
|
return names.map(n => n.trim());
|
|
}
|
|
|
|
/**
|
|
* Get subtotal amount
|
|
*/
|
|
async getSubtotal(): Promise<string> {
|
|
const subtotalText = await this.subtotal.textContent();
|
|
const match = subtotalText?.match(/[\d,.]+/);
|
|
return match ? match[0] : '0';
|
|
}
|
|
|
|
/**
|
|
* Update item quantity by index
|
|
*/
|
|
async updateQuantity(itemIndex: number, newQuantity: number) {
|
|
const quantityInput = this.itemQuantities.nth(itemIndex);
|
|
await quantityInput.fill(newQuantity.toString());
|
|
|
|
if (await this.updateCartButton.isVisible()) {
|
|
await this.updateCartButton.click();
|
|
await this.waitForPageLoad();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove item by index
|
|
*/
|
|
async removeItem(itemIndex: number) {
|
|
const removeButton = this.removeButtons.nth(itemIndex);
|
|
await removeButton.click();
|
|
await this.waitForPageLoad();
|
|
}
|
|
|
|
/**
|
|
* Remove item by name
|
|
*/
|
|
async removeItemByName(itemName: string) {
|
|
const item = this.cartItems.filter({ hasText: itemName }).first();
|
|
const removeButton = item.locator('.cart-item__remove, [data-remove], a:has-text("Remove")');
|
|
await removeButton.click();
|
|
await this.waitForPageLoad();
|
|
}
|
|
|
|
/**
|
|
* Apply promo code
|
|
*/
|
|
async applyPromoCode(code: string) {
|
|
if (await this.promoCodeInput.isVisible()) {
|
|
await this.promoCodeInput.fill(code);
|
|
await this.applyPromoButton.click();
|
|
await this.page.waitForTimeout(1000);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if promo code was accepted
|
|
*/
|
|
async isPromoCodeAccepted(): Promise<boolean> {
|
|
return await this.promoSuccess.isVisible().catch(() => false);
|
|
}
|
|
|
|
/**
|
|
* Check if promo code has error
|
|
*/
|
|
async hasPromoCodeError(): Promise<boolean> {
|
|
return await this.promoError.isVisible().catch(() => false);
|
|
}
|
|
|
|
/**
|
|
* Proceed to checkout
|
|
*/
|
|
async proceedToCheckout() {
|
|
await this.checkoutButton.click();
|
|
await this.page.waitForURL('**/checkout/**', { timeout: 15000 });
|
|
}
|
|
|
|
/**
|
|
* Continue shopping
|
|
*/
|
|
async continueShopping() {
|
|
await this.continueShoppingLink.click();
|
|
await this.page.waitForURL('**/collections/**');
|
|
}
|
|
|
|
/**
|
|
* Add cart notes
|
|
*/
|
|
async addCartNotes(notes: string) {
|
|
if (await this.cartNotes.isVisible()) {
|
|
await this.cartNotes.fill(notes);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verify cart page loaded
|
|
*/
|
|
async verifyCartPageLoaded() {
|
|
await expect(this.page).toHaveURL(/.*\/cart.*/);
|
|
// Either empty message or cart items should be visible
|
|
const isEmpty = await this.isCartEmpty();
|
|
if (!isEmpty) {
|
|
await expect(this.cartTable.or(this.cartItems.first())).toBeVisible();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear cart (remove all items)
|
|
*/
|
|
async clearCart() {
|
|
while (!(await this.isCartEmpty())) {
|
|
await this.removeItem(0);
|
|
await this.page.waitForTimeout(500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get item quantity by index
|
|
*/
|
|
async getItemQuantity(itemIndex: number): Promise<number> {
|
|
const quantityInput = this.itemQuantities.nth(itemIndex);
|
|
const value = await quantityInput.inputValue();
|
|
return parseInt(value, 10) || 0;
|
|
}
|
|
}
|