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>
170 lines
5.0 KiB
TypeScript
170 lines
5.0 KiB
TypeScript
import { Page, Locator, expect } from '@playwright/test';
|
|
import { BasePage } from './BasePage';
|
|
|
|
/**
|
|
* Catalog/Collection Page Object Model
|
|
* Handles interactions with the product catalog
|
|
*
|
|
* Generated by: qe-queen-coordinator
|
|
*/
|
|
export class CatalogPage extends BasePage {
|
|
// Page elements
|
|
readonly pageTitle: Locator;
|
|
readonly productGrid: Locator;
|
|
readonly productCards: Locator;
|
|
readonly productCount: Locator;
|
|
|
|
// Product card elements
|
|
readonly productImages: Locator;
|
|
readonly productTitles: Locator;
|
|
readonly productPrices: Locator;
|
|
readonly soldOutBadges: Locator;
|
|
|
|
// Sorting/Filtering (if available)
|
|
readonly sortDropdown: Locator;
|
|
readonly filterOptions: Locator;
|
|
|
|
// Pagination
|
|
readonly pagination: Locator;
|
|
readonly nextPageButton: Locator;
|
|
readonly prevPageButton: Locator;
|
|
|
|
constructor(page: Page) {
|
|
super(page);
|
|
|
|
// Page elements
|
|
this.pageTitle = page.locator('h1, .collection-title, .page-title');
|
|
this.productGrid = page.locator('.collection-products, .product-grid, [data-collection]');
|
|
this.productCards = page.locator('.product-card, .grid-item, .product-item');
|
|
this.productCount = page.locator('.collection-count, .product-count');
|
|
|
|
// Product card details
|
|
this.productImages = page.locator('.product-card img, .product-image');
|
|
this.productTitles = page.locator('.product-card h3, .product-title, .product-card__title');
|
|
this.productPrices = page.locator('.product-price, .price, [data-price]');
|
|
this.soldOutBadges = page.locator('.sold-out, .badge--sold-out, [data-sold-out]');
|
|
|
|
// Sorting/Filtering
|
|
this.sortDropdown = page.locator('[data-sort], .sort-by, select[name="sort_by"]');
|
|
this.filterOptions = page.locator('.filter-options, [data-filter]');
|
|
|
|
// Pagination
|
|
this.pagination = page.locator('.pagination, [data-pagination]');
|
|
this.nextPageButton = page.locator('.pagination-next, [rel="next"]');
|
|
this.prevPageButton = page.locator('.pagination-prev, [rel="prev"]');
|
|
}
|
|
|
|
/**
|
|
* Navigate to catalog page
|
|
*/
|
|
async navigate() {
|
|
await this.goto('/collections/all');
|
|
await this.waitForPageLoad();
|
|
}
|
|
|
|
/**
|
|
* Get total product count on page
|
|
*/
|
|
async getProductCount(): Promise<number> {
|
|
return await this.productCards.count();
|
|
}
|
|
|
|
/**
|
|
* Get all product names
|
|
*/
|
|
async getAllProductNames(): Promise<string[]> {
|
|
const titles = await this.productTitles.allTextContents();
|
|
return titles.map(t => t.trim());
|
|
}
|
|
|
|
/**
|
|
* Get all product prices
|
|
*/
|
|
async getAllProductPrices(): Promise<string[]> {
|
|
const prices = await this.productPrices.allTextContents();
|
|
return prices.map(p => p.trim());
|
|
}
|
|
|
|
/**
|
|
* Click on product by name
|
|
*/
|
|
async clickProduct(productName: string) {
|
|
const product = this.productCards.filter({ hasText: productName }).first();
|
|
await product.click();
|
|
await this.page.waitForURL('**/products/**');
|
|
}
|
|
|
|
/**
|
|
* Click on product by index
|
|
*/
|
|
async clickProductByIndex(index: number) {
|
|
await this.productCards.nth(index).click();
|
|
await this.page.waitForURL('**/products/**');
|
|
}
|
|
|
|
/**
|
|
* Check if product is sold out
|
|
*/
|
|
async isProductSoldOut(productName: string): Promise<boolean> {
|
|
const product = this.productCards.filter({ hasText: productName }).first();
|
|
const soldOutBadge = product.locator('.sold-out, .badge--sold-out, [data-sold-out]');
|
|
return await soldOutBadge.isVisible().catch(() => false);
|
|
}
|
|
|
|
/**
|
|
* Get list of sold out products
|
|
*/
|
|
async getSoldOutProducts(): Promise<string[]> {
|
|
const soldOutProducts: string[] = [];
|
|
const count = await this.productCards.count();
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
const card = this.productCards.nth(i);
|
|
const soldOutBadge = card.locator('.sold-out, .badge--sold-out, [data-sold-out]');
|
|
|
|
if (await soldOutBadge.isVisible().catch(() => false)) {
|
|
const title = await card.locator('.product-title, h3').textContent();
|
|
if (title) soldOutProducts.push(title.trim());
|
|
}
|
|
}
|
|
|
|
return soldOutProducts;
|
|
}
|
|
|
|
/**
|
|
* Verify catalog page loaded
|
|
*/
|
|
async verifyCatalogLoaded() {
|
|
await expect(this.productGrid.or(this.productCards.first())).toBeVisible();
|
|
const count = await this.getProductCount();
|
|
expect(count).toBeGreaterThan(0);
|
|
}
|
|
|
|
/**
|
|
* Sort products (if sorting available)
|
|
*/
|
|
async sortBy(option: 'price-asc' | 'price-desc' | 'title-asc' | 'title-desc' | 'best-selling') {
|
|
if (await this.sortDropdown.isVisible()) {
|
|
await this.sortDropdown.selectOption(option);
|
|
await this.waitForPageLoad();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if pagination exists
|
|
*/
|
|
async hasPagination(): Promise<boolean> {
|
|
return await this.pagination.isVisible().catch(() => false);
|
|
}
|
|
|
|
/**
|
|
* Go to next page
|
|
*/
|
|
async goToNextPage() {
|
|
if (await this.nextPageButton.isVisible()) {
|
|
await this.nextPageButton.click();
|
|
await this.waitForPageLoad();
|
|
}
|
|
}
|
|
}
|