Add scoped feature regeneration and retained pixel measurements
This commit is contained in:
parent
39ee37db02
commit
3058b9a5f2
15 changed files with 889 additions and 81 deletions
|
|
@ -233,7 +233,8 @@ async function main() {
|
|||
if (probe && probe.drawn > 0) break;
|
||||
await sleep(100);
|
||||
}
|
||||
if (!probe) throw new Error('no canvas.stage on the page — is `shadow-cljs watch app` running?');
|
||||
if (!probe) throw new Error('no canvas.stage on the page — ' +
|
||||
(page.logs.slice(0, 3).join(' | ') || 'is `shadow-cljs watch app` running?'));
|
||||
|
||||
console.log(`\ncanvas ${probe.w}x${probe.h}, clip ${JSON.stringify(probe.selectedClip)}`);
|
||||
|
||||
|
|
@ -393,6 +394,138 @@ async function main() {
|
|||
check(!back[0].selectedClip.includes('take'), 'the picture is the reopened document',
|
||||
JSON.stringify(back[0].selectedClip));
|
||||
|
||||
// The local 8625 study is optional in a fresh test database. When present,
|
||||
// tune its shared symbol while its stage is playing.
|
||||
const stageSource = await page.eval(`fetch('/api/projects/4379f900-bdd2-409b-acf6-32081f8ce01f')
|
||||
.then(r => r.ok)`);
|
||||
if (stageSource) {
|
||||
check(await page.eval(CLICK('stage 8625')), 'the 8625 stage loads');
|
||||
const stageLoaded = await statusMatching(/loaded 8625 stage study/, 160);
|
||||
check(stageLoaded !== null, 'the 8625 stage is ready', stageLoaded ?? (await page.eval(STATUS)));
|
||||
const eyeSelected = await page.eval(`(() => {
|
||||
const select = document.querySelector('.controls select');
|
||||
const option = [...select.options]
|
||||
.find(o => o.textContent.trim() === 'left / feature · eye-r');
|
||||
if (!option) return false;
|
||||
select.value = option.value;
|
||||
select.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
return true;
|
||||
})()`);
|
||||
check(eyeSelected, 'a stage instance exposes its tracked right eye');
|
||||
await sleep(100);
|
||||
const irisSlider = await page.eval(`(() => {
|
||||
const row = [...document.querySelectorAll('.control-row')]
|
||||
.find(row => row.querySelector('span')?.textContent === 'iris-size');
|
||||
if (!row) return null;
|
||||
const r = row.querySelector('input').getBoundingClientRect();
|
||||
return { x: r.x + r.width * 0.9, y: r.y + r.height / 2 };
|
||||
})()`);
|
||||
check(irisSlider !== null, 'the stage eye has an iris-size slider');
|
||||
if (irisSlider) {
|
||||
check(await page.eval(CLICK('play')), 'the stage starts playing');
|
||||
const startFrame = await page.eval(`Number(document.querySelector('.readout span').textContent.match(/\\d+/)[0])`);
|
||||
await page.send('Input.dispatchMouseEvent', {
|
||||
type: 'mousePressed', x: irisSlider.x, y: irisSlider.y, button: 'left', clickCount: 1,
|
||||
});
|
||||
await page.send('Input.dispatchMouseEvent', {
|
||||
type: 'mouseReleased', x: irisSlider.x, y: irisSlider.y, button: 'left', clickCount: 1,
|
||||
});
|
||||
const preview = await statusMatching(/preview · unsaved/, 160);
|
||||
const debug = await page.eval(`document.querySelector('.regeneration-debug')?.textContent ?? ''`);
|
||||
check(preview !== null, 'the slider updates the stage preview', preview ?? debug);
|
||||
check(debug.includes(':eye-r') && debug.includes('tier 1 only'),
|
||||
'the panel reports the affected feature and tier', debug);
|
||||
const endFrame = await page.eval(`Number(document.querySelector('.readout span').textContent.match(/\\d+/)[0])`);
|
||||
check(endFrame > startFrame, 'playback continues during tuning', `${startFrame} -> ${endFrame}`);
|
||||
await page.eval(CLICK('pause'));
|
||||
}
|
||||
const subjectSelected = await page.eval(`(() => {
|
||||
const select = document.querySelector('.controls select');
|
||||
const option = [...select.options]
|
||||
.find(o => o.textContent.includes('subject · face-1'));
|
||||
if (!option) return false;
|
||||
select.value = option.value;
|
||||
select.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
return true;
|
||||
})()`);
|
||||
check(subjectSelected, 'the stage exposes its tracked subject');
|
||||
if (subjectSelected) {
|
||||
await sleep(100);
|
||||
const anchorSlider = await page.eval(`(() => {
|
||||
const row = [...document.querySelectorAll('.control-row')]
|
||||
.find(row => row.querySelector('span')?.textContent === 'anchor-avg');
|
||||
if (!row) return null;
|
||||
const r = row.querySelector('input').getBoundingClientRect();
|
||||
return { x: r.x + r.width * 0.9, y: r.y + r.height / 2 };
|
||||
})()`);
|
||||
check(anchorSlider !== null, 'the subject has an anchor slider');
|
||||
if (anchorSlider) {
|
||||
const anchorStart = Date.now();
|
||||
await page.send('Input.dispatchMouseEvent', {
|
||||
type: 'mousePressed', x: anchorSlider.x, y: anchorSlider.y,
|
||||
button: 'left', clickCount: 1,
|
||||
});
|
||||
await page.send('Input.dispatchMouseEvent', {
|
||||
type: 'mouseReleased', x: anchorSlider.x, y: anchorSlider.y,
|
||||
button: 'left', clickCount: 1,
|
||||
});
|
||||
let debug = '';
|
||||
for (let i = 0; i < 160; i++) {
|
||||
debug = await page.eval(`document.querySelector('.regeneration-debug')?.textContent ?? ''`);
|
||||
if (debug.includes('head-pos')) break;
|
||||
await sleep(250);
|
||||
}
|
||||
check(debug.includes('head-pos') && debug.includes(':teeth'),
|
||||
'anchor invalidates the head and teeth', debug);
|
||||
const preview = await statusMatching(/preview · unsaved/, 160);
|
||||
check(preview !== null, 'the anchor edit finishes previewing',
|
||||
preview ? `${Date.now() - anchorStart}ms` : (await page.eval(STATUS)));
|
||||
}
|
||||
}
|
||||
const teethSelected = await page.eval(`(() => {
|
||||
const select = document.querySelector('.controls select');
|
||||
const option = [...select.options]
|
||||
.find(o => o.textContent.trim() === 'left / feature · teeth');
|
||||
if (!option) return false;
|
||||
select.value = option.value;
|
||||
select.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
return true;
|
||||
})()`);
|
||||
check(teethSelected, 'the stage exposes its tracked teeth');
|
||||
if (teethSelected) {
|
||||
await sleep(100);
|
||||
const slider = await page.eval(`(() => {
|
||||
const row = [...document.querySelectorAll('.control-row')]
|
||||
.find(row => row.querySelector('span')?.textContent === 'cavity-erode');
|
||||
if (!row) return null;
|
||||
const r = row.querySelector('input').getBoundingClientRect();
|
||||
return { x: r.x + r.width * 0.9, y: r.y + r.height / 2 };
|
||||
})()`);
|
||||
check(slider !== null, 'the teeth have a pixel-setting slider');
|
||||
if (slider) {
|
||||
await page.send('Input.dispatchMouseEvent', {
|
||||
type: 'mousePressed', x: slider.x, y: slider.y,
|
||||
button: 'left', clickCount: 1,
|
||||
});
|
||||
await page.send('Input.dispatchMouseEvent', {
|
||||
type: 'mouseReleased', x: slider.x, y: slider.y,
|
||||
button: 'left', clickCount: 1,
|
||||
});
|
||||
let debug = '';
|
||||
for (let i = 0; i < 160; i++) {
|
||||
debug = await page.eval(`document.querySelector('.regeneration-debug')?.textContent ?? ''`);
|
||||
if (debug.includes('dirty features: [:teeth]')) break;
|
||||
await sleep(250);
|
||||
}
|
||||
check(debug.includes('dirty features: [:teeth]'),
|
||||
'the pixel setting invalidates only teeth', debug);
|
||||
const preview = await statusMatching(/preview · unsaved/, 160);
|
||||
check(preview !== null, 'the teeth edit finishes previewing',
|
||||
preview ?? (await page.eval(STATUS)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exercise the actual file input and FormData path, then wait for the
|
||||
// extraction job's footage to appear in the server list.
|
||||
const uploadDir = mkdtempSync(join(tmpdir(), 'arthur-upload-'));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue