test: make inquiry/observation Flow A visible (UI-driven with API fallback)
All checks were successful
Build and Push Docker Image / build (push) Successful in 28s

Flow A now drives the real screens - detail page, Send-to modal, dept-response
page, Accept button - so the run is watchable in headed mode. Each visible step
falls back to a direct POST if the UI doesn't persist, so the flow still
completes reliably. Flows B/C unchanged.
This commit is contained in:
ismail 2026-06-14 19:05:07 +03:00
parent 23b6e239b5
commit 8c75baf30a

View File

@ -28,6 +28,7 @@ interface KindCfg {
label: string; label: string;
module: string; module: string;
seedKind: string; seedKind: string;
detail: (id: string) => string;
sendTo: (id: string) => string; sendTo: (id: string) => string;
sendToken: (id: string) => string; sendToken: (id: string) => string;
deptResponse: (id: string) => string; deptResponse: (id: string) => string;
@ -44,6 +45,7 @@ interface KindCfg {
const KINDS: KindCfg[] = [ const KINDS: KindCfg[] = [
{ {
label: 'Inquiry', module: 'InquiryDeptResponse', seedKind: 'inquiry', label: 'Inquiry', module: 'InquiryDeptResponse', seedKind: 'inquiry',
detail: (id) => `/inquiries/${id}/`,
sendTo: (id) => `/inquiries/${id}/send-to/`, sendTo: (id) => `/inquiries/${id}/send-to/`,
sendToken: (id) => `/inquiries/${id}/transfer-to-department/`, sendToken: (id) => `/inquiries/${id}/transfer-to-department/`,
deptResponse: (id) => `/inquiries/${id}/department-response/`, deptResponse: (id) => `/inquiries/${id}/department-response/`,
@ -56,6 +58,7 @@ const KINDS: KindCfg[] = [
}, },
{ {
label: 'Observation', module: 'ObservationDeptResponse', seedKind: 'observation', label: 'Observation', module: 'ObservationDeptResponse', seedKind: 'observation',
detail: (id) => `/observations/${id}`,
sendTo: (id) => `/observations/${id}/send-to/`, sendTo: (id) => `/observations/${id}/send-to/`,
sendToken: (id) => `/observations/${id}/send-to-department/`, sendToken: (id) => `/observations/${id}/send-to-department/`,
deptResponse: (id) => `/observations/${id}/department-response/`, deptResponse: (id) => `/observations/${id}/department-response/`,
@ -139,21 +142,72 @@ async function flowA(page: Page, K: KindCfg) {
const s = seed(K.seedKind); const s = seed(K.seedKind);
itemId = s.itemId; itemId = s.itemId;
observe(M, 'A-seed', 'INFO', `${K.label} ${itemId}`, { role: PXT }); observe(M, 'A-seed', 'INFO', `${K.label} ${itemId}`, { role: PXT });
// 1. PX send (AJAX)
// Helper: try the visible UI path; if it doesn't achieve the expected state, fall back to a direct POST.
const ensureState = async (want: Record<string, string>, step: string, role: string, fallback?: () => Promise<void>) => {
const before = state(K.seedKind, itemId);
const okBefore = Object.entries(want).every(([k, v]) => (v === 'NOT_NONE' ? (before[k] && before[k] !== 'NONE') : before[k] === v));
if (!okBefore && fallback) { await fallback(); }
assertSt(M, K.seedKind, itemId, want, step, role);
};
// 1. PX send - VISIBLE: detail page + Send-to modal
await login(page, PXT, M); await ensureAuth(page, PXT, M); await login(page, PXT, M); await ensureAuth(page, PXT, M);
await postObs(page, M, `${BASE_URL}${K.sendTo(itemId)}`, { recipient_type: 'department', department_id: s.deptId, contact_person_id: s.champStaffId, note: 'E2E send' }, 'A-send', PXT); await page.goto(`${BASE_URL}${K.detail(itemId)}`);
assertSt(M, K.seedKind, itemId, { [K.sentField]: K.sentWant }, 'A-send-state', PXT); await page.waitForLoadState('domcontentloaded');
// 2. champion responds (logged-in) await page.waitForTimeout(700);
await page.evaluate((args) => {
const fn = (window as any).showSendModal;
if (typeof fn === 'function') { try { fn(args.id, args.kind); } catch { /* ignore */ } }
const m = document.getElementById('sendToModal');
if (m) m.classList.remove('hidden');
}, { id: itemId, kind: K.seedKind });
await page.waitForTimeout(500);
await page.locator('input[name="recipient_type"][value="department"]').first().check({ force: true }).catch(() => {});
await page.locator('select[name="department_id"]').first().selectOption({ value: s.deptId }).catch(() => {});
await page.waitForTimeout(1200);
await page.locator('select[name="contact_person_id"]').first().selectOption({ value: s.champStaffId }).catch(() => {});
await page.waitForTimeout(300);
await page.locator('#sendToModal button[type="submit"], #sendToModal button:not([type="button"])').first().click({ force: true }).catch(() => {});
await page.waitForLoadState('networkidle', { timeout: 8000 }).catch(() => {});
await page.waitForTimeout(1200);
await ensureState({ [K.sentField]: K.sentWant }, 'A-send-state', PXT,
async () => { await postObs(page, M, `${BASE_URL}${K.sendTo(itemId)}`, { recipient_type: 'department', department_id: s.deptId, contact_person_id: s.champStaffId, note: 'E2E send' }, 'A-send-fallback', PXT); });
// 2. Champion responds - VISIBLE: dept-response page
await login(page, CHAMP, M); await ensureAuth(page, CHAMP, M); await login(page, CHAMP, M); await ensureAuth(page, CHAMP, M);
await postObs(page, M, `${BASE_URL}${K.deptResponse(itemId)}`, { response_en: `E2E ${K.label} champion response (A)` }, 'A-champion-response', CHAMP); await page.goto(`${BASE_URL}${K.deptResponse(itemId)}`);
assertSt(M, K.seedKind, itemId, { department_response_en_set: 'True', dept_response_acceptance_status: 'pending' }, 'A-champion-state', CHAMP); await page.waitForLoadState('domcontentloaded');
// 3. PX accepts await page.waitForTimeout(500);
const tb = await bodyHasTraceback(page);
if (!tb) {
const ta = page.locator('textarea[name="response_en"]').first();
if (await ta.count()) {
await ta.fill(`E2E ${K.label} champion response (A)`);
// submit the response form specifically
await page.locator('form[action*="department-response"] button[type="submit"], textarea[name="response_en"] ~ * button[type="submit"], button[type="submit"]').first().click({ force: true }).catch(() => {});
await page.waitForLoadState('networkidle', { timeout: 8000 }).catch(() => {});
await page.waitForTimeout(1000);
}
}
await ensureState({ department_response_en_set: 'True', dept_response_acceptance_status: 'pending' }, 'A-champion-state', CHAMP,
async () => { await postObs(page, M, `${BASE_URL}${K.deptResponse(itemId)}`, { response_en: `E2E ${K.label} champion response (A)` }, 'A-champion-fallback', CHAMP); });
// 3. PX accepts - VISIBLE: click Accept on the detail page
await login(page, PXT, M); await ensureAuth(page, PXT, M); await login(page, PXT, M); await ensureAuth(page, PXT, M);
await postObs(page, M, `${BASE_URL}${K.review(itemId)}`, { acceptance_status: 'acceptable' }, 'A-px-accept', PXT); await page.goto(`${BASE_URL}${K.detail(itemId)}`);
assertSt(M, K.seedKind, itemId, { dept_response_acceptance_status: 'acceptable' }, 'A-accept-state', PXT); await page.waitForLoadState('domcontentloaded');
// 4. resolve (inquiry needs a PX response first). Best-effort: some status await page.waitForTimeout(600);
// machines (observation) can't jump straight to resolved; the dept-response const acceptForm = page.locator('form').filter({ has: page.locator('input[name="acceptance_status"][value="acceptable"]') }).first();
// flow itself is already proven by the acceptance assertion above. if (await acceptForm.count()) {
await acceptForm.locator('button[type="submit"]').first().click({ force: true }).catch(() => {});
await page.waitForLoadState('networkidle', { timeout: 8000 }).catch(() => {});
await page.waitForTimeout(1000);
}
await ensureState({ dept_response_acceptance_status: 'acceptable' }, 'A-accept-state', PXT,
async () => { await postObs(page, M, `${BASE_URL}${K.review(itemId)}`, { acceptance_status: 'acceptable' }, 'A-accept-fallback', PXT); });
// 4. Resolve (best-effort; some status machines can't jump to resolved)
if (K.pxRespond) await postObs(page, M, `${BASE_URL}${K.pxRespond(itemId)}`, { response_en: `E2E ${K.label} PX response` }, 'A-px-respond', PXT); if (K.pxRespond) await postObs(page, M, `${BASE_URL}${K.pxRespond(itemId)}`, { response_en: `E2E ${K.label} PX response` }, 'A-px-respond', PXT);
await postForm(page, `${BASE_URL}${K.activate(itemId)}`, {}); await postForm(page, `${BASE_URL}${K.activate(itemId)}`, {});
await postObs(page, M, `${BASE_URL}${K.changeStatus(itemId)}`, { status: 'resolved', note: 'E2E resolved' }, 'A-resolve', PXT); await postObs(page, M, `${BASE_URL}${K.changeStatus(itemId)}`, { status: 'resolved', note: 'E2E resolved' }, 'A-resolve', PXT);