File size: 4,381 Bytes
497bb49 fbf73ff 497bb49 fbf73ff 497bb49 fbf73ff 497bb49 fbf73ff 497bb49 fbf73ff 497bb49 fbf73ff 497bb49 fbf73ff 497bb49 fbf73ff 497bb49 fbf73ff 497bb49 fbf73ff 497bb49 fbf73ff 497bb49 fbf73ff 497bb49 fbf73ff 497bb49 fbf73ff |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
import { test as base, expect, Page } from '@playwright/test'
// API response mocks matching the async job queue pattern
const MOCK_CASES = ['sub-stroke0001', 'sub-stroke0002', 'sub-stroke0003']
// Track jobs for the async pattern
interface MockJob {
id: string
caseId: string
status: 'pending' | 'running' | 'completed' | 'failed'
progress: number
progressMessage: string
createdAt: number
}
// Job store per test (reset for each test)
const createJobStore = () => {
const jobs = new Map<string, MockJob>()
let jobCounter = 0
return {
createJob(caseId: string): MockJob {
const jobId = `e2e-job-${++jobCounter}`
const job: MockJob = {
id: jobId,
caseId,
status: 'pending',
progress: 0,
progressMessage: 'Job queued',
createdAt: Date.now(),
}
jobs.set(jobId, job)
return job
},
getJob(jobId: string): MockJob | undefined {
return jobs.get(jobId)
},
updateJobProgress(job: MockJob): MockJob {
// Simulate job progression over 1 second
const elapsed = Date.now() - job.createdAt
if (elapsed < 200) {
return { ...job, status: 'running', progress: 25, progressMessage: 'Loading case data...' }
} else if (elapsed < 500) {
return { ...job, status: 'running', progress: 50, progressMessage: 'Running inference...' }
} else if (elapsed < 800) {
return { ...job, status: 'running', progress: 75, progressMessage: 'Processing results...' }
} else {
return { ...job, status: 'completed', progress: 100, progressMessage: 'Segmentation complete' }
}
},
}
}
// Mock completed job result
const createMockResult = (caseId: string) => ({
caseId,
diceScore: 0.847,
volumeMl: 15.32,
elapsedSeconds: 12.5,
// Use real public NIfTI for visual testing (NiiVue demo image)
dwiUrl: 'https://niivue.github.io/niivue-demo-images/mni152.nii.gz',
predictionUrl: 'https://niivue.github.io/niivue-demo-images/mni152.nii.gz',
})
// Setup API mocking for async job queue pattern
async function setupApiMocks(page: Page) {
const jobStore = createJobStore()
// Mock GET /api/cases
await page.route('**/api/cases', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ cases: MOCK_CASES }),
})
})
// Mock POST /api/segment - returns 202 with job ID (async pattern)
await page.route('**/api/segment', async (route) => {
const request = route.request()
const body = JSON.parse(request.postData() || '{}') as { case_id?: string }
const caseId = body.case_id || 'sub-stroke0001'
// Create a new job
const job = jobStore.createJob(caseId)
// Small delay to simulate network
await new Promise((r) => setTimeout(r, 50))
route.fulfill({
status: 202,
contentType: 'application/json',
body: JSON.stringify({
jobId: job.id,
status: 'pending',
message: `Segmentation job queued for ${caseId}`,
}),
})
})
// Mock GET /api/jobs/:jobId - returns job status (for polling)
await page.route('**/api/jobs/*', async (route) => {
const url = route.request().url()
const jobId = url.split('/api/jobs/')[1]
const job = jobStore.getJob(jobId)
if (!job) {
route.fulfill({
status: 404,
contentType: 'application/json',
body: JSON.stringify({ detail: `Job not found: ${jobId}` }),
})
return
}
// Update job progress based on elapsed time
const updatedJob = jobStore.updateJobProgress(job)
const response: Record<string, unknown> = {
jobId: updatedJob.id,
status: updatedJob.status,
progress: updatedJob.progress,
progressMessage: updatedJob.progressMessage,
elapsedSeconds: (Date.now() - updatedJob.createdAt) / 1000,
}
// Include result when completed
if (updatedJob.status === 'completed') {
response.result = createMockResult(updatedJob.caseId)
}
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(response),
})
})
}
// Extend base test to include API mocking
export const test = base.extend({
// Auto-mock API routes for every test
page: async ({ page }, use) => {
await setupApiMocks(page)
await use(page)
},
})
export { expect }
|