255 lines
9.0 KiB
TypeScript
255 lines
9.0 KiB
TypeScript
/**
|
|
* Integration tests for Playwright adapter step 17 checkout flow with confirmation callback.
|
|
* Tests the pause-for-confirmation mechanism before clicking checkout button.
|
|
*/
|
|
|
|
import { describe, it, expect, beforeAll, afterAll, beforeEach, vi } from 'vitest';
|
|
import { FixtureServer } from '../../../packages/infrastructure/adapters/automation/FixtureServer';
|
|
import { PlaywrightAutomationAdapter } from '../../../packages/infrastructure/adapters/automation/PlaywrightAutomationAdapter';
|
|
import { StepId } from '../../../packages/domain/value-objects/StepId';
|
|
import { CheckoutConfirmation } from '../../../packages/domain/value-objects/CheckoutConfirmation';
|
|
import { CheckoutPrice } from '../../../packages/domain/value-objects/CheckoutPrice';
|
|
import { CheckoutState } from '../../../packages/domain/value-objects/CheckoutState';
|
|
|
|
describe('Playwright Step 17 Checkout Flow with Confirmation', () => {
|
|
let server: FixtureServer;
|
|
let adapter: PlaywrightAutomationAdapter;
|
|
let baseUrl: string;
|
|
|
|
beforeAll(async () => {
|
|
server = new FixtureServer();
|
|
const serverInfo = await server.start();
|
|
baseUrl = serverInfo.url;
|
|
|
|
adapter = new PlaywrightAutomationAdapter({
|
|
headless: true,
|
|
timeout: 5000,
|
|
baseUrl,
|
|
mode: 'mock',
|
|
});
|
|
|
|
const connectResult = await adapter.connect();
|
|
expect(connectResult.success).toBe(true);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await adapter.disconnect();
|
|
await server.stop();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
await adapter.navigateToPage(server.getFixtureUrl(17));
|
|
// Clear any previous callback
|
|
adapter.setCheckoutConfirmationCallback(undefined);
|
|
});
|
|
|
|
describe('Checkout Confirmation Callback Injection', () => {
|
|
it('should accept and store checkout confirmation callback', () => {
|
|
const mockCallback = vi.fn();
|
|
|
|
// Should not throw
|
|
expect(() => {
|
|
adapter.setCheckoutConfirmationCallback(mockCallback);
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it('should allow clearing the callback by passing undefined', () => {
|
|
const mockCallback = vi.fn();
|
|
adapter.setCheckoutConfirmationCallback(mockCallback);
|
|
|
|
// Should not throw when clearing
|
|
expect(() => {
|
|
adapter.setCheckoutConfirmationCallback(undefined);
|
|
}).not.toThrow();
|
|
});
|
|
});
|
|
|
|
describe('Step 17 Execution with Confirmation Flow', () => {
|
|
it('should extract checkout info before requesting confirmation', async () => {
|
|
const mockCallback = vi.fn().mockResolvedValue(
|
|
CheckoutConfirmation.create('confirmed')
|
|
);
|
|
|
|
adapter.setCheckoutConfirmationCallback(mockCallback);
|
|
|
|
const stepId = StepId.create(17);
|
|
const result = await adapter.executeStep(stepId, {});
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(mockCallback).toHaveBeenCalledTimes(1);
|
|
|
|
// Verify callback was called with price and state
|
|
const callArgs = mockCallback.mock.calls[0];
|
|
expect(callArgs).toHaveLength(2);
|
|
|
|
const [price, state] = callArgs;
|
|
expect(price).toBeInstanceOf(CheckoutPrice);
|
|
expect(state).toBeInstanceOf(CheckoutState);
|
|
});
|
|
|
|
it('should show "Awaiting confirmation..." overlay before callback', async () => {
|
|
const mockCallback = vi.fn().mockImplementation(async () => {
|
|
// Check overlay message during callback execution
|
|
const page = adapter.getPage()!;
|
|
const overlayText = await page.locator('#gridpilot-action').textContent();
|
|
expect(overlayText).toContain('Awaiting confirmation');
|
|
|
|
return CheckoutConfirmation.create('confirmed');
|
|
});
|
|
|
|
adapter.setCheckoutConfirmationCallback(mockCallback);
|
|
|
|
const stepId = StepId.create(17);
|
|
await adapter.executeStep(stepId, {});
|
|
|
|
expect(mockCallback).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should click checkout button only if confirmation is "confirmed"', async () => {
|
|
const mockCallback = vi.fn().mockResolvedValue(
|
|
CheckoutConfirmation.create('confirmed')
|
|
);
|
|
|
|
adapter.setCheckoutConfirmationCallback(mockCallback);
|
|
|
|
const stepId = StepId.create(17);
|
|
const result = await adapter.executeStep(stepId, {});
|
|
|
|
expect(result.success).toBe(true);
|
|
|
|
// Verify button was clicked by checking if navigation occurred
|
|
const page = adapter.getPage()!;
|
|
const currentUrl = page.url();
|
|
// In mock mode, clicking checkout would navigate to a success page or different step
|
|
expect(currentUrl).toBeDefined();
|
|
});
|
|
|
|
it('should NOT click checkout button if confirmation is "cancelled"', async () => {
|
|
const mockCallback = vi.fn().mockResolvedValue(
|
|
CheckoutConfirmation.create('cancelled')
|
|
);
|
|
|
|
adapter.setCheckoutConfirmationCallback(mockCallback);
|
|
|
|
const stepId = StepId.create(17);
|
|
const result = await adapter.executeStep(stepId, {});
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.error).toContain('cancelled');
|
|
expect(mockCallback).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should NOT click checkout button if confirmation is "timeout"', async () => {
|
|
const mockCallback = vi.fn().mockResolvedValue(
|
|
CheckoutConfirmation.create('timeout')
|
|
);
|
|
|
|
adapter.setCheckoutConfirmationCallback(mockCallback);
|
|
|
|
const stepId = StepId.create(17);
|
|
const result = await adapter.executeStep(stepId, {});
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.error).toContain('timeout');
|
|
expect(mockCallback).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should show success overlay after confirmed checkout', async () => {
|
|
const mockCallback = vi.fn().mockResolvedValue(
|
|
CheckoutConfirmation.create('confirmed')
|
|
);
|
|
|
|
adapter.setCheckoutConfirmationCallback(mockCallback);
|
|
|
|
const stepId = StepId.create(17);
|
|
await adapter.executeStep(stepId, {});
|
|
|
|
// Check for success overlay
|
|
const page = adapter.getPage()!;
|
|
const overlayExists = await page.locator('#gridpilot-overlay').count();
|
|
expect(overlayExists).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should execute step normally if no callback is set', async () => {
|
|
// No callback set - should execute without confirmation
|
|
const stepId = StepId.create(17);
|
|
const result = await adapter.executeStep(stepId, {});
|
|
|
|
// Should succeed without asking for confirmation
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it('should handle callback errors gracefully', async () => {
|
|
const mockCallback = vi.fn().mockRejectedValue(
|
|
new Error('Callback failed')
|
|
);
|
|
|
|
adapter.setCheckoutConfirmationCallback(mockCallback);
|
|
|
|
const stepId = StepId.create(17);
|
|
const result = await adapter.executeStep(stepId, {});
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.error).toBeDefined();
|
|
expect(mockCallback).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should pass correct price from CheckoutPriceExtractor to callback', async () => {
|
|
let capturedPrice: CheckoutPrice | null = null;
|
|
|
|
const mockCallback = vi.fn().mockImplementation(async (price: CheckoutPrice) => {
|
|
capturedPrice = price;
|
|
return CheckoutConfirmation.create('confirmed');
|
|
});
|
|
|
|
adapter.setCheckoutConfirmationCallback(mockCallback);
|
|
|
|
const stepId = StepId.create(17);
|
|
await adapter.executeStep(stepId, {});
|
|
|
|
expect(capturedPrice).not.toBeNull();
|
|
expect(capturedPrice).toBeInstanceOf(CheckoutPrice);
|
|
// The mock fixture should have a price formatted as $X.XX
|
|
expect(capturedPrice!.toDisplayString()).toMatch(/^\$\d+\.\d{2}$/);
|
|
});
|
|
|
|
it('should pass correct state from CheckoutState validation to callback', async () => {
|
|
let capturedState: CheckoutState | null = null;
|
|
|
|
const mockCallback = vi.fn().mockImplementation(
|
|
async (_price: CheckoutPrice, state: CheckoutState) => {
|
|
capturedState = state;
|
|
return CheckoutConfirmation.create('confirmed');
|
|
}
|
|
);
|
|
|
|
adapter.setCheckoutConfirmationCallback(mockCallback);
|
|
|
|
const stepId = StepId.create(17);
|
|
await adapter.executeStep(stepId, {});
|
|
|
|
expect(capturedState).not.toBeNull();
|
|
expect(capturedState).toBeInstanceOf(CheckoutState);
|
|
// State should indicate whether checkout is ready (method, not property)
|
|
expect(typeof capturedState!.isReady()).toBe('boolean');
|
|
});
|
|
});
|
|
|
|
describe('Step 17 with Track State Configuration', () => {
|
|
it('should set track state before requesting confirmation', async () => {
|
|
const mockCallback = vi.fn().mockResolvedValue(
|
|
CheckoutConfirmation.create('confirmed')
|
|
);
|
|
|
|
adapter.setCheckoutConfirmationCallback(mockCallback);
|
|
|
|
const stepId = StepId.create(17);
|
|
const result = await adapter.executeStep(stepId, {
|
|
trackState: 'moderately-low',
|
|
});
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(mockCallback).toHaveBeenCalled();
|
|
});
|
|
});
|
|
}); |