mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
Add Director authoring, import previews and scene bundles
This commit is contained in:
1 parent
62f66c4b86
commit
078fc290ec
25 files changed
+1930
-48
No files matched your search
@@ -741,5 +741,7 @@
|
||||
"scripts/qa-director-packs.mjs",
|
||||
"src/scenes/dataPacks/presentation.test.mjs",
|
||||
"src/director/interactions/interactions.test.mjs",
|
||||
"scripts/qa-director-interactions.mjs"
|
||||
"scripts/qa-director-interactions.mjs",
|
||||
"src/director/sharing/sharing.test.mjs",
|
||||
"scripts/qa-director-sharing.mjs"
|
||||
]
|
||||
@@ -627,7 +627,11 @@
|
||||
"src/director/packs/geojson.js",
|
||||
"src/director/packs/manifest.js",
|
||||
"src/director/interactions/document.js",
|
||||
"src/director/interactions/session.js"
|
||||
"src/director/interactions/session.js",
|
||||
"src/director/sharing/bundle.js",
|
||||
"src/director/sharing/preview.js",
|
||||
"src/director/sharing/lifetime.js",
|
||||
"src/director/authoring.js"
|
||||
],
|
||||
"external": []
|
||||
},
|
||||
@@ -672,7 +676,13 @@
|
||||
"src/director/interactions/document.js",
|
||||
"src/director/interactions/session.js",
|
||||
"src/scenes/interactions.js",
|
||||
"src/data/inputOwnership.js"
|
||||
"src/data/inputOwnership.js",
|
||||
"src/director/sharing/bundle.js",
|
||||
"src/director/sharing/preview.js",
|
||||
"src/director/sharing/lifetime.js",
|
||||
"src/director/authoring.js",
|
||||
"src/scenes/sharing.js",
|
||||
"src/ui/sceneSharing.js"
|
||||
],
|
||||
"external": ["cesium"]
|
||||
},
|
||||
@@ -1793,7 +1803,13 @@
|
||||
"src/scenes/dataPacks/geometry.js",
|
||||
"src/director/interactions/document.js",
|
||||
"src/director/interactions/session.js",
|
||||
"src/scenes/interactions.js"
|
||||
"src/scenes/interactions.js",
|
||||
"src/director/sharing/bundle.js",
|
||||
"src/director/sharing/preview.js",
|
||||
"src/director/sharing/lifetime.js",
|
||||
"src/director/authoring.js",
|
||||
"src/scenes/sharing.js",
|
||||
"src/ui/sceneSharing.js"
|
||||
],
|
||||
"external": [
|
||||
"@mapbox/vector-tile",
|
||||
|
||||
@@ -0,0 +1,482 @@
|
||||
#!/usr/bin/env node
|
||||
/** Sharing/authoring acceptance with local synthetic assets and real installed controls. */
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import puppeteer from 'puppeteer';
|
||||
const output = path.resolve('qa-shots/director-sharing');
|
||||
fs.mkdirSync(output, { recursive: true });
|
||||
const geometry = {
|
||||
type: 'FeatureCollection',
|
||||
features: [
|
||||
{
|
||||
type: 'Feature',
|
||||
id: 'point',
|
||||
geometry: { type: 'Point', coordinates: [-97.742, 30.2672, 500] },
|
||||
},
|
||||
],
|
||||
};
|
||||
fs.writeFileSync(path.join(output, 'data.geojson'), JSON.stringify(geometry));
|
||||
const camera = {
|
||||
lat: 30.2672,
|
||||
lon: -97.742,
|
||||
alt: 4000,
|
||||
heading: 0,
|
||||
pitch: -90,
|
||||
roll: 0,
|
||||
};
|
||||
const project = {
|
||||
version: 6,
|
||||
scenes: [
|
||||
{
|
||||
id: 'share',
|
||||
title: 'Sharing fixture',
|
||||
dataPacks: [
|
||||
{
|
||||
id: 'data',
|
||||
version: 1,
|
||||
format: 'geojson',
|
||||
source: { adapter: 'assets', path: 'example/data.geojson' },
|
||||
attribution: { text: 'Synthetic <b>fixture</b>', license: 'CC0-1.0' },
|
||||
placement: { altitudeReference: 'ellipsoid' },
|
||||
},
|
||||
],
|
||||
shots: [
|
||||
{
|
||||
id: 'shot',
|
||||
title: 'First',
|
||||
camera,
|
||||
durationSec: 0.3,
|
||||
holdSec: 0,
|
||||
visual: { style: 'normal', mapStack: 'photoreal' },
|
||||
layers: {},
|
||||
dataPackIds: ['data'],
|
||||
interactions: [
|
||||
{
|
||||
id: 'info',
|
||||
label: 'Read fixture',
|
||||
target: { packId: 'data', featureId: 'point' },
|
||||
action: { type: 'card', text: 'Synthetic scene note' },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{ id: 'second', title: 'Other scene', shots: [] },
|
||||
],
|
||||
};
|
||||
const file = path.join(output, 'project.json');
|
||||
fs.writeFileSync(file, JSON.stringify(project));
|
||||
const browser = await puppeteer.launch({
|
||||
headless: true,
|
||||
args: ['--no-sandbox', '--disable-dev-shm-usage'],
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
let failures = 0;
|
||||
const errors = [],
|
||||
requests = [];
|
||||
const check = (name, ok) => {
|
||||
console.log(`[${ok ? 'PASS' : 'FAIL'}] ${name}`);
|
||||
if (!ok) failures++;
|
||||
};
|
||||
page.on('pageerror', (e) => errors.push(e.message));
|
||||
page.on('request', (r) => {
|
||||
if (new URL(r.url()).pathname.startsWith('/scene-assets/'))
|
||||
requests.push(r.url());
|
||||
});
|
||||
async function click(label) {
|
||||
const handle = await page.evaluateHandle(
|
||||
(label) =>
|
||||
[...document.querySelectorAll('button')].find(
|
||||
(b) => b.textContent === label,
|
||||
),
|
||||
label,
|
||||
);
|
||||
await handle.asElement().click();
|
||||
await handle.dispose();
|
||||
}
|
||||
async function value(label, text) {
|
||||
await page.$eval(
|
||||
`[aria-label="${label}"]`,
|
||||
(node, text) => {
|
||||
node.value = text;
|
||||
node.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
},
|
||||
text,
|
||||
);
|
||||
}
|
||||
try {
|
||||
await page.setViewport({ width: 1440, height: 900 });
|
||||
await page.goto(
|
||||
`${process.env.QA_BASE_URL || 'http://localhost:4173'}/?welcome=0`,
|
||||
{ waitUntil: 'domcontentloaded' },
|
||||
);
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
window.__godsEyeView?.sceneDirector &&
|
||||
document.getElementById('loading-screen')?.classList.contains('hidden'),
|
||||
{ timeout: 60000 },
|
||||
);
|
||||
await page.click('[data-collapse-target="scene-panel"]');
|
||||
const before = await page.evaluate(() =>
|
||||
JSON.stringify(window.__godsEyeView.sceneDirector._project),
|
||||
);
|
||||
const input = await page.$('#scene-import-file');
|
||||
await input.uploadFile(file);
|
||||
await page.waitForSelector('[data-director-apply-import]');
|
||||
check(
|
||||
'preview parses metadata without acquiring assets or replacing the current project',
|
||||
requests.length === 0 &&
|
||||
(await page.evaluate(
|
||||
(before) =>
|
||||
JSON.stringify(window.__godsEyeView.sceneDirector._project) ===
|
||||
before,
|
||||
before,
|
||||
)),
|
||||
);
|
||||
check(
|
||||
'preview renders author attribution as literal text',
|
||||
await page.evaluate(
|
||||
() =>
|
||||
document
|
||||
.querySelector('[data-director-dialog]')
|
||||
.textContent.includes('Synthetic <b>fixture</b>') &&
|
||||
!document.querySelector('[data-director-dialog] b'),
|
||||
),
|
||||
);
|
||||
await page.keyboard.press('Escape');
|
||||
check(
|
||||
'Escape cancels preview without altering saved work',
|
||||
await page.evaluate(
|
||||
(before) =>
|
||||
!document.querySelector('[data-director-dialog]') &&
|
||||
JSON.stringify(window.__godsEyeView.sceneDirector._project) === before,
|
||||
before,
|
||||
),
|
||||
);
|
||||
await input.uploadFile(file);
|
||||
await page.waitForSelector('[data-director-apply-import]');
|
||||
await page.click('[data-director-apply-import]');
|
||||
await page.waitForFunction(
|
||||
() => !document.querySelector('[data-director-dialog]'),
|
||||
);
|
||||
check(
|
||||
'Apply import replaces metadata but does not start playback or fetch a pack',
|
||||
requests.length === 0 &&
|
||||
(await page.evaluate(
|
||||
() =>
|
||||
window.__godsEyeView.sceneDirector._project.scenes.length === 2 &&
|
||||
!window.__godsEyeView.sceneDirector.running,
|
||||
)),
|
||||
);
|
||||
await click('EDIT DETAILS');
|
||||
await click('Capture camera as anchor');
|
||||
await page.waitForFunction(() =>
|
||||
document
|
||||
.querySelector('[data-director-dialog] [role=status]')
|
||||
.textContent.includes('Anchor added'),
|
||||
);
|
||||
await click('Set move start to current camera');
|
||||
await page.waitForFunction(() =>
|
||||
document
|
||||
.querySelector('[data-director-dialog] [role=status]')
|
||||
.textContent.includes('Move added'),
|
||||
);
|
||||
const draft = await page.$eval(
|
||||
'[aria-label="Shot camera, timing, packs and actions"]',
|
||||
(n) => n.value,
|
||||
);
|
||||
const bad = JSON.parse(draft);
|
||||
bad.camera.lat = 91;
|
||||
await value('Shot camera, timing, packs and actions', JSON.stringify(bad));
|
||||
const saved = await page.evaluate(() =>
|
||||
localStorage.getItem('godsEyeView.sceneProject.v2'),
|
||||
);
|
||||
await page.click('[data-director-apply-details]');
|
||||
await page.waitForFunction(() =>
|
||||
document
|
||||
.querySelector('[data-director-dialog] [role=status]')
|
||||
.textContent.includes('camera.lat'),
|
||||
);
|
||||
check(
|
||||
'invalid draft preserves saved bytes and existing authored content',
|
||||
await page.evaluate(
|
||||
(saved) => localStorage.getItem('godsEyeView.sceneProject.v2') === saved,
|
||||
saved,
|
||||
),
|
||||
);
|
||||
await value('Shot camera, timing, packs and actions', draft);
|
||||
await page.click('[data-director-apply-details]');
|
||||
await page.waitForFunction(
|
||||
() => !document.querySelector('[data-director-dialog]'),
|
||||
);
|
||||
check(
|
||||
'author controls preserve selected shot, pack attribution and action definitions',
|
||||
await page.evaluate(() => {
|
||||
const d = window.__godsEyeView.sceneDirector,
|
||||
s = d._getSelectedScene();
|
||||
return (
|
||||
d._selectedShotId === 'shot' &&
|
||||
s.anchors.length === 1 &&
|
||||
s.shots[0].move.easing === 'cubic-in-out' &&
|
||||
s.shots[0].interactions.length === 1 &&
|
||||
s.dataPacks[0].attribution.license === 'CC0-1.0'
|
||||
);
|
||||
}),
|
||||
);
|
||||
await page.evaluate(() => {
|
||||
window.__shareExports = [];
|
||||
const create = URL.createObjectURL.bind(URL);
|
||||
URL.createObjectURL = (blob) => {
|
||||
if (blob.type === 'application/json')
|
||||
window.__shareExports.push(blob.text());
|
||||
return create(blob);
|
||||
};
|
||||
});
|
||||
await click('SHARE SCENE');
|
||||
await click('Download scene JSON');
|
||||
const exported = await page.evaluate(async () =>
|
||||
JSON.parse(await window.__shareExports.at(-1)),
|
||||
);
|
||||
check(
|
||||
'scene JSON exports only the selected scene and keeps its credit',
|
||||
exported.scenes.length === 1 &&
|
||||
exported.scenes[0].dataPacks[0].attribution.license === 'CC0-1.0',
|
||||
);
|
||||
await click('Download asset bundle');
|
||||
await page.waitForFunction(() =>
|
||||
document
|
||||
.querySelector('[data-director-dialog] [role=status]')
|
||||
.textContent.includes('Could not complete'),
|
||||
);
|
||||
check(
|
||||
'missing explicit files produce no partial download or network fallback',
|
||||
requests.length === 0 &&
|
||||
(await page.evaluate(() => window.__shareExports.length === 1)),
|
||||
);
|
||||
const files = await page.$('[aria-label="Choose data-pack files"]');
|
||||
await files.uploadFile(path.join(output, 'data.geojson'));
|
||||
await click('Download asset bundle');
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
document.querySelector('[data-director-dialog] [role=status]')
|
||||
.textContent === 'Asset bundle downloaded',
|
||||
);
|
||||
const bundle = await page.evaluate(
|
||||
async () => await window.__shareExports.at(-1),
|
||||
);
|
||||
const bundleFile = path.join(output, 'scene.gevbundle.json');
|
||||
fs.writeFileSync(bundleFile, bundle);
|
||||
check(
|
||||
'bundle contains the selected bytes and preserves attribution',
|
||||
JSON.parse(bundle).assets.length === 1 &&
|
||||
JSON.parse(bundle).project.scenes[0].dataPacks[0].attribution.license ===
|
||||
'CC0-1.0',
|
||||
);
|
||||
await page.keyboard.press('Escape');
|
||||
await input.uploadFile(bundleFile);
|
||||
await page.waitForSelector('[data-director-apply-import]');
|
||||
check(
|
||||
'bundle preview verifies bytes and explains session-only storage',
|
||||
await page.evaluate(
|
||||
() =>
|
||||
document
|
||||
.querySelector('[data-director-dialog]')
|
||||
.textContent.includes('bundled bytes verified') &&
|
||||
window.__godsEyeView.sceneDirector.getSharingState().assets.count === 0,
|
||||
),
|
||||
);
|
||||
await page.screenshot({ path: path.join(output, 'preview.png') });
|
||||
await page.click('[data-director-apply-import]');
|
||||
await page.waitForFunction(
|
||||
() => !document.querySelector('[data-director-dialog]'),
|
||||
);
|
||||
await page.evaluate(() =>
|
||||
window.__godsEyeView.sceneDirector.loadShot('share', 'shot', {
|
||||
flyDuration: 0.3,
|
||||
}),
|
||||
);
|
||||
check(
|
||||
'imported bundle renders and enables feature actions with zero asset requests',
|
||||
requests.length === 0 &&
|
||||
(await page.evaluate(
|
||||
() =>
|
||||
window.__godsEyeView.sceneDirector.getDataPackState().count === 1 &&
|
||||
window.__godsEyeView.sceneDirector.getInteractionState().count === 1,
|
||||
)),
|
||||
);
|
||||
await page.waitForFunction(
|
||||
() => window.__godsEyeView.tileset?.tilesLoaded === true,
|
||||
{ timeout: 60000 },
|
||||
);
|
||||
await page.screenshot({ path: path.join(output, 'rendered.png') });
|
||||
fs.writeFileSync(
|
||||
path.join(output, 'report.json'),
|
||||
JSON.stringify(
|
||||
await page.evaluate(() => ({
|
||||
tilesSettled: window.__godsEyeView.tileset?.tilesLoaded === true,
|
||||
})),
|
||||
),
|
||||
);
|
||||
await page.evaluate(() => window.__godsEyeView.sceneDirector.stopScene());
|
||||
check(
|
||||
'Stop releases rendered resources while retaining reusable project bytes',
|
||||
await page.evaluate(() => {
|
||||
const d = window.__godsEyeView.sceneDirector;
|
||||
return (
|
||||
d.getDataPackState().count === 0 &&
|
||||
d.getSharingState().assets.count === 1
|
||||
);
|
||||
}),
|
||||
);
|
||||
await page.evaluate(() =>
|
||||
window.__godsEyeView.sceneDirector.seekScene('share', 0),
|
||||
);
|
||||
check(
|
||||
'seek reloads the bundle without a network fallback',
|
||||
requests.length === 0 &&
|
||||
(await page.evaluate(
|
||||
() => window.__godsEyeView.sceneDirector.getDataPackState().count === 1,
|
||||
)),
|
||||
);
|
||||
await click('EDIT DETAILS');
|
||||
await page.evaluate(() => {
|
||||
window.__godsEyeView.sceneDirector._project.scenes[0].title = 'Newer edit';
|
||||
});
|
||||
await page.click('[data-director-apply-details]');
|
||||
check(
|
||||
'stale author draft cannot overwrite a newer edit',
|
||||
await page.evaluate(
|
||||
() =>
|
||||
window.__godsEyeView.sceneDirector._project.scenes[0].title ===
|
||||
'Newer edit',
|
||||
),
|
||||
);
|
||||
await page.keyboard.press('Escape');
|
||||
await click('SHARE SCENE');
|
||||
await page.setViewport({ width: 390, height: 844 });
|
||||
check(
|
||||
'modal remains within a narrow viewport',
|
||||
await page.$eval('[data-director-dialog]', (n) => {
|
||||
const r = n.getBoundingClientRect();
|
||||
return (
|
||||
r.left >= 0 &&
|
||||
r.right <= innerWidth &&
|
||||
r.top >= 0 &&
|
||||
r.bottom <= innerHeight
|
||||
);
|
||||
}),
|
||||
);
|
||||
await page.screenshot({ path: path.join(output, 'narrow.png') });
|
||||
await page.keyboard.press('Escape');
|
||||
const pending = await page.evaluate(async () => {
|
||||
const d = window.__godsEyeView.sceneDirector;
|
||||
let finish;
|
||||
const work = d._sharing.preview({
|
||||
name: 'pending.json',
|
||||
text: () => new Promise((r) => (finish = r)),
|
||||
});
|
||||
d._sharing.close();
|
||||
await work;
|
||||
finish(JSON.stringify({ version: 6, scenes: [] }));
|
||||
return !d.getSharingState().open;
|
||||
});
|
||||
check(
|
||||
'cancel settles a pending file read and suppresses its late completion',
|
||||
pending,
|
||||
);
|
||||
const cancelledApply = await page.evaluate(async () => {
|
||||
const d = window.__godsEyeView.sceneDirector;
|
||||
const before = JSON.stringify(d._project);
|
||||
const saved = JSON.stringify({ ...localStorage });
|
||||
let finish;
|
||||
const pendingWork = new Promise((resolve) => (finish = resolve));
|
||||
d._pendingWork.add(pendingWork);
|
||||
const owner = new AbortController();
|
||||
const work = d.importProjectFile(
|
||||
{ name: 'cancelled.json' },
|
||||
{
|
||||
prepared: { project: { version: 6, scenes: [] }, assets: new Map() },
|
||||
expectedProject: JSON.stringify(d._project, null, 2),
|
||||
signal: owner.signal,
|
||||
},
|
||||
);
|
||||
owner.abort();
|
||||
finish();
|
||||
const applied = await work;
|
||||
d._pendingWork.delete(pendingWork);
|
||||
return (
|
||||
applied === false &&
|
||||
JSON.stringify(d._project) === before &&
|
||||
JSON.stringify({ ...localStorage }) === saved &&
|
||||
d.getSharingState().assets.count === 1
|
||||
);
|
||||
});
|
||||
check(
|
||||
'cancelling Apply during pending scene cleanup preserves saved project and bundle bytes',
|
||||
cancelledApply,
|
||||
);
|
||||
await click('EDIT DETAILS');
|
||||
await page.evaluate(() => {
|
||||
const fields = [
|
||||
...document.querySelectorAll('[data-director-dialog] textarea'),
|
||||
];
|
||||
const scene = JSON.parse(fields[0].value),
|
||||
shot = JSON.parse(fields[1].value);
|
||||
scene.dataPacks = [];
|
||||
shot.dataPackIds = [];
|
||||
shot.interactions = [];
|
||||
fields[0].value = JSON.stringify(scene);
|
||||
fields[1].value = JSON.stringify(shot);
|
||||
});
|
||||
await page.click('[data-director-apply-details]');
|
||||
await page.waitForFunction(
|
||||
() => !document.querySelector('[data-director-dialog]'),
|
||||
);
|
||||
check(
|
||||
'removing a pack through authoring releases its retained bytes',
|
||||
await page.evaluate(
|
||||
() =>
|
||||
window.__godsEyeView.sceneDirector.getSharingState().assets.count === 0,
|
||||
),
|
||||
);
|
||||
await page.evaluate(
|
||||
(text) =>
|
||||
window.__godsEyeView.sceneDirector.importProjectFile(
|
||||
new File([text], 'again.gevbundle.json'),
|
||||
),
|
||||
bundle,
|
||||
);
|
||||
await page.evaluate(() =>
|
||||
window.__godsEyeView.sceneDirector.importProjectFile(
|
||||
new File([JSON.stringify({ version: 6, scenes: [] })], 'empty.json'),
|
||||
),
|
||||
);
|
||||
check(
|
||||
'replacement releases stored bundle bytes',
|
||||
await page.evaluate(
|
||||
() =>
|
||||
window.__godsEyeView.sceneDirector.getSharingState().assets.count === 0,
|
||||
),
|
||||
);
|
||||
await page.evaluate(async () => {
|
||||
const d = window.__godsEyeView.sceneDirector;
|
||||
void d._sharing.preview({
|
||||
name: 'pending.json',
|
||||
text: () => new Promise(() => {}),
|
||||
});
|
||||
await d.destroy();
|
||||
});
|
||||
check(
|
||||
'teardown removes pending dialogs and authoring controls',
|
||||
await page.evaluate(
|
||||
() =>
|
||||
!document.querySelector('[data-director-dialog]') &&
|
||||
!document.querySelector('[data-director-authoring]') &&
|
||||
!window.__godsEyeView.sceneDirector.getSharingState().open,
|
||||
),
|
||||
);
|
||||
check('no uncaught browser errors', errors.length === 0);
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
process.exitCode = failures ? 1 : 0;
|
||||
@@ -154,7 +154,20 @@ try {
|
||||
const projectFile = path.join(shots, 'project.json');
|
||||
fs.writeFileSync(projectFile, JSON.stringify(fixture));
|
||||
const input = await page.$('#scene-import-file');
|
||||
const beforePreview = await page.evaluate(() =>
|
||||
JSON.stringify(window.__godsEyeView.sceneDirector._project),
|
||||
);
|
||||
await input.uploadFile(projectFile);
|
||||
await page.waitForSelector('[data-director-apply-import]');
|
||||
check(
|
||||
'Import preview leaves the current project untouched',
|
||||
await page.evaluate(
|
||||
(saved) =>
|
||||
JSON.stringify(window.__godsEyeView.sceneDirector._project) === saved,
|
||||
beforePreview,
|
||||
),
|
||||
);
|
||||
await page.click('[data-director-apply-import]');
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
document.getElementById('scene-status').textContent ===
|
||||
@@ -262,8 +275,8 @@ try {
|
||||
await input.uploadFile(badFile);
|
||||
await page.waitForFunction(() =>
|
||||
document
|
||||
.getElementById('scene-status')
|
||||
.textContent.includes('invalid JSON'),
|
||||
.querySelector('[data-director-dialog] [role=status]')
|
||||
?.textContent.includes('invalid JSON'),
|
||||
);
|
||||
check(
|
||||
'Invalid import reports failure and preserves the current project',
|
||||
@@ -281,7 +294,9 @@ try {
|
||||
fs.writeFileSync(futureFile, JSON.stringify({ version: 99, scenes: [] }));
|
||||
await input.uploadFile(futureFile);
|
||||
await page.waitForFunction(() =>
|
||||
document.getElementById('scene-status').textContent.includes('$.version'),
|
||||
document
|
||||
.querySelector('[data-director-dialog] [role=status]')
|
||||
?.textContent.includes('$.version'),
|
||||
);
|
||||
check(
|
||||
'Unsupported versions leave authored state and saved bytes unchanged',
|
||||
@@ -299,7 +314,9 @@ try {
|
||||
fs.writeFileSync(malformedFile, JSON.stringify(malformed));
|
||||
await input.uploadFile(malformedFile);
|
||||
await page.waitForFunction(() =>
|
||||
document.getElementById('scene-status').textContent.includes('camera.lat'),
|
||||
document
|
||||
.querySelector('[data-director-dialog] [role=status]')
|
||||
?.textContent.includes('camera.lat'),
|
||||
);
|
||||
check(
|
||||
'Invalid camera field identifies its path without replacing the project',
|
||||
@@ -308,6 +325,7 @@ try {
|
||||
savedBefore,
|
||||
),
|
||||
);
|
||||
await page.keyboard.press('Escape');
|
||||
await page.screenshot({ path: path.join(shots, 'desktop.png') });
|
||||
await page.setViewport({ width: 390, height: 844 });
|
||||
await page.screenshot({ path: path.join(shots, 'narrow.png') });
|
||||
|
||||
Reference in new issue
Block a user