E2e testing of pricing (#42)

* a simp0le scaffold

* feature: simple npm setup

* feature: testing setup and dummy scenarios

* chore: dumping kafak just via backend

* chore: dcleaning gitignore

* features: boilerplate fixtures and stuff

* test: extra tests

* chore: update the test suite to be callable via makefile

* chore: cleaning

* chore: updating interactions setup

* small cleaning

* chore: cleaning shitty code
This commit is contained in:
Daniel Alves Rösel
2026-01-12 11:02:18 +01:00
committed by GitHub
parent f2271e368e
commit 221e71a503
12 changed files with 713 additions and 13 deletions

View File

@@ -0,0 +1,156 @@
import { test, expect } from '../fixtures';
import {
createFreshSession,
viewProductViaFlow,
rapidViewProductViaFlow,
humanLikeViewProduct,
getPriceFromDOM,
verifySessionConsistency,
addToCart,
} from '../helpers/interactions';
import { getSessionEvents } from '../helpers/kafka';
test.describe('SessionAwarePricer E2E', () => {
const STORE_TYPE = 'hotel';
test('baseline: human-like behavior maintains base price', async ({ page, backendUrl }) => {
const sessionId = await createFreshSession(page, STORE_TYPE);
const productId1 = await humanLikeViewProduct(page, STORE_TYPE);
const baselinePrice = await getPriceFromDOM(page);
expect(await verifySessionConsistency(page, sessionId)).toBeTruthy();
await page.waitForTimeout(1500);
const productId2 = await humanLikeViewProduct(page, STORE_TYPE);
const secondPrice = await getPriceFromDOM(page);
expect(await verifySessionConsistency(page, sessionId)).toBeTruthy();
expect(Math.abs(secondPrice - baselinePrice) / baselinePrice).toBeLessThan(0.1);
});
test('agent detection: rapid robot-like behavior increases price', async ({ page, backendUrl }) => {
const sessionId = await createFreshSession(page, STORE_TYPE);
const productId = await viewProductViaFlow(page, STORE_TYPE);
const baselinePrice = await getPriceFromDOM(page);
await page.waitForTimeout(500);
await rapidViewProductViaFlow(page, 8, 100, STORE_TYPE);
expect(await verifySessionConsistency(page, sessionId)).toBeTruthy();
await page.waitForTimeout(2500);
const events = await getSessionEvents(backendUrl, sessionId);
expect(events.length).toBeGreaterThanOrEqual(8);
await page.goto(`/products/${productId}`);
await page.waitForLoadState('networkidle');
const agentPrice = await getPriceFromDOM(page);
expect(agentPrice).toBeGreaterThan(baselinePrice);
expect((agentPrice - baselinePrice) / baselinePrice).toBeGreaterThan(0.01);
});
test('velocity threshold: high event rate triggers detection', async ({ page, backendUrl }) => {
const sessionId = await createFreshSession(page, STORE_TYPE);
const productId = await viewProductViaFlow(page, STORE_TYPE);
const baselinePrice = await getPriceFromDOM(page);
const startTime = Date.now();
await rapidViewProductViaFlow(page, 10, 80, STORE_TYPE);
const duration = (Date.now() - startTime) / 1000;
const eventsPerSec = 10 / duration;
expect(eventsPerSec).toBeGreaterThan(2.0);
await page.waitForTimeout(2000);
await page.goto(`/products/${productId}`);
await page.waitForLoadState('networkidle');
const agentPrice = await getPriceFromDOM(page);
expect(agentPrice).toBeGreaterThan(baselinePrice);
expect(await verifySessionConsistency(page, sessionId)).toBeTruthy();
});
test('cart ratio: high cart/view ratio signals intent', async ({ page, backendUrl }) => {
const sessionId = await createFreshSession(page, STORE_TYPE);
const productId = await viewProductViaFlow(page, STORE_TYPE);
const baselinePrice = await getPriceFromDOM(page);
await page.waitForTimeout(500);
await addToCart(page);
await page.waitForTimeout(2000);
await page.goto(`/products/${productId}`);
await page.waitForLoadState('networkidle');
const cartPrice = await getPriceFromDOM(page);
expect(cartPrice).toBeGreaterThanOrEqual(baselinePrice);
expect(await verifySessionConsistency(page, sessionId)).toBeTruthy();
});
test('mixed behavior: occasional fast actions tolerated', async ({ page, backendUrl }) => {
const sessionId = await createFreshSession(page, STORE_TYPE);
const productId1 = await humanLikeViewProduct(page, STORE_TYPE);
const baselinePrice = await getPriceFromDOM(page);
await page.waitForTimeout(1200);
await rapidViewProductViaFlow(page, 2, 150, STORE_TYPE);
await page.waitForTimeout(1500);
await humanLikeViewProduct(page, STORE_TYPE);
const finalPrice = await getPriceFromDOM(page);
expect(Math.abs(finalPrice - baselinePrice) / baselinePrice).toBeLessThan(0.3);
expect(await verifySessionConsistency(page, sessionId)).toBeTruthy();
});
test('session isolation: agent behavior in one session does not affect others', async ({
page,
context,
backendUrl,
}) => {
const sessionIdA = await createFreshSession(page, STORE_TYPE);
const productId = await viewProductViaFlow(page, STORE_TYPE);
const basePrice = await getPriceFromDOM(page);
await rapidViewProductViaFlow(page, 10, 100, STORE_TYPE);
await page.waitForTimeout(2000);
await page.goto(`/products/${productId}`);
await page.waitForLoadState('networkidle');
const agentPrice = await getPriceFromDOM(page);
expect(agentPrice).toBeGreaterThan(basePrice * 0.99);
const page2 = await context.newPage();
const sessionIdB = await createFreshSession(page2, STORE_TYPE);
await page2.goto(`/products/${productId}`);
await page2.waitForLoadState('networkidle');
const cleanPrice = await getPriceFromDOM(page2);
expect(Math.abs(cleanPrice - basePrice) / basePrice).toBeLessThan(0.1);
expect(sessionIdA).not.toBe(sessionIdB);
});
test('session persistence: session ID maintained across views', async ({ page }) => {
const sessionId = await createFreshSession(page, STORE_TYPE);
await viewProductViaFlow(page, STORE_TYPE);
expect(await verifySessionConsistency(page, sessionId)).toBeTruthy();
await viewProductViaFlow(page, STORE_TYPE);
expect(await verifySessionConsistency(page, sessionId)).toBeTruthy();
await viewProductViaFlow(page, STORE_TYPE);
expect(await verifySessionConsistency(page, sessionId)).toBeTruthy();
});
});

View File

@@ -0,0 +1,111 @@
import { test, expect } from '../fixtures';
import {
createFreshSession,
viewProductViaFlow,
rapidViewProductViaFlow,
getPriceFromDOM,
verifySessionConsistency,
} from '../helpers/interactions';
import { waitForInteractionEvent, countProductViews } from '../helpers/kafka';
test.describe('SimpleSurgePricer E2E', () => {
const STORE_TYPE = 'hotel';
test('baseline: initial price equals base price', async ({ page, backendUrl }) => {
const sessionId = await createFreshSession(page, STORE_TYPE);
const productId = await viewProductViaFlow(page, STORE_TYPE);
const price = await getPriceFromDOM(page);
expect(price).toBeGreaterThan(0);
expect(await verifySessionConsistency(page, sessionId)).toBeTruthy();
});
test('surge: rapid views trigger price increase', async ({ page, backendUrl }) => {
const sessionId = await createFreshSession(page, STORE_TYPE);
const productId = await viewProductViaFlow(page, STORE_TYPE);
const baselinePrice = await getPriceFromDOM(page);
await rapidViewProductViaFlow(page, 5, 200, STORE_TYPE);
await page.waitForTimeout(2000);
const evt = await waitForInteractionEvent(backendUrl, sessionId, 'view_item_page');
expect(evt).not.toBeNull();
const viewCount = await countProductViews(backendUrl, productId);
expect(viewCount).toBeGreaterThanOrEqual(5);
await page.goto(`/products/${productId}`);
await page.waitForLoadState('networkidle');
const surgedPrice = await getPriceFromDOM(page);
expect(surgedPrice).toBeGreaterThan(baselinePrice);
expect((surgedPrice - baselinePrice) / baselinePrice).toBeGreaterThan(0.01);
expect(await verifySessionConsistency(page, sessionId)).toBeTruthy();
});
test('threshold: price unchanged below threshold', async ({ page, backendUrl }) => {
const sessionId = await createFreshSession(page, STORE_TYPE);
const productId = await viewProductViaFlow(page, STORE_TYPE);
const baselinePrice = await getPriceFromDOM(page);
await rapidViewProductViaFlow(page, 2, 300, STORE_TYPE);
await page.waitForTimeout(1500);
await page.goto(`/products/${productId}`);
await page.waitForLoadState('networkidle');
const currentPrice = await getPriceFromDOM(page);
expect(Math.abs(currentPrice - baselinePrice) / baselinePrice).toBeLessThan(0.05);
expect(await verifySessionConsistency(page, sessionId)).toBeTruthy();
});
test('window: surge decays after window expires', async ({ page, backendUrl }) => {
const sessionId = await createFreshSession(page, STORE_TYPE);
const productId = await viewProductViaFlow(page, STORE_TYPE);
const baselinePrice = await getPriceFromDOM(page);
await rapidViewProductViaFlow(page, 5, 150, STORE_TYPE);
await page.waitForTimeout(1500);
await page.goto(`/products/${productId}`);
await page.waitForLoadState('networkidle');
const surgedPrice = await getPriceFromDOM(page);
expect(surgedPrice).toBeGreaterThan(baselinePrice);
await page.waitForTimeout(12000);
await page.goto(`/products/${productId}`);
await page.waitForLoadState('networkidle');
const decayedPrice = await getPriceFromDOM(page);
expect(decayedPrice).toBeLessThan(surgedPrice);
expect(await verifySessionConsistency(page, sessionId)).toBeTruthy();
});
test('isolation: different products have independent surge', async ({ page, backendUrl }) => {
const sessionId = await createFreshSession(page, STORE_TYPE);
const productIdA = await viewProductViaFlow(page, STORE_TYPE);
const basePriceA = await getPriceFromDOM(page);
await rapidViewProductViaFlow(page, 5, 200, STORE_TYPE);
await page.waitForTimeout(2000);
await page.goto(`/products/${productIdA}`);
await page.waitForLoadState('networkidle');
const surgedPriceA = await getPriceFromDOM(page);
const productIdB = await viewProductViaFlow(page, STORE_TYPE);
const priceB = await getPriceFromDOM(page);
expect(surgedPriceA).toBeGreaterThan(basePriceA * 0.99);
expect(productIdA).not.toBe(productIdB);
expect(await verifySessionConsistency(page, sessionId)).toBeTruthy();
});
});