wip
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import { Result } from '../../../shared/result/Result';
|
||||
import { CheckoutPrice } from '../../../domain/value-objects/CheckoutPrice';
|
||||
import { CheckoutState } from '../../../domain/value-objects/CheckoutState';
|
||||
import { CheckoutInfo } from '../../../application/ports/ICheckoutService';
|
||||
|
||||
interface Page {
|
||||
locator(selector: string): Locator;
|
||||
}
|
||||
|
||||
interface Locator {
|
||||
getAttribute(name: string): Promise<string | null>;
|
||||
innerHTML(): Promise<string>;
|
||||
textContent(): Promise<string | null>;
|
||||
}
|
||||
|
||||
export class CheckoutPriceExtractor {
|
||||
private readonly selector = '.wizard-footer a.btn:has(span.label-pill)';
|
||||
|
||||
constructor(private readonly page: Page) {}
|
||||
|
||||
async extractCheckoutInfo(): Promise<Result<CheckoutInfo>> {
|
||||
try {
|
||||
// Prefer the explicit pill element which contains the price
|
||||
const pillLocator = this.page.locator('span.label-pill');
|
||||
const pillText = await pillLocator.first().textContent().catch(() => null);
|
||||
|
||||
let price: CheckoutPrice | null = null;
|
||||
let state = CheckoutState.unknown();
|
||||
let buttonHtml = '';
|
||||
|
||||
if (pillText) {
|
||||
// Parse price if possible
|
||||
try {
|
||||
price = CheckoutPrice.fromString(pillText.trim());
|
||||
} catch {
|
||||
price = null;
|
||||
}
|
||||
|
||||
// Try to find the containing button and its classes/html
|
||||
// Primary: locate button via known selector that contains the pill
|
||||
const buttonLocator = this.page.locator(this.selector).first();
|
||||
let classes = await buttonLocator.getAttribute('class').catch(() => null);
|
||||
let html = await buttonLocator.innerHTML().catch(() => '');
|
||||
|
||||
if (!classes) {
|
||||
// Fallback: find ancestor <a> of the pill (XPath)
|
||||
const ancestorButton = pillLocator.first().locator('xpath=ancestor::a[1]');
|
||||
classes = await ancestorButton.getAttribute('class').catch(() => null);
|
||||
html = await ancestorButton.innerHTML().catch(() => '');
|
||||
}
|
||||
|
||||
if (classes) {
|
||||
state = CheckoutState.fromButtonClasses(classes);
|
||||
buttonHtml = html ?? '';
|
||||
}
|
||||
} else {
|
||||
// No pill found — attempt to read button directly (best-effort)
|
||||
const buttonLocator = this.page.locator(this.selector).first();
|
||||
const classes = await buttonLocator.getAttribute('class').catch(() => null);
|
||||
const html = await buttonLocator.innerHTML().catch(() => '');
|
||||
|
||||
if (classes) {
|
||||
state = CheckoutState.fromButtonClasses(classes);
|
||||
buttonHtml = html ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
// Additional fallback: search the wizard-footer for any price text if pill was not present or parsing failed
|
||||
if (!price) {
|
||||
try {
|
||||
const footerLocator = this.page.locator('.wizard-footer').first();
|
||||
const footerText = await footerLocator.textContent().catch(() => null);
|
||||
if (footerText) {
|
||||
const match = footerText.match(/\$\d+\.\d{2}/);
|
||||
if (match) {
|
||||
try {
|
||||
price = CheckoutPrice.fromString(match[0]);
|
||||
} catch {
|
||||
price = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore footer parse errors
|
||||
}
|
||||
}
|
||||
|
||||
return Result.ok({
|
||||
price,
|
||||
state,
|
||||
buttonHtml
|
||||
});
|
||||
} catch (error) {
|
||||
// On any unexpected error, return an "unknown" result (do not throw)
|
||||
return Result.ok({
|
||||
price: null,
|
||||
state: CheckoutState.unknown(),
|
||||
buttonHtml: ''
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user