103 lines
3.8 KiB
TypeScript
103 lines
3.8 KiB
TypeScript
// e2e helper: seed an in-progress device draft (stage='measure', with results) so
|
||
// the Resume path restores straight to the Photos-ready result step — the
|
||
// deterministic driver for the create→review→publish flow (no live camera/canvas
|
||
// in e2e). The draft is written to the real idb-keyval DB ('keyval-store'/'keyval',
|
||
// key 'slabuploader-draft') exactly as `saveDraft` would, so `resumeProbe` +
|
||
// `continueDraft` + `continueToReview` consume it unmodified.
|
||
//
|
||
// `source.blob` MUST be a decodable image: restoreFromDraft re-derives the mask
|
||
// in a Worker that decodes the blob; a failed decode falls back to `mask` (no
|
||
// results → the Continue button never renders). A 1×1 white PNG decodes fine and
|
||
// yields slabPx:0 (an empty mask) → rederiveMask resolves → stage reaches measure.
|
||
import type { Page } from '@playwright/test';
|
||
|
||
const KEY = 'slabuploader-draft';
|
||
|
||
export interface SeedMeasure {
|
||
sku: string;
|
||
lengthIn: number;
|
||
thicknessIn: number;
|
||
sqft: number;
|
||
bdft: number;
|
||
widthIn: number;
|
||
}
|
||
|
||
const toEighths = (inches: number) => Math.round(inches * 8);
|
||
|
||
export async function seedMeasureDraft(
|
||
page: Page,
|
||
d: SeedMeasure,
|
||
): Promise<void> {
|
||
const widthEighths = toEighths(d.widthIn);
|
||
// Build the decodable source PNG in the page via canvas (a fixed base64 1×1
|
||
// gray+alpha PNG trips Chromium's software decoder in headless), then write
|
||
// the draft with that blob so restoreFromDraft's worker decode succeeds.
|
||
await page.evaluate(
|
||
(payload) =>
|
||
new Promise<void>((resolve, reject) => {
|
||
const c = document.createElement('canvas');
|
||
c.width = 1;
|
||
c.height = 1;
|
||
const ctx = c.getContext('2d')!;
|
||
ctx.fillStyle = '#fff';
|
||
ctx.fillRect(0, 0, 1, 1);
|
||
c.toBlob((png) => {
|
||
if (!png) { reject(new Error('canvas toBlob returned null')); return; }
|
||
const draft = {
|
||
version: 1,
|
||
stage: 'measure',
|
||
batch: [],
|
||
calibrationIndex: null,
|
||
source: { widthPx: 1, heightPx: 1, blob: png },
|
||
maskParams: { sheet: 'green', sensitivity: 50 },
|
||
mask: {
|
||
slabPx: 0,
|
||
bbox: { corners: [], longSidePx: 0, angleDeg: 0 },
|
||
slabBounds: { x: 0, y: 0, w: 0, h: 0 },
|
||
},
|
||
axisBbox: null,
|
||
lengthEighths: payload.lengthEighths,
|
||
thicknessEighths: payload.thicknessEighths,
|
||
results: {
|
||
lengthEighths: payload.lengthEighths,
|
||
thicknessEighths: payload.thicknessEighths,
|
||
scale: 8,
|
||
widths: {
|
||
min: payload.widthEighths,
|
||
max: payload.widthEighths,
|
||
avg: payload.widthEighths,
|
||
every6In: [],
|
||
},
|
||
sqft: payload.sqft,
|
||
bdft: payload.bdft,
|
||
sourceBelowFloor: false,
|
||
},
|
||
inventoryPng: png,
|
||
overlayPng: null,
|
||
batchApply: null,
|
||
};
|
||
const req = indexedDB.open('keyval-store', 1);
|
||
req.onupgradeneeded = () => {
|
||
const db = req.result;
|
||
if (!db.objectStoreNames.contains('keyval')) db.createObjectStore('keyval');
|
||
};
|
||
req.onsuccess = () => {
|
||
const db = req.result;
|
||
const tx = db.transaction('keyval', 'readwrite');
|
||
tx.objectStore('keyval').put(draft, 'slabuploader-draft');
|
||
tx.oncomplete = () => { db.close(); resolve(); };
|
||
tx.onerror = () => { db.close(); reject(tx.error); };
|
||
};
|
||
req.onerror = () => reject(req.error);
|
||
}, 'image/png');
|
||
}),
|
||
{
|
||
lengthEighths: toEighths(d.lengthIn),
|
||
thicknessEighths: toEighths(d.thicknessIn),
|
||
widthEighths,
|
||
sqft: d.sqft,
|
||
bdft: d.bdft,
|
||
},
|
||
);
|
||
}
|